diff --git a/AUTHORS b/AUTHORS index e6000c344..8361e2772 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,7 @@ This program is developed in the group of Sjors Scheres at the MRC Laboratory of Molecular Biology, with contributions from the following people (in alphabetical order): - Tom Burnley (from the CCP-EM team at STFC) +- Alister Burt (from David Barford's group at the MRC-LMB) - Liyi Dong - Bjoern Forsberg (from the Lindahl group at SciLifeLabs) - Shaoda He @@ -10,6 +11,7 @@ This program is developed in the group of Sjors Scheres at the MRC Laboratory of - Takanori Nakane - Joaquin Oton (from the Briggs group at MRC-LMB) - Colin Palmer (from the CCP-EM team at STFC) +- Euan Pyle (from Giulia Zanetti's group at Birkbeck) - Sjors Scheres - Jasenko Zivanov diff --git a/CMakeLists.txt b/CMakeLists.txt index dbfa3c85b..606fce049 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ if(POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() -# Add the path to the additional Find.cmake files +# Add the path to the additional Find.cmake files # which are included with the distributed RLEION-code list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -45,17 +45,17 @@ endif(NOT APPLE) # ---------------------------------------------------------SET SPECIFIC BUILD TYPE-- if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "") string( TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER ) - + if( ( NOT ${CMAKE_BUILD_TYPE_LOWER} STREQUAL "none" ) AND - ( NOT ${CMAKE_BUILD_TYPE_LOWER} STREQUAL "release" ) AND + ( NOT ${CMAKE_BUILD_TYPE_LOWER} STREQUAL "release" ) AND ( NOT ${CMAKE_BUILD_TYPE_LOWER} STREQUAL "debug" ) AND - ( NOT ${CMAKE_BUILD_TYPE_LOWER} STREQUAL "relwithdebinfo" ) AND - ( NOT ${CMAKE_BUILD_TYPE_LOWER} STREQUAL "profiling" ) AND + ( NOT ${CMAKE_BUILD_TYPE_LOWER} STREQUAL "relwithdebinfo" ) AND + ( NOT ${CMAKE_BUILD_TYPE_LOWER} STREQUAL "profiling" ) AND ( NOT ${CMAKE_BUILD_TYPE_LOWER} STREQUAL "benchmarking" ) ) message( FATAL_ERROR "CMAKE_BUILD_TYPE : '${CMAKE_BUILD_TYPE}' is not a valid build type. " "Valid options are: 'None', 'Release', 'Debug', 'RelWithDebInfo', and 'Profiling'." ) endif() - + message(STATUS "BUILD TYPE set to '${CMAKE_BUILD_TYPE}'") SET(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "Choose the type of build, options are: 'None', 'Release', 'Debug', 'RelWithDebInfo', and 'Profiling'.") @@ -70,26 +70,75 @@ else() string( TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER ) endif() +# ----------------------------------------------------------------SET CXX STANDARD-- +set(CXX_STANDARD_REQUIRED ON) +if(SYCL) + set(CMAKE_CXX_STANDARD 17) +else() + set(CMAKE_CXX_STANDARD 14) +endif() +set(CMAKE_CXX_EXTENSIONS OFF) + # ------------------OPTIONS WHICH ARE NEEDED TO SET BUILD-TYPES (COMPILATION FLAGS)-- -# ------------------------------------------------------------------------CUDA-ARCH-- -if(NOT DEFINED CUDA_ARCH) - message(STATUS "Setting fallback CUDA_ARCH=50") - set(CUDARCH "-arch=sm_50") -else(NOT DEFINED CUDA_ARCH) - message(STATUS "Using provided CUDA_ARCH=${CUDA_ARCH}") - set(CUDARCH "-arch=sm_${CUDA_ARCH}") -endif(NOT DEFINED CUDA_ARCH) - -# -------------------------------------------------------------------FURTHER OPTIONS-- +# ----------------------------------------------------------CUDA-ARCH-- + +set(CUDA_ARCH "" CACHE STRING "CUDA architecture to use") +if (CUDA_ARCH STREQUAL "") + message(STATUS "Setting fallback CUDA_ARCH=50") + set(CUDARCH "-arch=sm_50") +else () + message(STATUS "Using provided CUDA_ARCH=${CUDA_ARCH}") + set(CUDARCH "-arch=sm_${CUDA_ARCH}") +endif () +# -------------------------------------------------------------------FURTHER OPTIONS-- -# CUDA on by default, so check for CPU-accelration request and possible conflicting dual-request +# CUDA on by default, so check for other GPU/CPU-accelration request and possible conflicting dual-requests +option(HIP "Enable HIP GPU acceleration" OFF) +option(SYCL "Enable SYCL acceleration" OFF) option(ALTCPU "Enable Accelerated CPU version" OFF) -if(ALTCPU) +if(HIP) + set(HIP_ARCH "" CACHE STRING "HIP architecture to use") + if (HIP_ARCH STREQUAL "") + message(STATUS "Setting fallback HIP_ARCH=gfx906") + set(AMDGPU_TARGETS "gfx906" CACHE STRING + "Semicolon delimited list of AMD GPU targets to compile for (e.g gf908;gfx90a)") + else () + message(STATUS "Using provided HIP_ARCH=${HIP_ARCH}") + set(AMDGPU_TARGETS "${HIP_ARCH}" CACHE STRING + "Semicolon delimited list of AMD GPU targets to compile for (e.g gf908;gfx90a)") + endif () option(CUDA "Enable CUDA GPU acceleration" OFF) - if(ALTCPU AND CUDA) - message(FATAL_ERROR "You cannot build with both CUDA=ON and ALTCPU=ON. Please choose one and rerun CMAKE") + if(HIP AND CUDA) + message(FATAL_ERROR "You cannot build with both CUDA=ON and HIP=ON. Please choose one and rerun CMAKE") endif() +elseif(SYCL) + option(CUDA "Enable CUDA GPU acceleration" OFF) + if(SYCL AND CUDA) + message(FATAL_ERROR "You cannot build with both CUDA=ON and SYCL=ON. Please choose one and rerun CMAKE") + endif() + + if(DEFINED SYCL_CUDA_COMPILE) + if(DEFINED SYCL_CUDA_TARGET) + message(STATUS "Using provided SYCL_CUDA_TARGET: sm_${SYCL_CUDA_TARGET}") + else(DEFINED SYCL_CUDA_TARGET) + message(STATUS "Setting fallback SYCL_CUDA_TARGET: sm_50") + set(SYCL_CUDA_TARGET "50") + endif(DEFINED SYCL_CUDA_TARGET) + endif(DEFINED SYCL_CUDA_COMPILE) + if(DEFINED SYCL_HIP_COMPILE) + if(DEFINED SYCL_HIP_TARGET) + message(STATUS "Using provided SYCL_HIP_TARGET: ${SYCL_HIP_TARGET}") + else(DEFINED SYCL_HIP_TARGET) + message(STATUS "Setting fallback SYCL_HIP_TARGET: gfx906") + set(SYCL_HIP_TARGET "gfx906") + endif(DEFINED SYCL_HIP_TARGET) + endif(DEFINED SYCL_HIP_COMPILE) +elseif(ALTCPU) + option(CUDA "Enable CUDA GPU acceleration" OFF) + if(ALTCPU AND CUDA) + message(FATAL_ERROR "You cannot build with both CUDA=ON and ALTCPU=ON. Please choose one and rerun CMAKE") + endif() else() option(CUDA "Enable CUDA GPU acceleration" ON) endif() @@ -97,23 +146,27 @@ endif() option(DoublePrec_CPU "DoublePrec_CPU" ON) option(DoublePrec_ACC "Accelerated Code use double-precision" OFF) option(MKLFFT "Use MKL rather than FFTW for FFT" OFF) -option(CudaTexture "CudaTexture" ON) +option(DeviceTexture "Texture Memory on the Device" ON) if(ALTCPU) message(STATUS "ALTCPU enabled - Building CPU-accelerated version of RELION") -endif() - -if(CUDA) +elseif(SYCL) + message(STATUS "SYCL enabled - Building SYCL-accelerated version of RELION") +elseif(HIP) + message(STATUS "HIP enabled - Building HIP-accelerated version of RELION") +elseif(CUDA) message(STATUS "CUDA enabled - Building CUDA-accelerated version of RELION") +else() + message(STATUS "Acceleration not enabled - Building CPU-only version of RELION") endif() if(CUDA OR ALTCPU) - add_definitions(-DACC_CUDA=2 -DACC_CPU=1) + add_definitions(-DACC_HIP=3 -DACC_CUDA=2 -DACC_CPU=1) endif() -# -----------------------------------------------DOUBLE PRECISION (CUDA-CODE) OR NOT-- +# -----------------------------------------------DOUBLE PRECISION (GPU-CODE) OR NOT-- if(DoublePrec_CPU) - message(STATUS "Setting cpu precision to double") + message(STATUS "Setting cpu precision to double") else(DoublePrec_CPU) message(STATUS "Setting cpu precision to single") add_definitions(-DRELION_SINGLE_PRECISION) @@ -122,7 +175,7 @@ endif(DoublePrec_CPU) if(DoublePrec_ACC) message(STATUS "Setting accelerated code precision to double") add_definitions(-DACC_DOUBLE_PRECISION) - set(CudaTexture FALSE) + set(DeviceTexture FALSE) else(DoublePrec_ACC) message(STATUS "Setting accelerated code precision to single") endif(DoublePrec_ACC) @@ -132,37 +185,85 @@ if(MDT_TYPE_CHECK) add_definitions(-DMETADATA_TABLE_TYPE_CHECK) endif() -# ----------------------------------------------------------INCLUDE ALL BUILD TYPES-- - #This *has* to be AFTER project() -include(${CMAKE_SOURCE_DIR}/cmake/BuildTypes.cmake) - if(CUDA) # -----------------------------------------------------------------------------CUDA-- # DOC: http://www.cmake.org/cmake/help/v3.0/module/FindCUDA.html FIND_PACKAGE(CUDA) -endif() -if(CUDA_FOUND) - message(STATUS "Using cuda wrapper to compile....") - if( (NOT ${CUDA_VERSION} VERSION_LESS "7.5") AND (NOT DoublePrec_ACC) ) - message(STATUS "Cuda version is >= 7.5 and single-precision build, enable double usage warning.") - set(WARN_DBL "--ptxas-options=-warn-double-usage") # cuda>=7.5 - elseif( ${CUDA_VERSION} VERSION_LESS "7.0") - message(WARNING "Cuda version is less than 7.0, so relion will be compiled without GPU support.") - set(CUDA OFF) - endif() - - if(CUDA) - add_definitions(-D_CUDA_ENABLED) + if(CUDA_FOUND) + message(STATUS "Using cuda wrapper to compile....") + if( (NOT ${CUDA_VERSION} VERSION_LESS "7.5") AND (NOT DoublePrec_ACC) ) + message(STATUS "Cuda version is >= 7.5 and single-precision build, enable double usage warning.") + set(WARN_DBL "--ptxas-options=-warn-double-usage") # cuda>=7.5 + elseif( ${CUDA_VERSION} VERSION_LESS "7.0") + message(WARNING "Cuda version is less than 7.0, so relion will be compiled without GPU support.") + message(STATUS "Using non-cuda compilation....") + set(CUDA OFF) + endif() + if(CUDA) + add_definitions(-D_CUDA_ENABLED) + endif() + else(CUDA_FOUND) + message(FATAL_ERROR "CUDA enabled but unlable to locate packages...") + endif(CUDA_FOUND) +elseif(HIP) + # ------------------------------------------------------------------------------HIP-- + if (DEFINED ENV{ROCM_PATH}) + include($ENV{ROCM_PATH}/hip/cmake/FindHIP.cmake) + elseif(DEFINED ENV{HIP_PATH}) + include($ENV{HIP_PATH}/cmake/FindHIP.cmake) + elseif(EXISTS "${CMAKE_SOURCE_DIR}/cmake/FindHIP.cmake") + include(${CMAKE_SOURCE_DIR}/cmake/FindHIP.cmake) + else() + include(/opt/rocm/hip/cmake/FindHIP.cmake) endif() -else(CUDA_FOUND) - message(STATUS "Using non-cuda compilation....") -endif(CUDA_FOUND) + find_package(HIP REQUIRED) + + if(HIP_FOUND) + message(STATUS "Using hip wrapper to compile....") + if( ${HIP_VERSION} VERSION_LESS "5.0") + message(WARNING "ROCm version is less than 5.0, so relion will be compiled without GPU support.") + set(HIP OFF) + endif() + if(HIP) + add_definitions(-D_HIP_ENABLED) + foreach(hiplib hipfft hipcub rocfft rocprim hiprand rocrand) + find_package(${hiplib} REQUIRED) + if(${hiplib}_FOUND) + message(STATUS "Found ${${hiplib}_LIBRARIES} in ${${hiplib}_INCLUDE_DIR}") + endif() + endforeach() + endif() + else(HIP_FOUND) + message(FATAL_ERROR "HIP enabled but unlable to locate packages. ROCm >= 5.0 is required to configure RELION with HIP using CMake.") + endif(HIP_FOUND) +elseif(SYCL) + add_definitions(-D_SYCL_ENABLED=1 -DUSE_MPI_COLLECTIVE) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "IntelLLVM" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER}" MATCHES "mpiicpx") + add_definitions(-D_DPCPP_ENABLED=1) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsigned-zeros") + endif() +elseif(ALTCPU) + add_definitions(-DALTCPU=1 -DUSE_MPI_COLLECTIVE) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "IntelLLVM" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsigned-zeros") + endif() +endif() + +# ----------------------------------------------------------INCLUDE ALL BUILD TYPES-- + #This *has* to be AFTER project() + include(${CMAKE_SOURCE_DIR}/cmake/BuildTypes.cmake) + # ------------------------------------------------------------------ALLOCATOR CHOICE-- option(CachedAlloc "CachedAlloc" ON) if(NOT CachedAlloc) - add_definitions(-DCUDA_NO_CUSTOM_ALLOCATION) + if (CUDA) + add_definitions(-DCUDA_NO_CUSTOM_ALLOCATION) + endif() + if (HIP) + add_definitions(-DHIP_NO_CUSTOM_ALLOCATION) + endif() message(STATUS "Cached allocation is disabled.") endif(NOT CachedAlloc) option(CustomAllocMemGuards "CustomAllocMemGuards" OFF) @@ -171,12 +272,30 @@ if(CustomAllocMemGuards) message(STATUS "Abort on out of bound write.") endif(CustomAllocMemGuards) # -------------------------------------------------------------FORCE USE OF STL-LIBS-- -option(CudaForceSTL "CudaForceSTL" OFF) -if(CudaForceSTL) - add_definitions(-DCUDA_FORCESTL) - message(STATUS "Building cuda files wusing stl-libs for sort, min and max.") -endif(CudaForceSTL) - +if (CUDA) + option(CudaForceSTL "CudaForceSTL" OFF) + if(CudaForceSTL) + add_definitions(-DCUDA_FORCESTL) + message(STATUS "Building cuda files using stl-libs for sort, min and max.") + endif(CudaForceSTL) +elseif (HIP) + option(HipForceSTL "HipForceSTL" OFF) + if(HipForceSTL) + add_definitions(-DHIP_FORCESTL) + message(STATUS "Building hip files using stl-libs for sort, min and max.") + endif(HipForceSTL) +elseif (SYCL) + option(SyclForceOneDPL "SyclForceOneDPL" OFF) + if(SyclForceOneDPL) + add_definitions(-DUSE_ONEDPL) + if(EXISTS "$ENV{DPL_ROOT}/linux/include/oneapi/dpl/algorithm") + include_directories("$ENV{DPL_ROOT}/linux/include") + message(STATUS "Building SYCL files using oneDPL: $ENV{DPL_ROOT}/linux/include") + else() + message(WARNING "oneDPL header is not found. Please set DPL_ROOT environment or include oneDPL header directory for CMAKE_CXX_FLAGS") + endif() + endif(SyclForceOneDPL) +endif() # ------------------------------------------------------------------------GUI OR NOT-- # Skip FLTK/X11-dependent binaries or not option(GUI "GUI" ON) @@ -189,7 +308,6 @@ endif() option(FORCE_OWN_TBB "FORCE_OWN_TBB" OFF) if (ALTCPU) - if (FORCE_OWN_TBB) message(STATUS "Will ignore any potentially installed system TBB lib, as per your request.") include(${CMAKE_SOURCE_DIR}/cmake/BuildTBB.cmake) @@ -232,55 +350,77 @@ message(STATUS "MPI_CXX_COMPILER : ${MPI_CXX_COMPILER}") SET(CMAKE_C_COMPILER ${MPI_C_COMPILER}) SET(CMAKE_CXX_COMPILER ${MPI_CXX_COMPILER}) +if(HIP) + set(CMAKE_CXX_COMPILER hipcc) + message(STATUS "CMAKE_CXX_COMPILER : ${CMAKE_CXX_COMPILER}") +elseif(SYCL) +# set(CMAKE_C_COMPILER icx) +# set(CMAKE_CXX_COMPILER icpx) + message(STATUS "CMAKE_C_COMPILER : ${CMAKE_C_COMPILER}") + message(STATUS "CMAKE_CXX_COMPILER : ${CMAKE_CXX_COMPILER}") +endif() set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # ----------------------------------------------------------Intel Compiler support -- -# ---------------------------------- and build flags including MKL and TBB --------- +# ---------------------------------- and build flags including MKL and IPP --------- message(STATUS "CMAKE_CXX_COMPILER_ID : ${CMAKE_CXX_COMPILER_ID}") if(MKLFFT) - if (NOT "$ENV{MKLROOT}" STREQUAL "") - include_directories("$ENV{MKLROOT}/include/fftw") - message(STATUS "MKL FFTW wrapper header files: $ENV{MKLROOT}/include/fftw") - else() - message("COMPILATION MAY FAIL since no MKL FFTW wrapper header files could be found. Please make sure the MKLROOT environmental variable is set.") - endif() - add_definitions(-DMKLFFT) + if (NOT "$ENV{MKLROOT}" STREQUAL "") + include_directories("$ENV{MKLROOT}/include/fftw") + message(STATUS "MKL FFTW wrapper header files: $ENV{MKLROOT}/include/fftw") + else() + message(WARNING "COMPILATION MAY FAIL since no MKL FFTW wrapper header files could be found. Please make sure the MKLROOT environmental variable is set.") + endif() + add_definitions(-DMKLFFT) endif(MKLFFT) -if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel" OR "${CMAKE_CXX_COMPILER}" MATCHES "icpx") +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel" OR "${CMAKE_CXX_COMPILER}" MATCHES "mpiicpx") # "Intel" is for classic Intel compiler and "IntelLLVM" is for oneAPI compiler which is supported from CMake 3.20 - if(MKLFFT) - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -qopenmp -mkl=parallel -limf ") - else() # Intel oneAPI compiler - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fiopenmp -qmkl=parallel -limf ") - endif() - endif(MKLFFT) - if(ALTCPU) - add_definitions(-DFAST_CENTERFFT) - endif() -else() - if(MKLFFT) - # For the time being, let's use the sequential version (as with FFTW) - link_directories("$ENV{MKLROOT}/lib/intel64") - SET(FFTW_LIBRARIES mkl_intel_lp64 mkl_sequential mkl_core) - endif(MKLFFT) -endif() + if(ALTCPU OR SYCL) + add_definitions(-DFAST_CENTERFFT) + endif() -if(ALTCPU) - add_definitions(-DALTCPU) + if(MKLFFT) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "IntelLLVM") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -qopenmp -qmkl=parallel -limf") + else() + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -diag-disable=10441,10412") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -qopenmp -mkl=parallel -limf -diag-disable=10441,10412") + endif() + endif(MKLFFT) + + if(INTEL_IPP OR DEFINED ENV{IPPROOT}) + if(EXISTS "$ENV{IPPROOT}/include/ipps.h" AND EXISTS "$ENV{IPPROOT}/lib/intel64/libipps.a") + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "IntelLLVM") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -qipp") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -qipp -qipp-link=static") + else() + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -diag-disable=10441,10412") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ipp -diag-disable=10441,10412") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -ipp -ipp-link=static") + endif() + add_definitions(-DUSE_IPP) + message(STATUS "Using Intel IPP library: $ENV{IPPROOT}") + endif() + endif() +else() + if(MKLFFT) + # For the time being, let's use the sequential version (as with FFTW) + link_directories("$ENV{MKLROOT}/lib/intel64") + SET(FFTW_LIBRARIES mkl_intel_lp64 mkl_sequential mkl_core) + endif(MKLFFT) endif() # ---------------------------------------------------------------USE TEXTURES OR NOT-- -if(NOT CudaTexture OR ALTCPU) +if(NOT DeviceTexture OR ALTCPU OR SYCL) add_definitions(-DPROJECTOR_NO_TEXTURES) message(STATUS "Texture interpolation is omitted.") -endif(NOT CudaTexture OR ALTCPU) +endif() # --------------------------------------------------------------------------X11/FLTK-- option(FORCE_OWN_FLTK "FORCE_OWN_FLTK" OFF) @@ -296,9 +436,9 @@ if(GUI) message(STATUS "FLTK_LIBRARIES: ${FLTK_LIBRARIES}") else() message(STATUS "No FLTK installation was found") - endif() + endif() endif(NOT FORCE_OWN_FLTK) - + if(NOT FLTK_FOUND) include(${CMAKE_SOURCE_DIR}/cmake/BuildFLTK.cmake) set(INSTALL_OWN_FLTK 1) @@ -310,7 +450,7 @@ if(GUI) message( STATUS " You CAN add the flag -DGUI=OFF to avoid using X11" ) message(FATAL_ERROR "X11 is required for GUI.") endif(X11_FOUND) - + endif(GUI) # -------------------------------------------------------------------------------FFT-- @@ -318,10 +458,10 @@ endif(GUI) if(NOT MKLFFT) option(FORCE_OWN_FFTW "FORCE_OWN_FFTW" OFF) option(AMDFFTW "Use AMD optimized version of FFTW. This needs a new version of GCC (>= 8.3 recommended)." OFF) - + set(FFTW_DOUBLE_REQUIRED TRUE) set(FFTW_SINGLE_REQUIRED TRUE) - + if(AMDFFTW) set(FORCE_OWN_FFTW ON) endif() @@ -355,26 +495,73 @@ if(TIFF_FOUND) add_definitions(-DHAVE_TIFF) endif() +# ------------------------------------------------------------------ZLIB, PNG & JPEG-- find_package(ZLIB) find_package(PNG) if(PNG_FOUND) add_definitions(-DHAVE_PNG) -endif() - - -# -----------------------------------------------------------------------------Torch-- +endif(PNG_FOUND) + +find_package(JPEG) +if(JPEG_FOUND) + add_definitions(-DHAVE_JPEG) +endif(JPEG_FOUND) + +# -----------------------------------------------------------------PYTHON DEPENDENCIES-- + + +set(CONDA_ENV_NAME "relion-5.0" CACHE STRING "The Conda environment name") +include(${CMAKE_SOURCE_DIR}/cmake/FindCondaPython.cmake) + +list(APPEND RELION_PYTHON_WRAPPERS + python_classranker + python_blush + python_dynamight + python_topaz + python_modelangelo + python_fetch_weights + python_tomo_import + python_tomo_exclude_tilt_images + python_tomo_align_tilt_series + python_tomo_view + python_tomo_pick + python_tomo_get_particle_poses + python_tomo_denoise + filament_selection +) -#option(TORCH "Enable support for LibTorch" OFF) -#if(TORCH) -# message(STATUS "Torch support requested by user.") -# include(${CMAKE_SOURCE_DIR}/cmake/BuildTorch.cmake) -# add_definitions(-D_TORCH_ENABLED) -#endif(TORCH) +foreach (SCRIPT_FILE ${RELION_PYTHON_WRAPPERS}) + configure_file( + ${CMAKE_SOURCE_DIR}/scripts/${SCRIPT_FILE}.in + ${CMAKE_BINARY_DIR}/bin/relion_${SCRIPT_FILE} + @ONLY + ) +endforeach() -option(FETCH_TORCH_MODELS "Do download pre-trained torch models" ON) -if (FETCH_TORCH_MODELS) - include(${CMAKE_SOURCE_DIR}/cmake/FetchTorchModels.cmake) -endif() +option(FETCH_WEIGHTS "Download model weights for dependent packages" ON) +if (FETCH_WEIGHTS) + message(STATUS "Will attempts to download model weights for dependent packages...") + + execute_process( + COMMAND bash ${CMAKE_BINARY_DIR}/bin/relion_python_fetch_weights + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE SCRIPT_RESULT + ) + + # Check the result of the script execution + if(SCRIPT_RESULT EQUAL 0) + message(STATUS "Successfully downloaded model weights") + else() + message(WARNING + "Failed to download model weights." + "You could ignore this." + "The downloads will be attempted again when the model weights are required later." + "NOTE: You can skip this step by setting -DFETCH_WEIGHTS=OFF" + ) + endif() +else() + message(STATUS "Omitting download of model weights for dependent packages") +endif () # ----------------------------------------------------------------------COPY SCRIPTS-- @@ -392,15 +579,14 @@ list(APPEND RELION_SCRIPT_FILES qsub.csh it.py schemegui.py - class_ranker.py ) add_custom_target(copy_scripts ALL) - + foreach (SCRIPT_FILE ${RELION_SCRIPT_FILES}) add_custom_command(TARGET copy_scripts POST_BUILD COMMAND ${CMAKE_COMMAND} -E - copy ${CMAKE_SOURCE_DIR}/scripts/${SCRIPT_FILE} + copy ${CMAKE_SOURCE_DIR}/scripts/${SCRIPT_FILE} ${CMAKE_BINARY_DIR}/bin/relion_${SCRIPT_FILE} ) endforeach() @@ -416,11 +602,11 @@ endif() # -----------------------------------------------------------------RELION COMPONENTS-- option(BUILD_SHARED_LIBS "BUILD_SHARED_LIBS" OFF) -message("BUILD_SHARED_LIBS = ${BUILD_SHARED_LIBS}") +#message(STATUS "BUILD_SHARED_LIBS = ${BUILD_SHARED_LIBS}") if(BUILD_SHARED_LIBS) message(STATUS "Building shared libs (smaller build size and binaries)") else() - message(STATUS "Building static libs (larger build size and binaries)") + message(STATUS "Building static libs (larger build size and binaries)") endif() @@ -428,10 +614,12 @@ ADD_SUBDIRECTORY(src/apps) #message(STATUS "CUDA option = ${CUDA}") #message(STATUS "ALTCPU option = ${ALTCPU}") +#message(STATUS "HIP option = ${HIP}") +#message(STATUS "SYCL option = ${SYCL}") #message(STATUS "DoublePrec_CPU option = ${DoublePrec_CPU}") #message(STATUS "DoublePrec_ACC option = ${DoublePrec_ACC}") #message(STATUS "MKLFFT option = ${MKLFFT}") -#message(STATUS "CudaTexture option = ${CudaTexture}") +#message(STATUS "DeviceTexture option = ${DeviceTexture}") #get_directory_property( DirDefs COMPILE_DEFINITIONS ) #message(STATUS "COMPILE_DEFINITIONS = ${DirDefs}" ) @@ -444,7 +632,7 @@ ADD_SUBDIRECTORY(src/apps) #message(STATUS "CMAKE_EXE_LINKER_FLAGS : ${CMAKE_EXE_LINKER_FLAGS}") # -----------------------------------------------------------------------------TESTS-- -# Include testing flag(s) as precomiler +# Include testing flag(s) as precomiler # definitions and include test directives #enable_testing() #include(${CMAKE_SOURCE_DIR}/tests/RelionTests.cmake) @@ -460,4 +648,3 @@ endif() #foreach (_variableName ${_variableNames}) # message(STATUS "${_variableName}=${${_variableName}}") #endforeach() - diff --git a/README.md b/README.md index 8033bd7ca..70ef51994 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,23 @@ -RELION 4.0.1 -============ +RELION 5.0-beta +=============== RELION (for REgularised LIkelihood OptimisatioN) is a stand-alone computer program for Maximum A Posteriori refinement of (multiple) 3D reconstructions or 2D class averages in cryo-electron microscopy. It is developed in the research group of Sjors Scheres at the MRC Laboratory of Molecular Biology. -The underlying theory of MAP refinement is given in a [scientific publication](https://www.ncbi.nlm.nih.gov/pubmed/22100448). -If RELION is useful in your work, please cite this paper. +If RELION is useful in your work, please cite our papers. -The more comprehensive documentation of RELION is stored [here](https://relion.readthedocs.io/en/release-4.0/). +Comprehensive documentation of RELION and tutorials are stored [here](https://relion.readthedocs.io/). ## Installation -More extensive options and configurations are available [here](https://relion.readthedocs.io/en/release-4.0/Installation.html), -but the outlines to clone and install relion for typical use are made easy through [cmake](https://en.wikipedia.org/wiki/CMake). - -On Debian or Ubuntu machines, installing cmake, the compiler, and additional dependencies (mpi, fftw) is as easy as: - -``` -sudo apt install cmake git build-essential mpi-default-bin mpi-default-dev libfftw3-dev libtiff-dev libpng-dev ghostscript libxft-dev -``` - -RedHat-like systems (CentOS, RHEL, Scientific Linux etc) use `yum` package manager: - -``` -sudo yum install cmake git gcc gcc-c++ openmpi-devel fftw-devel libtiff-devel libpng-devel ghostscript libXft-devel libX11-devel -``` - -Once git and cmake are installed, relion can be easily installed through: - -``` -git clone https://github.com/3dem/relion.git -cd relion -git checkout master # or ver4.0; see below -mkdir build -cd build -cmake .. -make -``` - -By performing `git checkout ver4.0` instead of `git checkout master`, you can access the latest -(developmental) updates for RELION 4.0.x. The code there is not tested as throughfully as that in -the master branch and not generally recommended. - -The binaries will be produced in the `build/bin` directory. If you want to copy binaries -into somewhere else, run `cmake` with `-DCMAKE_INSTALL_PREFIX=/where/to/install/` and -perform `make install` as the final step. Do not specify the build directory itself -as `CMAKE_INSTALL_PREFIX`! This will not work. - -Also note that the MPI library used for compilation must be the one you intend to use RELION with. -Compiling RELION with one version of MPI and running the resulting binary with mpirun from another -version can cause crash. See our wiki below for details. - -In any case, you have to make sure your PATH environmental variable points to the directory -containing relion binaries. Launching RELION as `/path/to/relion` is NOT a right way; this -starts the right GUI, but the GUI might invoke other versions of RELION in the PATH. - -If FLTK related errors are reported, please add `-DFORCE_OWN_FLTK=ON` to -`cmake`. For FFTW related errors, try `-DFORCE_OWN_FFTW=ON`. - -RELION also requires libtiff. Most Linux distributions have packages like `libtiff-dev` or `libtiff-devel`. -Note that you need a developer package. You need version 4.0.x to read BigTIFF files. If you installed -libtiff in a non-standard location, specify the location by -`-DTIFF_INCLUDE_DIR=/path/to/include -DTIFF_LIBRARY=/path/to/libtiff.so.5`. - -## Updating - -RELION is intermittently updated, with both minor and major features. -To update an existing installation, simply use the following commands - -``` -cd relion -git pull -cd build -make -make install # Only when you have specified CMAKE_INSTALL_PREFIX in the cmake step -``` - -If something went wrong, remove the `build` directory and try again from `cmake`. +See our [installation instructions](https://relion.readthedocs.io/en/release-5.0/Installation.html). +You will have to set up a Python environment to use Python modules (e.g. Blush, ModelAngelo and DynaMight). +Thus, please read the above instructions carefully even if you are familiar with earlier versions. ## Class Ranker + The default model for the class ranker has been trained and tested in Python 3.9.12 with Pytorch 1.10.0 and Numpy 1.20.0. If you wish to retrain the class ranker model with your own data, please refer to [this repo](https://github.com/3dem/relion-classranker). diff --git a/README_sycl.md b/README_sycl.md new file mode 100644 index 000000000..9c497185a --- /dev/null +++ b/README_sycl.md @@ -0,0 +1,98 @@ +# RELION SYCL/DPC++ version + +This is SYCL/DPC++ version for [RELION](https://github.com/3dem/relion) + +## Build & Running + ++ Additional requirements for RELION SYCL/DPC++ version + + Intel(R) oneAPI Base Toolkit and HPC Toolkit (Installing all components are recommended. - https://www.intel.com/content/www/us/en/developer/tools/oneapi/toolkits.html) + + Intel(R) software for general purpose GPU capabilities (https://dgpu-docs.intel.com) + + Intel(R) CPU Runtime for OpenCL(TM) Applications (optional - https://www.intel.com/content/www/us/en/developer/articles/technical/intel-cpu-runtime-for-opencl-applications-with-sycl-support.html) + + Codeplay(R) oneAPI for NVIDIA(R) GPU (optional - https://developer.codeplay.com/products/oneapi/nvidia/) + + Codeplay(R) oneAPI for AMD GPU (optional - https://developer.codeplay.com/products/oneapi/amd/) + ++ SYCL specific command line arguments + + `--gpu`, `--sycl` or `--sycl-levelzero`: This is for Intel(R) GPU specific Level Zero backend (Recommended for Intel GPU) + + `--sycl-opencl`: This is for OpenCL(TM) GPU backend + + `--sycl-cpu`: This is for OpenCL(TM) CPU backend + + `--sycl-cuda`: This is for CUDA(R) backend over SYCL (Not tested) + + `--sycl-hip`: This is for HIP backend over SYCL (Not tested) + + If you need finer control of SYCL devices, you can set `ONEAPI_DEVICE_SELECTOR` environment variable. For detailed description, please look at https://intel.github.io/llvm-docs/EnvironmentVariables.html#oneapi-device-selector + + +```bash +$ mkdir build_sycl; cd build_sycl +$ {Load Intel oneAPI toolkit and SYCL/Level Zero/OpenCL runtime environment} +$ sycl-ls # This will display available SYCL devices +$ cmake \ +-DCMAKE_C_COMPILER=mpiicx \ +-DCMAKE_CXX_COMPILER=mpiicpx \ +-DMPI_C_COMPILER=mpiicx \ +-DMPI_CXX_COMPILER=mpiicpx \ +-DMKLFFT=ON \ +-DSYCL=ON \ +-DCUDA=OFF \ +-DCMAKE_BUILD_TYPE=Release \ +-DCMAKE_C_FLAGS="-O3" \ +-DCMAKE_CXX_FLAGS="-O3 -march=native" \ +.. + +$ #### This is Intel GPU Level Zero backend specific ##### +$ export ZE_AFFINITY_MASK=0 # Use only the first available Level Zero device. This can be replaced by --gpu 0 syntax. +$ export ZEX_NUMBER_OF_CCS=0:4,1:4 # Set this only if you are putting more than one MPI ranks per GPU. 0:4 means 4 MPI ranks running on card 0 +$ export SYCL_PI_LEVEL_ZERO_USE_IMMEDIATE_COMMANDLISTS=2 +$ export SYCL_PI_LEVEL_ZERO_DEVICE_SCOPE_EVENTS=0 +$ export SYCL_PI_LEVEL_ZERO_USM_ALLOCATOR="1;4G;host:16M,512,64K;device:16M,1024,64K;shared:0,0,64K" +$ #### End of Intel GPU Level Zero backend specific ##### +$ # For finer control of SYCL devcices, please see the above descrpition on ONEAPI_DEVICE_SELECTOR +$ +$ ulimit -n 512000 # this is necessary for multi-GPU jobs +$ {Run 2D/3D/refinement application by replacing --gpu/--cpu with --gpu/--sycl/--sycl-opencl/--sycl-cpu/--sycl-cuda/--sycl-hip} +``` + +## Optional runtime environment variables + ++ The below shell environment variables can be tested for more potential SYCL specific tuning. Setting it to "1" or "on" will enable these features. + + `relionSyclUseCuda`: --sycl-cuda will be used even if --gpu/--sycl is specified in command lines + + `relionSyclUseHip`: --sycl-hip will be used even if --gpu/--sycl is specified in command lines + + `relionSyclUseInOrderQueue`: Use in-order SYCL queue. Without this, out-of-order SYCL queue is used by default. (experimental) + + `relionSyclUseAsyncSubmission`: Remove wait() for each SYCL kernel submission. (experimental) + + `relionSyclUseStream`: Create new in-order SYCL queue for each cudaStream. (experimental) + + `relionSyclUseSubSubDevice`: Create separate SYCL queue for each CCS. (experimental) + + +## Added macros + ++ For CMake configuration + + `SYCL`(=ON/OFF): Enable SYCL based acceleration build + + `SyclForceOneDPL`(=ON/OFF): Use oneDPL(https://github.com/oneapi-src/oneDPL) if it can be used. This has the same effect as setting "-DUSE_ONEDPL" for CMAKE_CXX_FLAGS below. (experimental) + + `SYCL_AOT_COMPILE`(=ON/OFF): Enable AOT(Ahead-Of-Time) compilation for SPIR64 target. Default target is pvc. (for future use) + + `SYCL_AOT_TARGET`(=ON/OFF): Specify AOT(Ahead-Of-Time) SPIR64 target. Possible list can be checked using "ocloc compile --help" command. (for future use) + + `SYCL_CUDA_COMPILE`(=ON/OFF): Enable SYCL compilation for CUDA target (Not tested) + + `SYCL_CUDA_TARGET`: SYCL CUDA arch target (Not tested) + + `SYCL_HIP_COMPILE`(=ON/OFF): Enable SYCL compilation for HIP target (Not tested) + + `SYCL_HIP_TARGET`: SYCL HIP arch target (Not tested) + + `SYCL_HOST_FLAGS`(=list of flags with space as separator): Additional flags for host compiler (for future use) + + `SYCL_COMPILER_NAME`: SYCL compiler command name (for future use) + + `SYCL_COMPILE_FLAGS`: Additional SYCL compile flags (for future use) + + `SYCL_LINK_FLAGS`: SYCL link flags except "-lsycl -lOpenCL" if needed (for future use) + ++ Others for testing purpose (just defining with -D* is needed in cmake -DCMAKE_CXX_FLAGS) + + `USE_ONEDPL`: Use SYCL kernel for weight sorting routines. If this is set, oneDPL(https://github.com/oneapi-src/oneDPL) is used when it is beneficial. (experimental) + + `USE_LESS_ONEDPL`: If this is set, oneDPL is used only when there is no other implementation. + + `USE_MORE_ONEDPL`: If this is set, oneDPL is used everywhere when it is applicable. If this is set, you SHOULD NOT SET DisableIndirectAccess=1. + + `SYCL_OFFLOAD_FFT`: Use SYCL kernel for the current FFTW routines. (Not implemented) + + `INTEL_EXPLICIT_SIMD`: Use Explicit SIMD extension for SYCL kernels. (Not implemented) + + `INTEL_SG_SIZE`: Used for Intel sub-group size in SYCL kernel. 32 is recommended for PVC and 16 is for ATS. (Not tested well) + + `USE_IPP`: Use Intel IPP library's RadixSort for sortOnDevice instead of std::sort. Enabled by default if IPP library exists. + + `USE_MPI_COLLECTIVE`: Use MPI collective whenever possible. Enabled by default for ALTCPU and SYCL. + + `USE_EXISTING_SYCL_DEVICE`: This will copy and use created SYCL device pointer instead of creating new SYCL device for each thread. Not recommended. + + `USE_SINCOS_TABLE`: Pre-calculate sine/cosine table before main loop in some kernels (Not implemented) + ++ Macros defined in source code (No need to specify in CMake configuration and it is defined by -DSYCL=ON above) + + `_SYCL_ENABLED`: source codes with SYCL which may be compiled with any SYCL compiler + + `_DPCPP_ENABLED`: source codes with DPC++ which should be compiled with Intel oneAPI compiler only + ++ Prefined macros by Intel(R) oneAPI compiler + + `__INTEL_LLVM_COMPILER`: For icx and icpx + diff --git a/cmake/BuildTorch.cmake b/cmake/BuildTorch.cmake deleted file mode 100644 index a7798d9cb..000000000 --- a/cmake/BuildTorch.cmake +++ /dev/null @@ -1,91 +0,0 @@ -################################################################################# -# Description: Cmake helper to download and configure LibTorch at cmake config -# time. This is done WITHOUT the cmake util ExternalProject, -# because LibTorch is required during config time. It is instead -# implemented using linux command execution and is thus not -# cross-platform compatible. Download integrity is managed with -# MD5 checksum and 5 attempts will be made to download and unpack. -# Author: Dari Kimanius (github.com/dkimanius) -# Date created: 2020-06-07 -################################################################################# - -option(TORCH_EXTERNAL_VERBOSE "Verbose mode during Torch fetch and configure." OFF) -set(TORCH_EXTERNAL_PATH "${CMAKE_SOURCE_DIR}/external/libtorch") -set(TORCH_FILE_NAME libtorch-cxx11-abi-glibc2.17-shared_openblas_rpath.tar.gz) -set(TORCH_FILE_PATH ${TORCH_EXTERNAL_PATH}/${TORCH_FILE_NAME}) -set(TORCH_URL "ftp://ftp.mrc-lmb.cam.ac.uk/pub/dari/${TORCH_FILE_NAME}") -set(TORCH_HASH 38b21f006fb8c639e03f093da3c9c186) -set(TORCH_FOUND 0) - -if(EXISTS ${TORCH_FILE_PATH}) - file(MD5 ${TORCH_FILE_PATH} CHECKSUM) - if (${CHECKSUM} STREQUAL ${TORCH_HASH}) - find_package(Torch PATHS ${TORCH_EXTERNAL_PATH}) - else() - message(STATUS "Checksum of local file did not match...") - endif() -endif() - -if(NOT TORCH_FOUND) - file(REMOVE_RECURSE ${TORCH_EXTERNAL_PATH}) - file(MAKE_DIRECTORY ${TORCH_EXTERNAL_PATH}) - message(STATUS "Downloading Torch...") - foreach(ATTEMPT RANGE 2 6) - if (BUILD_TORCH_VERBOSE) - execute_process( - COMMAND wget -O ${TORCH_FILE_PATH} ${TORCH_URL} - RESULT_VARIABLE WGET_FAIL) - else() - execute_process( - COMMAND wget -O ${TORCH_FILE_PATH} ${TORCH_URL} - RESULT_VARIABLE WGET_FAIL - OUTPUT_QUIET ERROR_QUIET) - endif() - if(NOT WGET_FAIL) - file(MD5 ${TORCH_FILE_PATH} CHECKSUM) - if (${CHECKSUM} STREQUAL ${TORCH_HASH}) - if (BUILD_TORCH_VERBOSE) - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xzf libtorch/${TORCH_FILE_NAME} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/external - RESULT_VARIABLE UNTAR_FAIL) - else() - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xzf libtorch/${TORCH_FILE_NAME} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/external - RESULT_VARIABLE UNTAR_FAIL - OUTPUT_QUIET ERROR_QUIET) - endif() - if(NOT UNTAR_FAIL) - message(STATUS "Torch was successfully downloaded and configured") - break() - else() - message(FATAL_ERROR "Unpacking failed with error: ${UNTAR_FAIL}") - endif() - else() - message(STATUS "Torch checksum mismatch") - endif() - else() - message(STATUS "Download failed with error: ${WGET_FAIL}") - endif() - if (ATTEMPT LESS 6) - message(STATUS "Retrying (attempt ${ATTEMPT})...") - else() - message(FATAL_ERROR "Could not donwload and configure TORCH") - endif() - endforeach() - find_package(Torch REQUIRED PATHS ${TORCH_EXTERNAL_PATH}) -else(NOT TORCH_FOUND) - message(STATUS "Found previously built non-system Torch libraries that will be used.") -endif() - -set(TORCH_LIBRARY_DIRS "${TORCH_EXTERNAL_PATH}/lib") - -include_directories("${TORCH_INCLUDE_DIRS}") -link_directories(${TORCH_LIBRARY_DIRS}) - -install(DIRECTORY ${TORCH_LIBRARY_DIRS}/ DESTINATION lib FILES_MATCHING PATTERN "lib*.so*") - -#message(STATUS "TORCH_INCLUDE_DIRS: ${TORCH_INCLUDE_DIRS}") -#message(STATUS "TORCH_LIBRARY_DIRS: ${TORCH_LIBRARY_DIRS}") - diff --git a/cmake/BuildTypes.cmake b/cmake/BuildTypes.cmake index db12c27e7..254b208c1 100644 --- a/cmake/BuildTypes.cmake +++ b/cmake/BuildTypes.cmake @@ -1,181 +1,249 @@ # Extra flags defined on each build type (this file is all optional to include) # -# Because gcc is compliant with a float128 type, fftw has become as well. nvcc is NOT. +# Because gcc is compliant with a float128 type, fftw has become as well. nvcc is NOT. # So -D__INTEL_COMPILER just manages to avoid compiling float128-targets (see fftw3.h, for instance). # Add -G to allow cuda-gdb to break inside kernels. -set(EXTRA_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread --std=c++11") - -set(RELION_NVCC_FLAGS "${CUDARCH} ${WARN_DBL} ${EXTRA_NVCC_FLAGS}" CACHE STRING "" FORCE) -#message(STATUS "RELION_NVCC_FLAGS: ${RELION_NVCC_FLAGS}") - -# -------------------------- -# Debug BUILD -# -------------------------- +if(CUDA) + set(EXTRA_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread --std=c++11") + set(RELION_NVCC_FLAGS "${CUDARCH} ${WARN_DBL} ${EXTRA_NVCC_FLAGS}" CACHE STRING "" FORCE) +elseif (HIP) + if (${HIP_VERSION} VERSION_LESS "5.3" ) + set(EXTRA_HIPCC_FLAGS "-fgpu-default-stream=legacy -fno-gpu-rdc -munsafe-fp-atomics") + else() + set(EXTRA_HIPCC_FLAGS "-fno-gpu-rdc -munsafe-fp-atomics -fgpu-default-stream=per-thread") + endif() + set(RELION_HIPCC_FLAGS "${EXTRA_HIPCC_FLAGS}" CACHE STRING "Compiler flags for HIP" FORCE) +endif() +# message(STATUS "RELION_NVCC_FLAGS: ${RELION_NVCC_FLAGS}") +# message(STATUS "RELION_HIPCC_FLAGS: ${RELION_HIPCC_FLAGS}") +# -------------------------- +# Debug BUILD +# -------------------------- # Additional useful nvcc-flags for debugging # # -keep Keep all intermediate files that are generated during internal compilation steps. -# --resource-usage how resource usage such as registers and memeory of the GPU code. This option implies -# --nvlink-options=--verbose when --relocatable-device-code=true is set. Otherwise, +# --resource-usage how resource usage such as registers and memeory of the GPU code. This option implies +# --nvlink-options=--verbose when --relocatable-device-code=true is set. Otherwise, # it implies --ptxas-options=--verbose. # -- Compiler flags ------------------------------------------------- -set(RELION_FLAGS_DEBUG "-O0" CACHE STRING "") -set(RELION_NVCC_FLAGS_DEBUG "${RELION_NVCC_FLAGS}" CACHE STRING "") +set(RELION_FLAGS_DEBUG "-O0 -DDEBUG" CACHE STRING "") +if(CUDA) + set(RELION_NVCC_FLAGS_DEBUG "${RELION_NVCC_FLAGS}" CACHE STRING "") +elseif(HIP) + set(RELION_HIPCC_FLAGS_DEBUG "${RELION_HIPCC_FLAGS} -g -ggdb" CACHE STRING "Compiler flags for HIPCC with DEBUG configuration") +endif() + # -- Linker flags --------------------------------------------------- set(RELION_LINKER_FLAGS_DEBUG " ") # -- Append compiler and linker flags ------------------------------- -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${RELION_FLAGS_DEBUG}" CACHE STRING "") -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${RELION_FLAGS_DEBUG}" CACHE STRING "") -set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${RELION_LINKER_FLAGS_DEBUG}" CACHE STRING "") -set(CUDA_NVCC_FLAGS_DEBUG "${RELION_NVCC_FLAGS_DEBUG}" CACHE STRING "") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${RELION_FLAGS_DEBUG}") +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${RELION_FLAGS_DEBUG}") +set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${RELION_LINKER_FLAGS_DEBUG}") -# -- Add preprocessor defintions ------------------------------------ -set(RELION_DEFINITIONS_DEBUG "-DDEBUG_CUDA") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${RELION_DEFINITIONS_DEBUG}") +if(CUDA) + set(CUDA_NVCC_FLAGS_DEBUG "${RELION_NVCC_FLAGS_DEBUG}" CACHE STRING "") +elseif(HIP) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${RELION_HIPCC_FLAGS_DEBUG}") +endif() -#message(STATUS "Set the extra flags for Debug build type") -#message(STATUS "RELION_NVCC_FLAGS_DEBUG : ${RELION_NVCC_FLAGS_DEBUG}") -#message(STATUS "CUDA_NVCC_FLAGS_DEBUG : ${CUDA_NVCC_FLAGS_DEBUG}") -#message(STATUS "CMAKE_CXX_FLAGS_DEBUG : ${CMAKE_CXX_FLAGS_DEBUG}") +# -- Add preprocessor defintions ------------------------------------ +if(CUDA) + set(RELION_DEFINITIONS_DEBUG "-DDEBUG_CUDA") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${RELION_DEFINITIONS_DEBUG}") +elseif(HIP) + set(RELION_DEFINITIONS_DEBUG "-DDEBUG_HIP") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${RELION_DEFINITIONS_DEBUG}") +endif() + +# message(STATUS "Set the extra flags for Debug build type") +# message(STATUS "RELION_NVCC_FLAGS_DEBUG : ${RELION_NVCC_FLAGS_DEBUG}") +# message(STATUS "CUDA_NVCC_FLAGS_DEBUG : ${CUDA_NVCC_FLAGS_DEBUG}") +# message(STATUS "CMAKE_CXX_FLAGS_DEBUG : ${CMAKE_CXX_FLAGS_DEBUG}") #-------------------------------------------------------------------- - - - - -# -------------------------- -# RELWITHDEBINFO BUILD -# -------------------------- - +# -------------------------- +# RELWITHDEBINFO BUILD +# -------------------------- +set(RELION_FLAGS_RELWITHDEBINFO "-O2 -DDEBUG" CACHE STRING "") # -- Compiler flags ------------------------------------------------- -set(RELION_NVCC_FLAGS_RELWITHDEBINFO "${RELION_NVCC_FLAGS}" CACHE STRING "") +if(CUDA) + set(RELION_NVCC_FLAGS_RELWITHDEBINFO "${RELION_NVCC_FLAGS}" CACHE STRING "") +elseif(HIP) + set(RELION_HIPCC_FLAGS_RELWITHDEBINFO "${RELION_HIPCC_FLAGS} -g -ggdb" CACHE STRING "Compiler flags for HIPCC with RELWITHDEBINFO configuration") +endif() + # -- Linker flags --------------------------------------------------- set(RELION_LINKER_FLAGS_RELWITHDEBINFO " ") # -- Append compiler and linker flags ------------------------------- -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${RELION_FLAGS_RELWITHDEBINFO}" CACHE STRING "") -set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${RELION_FLAGS_RELWITHDEBINFO}" CACHE STRING "") -set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} ${RELION_LINKER_FLAGS_RELWITHDEBINFO}" CACHE STRING "") -set(CUDA_NVCC_FLAGS_RELWITHDEBINFO "${RELION_NVCC_FLAGS_RELWITHDEBINFO}" CACHE STRING "") - -# -- Add preprocessor defintions ------------------------------------ -set(RELION_DEFINITIONS_RELWITHDEBINFO "-DDEBUG_CUDA") - -#message(STATUS "Set the extra flags for RELWITHDEBINFO build type") -#message(STATUS "RELION_NVCC_FLAGS_RELWITHDEBINFO : ${RELION_NVCC_FLAGS_RELWITHDEBINFO}") -#message(STATUS "CUDA_NVCC_FLAGS_RELWITHDEBINFO : ${CUDA_NVCC_FLAGS_RELWITHDEBINFO}") -#message(STATUS "CMAKE_CXX_FLAGS_RELWITHDEBINFO : ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${RELION_FLAGS_RELWITHDEBINFO}") +set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${RELION_FLAGS_RELWITHDEBINFO}") +set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} ${RELION_LINKER_FLAGS_RELWITHDEBINFO}") +if(CUDA) + set(CUDA_NVCC_FLAGS_RELWITHDEBINFO "${RELION_NVCC_FLAGS_RELWITHDEBINFO}" CACHE STRING "") + # -- Add preprocessor defintions ------------------------------------ + set(RELION_DEFINITIONS_RELWITHDEBINFO "-DDEBUG_CUDA") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${RELION_DEFINITIONS_RELWITHDEBINFO}") +elseif(HIP) + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${RELION_HIPCC_FLAGS_RELWITHDEBINFO}") + # -- Add preprocessor defintions ------------------------------------ + set(RELION_DEFINITIONS_RELWITHDEBINFO "-DDEBUG_HIP") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${RELION_DEFINITIONS_RELWITHDEBINFO}") +endif() + + +# message(STATUS "Set the extra flags for RELWITHDEBINFO build type") +# message(STATUS "RELION_NVCC_FLAGS_RELWITHDEBINFO : ${RELION_NVCC_FLAGS_RELWITHDEBINFO}") +# message(STATUS "CUDA_NVCC_FLAGS_RELWITHDEBINFO : ${CUDA_NVCC_FLAGS_RELWITHDEBINFO}") +# message(STATUS "CMAKE_CXX_FLAGS_RELWITHDEBINFO : ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") #-------------------------------------------------------------------- - - - -# -------------------------- -# Release BUILD -# -------------------------- +# -------------------------- +# Release BUILD +# -------------------------- # Additional useful nvcc-flags for optimization # # --use_fast_math -# --prec-div This option controls single-precision floating-point division and reciprocals. -# --prec-div=true enables the IEEE round-to-nearest mode and --prec-div=false enables +# --prec-div This option controls single-precision floating-point division and reciprocals. +# --prec-div=true enables the IEEE round-to-nearest mode and --prec-div=false enables # the fast approximation mode. --use_fast_math implies --prec-div=false. # --prec-sqrt -||- sqrt -# --fmad This option enables (disables) the contraction of floating-point multiplies and -# adds/subtracts into floating-point multiply-add operations (FMAD, FFMA, or DFMA). +# --fmad This option enables (disables) the contraction of floating-point multiplies and +# adds/subtracts into floating-point multiply-add operations (FMAD, FFMA, or DFMA). # --use_fast_math implies --fmad=true. # --restrict Programmer assertion that all kernel pointer parameters are restrict pointers. # -- Compiler flags ------------------------------------------------- -set(RELION_FLAGS_RELEASE "" CACHE STRING "") -set(RELION_NVCC_FLAGS_RELEASE "${RELION_NVCC_FLAGS} --disable-warnings" CACHE STRING "") +set(RELION_FLAGS_RELEASE " " CACHE STRING "") +if(CUDA) + set(RELION_NVCC_FLAGS_RELEASE "${RELION_NVCC_FLAGS} --disable-warnings" CACHE STRING "") +elseif(HIP) + set(RELION_HIPCC_FLAGS_RELEASE "${RELION_HIPCC_FLAGS} -w" CACHE STRING "Compiler flags for HIPCC with RELEASE configuration") +endif() # -- Linker flags --------------------------------------------------- set(RELION_LINKER_FLAGS_RELEASE "") # -- Append compiler and linker flags ------------------------------- #message(STATUS "CCF_RELEASE : ${CMAKE_CXX_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_FLAGS_RELEASE}" CACHE STRING "") -set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${RELION_FLAGS_RELEASE}" CACHE STRING "") -set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${RELION_LINKER_FLAGS_RELEASE}" CACHE STRING "") -set(CUDA_NVCC_FLAGS_RELEASE "${RELION_NVCC_FLAGS_RELEASE}" CACHE STRING "") - +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${RELION_FLAGS_RELEASE}") +set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${RELION_LINKER_FLAGS_RELEASE}") +if(CUDA) + set(CUDA_NVCC_FLAGS_RELEASE "${RELION_NVCC_FLAGS_RELEASE}" CACHE STRING "") +elseif(HIP) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_HIPCC_FLAGS_RELEASE}") +endif() # -- Add preprocessor defintions ------------------------------------ set(RELION_DEFINITIONS_RELEASE "") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_DEFINITIONS_RELEASE}") -#message(STATUS "RELION_FLAGS_PROFILING : ${RELION_FLAGS_PROFILING}") -#message(STATUS "CMAKE_CXX_FLAGS_PROFILING : ${CMAKE_CXX_FLAGS_PROFILING}") +# message(STATUS "RELION_FLAGS_PROFILING : ${RELION_FLAGS_PROFILING}") +# message(STATUS "CMAKE_CXX_FLAGS_RELEASE : ${CMAKE_CXX_FLAGS_RELEASE}") #-------------------------------------------------------------------- +if(CUDA) + # ---------------------------------- + # NVIDIA Profiling BUILD + # (Release for nvprof) + # ---------------------------------- + # ** NOTE: this will not have overall Release perf. ** + # Additional useful nvcc-flags for profiling + # + # -pg gprof profiling output (needs linker flag) + # --resource-usage how resource usage such as registers and memeory of the GPU code. This option implies + # --nvlink-options=--verbose when --relocatable-device-code=true is set. Otherwise, + # it implies --ptxas-options=--verbose# + # -- Compiler flags ------------------------------------------------- + set(RELION_FLAGS_PROFILING "" CACHE STRING "") + set(RELION_NVCC_FLAGS_PROFILING "${RELION_NVCC_FLAGS} -lineinfo" CACHE STRING "") + # -- Linker flags --------------------------------------------------- + set(RELION_LINKER_FLAGS_PROFILING "") + # -- Append compiler and linker flags ------------------------------- + set(CMAKE_CXX_FLAGS_PROFILING "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_FLAGS_PROFILING}" CACHE STRING "") + set(CMAKE_C_FLAGS_PROFILING "${CMAKE_C_FLAGS_RELEASE} ${RELION_FALAGS_PROFILING}" CACHE STRING "") + set(CMAKE_EXE_LINKER_FLAGS_PROFILING "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${RELION_LINKER_FLAGS_PROFILING}" CACHE STRING "") + set(CUDA_NVCC_FLAGS_PROFILING "${RELION_NVCC_FLAGS_PROFILING}" CACHE STRING "") + # -- Add preprocessor defintions ------------------------------------ + set(RELION_DEFINITIONS_PROFILING "-DCUDA_PROFILING") + set(CMAKE_CXX_FLAGS_PROFILING "${CMAKE_CXX_FLAGS_PROFILING} ${RELION_DEFINITIONS_PROFILING}") + #message(STATUS "RELION_FLAGS_PROFILING : ${RELION_FLAGS_PROFILING}") + #message(STATUS "CMAKE_CXX_FLAGS_PROFILING : ${CMAKE_CXX_FLAGS_PROFILING}") + #-------------------------------------------------------------------- -# ---------------------------------- -# NVIDIA Profiling BUILD -# (Release for nvprof) -# ---------------------------------- -# ** NOTE: this will not have overall Release perf. ** - -# Additional useful nvcc-flags for profiling -# -# -pg gprof profiling output (needs linker flag) -# --resource-usage how resource usage such as registers and memeory of the GPU code. This option implies -# --nvlink-options=--verbose when --relocatable-device-code=true is set. Otherwise, -# it implies --ptxas-options=--verbose# - -# -- Compiler flags ------------------------------------------------- -set(RELION_FLAGS_PROFILING "" CACHE STRING "") -set(RELION_NVCC_FLAGS_PROFILING "${RELION_NVCC_FLAGS} -lineinfo" CACHE STRING "") -# -- Linker flags --------------------------------------------------- -set(RELION_LINKER_FLAGS_PROFILING "") - -# -- Append compiler and linker flags ------------------------------- -set(CMAKE_CXX_FLAGS_PROFILING "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_FLAGS_PROFILING}" CACHE STRING "") -set(CMAKE_C_FLAGS_PROFILING "${CMAKE_C_FLAGS_RELEASE} ${RELION_FALAGS_PROFILING}" CACHE STRING "") -set(CMAKE_EXE_LINKER_FLAGS_PROFILING "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${RELION_LINKER_FLAGS_PROFILING}" CACHE STRING "") -set(CUDA_NVCC_FLAGS_PROFILING "${RELION_NVCC_FLAGS_PROFILING}" CACHE STRING "") - -# -- Add preprocessor defintions ------------------------------------ -set(RELION_DEFINITIONS_PROFILING "-DCUDA_PROFILING") -set(CMAKE_CXX_FLAGS_PROFILING "${CMAKE_CXX_FLAGS_PROFILING} ${RELION_DEFINITIONS_PROFILING}") +elseif(HIP) + # ---------------------------------- + # AMD Profiling BUILD + # (Release for rocprof) + # ---------------------------------- + # ** NOTE: this will not have overall Release perf. ** -#message(STATUS "RELION_FLAGS_PROFILING : ${RELION_FLAGS_PROFILING}") -#message(STATUS "CMAKE_CXX_FLAGS_PROFILING : ${CMAKE_CXX_FLAGS_PROFILING}") -#-------------------------------------------------------------------- + # -- Compiler flags ------------------------------------------------- + set(RELION_FLAGS_PROFILING "-O2" CACHE STRING "") + set(RELION_HIPCC_FLAGS_PROFILING "${RELION_HIPCC_FLAGS} -g -ggdb" CACHE STRING "Semicolon delimited flags") + # -- Linker flags --------------------------------------------------- + set(RELION_LINKER_FLAGS_PROFILING "") + # -- Append compiler and linker flags ------------------------------- + set(CMAKE_CXX_FLAGS_PROFILING "${RELION_FLAGS_PROFILING} ${RELION_HIPCC_FLAGS_PROFILING}") + set(CMAKE_C_FLAGS_PROFILING "${RELION_FLAGS_PROFILING} ${RELION_HIPCC_FLAGS_PROFILING}") + set(CMAKE_EXE_LINKER_FLAGS_PROFILING "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${RELION_LINKER_FLAGS_PROFILING}") + # -- Add preprocessor defintions ------------------------------------ + set(RELION_DEFINITIONS_PROFILING "-DHIP_PROFILING -DDEBUG_HIP -DDEBUG") + set(CMAKE_CXX_FLAGS_PROFILING "${CMAKE_CXX_FLAGS_PROFILING} ${RELION_DEFINITIONS_PROFILING}") + # message(STATUS "RELION_FLAGS_PROFILING : ${RELION_FLAGS_PROFILING}") + # message(STATUS "CMAKE_CXX_FLAGS_PROFILING : ${CMAKE_CXX_FLAGS_PROFILING}") + #-------------------------------------------------------------------- +endif() -# ---------------------------------- -# Benchmarking BUILD -# (Release with profiling output) +# ---------------------------------- +# Benchmarking BUILD +# (Release with profiling output) # ---------------------------------- # -- Compiler flags ------------------------------------------------- set(RELION_FLAGS_BENCHMARKING "" CACHE STRING "") -set(RELION_NVCC_FLAGS_BENCHMARKING "${RELION_NVCC_FLAGS} " CACHE STRING "") -# -- Linker flags --------------------------------------------------- +if(CUDA) + set(RELION_NVCC_FLAGS_BENCHMARKING "${RELION_NVCC_FLAGS} " CACHE STRING "") +elseif(HIP) + set(RELION_HIPCC_FLAGS_BENCHMARKING "${RELION_HIPCC_FLAGS} " CACHE STRING "Semicolon delimited flags for HIPCC with Benchmarking configuration") +endif() + # -- Linker flags --------------------------------------------------- set(RELION_LINKER_FLAGS_BENCHMARKING "") # -- Append compiler and linker flags ------------------------------- set(CMAKE_CXX_FLAGS_BENCHMARKING "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_FLAGS_BENCHMARKING}" CACHE STRING "" FORCE) set(CMAKE_C_FLAGS_BENCHMARKING "${CMAKE_C_FLAGS_RELEASE} ${RELION_FLAGS_BENCHMARKING}" CACHE STRING "" FORCE) set(CMAKE_EXE_LINKER_FLAGS_BENCHMARKING "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${RELION_LINKER_FLAGS_BENCHMARKING}" CACHE STRING "" FORCE) -set(CUDA_NVCC_FLAGS_BENCHMARKING "${RELION_NVCC_FLAGS_BENCHMARKING}" CACHE STRING "" FORCE) - +if(CUDA) + set(CUDA_NVCC_FLAGS_BENCHMARKING "${RELION_NVCC_FLAGS_BENCHMARKING}" CACHE STRING "" FORCE) +elseif(HIP) + set(CMAKE_CXX_FLAGS_BENCHMARKING "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_HIPCC_FLAGS_BENCHMARKING}") +endif() # -- Add preprocessor defintions ------------------------------------ -set(RELION_DEFINITIONS_BENCHMARKING "-DCUDA_BENCHMARK -DTIMING") -set(CMAKE_CXX_FLAGS_BENCHMARKING "${CMAKE_CXX_FLAGS_BENCHMARKING} ${RELION_DEFINITIONS_BENCHMARKING}") +if(CUDA) + set(RELION_DEFINITIONS_BENCHMARKING "-DCUDA_BENCHMARK -DTIMING") + set(CMAKE_CXX_FLAGS_BENCHMARKING "${CMAKE_CXX_FLAGS_BENCHMARKING} ${RELION_DEFINITIONS_BENCHMARKING}") +elseif(HIP) + set(RELION_DEFINITIONS_BENCHMARKING "-DHIP_BENCHMARK -DTIMING") + set(CMAKE_CXX_FLAGS_BENCHMARKING "${CMAKE_CXX_FLAGS_BENCHMARKING} ${RELION_DEFINITIONS_BENCHMARKING}") +endif() #-------------------------------------------------------------------- +# message(STATUS "CMAKE_CXX_FLAGS_BENCHMARKING : ${CMAKE_CXX_FLAGS_BENCHMARKING}") \ No newline at end of file diff --git a/cmake/FetchTorchModels.cmake b/cmake/FetchTorchModels.cmake deleted file mode 100644 index bc83a18ec..000000000 --- a/cmake/FetchTorchModels.cmake +++ /dev/null @@ -1,62 +0,0 @@ -################################################################################# -# Description: Cmake helper to download and configure Torch models. -# Author: Dari Kimanius (github.com/dkimanius) -# Date created: 2021-05-04 -################################################################################# - -include(ExternalProject) -set(CLASS_RANKER_DEFAULT_MODEL_FILE_NAME "relion_class_ranker_default_model.pt") - -set(MODELS_DIR ${CMAKE_SOURCE_DIR}/external/torch_models) -if(NOT EXISTS ${MODELS_DIR}) - file(MAKE_DIRECTORY ${MODELS_DIR}) -endif() - -set(CLASS_RANKER_MODEL_FILE_NAME_TAR class_ranker_0.1.3_torch_1.0.1.pt.tar.gz) -set(CLASS_RANKER_MODEL_FILE_NAME class_ranker_0.1.3_torch_1.0.1.pt) -set(CLASS_RANKER_MODEL_URL "ftp://ftp.mrc-lmb.cam.ac.uk/pub/dari/${CLASS_RANKER_MODEL_FILE_NAME_TAR}") -set(CLASS_RANKER_MODEL_MD5 b39f0cbc31f510e3a03e89f1e11110fe) - -set(CLASS_RANKER_MODEL_FOUND 0) - -message(STATUS "Checking class ranker model file...") -if(EXISTS ${MODELS_DIR}/${CLASS_RANKER_MODEL_FILE_NAME_TAR}) - file(MD5 ${MODELS_DIR}/${CLASS_RANKER_MODEL_FILE_NAME_TAR} CHECKSUM) - if (${CHECKSUM} STREQUAL ${CLASS_RANKER_MODEL_MD5}) - set(CLASS_RANKER_MODEL_FOUND 1) - message(STATUS "Found local copy of class ranker model") - else() - message(STATUS "Checksum of local class ranker model did not match...") - endif() -endif() - -if(NOT CLASS_RANKER_MODEL_FOUND) - externalproject_add(class_ranker_model_file - URL ${CLASS_RANKER_MODEL_URL} - URL_MD5 ${CLASS_RANKER_MODEL_MD5} - DOWNLOAD_DIR ${MODELS_DIR} - SOURCE_DIR "${MODELS_DIR}/class_ranker" - BUILD_COMMAND "" - INSTALL_COMMAND "" - CONFIGURE_COMMAND "" - # LOG_CONFIGURE - # LOG_BUILD - LOG_INSTALL) - - message(STATUS "--------------------------------------------------------") - message(STATUS "--------- FOUND NO LOCAL COPY OF TORCH MODELS. ---------") - message(STATUS "------- WILL BE DOWNLOADED DURING COMPILE-TIME. --------") - message(STATUS "--------------------------------------------------------") - message(STATUS "---- A WORKING INTERNET CONNECTION WILL BE REQUIRED. ---") - message(STATUS "-- TO SKIP, RECONFIGURE WITH -DFETCH_TORCH_MODELS=OFF --") - message(STATUS "--------------------------------------------------------") -else() - add_custom_target(class_ranker_model_file ALL) -endif() - -add_custom_command(TARGET class_ranker_model_file POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${MODELS_DIR}/class_ranker/${CLASS_RANKER_MODEL_FILE_NAME} - ${CMAKE_BINARY_DIR}/bin/${CLASS_RANKER_DEFAULT_MODEL_FILE_NAME}) - -#add_definitions(-DCLASS_RANKER_DEFAULT_MODEL_FILE_NAME="${CLASS_RANKER_DEFAULT_MODEL_FILE_NAME}") diff --git a/cmake/FindCondaPython.cmake b/cmake/FindCondaPython.cmake new file mode 100644 index 000000000..26805f0a9 --- /dev/null +++ b/cmake/FindCondaPython.cmake @@ -0,0 +1,111 @@ +# The MIT License (MIT) +# +# Copyright (c) 2023 Dari Kimanius +# +# 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. + +# This script finds Conda environment and its Python executable +# and puts it into PYTHON_EXE_PATH. +# It requires that CONDA_ENV_NAME is set. +# It also find the Torch home and puts it into TORCH_HOME_PATH. + + +set(PYTHON_EXE_PATH "" CACHE PATH "Path to Python executable") + +if(NOT PYTHON_EXE_PATH STREQUAL "" AND NOT EXISTS "${PYTHON_EXE_PATH}") + message(WARNING "Provided path to the Python executable does not exist: ${PYTHON_EXE_PATH}") + set(PYTHON_EXE_PATH "") +endif () + +if (PYTHON_EXE_PATH STREQUAL "") + message(STATUS "Will try to find Conda environment (name: ${CONDA_ENV_NAME}) and its Python executable...") + + find_program(CONDA_EXECUTABLE conda) + + if(CONDA_EXECUTABLE) + message(STATUS "Found Conda executable: ${CONDA_EXECUTABLE}") + + # Run the conda command and capture its output + execute_process(COMMAND ${CONDA_EXECUTABLE} config --show envs_dirs + OUTPUT_VARIABLE CONDA_ENVS_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE CONDA_RESULT) + + # Check if the conda command was successful + if(CONDA_RESULT EQUAL 0) + # Split the output into lines + string(REPLACE "\n" ";" CONDA_ENVS_LIST ${CONDA_ENVS_OUTPUT}) + + # Loop through the list of paths + foreach(path ${CONDA_ENVS_LIST}) + string(STRIP "${path}" path) + string(REPLACE " " ";" path "${path}") + list(GET path -1 path) + set(path "${path}/${CONDA_ENV_NAME}/bin/python") + if(EXISTS "${path}") + set(PYTHON_EXE_PATH "${path}") + message(STATUS "Found Conda environment (name: ${CONDA_ENV_NAME}) and its Python executable.") + break() # Exit the loop once a valid path is found + endif() + endforeach() + endif() + if (PYTHON_EXE_PATH STREQUAL "") + message( + WARNING + "Could NOT find path to Conda environment (name: ${CONDA_ENV_NAME}) and its Python executable.\n" + "You can specify it directly with -DPYTHON_EXE_PATH=\n" + ) + endif() + else (CONDA_EXECUTABLE) + message(WARNING "Could NOT find Conda executable...") + endif() +endif () + +set(TORCH_HOME_PATH "" CACHE PATH "Path to Torch home directory for storage of trained models.") + +if(NOT TORCH_HOME_PATH STREQUAL "" AND NOT EXISTS "${TORCH_HOME_PATH}") + message(WARNING "Provided path to the Torch home directory does not exist: ${TORCH_HOME_PATH}") + set(TORCH_HOME_PATH "") +endif () + +message(STATUS "Using Python executable: ${PYTHON_EXE_PATH}") + +if (NOT PYTHON_EXE_PATH STREQUAL "") + message(STATUS "Will try to find Torch home directory...") + + if (TORCH_HOME_PATH STREQUAL "") + execute_process( + COMMAND ${PYTHON_EXE_PATH} -c "import torch; print(torch.hub._get_torch_home())" + OUTPUT_VARIABLE TORCH_HOME_PATH_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE PYTHON_RESULT + ) + if(EXISTS "${TORCH_HOME_PATH_OUTPUT}") + set(TORCH_HOME_PATH ${TORCH_HOME_PATH_OUTPUT}) + else () + message( + WARNING + "Could NOT find Torch home directory for Conda environment (name: ${CONDA_ENV_NAME}).\n" + "You can specify it directly with -DTORCH_HOME_PATH=\n" + ) + endif () + endif () +endif () + +message(STATUS "Using Torch home: ${TORCH_HOME_PATH}") diff --git a/environment.yml b/environment.yml new file mode 100644 index 000000000..330e9eea4 --- /dev/null +++ b/environment.yml @@ -0,0 +1,39 @@ +name: relion-5.0 +channels: + - conda-forge + - defaults +dependencies: + - pip + - python=3.10 + - setuptools=59.5.0 + - pip: + - torch==2.0.1 + - torchvision==0.15.2 + - numpy==1.24.4 + - tqdm==4.65.0 + - mrcfile==1.4.3 + - starfile==0.4.12 + - loguru==0.7.0 + - scikit-learn==1.3.0 + - umap-learn==0.5.3 + - matplotlib==3.7.2 + - napari[all]==0.4.18 + - tsnecuda==3.0.1 + - PyQt5==5.15.9 + - typer==0.9.0 + - biopython==1.81 + - fastcluster==1.2.6 + - seaborn==0.12.2 + - dill==0.3.7 + - pyhmmer>=0.10.1 + - fair-esm + - einops + - scipy + - git+https://github.com/3dem/relion-classranker@b6e751e5cb4205d8e9b36d0ae38c3687b3395acb + - git+https://github.com/3dem/relion-blush + - git+https://github.com/3dem/DynaMight + - git+https://github.com/3dem/topaz + - -e git+https://github.com/3dem/model-angelo#egg=model-angelo + - ".[vis]" + + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..5166fa0b9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,158 @@ +# https://peps.python.org/pep-0517/ +[build-system] +requires = ["setuptools>=45", "wheel", "setuptools-scm>=6.2"] +build-backend = "setuptools.build_meta" + +# https://peps.python.org/pep-0621/ +[project] +name = "relion" +description = "Python programs for RELION" +readme = "README.md" +requires-python = ">=3.8" +license = {text = "BSD 3-Clause License"} +authors = [ + {email = "alisterburt@gmail.com"}, + {name = "Alister Burt"}, +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", +] +dynamic = ["version"] +dependencies = [ + "numpy", + "pandas", + "starfile", + "mrcfile", + "mdocfile", + "typer", + "rich", + "einops", + "lil_aretomo", + "yet-another-imod-wrapper>=0.0.9", + "makefun", + "lru-dict", + "superqt", + "morphosamplers", +] + +# extras +# https://peps.python.org/pep-0621/#dependencies-optional-dependencies +[project.optional-dependencies] +test = [ + "pytest>=6.0", + "pytest-cov", +] +dev = [ + "black", + "cruft", + "flake8-bugbear", + "flake8-docstrings", + "flake8-pyprojecttoml", + "flake8-typing-imports", + "flake8", + "ipython", + "isort", + "mypy", + "pdbpp", + "pre-commit", + "pydocstyle", + "pytest-cov", + "pytest", + "rich", +] +vis = [ + "napari", + "napari-threedee", + "qtpy", + "psygnal", + "pyqt5", +] + + +[project.urls] +homepage = "https://github.com/3dem/relion" +repository = "https://github.com/3dem/relion" + +# same as console_scripts entry point +[project.scripts] +relion_tomo_import = "tomography_python_programs.import_tilt_series:cli" +relion_tomo_exclude_tilt_images = "tomography_python_programs.exclude_tilt_images:cli" +relion_tomo_align_tilt_series = "tomography_python_programs.align_tilt_series:cli" +relion_tomo_view = "tomography_python_programs.view:cli" +relion_tomo_pick = "tomography_python_programs.pick:cli" +relion_tomo_get_particle_poses = "tomography_python_programs.get_particle_poses:cli" +relion_tomo_denoise = "tomography_python_programs.denoise:cli" + + +# Entry points +# https://peps.python.org/pep-0621/#entry-points +# [project.entry-points."spam.magical"] +# tomatoes = "spam:main_tomatoes" + +# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html +[tool.setuptools] +zip-safe = false +include-package-data = true +packages = {find = {where = ["src"], exclude=[]}} + +[tool.setuptools.package-data] +"*" = ["py.typed"] + + +# https://github.com/pypa/setuptools_scm/#pyprojecttoml-usage +[tool.setuptools_scm] + +# https://pycqa.github.io/isort/docs/configuration/options.html +[tool.isort] +profile = "black" +src_paths = ["src/tomography_python_programs", "tests"] + +# https://flake8.pycqa.org/en/latest/user/options.html +# https://gitlab.com/durko/flake8-pyprojecttoml +[tool.flake8] +exclude = "docs,.eggs,examples,_version.py" +max-line-length = 88 +ignore = "E203" +min-python-version = "3.8.0" +docstring-convention = "all" # use numpy convention, while allowing D417 +extend-ignore = """ +E203 # whitespace before ':' +D107,D203,D212,D213,D402,D413,D415,D416 # numpy +D100 # missing docstring in public module +D401 # imperative mood +W503 # line break before binary operator +""" +per-file-ignores = [ + "tests/*:D", +] + + +# http://www.pydocstyle.org/en/stable/usage.html +[tool.pydocstyle] +match_dir = "src/tomography_python_programs" +convention = "numpy" +add_select = "D402,D415,D417" +ignore = "D100,D213,D401,D413,D107" + +# https://docs.pytest.org/en/6.2.x/customize.html +[tool.pytest.ini_options] +minversion = "6.0" +testpaths = ["src/tomography_python_programs/tests"] +filterwarnings = [ + "error", +] + +# https://mypy.readthedocs.io/en/stable/config_file.html +[tool.mypy] +files = "src/**/" +strict = true +disallow_any_generics = false +disallow_subclassing_any = false +show_error_codes = true +pretty = true + + diff --git a/remove_duplicates.py b/remove_duplicates.py new file mode 100644 index 000000000..8725eb247 --- /dev/null +++ b/remove_duplicates.py @@ -0,0 +1,120 @@ +from enum import Enum +from functools import lru_cache +from pathlib import Path + +import napari +import numpy as np +import pandas as pd +import starfile +import magicgui +from scipy.spatial import KDTree + +from magicgui.experimental import guiclass +from magicgui.widgets import Button + +STAR_FILE = 'run_it007_data.star' +star = starfile.read(STAR_FILE) + +df = star['particles'] + +grouped = df.groupby('rlnTomoName') +TiltSeriesId = Enum('TiltSeriesIds', {ts_id: ts_id for ts_id in grouped.groups.keys()}) +for ts_id in TiltSeriesId: + first_tilt_series = ts_id + break + +viewer = napari.Viewer(ndisplay=3) +widget = magicgui.widgets.create_widget(annotation=TiltSeriesId) + + +@guiclass +class ParameterClass: + tilt_series_id: TiltSeriesId = first_tilt_series + max_distance: int = 1 + output: Path = 'deduplicated.star' + + +parameters = ParameterClass() + + +def get_zyx(tilt_series_id: TiltSeriesId) -> np.ndarray: + df = grouped.get_group(tilt_series_id.value) + zyx = df[['rlnCoordinateZ', 'rlnCoordinateY', 'rlnCoordinateX']].to_numpy() + if 'rlnOriginZAngst' in df.columns: + shifts = df[ + ['rlnOriginZAngst', 'rlnOriginYAngst', 'rlnOriginXAngst']].to_numpy() + zyx -= shifts + return zyx + + +@parameters.events.max_distance.connect +@parameters.events.tilt_series_id.connect +def remove_duplicates(): + points = get_zyx(parameters.tilt_series_id) + points = _collapse_knn( + points=points, + max_distance=parameters.max_distance, + + ) + if 'collapsed points' not in viewer.layers: + viewer.add_points(points, size=40, name='collapsed points') + else: + viewer.layers['collapsed points'].data = points + viewer.camera.center = np.mean(points, axis=0) + + +def _collapse_knn( + points: np.ndarray, + max_distance: float, + k: int = 15, +) -> np.ndarray: + tree = KDTree(data=points) + distance, idx = tree.query(points, k=k, distance_upper_bound=max_distance) + + # remove distances to self + distance, idx = distance[:, 1:], idx[:, 1:] + + # collapse knn up to distance + idx_removed = [] + collapsed_points = [] + for i, (_distance, _idx) in enumerate(zip(distance, idx)): + if i in idx_removed: + continue + valid_idx = _idx[_idx < len(points)] + if len(valid_idx) == 0: + collapsed_points.append(points[i]) + continue + point_group = points[valid_idx] + collapsed_points.append(point_group.mean(axis=0)) + idx_removed.extend(valid_idx) + return np.stack(collapsed_points, axis=0) + + +def save_star_file(): + path = parameters.output + dfs = [] + for ts_id in TiltSeriesId: + zyx = get_zyx(ts_id) + tree = KDTree(data=zyx) + zyx_final = _collapse_knn(zyx, max_distance=parameters.max_distance) + _, idx = tree.query(zyx_final, k=1) + df = grouped.get_group(ts_id.value) + df = df.iloc[idx] + dfs.append(df) + print(f'deduplicated {ts_id.value}') + df = pd.concat(dfs) + new_star = star.copy() + new_star['particles'] = df + starfile.write(star, path, overwrite=True) + print(f'saving particles to {path}') + + pass + + +button = Button(text='save STAR file') +button.clicked.connect(save_star_file) +parameters.gui.append(button) + +viewer.window.add_dock_widget(parameters.gui, area='left', name='collapse kNN') +remove_duplicates() +napari.run() diff --git a/scripts/class_ranker.py b/scripts/class_ranker.py deleted file mode 100644 index b80f79ecc..000000000 --- a/scripts/class_ranker.py +++ /dev/null @@ -1,44 +0,0 @@ -import os -import argparse -import sys - -if (sys.version_info < (3, 0)): - raise Exception('This script supports Python 3 or above.') - print(end="") # This script requires Python 3. A Syntax error here means you are running it in Python 2. - -try: - import torch -except ImportError: - print("PYTHON ERROR: The required python module 'torch' was not found.") - exit(1) - -try: - import numpy as np -except ImportError: - print("PYTHON ERROR: The required python module 'numpy' was not found.") - exit(1) - -parser = argparse.ArgumentParser() - -parser.add_argument('model_path', type=str) -parser.add_argument('project_dir', type=str) -args = parser.parse_args() - -project_dir = args.project_dir -model_fn = args.model_path -feature_fn = os.path.join(project_dir, "features.npy") -images_fn = os.path.join(project_dir, "images.npy") - -model = torch.jit.load(model_fn) -features = np.load(feature_fn) -images = np.load(images_fn) - -count = features.shape[0] - -features_tensor = torch.Tensor(features) -images_tensor = torch.unsqueeze(torch.Tensor(images), 1) -score = model(images_tensor, features_tensor).detach().cpu().numpy() - -for i in range(count): - print(score[i, 0], end=" ") - diff --git a/scripts/filament_selection.in b/scripts/filament_selection.in new file mode 100755 index 000000000..3148fca9e --- /dev/null +++ b/scripts/filament_selection.in @@ -0,0 +1,661 @@ +#!@PYTHON_EXE_PATH@ + +import numpy as np +import pandas as pd +import starfile +import seaborn as sns +import collections +from collections import Counter +from matplotlib import pyplot as plt +from scipy.cluster.hierarchy import fcluster, dendrogram +import mrcfile +from pathlib import Path +import dill as pickle; +import argparse +import shutil +import glob, os + +# Functions for file input/output +def read_particle_star(filename: str) -> (pd.DataFrame, collections.OrderedDict): + ''' + Reads in particle star files, writes out a DataFrames with particles only and and an OrderedDict with all data + ''' + if filename[-5:] != ".star": + raise ValueError(f"{filename} is not in .star format") + else: + try: + all_data = starfile.read(filename).copy() + particle_df = all_data["particles"].copy() + except: + raise ValueError(f"Particle/data .star file {filename} does not contain particles or does not exist") + return particle_df, all_data + +def read_models_star(filename: str) -> (pd.DataFrame, collections.OrderedDict): + ''' + Reads in models star files, writes out a DataFrames with model classes only and and an OrderedDict with all data + ''' + if filename[-5:] != ".star": + raise ValueError("File is not in .star format") + else: + try: + all_data = starfile.read(filename).copy() + models_df = all_data["model_classes"].copy() + except: + raise ValueError(f"Models .star file {filename} does not contain 2D classes or does not exist") + return models_df, all_data + +def read_mrcs_file(filename: str) -> (list, np.recarray): + ''' + Reads in .mrcs files, writes out a list containing the frames and a Numpy recarray containing voxel sizes + ''' + if filename[-5:] != ".mrcs": + raise ValueError(f"{filename} is not in .mrcs format") + else: + data = [] + try: + with mrcfile.open(filename,permissive=True) as mrcs: + for i, frame in enumerate(mrcs.data): + data.append(frame) + voxel_size = mrcs.voxel_size + except: + raise ValueError(f"Unable to parse .mrcs file {filename}") + return data, voxel_size + +def read_mrc_file(filename: str) -> (np.ndarray, np.recarray): + ''' + Reads in .mrc files, writes out a Numpy ndarray containing the data and a Numpy recarray containing voxel sizes + ''' + if filename[-4:] != ".mrc": + raise ValueError(f"{filename} is not in .mrc format") + else: + try: + with mrcfile.open(filename,permissive=True) as mrc: + data = mrc.data + voxel_size = mrc.voxel_size + except: + raise ValueError(f"Unable to parse .mrc file {filename}") + return data, voxel_size + +def write_particle_star(all_data: collections.OrderedDict, particle_df: pd.DataFrame, filename: str, overwrite: bool = True): + ''' + Takes in the data_df as an OrderedDict and a DataFrame of particles and writes out a particle.star combining the two + ''' + data_output = all_data.copy() + data_output["particles"] = particle_df + starfile.write(data_output, f"{filename}_particles.star", overwrite = overwrite) + print("Particles saved as " + f"{filename}_particles.star") + +def write_classes_star(classes_df: pd.DataFrame, filename: str, overwrite: bool = True): + ''' + Takes in classes as a DataFrame and writes out a classes.star + ''' + starfile.write(classes_df, f"{filename}_classes.star", overwrite = overwrite) + print("Class averages saved as " + f"{filename}_classes.star") + +def write_models_star(classes_df: pd.DataFrame, all_data: collections.OrderedDict, filename: str, overwrite: bool = True): + ''' + Takes in classes as a new DataFrame, together with all_data from a model.star and writes out a new model.star + ''' + all_data["model_classes"] = classes_df + starfile.write(all_data, f"{filename}_model.star", overwrite = overwrite) + print("Updated model saved as " + f"{filename}_model.star") + +# Functions for adding columns to particle_df +def hash_particle_df(particle_df: pd.DataFrame, verbose: bool = True) -> pd.DataFrame: + ''' + Adds a particle and filament hash to a copy of the input dataframe, replacing old hashes + Optionally prints out total number of filaments and total particles + ''' + hashed_particle_df = particle_df.copy() + + # Remove old hashes + if "Hash ID" in hashed_particle_df.columns: # From old versions of FiTSuite, so keep for back compatibility + if verbose: + print("Dropping old 'Hash ID' column in hashed_particle_df") + hashed_particle_df = hashed_particle_df.drop(columns = ["Hash ID"]) + if "Hash" in hashed_particle_df.columns: # From old versions of FiTSuite, so keep for back compatibility + if verbose: + print("Dropping old 'Hash' column in hashed_particle_df") + hashed_particle_df = hashed_particle_df.drop(columns = ["Hash"]) + if "filamentHash" in hashed_particle_df.columns: + if verbose: + print("Dropping old 'filamentHash' column in hashed_particle_df") + hashed_particle_df = hashed_particle_df.drop(columns = ["filamentHash"]) + if "particleHash" in hashed_particle_df.columns: + if verbose: + print("Dropping old 'particleHash' column in hashed_particle_df") + hashed_particle_df = hashed_particle_df.drop(columns = ["particleHash"]) + + # Add in hashes + try: + hashed_particle_df["filamentHash"] = hashed_particle_df["rlnHelicalTubeID"].astype(str) + hashed_particle_df["rlnMicrographName"] + hashed_particle_df["particleHash"] = hashed_particle_df["rlnHelicalTubeID"].astype(str) + hashed_particle_df["rlnMicrographName"] + hashed_particle_df["rlnHelicalTrackLengthAngst"].astype(str) + except: + raise KeyError("Unable to hash particle_df as not all required fields exist") + + total_filaments = hashed_particle_df["filamentHash"].nunique() + total_particles = hashed_particle_df["particleHash"].nunique() + assert total_particles == len(hashed_particle_df.index), f"particle_df contains duplicate particles" + if verbose: + print(f"Total filaments: {total_filaments} and total particles: {total_particles}") + + return hashed_particle_df + +def add_classnumber_to_particle_df(hashed_particle_df: pd.DataFrame, particleHashClassDict: dict, verbose: bool = True) -> pd.DataFrame: + ''' + Takes in a hashed_particle_df and adds the rlnClassNumber of each particle from a dict to a copy of the DataFrame + Replaces old rlnClassNumber if it exists + ''' + classadded_particle_df = hashed_particle_df.copy() + + # Remove old labels + if 'rlnClassNumber' in classadded_particle_df.columns: + if verbose: + print("Dropping old 'rlnClassNumber' column in classadded_particle_df") + classadded_particle_df = classadded_particle_df.drop(columns = ["rlnClassNumber"]) + + # Add in classes + try: + classadded_particle_df['rlnClassNumber']=classadded_particle_df['particleHash'].map(particleHashClassDict) + except: + raise KeyError("Unable to assign classes as not all particle assignments exist or particle_df not hashed") + + total_classes = classadded_particle_df['rlnClassNumber'].nunique() + if verbose: + print("Total classes:", total_classes) + + return classadded_particle_df + +def add_classID_to_particle_df(hashed_particle_df: pd.DataFrame, classIDdict: dict, verbose: bool = True) -> pd.DataFrame: + ''' + Takes in a hashed_particle_df and adds the classID of each particle from a dict to a copy of the DataFrame + Replaces old classID if it exists + ''' + classIDadded_particle_df = hashed_particle_df.copy() + + # Remove old labels + if 'classID' in classIDadded_particle_df.columns: + if verbose: + print("Dropping old 'classID' column in classIDadded_particle_df") + classIDadded_particle_df = classIDadded_particle_df.drop(columns = ["classID"]) + + # Add in classes + try: + classIDadded_particle_df['classID'] = classIDadded_particle_df['rlnClassNumber'].map(classIDdict) + except: + raise KeyError("Unable to assign class IDs as not all class assignments exist or particle_df does not class numbers") + + total_classIDs = classIDadded_particle_df['classID'].nunique() + if verbose: + print("Total class IDs:", total_classIDs) + + return classIDadded_particle_df + +def add_filamentID_to_particle_df(hashed_particle_df: pd.DataFrame, filamentIDdict: dict, verbose: bool = True) -> pd.DataFrame: + ''' + Takes in a hashed_particle_df and adds the filamentID of each particle from a dict to a copy of the DataFrame + Replaces old filamentID if it exists + ''' + filamentIDadded_particle_df = hashed_particle_df.copy() + + # Remove old labels + if 'filamentID' in filamentIDadded_particle_df.columns: + if verbose: + print("Dropping old 'filamentID' column in filamentIDadded_particle_df") + filamentIDadded_particle_df = filamentIDadded_particle_df.drop(columns = ["filamentID"]) + + # Add in classes + try: + filamentIDadded_particle_df['filamentID']=filamentIDadded_particle_df['filamentHash'].map(filamentIDdict) + except: + raise KeyError("Unable to assign filament IDs as not all particle assignments exist or particle_df not hashed") + + total_filamentIDs = filamentIDadded_particle_df['filamentID'].nunique() + if verbose: + print("Total filament IDs:", total_filamentIDs) + + return filamentIDadded_particle_df + +def add_particleID_to_particle_df(hashed_particle_df: pd.DataFrame, particleIDdict: dict, verbose: bool = True) -> pd.DataFrame: + ''' + Takes in a hashed_particle_df and adds the particleID of each particle from a dict to a copy of the DataFrame + Replaces old particelID if it exists + ''' + particleIDadded_particle_df = hashed_particle_df.copy() + + # Remove old labels + if 'particleID' in particleIDadded_particle_df.columns: + if verbose: + print("Dropping old 'particleID' column in particleIDadded_particle_df") + particleIDadded_particle_df = particleIDadded_particle_df.drop(columns = ["particleID"]) + + # Add in classes + try: + particleIDadded_particle_df['particleID']=particleIDadded_particle_df['particleHash'].map(filamentIDdict) + except: + raise KeyError("Unable to assign particle IDs as not all particle assignments exist or particle_df not hashed") + + total_particleIDs = particleIDadded_particle_df['particleID'].nunique() + if verbose: + print("Total particle IDs:", total_particleIDs) + + return particleIDadded_particle_df + +# Functions for computing or manipulating counts_df s +def compute_particle_counts_df(hashed_particle_df: pd.DataFrame, classIDdict: dict = None, filamentIDdict: dict = None, + verbose: bool = True) -> (pd.DataFrame, pd.DataFrame, Counter, dict, Counter, dict): + ''' + Computes (class x filament) dataframe of counts of particles per class per filament given a hashed_particle_df + If provided with a previously computed classIDdict or filamentIDdict can use those too + Also outputs other useful info - counts of particles per class/filament and dicts for filament/class hash to ID lookup + ''' + # Check that input is valid + assert "rlnClassNumber" in hashed_particle_df.columns, "hashed_particle_df does not contain class numbers" + assert "filamentHash" in hashed_particle_df.columns, "hashed_particle_df does not contain filament hashes" + + # Compute dimensions of matrix and make up dicts to calculate IDs from + total_classes = hashed_particle_df["rlnClassNumber"].nunique() + total_filaments = hashed_particle_df["filamentHash"].nunique() + total_particles = len(hashed_particle_df.index) + classcount = Counter(hashed_particle_df["rlnClassNumber"].to_list()) + if classIDdict == None: + classIDdict = {val:idx for idx, val in enumerate(sorted(classcount.keys()))} + filamentcount = Counter(hashed_particle_df["filamentHash"].to_list()) + if filamentIDdict == None: + filamentIDdict = {val:idx for idx, val in enumerate(filamentcount.keys())} + if verbose: + print(f"Total classes = {total_classes}, total particles = {total_particles} and total filaments = {total_filaments}") + print("Particles per class: ", classcount) + + # Add filament and class IDs to hashed particles + IDadded_particle_df = add_classID_to_particle_df(hashed_particle_df, classIDdict, verbose = False) + IDadded_particle_df = add_filamentID_to_particle_df(IDadded_particle_df, filamentIDdict, verbose = False) + + #Compute counts_df (note that pivot table approach runs much slower) + # counts_df = pd.pivot_table(IDadded_particle_df, values='particleHash', index='classID', columns='filamentID', aggfunc=pd.Series.nunique, fill_value = 0) + countsmatrix = np.zeros((len(classIDdict),len(filamentIDdict))) + for x, y in zip(IDadded_particle_df["classID"],IDadded_particle_df["filamentID"]): + countsmatrix[x, y] += 1 + + # Making row and col labels strings facilitates plotting, row labels are original class numbers + counts_df = pd.DataFrame(countsmatrix, index = [str(x) for x in classIDdict.keys()], columns = [str(x) for x in range(len(filamentIDdict))]) + counts_df.index.name = "rlnClassNumber" + counts_df.columns.name = "filamentID" + + return counts_df, IDadded_particle_df, classcount, classIDdict, filamentcount, filamentIDdict + +# Functions for plotting figures +def clusterplot_counts_df(counts_df: pd.DataFrame, filepath: str = None, savefig: bool = True, dpi: int = 300, + metric: str = 'cosine', vmax: int = None, cmap: str = 'inferno', standardize: int = None, + row_colors: list = None, col_colors: list = None, + row_linkage: np.ndarray = None, col_linkage: np.ndarray = None, + figsize_x: int = 25, figsize_y: int = 25, label_fontsize: int = 15, + dendrogram_ratio: float = 0.2, colors_ratio: float = 0.03, cbar_pos: tuple = (0.02, 0.81, 0.05, 0.17), + panel_label: bool = False, panel_label_letter: str = "a", panel_label_fontsize: int = 15) -> sns.matrix.ClusterGrid: + ''' + Takes in counts_df and plots clustermap using a UPGMA/average hierarchical clustering + Can modify various parameters and optionally save figure + Refer to https://seaborn.pydata.org/generated/seaborn.clustermap.html for most params + cmaps that are perceptually uniform sequential are viridis, plasma, inferno, magma, cividis + ''' + clustered = sns.clustermap(counts_df, metric = metric, vmax = vmax, cmap = cmap, standard_scale = standardize, + figsize =(figsize_x, figsize_y), row_linkage = row_linkage, col_linkage = col_linkage, + row_colors = row_colors, col_colors = col_colors, cbar_kws={'label': 'Particle Counts'}, + dendrogram_ratio = dendrogram_ratio, colors_ratio = colors_ratio, cbar_pos=cbar_pos) + + # Add labels + clustered.ax_heatmap.set_ylabel("2D Class Average Numbers", fontsize = label_fontsize) + clustered.ax_heatmap.set_xlabel("Filament IDs", fontsize = label_fontsize) + cbar = clustered.ax_heatmap.collections[0].colorbar + cbar.ax.yaxis.label.set_fontsize(label_fontsize) + + # For figure plotting can add a panel label, if you do this almost certainly want to move the cbar_pos + # With a panel_label_fontsize of 15, cbar_pos = (0.1, 0.82, 0.05, 0.16) seems to work + if panel_label: + font = {'fontsize': panel_label_fontsize, 'fontweight': 'bold'}# 'fontname': 'Arial', + plt.text(0.02, 0.97, panel_label_letter, fontdict = font, transform=plt.gcf().transFigure) + #plt.show() + + if savefig: + if filepath != None: + clustered.savefig(f"{filepath}logfile.pdf", dpi=dpi) + print("Figure saved as "+f"{filepath}logfile.pdf") + else: + clustered.savefig("logfile.pdf", dpi=dpi) + print("Figure saved as logfile.pdf") + + return clustered + +# Functions for generating clustering and using clustering +def typecluster_initial_clustering(particles_star_file: str, output_path: str = None, savefig: bool = True, verbose: bool = True, + metric: str = "cosine", vmax: int = None, cmap: str = "inferno", standardize: int = None, + figsize_x: int = 25, figsize_y: int =25, label_fontsize: int = 15, dpi: int = 300, + dendrogram_ratio: float = 0.2, cbar_pos: tuple = (0.02, 0.81, 0.05, 0.17), + panel_label: bool = False, panel_label_letter: str = "a", panel_label_fontsize: int = 15 + ) -> (sns.matrix.ClusterGrid, pd.DataFrame, pd.DataFrame, collections.OrderedDict, Counter, dict, Counter, dict): + ''' + Computes 2D class x unique filament particle count matrix + Plots and saves initial hierarchical clustering figure with distograms and performs other useful calculations + If saved, hierachical clustering will be saved as {output_path}_clustered.png or "counts_df_clustered.png" + + General params: + particles_star_file = path to particle/data.star file as string, needs to have rlnClassNumbers assigned + e.g. from Select or Class2D jobs (required!) + savefig = True or False, boolean flag + output_path = path name that will be appended to the front of anything written out + verbose = True or False, boolean flag for extra text output + + Clustering Figure params (all optional): + metric = distance metric as string, default is "cosine", "jaccard" and "euclidean" also can work well + savefig = bool, default True, controls whether figures are saved + vmax = int, maximum value for colormap in hierarchical clustering e.g. 15 + cmap = color map as string, default "inferno", ideally perceptually uniform sequential cmaps + standardize = int, normalize counts matrix by row (0) or column (1) + figsize_x, figsize_y = ints, default 25, figure dimensions to be saved + label_fontsize: int, default 15, + dpi: int, default 300, + dendrogram_ratio: float controlling ratio of plot that is dendrogram + cbar_pos: tuple = (0.02, 0.81, 0.05, 0.17), + panel_label: Controls whether to add panel label for figure making + panel_label_letter: str, default "a", + panel_label_fontsize: int, default 15 + ''' + # Read in relevant data and calculate class x filament particle count matrix + particle_df, all_data = read_particle_star(particles_star_file) + hashed_particle_df = hash_particle_df(particle_df, verbose = False) + counts_df, IDadded_particle_df, classcount, classIDdict, filamentcount, filamentIDdict = compute_particle_counts_df(hashed_particle_df, verbose = verbose) + + # Compute and plot hierarchical clustering + clustered = clusterplot_counts_df(counts_df, filepath = output_path, savefig = savefig, dpi = dpi, + metric = metric, vmax = vmax, cmap = cmap, standardize = standardize, + figsize_x = figsize_x, figsize_y = figsize_y, label_fontsize = label_fontsize, + dendrogram_ratio = dendrogram_ratio, cbar_pos = cbar_pos, + panel_label = panel_label, panel_label_letter = panel_label_letter, panel_label_fontsize = panel_label_fontsize) + + return clustered, counts_df, IDadded_particle_df, all_data, classcount, classIDdict, filamentcount, filamentIDdict + +def reorder_class_average_star_by_clustered(model_star_file: str, clustered: sns.matrix.ClusterGrid, optimiser_star: str, classIDdict: dict, + output_path: str = None, savestar: bool = True, overwrite: bool = True + ) -> pd.DataFrame: + ''' + Reorders class averages in a model_classes_df and appends a rlnClassNumber and clusteredOrder column + Writes out if desired, and can be visualized by plot_class_averages + + Params: + model_star_file = path to model.star file as string, required if classes are to be written out + clustered = sns.matrix.ClusterGrid, output e.g. from typecluster_initial_clustering + classIDdict = dict, output e.g. from typecluster_initial_clustering + output_path = path name that will be appended to the front of anything written out + savestar = bool, default True, controls whether reordered star file will be written out + overwrite = bool, default True, controls whether it is okay to overwrite existing star + ''' + # Read in order + ordered_row_indices = clustered.dendrogram_row.reordered_ind + classes_to_keep_dict = {classID:ordered_row_indices.index(classIDdict[classID]) for classID in classIDdict.keys()} + + # Read in and add columns to model_star + model_classes_df, all_model_data = read_models_star(model_star_file) + reordered_classes_df = model_classes_df.copy() + reordered_classes_df['rlnClassNumber'] = list(np.arange(1,len(reordered_classes_df.index)+1)) + + # Filter 2D class files with new order and write out + reordered_classes_df = reordered_classes_df[reordered_classes_df['rlnClassNumber'].isin(set(classes_to_keep_dict.keys()))] + reordered_classes_df['clusteredOrder'] = reordered_classes_df['rlnClassNumber'].map(classes_to_keep_dict) + reordered_classes_df = reordered_classes_df.sort_values(by=['clusteredOrder']) + + if savestar: + if output_path == None: + output_path = "run" + write_models_star(reordered_classes_df, all_model_data, filename = f"{output_path}", overwrite = overwrite) + opt = starfile.read(optimiser_star) + opt["rlnModelStarFile"] = output_path+"_model.star" + data_star = opt["rlnExperimentalDataStarFile"][0] + shutil.copy(data_star, output_path+"_data.star") + opt["rlnExperimentalDataStarFile"] = output_path+"_data.star" + starfile.write(opt, output_path+"_optimiser.star") + print("Saved optimiser.star with reordered classes as: ",output_path,"_optimiser.star") + + return reordered_classes_df + +def reorder_counts_df_by_clustered(counts_df: pd.DataFrame, clustered: sns.matrix.ClusterGrid) -> pd.DataFrame: + ''' + Takes in a counts_df and a clustermap and rearranges counts_df to match order + ''' + new_cols = counts_df.columns[clustered.dendrogram_col.reordered_ind] + new_ind = counts_df.index[clustered.dendrogram_row.reordered_ind] + reordered_counts_df = counts_df.loc[new_ind, new_cols] + return reordered_counts_df + +def typecluster_compute_dendrogram_threshold_labels(clustered: sns.matrix.ClusterGrid, threshold: float, + output_path: str, counts_df: pd.DataFrame, savefig: bool = True, + vmax: int = None, cmap: str = "inferno", standardize: int = None, + figsize_x: int = 25, figsize_y: int = 25, label_fontsize: int = 15, + dpi: int = 300, dendrogram_ratio: float = 0.2, cbar_pos: tuple = (0.02, 0.81, 0.05, 0.17), + panel_label: bool = False, panel_label_letter: str = "a", panel_label_fontsize: int = 15 + ) -> (list, Counter, sns.matrix.ClusterGrid): + ''' + Uses fcluster to find filament clusters under a minimum distance_threshold (nominally cosine distance) + Outputs cluster labels for filaments and a counter of filaments/cluster + Also plots labeled clusters on clustermap + + Params: + clustered = Clustermap, e.g. from typecluster_initial_clustering + threshold = float that is the maximum average cosine distance for a cluster + probably either a float threshold or threshold_slider.value + counts_df = DataFrame containing counts, e.g. from typecluster_initial_clustering + output_path = path name that will be appended to the front of anything written out (e.g. /TypeClassifer/group1) + + Otherwise, most params the same as `typecluster_initial_clustering` for visualization purposes + ''' + col_clustered_labels = fcluster(clustered.dendrogram_col.linkage, t=threshold, criterion='distance') + + # Compute figure + col_color_labels = convert_labels_to_colors(col_clustered_labels) + labeled_clustered = clusterplot_counts_df(counts_df, filepath = output_path, savefig = savefig, dpi = dpi, + row_linkage = clustered.dendrogram_row.linkage, col_linkage = clustered.dendrogram_col.linkage, + col_colors = col_color_labels, vmax = vmax, cmap = cmap, standardize = standardize, + figsize_x = figsize_x, figsize_y = figsize_y, label_fontsize = label_fontsize, + dendrogram_ratio = dendrogram_ratio, cbar_pos = cbar_pos, + panel_label = panel_label, panel_label_letter = panel_label_letter, panel_label_fontsize = panel_label_fontsize) + + return col_clustered_labels, labeled_clustered + +def convert_labels_to_colors(labels, color_dict: dict = None, plot_legend: bool = True, verbose: bool = True) -> list: + ''' + Converts a list of labels (ints) to a list of colors either through a specificed color_dict or by uniform sampling + ''' + counted_labels = Counter(labels) + if verbose: + print(f"Number of filaments/classes per label: {counted_labels}") + unique_labels = len(counted_labels.keys()) + + if color_dict != None: + assert len(color_dict) >= unique_labels, "Not enough colors specified in provided color_dict" + try: + colors = [color_dict[label] for label in labels] + except: + raise KeyError("Unable to convert labels to colors using provided color_dict") + else: + colors = sns.color_palette("husl", unique_labels) + color_dict = {label_id: colors[i] for i, label_id in enumerate(counted_labels.keys())} + colors = [color_dict[label] for label in labels] + + if plot_legend: + # Create subplots for each color + num_rows = int(np.ceil(unique_labels/20)) + fig, axs = plt.subplots(num_rows, 20, figsize=(20, num_rows)) + + # Flatten the axs array if it has multiple dimensions + if isinstance(axs, np.ndarray): + axs = axs.flatten() + + # Iterate over the color list and display each color in a subplot + for i, label in enumerate(sorted(counted_labels.keys())): + ax = axs[i] + ax.set_facecolor(color_dict[label]) + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_title(str(label)) + + # Remove empty subplots if necessary + if unique_labels < len(axs): + for j in range(unique_labels, len(axs)): + axs[j].axis('off') + + # Adjust spacing between subplots + plt.subplots_adjust(hspace=0.5, wspace=0.1) + #plt.show() + + return colors + +def typecluster_output_filaments_from_labels(col_labels: list, output_path: str, IDadded_particle_df: pd.DataFrame, + all_data: collections.OrderedDict, particle_threshold: int = 1000, + overwrite: bool = True, drophashes: bool = True, verbose: bool = True + ) -> pd.DataFrame: + ''' + Writes out .star files containing particles from clusters with particle #s exceeding a threshold + Clusters that fail this threshold are merged and output as one joint merged particle .star file + Filament clusters specified by col_labels as a list, which come from k-means or distogram thresholding + + Params: + col_labels = List of labels, probably from kmeans or distogram thresholding + output_path = path name that will be appended to the front of anything written out (e.g. /TypeClassifer/job009) + IDadded_particle_df = DataFrame with filamentIDs, probably from typecluster_initial_clustering + all_data = reading in from read_particle_star, template for output + particle_threshold = minimum number of particles in a filament cluster to output it standalone (float or int) + overwrite = boolean that controls whether it is okay to overwrite existing star files + drophashes = boolean that controls whether to drop hashes when writing out star files + verbose = boolean that controls verbosity of text output + ''' + + # Compute some stats + counted_labels = Counter(col_labels) + num_filaments = IDadded_particle_df['filamentID'].nunique() + num_particles = len(IDadded_particle_df.index) + if verbose: + print(f"Number of filaments/classes per label: {counted_labels}") + + # Map particles to label + labeled_particle_df = IDadded_particle_df.copy() + filamentID_label_dict = {i: label for i, label in enumerate(col_labels)} + if 'clusterLabel' in labeled_particle_df.columns: + labeled_particle_df = labeled_particle_df.drop(columns = ["clusterLabel"]) + labeled_particle_df['clusterLabel'] = labeled_particle_df['filamentID'].map(filamentID_label_dict) + + # Iterate through clusters and extract particles + failed_cluster_labels = [] + for cluster_label in sorted(counted_labels.keys()): + filtered_df = labeled_particle_df.copy() + filtered_df = filtered_df[filtered_df['clusterLabel']==cluster_label] + filtered_particle_count = filtered_df.shape[0] + + # Write out if passing particle threshold, else, group with other clusters that failed + if filtered_particle_count >= particle_threshold: + if drophashes: + try: # We keep cluster labels but could also drop in the future, it helps keep dimensions consistent + columns_to_drop = list(set(["filamentID", "particleID", "filamentHash", "particleHash", "classID"]).intersection(filtered_df.columns)) + filtered_df = filtered_df.drop(columns = columns_to_drop) + except: + print("Unable to drop hashes") + if verbose: + print(f"{filtered_particle_count} particles from filament cluster {cluster_label}") + write_particle_star(all_data, filtered_df, f"{output_path}_cluster{cluster_label}", overwrite = overwrite) + else: + failed_cluster_labels.append(cluster_label) + + # Deal with all of the failed clusters, if any + num_failed_filaments = 0 + num_failed_particles = 0 + if failed_cluster_labels != []: + failed_clusters_df = labeled_particle_df.copy() + failed_clusters_df = failed_clusters_df[failed_clusters_df['clusterLabel'].isin(set(failed_cluster_labels))] + num_failed_filaments = failed_clusters_df['filamentID'].nunique() + num_failed_particles = len(failed_clusters_df.index) + if verbose: + print(f"Clusters {failed_cluster_labels} did not pass particle threshold of {particle_threshold}, so {num_failed_particles} particles will be merged together.") + write_particle_star(all_data, failed_clusters_df, f"{output_path}_clustersfailed", overwrite = overwrite) + + # Output summary statistics and output relevant df + if verbose: + ratio1 = float("{:.2f}".format(num_failed_particles/num_particles*100)) + ratio2 = float("{:.2f}".format(num_failed_filaments/num_filaments*100)) + print(f"{num_failed_particles} particles out of {num_particles} total particles in merged clusters = {ratio1}%.") + print(f"{num_failed_filaments} filaments out of {num_filaments} total filaments in merged clusters = {ratio2}%.") + return labeled_particle_df + + + +def get_starfiles_from_optimiser(filename: str) -> (str, str, str): + ''' + Reads in optimiser.star file, and returns the names of particle_star, model_star and class_mrcs + ''' + if filename[-5:] != ".star": + raise ValueError(f"{filename} is not in .star format") + else: + try: + opt = starfile.read(filename) + particle_star = opt['rlnExperimentalDataStarFile'][0] + model_star = opt['rlnModelStarFile'][0] + model = starfile.read(model_star) + class_mrcs=model["model_classes"]["rlnReferenceImage"][0].split("@",1)[1] + except: + raise ValueError(f"optimiser.star file {filename} cannot be parsed correctly for particle and model star files") + return particle_star, model_star, class_mrcs + + + +if __name__ == "__main__": + + argParser = argparse.ArgumentParser() + argParser.add_argument("-i", "--input", required=True, help="Input optimiser.star from 2D classification") + argParser.add_argument("-o", "--output", required=True, help="Output directory") + argParser.add_argument("-t", "--threshold", type=float, default=0.8, help="Dendrogram threshold") + argParser.add_argument("-c", "--classmin", type=int, default=-1, help="Minimum number of particles per class; write out star files if positive") + argParser.add_argument("--pipeline_control", default="", help="Needed to work together with relion GUI") + + args = argParser.parse_args() + try: + input_optimiser_star = args.input + output_directory = args.output+"/" + + + output = output_directory+"run" + Path(output_directory).mkdir(exist_ok=True, parents=True) + + mypickle = output+'_state.pkl' + if (Path(mypickle).exists()): + pickle.load_session(mypickle) + args = argParser.parse_args() # to re-parse the arguments after having saved the session! + else: + particle_star, model_star, class_mrcs = get_starfiles_from_optimiser(input_optimiser_star) + clustered, counts_df, IDadded_particle_df, all_data, classcount, classIDdict, filamentcount, filamentIDdict = typecluster_initial_clustering(particle_star, output_path = output, savefig = False, vmax = 30) + reordered_classes_df = reorder_class_average_star_by_clustered(model_star, clustered, input_optimiser_star, classIDdict, output_path = output, savestar = True, overwrite = True) + + pickle.dump_session(mypickle) + print("Continuation session saved as: ", mypickle) + + + # Delete any cluster star files from a previous execution + for f in glob.glob(output+"_cluster*.star"): + os.remove(f) + + dendrogram_threshold = args.threshold + particle_threshold = args.classmin + print("Dendrogram threshold = ", dendrogram_threshold) + col_clustered_labels, labeled_clustered = typecluster_compute_dendrogram_threshold_labels(clustered, dendrogram_threshold, output_directory, counts_df, savefig = True, vmax = 30) + + if (particle_threshold > 0): + print("Min number of particles per class = ", particle_threshold) + labeled_particle_df = typecluster_output_filaments_from_labels(col_clustered_labels, output, IDadded_particle_df, all_data, verbose = True, particle_threshold = particle_threshold, overwrite = True) + + if (args.pipeline_control != ""): + Path(output_directory+"RELION_JOB_EXIT_SUCCESS").touch() + + print("Done!") + + except: + if (args.pipeline_control != ""): + Path(output_directory+"RELION_JOB_EXIT_FAILURE").touch() + raise ValueError("Oops! Something went wrong..") + + diff --git a/scripts/python_blush.in b/scripts/python_blush.in new file mode 100755 index 000000000..42cedb551 --- /dev/null +++ b/scripts/python_blush.in @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. + NOTE: For some modules TORCH_HOME needs to be set to find pretrained models +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" +torch_home="@TORCH_HOME_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +if [ -n "$torch_home" ]; then + TORCH_HOME="$torch_home" "$python_executable" -c "from relion_blush import main; exit(main())" "$@" +else + "$python_executable" -c "from relion_blush import main; exit(main())" "$@" +fi + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_classranker.in b/scripts/python_classranker.in new file mode 100755 index 000000000..72a3a41dc --- /dev/null +++ b/scripts/python_classranker.in @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. + NOTE: For some modules TORCH_HOME needs to be set to find pretrained models +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" +torch_home="@TORCH_HOME_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +if [ -n "$torch_home" ]; then + TORCH_HOME="$torch_home" "$python_executable" -c "from relion_classranker import main; exit(main())" "$@" +else + "$python_executable" -c "from relion_classranker import main; exit(main())" "$@" +fi + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_dynamight.in b/scripts/python_dynamight.in new file mode 100755 index 000000000..336493227 --- /dev/null +++ b/scripts/python_dynamight.in @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +"$python_executable" -c "from dynamight import cli; exit(cli())" "$@" + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_fetch_weights.in b/scripts/python_fetch_weights.in new file mode 100755 index 000000000..b0de26dc3 --- /dev/null +++ b/scripts/python_fetch_weights.in @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# Get the directory of the currently executing script +script_dir="$(dirname "$0")" + +# Custom function to exit with status 1 +exit_with_error() { + echo "One or more download tasks failed. See above error messages." + exit 1 +} + + +# Call Class Ranker weight download ############################################## +echo "Attempting to download weights for class ranker..." + +target_path="$script_dir/relion_python_classranker" + +if [ -x "$target_path" ]; then + # Execute the target binary + "$target_path" + if [ $? -ne 0 ]; then + # Unsuccessful, trap the EXIT signal to call the custom exit function + trap 'exit_with_error' EXIT + fi +else + echo "Error: $target_binary not found or not executable in $script_dir." + exit 1 +fi + +# Call Blush weight download ##################################################### +echo "Attempting to download weights for Blush..." + +target_path="$script_dir/relion_python_blush" + +if [ -x "$target_path" ]; then + # Execute the target binary + "$target_path" + if [ $? -ne 0 ]; then + # Unsuccessful, trap the EXIT signal to call the custom exit function + trap 'exit_with_error' EXIT + fi +else + echo "Error: $target_binary not found or not executable in $script_dir." + exit 1 +fi + +# ModelAngelo weight download #################################################### +echo "Attempting to download weights for ModelAngelo..." +target_path="$script_dir/relion_python_modelangelo" + +if [ -x "$target_path" ]; then + # Execute the target binary + "$target_path" setup_weights --bundle-name nucleotides && \ + "$target_path" setup_weights --bundle-name nucleotides_no_seq + if [ $? -ne 0 ]; then + # Unsuccessful, trap the EXIT signal to call the custom exit function + trap 'exit_with_error' EXIT + fi +else + echo "Error: $target_binary not found or not executable in $script_dir." + exit 1 +fi + +# Exit +exit 0 + diff --git a/scripts/python_modelangelo.in b/scripts/python_modelangelo.in new file mode 100755 index 000000000..52009480e --- /dev/null +++ b/scripts/python_modelangelo.in @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. + NOTE: For some modules TORCH_HOME needs to be set to find pretrained models +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" +torch_home="@TORCH_HOME_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +if [ -n "$torch_home" ]; then + TORCH_HOME="$torch_home" "$python_executable" -c "from model_angelo.__main__ import main; exit(main())" "$@" +else + "$python_executable" -c "from model_angelo.__main__ import main; exit(main())" "$@" +fi + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_tomo_align_tilt_series.in b/scripts/python_tomo_align_tilt_series.in new file mode 100755 index 000000000..c7fb7471a --- /dev/null +++ b/scripts/python_tomo_align_tilt_series.in @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +"$python_executable" -c "from tomography_python_programs.align_tilt_series import cli; exit(cli())" "$@" + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_tomo_denoise.in b/scripts/python_tomo_denoise.in new file mode 100755 index 000000000..25545fdca --- /dev/null +++ b/scripts/python_tomo_denoise.in @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +"$python_executable" -c "from tomography_python_programs.denoise import cli; exit(cli())" "$@" + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_tomo_exclude_tilt_images.in b/scripts/python_tomo_exclude_tilt_images.in new file mode 100755 index 000000000..151036a7b --- /dev/null +++ b/scripts/python_tomo_exclude_tilt_images.in @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +"$python_executable" -c "from tomography_python_programs.exclude_tilt_images import cli; exit(cli())" "$@" + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_tomo_get_particle_poses.in b/scripts/python_tomo_get_particle_poses.in new file mode 100755 index 000000000..cbf06dec6 --- /dev/null +++ b/scripts/python_tomo_get_particle_poses.in @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +"$python_executable" -c "from tomography_python_programs.get_particle_poses import cli; exit(cli())" "$@" + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_tomo_import.in b/scripts/python_tomo_import.in new file mode 100755 index 000000000..eae88efbf --- /dev/null +++ b/scripts/python_tomo_import.in @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +"$python_executable" -c "from tomography_python_programs.import_tilt_series import cli; exit(cli())" "$@" + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_tomo_pick.in b/scripts/python_tomo_pick.in new file mode 100755 index 000000000..ecaae8c73 --- /dev/null +++ b/scripts/python_tomo_pick.in @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +"$python_executable" -c "from tomography_python_programs.pick import cli; exit(cli())" "$@" + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_tomo_view.in b/scripts/python_tomo_view.in new file mode 100755 index 000000000..cf8e87515 --- /dev/null +++ b/scripts/python_tomo_view.in @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +"$python_executable" -c "from tomography_python_programs.view cli; exit(cli())" "$@" + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/scripts/python_topaz.in b/scripts/python_topaz.in new file mode 100755 index 000000000..a4949e9ce --- /dev/null +++ b/scripts/python_topaz.in @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +"$python_executable" -c "import re, sys; from topaz.main import main; sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]); sys.exit(main())" "$@" + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 + diff --git a/src/Eigen/src/Core/util/Meta.h b/src/Eigen/src/Core/util/Meta.h index e9e2f1873..047d3fa14 100755 --- a/src/Eigen/src/Core/util/Meta.h +++ b/src/Eigen/src/Core/util/Meta.h @@ -20,7 +20,7 @@ #endif #if defined(EIGEN_HIP_DEVICE_COMPILE) - #include "Eigen/src/Core/arch/HIP/hcc/math_constants.h" + #include "src/Eigen/src/Core/arch/HIP/hcc/math_constants.h" #endif #endif diff --git a/src/acc/acc_backprojector.h b/src/acc/acc_backprojector.h index 0ea29ff75..5946cb85d 100644 --- a/src/acc/acc_backprojector.h +++ b/src/acc/acc_backprojector.h @@ -3,12 +3,21 @@ #ifdef _CUDA_ENABLED # include +using deviceStream_t = cudaStream_t; +#elif _HIP_ENABLED +# include +using deviceStream_t = hipStream_t; +#elif _SYCL_ENABLED +# include "src/acc/sycl/sycl_virtual_dev.h" +using deviceStream_t = virtualSYCL*; +#else +using deviceStream_t = float; #endif #include "src/complex.h" #include "src/acc/settings.h" #include "src/acc/acc_ptr.h" -#ifndef _CUDA_ENABLED +#ifdef ALTCPU # include #endif @@ -22,8 +31,8 @@ class AccBackprojector XFLOAT padding_factor; size_t mdlXYZ; -#ifndef _CUDA_ENABLED -tbb::spin_mutex *mutexes; +#ifdef ALTCPU + tbb::spin_mutex *mutexes; #endif size_t allocaton_size; @@ -31,7 +40,7 @@ tbb::spin_mutex *mutexes; XFLOAT *d_mdlReal, *d_mdlImag, *d_mdlWeight; - cudaStream_t stream; + deviceStream_t stream; public: @@ -43,12 +52,15 @@ tbb::spin_mutex *mutexes; allocaton_size(0), voxelCount(0), d_mdlReal(NULL), d_mdlImag(NULL), d_mdlWeight(NULL), stream(0) -#ifndef _CUDA_ENABLED -, mutexes(0) +#ifdef ALTCPU + , mutexes(0) #endif {} size_t setMdlDim( +#ifdef _SYCL_ENABLED + deviceStream_t dev, +#endif int xdim, int ydim, int zdim, int inity, int initz, int max_r, XFLOAT paddingFactor); @@ -73,13 +85,13 @@ tbb::spin_mutex *mutexes; int imgZ, unsigned long imageCount, bool data_is_3D, - cudaStream_t optStream); + deviceStream_t optStream); void getMdlData(XFLOAT *real, XFLOAT *imag, XFLOAT * weights); void getMdlDataPtrs(XFLOAT *& real, XFLOAT *& imag, XFLOAT *& weights); - void setStream(cudaStream_t s) { stream = s; } - cudaStream_t getStream() { return stream; } + void setStream(deviceStream_t s) { stream = s; } + deviceStream_t getStream() { return stream; } void clear(); diff --git a/src/acc/acc_backprojector_impl.h b/src/acc/acc_backprojector_impl.h index a08e710ff..18a49560f 100644 --- a/src/acc/acc_backprojector_impl.h +++ b/src/acc/acc_backprojector_impl.h @@ -1,11 +1,19 @@ //#include +//#ifdef _CUDA_ENABLED //#include +//#include "src/acc/cuda/cuda_kernels/cuda_device_utils.cuh" +//#elif _HIP_ENABLED +//#include +//#include "src/acc/hip/hip_kernels/hip_device_utils.h" +//#endif //#include "src/acc/settings.h" //#include "src/acc/acc_backprojector.h" -//#include "src/acc/cuda/cuda_kernels/cuda_device_utils.cuh" -//#include "src/acc/acc_projector.h" +#include "src/acc/acc_projector.h" size_t AccBackprojector::setMdlDim( +#ifdef _SYCL_ENABLED + deviceStream_t dev, +#endif int xdim, int ydim, int zdim, int inity, int initz, int max_r, XFLOAT paddingFactor) @@ -36,13 +44,21 @@ size_t AccBackprojector::setMdlDim( HANDLE_ERROR(cudaMalloc( (void**) &d_mdlReal, mdlXYZ * sizeof(XFLOAT))); HANDLE_ERROR(cudaMalloc( (void**) &d_mdlImag, mdlXYZ * sizeof(XFLOAT))); HANDLE_ERROR(cudaMalloc( (void**) &d_mdlWeight, mdlXYZ * sizeof(XFLOAT))); +#elif _HIP_ENABLED + HANDLE_ERROR(hipMalloc( (void**) &d_mdlReal, mdlXYZ * sizeof(XFLOAT))); + HANDLE_ERROR(hipMalloc( (void**) &d_mdlImag, mdlXYZ * sizeof(XFLOAT))); + HANDLE_ERROR(hipMalloc( (void**) &d_mdlWeight, mdlXYZ * sizeof(XFLOAT))); +#elif _SYCL_ENABLED + stream = dev; + d_mdlReal = (XFLOAT*)(stream->syclMalloc(mdlXYZ * sizeof(XFLOAT), syclMallocType::device, "d_mdlReal")); + d_mdlImag = (XFLOAT*)(stream->syclMalloc(mdlXYZ * sizeof(XFLOAT), syclMallocType::device, "d_mdlImag")); + d_mdlWeight = (XFLOAT*)(stream->syclMalloc(mdlXYZ * sizeof(XFLOAT), syclMallocType::device, "d_mdlWeight")); #else if (posix_memalign((void **)&d_mdlReal, MEM_ALIGN, mdlXYZ * sizeof(XFLOAT))) CRITICAL(RAMERR); if (posix_memalign((void **)&d_mdlImag, MEM_ALIGN, mdlXYZ * sizeof(XFLOAT))) CRITICAL(RAMERR); if (posix_memalign((void **)&d_mdlWeight, MEM_ALIGN, mdlXYZ * sizeof(XFLOAT))) CRITICAL(RAMERR); - mutexes = new tbb::spin_mutex[mdlZ*mdlY]; - + mutexes = new tbb::spin_mutex[mdlZ*mdlY]; #endif allocaton_size = mdlXYZ * sizeof(XFLOAT) * 3; @@ -53,7 +69,7 @@ size_t AccBackprojector::setMdlDim( void AccBackprojector::initMdl() { -#ifdef DEBUG_CUDA +#if defined DEBUG_CUDA || defined DEBUG_HIP if (mdlXYZ == 0) { printf("Model dimensions must be set with setMdlDim before call to initMdl."); @@ -71,6 +87,15 @@ void AccBackprojector::initMdl() DEBUG_HANDLE_ERROR(cudaMemset( d_mdlReal, 0, mdlXYZ * sizeof(XFLOAT))); DEBUG_HANDLE_ERROR(cudaMemset( d_mdlImag, 0, mdlXYZ * sizeof(XFLOAT))); DEBUG_HANDLE_ERROR(cudaMemset( d_mdlWeight, 0, mdlXYZ * sizeof(XFLOAT))); +#elif _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipMemset( d_mdlReal, 0, mdlXYZ * sizeof(XFLOAT))); + DEBUG_HANDLE_ERROR(hipMemset( d_mdlImag, 0, mdlXYZ * sizeof(XFLOAT))); + DEBUG_HANDLE_ERROR(hipMemset( d_mdlWeight, 0, mdlXYZ * sizeof(XFLOAT))); +#elif _SYCL_ENABLED + stream->syclMemset(d_mdlReal, 0, mdlXYZ * sizeof(XFLOAT)); + stream->syclMemset(d_mdlImag, 0, mdlXYZ * sizeof(XFLOAT)); + stream->syclMemset(d_mdlWeight, 0, mdlXYZ * sizeof(XFLOAT)); + stream->waitAll(); #else memset(d_mdlReal, 0, mdlXYZ * sizeof(XFLOAT)); memset(d_mdlImag, 0, mdlXYZ * sizeof(XFLOAT)); @@ -91,6 +116,20 @@ void AccBackprojector::getMdlData(XFLOAT *r, XFLOAT *i, XFLOAT * w) DEBUG_HANDLE_ERROR(cudaMemcpyAsync( w, d_mdlWeight, mdlXYZ * sizeof(XFLOAT), cudaMemcpyDeviceToHost, stream)); DEBUG_HANDLE_ERROR(cudaStreamSynchronize(stream)); //Wait for copy +#elif _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipStreamSynchronize(stream)); //Make sure to wait for remaining kernel executions + + DEBUG_HANDLE_ERROR(hipMemcpyAsync( r, d_mdlReal, mdlXYZ * sizeof(XFLOAT), hipMemcpyDeviceToHost, stream)); + DEBUG_HANDLE_ERROR(hipMemcpyAsync( i, d_mdlImag, mdlXYZ * sizeof(XFLOAT), hipMemcpyDeviceToHost, stream)); + DEBUG_HANDLE_ERROR(hipMemcpyAsync( w, d_mdlWeight, mdlXYZ * sizeof(XFLOAT), hipMemcpyDeviceToHost, stream)); + + DEBUG_HANDLE_ERROR(hipStreamSynchronize(stream)); //Wait for copy +#elif _SYCL_ENABLED + stream->waitAll(); + stream->syclMemcpy(r, d_mdlReal, mdlXYZ * sizeof(XFLOAT)); + stream->syclMemcpy(i, d_mdlImag, mdlXYZ * sizeof(XFLOAT)); + stream->syclMemcpy(w, d_mdlWeight, mdlXYZ * sizeof(XFLOAT)); + stream->waitAll(); #else memcpy(r, d_mdlReal, mdlXYZ * sizeof(XFLOAT)); memcpy(i, d_mdlImag, mdlXYZ * sizeof(XFLOAT)); @@ -100,7 +139,7 @@ void AccBackprojector::getMdlData(XFLOAT *r, XFLOAT *i, XFLOAT * w) void AccBackprojector::getMdlDataPtrs(XFLOAT *& r, XFLOAT *& i, XFLOAT *& w) { -#ifndef _CUDA_ENABLED +#ifdef ALTCPU r = d_mdlReal; i = d_mdlImag; w = d_mdlWeight; @@ -126,6 +165,15 @@ void AccBackprojector::clear() DEBUG_HANDLE_ERROR(cudaFree(d_mdlReal)); DEBUG_HANDLE_ERROR(cudaFree(d_mdlImag)); DEBUG_HANDLE_ERROR(cudaFree(d_mdlWeight)); +#elif _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipFree(d_mdlReal)); + DEBUG_HANDLE_ERROR(hipFree(d_mdlImag)); + DEBUG_HANDLE_ERROR(hipFree(d_mdlWeight)); +#elif _SYCL_ENABLED + stream->waitAll(); + stream->syclFree(d_mdlReal); + stream->syclFree(d_mdlImag); + stream->syclFree(d_mdlWeight); #else free(d_mdlReal); free(d_mdlImag); diff --git a/src/acc/acc_helper_functions.h b/src/acc/acc_helper_functions.h index 2f65ac492..0b14c6603 100644 --- a/src/acc/acc_helper_functions.h +++ b/src/acc/acc_helper_functions.h @@ -2,7 +2,16 @@ #define ACC_HELPER_FUNCTIONS_H_ #include "src/acc/acc_ml_optimiser.h" - +#ifdef _CUDA_ENABLED + using deviceStream_t = cudaStream_t; +#elif _HIP_ENABLED + using deviceStream_t = hipStream_t; +#elif _SYCL_ENABLED + #include "src/acc/sycl/sycl_virtual_dev.h" + using deviceStream_t = virtualSYCL*; +#else + using deviceStream_t = float; +#endif /* * This assisting function goes over the orientations determined as significant for this image, and checks * which translations should be included in the list of those which differences will be calculated for. @@ -17,7 +26,7 @@ long int makeJobsForDiff2Fine( ProjectionParams &FineProjectionData, std::vector< long unsigned > &iover_transes, std::vector< long unsigned > &ihiddens, - long int nr_over_orient, long int nr_over_trans, int img_id, + long int nr_over_orient, long int nr_over_trans, IndexedDataArray &FPW, // FPW=FinePassWeights IndexedDataArrayMask &dataMask, int chunk); @@ -90,7 +99,7 @@ void runWavgKernel( bool refs_are_ctf_corrected, bool ctf_premultiplied, bool data_is_3D, - cudaStream_t stream); + deviceStream_t stream); void runBackProjectKernel( AccBackprojector &BP, @@ -114,7 +123,7 @@ void runBackProjectKernel( bool data_is_3D, bool do_grad, bool ctf_premultiplied, - cudaStream_t optStream); + deviceStream_t optStream); template< typename T> void deviceInitComplexValue(AccPtr &data, XFLOAT value) @@ -140,7 +149,7 @@ void mapAllWeightsToMweights( XFLOAT * d_mweights, //Mweight unsigned long orientation_num, //projectorPlan.orientation_num unsigned long translation_num, //translation_num - cudaStream_t stream + deviceStream_t stream ); #define OVER_THRESHOLD_BLOCK_SIZE 512 @@ -156,6 +165,15 @@ void arrayOverThreshold(AccPtr &data, AccPtr &passed, T threshold) data.getSize(), OVER_THRESHOLD_BLOCK_SIZE); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + int grid_size = ceil((float)data.getSize()/(float)OVER_THRESHOLD_BLOCK_SIZE); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_array_over_threshold), dim3(grid_size), dim3(OVER_THRESHOLD_BLOCK_SIZE), 0, data.getStream(), + ~data, + ~passed, + threshold, + data.getSize(), + OVER_THRESHOLD_BLOCK_SIZE); + LAUNCH_HANDLE_ERROR(hipGetLastError()); #else int Size = data.getSize(); for(size_t i=0; i &data, T threshold) idx.cpToHost(); DEBUG_HANDLE_ERROR(cudaStreamSynchronize(data.getStream())); return idx[0]; +#elif _HIP_ENABLED + AccPtr idx(1, data.getStream(), data.getAllocator()); + idx[0] = 0; + + idx.putOnDevice(); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_find_threshold_idx_in_cumulative), dim3(grid_size), dim3(FIND_IN_CUMULATIVE_BLOCK_SIZE), 0, data.getStream(), + ~data, + threshold, + data.getSize()-1, + ~idx, + FIND_IN_CUMULATIVE_BLOCK_SIZE); + idx.cpToHost(); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(data.getStream())); + return idx[0]; +#elif defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + return AccUtilities::findThresholdIdxInCumulativeSum(data, threshold); #else size_t idx = 0; size_t size_m1 = data.getSize()-1; @@ -219,7 +253,7 @@ void runDiff2KernelCoarse( long unsigned orientation_num, unsigned long translation_num, unsigned long image_size, - cudaStream_t stream, + deviceStream_t stream, bool do_CC, bool data_is_3D); @@ -246,7 +280,7 @@ void runDiff2KernelFine( unsigned long image_size, int img_id, int exp_iclass, - cudaStream_t stream, + deviceStream_t stream, long unsigned job_num_count, bool do_CC, bool data_is_3D); @@ -273,8 +307,8 @@ void runCollect2jobs( int grid_dim, size_t * trans_idx, size_t * jobOrigin, size_t * jobExtent, - bool data_is_3D - ); + bool data_is_3D, + deviceStream_t stream); void windowFourierTransform2( AccPtr &d_in, @@ -283,14 +317,22 @@ void windowFourierTransform2( size_t oX, size_t oY, size_t oZ, //Output dimensions size_t Npsi = 1, size_t pos = 0, - cudaStream_t stream = 0); + deviceStream_t stream = 0); void selfApplyBeamTilt2(MultidimArray &Fimg, RFLOAT beamtilt_x, RFLOAT beamtilt_y, RFLOAT wavelength, RFLOAT Cs, RFLOAT angpix, int ori_size); template -void runCenterFFT(MultidimArray< T >& v, bool forward, CudaCustomAllocator *allocator) +void runCenterFFT(MultidimArray< T >& v, bool forward, +#ifdef _CUDA_ENABLED + CudaCustomAllocator *allocator +#elif _HIP_ENABLED + HipCustomAllocator *allocator +#else + CudaCustomAllocator *allocator +#endif +) { AccPtr img_in (v.nzyxdim, allocator); // with original data pointer // AccPtr img_aux(v.nzyxdim, allocator); // temporary holder @@ -349,7 +391,7 @@ void runCenterFFT(MultidimArray< T >& v, bool forward, CudaCustomAllocator *allo int dim=ceilf((float)(v.nzyxdim/(float)(2*CFTT_BLOCK_SIZE))); AccUtilities::centerFFT_2D(dim, 0, CFTT_BLOCK_SIZE, -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED ~img_in, #else &img_in[0], @@ -359,8 +401,11 @@ void runCenterFFT(MultidimArray< T >& v, bool forward, CudaCustomAllocator *allo YSIZE(v), xshift, yshift); +#ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); - +#elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); +#endif img_in.cpToHost(); for (unsigned long i = 0; i < v.nzyxdim; i ++) @@ -498,13 +543,14 @@ void runCenterFFT( AccPtr< T > &img_in, ySize, xshift, yshift); - +#ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); - -// HANDLE_ERROR(cudaStreamSynchronize(0)); + // HANDLE_ERROR(cudaStreamSynchronize(0)); +#elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); + // HANDLE_ERROR(hipStreamSynchronize(0)); +#endif // img_aux.cpOnDevice(img_in.d_ptr); //update input image with centered kernel-output. - - } template @@ -543,8 +589,13 @@ void runCenterFFT( AccPtr< T > &img_in, xshift, yshift, zshift); + #ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); // HANDLE_ERROR(cudaStreamSynchronize(0)); + #elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); + // HANDLE_ERROR(hipStreamSynchronize(0)); + #endif // img_aux.cpOnDevice(img_in.d_ptr); //update input image with centered kernel-output. } else @@ -567,7 +618,11 @@ void runCenterFFT( AccPtr< T > &img_in, ySize, xshift, yshift); + #ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); + #elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); + #endif } } @@ -626,8 +681,11 @@ void lowPassFilterMapGPU( (XFLOAT)angpix, (size_t)Xdim*(size_t)Ydim*(size_t)Zdim); } +#ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); +#endif } #endif //ACC_HELPER_FUNCTIONS_H_ - diff --git a/src/acc/acc_helper_functions_impl.h b/src/acc/acc_helper_functions_impl.h index 956412cf4..c06a1221b 100644 --- a/src/acc/acc_helper_functions_impl.h +++ b/src/acc/acc_helper_functions_impl.h @@ -1,11 +1,27 @@ /* #undef ALTCPU +#ifdef _CUDA_ENABLED #include #include "src/acc/cuda/cuda_settings.h" #include "src/acc/cuda/cuda_kernels/BP.cuh" +#elif _HIP_ENABLED +#include +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_kernels/BP.h" +#endif #include "src/macros.h" #include "src/error.h" */ +#ifdef _CUDA_ENABLED + using deviceStream_t = cudaStream_t; +#elif _HIP_ENABLED + using deviceStream_t = hipStream_t; +#elif _SYCL_ENABLED + #include "src/acc/sycl/sycl_virtual_dev.h" + using deviceStream_t = virtualSYCL*; +#else + using deviceStream_t = float; +#endif long int makeJobsForDiff2Fine( OptimisationParamters &op, SamplingParameters &sp, @@ -13,7 +29,7 @@ long int makeJobsForDiff2Fine( ProjectionParams &FineProjectionData, std::vector< long unsigned > &iover_transes, std::vector< long unsigned > &ihiddens, - long int nr_over_orient, long int nr_over_trans, int img_id, + long int nr_over_orient, long int nr_over_trans, IndexedDataArray &FPW, // FPW=FinePassWeights IndexedDataArrayMask &dataMask, int chunk) @@ -23,6 +39,8 @@ long int makeJobsForDiff2Fine( // (this will be reduced at exit of this function) dataMask.setNumberOfJobs(orientation_num*translation_num); dataMask.setNumberOfWeights(orientation_num*translation_num); + dataMask.jobOrigin.freeHostIfSet(); + dataMask.jobExtent.freeHostIfSet(); dataMask.jobOrigin.hostAlloc(); dataMask.jobExtent.hostAlloc(); @@ -37,7 +55,7 @@ long int makeJobsForDiff2Fine( long int iover_trans = iover_transes[j]; long int ihidden = FineProjectionData.iorientclasses[i] * sp.nr_trans + ihiddens[j]; - if(DIRECT_A2D_ELEM(op.Mcoarse_significant, img_id, ihidden)==1) + if(DIRECT_A1D_ELEM(op.Mcoarse_significant, ihidden)==1) { FPW.rot_id[w_base+w] = FineProjectionData.iorientclasses[i] % (sp.nr_dir*sp.nr_psi); // where to look for priors etc FPW.rot_idx[w_base+w] = i; // which rot for this significant task @@ -98,17 +116,17 @@ long int makeJobsForCollect(IndexedDataArray &FPW, long int jobid=0; dataMask.jobOrigin[jobid]=0; dataMask.jobExtent[jobid]=1; - long int crot =FPW.rot_idx[jobid]; // set current rot + long int crot =FPW.rot_idx[jobid]; // set current rot for(long int n=1; n::max(); + mapped_weights[i] = std::numeric_limits::lowest(); for (long unsigned i = idxArr_start; i < idxArr_end; i++) mapped_weights[ (rot_idx[i]-orientation_start) * translation_num + trans_idx[i] ]= weights[i]; @@ -156,7 +174,7 @@ void buildCorrImage(MlOptimiser *baseMLO, corr_img[i] = 1. / (op.local_sqrtXi2[img_id]*op.local_sqrtXi2[img_id]); else for(size_t i = 0; i < corr_img.getSize(); i++) - corr_img[i] = *(op.local_Minvsigma2[img_id].data + i ); + corr_img[i] = *(op.local_Minvsigma2.data + i ); // ctf-correction or not ( NOTE this is not were the difference metric is ctf-corrected, but // rather where we apply the additional correction to make the GPU-specific arithmetic equal @@ -319,7 +337,7 @@ void runWavgKernel( bool refs_are_ctf_corrected, bool ctf_premultiplied, bool data_is_3D, - cudaStream_t stream) + deviceStream_t stream) { //cudaFuncSetCacheConfig(cuda_kernel_wavg_fast, cudaFuncCachePreferShared); @@ -342,13 +360,17 @@ void runWavgKernel( wdiff2s_AA, wdiff2s_XA, translation_num, - (XFLOAT) op.sum_weight[img_id], - (XFLOAT) op.significant_weight[img_id], + (XFLOAT) op.sum_weight, + (XFLOAT) op.significant_weight, part_scale, stream ); else if (projector.mdlZ!=0) +#if defined ALTCPU || defined _SYCL_ENABLED + AccUtilities::kernel_wavg( +#else AccUtilities::kernel_wavg( +#endif eulers, projector, image_size, @@ -364,13 +386,17 @@ void runWavgKernel( wdiff2s_AA, wdiff2s_XA, translation_num, - (XFLOAT) op.sum_weight[img_id], - (XFLOAT) op.significant_weight[img_id], + (XFLOAT) op.sum_weight, + (XFLOAT) op.significant_weight, part_scale, stream ); else +#if defined ALTCPU || defined _SYCL_ENABLED + AccUtilities::kernel_wavg( +#else AccUtilities::kernel_wavg( +#endif eulers, projector, image_size, @@ -386,8 +412,8 @@ void runWavgKernel( wdiff2s_AA, wdiff2s_XA, translation_num, - (XFLOAT) op.sum_weight[img_id], - (XFLOAT) op.significant_weight[img_id], + (XFLOAT) op.sum_weight, + (XFLOAT) op.significant_weight, part_scale, stream ); @@ -411,13 +437,17 @@ void runWavgKernel( wdiff2s_AA, wdiff2s_XA, translation_num, - (XFLOAT) op.sum_weight[img_id], - (XFLOAT) op.significant_weight[img_id], + (XFLOAT) op.sum_weight, + (XFLOAT) op.significant_weight, part_scale, stream ); else if (projector.mdlZ!=0) +#if defined ALTCPU || defined _SYCL_ENABLED + AccUtilities::kernel_wavg( +#else AccUtilities::kernel_wavg( +#endif eulers, projector, image_size, @@ -433,13 +463,17 @@ void runWavgKernel( wdiff2s_AA, wdiff2s_XA, translation_num, - (XFLOAT) op.sum_weight[img_id], - (XFLOAT) op.significant_weight[img_id], + (XFLOAT) op.sum_weight, + (XFLOAT) op.significant_weight, part_scale, stream ); else +#if defined ALTCPU || defined _SYCL_ENABLED + AccUtilities::kernel_wavg( +#else AccUtilities::kernel_wavg( +#endif eulers, projector, image_size, @@ -455,13 +489,17 @@ void runWavgKernel( wdiff2s_AA, wdiff2s_XA, translation_num, - (XFLOAT) op.sum_weight[img_id], - (XFLOAT) op.significant_weight[img_id], + (XFLOAT) op.sum_weight, + (XFLOAT) op.significant_weight, part_scale, stream ); } +#ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); +#endif } void runBackProjectKernel( @@ -486,7 +524,7 @@ void runBackProjectKernel( bool data_is_3D, bool do_grad, bool ctf_premultiplied, - cudaStream_t optStream) + deviceStream_t optStream) { if(BP.mdlZ==1) @@ -537,6 +575,103 @@ void runBackProjectKernel( imgX, imgY, imgX*imgY, BP.mdlX, BP.mdlInitY); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + if(do_grad) + if(ctf_premultiplied) + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject2D_SGD), dim3(imageCount), dim3(BP_2D_BLOCK_SIZE), 0, optStream, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgX*imgY, + BP.mdlX, BP.mdlInitY); + else + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject2D_SGD), dim3(imageCount), dim3(BP_2D_BLOCK_SIZE), 0, optStream, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgX*imgY, + BP.mdlX, BP.mdlInitY); + else + if(ctf_premultiplied) + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject2D), dim3(imageCount), dim3(BP_2D_BLOCK_SIZE), 0, optStream, + d_img_real, d_img_imag, + trans_x, trans_y, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgX*imgY, + BP.mdlX, BP.mdlInitY); + else + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject2D), dim3(imageCount), dim3(BP_2D_BLOCK_SIZE), 0, optStream, + d_img_real, d_img_imag, + trans_x, trans_y, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgX*imgY, + BP.mdlX, BP.mdlInitY); + LAUNCH_HANDLE_ERROR(hipGetLastError()); +#elif _SYCL_ENABLED + if(do_grad) + if(ctf_premultiplied) + syclKernels::backproject2D( + imageCount, BP_2D_BLOCK_SIZE, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgX*imgY, + BP.mdlX, BP.mdlInitY, BP.mdlY, optStream); + else + syclKernels::backproject2D( + imageCount, BP_2D_BLOCK_SIZE, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgX*imgY, + BP.mdlX, BP.mdlInitY, BP.mdlY, optStream); + else + if(ctf_premultiplied) + syclKernels::backproject2D( + imageCount, BP_2D_BLOCK_SIZE, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgX*imgY, + BP.mdlX, BP.mdlInitY, BP.mdlY, optStream); + else + syclKernels::backproject2D( + imageCount, BP_2D_BLOCK_SIZE, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgX*imgY, + BP.mdlX, BP.mdlInitY, BP.mdlY, optStream); #else if(do_grad) if(ctf_premultiplied) @@ -614,6 +749,48 @@ void runBackProjectKernel( BP.maxR, BP.maxR2, BP.padding_factor, imgX, imgY, imgZ, imgX * imgY * imgZ, BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); +#elif _HIP_ENABLED + if(ctf_premultiplied) + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject3D_SGD),dim3(imageCount), dim3(BP_DATA3D_BLOCK_SIZE), 0, optStream, + projector, d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX * imgY * imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); + else + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject3D_SGD), dim3(imageCount), dim3(BP_DATA3D_BLOCK_SIZE), 0, optStream, + projector, d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX * imgY * imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); +#elif _SYCL_ENABLED + if(ctf_premultiplied) + syclKernels::backproject3D(imageCount, BP_DATA3D_BLOCK_SIZE, + projector, d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mdlXYZ, optStream); + else + syclKernels::backproject3D(imageCount, BP_DATA3D_BLOCK_SIZE, + projector, d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mdlXYZ, optStream); #else if(ctf_premultiplied) CpuKernels::backproject3D_SGD(imageCount, BP_DATA3D_BLOCK_SIZE, @@ -659,7 +836,48 @@ void runBackProjectKernel( BP.maxR, BP.maxR2, BP.padding_factor, imgX, imgY, imgZ, imgX * imgY * imgZ, BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); - +#elif _HIP_ENABLED + if(ctf_premultiplied) + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject3D_SGD), dim3(imageCount), dim3(BP_REF3D_BLOCK_SIZE), 0, optStream, + projector, d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX * imgY * imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); + else + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject3D_SGD), dim3(imageCount), dim3(BP_REF3D_BLOCK_SIZE), 0, optStream, + projector, d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX * imgY * imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); +#elif _SYCL_ENABLED + if(ctf_premultiplied) + syclKernels::backproject3D(imageCount, BP_REF3D_BLOCK_SIZE, + projector, d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mdlXYZ, optStream); + else + syclKernels::backproject3D(imageCount, BP_REF3D_BLOCK_SIZE, + projector, d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mdlXYZ, optStream); #else if(ctf_premultiplied) CpuKernels::backproject3D_SGD(imageCount, BP_REF3D_BLOCK_SIZE, @@ -707,7 +925,50 @@ void runBackProjectKernel( BP.maxR, BP.maxR2, BP.padding_factor, imgX, imgY, imgZ, imgX*imgY*imgZ, BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); - +#elif _HIP_ENABLED + if(ctf_premultiplied) + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject3D), dim3(imageCount), dim3(BP_DATA3D_BLOCK_SIZE), 0, optStream, + d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); + else + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject3D), dim3(imageCount), dim3(BP_DATA3D_BLOCK_SIZE), 0, optStream, + d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); +#elif _SYCL_ENABLED + if(ctf_premultiplied) + syclKernels::backproject3D(imageCount,BP_DATA3D_BLOCK_SIZE, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mdlXYZ, optStream); + else + syclKernels::backproject3D(imageCount,BP_DATA3D_BLOCK_SIZE, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mdlXYZ, optStream); #else if(ctf_premultiplied) CpuKernels::backproject3D(imageCount,BP_DATA3D_BLOCK_SIZE, @@ -753,9 +1014,51 @@ void runBackProjectKernel( BP.maxR, BP.maxR2, BP.padding_factor, imgX, imgY, imgZ, imgX*imgY*imgZ, BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); - +#elif _HIP_ENABLED + if(ctf_premultiplied) + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject3D), dim3(imageCount), dim3(BP_REF3D_BLOCK_SIZE), 0, optStream, + d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); + else + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_backproject3D), dim3(imageCount), dim3(BP_REF3D_BLOCK_SIZE), 0, optStream, + d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ); +#elif _SYCL_ENABLED + if(ctf_premultiplied) + syclKernels::backproject3D(imageCount, BP_REF3D_BLOCK_SIZE, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mdlXYZ, optStream); + else + syclKernels::backproject3D(imageCount, BP_REF3D_BLOCK_SIZE, + projector, + d_img_real, d_img_imag, + trans_x, trans_y, trans_z, + d_weights, d_Minvsigma2s, d_ctfs, + translation_num, significant_weight, weight_norm, d_eulers, + BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, + BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, + imgX, imgY, imgZ, imgX*imgY*imgZ, + BP.mdlX, BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mdlXYZ, optStream); #else -#if 1 //TODO Clean this up if(ctf_premultiplied) CpuKernels::backprojectRef3D(imageCount, d_img_real, d_img_imag, @@ -776,32 +1079,13 @@ void runBackProjectKernel( BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, (unsigned)imgX, (unsigned)imgY, (unsigned)imgZ, (size_t)imgX*(size_t)imgY*(size_t)imgZ, (unsigned)BP.mdlX, (unsigned)BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mutexes); - -#else - if(ctf_premultiplied) - CpuKernels::backproject3D(imageCount,BP_REF3D_BLOCK_SIZE, - d_img_real, d_img_imag, - trans_x, trans_y, trans_z, - d_weights, d_Minvsigma2s, d_ctfs, - translation_num, significant_weight, weight_norm, d_eulers, - BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, - BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, - (unsigned)imgX, (unsigned)imgY, (unsigned)imgZ, (size_t)imgX*(size_t)imgY*(size_t)imgZ, - (unsigned)BP.mdlX, (unsigned)BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mutexes); - else - CpuKernels::backproject3D(imageCount,BP_REF3D_BLOCK_SIZE, - d_img_real, d_img_imag, - trans_x, trans_y, trans_z, - d_weights, d_Minvsigma2s, d_ctfs, - translation_num, significant_weight, weight_norm, d_eulers, - BP.d_mdlReal, BP.d_mdlImag, BP.d_mdlWeight, - BP.maxR, BP.maxR2, (XFLOAT)BP.padding_factor, - (unsigned)imgX, (unsigned)imgY, (unsigned)imgZ, (size_t)imgX*(size_t)imgY*(size_t)imgZ, - (unsigned)BP.mdlX, (unsigned)BP.mdlY, BP.mdlInitY, BP.mdlInitZ, BP.mutexes); -#endif #endif } // do_grad is false + #ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); + #elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); + #endif } } @@ -813,7 +1097,7 @@ void mapAllWeightsToMweights( XFLOAT * d_mweights, //Mweight unsigned long orientation_num, //projectorPlan.orientation_num unsigned long translation_num, //translation_num - cudaStream_t stream + deviceStream_t stream ) { size_t combinations = orientation_num*translation_num; @@ -827,6 +1111,23 @@ void mapAllWeightsToMweights( translation_num, WEIGHT_MAP_BLOCK_SIZE); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_allweights_to_mweights), dim3(grid_size), dim3(WEIGHT_MAP_BLOCK_SIZE), 0, stream, + d_iorient, + d_allweights, + d_mweights, + orientation_num, + translation_num, + WEIGHT_MAP_BLOCK_SIZE); + LAUNCH_HANDLE_ERROR(hipGetLastError()); +#elif defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + syclGpuKernels::sycl_kernel_allweights_to_mweights( + d_iorient, + d_allweights, + d_mweights, + orientation_num, + translation_num, + WEIGHT_MAP_BLOCK_SIZE, stream); #else for (size_t i=0; i < combinations; i++) d_mweights[d_iorient[i/translation_num] * translation_num + i%translation_num] = @@ -849,7 +1150,7 @@ void runDiff2KernelCoarse( long unsigned orientation_num, long unsigned translation_num, long unsigned image_size, - cudaStream_t stream, + deviceStream_t stream, bool do_CC, bool data_is_3D) { @@ -859,7 +1160,90 @@ void runDiff2KernelCoarse( { if(projector.mdlZ!=0) { +#ifdef _SYCL_ENABLED + assert(translation_num <= blocks3D); + + long unsigned rest = orientation_num % blocks3D; + long unsigned even_orientation_num = orientation_num - rest; + + if (even_orientation_num) + { + if (data_is_3D) + { + unsigned long grid_size = (D2C_EULERS_PER_BLOCK_DATA3D == 1) ? orientation_num : even_orientation_num/(unsigned long)D2C_EULERS_PER_BLOCK_DATA3D; + AccUtilities::diff2_coarse( + grid_size, + D2C_BLOCK_SIZE_DATA3D, + d_eulers, + trans_x, + trans_y, + trans_z, + Fimg_real, + Fimg_imag, + projector, + corr_img, + diff2s, + translation_num, + image_size, + stream); + } + else + { + unsigned long grid_size = (D2C_EULERS_PER_BLOCK_REF3D == 1) ? orientation_num : even_orientation_num/(unsigned long)D2C_EULERS_PER_BLOCK_REF3D; + AccUtilities::diff2_coarse( + grid_size, + D2C_BLOCK_SIZE_REF3D, + d_eulers, + trans_x, + trans_y, + trans_z, + Fimg_real, + Fimg_imag, + projector, + corr_img, + diff2s, + translation_num, + image_size, + stream); + } + } + if (rest) + { + if (data_is_3D && D2C_EULERS_PER_BLOCK_DATA3D != 1) + AccUtilities::diff2_coarse( + rest, + D2C_BLOCK_SIZE_DATA3D, + &d_eulers[9*even_orientation_num], + trans_x, + trans_y, + trans_z, + Fimg_real, + Fimg_imag, + projector, + corr_img, + &diff2s[translation_num*even_orientation_num], + translation_num, + image_size, + stream); + else if (!data_is_3D && D2C_EULERS_PER_BLOCK_REF3D != 1) + AccUtilities::diff2_coarse( + rest, + D2C_BLOCK_SIZE_REF3D, + &d_eulers[9*even_orientation_num], + trans_x, + trans_y, + trans_z, + Fimg_real, + Fimg_imag, + projector, + corr_img, + &diff2s[translation_num*even_orientation_num], + translation_num, + image_size, + stream); + } +#else #ifdef ACC_DOUBLE_PRECISION if (translation_num > blocks3D*4) CRITICAL(ERR_TRANSLIM); @@ -1171,10 +1555,89 @@ void runDiff2KernelCoarse( } } #endif +#endif // End of non-SYCL code } // projector.mdlZ!=0 else { +#ifdef _SYCL_ENABLED + assert(translation_num <= D2C_BLOCK_SIZE_2D); + + long unsigned rest = orientation_num % (unsigned long)D2C_EULERS_PER_BLOCK_2D; + long unsigned even_orientation_num = orientation_num - rest; + long unsigned grid_size = (D2C_EULERS_PER_BLOCK_2D == 1) ? orientation_num : even_orientation_num/(unsigned long)D2C_EULERS_PER_BLOCK_2D; + + if (even_orientation_num) + { + if(data_is_3D) + AccUtilities::diff2_coarse( + grid_size, + D2C_BLOCK_SIZE_2D, + d_eulers, + trans_x, + trans_y, + trans_z, + Fimg_real, + Fimg_imag, + projector, + corr_img, + diff2s, + translation_num, + image_size, + stream); + else + AccUtilities::diff2_coarse( + grid_size, + D2C_BLOCK_SIZE_2D, + d_eulers, + trans_x, + trans_y, + trans_z, + Fimg_real, + Fimg_imag, + projector, + corr_img, + diff2s, + translation_num, + image_size, + stream); + } + if (rest && D2C_EULERS_PER_BLOCK_2D != 1) + { + if (data_is_3D) + AccUtilities::diff2_coarse( + rest, + D2C_BLOCK_SIZE_2D, + &d_eulers[9*even_orientation_num], + trans_x, + trans_y, + trans_z, + Fimg_real, + Fimg_imag, + projector, + corr_img, + &diff2s[translation_num*even_orientation_num], + translation_num, + image_size, + stream); + else + AccUtilities::diff2_coarse( + rest, + D2C_BLOCK_SIZE_2D, + &d_eulers[9*even_orientation_num], + trans_x, + trans_y, + trans_z, + Fimg_real, + Fimg_imag, + projector, + corr_img, + &diff2s[translation_num*even_orientation_num], + translation_num, + image_size, + stream); + } +#else if (translation_num > D2C_BLOCK_SIZE_2D) { printf("Number of coarse translations larger than %d on the GPU not supported.\n", D2C_BLOCK_SIZE_2D); @@ -1220,7 +1683,11 @@ void runDiff2KernelCoarse( translation_num, image_size, stream); + #ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); + #elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); + #endif } if (rest != 0) @@ -1257,8 +1724,13 @@ void runDiff2KernelCoarse( translation_num, image_size, stream); + #ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); + #elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); + #endif } +#endif // End of non-SYCL code } // projector.mdlZ==0 } // !do_CC else @@ -1267,7 +1739,11 @@ void runDiff2KernelCoarse( // a single call to diff2_CC_course? // dim3 CCblocks(orientation_num,translation_num); if(data_is_3D) +#if defined ALTCPU || defined _SYCL_ENABLED + AccUtilities::diff2_CC_coarse( +#else AccUtilities::diff2_CC_coarse( +#endif orientation_num, D2C_BLOCK_SIZE_DATA3D, d_eulers, @@ -1284,7 +1760,11 @@ void runDiff2KernelCoarse( local_sqrtXi2, stream); else if(projector.mdlZ!=0) +#if defined ALTCPU || defined _SYCL_ENABLED + AccUtilities::diff2_CC_coarse( +#else AccUtilities::diff2_CC_coarse( +#endif orientation_num, D2C_BLOCK_SIZE_REF3D, d_eulers, @@ -1301,7 +1781,11 @@ void runDiff2KernelCoarse( local_sqrtXi2, stream); else +#if defined ALTCPU || defined _SYCL_ENABLED + AccUtilities::diff2_CC_coarse( +#else AccUtilities::diff2_CC_coarse( +#endif orientation_num, D2C_BLOCK_SIZE_2D, d_eulers, @@ -1317,7 +1801,11 @@ void runDiff2KernelCoarse( image_size, local_sqrtXi2, stream); + #ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); + #elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); + #endif } // do_CC } @@ -1345,7 +1833,7 @@ void runDiff2KernelFine( unsigned long image_size, int img_id, int exp_iclass, - cudaStream_t stream, + deviceStream_t stream, long unsigned job_num_count, bool do_CC, bool data_is_3D) @@ -1425,7 +1913,11 @@ void runDiff2KernelFine( job_idx, job_num, stream); + #ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); + #elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); + #endif } else { @@ -1503,7 +1995,11 @@ void runDiff2KernelFine( job_idx, job_num, stream); + #ifdef _CUDA_ENABLED LAUNCH_HANDLE_ERROR(cudaGetLastError()); + #elif _HIP_ENABLED + LAUNCH_HANDLE_ERROR(hipGetLastError()); + #endif } } @@ -1530,8 +2026,8 @@ void runCollect2jobs( int grid_dim, size_t * trans_idx, size_t * jobOrigin, size_t * jobExtent, - bool data_is_3D - ) + bool data_is_3D, + deviceStream_t stream) { if (data_is_3D) { #ifdef _CUDA_ENABLED @@ -1559,6 +2055,54 @@ void runCollect2jobs( int grid_dim, trans_idx, jobOrigin, jobExtent); +#elif _HIP_ENABLED + dim3 numblocks(grid_dim); + size_t shared_buffer = sizeof(XFLOAT)*SUMW_BLOCK_SIZE*5; // x+y+z+myp+weights + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_collect2jobs), numblocks, dim3(SUMW_BLOCK_SIZE), shared_buffer, stream, + oo_otrans_x, // otrans-size -> make const + oo_otrans_y, // otrans-size -> make const + oo_otrans_z, // otrans-size -> make const + myp_oo_otrans_x2y2z2, // otrans-size -> make const + weights, + significant_weight, + sum_weight, + nr_trans, + nr_oversampled_trans, + nr_oversampled_rot, + oversamples, + skip_rots, + p_weights, + p_thr_wsum_prior_offsetx_class, + p_thr_wsum_prior_offsety_class, + p_thr_wsum_prior_offsetz_class, + p_thr_wsum_sigma2_offset, + rot_idx, + trans_idx, + jobOrigin, + jobExtent); +#elif _SYCL_ENABLED + syclKernels::collect2jobs(grid_dim, SUMW_BLOCK_SIZE, + oo_otrans_x, // otrans-size -> make const + oo_otrans_y, // otrans-size -> make const + oo_otrans_z, // otrans-size -> make const + myp_oo_otrans_x2y2z2, // otrans-size -> make const + weights, + significant_weight, + sum_weight, + nr_trans, + nr_oversampled_trans, + nr_oversampled_rot, + oversamples, + skip_rots, + p_weights, + p_thr_wsum_prior_offsetx_class, + p_thr_wsum_prior_offsety_class, + p_thr_wsum_prior_offsetz_class, + p_thr_wsum_sigma2_offset, + rot_idx, + trans_idx, + jobOrigin, + jobExtent); #else CpuKernels::collect2jobs(grid_dim, SUMW_BLOCK_SIZE, oo_otrans_x, // otrans-size -> make const @@ -1611,6 +2155,54 @@ void runCollect2jobs( int grid_dim, trans_idx, jobOrigin, jobExtent); +#elif _HIP_ENABLED + dim3 numblocks(grid_dim); + size_t shared_buffer = sizeof(XFLOAT)*SUMW_BLOCK_SIZE*4; // x+y+myp+weights + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_collect2jobs), numblocks, dim3(SUMW_BLOCK_SIZE), shared_buffer, stream, + oo_otrans_x, // otrans-size -> make const + oo_otrans_y, // otrans-size -> make const + oo_otrans_z, // otrans-size -> make const + myp_oo_otrans_x2y2z2, // otrans-size -> make const + weights, + significant_weight, + sum_weight, + nr_trans, + nr_oversampled_trans, + nr_oversampled_rot, + oversamples, + skip_rots, + p_weights, + p_thr_wsum_prior_offsetx_class, + p_thr_wsum_prior_offsety_class, + p_thr_wsum_prior_offsetz_class, + p_thr_wsum_sigma2_offset, + rot_idx, + trans_idx, + jobOrigin, + jobExtent); +#elif _SYCL_ENABLED + syclKernels::collect2jobs(grid_dim, SUMW_BLOCK_SIZE, + oo_otrans_x, // otrans-size -> make const + oo_otrans_y, // otrans-size -> make const + oo_otrans_z, // otrans-size -> make const + myp_oo_otrans_x2y2z2, // otrans-size -> make const + weights, + significant_weight, + sum_weight, + nr_trans, + nr_oversampled_trans, + nr_oversampled_rot, + oversamples, + skip_rots, + p_weights, + p_thr_wsum_prior_offsetx_class, + p_thr_wsum_prior_offsety_class, + p_thr_wsum_prior_offsetz_class, + p_thr_wsum_sigma2_offset, + rot_idx, + trans_idx, + jobOrigin, + jobExtent); #else CpuKernels::collect2jobs(grid_dim, SUMW_BLOCK_SIZE, oo_otrans_x, // otrans-size -> make const @@ -1637,53 +2229,7 @@ void runCollect2jobs( int grid_dim, #endif } } -//void windowFourierTransform2( -// XFLOAT *d_in_real, -// XFLOAT *d_in_imag, -// XFLOAT *d_out_real, -// XFLOAT *d_out_imag, -// unsigned iX, unsigned iY, unsigned iZ, //Input dimensions -// unsigned oX, unsigned oY, unsigned oZ, //Output dimensions -// cudaStream_t stream -// ) -//{ -// if (iX > 1 && iY/2 + 1 != iX) -// REPORT_ERROR("windowFourierTransform ERROR: the Fourier transform should be of an image with equal sizes in all dimensions!"); -// -// if (oY == iX) -// REPORT_ERROR("windowFourierTransform ERROR: there is a one-to-one map between input and output!"); -// -// cudaMemInit( d_out_real, 0, (size_t) oX*oY*oZ, stream ); -// cudaMemInit( d_out_imag, 0, (size_t) oX*oY*oZ, stream ); -// -// if (oY > iX) -// { -// long int max_r2 = (iX - 1) * (iX - 1); -// -// unsigned grid_dim = ceil((float)(iX*iY*iZ) / (float) WINDOW_FT_BLOCK_SIZE); -// cuda_kernel_window_fourier_transform<<< grid_dim, WINDOW_FT_BLOCK_SIZE, 0, stream >>>( -// d_in_real, -// d_in_imag, -// d_out_real, -// d_out_imag, -// iX, iY, iZ, iX * iY, //Input dimensions -// oX, oY, oZ, oX * oY, //Output dimensions -// iX*iY*iZ, -// max_r2 ); -// } -// else -// { -// unsigned grid_dim = ceil((float)(oX*oY*oZ) / (float) WINDOW_FT_BLOCK_SIZE); -// cuda_kernel_window_fourier_transform<<< grid_dim, WINDOW_FT_BLOCK_SIZE, 0, stream >>>( -// d_in_real, -// d_in_imag, -// d_out_real, -// d_out_imag, -// iX, iY, iZ, iX * iY, //Input dimensions -// oX, oY, oZ, oX * oY, //Output dimensions -// oX*oY*oZ); -// } -//} + #define WINDOW_FT_BLOCK_SIZE 128 void windowFourierTransform2( @@ -1693,7 +2239,7 @@ void windowFourierTransform2( size_t oX, size_t oY, size_t oZ, //Output dimensions size_t Npsi, size_t pos, - cudaStream_t stream) + deviceStream_t stream) { if (iX > 1 && iY/2 + 1 != iX) REPORT_ERROR("windowFourierTransform ERROR: the Fourier transform should be of an image with equal sizes in all dimensions!"); @@ -1703,13 +2249,19 @@ void windowFourierTransform2( deviceInitComplexValue(d_out, (XFLOAT)0.); +#ifdef _CUDA_ENABLED HANDLE_ERROR(cudaStreamSynchronize(d_out.getStream())); - +#elif _HIP_ENABLED + HANDLE_ERROR(hipStreamSynchronize(d_out.getStream())); +#endif if(oX==iX) { - HANDLE_ERROR(cudaStreamSynchronize(d_in.getStream())); #ifdef _CUDA_ENABLED + HANDLE_ERROR(cudaStreamSynchronize(d_in.getStream())); cudaCpyDeviceToDevice(&d_in(pos), ~d_out, oX*oY*oZ*Npsi, d_out.getStream() ); +#elif _HIP_ENABLED + HANDLE_ERROR(hipStreamSynchronize(d_in.getStream())); + hipCpyDeviceToDevice(&d_in(pos), ~d_out, oX*oY*oZ*Npsi, d_out.getStream() ); #else memcpy(&d_out[0], &d_in[0], oX*oY*oZ*Npsi*sizeof(ACCCOMPLEX)); #endif @@ -1731,6 +2283,29 @@ void windowFourierTransform2( WINDOW_FT_BLOCK_SIZE, max_r2); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + dim3 grid_dim(ceil((float)(iX*iY*iZ) / (float) WINDOW_FT_BLOCK_SIZE),Npsi); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_window_fourier_transform), grid_dim, dim3(WINDOW_FT_BLOCK_SIZE), 0, d_out.getStream(), + &d_in(pos), + ~d_out, + iX, iY, iZ, iX * iY, //Input dimensions + oX, oY, oZ, oX * oY, //Output dimensions + iX*iY*iZ, + WINDOW_FT_BLOCK_SIZE, + max_r2); + LAUNCH_HANDLE_ERROR(hipGetLastError()); +#elif _SYCL_ENABLED + size_t grid_dim = (size_t)( ceil((float)(iX*iY*iZ) / (float) WINDOW_FT_BLOCK_SIZE)); + syclKernels::window_fourier_transform( + grid_dim, + Npsi, + WINDOW_FT_BLOCK_SIZE, + &d_in[pos], + &d_out[0], + iX, iY, iZ, iX * iY, //Input dimensions + oX, oY, oZ, oX * oY, //Output dimensions + iX*iY*iZ, + max_r2); #else size_t grid_dim = (size_t)( ceil((float)(iX*iY*iZ) / (float) WINDOW_FT_BLOCK_SIZE)); CpuKernels::window_fourier_transform( @@ -1757,6 +2332,28 @@ void windowFourierTransform2( oX*oY*oZ, WINDOW_FT_BLOCK_SIZE); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + dim3 grid_dim(ceil((float)(oX*oY*oZ) / (float) WINDOW_FT_BLOCK_SIZE),Npsi); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_window_fourier_transform), grid_dim, dim3(WINDOW_FT_BLOCK_SIZE), 0, d_out.getStream(), + &d_in(pos), + ~d_out, + iX, iY, iZ, iX * iY, //Input dimensions + oX, oY, oZ, oX * oY, //Output dimensions + oX*oY*oZ, + WINDOW_FT_BLOCK_SIZE); + LAUNCH_HANDLE_ERROR(hipGetLastError()); +#elif _SYCL_ENABLED + int grid_dim = (int)( ceil((float)(oX*oY*oZ) / (float) WINDOW_FT_BLOCK_SIZE)); + syclKernels::window_fourier_transform( + grid_dim, + Npsi, + WINDOW_FT_BLOCK_SIZE, + &d_in[pos], + &d_out[0], + iX, iY, iZ, iX * iY, //Input dimensions + oX, oY, oZ, oX * oY, //Output dimensions + oX*oY*oZ + ); #else int grid_dim = (int)( ceil((float)(oX*oY*oZ) / (float) WINDOW_FT_BLOCK_SIZE)); CpuKernels::window_fourier_transform( @@ -1777,13 +2374,13 @@ void run_calcPowerSpectrum(Complex *dFaux, int padoridim, Complex *ddata, int da int max_r2, int min_r2, RFLOAT normfft, RFLOAT padding_factor, RFLOAT weight, RFLOAT *dfourier_mask, int fx, int fy, int fz, bool do_fourier_mask, bool if3D) { -#ifdef CUDA +#ifdef _CUDA_ENABLED dim3 bs(32,4); dim3 gs(ceil((padoridim/2+1)/(float)bs.x), ceil(padoridim/(float)bs.y)); if(if3D) { bs.z = 2; - gs.z = ceil(padoridim/(float)bs.z); + gs.z = ceil(padoridim/(float)bs.z); } if(sizeof(RFLOAT) == sizeof(double)) cuda_kernel_calcPowerSpectrum<<>>((double2*)dFaux,padoridim,(double2*)ddata,data_sz,dpower_spectrum,dcounter, @@ -1792,22 +2389,40 @@ void run_calcPowerSpectrum(Complex *dFaux, int padoridim, Complex *ddata, int da cuda_kernel_calcPowerSpectrum<<>>((float2*)dFaux,padoridim,(float2*)ddata,data_sz,dpower_spectrum,dcounter, max_r2,min_r2,normfft,padding_factor,weight,dfourier_mask,fx,fy,fz,do_fourier_mask); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + dim3 bs(32,4); + dim3 gs(ceil((padoridim/2+1)/(float)bs.x), ceil(padoridim/(float)bs.y)); + if(if3D) + { + bs.z = 2; + gs.z = ceil(padoridim/(float)bs.z); + } + if(sizeof(RFLOAT) == sizeof(double)) + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_calcPowerSpectrum), gs, bs, 0, 0, (double2*)dFaux,padoridim,(double2*)ddata,data_sz,dpower_spectrum,dcounter, + max_r2,min_r2,normfft,padding_factor,weight,dfourier_mask,fx,fy,fz,do_fourier_mask); + else + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_calcPowerSpectrum), gs, bs, 0, 0, (float2*)dFaux,padoridim,(float2*)ddata,data_sz,dpower_spectrum,dcounter, + max_r2,min_r2,normfft,padding_factor,weight,dfourier_mask,fx,fy,fz,do_fourier_mask); + LAUNCH_HANDLE_ERROR(hipGetLastError()); #endif } void run_updatePowerSpectrum(RFLOAT *dcounter, int sz, RFLOAT *dpower_spectrum) { -#ifdef CUDA +#ifdef _CUDA_ENABLED cuda_kernel_updatePowerSpectrum<<>>(dcounter, dpower_spectrum, sz); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_updatePowerSpectrum), dim3(ceil(sz/(float)256)), dim3(256), 0, 0, dcounter, dpower_spectrum, sz); + LAUNCH_HANDLE_ERROR(hipGetLastError()); #endif } -void scale(RFLOAT *img, size_t sz, RFLOAT val, cudaStream_t stream) +void scale(RFLOAT *img, size_t sz, RFLOAT val, deviceStream_t stream) { int block_size = 256; int MultiBsize = ceil(sz/(float)block_size); -#ifdef CUDA +#if defined _CUDA_ENABLED || defined _HIP_ENABLED AccUtilities::multiply(MultiBsize,block_size, stream, img, val, (size_t)sz); #endif } diff --git a/src/acc/acc_ml_optimiser.h b/src/acc/acc_ml_optimiser.h index 38dec2898..94888eee5 100644 --- a/src/acc/acc_ml_optimiser.h +++ b/src/acc/acc_ml_optimiser.h @@ -5,6 +5,9 @@ #ifdef ALTCPU #include +#elif _SYCL_ENABLED +#include "src/acc/sycl/sycl_virtual_dev.h" +using deviceStream_t = virtualSYCL*; #endif /* @@ -136,22 +139,27 @@ class OptimisationParamters unsigned long part_id; + bool is_tomo; + std::vector > Fimg, Fimg_nomask, local_Fimgs_shifted, local_Fimgs_shifted_nomask; - std::vector > Fctf, local_Fctf, local_Minvsigma2, FstMulti; + std::vector > Fctf, local_Fctf; + MultidimArray FstMulti, local_Minvsigma2; std::vector pointer_dir_nonzeroprior, pointer_psi_nonzeroprior; std::vector directions_prior, psi_prior, local_sqrtXi2; - std::vector highres_Xi2_img, min_diff2; + std::vector highres_Xi2_img; + RFLOAT min_diff2; MultidimArray Mcoarse_significant; // And from storeWeightedSums - std::vector sum_weight, significant_weight, max_weight; - std::vector< std::vector > sum_weight_class; - std::vector > old_offset, prior; + RFLOAT sum_weight, significant_weight, max_weight; + std::vector sum_weight_class; + Matrix1D old_offset, prior; std::vector > power_img; MultidimArray Mweight; - std::vector max_index; + Indices max_index; - OptimisationParamters (unsigned nr_images, unsigned long part_id): + OptimisationParamters (unsigned nr_images, unsigned long part_id, bool is_tomo): metadata_offset(0), + is_tomo(is_tomo), part_id(part_id) { power_img.resize(nr_images); @@ -159,10 +167,6 @@ class OptimisationParamters Fimg.resize(nr_images); Fimg_nomask.resize(nr_images); Fctf.resize(nr_images); - old_offset.resize(nr_images); - prior.resize(nr_images); - max_index.resize(nr_images); - sum_weight_class.resize(nr_images); }; }; @@ -335,6 +339,32 @@ class IndexedDataArray ihidden_overs.setSize(newSize); } +#ifdef _SYCL_ENABLED + void setStream_all(deviceStream_t dev) + { + weights.setStream(dev); + rot_id.setStream(dev); + rot_idx.setStream(dev); + trans_idx.setStream(dev); + ihidden_overs.setStream(dev); + } + + void setAccType_all(AccType accT) + { + weights.setAccType(accT); + rot_id.setAccType(accT); + rot_idx.setAccType(accT); + trans_idx.setAccType(accT); + ihidden_overs.setAccType(accT); + } + + void setStreamAccType_all(deviceStream_t dev, AccType accT = accSYCL) + { + setStream_all(dev); + setAccType_all(accT); + } +#endif + void host_alloc_all() { weights.freeHostIfSet(); diff --git a/src/acc/acc_ml_optimiser_impl.h b/src/acc/acc_ml_optimiser_impl.h index c741c7ed0..1fb2ef405 100644 --- a/src/acc/acc_ml_optimiser_impl.h +++ b/src/acc/acc_ml_optimiser_impl.h @@ -1,6 +1,9 @@ static omp_lock_t global_mutex; -#include "src/ml_optimiser_mpi.h" +#ifdef _SYCL_ENABLED +#include "src/acc/sycl/sycl_virtual_dev.h" +using deviceStream_t = virtualSYCL*; +#endif // ---------------------------------------------------------------------------- // -------------------- getFourierTransformsAndCtfs --------------------------- @@ -22,290 +25,326 @@ void getFourierTransformsAndCtfs(long int part_id, #endif CUSTOM_ALLOCATOR_REGION_NAME("GFTCTF"); + Matrix2D Aori; + int shiftdim = (accMLO->shiftsIs3D) ? 3 : 2 ; + Matrix1D my_projected_com(shiftdim), my_refined_ibody_offset(shiftdim); - for (int img_id = 0; img_id < sp.nr_images; img_id++) - { - CTIC(accMLO->timer,"init"); - FileName fn_img; - Image img, rec_img; - MultidimArray Fimg; - MultidimArray Faux; - MultidimArray Fctf, FstMulti; - Matrix2D Aori; - Matrix1D my_projected_com(baseMLO->mymodel.data_dim), my_refined_ibody_offset(baseMLO->mymodel.data_dim); - - // Which group do I belong? - int group_id =baseMLO->mydata.getGroupId(part_id, img_id); - RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(part_id, img_id); - // What is my optics group? - int optics_group = baseMLO->mydata.getOpticsGroup(part_id, img_id); - bool ctf_premultiplied = baseMLO->mydata.obsModel.getCtfPremultiplied(optics_group); - - // metadata offset for this image in the particle - int my_metadata_offset = op.metadata_offset + img_id; - - // Get the right line in the exp_fn_img strings (also exp_fn_recimg and exp_fn_ctfs) - int istop = 0; - for (long int ii = baseMLO->exp_my_first_part_id; ii < part_id; ii++) - istop += baseMLO->mydata.numberOfImagesInParticle(part_id); - istop += img_id; - - if (!baseMLO->mydata.getImageNameOnScratch(part_id, img_id, fn_img)) - { - std::istringstream split(baseMLO->exp_fn_img); - for (int i = 0; i <= my_metadata_offset; i++) - getline(split, fn_img); - } - sp.current_img = fn_img; + // Get the norm_correction + RFLOAT normcorr = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_NORM); - // Get the norm_correction - RFLOAT normcorr = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_NORM); + // Safeguard against gold-standard separation + if (baseMLO->do_split_random_halves) + { + int halfset = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_NR_SIGN); + if (halfset != baseMLO->my_halfset) + { + std::cerr << "BUG!!! halfset= " << halfset << " my_halfset= " << baseMLO->my_halfset << " part_id= " << part_id << std::endl; + REPORT_ERROR("BUG! Mixing gold-standard separation!!!!"); + } - // Safeguard against gold-standard separation - if (baseMLO->do_split_random_halves) - { - int halfset = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_NR_SIGN); - if (halfset != baseMLO->my_halfset) - { - std::cerr << "BUG!!! halfset= " << halfset << " my_halfset= " << baseMLO->my_halfset << " part_id= " << part_id << std::endl; - REPORT_ERROR("BUG! Mixing gold-standard separation!!!!"); - } + } - } + // Get the optimal origin offsets from the previous iteration + // Sjors 5mar18: it is very important that my_old_offset has baseMLO->mymodel.data_dim and not just (3), as transformCartesianAndHelicalCoords will give different results!!! + Matrix1D my_old_offset(shiftdim), my_prior(shiftdim), my_old_offset_ori; + int icol_rot, icol_tilt, icol_psi, icol_xoff, icol_yoff, icol_zoff; + XX(my_old_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_XOFF); + YY(my_old_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_YOFF); + XX(my_prior) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_XOFF_PRIOR); + YY(my_prior) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_YOFF_PRIOR); + // Uninitialised priors were set to 999. + if (XX(my_prior) > 998.99 && XX(my_prior) < 999.01) + XX(my_prior) = 0.; + if (YY(my_prior) > 998.99 && YY(my_prior) < 999.01) + YY(my_prior) = 0.; + + if (accMLO->shiftsIs3D) + { + ZZ(my_old_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ZOFF); + ZZ(my_prior) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ZOFF_PRIOR); + // Unitialised priors were set to 999. + if (ZZ(my_prior) > 998.99 && ZZ(my_prior) < 999.01) + ZZ(my_prior) = 0.; + } - // Get the optimal origin offsets from the previous iteration - // Sjors 5mar18: it is very important that my_old_offset has baseMLO->mymodel.data_dim and not just (3), as transformCartesianAndHelicalCoords will give different results!!! - Matrix1D my_old_offset(baseMLO->mymodel.data_dim), my_prior(baseMLO->mymodel.data_dim), my_old_offset_ori; - int icol_rot, icol_tilt, icol_psi, icol_xoff, icol_yoff, icol_zoff; - XX(my_old_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_XOFF); - YY(my_old_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_YOFF); - XX(my_prior) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_XOFF_PRIOR); - YY(my_prior) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_YOFF_PRIOR); - // Uninitialised priors were set to 999. - if (XX(my_prior) > 998.99 && XX(my_prior) < 999.01) - XX(my_prior) = 0.; - if (YY(my_prior) > 998.99 && YY(my_prior) < 999.01) - YY(my_prior) = 0.; - - if (accMLO->dataIs3D) - { - ZZ(my_old_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ZOFF); - ZZ(my_prior) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ZOFF_PRIOR); - // Unitialised priors were set to 999. - if (ZZ(my_prior) > 998.99 && ZZ(my_prior) < 999.01) - ZZ(my_prior) = 0.; - } + if (baseMLO->mymodel.nr_bodies > 1) + { - if (baseMLO->mymodel.nr_bodies > 1) - { + // 17May2017: Shift image to the projected COM for this body! + // Aori is the original transformation matrix of the consensus refinement + Euler_angles2matrix(DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT), + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT), + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI), Aori, false); + my_projected_com = Aori * baseMLO->mymodel.com_bodies[ibody]; + // This will have made my_projected_com of size 3 again! resize to shiftdim + int shiftdim = (accMLO->shiftsIs3D) ? 3 : 2 ; + my_projected_com.resize(shiftdim); + + // Subtract the projected COM offset, to position this body in the center + // Also keep the my_old_offset in my_old_offset_ori + my_old_offset_ori = my_old_offset; + my_old_offset -= my_projected_com; + + // Also get refined offset for this body + icol_xoff = 3 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + icol_yoff = 4 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + icol_zoff = 5 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + XX(my_refined_ibody_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_xoff); + YY(my_refined_ibody_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_yoff); + if (accMLO->shiftsIs3D) + ZZ(my_refined_ibody_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_zoff); + + // For multi-body refinement: set the priors of the translations to zero (i.e. everything centred around consensus offset) + my_prior.initZeros(); + } - // 17May2017: Shift image to the projected COM for this body! - // Aori is the original transformation matrix of the consensus refinement - Euler_angles2matrix(DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ROT), - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_TILT), - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PSI), Aori, false); - my_projected_com = Aori * baseMLO->mymodel.com_bodies[ibody]; - // This will have made my_projected_com of size 3 again! resize to mymodel.data_dim - my_projected_com.resize(baseMLO->mymodel.data_dim); - - // Subtract the projected COM offset, to position this body in the center - // Also keep the my_old_offset in my_old_offset_ori - my_old_offset_ori = my_old_offset; - my_old_offset -= my_projected_com; - - // Also get refined offset for this body - icol_xoff = 3 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - icol_yoff = 4 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - icol_zoff = 5 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - XX(my_refined_ibody_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_xoff); - YY(my_refined_ibody_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_yoff); - if (baseMLO->mymodel.data_dim == 3) - ZZ(my_refined_ibody_offset) = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_zoff); - - // For multi-body refinement: set the priors of the translations to zero (i.e. everything centred around consensus offset) - my_prior.initZeros(); - } + // Store priors on translations + op.prior = my_prior; - CTOC(accMLO->timer,"init"); + CTOC(accMLO->timer,"init"); - CTIC(accMLO->timer,"nonZeroProb"); - // Orientational priors - if (baseMLO->mymodel.nr_bodies > 1 ) - { + CTIC(accMLO->timer,"nonZeroProb"); + // Orientational priors + if (baseMLO->mymodel.nr_bodies > 1 ) + { - // Centre local searches around the orientation from the previous iteration, this one goes with overall sigma2_ang - // On top of that, apply prior on the deviation from (0,0,0) with mymodel.sigma_tilt_bodies[ibody] and mymodel.sigma_psi_bodies[ibody] - icol_rot = 0 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - icol_tilt = 1 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - icol_psi = 2 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - RFLOAT prior_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_rot); - RFLOAT prior_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_tilt); - RFLOAT prior_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_psi); - baseMLO->sampling.selectOrientationsWithNonZeroPriorProbability( - prior_rot, prior_tilt, prior_psi, - sqrt(baseMLO->mymodel.sigma2_rot), - sqrt(baseMLO->mymodel.sigma2_tilt), - sqrt(baseMLO->mymodel.sigma2_psi), - op.pointer_dir_nonzeroprior, op.directions_prior, - op.pointer_psi_nonzeroprior, op.psi_prior, false, 3., - baseMLO->mymodel.sigma_tilt_bodies[ibody], - baseMLO->mymodel.sigma_psi_bodies[ibody]); + // Centre local searches around the orientation from the previous iteration, this one goes with overall sigma2_ang + // On top of that, apply prior on the deviation from (0,0,0) with mymodel.sigma_tilt_bodies[ibody] and mymodel.sigma_psi_bodies[ibody] + icol_rot = 0 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + icol_tilt = 1 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + icol_psi = 2 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + RFLOAT prior_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_rot); + RFLOAT prior_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_tilt); + RFLOAT prior_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_psi); + baseMLO->sampling.selectOrientationsWithNonZeroPriorProbability( + prior_rot, prior_tilt, prior_psi, + sqrt(baseMLO->mymodel.sigma2_rot), + sqrt(baseMLO->mymodel.sigma2_tilt), + sqrt(baseMLO->mymodel.sigma2_psi), + op.pointer_dir_nonzeroprior, op.directions_prior, + op.pointer_psi_nonzeroprior, op.psi_prior, false, 3., + baseMLO->mymodel.sigma_tilt_bodies[ibody], + baseMLO->mymodel.sigma_psi_bodies[ibody]); - } - else if (baseMLO->mymodel.orientational_prior_mode != NOPRIOR && !(baseMLO->do_skip_align ||baseMLO-> do_skip_rotate)) - { - // First try if there are some fixed prior angles - // For multi-body refinements, ignore the original priors and get the refined residual angles from the previous iteration - RFLOAT prior_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ROT_PRIOR); - RFLOAT prior_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_TILT_PRIOR); - RFLOAT prior_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PSI_PRIOR); - RFLOAT prior_psi_flip_ratio = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PSI_PRIOR_FLIP_RATIO); - - bool do_auto_refine_local_searches = (baseMLO->do_auto_refine) && (baseMLO->sampling.healpix_order >= baseMLO->autosampling_hporder_local_searches); - bool do_classification_local_searches = (! baseMLO->do_auto_refine) && (baseMLO->mymodel.orientational_prior_mode == PRIOR_ROTTILT_PSI) - && (baseMLO->mymodel.sigma2_rot > 0.) && (baseMLO->mymodel.sigma2_tilt > 0.) && (baseMLO->mymodel.sigma2_psi > 0.); - bool do_local_angular_searches = (do_auto_refine_local_searches) || (do_classification_local_searches); - - // If there were no defined priors (i.e. their values were 999.), then use the "normal" angles - if (prior_rot > 998.99 && prior_rot < 999.01) - prior_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ROT); - if (prior_tilt > 998.99 && prior_tilt < 999.01) - prior_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_TILT); - if (prior_psi > 998.99 && prior_psi < 999.01) - prior_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PSI); - if (prior_psi_flip_ratio > 998.99 && prior_psi_flip_ratio < 999.01) - prior_psi_flip_ratio = 0.5; - - ////////// How does this work now: each particle has a different sampling object?!!! - // Select only those orientations that have non-zero prior probability - - if (baseMLO->do_helical_refine && baseMLO->mymodel.ref_dim == 3) - { - baseMLO->sampling.selectOrientationsWithNonZeroPriorProbabilityFor3DHelicalReconstruction(prior_rot, prior_tilt, prior_psi, - sqrt(baseMLO->mymodel.sigma2_rot), sqrt(baseMLO->mymodel.sigma2_tilt), sqrt(baseMLO->mymodel.sigma2_psi), - op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior, - do_local_angular_searches, prior_psi_flip_ratio); - } - else - { - baseMLO->sampling.selectOrientationsWithNonZeroPriorProbability(prior_rot, prior_tilt, prior_psi, - sqrt(baseMLO->mymodel.sigma2_rot), sqrt(baseMLO->mymodel.sigma2_tilt), sqrt(baseMLO->mymodel.sigma2_psi), - op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior); - } + } + else if (baseMLO->mymodel.orientational_prior_mode != NOPRIOR && !(baseMLO->do_skip_align ||baseMLO-> do_skip_rotate)) + { + // First try if there are some fixed prior angles + // For multi-body refinements, ignore the original priors and get the refined residual angles from the previous iteration + RFLOAT prior_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT_PRIOR); + RFLOAT prior_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT_PRIOR); + RFLOAT prior_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI_PRIOR); + RFLOAT prior_psi_flip_ratio = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI_PRIOR_FLIP_RATIO); + + bool do_auto_refine_local_searches = (baseMLO->do_auto_refine) && (baseMLO->sampling.healpix_order >= baseMLO->autosampling_hporder_local_searches); + bool do_classification_local_searches = (! baseMLO->do_auto_refine) && (baseMLO->mymodel.orientational_prior_mode == PRIOR_ROTTILT_PSI) + && (baseMLO->mymodel.sigma2_rot > 0.) && (baseMLO->mymodel.sigma2_tilt > 0.) && (baseMLO->mymodel.sigma2_psi > 0.); + bool do_local_angular_searches = (do_auto_refine_local_searches) || (do_classification_local_searches); + + // If there were no defined priors (i.e. their values were 999.), then use the "normal" angles + if (prior_rot > 998.99 && prior_rot < 999.01) + prior_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); + if (prior_tilt > 998.99 && prior_tilt < 999.01) + prior_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); + if (prior_psi > 998.99 && prior_psi < 999.01) + prior_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); + if (prior_psi_flip_ratio > 998.99 && prior_psi_flip_ratio < 999.01) + prior_psi_flip_ratio = 0.5; + + ////////// How does this work now: each particle has a different sampling object?!!! + // Select only those orientations that have non-zero prior probability + + if (baseMLO->do_helical_refine && baseMLO->mymodel.ref_dim == 3) + { + baseMLO->sampling.selectOrientationsWithNonZeroPriorProbabilityFor3DHelicalReconstruction(prior_rot, prior_tilt, prior_psi, + sqrt(baseMLO->mymodel.sigma2_rot), sqrt(baseMLO->mymodel.sigma2_tilt), sqrt(baseMLO->mymodel.sigma2_psi), + op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior, + do_local_angular_searches, prior_psi_flip_ratio); + } + else + { + baseMLO->sampling.selectOrientationsWithNonZeroPriorProbability(prior_rot, prior_tilt, prior_psi, + sqrt(baseMLO->mymodel.sigma2_rot), sqrt(baseMLO->mymodel.sigma2_tilt), sqrt(baseMLO->mymodel.sigma2_psi), + op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior); + } - long int nr_orients = baseMLO->sampling.NrDirections(0, &op.pointer_dir_nonzeroprior) * baseMLO->sampling.NrPsiSamplings(0, &op.pointer_psi_nonzeroprior); - if (nr_orients == 0) - { - std::cerr << " sampling.NrDirections()= " << baseMLO->sampling.NrDirections(0, &op.pointer_dir_nonzeroprior) - << " sampling.NrPsiSamplings()= " << baseMLO->sampling.NrPsiSamplings(0, &op.pointer_psi_nonzeroprior) << std::endl; - REPORT_ERROR("Zero orientations fall within the local angular search. Increase the sigma-value(s) on the orientations!"); - } + long int nr_orients = baseMLO->sampling.NrDirections(0, &op.pointer_dir_nonzeroprior) * baseMLO->sampling.NrPsiSamplings(0, &op.pointer_psi_nonzeroprior); + if (nr_orients == 0) + { + std::cerr << " sampling.NrDirections()= " << baseMLO->sampling.NrDirections(0, &op.pointer_dir_nonzeroprior) + << " sampling.NrPsiSamplings()= " << baseMLO->sampling.NrPsiSamplings(0, &op.pointer_psi_nonzeroprior) << std::endl; + REPORT_ERROR("Zero orientations fall within the local angular search. Increase the sigma-value(s) on the orientations!"); + } - } - CTOC(accMLO->timer,"nonZeroProb"); + } + CTOC(accMLO->timer,"nonZeroProb"); + + // Helical reconstruction: calculate old_offset in the system of coordinates of the helix, i.e. parallel & perpendicular, depending on psi-angle! + // For helices do NOT apply old_offset along the direction of the helix!! + Matrix1D my_old_offset_helix_coords; + RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); + RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); + RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); + if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) ) + { + // Calculate my_old_offset_helix_coords from my_old_offset and psi angle + transformCartesianAndHelicalCoords(my_old_offset, my_old_offset_helix_coords, rot_deg, tilt_deg, psi_deg, CART_TO_HELICAL_COORDS); + // We do NOT want to accumulate the offsets in the direction along the helix (which is X in the helical coordinate system!) + // However, when doing helical local searches, we accumulate offsets + // Do NOT accumulate offsets in 3D classification of helices + if ( (! baseMLO->do_skip_align) && (! baseMLO->do_skip_rotate) ) + { + // TODO: check whether the following lines make sense + bool do_auto_refine_local_searches = (baseMLO->do_auto_refine) && (baseMLO->sampling.healpix_order >= baseMLO->autosampling_hporder_local_searches); + bool do_classification_local_searches = (! baseMLO->do_auto_refine) && (baseMLO->mymodel.orientational_prior_mode == PRIOR_ROTTILT_PSI) + && (baseMLO->mymodel.sigma2_rot > 0.) && (baseMLO->mymodel.sigma2_tilt > 0.) && (baseMLO->mymodel.sigma2_psi > 0.); + bool do_local_angular_searches = (do_auto_refine_local_searches) || (do_classification_local_searches); + if (!do_local_angular_searches) + { + if (accMLO->shiftsIs3D) + ZZ(my_old_offset_helix_coords) = 0.; + else + XX(my_old_offset_helix_coords) = 0.; + } + } + // TODO: Now re-calculate the my_old_offset in the real (or image) system of coordinate (rotate -psi angle) + transformCartesianAndHelicalCoords(my_old_offset_helix_coords, my_old_offset, rot_deg, tilt_deg, psi_deg, HELICAL_TO_CART_COORDS); + } + CTOC(accMLO->timer,"HelicalPrep"); - // ------------------------------------------------------------------------------------------ + // ------------------------------------------------------------------------------------------ - CTIC(accMLO->timer,"readData"); - // Get the image and recimg data - if (baseMLO->do_parallel_disc_io) - { + my_old_offset.selfROUND(); // Below, this rounded my_old_offset will be applied to the actual images - // If all followers had preread images into RAM: get those now - if (baseMLO->do_preread_images) - { + // ------------------------------------------------------------------------------------------ - img().reshape(baseMLO->mydata.particles[part_id].images[img_id].img); - CTIC(accMLO->timer,"ParaReadPrereadImages"); - FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(baseMLO->mydata.particles[part_id].images[img_id].img) - { - DIRECT_MULTIDIM_ELEM(img(), n) = (RFLOAT)DIRECT_MULTIDIM_ELEM(baseMLO->mydata.particles[part_id].images[img_id].img, n); - } - CTOC(accMLO->timer,"ParaReadPrereadImages"); - } - else - { - if (accMLO->dataIs3D) - { - CTIC(accMLO->timer,"ParaRead3DImages"); - img.read(fn_img); - img().setXmippOrigin(); - CTOC(accMLO->timer,"ParaRead3DImages"); - } - else - { - CTIC(accMLO->timer,"ParaRead2DImages"); - img() = baseMLO->exp_imgs[my_metadata_offset]; - CTOC(accMLO->timer,"ParaRead2DImages"); - } - } - if (baseMLO->has_converged && baseMLO->do_use_reconstruct_images) - { - FileName fn_recimg; - std::istringstream split2(baseMLO->exp_fn_recimg); - // Get the right line in the exp_fn_img string - for (int i = 0; i <= my_metadata_offset; i++) - getline(split2, fn_recimg); - rec_img.read(fn_recimg); - rec_img().setXmippOrigin(); - } - } - else - { - // Unpack the image from the imagedata - if (accMLO->dataIs3D) - { - CTIC(accMLO->timer,"Read3DImages"); - CTIC(accMLO->timer,"resize"); - img().resize(baseMLO->image_full_size[optics_group], baseMLO->image_full_size[optics_group], baseMLO->image_full_size[optics_group]); - CTOC(accMLO->timer,"resize"); - // Only allow a single image per call of this function!!! nr_pool needs to be set to 1!!!! - // This will save memory, as we'll need to store all translated images in memory.... - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(img()) - { - DIRECT_A3D_ELEM(img(), k, i, j) = DIRECT_A3D_ELEM(baseMLO->exp_imagedata, k, i, j); - } - img().setXmippOrigin(); + if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) ) + { + // Transform rounded Cartesian offsets to corresponding helical ones + transformCartesianAndHelicalCoords(my_old_offset, my_old_offset_helix_coords, rot_deg, tilt_deg, psi_deg, CART_TO_HELICAL_COORDS); + op.old_offset = my_old_offset_helix_coords; + } + else + { + // For multi-bodies: store only the old refined offset, not the constant consensus offset or the projected COM of this body + if (baseMLO->mymodel.nr_bodies > 1) + op.old_offset = my_refined_ibody_offset; + else + op.old_offset = my_old_offset; // Not doing helical refinement. Rounded Cartesian offsets are stored. + } - if (baseMLO->has_converged && baseMLO->do_use_reconstruct_images) - { - rec_img().resize(baseMLO->image_full_size[optics_group], baseMLO->image_full_size[optics_group], baseMLO->image_full_size[optics_group]); - int offset = (baseMLO->do_ctf_correction) ? 2 * baseMLO->image_full_size[optics_group] : baseMLO->image_full_size[optics_group]; - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(rec_img()) - { - DIRECT_A3D_ELEM(rec_img(), k, i, j) = DIRECT_A3D_ELEM(baseMLO->exp_imagedata, offset + k, i, j); - } - rec_img().setXmippOrigin(); + // Which group do I belong? + int group_id =baseMLO->mydata.getGroupId(part_id); + RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(part_id); + // What is my optics group? + int optics_group = baseMLO->mydata.getOpticsGroup(part_id); + bool ctf_premultiplied = baseMLO->mydata.obsModel.getCtfPremultiplied(optics_group); - } - CTOC(accMLO->timer,"Read3DImages"); + FileName fn_img; + Image img, rec_img; + if (!baseMLO->mydata.getImageNameOnScratch(part_id, fn_img)) + { + std::istringstream split(baseMLO->exp_fn_img); + for (int i = 0; i <= op.metadata_offset; i++) + getline(split, fn_img); + } + sp.current_img = fn_img; - } - else - { - CTIC(accMLO->timer,"Read2DImages"); - img().resize(baseMLO->image_full_size[optics_group], baseMLO->image_full_size[optics_group]); - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(img()) - { - DIRECT_A2D_ELEM(img(), i, j) = DIRECT_A3D_ELEM(baseMLO->exp_imagedata, my_metadata_offset, i, j); - } - img().setXmippOrigin(); - if (baseMLO->has_converged && baseMLO->do_use_reconstruct_images) - { + CTIC(accMLO->timer,"readData"); + // Get the image and recimg data + if (baseMLO->do_parallel_disc_io) + { - /// TODO: this will be WRONG for multi-image particles, but I guess that's not going to happen anyway... - int my_nr_particles = baseMLO->exp_my_last_part_id - baseMLO->exp_my_first_part_id + 1; - rec_img().resize(baseMLO->image_full_size[optics_group], baseMLO->image_full_size[optics_group]); - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(rec_img()) - { - DIRECT_A2D_ELEM(rec_img(), i, j) = DIRECT_A3D_ELEM(baseMLO->exp_imagedata, my_nr_particles + my_metadata_offset, i, j); - } - rec_img().setXmippOrigin(); - } - CTOC(accMLO->timer,"Read2DImages"); - } - } - CTOC(accMLO->timer,"readData"); + // If all followers had preread images into RAM: get those now + if (baseMLO->do_preread_images) + { + + img().reshape(baseMLO->mydata.particles[part_id].img); + CTIC(accMLO->timer,"ParaReadPrereadImages"); + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(baseMLO->mydata.particles[part_id].img) + { + DIRECT_MULTIDIM_ELEM(img(), n) = (RFLOAT)DIRECT_MULTIDIM_ELEM(baseMLO->mydata.particles[part_id].img, n); + } + CTOC(accMLO->timer,"ParaReadPrereadImages"); + } + else + { + if (accMLO->dataIs3D) + { + CTIC(accMLO->timer,"ParaRead3DImages"); + img.read(fn_img); + img().setXmippOrigin(); + CTOC(accMLO->timer,"ParaRead3DImages"); + } + else + { + CTIC(accMLO->timer,"ParaRead2DImages"); + img() = baseMLO->exp_imgs[op.metadata_offset]; + CTOC(accMLO->timer,"ParaRead2DImages"); + } + } + if (baseMLO->has_converged && baseMLO->do_use_reconstruct_images) + { + FileName fn_recimg; + std::istringstream split2(baseMLO->exp_fn_recimg); + // Get the right line in the exp_fn_img string + for (int i = 0; i <= op.metadata_offset; i++) + getline(split2, fn_recimg); + rec_img.read(fn_recimg); + rec_img().setXmippOrigin(); + } + } + else + { + // Unpack the image from the imagedata + if (accMLO->dataIs3D || op.is_tomo) REPORT_ERROR("BUG: STA should always use parallel disc IO"); + + CTIC(accMLO->timer,"Read2DImages"); + img().resize(baseMLO->image_full_size[optics_group], baseMLO->image_full_size[optics_group]); + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(img()) + { + DIRECT_A2D_ELEM(img(), i, j) = DIRECT_A3D_ELEM(baseMLO->exp_imagedata, op.metadata_offset, i, j); + } + img().setXmippOrigin(); + if (baseMLO->has_converged && baseMLO->do_use_reconstruct_images) + { + + /// TODO: this will be WRONG for multi-image particles, but I guess that's not going to happen anyway... + int my_nr_particles = baseMLO->exp_my_last_part_id - baseMLO->exp_my_first_part_id + 1; + if (op.is_tomo) REPORT_ERROR("ERROR: you can not use reconstruct images for 2Dstack-subtomograms!"); + + rec_img().resize(baseMLO->image_full_size[optics_group], baseMLO->image_full_size[optics_group]); + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(rec_img()) + { + DIRECT_A2D_ELEM(rec_img(), i, j) = DIRECT_A3D_ELEM(baseMLO->exp_imagedata, my_nr_particles + op.metadata_offset, i, j); + } + rec_img().setXmippOrigin(); + } + CTOC(accMLO->timer,"Read2DImages"); + + } + CTOC(accMLO->timer,"readData"); + + Image wholestack; + if (op.is_tomo) wholestack=img; + + for (int img_id = 0; img_id < sp.nr_images; img_id++) + { + CTIC(accMLO->timer,"init"); + MultidimArray Fimg; + MultidimArray Faux; + MultidimArray Fctf, FstMulti; + + // For 2D stacks in subtomogram averaging: + if (op.is_tomo) + { + MultidimArray my_img; + wholestack().getImage(img_id, my_img); + img() = my_img; + + // Keep track whether this image is empty + baseMLO->mydata.particles[part_id].images[img_id].is_empty = (img().computeStddev() == 0.); + } // ------------------------------------------------------------------------------------------ @@ -316,8 +355,11 @@ void getFourierTransformsAndCtfs(long int part_id, Fimg.initZeros(current_size_z, current_size_y, current_size_x); // ------------------------------------------------------------------------------------------ - +#ifdef _HIP_ENABLED + CTIC(hipMLO->timer,"makeNoiseMask"); +#else CTIC(cudaMLO->timer,"makeNoiseMask"); +#endif // Either mask with zeros or noise. Here, make a noise-image that will be optional in the softMask-kernel. AccDataTypes::Image RandomImage(img(),ptrFactory); @@ -345,8 +387,11 @@ void getFourierTransformsAndCtfs(long int part_id, DIRECT_A1D_ELEM(remapped_sigma2_noise, i_remap) = DIRECT_A1D_ELEM(baseMLO->mymodel.sigma2_noise[optics_group], i); } - - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#ifdef _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); +#else + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#endif // construct the noise-image AccUtilities::makeNoiseImage( temp_sigmaFudgeFactor, remapped_sigma2_noise, @@ -354,9 +399,17 @@ void getFourierTransformsAndCtfs(long int part_id, accMLO, RandomImage, RandomImage.is3D()); - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#ifdef _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); +#else + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#endif } +#ifdef _HIP_ENABLED + CTOC(hipMLO->timer,"makeNoiseMask"); +#else CTOC(cudaMLO->timer,"makeNoiseMask"); +#endif // ------------------------------------------------------------------------------------------ @@ -367,46 +420,6 @@ void getFourierTransformsAndCtfs(long int part_id, * allocation-cost to that region. /BjoernF,160129 */ - // Apply (rounded) old offsets first - my_old_offset.selfROUND(); - - // Helical reconstruction: calculate old_offset in the system of coordinates of the helix, i.e. parallel & perpendicular, depending on psi-angle! - // For helices do NOT apply old_offset along the direction of the helix!! - Matrix1D my_old_offset_helix_coords; - RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ROT); - RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_TILT); - RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PSI); - if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) ) - { - // Calculate my_old_offset_helix_coords from my_old_offset and psi angle - transformCartesianAndHelicalCoords(my_old_offset, my_old_offset_helix_coords, rot_deg, tilt_deg, psi_deg, CART_TO_HELICAL_COORDS); - // We do NOT want to accumulate the offsets in the direction along the helix (which is X in the helical coordinate system!) - // However, when doing helical local searches, we accumulate offsets - // Do NOT accumulate offsets in 3D classification of helices - if ( (! baseMLO->do_skip_align) && (! baseMLO->do_skip_rotate) ) - { - // TODO: check whether the following lines make sense - bool do_auto_refine_local_searches = (baseMLO->do_auto_refine) && (baseMLO->sampling.healpix_order >= baseMLO->autosampling_hporder_local_searches); - bool do_classification_local_searches = (! baseMLO->do_auto_refine) && (baseMLO->mymodel.orientational_prior_mode == PRIOR_ROTTILT_PSI) - && (baseMLO->mymodel.sigma2_rot > 0.) && (baseMLO->mymodel.sigma2_tilt > 0.) && (baseMLO->mymodel.sigma2_psi > 0.); - bool do_local_angular_searches = (do_auto_refine_local_searches) || (do_classification_local_searches); - if (!do_local_angular_searches) - { - if (! accMLO->dataIs3D) - XX(my_old_offset_helix_coords) = 0.; - else - ZZ(my_old_offset_helix_coords) = 0.; - } - } - // TODO: Now re-calculate the my_old_offset in the real (or image) system of coordinate (rotate -psi angle) - transformCartesianAndHelicalCoords(my_old_offset_helix_coords, my_old_offset, rot_deg, tilt_deg, psi_deg, HELICAL_TO_CART_COORDS); - } - CTOC(accMLO->timer,"HelicalPrep"); - - // ------------------------------------------------------------------------------------------ - - my_old_offset.selfROUND(); - // ------------------------------------------------------------------------------------------ CTIC(accMLO->timer,"TranslateAndNormCorrect"); @@ -417,247 +430,333 @@ void getFourierTransformsAndCtfs(long int part_id, d_img.allAlloc(); d_img.allInit(0); - XFLOAT normcorr_val = baseMLO->do_norm_correction ? (XFLOAT)(baseMLO->mymodel.avg_norm_correction / normcorr) : 1; - AccUtilities::TranslateAndNormCorrect( img.data, // input host-side MultidimArray - d_img, // output acc-side Array - normcorr_val, - XX(my_old_offset), - YY(my_old_offset), - (accMLO->dataIs3D) ? ZZ(my_old_offset) : 0., - accMLO->dataIs3D); - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); - - CTOC(accMLO->timer,"TranslateAndNormCorrect"); - - // Set up the UNMASKED image to use for reconstruction, which may be a separate image altogether (rec_img) - // - // d_img has the image information which will be masked - // - if(baseMLO->has_converged && baseMLO->do_use_reconstruct_images) - { - CTIC(accMLO->timer,"TranslateAndNormCorrect_recImg"); - d_rec_img.allAlloc(); - d_rec_img.allInit(0); - AccUtilities::TranslateAndNormCorrect( rec_img.data, // input host-side MultidimArray - d_rec_img, // output acc-side Array - normcorr_val, - XX(my_old_offset), - YY(my_old_offset), - (accMLO->dataIs3D) ? ZZ(my_old_offset) : 0., - accMLO->dataIs3D); - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); - CTOC(accMLO->timer,"TranslateAndNormCorrect_recImg"); - - CTIC(cudaMLO->timer,"normalizeAndTransform_recImg"); - // The image used to reconstruct is not masked, so we transform and beam-tilt it - AccUtilities::normalizeAndTransformImage(d_rec_img, // input acc-side Array - Fimg, // output host-side MultidimArray - accMLO, - current_size_x, - current_size_y, - current_size_z); - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); - CTOC(cudaMLO->timer,"normalizeAndTransform_recImg"); - } - else // if we don't have special images, just use the same as for alignment. But do it here, *before masking* - { - CTIC(cudaMLO->timer,"normalizeAndTransform_recImg"); - // The image used to reconstruct is not masked, so we transform and beam-tilt it - AccUtilities::normalizeAndTransformImage( d_img, // input acc-side Array - Fimg, // output host-side MultidimArray - accMLO, - current_size_x, - current_size_y, - current_size_z); - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); - CTOC(cudaMLO->timer,"normalizeAndTransform_recImg"); - } - - // ------------------------------------------------------------------------------------------ - - if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) ) - { - // Transform rounded Cartesian offsets to corresponding helical ones - transformCartesianAndHelicalCoords(my_old_offset, my_old_offset_helix_coords, rot_deg, tilt_deg, psi_deg, CART_TO_HELICAL_COORDS); - op.old_offset[img_id] = my_old_offset_helix_coords; - } - else - { - // For multi-bodies: store only the old refined offset, not the constant consensus offset or the projected COM of this body - if (baseMLO->mymodel.nr_bodies > 1) - op.old_offset[img_id] = my_refined_ibody_offset; - else - op.old_offset[img_id] = my_old_offset; // Not doing helical refinement. Rounded Cartesian offsets are stored. - } - // Also store priors on translations - op.prior[img_id] = my_prior; - - // ------------------------------------------------------------------------------------------ + // TODO!: think about applying 3D offsets as 2D shifts to the 2D STA stacks here... + if (!op.is_tomo) + { + XFLOAT normcorr_val = baseMLO->do_norm_correction ? (XFLOAT)(baseMLO->mymodel.avg_norm_correction / normcorr) : 1; + AccUtilities::TranslateAndNormCorrect( img.data, // input host-side MultidimArray + d_img, // output acc-side Array + normcorr_val, + XX(my_old_offset), + YY(my_old_offset), + (accMLO->shiftsIs3D) ? ZZ(my_old_offset) : 0., + accMLO->dataIs3D); +#ifdef _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); +#else + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#endif - CTIC(accMLO->timer,"selfApplyBeamTilt"); - baseMLO->mydata.obsModel.demodulatePhase(optics_group, Fimg); - baseMLO->mydata.obsModel.divideByMtf(optics_group, Fimg); - CTOC(accMLO->timer,"selfApplyBeamTilt"); + CTOC(accMLO->timer,"TranslateAndNormCorrect"); + + // Set up the UNMASKED image to use for reconstruction, which may be a separate image altogether (rec_img) + // + // d_img has the image information which will be masked + // + if(baseMLO->has_converged && baseMLO->do_use_reconstruct_images) + { + CTIC(accMLO->timer, "TranslateAndNormCorrect_recImg"); + d_rec_img.allAlloc(); + d_rec_img.allInit(0); + AccUtilities::TranslateAndNormCorrect(rec_img.data, // input host-side MultidimArray + d_rec_img, // output acc-side Array + normcorr_val, + XX(my_old_offset), + YY(my_old_offset), + (accMLO->shiftsIs3D) ? ZZ(my_old_offset) : 0., + accMLO->dataIs3D); +#ifdef _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); +#else + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#endif + CTOC(accMLO->timer, "TranslateAndNormCorrect_recImg"); + } + } + else + { + // If op.is_tomo: don't translate, nor normalise: just set d_img and d_rec_img from img.data and rec_img.data + for (unsigned long i = 0; i < img.data.nzyxdim; i++) + d_img[i] = (XFLOAT) img().data[i]; + d_img.cpToDevice(); + + if(baseMLO->has_converged && baseMLO->do_use_reconstruct_images) + { + d_rec_img.allAlloc(); + for (unsigned long i = 0; i < rec_img.data.nzyxdim; i++) + d_rec_img[i] = (XFLOAT) rec_img().data[i]; + d_rec_img.cpToDevice(); + } + } - op.Fimg_nomask.at(img_id) = Fimg; + if(baseMLO->has_converged && baseMLO->do_use_reconstruct_images) + { - // ------------------------------------------------------------------------------------------ +#ifdef _HIP_ENABLED + CTIC(hipMLO->timer,"normalizeAndTransform_recImg"); +#else + CTIC(cudaMLO->timer,"normalizeAndTransform_recImg"); +#endif + // The image used to reconstruct is not masked, so we transform and beam-tilt it + AccUtilities::normalizeAndTransformImage(d_rec_img, // input acc-side Array + Fimg, // output host-side MultidimArray + accMLO, + current_size_x, + current_size_y, + current_size_z); +#ifdef _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); + CTOC(hipMLO->timer,"normalizeAndTransform_recImg"); +#else + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); + CTOC(cudaMLO->timer,"normalizeAndTransform_recImg"); +#endif - MultidimArray Mnoise; - bool is_helical_segment = (baseMLO->do_helical_refine) || ((baseMLO->mymodel.ref_dim == 2) && (baseMLO->helical_tube_outer_diameter > 0.)); + } + else // if we don't have special images, just use the same as for alignment. But do it here, *before masking* + { +#ifdef _HIP_ENABLED + CTIC(hipMLO->timer,"normalizeAndTransform_recImg"); +#else + CTIC(cudaMLO->timer,"normalizeAndTransform_recImg"); +#endif + // The image used to reconstruct is not masked, so we transform and beam-tilt it + AccUtilities::normalizeAndTransformImage( d_img, // input acc-side Array + Fimg, // output host-side MultidimArray + accMLO, + current_size_x, + current_size_y, + current_size_z); +#ifdef _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); + CTOC(hipMLO->timer,"normalizeAndTransform_recImg"); +#else + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); + CTOC(cudaMLO->timer,"normalizeAndTransform_recImg"); +#endif + } - // For multibodies: have the mask radius equal to maximum radius within body mask plus the translational offset search range - RFLOAT my_mask_radius = (baseMLO->mymodel.nr_bodies > 1 ) ? - (baseMLO->mymodel.max_radius_mask_bodies[ibody] + baseMLO->sampling.offset_range) / my_pixel_size : - baseMLO->particle_diameter / (2. * my_pixel_size); + // ------------------------------------------------------------------------------------------ - // ------------------------------------------------------------------------------------------ + CTIC(accMLO->timer,"selfApplyBeamTilt"); + baseMLO->mydata.obsModel.demodulatePhase(optics_group, Fimg); + baseMLO->mydata.obsModel.divideByMtf(optics_group, Fimg); + CTOC(accMLO->timer,"selfApplyBeamTilt"); - // We are now done with the unmasked image used for reconstruction. - // Now make the masked image used for alignment and classification. + op.Fimg_nomask.at(img_id) = Fimg; - if (is_helical_segment) - { - CTIC(accMLO->timer,"applyHelicalMask"); + // ------------------------------------------------------------------------------------------ - // download img... - d_img.cpToHost(); - d_img.streamSync(); - d_img.getHost(img()); + MultidimArray Mnoise; + bool is_helical_segment = (baseMLO->do_helical_refine) || ((baseMLO->mymodel.ref_dim == 2) && (baseMLO->helical_tube_outer_diameter > 0.)); - // ...modify img... - if(baseMLO->do_zero_mask) - { - softMaskOutsideMapForHelix(img(), psi_deg, tilt_deg, my_mask_radius, - (baseMLO->helical_tube_outer_diameter / (2. * my_pixel_size)), - baseMLO->width_mask_edge); - } - else - { - MultidimArray Mnoise; - RandomImage.hostAlloc(); - RandomImage.cpToHost(); - Mnoise.resize(img()); - RandomImage.getHost(Mnoise); - softMaskOutsideMapForHelix(img(), psi_deg, tilt_deg, my_mask_radius, - (baseMLO->helical_tube_outer_diameter / (2. * my_pixel_size)), - baseMLO->width_mask_edge, - &Mnoise); - } + // For multibodies: have the mask radius equal to maximum radius within body mask plus the translational offset search range + RFLOAT my_mask_radius = (baseMLO->mymodel.nr_bodies > 1 ) ? + (baseMLO->mymodel.max_radius_mask_bodies[ibody] + baseMLO->sampling.offset_range) / my_pixel_size : + baseMLO->particle_diameter / (2. * my_pixel_size); - // ... and re-upload img - d_img.setHost(img()); - d_img.cpToDevice(); - CTOC(accMLO->timer,"applyHelicalMask"); - } - else // this is not a helical segment - { - CTIC(accMLO->timer,"applyMask"); - - // Shared parameters for noise/zero masking - XFLOAT cosine_width = baseMLO->width_mask_edge; - XFLOAT radius = (XFLOAT) my_mask_radius; - if (radius < 0) - radius = ((RFLOAT)img.data.xdim)/2.; - XFLOAT radius_p = radius + cosine_width; - - // For zero-masking, we need the background-value - XFLOAT bg_val(0.); - if(baseMLO->do_zero_mask) - { - AccPtr softMaskSum = ptrFactory.make((size_t)SOFTMASK_BLOCK_SIZE, 0); - AccPtr softMaskSum_bg = ptrFactory.make((size_t)SOFTMASK_BLOCK_SIZE, 0); - softMaskSum.accAlloc(); - softMaskSum_bg.accAlloc(); - softMaskSum.accInit(0); - softMaskSum_bg.accInit(0); - - // Calculate the background value - AccUtilities::softMaskBackgroundValue( - d_img, - radius, - radius_p, - cosine_width, - softMaskSum, - softMaskSum_bg); - - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); - softMaskSum.streamSync(); - - // Finalize the background value - bg_val = (RFLOAT) AccUtilities::getSumOnDevice(softMaskSum_bg) / - (RFLOAT) AccUtilities::getSumOnDevice(softMaskSum); - softMaskSum.streamSync(); - } + // ------------------------------------------------------------------------------------------ + + // We are now done with the unmasked image used for reconstruction. + // Now make the masked image used for alignment and classification. - //avoid kernel-calls warning about null-pointer for RandomImage - if (baseMLO->do_zero_mask) - RandomImage.setAccPtr(d_img); - - // Apply a cosine-softened mask, using either the background value or the noise-image outside of the radius - AccUtilities::cosineFilter( - d_img, - baseMLO->do_zero_mask, - RandomImage, - radius, - radius_p, - cosine_width, - bg_val); - - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + if (is_helical_segment) + { + CTIC(accMLO->timer,"applyHelicalMask"); + + // download img... + d_img.cpToHost(); + d_img.streamSync(); + d_img.getHost(img()); + + RFLOAT myrot_deg, mytilt_deg, mypsi_deg; + if (op.is_tomo) + { + Matrix2D A; + Euler_angles2matrix(0, tilt_deg, psi_deg, A, false); + A = baseMLO->mydata.getRotationMatrix(part_id, img_id) * A; + Euler_matrix2angles(A, myrot_deg, mytilt_deg, mypsi_deg); + } + else + { + mypsi_deg = psi_deg; + mytilt_deg = tilt_deg; + } + + // ...modify img... + if(baseMLO->do_zero_mask) + { + softMaskOutsideMapForHelix(img(), mypsi_deg, mytilt_deg, my_mask_radius, + (baseMLO->helical_tube_outer_diameter / (2. * my_pixel_size)), + baseMLO->width_mask_edge); + } + else + { + MultidimArray Mnoise; + RandomImage.hostAlloc(); + RandomImage.cpToHost(); + Mnoise.resize(img()); + RandomImage.getHost(Mnoise); + softMaskOutsideMapForHelix(img(), mypsi_deg, mytilt_deg, my_mask_radius, + (baseMLO->helical_tube_outer_diameter / (2. * my_pixel_size)), + baseMLO->width_mask_edge, + &Mnoise); + } + + // ... and re-upload img + d_img.setHost(img()); + d_img.cpToDevice(); + CTOC(accMLO->timer,"applyHelicalMask"); + } + else // this is not a helical segment + { + CTIC(accMLO->timer,"applyMask"); + + // Shared parameters for noise/zero masking + XFLOAT cosine_width = baseMLO->width_mask_edge; + XFLOAT radius = (XFLOAT) my_mask_radius; + if (radius < 0) + radius = ((RFLOAT)img.data.xdim)/2.; + XFLOAT radius_p = radius + cosine_width; + + // For zero-masking, we need the background-value + XFLOAT bg_val(0.); + if(baseMLO->do_zero_mask) + { +#ifdef _SYCL_ENABLED + AccPtr softMaskSum = ptrFactory.make((size_t)SOFTMASK_BLOCK_SIZE); + AccPtr softMaskSum_bg = ptrFactory.make((size_t)SOFTMASK_BLOCK_SIZE); +#else + AccPtr softMaskSum = ptrFactory.make((size_t)SOFTMASK_BLOCK_SIZE, 0); + AccPtr softMaskSum_bg = ptrFactory.make((size_t)SOFTMASK_BLOCK_SIZE, 0); +#endif + softMaskSum.accAlloc(); + softMaskSum_bg.accAlloc(); + softMaskSum.accInit(0); + softMaskSum_bg.accInit(0); + + // Calculate the background value + AccUtilities::softMaskBackgroundValue( + d_img, + radius, + radius_p, + cosine_width, + softMaskSum, + softMaskSum_bg); + +#ifdef _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); +#else + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#endif + softMaskSum.streamSync(); + + // Finalize the background value + bg_val = (RFLOAT) AccUtilities::getSumOnDevice(softMaskSum_bg) / + (RFLOAT) AccUtilities::getSumOnDevice(softMaskSum); + softMaskSum.streamSync(); + } + + //avoid kernel-calls warning about null-pointer for RandomImage + if (baseMLO->do_zero_mask) + RandomImage.setAccPtr(d_img); + + // Apply a cosine-softened mask, using either the background value or the noise-image outside of the radius + AccUtilities::cosineFilter( + d_img, + baseMLO->do_zero_mask, + RandomImage, + radius, + radius_p, + cosine_width, + bg_val); + +#ifdef _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +#else + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +#endif - CTOC(accMLO->timer,"applyMask"); - } + CTOC(accMLO->timer,"applyMask"); + } - // ------------------------------------------------------------------------------------------ + // ------------------------------------------------------------------------------------------ - CTIC(cudaMLO->timer,"normalizeAndTransform"); - AccUtilities::normalizeAndTransformImage( d_img, // input - Fimg, // output - accMLO, - current_size_x, - current_size_y, - current_size_z); - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); - CTOC(cudaMLO->timer,"normalizeAndTransform"); +#ifdef _HIP_ENABLED + CTIC(hipMLO->timer,"normalizeAndTransform"); +#else + CTIC(cudaMLO->timer,"normalizeAndTransform"); +#endif + AccUtilities::normalizeAndTransformImage( d_img, // input + Fimg, // output + accMLO, + current_size_x, + current_size_y, + current_size_z); +#ifdef _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); + CTOC(hipMLO->timer,"normalizeAndTransform"); +#else + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); + CTOC(cudaMLO->timer,"normalizeAndTransform"); +#endif - // ------------------------------------------------------------------------------------------ + // ------------------------------------------------------------------------------------------ - CTIC(accMLO->timer,"powerClass"); - // Store the power_class spectrum of the whole image (to fill sigma2_noise between current_size and full_size - if (baseMLO->image_current_size[optics_group] < baseMLO->image_full_size[optics_group]) - { - AccPtr spectrumAndXi2 = ptrFactory.make((size_t)((baseMLO->image_full_size[optics_group]/2+1)+1), 0); // last +1 is the Xi2, to remove an expensive memcpy - spectrumAndXi2.allAlloc(); - spectrumAndXi2.accInit(0); - spectrumAndXi2.streamSync(); + CTIC(accMLO->timer,"powerClass"); + // Store the power_class spectrum of the whole image (to fill sigma2_noise between current_size and full_size + if (baseMLO->image_current_size[optics_group] < baseMLO->image_full_size[optics_group]) + { +#ifdef _SYCL_ENABLED + AccPtr spectrumAndXi2 = ptrFactory.make((size_t)((baseMLO->image_full_size[optics_group]/2+1)+1)); +#else + AccPtr spectrumAndXi2 = ptrFactory.make((size_t)((baseMLO->image_full_size[optics_group]/2+1)+1), 0); // last +1 is the Xi2, to remove an expensive memcpy +#endif + spectrumAndXi2.allAlloc(); + spectrumAndXi2.accInit(0); + spectrumAndXi2.streamSync(); + + int gridSize = CEIL((float)(accMLO->transformer1.fouriers.getSize()) / (float)POWERCLASS_BLOCK_SIZE); + if(accMLO->dataIs3D) + AccUtilities::powerClass(gridSize,POWERCLASS_BLOCK_SIZE, + ~accMLO->transformer1.fouriers, + ~spectrumAndXi2, + accMLO->transformer1.fouriers.getSize(), + spectrumAndXi2.getSize()-1, + accMLO->transformer1.xFSize, + accMLO->transformer1.yFSize, + accMLO->transformer1.zFSize, + (baseMLO->image_current_size[optics_group]/2)+1, // note: NOT baseMLO->image_full_size[optics_group]/2+1 + &(~spectrumAndXi2)[spectrumAndXi2.getSize()-1], +#ifdef _HIP_ENABLED + hipStreamPerThread); // last element is the hihgres_Xi2 +#else + cudaStreamPerThread); // last element is the hihgres_Xi2 +#endif + else + AccUtilities::powerClass(gridSize,POWERCLASS_BLOCK_SIZE, + ~accMLO->transformer1.fouriers, + ~spectrumAndXi2, + accMLO->transformer1.fouriers.getSize(), + spectrumAndXi2.getSize()-1, + accMLO->transformer1.xFSize, + accMLO->transformer1.yFSize, + accMLO->transformer1.zFSize, + (baseMLO->image_current_size[optics_group]/2)+1, // note: NOT baseMLO->image_full_size[optics_group]/2+1 + &(~spectrumAndXi2)[spectrumAndXi2.getSize()-1], // last element is the hihgres_Xi2 +#ifdef _HIP_ENABLED + hipStreamPerThread); // last element is the hihgres_Xi2 +#else + cudaStreamPerThread); // last element is the hihgres_Xi2 +#endif - int gridSize = CEIL((float)(accMLO->transformer1.fouriers.getSize()) / (float)POWERCLASS_BLOCK_SIZE); - if(accMLO->dataIs3D) - AccUtilities::powerClass(gridSize,POWERCLASS_BLOCK_SIZE, - ~accMLO->transformer1.fouriers, - ~spectrumAndXi2, - accMLO->transformer1.fouriers.getSize(), - spectrumAndXi2.getSize()-1, - accMLO->transformer1.xFSize, - accMLO->transformer1.yFSize, - accMLO->transformer1.zFSize, - (baseMLO->image_current_size[optics_group]/2)+1, // note: NOT baseMLO->image_full_size[optics_group]/2+1 - &(~spectrumAndXi2)[spectrumAndXi2.getSize()-1]); // last element is the hihgres_Xi2 - else - AccUtilities::powerClass(gridSize,POWERCLASS_BLOCK_SIZE, - ~accMLO->transformer1.fouriers, - ~spectrumAndXi2, - accMLO->transformer1.fouriers.getSize(), - spectrumAndXi2.getSize()-1, - accMLO->transformer1.xFSize, - accMLO->transformer1.yFSize, - accMLO->transformer1.zFSize, - (baseMLO->image_current_size[optics_group]/2)+1, // note: NOT baseMLO->image_full_size[optics_group]/2+1 - &(~spectrumAndXi2)[spectrumAndXi2.getSize()-1]); // last element is the hihgres_Xi2 - - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#ifdef _CUDA_ENABLED + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#elif _HIP_ENABLED + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); +#endif spectrumAndXi2.streamSync(); spectrumAndXi2.cpToHost(); @@ -687,11 +786,11 @@ void getFourierTransformsAndCtfs(long int part_id, CTIC(accMLO->timer,"CTFRead3D_disk"); // Read CTF-image from disc FileName fn_ctf; - if (!baseMLO->mydata.getImageNameOnScratch(part_id, img_id, fn_ctf, true)) + if (!baseMLO->mydata.getImageNameOnScratch(part_id, fn_ctf, true)) { std::istringstream split(baseMLO->exp_fn_ctf); // Get the right line in the exp_fn_img string - for (int i = 0; i <= my_metadata_offset; i++) + for (int i = 0; i <= op.metadata_offset; i++) getline(split, fn_ctf); } Ictf.read(fn_ctf); @@ -717,19 +816,32 @@ void getFourierTransformsAndCtfs(long int part_id, { CTIC(accMLO->timer,"CTFRead2D"); CTF ctf; - ctf.setValuesByGroup( - &(baseMLO->mydata).obsModel, optics_group, - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_CTF_DEFOCUS_U), - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_CTF_DEFOCUS_V), - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_CTF_DEFOCUS_ANGLE), - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_CTF_BFACTOR), - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_CTF_KFACTOR), - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_CTF_PHASE_SHIFT)); + + if (op.is_tomo) + ctf.setValuesByGroup( + &(baseMLO->mydata).obsModel, optics_group, + baseMLO->mydata.particles[part_id].images[img_id].defU, + baseMLO->mydata.particles[part_id].images[img_id].defV, + baseMLO->mydata.particles[part_id].images[img_id].defAngle, + 0., + (baseMLO->mydata.particles[part_id].images[img_id].is_empty) ? 0. : 1., + 0., + baseMLO->mydata.particles[part_id].images[img_id].dose); + else + ctf.setValuesByGroup( + &(baseMLO->mydata).obsModel, optics_group, + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_CTF_DEFOCUS_U), + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_CTF_DEFOCUS_V), + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_CTF_DEFOCUS_ANGLE), + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_CTF_BFACTOR), + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_CTF_KFACTOR), + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_CTF_PHASE_SHIFT), + -1.); ctf.getFftwImage(Fctf, baseMLO->image_full_size[optics_group], baseMLO->image_full_size[optics_group], my_pixel_size, - baseMLO->ctf_phase_flipped, baseMLO->only_flip_phases, baseMLO->intact_ctf_first_peak, true, baseMLO->do_ctf_padding); + baseMLO->ctf_phase_flipped, baseMLO->only_flip_phases, baseMLO->intact_ctf_first_peak, true, baseMLO->do_ctf_padding); - // SHWS 13feb2020: when using CTF-premultiplied, from now on use the normal kernels, but replace ctf by ctf^2 + // SHWS 13feb2020: when using CTF-premultiplied, from now on use the normal kernels, but replace ctf by ctf^2 if (ctf_premultiplied) { FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Fctf) @@ -738,7 +850,7 @@ void getFourierTransformsAndCtfs(long int part_id, } } - CTOC(accMLO->timer,"CTFRead2D"); + CTOC(accMLO->timer,"CTFRead2D"); } } else @@ -760,7 +872,7 @@ void getFourierTransformsAndCtfs(long int part_id, if ( NZYXSIZE(FstMulti) > 0 ) { baseMLO->applySubtomoCorrection(op.Fimg.at(img_id), op.Fimg_nomask.at(img_id), op.Fctf.at(img_id), FstMulti); - op.FstMulti.at(img_id) = FstMulti; + op.FstMulti = FstMulti; } // If we're doing multibody refinement, now subtract projections of the other bodies from both the masked and the unmasked particle @@ -784,16 +896,20 @@ void getFourierTransformsAndCtfs(long int part_id, Matrix2D Aresi, Abody; // Aresi is the residual orientation for this obody - Euler_angles2matrix(DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, ocol_rot), - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, ocol_tilt), - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, ocol_psi), Aresi, false); + Euler_angles2matrix(DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, ocol_rot), + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, ocol_tilt), + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, ocol_psi), Aresi, false); // The real orientation to be applied is the obody transformation applied and the original one Abody = Aori * (baseMLO->mymodel.orient_bodies[obody]).transpose() * baseMLO->A_rot90 * Aresi * baseMLO->mymodel.orient_bodies[obody]; // Apply anisotropic mag and scaling + if (op.is_tomo) Abody = baseMLO->mydata.getRotationMatrix(part_id, img_id) * Abody; Abody = baseMLO->mydata.obsModel.applyAnisoMag(Abody, optics_group); Abody = baseMLO->mydata.obsModel.applyScaleDifference(Abody, optics_group, baseMLO->mymodel.ori_size, baseMLO->mymodel.pixel_size); + // TODO! The translation below will now work for 2D stacks in 2D, just like selfTranslate() didn't work upwards; Also need to apply per-image rotations, Aproj! + if (op.is_tomo) REPORT_ERROR("ERROR: multibody for STA not implemented yet!"); + // Get the FT of the projection in the right direction MultidimArray FTo; FTo.initZeros(Fimg); @@ -803,32 +919,35 @@ void getFourierTransformsAndCtfs(long int part_id, /******************************************************************************** * Currently CPU-memory for projectors is not deallocated when doing multibody - * due to the previous line. See cpu_ml_optimiser.cpp and cuda_ml_optimiser.cu + * due to the previous line. See cpu_ml_optimiser.cpp, cuda_ml_optimiser.cu and + * hip_ml_optimiser.hip.cpp ********************************************************************************/ // 17May2017: Body is centered at its own COM // move it back to its place in the original particle image - Matrix1D other_projected_com(baseMLO->mymodel.data_dim); + int shiftdim = (accMLO->shiftsIs3D) ? 3 : 2 ; + Matrix1D other_projected_com(shiftdim); // Projected COM for this body (using Aori, just like above for ibody and my_projected_com!!!) other_projected_com = Aori * (baseMLO->mymodel.com_bodies[obody]); - // This will have made other_projected_com of size 3 again! resize to mymodel.data_dim - other_projected_com.resize(baseMLO->mymodel.data_dim); + // This will have made other_projected_com of size 3 again! resize to shiftdim + other_projected_com.resize(shiftdim); // Do the exact same as was done for the ibody, but DONT selfROUND here, as later phaseShift applied to ibody below!!! other_projected_com -= my_old_offset_ori; // Subtract refined obody-displacement - XX(other_projected_com) -= DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, ocol_xoff); - YY(other_projected_com) -= DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, ocol_yoff); - if (baseMLO->mymodel.data_dim == 3) - ZZ(other_projected_com) -= DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, ocol_zoff); + XX(other_projected_com) -= DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, ocol_xoff); + YY(other_projected_com) -= DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, ocol_yoff); + if (accMLO->shiftsIs3D) + ZZ(other_projected_com) -= DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, ocol_zoff); // Add the my_old_offset=selfRound(my_old_offset_ori - my_projected_com) already applied to this image for ibody other_projected_com += my_old_offset; shiftImageInFourierTransform(FTo, Faux, (RFLOAT)baseMLO->image_full_size[optics_group], - XX(other_projected_com), YY(other_projected_com), (accMLO->dataIs3D) ? ZZ(other_projected_com) : 0.); + XX(other_projected_com), YY(other_projected_com), + (accMLO->shiftsIs3D) ? ZZ(other_projected_com) : 0.); // Sum the Fourier transforms of all the obodies Fsum_obody += Faux; @@ -875,10 +994,12 @@ void getFourierTransformsAndCtfs(long int part_id, // 23jul17: NEW: as we haven't applied the (nonROUNDED!!) my_refined_ibody_offset yet, do this now in the FourierTransform Faux = op.Fimg.at(img_id); shiftImageInFourierTransform(Faux, op.Fimg.at(img_id), (RFLOAT)baseMLO->image_full_size[optics_group], - XX(my_refined_ibody_offset), YY(my_refined_ibody_offset), (accMLO->dataIs3D) ? ZZ(my_refined_ibody_offset) : 0); + XX(my_refined_ibody_offset), YY(my_refined_ibody_offset), + (accMLO->shiftsIs3D) ? ZZ(my_refined_ibody_offset) : 0); Faux = op.Fimg_nomask.at(img_id); shiftImageInFourierTransform(Faux, op.Fimg_nomask.at(img_id), (RFLOAT)baseMLO->image_full_size[optics_group], - XX(my_refined_ibody_offset), YY(my_refined_ibody_offset), (accMLO->dataIs3D) ? ZZ(my_refined_ibody_offset) : 0); + XX(my_refined_ibody_offset), YY(my_refined_ibody_offset), + (accMLO->shiftsIs3D) ? ZZ(my_refined_ibody_offset) : 0); } // end if mymodel.nr_bodies > 1 @@ -915,110 +1036,163 @@ void getAllSquaredDifferencesCoarse( CUSTOM_ALLOCATOR_REGION_NAME("DIFF_COARSE"); CTIC(accMLO->timer,"diff_pre_gpu"); - unsigned long weightsPerPart(baseMLO->mymodel.nr_classes * sp.nr_dir * sp.nr_psi * sp.nr_trans * sp.nr_oversampled_rot * sp.nr_oversampled_trans); std::vector > dummy; std::vector > > dummy2; - std::vector > dummyRF; + MultidimArray dummyRF; baseMLO->precalculateShiftedImagesCtfsAndInvSigma2s(false, false, op.part_id, sp.current_oversampling, op.metadata_offset, // inserted SHWS 12112015 - sp.itrans_min, sp.itrans_max, op.Fimg, dummy, op.Fctf, dummy2, dummy2, + sp.itrans_min, sp.itrans_max, op.Fimg, dummy, op.Fctf, op.old_offset, dummy2, dummy2, op.local_Fctf, op.local_sqrtXi2, op.local_Minvsigma2, op.FstMulti, dummyRF); CTOC(accMLO->timer,"diff_pre_gpu"); +#ifdef _SYCL_ENABLED + deviceStream_t devAcc = accMLO->getSyclDevice(); +#endif +#ifdef _HIP_ENABLED + std::vector< AccProjectorPlan > projectorPlans(0, (HipCustomAllocator *)accMLO->getAllocator()); +#else + std::vector< AccProjectorPlan > projectorPlans(0, (CudaCustomAllocator *)accMLO->getAllocator()); +#endif - std::vector< AccProjectorPlan > projectorPlans(0, (CudaCustomAllocator *)accMLO->getAllocator()); + int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id); // get optics group of first image for this particle... + long int group_id = baseMLO->mydata.getGroupId(op.part_id); + RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id); - //If particle specific sampling plan required + //If particle specific sampling plan required if (accMLO->generateProjectionPlanOnTheFly) { CTIC(accMLO->timer,"generateProjectionSetupCoarse"); - - projectorPlans.resize(baseMLO->mymodel.nr_classes, (CudaCustomAllocator *)accMLO->getAllocator()); - +#ifdef _HIP_ENABLED + projectorPlans.resize(baseMLO->mymodel.nr_classes * sp.nr_images, (HipCustomAllocator *)accMLO->getAllocator()); +#else + projectorPlans.resize(baseMLO->mymodel.nr_classes * sp.nr_images, (CudaCustomAllocator *)accMLO->getAllocator()); +#endif for (unsigned long iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) { if (baseMLO->mymodel.pdf_class[iclass] > 0.) { - Matrix2D MBL, MBR, Aori; - if (baseMLO->mymodel.nr_bodies > 1) - { - // img_id=0 because in multi-body refinement we do not do movie frames! - RFLOAT rot_ori = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); - RFLOAT tilt_ori = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); - RFLOAT psi_ori = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); - Euler_angles2matrix(rot_ori, tilt_ori, psi_ori, Aori, false); + for (unsigned long img_id = 0; img_id < sp.nr_images; img_id++) + { - MBL = Aori * (baseMLO->mymodel.orient_bodies[ibody]).transpose() * baseMLO->A_rot90; - MBR = baseMLO->mymodel.orient_bodies[ibody]; - } + Matrix2D MBL, MBR, Aori; - int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, 0); // get optics group of first image for this particle... - Matrix2D mag; - mag.initIdentity(3); - mag = baseMLO->mydata.obsModel.applyAnisoMag(mag, optics_group); - mag = baseMLO->mydata.obsModel.applyScaleDifference(mag, optics_group, baseMLO->mymodel.ori_size, baseMLO->mymodel.pixel_size); - if (!mag.isIdentity()) - { - if (MBL.mdimx == 3 && MBL.mdimx ==3) MBL = mag * MBL; - else MBL = mag; - } + if (baseMLO->mymodel.nr_bodies > 1) + { + RFLOAT rot_ori = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); + RFLOAT tilt_ori = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); + RFLOAT psi_ori = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); + Euler_angles2matrix(rot_ori, tilt_ori, psi_ori, Aori, false); - projectorPlans[iclass].setup( - baseMLO->sampling, - op.directions_prior, - op.psi_prior, - op.pointer_dir_nonzeroprior, - op.pointer_psi_nonzeroprior, - NULL, //Mcoarse_significant - baseMLO->mymodel.pdf_class, - baseMLO->mymodel.pdf_direction, - sp.nr_dir, - sp.nr_psi, - sp.idir_min, - sp.idir_max, - sp.ipsi_min, - sp.ipsi_max, - sp.itrans_min, - sp.itrans_max, - 0, //current_oversampling - 1, //nr_oversampled_rot - iclass, - true, //coarse - !IS_NOT_INV, - baseMLO->do_skip_align, - baseMLO->do_skip_rotate, - baseMLO->mymodel.orientational_prior_mode, - MBL, - MBR - ); + MBL = Aori * (baseMLO->mymodel.orient_bodies[ibody]).transpose() * baseMLO->A_rot90; + MBR = baseMLO->mymodel.orient_bodies[ibody]; + } + + Matrix2D mag; + if (op.is_tomo) + { + mag = baseMLO->mydata.getRotationMatrix(op.part_id, img_id); + } + else + { + mag.initIdentity(3); + } + + mag = baseMLO->mydata.obsModel.applyAnisoMag(mag, optics_group); + mag = baseMLO->mydata.obsModel.applyScaleDifference(mag, optics_group, baseMLO->mymodel.ori_size, baseMLO->mymodel.pixel_size); + if (!mag.isIdentity()) + { + if (MBL.mdimx == 3 && MBL.mdimx ==3) MBL = mag * MBL; + else MBL = mag; + } + +#ifdef _SYCL_ENABLED + projectorPlans[iclass*sp.nr_images + img_id].setSyclDevice(devAcc); +#endif + projectorPlans[iclass*sp.nr_images + img_id].setup( + baseMLO->sampling, + op.directions_prior, + op.psi_prior, + op.pointer_dir_nonzeroprior, + op.pointer_psi_nonzeroprior, + NULL, //Mcoarse_significant + baseMLO->mymodel.pdf_class, + baseMLO->mymodel.pdf_direction, + sp.nr_dir, + sp.nr_psi, + sp.idir_min, + sp.idir_max, + sp.ipsi_min, + sp.ipsi_max, + sp.itrans_min, + sp.itrans_max, + 0, //current_oversampling + 1, //nr_oversampled_rot + iclass, + true, //coarse + !IS_NOT_INV, + baseMLO->do_skip_align, + baseMLO->do_skip_rotate, + baseMLO->mymodel.orientational_prior_mode, + MBL, + MBR + ); + } } } CTOC(accMLO->timer,"generateProjectionSetupCoarse"); } else - projectorPlans = accMLO->bundle->coarseProjectionPlans; + projectorPlans = accMLO->bundle->coarseProjectionPlans; // Loop only from sp.iclass_min to sp.iclass_max to deal with seed generation in first iteration size_t allWeights_size(0); for (int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) - allWeights_size += projectorPlans[exp_iclass].orientation_num * sp.nr_trans*sp.nr_oversampled_trans; + allWeights_size += projectorPlans[exp_iclass*sp.nr_images + 0].orientation_num * sp.nr_trans*sp.nr_oversampled_trans; AccPtr allWeights = ptrFactory.make(allWeights_size); +#ifdef _SYCL_ENABLED + allWeights.setStreamAccType(devAcc); + #ifdef USE_ONEDPL allWeights.accAlloc(); + #else + allWeights.allAlloc(); + #endif +#else + allWeights.accAlloc(); +#endif deviceInitValue(allWeights, 0); // Make sure entire array initialized - long int allWeights_pos=0; bool do_CC = (baseMLO->iter == 1 && baseMLO->do_firstiter_cc) || baseMLO->do_always_cc; + bool do_CC = (baseMLO->iter == 1 && baseMLO->do_firstiter_cc) || baseMLO->do_always_cc; + long unsigned translation_num((sp.itrans_max - sp.itrans_min + 1) * sp.nr_oversampled_trans); + + unsigned long image_size = op.local_Minvsigma2.nzyxdim; + + // here we introduce offsets for the trans_ and img_ in an array as it is more efficient to + // copy one big array to/from GPU rather than four small arrays + size_t trans_x_offset = 0*(size_t)translation_num; + size_t trans_y_offset = 1*(size_t)translation_num; + size_t trans_z_offset = 2*(size_t)translation_num; + size_t img_re_offset = 0*(size_t)image_size; + size_t img_im_offset = 1*(size_t)image_size; + + AccPtr Fimg_ = ptrFactory.make((size_t)image_size*2); + AccPtr trans_xyz = ptrFactory.make((size_t)translation_num*3); + AccPtr corr_img = ptrFactory.make((size_t)image_size); + +#ifdef _SYCL_ENABLED + Fimg_.setStreamAccType(devAcc); + trans_xyz.setStreamAccType(devAcc); + corr_img.setStreamAccType(devAcc); +#endif + Fimg_.allAlloc(); + trans_xyz.allAlloc(); + corr_img.allAlloc(); for (int img_id = 0; img_id < sp.nr_images; img_id++) { - int my_metadata_offset = op.metadata_offset + img_id; - long int group_id = baseMLO->mydata.getGroupId(op.part_id, img_id); - RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id, img_id); - int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, img_id); - unsigned long image_size = op.local_Minvsigma2[img_id].nzyxdim; /*==================================== Generate Translations @@ -1026,21 +1200,6 @@ void getAllSquaredDifferencesCoarse( CTIC(accMLO->timer,"translation_1"); - long unsigned translation_num((sp.itrans_max - sp.itrans_min + 1) * sp.nr_oversampled_trans); - // here we introduce offsets for the trans_ and img_ in an array as it is more efficient to - // copy one big array to/from GPU rather than four small arrays - size_t trans_x_offset = 0*(size_t)translation_num; - size_t trans_y_offset = 1*(size_t)translation_num; - size_t trans_z_offset = 2*(size_t)translation_num; - size_t img_re_offset = 0*(size_t)image_size; - size_t img_im_offset = 1*(size_t)image_size; - - AccPtr Fimg_ = ptrFactory.make((size_t)image_size*2); - AccPtr trans_xyz = ptrFactory.make((size_t)translation_num*3); - - Fimg_.allAlloc(); - trans_xyz.allAlloc(); - std::vector oversampled_translations_x, oversampled_translations_y, oversampled_translations_z; for (long int itrans = 0; itrans < translation_num; itrans++) @@ -1053,17 +1212,29 @@ void getAllSquaredDifferencesCoarse( xshift = oversampled_translations_x[0]; yshift = oversampled_translations_y[0]; - if (accMLO->dataIs3D) + if (accMLO->shiftsIs3D) zshift = oversampled_translations_z[0]; if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) ) { - RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ROT); - RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_TILT); - RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata,my_metadata_offset, METADATA_PSI); - transformCartesianAndHelicalCoords(xshift, yshift, zshift, xshift, yshift, zshift, rot_deg, tilt_deg, psi_deg, (accMLO->dataIs3D) ? (3) : (2), HELICAL_TO_CART_COORDS); + RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); + RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); + RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata,op.metadata_offset, METADATA_PSI); + transformCartesianAndHelicalCoords(xshift, yshift, zshift, xshift, yshift, zshift, rot_deg, tilt_deg, psi_deg, + (accMLO->shiftsIs3D) ? (3) : (2), HELICAL_TO_CART_COORDS); } + if (op.is_tomo) + { + // op.old_offset was not yet applied for subtomos! + xshift += XX(op.old_offset); + yshift += YY(op.old_offset); + zshift += ZZ(op.old_offset); + baseMLO->mydata.getTranslationInTiltSeries(op.part_id, img_id, + xshift, yshift, zshift, + xshift, yshift, zshift); + } + trans_xyz[trans_x_offset+itrans] = -2 * PI * xshift / (double)baseMLO->image_full_size[optics_group]; trans_xyz[trans_y_offset+itrans] = -2 * PI * yshift / (double)baseMLO->image_full_size[optics_group]; trans_xyz[trans_z_offset+itrans] = -2 * PI * zshift / (double)baseMLO->image_full_size[optics_group]; @@ -1103,36 +1274,58 @@ void getAllSquaredDifferencesCoarse( // To speed up calculation, several image-corrections are grouped into a single pixel-wise "filter", or image-correciton - AccPtr corr_img = ptrFactory.make((size_t)image_size); - - corr_img.allAlloc(); - +#ifdef _SYCL_ENABLED + corr_img.setAccType(accCPU); +#endif buildCorrImage(baseMLO,op,corr_img,img_id,group_id); +#ifdef _SYCL_ENABLED + corr_img.setAccType(accSYCL); +#endif corr_img.cpToDevice(); - deviceInitValue(allWeights, (XFLOAT) (op.highres_Xi2_img[img_id] / 2.)); - allWeights_pos = 0; + // do_CC does not seem Xi2 in the input allWeights! + if (!do_CC) + { + AccUtilities::add( + BLOCK_SIZE, + allWeights.getStream(), + (XFLOAT*)~allWeights, + (XFLOAT) (op.highres_Xi2_img[img_id] / 2.), + allWeights.getSize() + ); + } + #ifdef _HIP_ENABLED for (int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); + #elif _CUDA_ENABLED + for (int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[exp_iclass])); DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); - - for (unsigned long iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) + #elif _SYCL_ENABLED + if (accMLO->useStream()) + for (int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + (accMLO->classStreams[exp_iclass])->waitAll(); + devAcc->waitAll(); + #endif + + for (unsigned long iclass = sp.iclass_min, allWeights_pos=0; iclass <= sp.iclass_max; iclass++) { int iproj; if (baseMLO->mymodel.nr_bodies > 1) iproj = ibody; else iproj = iclass; - if ( projectorPlans[iclass].orientation_num > 0 ) + if ( projectorPlans[iclass*sp.nr_images + img_id].orientation_num > 0 ) { AccProjectorKernel projKernel = AccProjectorKernel::makeKernel( accMLO->bundle->projectors[iproj], - op.local_Minvsigma2[img_id].xdim, - op.local_Minvsigma2[img_id].ydim, - op.local_Minvsigma2[img_id].zdim, - op.local_Minvsigma2[img_id].xdim-1); + op.local_Minvsigma2.xdim, + op.local_Minvsigma2.ydim, + op.local_Minvsigma2.zdim, + op.local_Minvsigma2.xdim-1); - runDiff2KernelCoarse( + runDiff2KernelCoarse( projKernel, &(~trans_xyz)[trans_x_offset], //~trans_x, &(~trans_xyz)[trans_y_offset], //~trans_y, @@ -1140,41 +1333,79 @@ void getAllSquaredDifferencesCoarse( ~corr_img, &(~Fimg_)[img_re_offset], //~Fimg_real, &(~Fimg_)[img_im_offset], //~Fimg_imag, - ~projectorPlans[iclass].eulers, + ~projectorPlans[iclass*sp.nr_images + img_id].eulers, &(~allWeights)[allWeights_pos], (XFLOAT) op.local_sqrtXi2[img_id], - projectorPlans[iclass].orientation_num, + projectorPlans[iclass*sp.nr_images + img_id].orientation_num, translation_num, image_size, accMLO->classStreams[iclass], do_CC, accMLO->dataIs3D); - - mapAllWeightsToMweights( - ~projectorPlans[iclass].iorientclasses, +#if !defined(_SYCL_ENABLED) || defined(USE_ONEDPL) + if (img_id == sp.nr_images - 1) + mapAllWeightsToMweights( + ~projectorPlans[iclass*sp.nr_images + img_id].iorientclasses, &(~allWeights)[allWeights_pos], - &(~Mweight)[img_id*weightsPerPart], - projectorPlans[iclass].orientation_num, + &(~Mweight)[0], + projectorPlans[iclass*sp.nr_images + img_id].orientation_num, translation_num, accMLO->classStreams[iclass] ); - +#endif /*==================================== Retrieve Results ======================================*/ - allWeights_pos += projectorPlans[iclass].orientation_num*translation_num; + allWeights_pos += projectorPlans[iclass*sp.nr_images + img_id].orientation_num*translation_num; + } - } - } + } // end loop iclass + + #ifdef _HIP_ENABLED + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); // does not appear to be NEEDED FOR NON-BLOCKING CLASS STREAMS in tests, but should be to sync against classStreams + #elif _CUDA_ENABLED for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[exp_iclass])); DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); // does not appear to be NEEDED FOR NON-BLOCKING CLASS STREAMS in tests, but should be to sync against classStreams - - op.min_diff2[img_id] = AccUtilities::getMinOnDevice(allWeights); + #elif _SYCL_ENABLED + if (accMLO->useStream()) + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + (accMLO->classStreams[exp_iclass])->waitAll(); + #ifdef USE_ONEDPL + devAcc->waitAll(); + #else + allWeights.cpToHost(); + allWeights.streamSync(); + + for (unsigned long iclass = sp.iclass_min, allWeights_pos = 0; iclass <= sp.iclass_max; iclass++) + { + if ( projectorPlans[iclass*sp.nr_images + img_id].orientation_num > 0 && img_id == sp.nr_images - 1) + { + mapAllWeightsToMweights( + ~projectorPlans[iclass*sp.nr_images + img_id].iorientclasses, + &allWeights[allWeights_pos], + &(~Mweight)[0], + projectorPlans[iclass*sp.nr_images + img_id].orientation_num, + translation_num, + accMLO->classStreams[iclass] + ); + allWeights_pos += projectorPlans[iclass*sp.nr_images + img_id].orientation_num*translation_num; + } + } + #endif + #endif } // end loop img_id +#if defined(_SYCL_ENABLED) && ! defined(USE_ONEDPL) + allWeights.setAccType(accCPU); +#endif + op.min_diff2 = AccUtilities::getMinOnDevice(allWeights); + //std::cerr << " min_diff2 coarse= " << op.min_diff2 << std::endl; + #ifdef TIMING if (op.part_id == baseMLO->exp_my_first_part_id) baseMLO->timer.toc(baseMLO->TIMING_ESP_DIFF1); @@ -1191,49 +1422,79 @@ void getAllSquaredDifferencesFine( SamplingParameters &sp, MlOptimiser *baseMLO, MlClass *accMLO, - std::vector &FinePassWeights, - std::vector > &FPCMasks, - std::vector &FineProjectionData, + IndexedDataArray &FinePassWeights, + std::vector< IndexedDataArrayMask > &FPCMasks, + ProjectionParams &FineProjectionData, AccPtrFactory ptrFactory, - int ibody, - std::vector &bundleD2) + int ibody) { #ifdef TIMING if (op.part_id == baseMLO->exp_my_first_part_id) baseMLO->timer.tic(baseMLO->TIMING_ESP_DIFF2); #endif - CUSTOM_ALLOCATOR_REGION_NAME("DIFF_FINE"); + // Make sure entire array initialized, as loop over img_id now sums to old value... + deviceInitValue(FinePassWeights.weights, 0); + FinePassWeights.weights.streamSync(); + + CUSTOM_ALLOCATOR_REGION_NAME("DIFF_FINE"); CTIC(accMLO->timer,"diff_pre_gpu"); CTIC(accMLO->timer,"precalculateShiftedImagesCtfsAndInvSigma2s"); std::vector > dummy; std::vector > > dummy2; - std::vector > dummyRF; + MultidimArray dummyRF; baseMLO->precalculateShiftedImagesCtfsAndInvSigma2s(false, false, op.part_id, sp.current_oversampling, op.metadata_offset, // inserted SHWS 12112015 - sp.itrans_min, sp.itrans_max, op.Fimg, dummy, op.Fctf, dummy2, dummy2, + sp.itrans_min, sp.itrans_max, op.Fimg, dummy, op.Fctf, op.old_offset, dummy2, dummy2, op.local_Fctf, op.local_sqrtXi2, op.local_Minvsigma2, op.FstMulti, dummyRF); CTOC(accMLO->timer,"precalculateShiftedImagesCtfsAndInvSigma2s"); CTOC(accMLO->timer,"diff_pre_gpu"); +#ifdef _SYCL_ENABLED + deviceStream_t devAcc = accMLO->getSyclDevice(); +#endif /*======================================================================================= Particle Iteration =========================================================================================*/ - for (int img_id = 0; img_id < sp.nr_images; img_id++) + unsigned long newDataSize(0); + + long int group_id = baseMLO->mydata.getGroupId(op.part_id); + RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id); + int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id); + unsigned long image_size = op.local_Minvsigma2.nzyxdim; + long unsigned translation_num((sp.itrans_max - sp.itrans_min + 1) * sp.nr_oversampled_trans); + // here we introduce offsets for the trans_ and img_ in an array as it is more efficient to + // copy one big array to/from GPU rather than four small arrays + size_t trans_x_offset = 0*(size_t)translation_num; + size_t trans_y_offset = 1*(size_t)translation_num; + size_t trans_z_offset = 2*(size_t)translation_num; + size_t img_re_offset = 0*(size_t)image_size; + size_t img_im_offset = 1*(size_t)image_size; + + AccPtr Fimg_ = ptrFactory.make((size_t)image_size*2); + AccPtr trans_xyz = ptrFactory.make((size_t)translation_num*3); + AccPtr corr_img = ptrFactory.make((size_t)image_size); + +#ifdef _SYCL_ENABLED + Fimg_.setStreamAccType(devAcc); + trans_xyz.setStreamAccType(devAcc); + corr_img.setStreamAccType(devAcc); +#endif + Fimg_.allAlloc(); + trans_xyz.allAlloc(); + corr_img.allAlloc(); + + for (int img_id = 0; img_id < sp.nr_images; img_id++) { + // Reset size without de-allocating: we will append everything significant within // the current allocation and then re-allocate the then determined (smaller) volume - int my_metadata_offset = op.metadata_offset + img_id; - long int group_id = baseMLO->mydata.getGroupId(op.part_id, img_id); - RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id, img_id); - int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, img_id); - unsigned long image_size = op.local_Minvsigma2[img_id].nzyxdim; MultidimArray Fref; - Fref.resize(op.local_Minvsigma2[img_id]); + Fref.resize(op.local_Minvsigma2); /*==================================== Generate Translations @@ -1241,21 +1502,6 @@ void getAllSquaredDifferencesFine( CTIC(accMLO->timer,"translation_2"); - long unsigned translation_num((sp.itrans_max - sp.itrans_min + 1) * sp.nr_oversampled_trans); - // here we introduce offsets for the trans_ and img_ in an array as it is more efficient to - // copy one big array to/from GPU rather than four small arrays - size_t trans_x_offset = 0*(size_t)translation_num; - size_t trans_y_offset = 1*(size_t)translation_num; - size_t trans_z_offset = 2*(size_t)translation_num; - size_t img_re_offset = 0*(size_t)image_size; - size_t img_im_offset = 1*(size_t)image_size; - - AccPtr Fimg_ = ptrFactory.make((size_t)image_size*2); - AccPtr trans_xyz = ptrFactory.make((size_t)translation_num*3); - - Fimg_.allAlloc(); - trans_xyz.allAlloc(); - std::vector oversampled_translations_x, oversampled_translations_y, oversampled_translations_z; int j = 0; @@ -1271,17 +1517,29 @@ void getAllSquaredDifferencesFine( xshift = oversampled_translations_x[iover_trans]; yshift = oversampled_translations_y[iover_trans]; - if (accMLO->dataIs3D) + if (accMLO->shiftsIs3D) zshift = oversampled_translations_z[iover_trans]; if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) ) { - RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ROT); - RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_TILT); - RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PSI); - transformCartesianAndHelicalCoords(xshift, yshift, zshift, xshift, yshift, zshift, rot_deg, tilt_deg, psi_deg, (accMLO->dataIs3D) ? (3) : (2), HELICAL_TO_CART_COORDS); + RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); + RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); + RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); + transformCartesianAndHelicalCoords(xshift, yshift, zshift, xshift, yshift, zshift, rot_deg, tilt_deg, psi_deg, + (accMLO->shiftsIs3D) ? (3) : (2), HELICAL_TO_CART_COORDS); } + if (op.is_tomo) + { + // op.old_offset was not yet applied for subtomos! + xshift += XX(op.old_offset); + yshift += YY(op.old_offset); + zshift += ZZ(op.old_offset); + baseMLO->mydata.getTranslationInTiltSeries(op.part_id, img_id, + xshift, yshift, zshift, + xshift, yshift, zshift); + } + trans_xyz[trans_x_offset+j] = -2 * PI * xshift / (double)baseMLO->image_full_size[optics_group]; trans_xyz[trans_y_offset+j] = -2 * PI * yshift / (double)baseMLO->image_full_size[optics_group]; trans_xyz[trans_z_offset+j] = -2 * PI * zshift / (double)baseMLO->image_full_size[optics_group]; @@ -1320,14 +1578,16 @@ void getAllSquaredDifferencesFine( CTIC(accMLO->timer,"kernel_init_1"); - AccPtr corr_img = ptrFactory.make((size_t)image_size); - - corr_img.allAlloc(); +#ifdef _SYCL_ENABLED + corr_img.setAccType(accCPU); +#endif buildCorrImage(baseMLO,op,corr_img,img_id,group_id); +#ifdef _SYCL_ENABLED + corr_img.setAccType(accSYCL); +#endif trans_xyz.cpToDevice(); - Fimg_.cpToDevice(); corr_img.cpToDevice(); @@ -1336,77 +1596,93 @@ void getAllSquaredDifferencesFine( std::vector< AccPtr > eulers((size_t)(sp.iclass_max-sp.iclass_min+1), ptrFactory.make()); AccPtrBundle AllEulers = ptrFactory.makeBundle(); - AllEulers.setSize(9*FineProjectionData[img_id].orientationNumAllClasses*sizeof(XFLOAT)); +#ifdef _SYCL_ENABLED + AllEulers.setStreamAccType(devAcc); +#endif + AllEulers.setSize(9*FineProjectionData.orientationNumAllClasses*sizeof(XFLOAT)); AllEulers.allAlloc(); - unsigned long newDataSize(0); + newDataSize = 0; for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) { - FPCMasks[img_id][exp_iclass].weightNum=0; + FPCMasks[exp_iclass].weightNum=0; - if ((baseMLO->mymodel.pdf_class[exp_iclass] > 0.) && (FineProjectionData[img_id].class_entries[exp_iclass] > 0) ) + if ((baseMLO->mymodel.pdf_class[exp_iclass] > 0.) && (FineProjectionData.class_entries[exp_iclass] > 0) ) { - // use "slice" constructor with class-specific parameters to retrieve a temporary ProjectionParams with data for this class - ProjectionParams thisClassProjectionData( FineProjectionData[img_id], - FineProjectionData[img_id].class_idx[exp_iclass], - FineProjectionData[img_id].class_idx[exp_iclass]+FineProjectionData[img_id].class_entries[exp_iclass]); - // since we retrieved the ProjectionParams for *the whole* class the orientation_num is also equal. - - thisClassProjectionData.orientation_num[0] = FineProjectionData[img_id].class_entries[exp_iclass]; - long unsigned orientation_num = thisClassProjectionData.orientation_num[0]; - - if(orientation_num==0) - continue; - - CTIC(accMLO->timer,"pair_list_1"); - long unsigned significant_num(0); - long int nr_over_orient = baseMLO->sampling.oversamplingFactorOrientations(sp.current_oversampling); - long int nr_over_trans = baseMLO->sampling.oversamplingFactorTranslations(sp.current_oversampling); - // Prepare the mask of the weight-array for this class - if (FPCMasks[img_id][exp_iclass].weightNum==0) - FPCMasks[img_id][exp_iclass].firstPos = newDataSize; - - long unsigned ihidden(0); - std::vector< long unsigned > iover_transes, ihiddens; - - for (long int itrans = sp.itrans_min; itrans <= sp.itrans_max; itrans++, ihidden++) - { - for (long int iover_trans = 0; iover_trans < sp.nr_oversampled_trans; iover_trans++) - { - ihiddens.push_back(ihidden); - iover_transes.push_back(iover_trans); - } - } + // use "slice" constructor with class-specific parameters to retrieve a temporary ProjectionParams with data for this class + ProjectionParams thisClassProjectionData( FineProjectionData, + FineProjectionData.class_idx[exp_iclass], + FineProjectionData.class_idx[exp_iclass]+FineProjectionData.class_entries[exp_iclass]); + // since we retrieved the ProjectionParams for *the whole* class the orientation_num is also equal. + + thisClassProjectionData.orientation_num[0] = FineProjectionData.class_entries[exp_iclass]; + long unsigned orientation_num = thisClassProjectionData.orientation_num[0]; + + if(orientation_num==0) + continue; + + CTIC(accMLO->timer,"pair_list_1"); + long unsigned significant_num(0); + long int nr_over_orient = baseMLO->sampling.oversamplingFactorOrientations(sp.current_oversampling); + long int nr_over_trans = baseMLO->sampling.oversamplingFactorTranslations(sp.current_oversampling); + // Prepare the mask of the weight-array for this class + if (FPCMasks[exp_iclass].weightNum==0) + FPCMasks[exp_iclass].firstPos = newDataSize; + + long unsigned ihidden(0); + std::vector< long unsigned > iover_transes, ihiddens; + + for (long int itrans = sp.itrans_min; itrans <= sp.itrans_max; itrans++, ihidden++) + { + for (long int iover_trans = 0; iover_trans < sp.nr_oversampled_trans; iover_trans++) + { + ihiddens.push_back(ihidden); + iover_transes.push_back(iover_trans); + } + } - int chunkSize(0); - if(accMLO->dataIs3D) - chunkSize = D2F_CHUNK_DATA3D; - else if(accMLO->refIs3D) - chunkSize = D2F_CHUNK_DATA3D; - else - chunkSize = D2F_CHUNK_2D; - - // Do more significance checks on translations and create jobDivision - significant_num = makeJobsForDiff2Fine( op, sp, // alot of different type inputs... - orientation_num, translation_num, - thisClassProjectionData, - iover_transes, ihiddens, - nr_over_orient, nr_over_trans, img_id, - FinePassWeights[img_id], - FPCMasks[img_id][exp_iclass], // ..and output into index-arrays mask... - chunkSize); // ..based on a given maximum chunk-size - - // extend size by number of significants found this class - newDataSize += significant_num; - FPCMasks[img_id][exp_iclass].weightNum = significant_num; - FPCMasks[img_id][exp_iclass].lastPos = FPCMasks[img_id][exp_iclass].firstPos + significant_num; - CTOC(accMLO->timer,"pair_list_1"); - - CTIC(accMLO->timer,"IndexedArrayMemCp2"); - bundleD2[img_id].pack(FPCMasks[img_id][exp_iclass].jobOrigin); - bundleD2[img_id].pack(FPCMasks[img_id][exp_iclass].jobExtent); - CTOC(accMLO->timer,"IndexedArrayMemCp2"); + int chunkSize(0); + if(accMLO->dataIs3D) + chunkSize = D2F_CHUNK_DATA3D; + else if(accMLO->refIs3D) + chunkSize = D2F_CHUNK_DATA3D; + else + chunkSize = D2F_CHUNK_2D; + + // Do more significance checks on translations and create jobDivision + significant_num = makeJobsForDiff2Fine(op, + sp, // alot of different type inputs... + orientation_num, translation_num, + thisClassProjectionData, + iover_transes, ihiddens, + nr_over_orient, nr_over_trans, + FinePassWeights, + FPCMasks[exp_iclass], // ..and output into index-arrays mask... + chunkSize); // ..based on a given maximum chunk-size + + // extend size by number of significants found this class + newDataSize += significant_num; + + FPCMasks[exp_iclass].weightNum = significant_num; + FPCMasks[exp_iclass].lastPos = FPCMasks[exp_iclass].firstPos + significant_num; + CTOC(accMLO->timer,"pair_list_1"); + + CTIC(accMLO->timer,"IndexedArrayMemCp2"); + //bundleD2.pack(FPCMasks[exp_iclass].jobOrigin); + //bundleD2.pack(FPCMasks[exp_iclass].jobExtent); + +#ifdef _SYCL_ENABLED + FPCMasks[exp_iclass].jobOrigin.setStreamAccType(devAcc); + FPCMasks[exp_iclass].jobExtent.setStreamAccType(devAcc); +#endif + FPCMasks[exp_iclass].jobOrigin.freeDeviceIfSet(); + FPCMasks[exp_iclass].jobExtent.freeDeviceIfSet(); + FPCMasks[exp_iclass].jobOrigin.deviceAlloc(); + FPCMasks[exp_iclass].jobExtent.deviceAlloc(); + FPCMasks[exp_iclass].jobOrigin.cpToDevice(); + FPCMasks[exp_iclass].jobExtent.cpToDevice(); + CTOC(accMLO->timer,"IndexedArrayMemCp2"); Matrix2D MBL, MBR; @@ -1423,11 +1699,19 @@ void getAllSquaredDifferencesFine( } CTIC(accMLO->timer,"generateEulerMatrices"); - eulers[exp_iclass-sp.iclass_min].setSize(9*FineProjectionData[img_id].class_entries[exp_iclass]); + eulers[exp_iclass-sp.iclass_min].setSize(9*FineProjectionData.class_entries[exp_iclass]); eulers[exp_iclass-sp.iclass_min].hostAlloc(); Matrix2D mag; - mag.initIdentity(3); + if (op.is_tomo) + { + mag = baseMLO->mydata.getRotationMatrix(op.part_id, img_id); + } + else + { + mag.initIdentity(3); + } + mag = baseMLO->mydata.obsModel.applyAnisoMag(mag, optics_group); mag = baseMLO->mydata.obsModel.applyScaleDifference(mag, optics_group, baseMLO->mymodel.ori_size, baseMLO->mymodel.pixel_size); if (!mag.isIdentity()) @@ -1443,22 +1727,36 @@ void getAllSquaredDifferencesFine( MBL, MBR); +#ifdef _SYCL_ENABLED + eulers[exp_iclass-sp.iclass_min].setStreamAccType(devAcc); +#endif AllEulers.pack(eulers[exp_iclass-sp.iclass_min]); CTOC(accMLO->timer,"generateEulerMatrices"); } } - bundleD2[img_id].cpToDevice(); +// bundleD2.cpToDevice(); AllEulers.cpToDevice(); - FinePassWeights[img_id].rot_id.cpToDevice(); //FIXME this is not used - FinePassWeights[img_id].rot_idx.cpToDevice(); - FinePassWeights[img_id].trans_idx.cpToDevice(); + FinePassWeights.rot_id.cpToDevice(); //FIXME this is not used + FinePassWeights.rot_idx.cpToDevice(); + FinePassWeights.trans_idx.cpToDevice(); + #ifdef _HIP_ENABLED + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); + #elif _CUDA_ENABLED for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[exp_iclass])); DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + #elif _SYCL_ENABLED + if (accMLO->useStream()) + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + (accMLO->classStreams[exp_iclass])->waitAll(); + devAcc->waitAll(); + #endif for (unsigned long iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) { @@ -1466,27 +1764,26 @@ void getAllSquaredDifferencesFine( if (baseMLO->mymodel.nr_bodies > 1) iproj = ibody; else iproj = iclass; - if ((baseMLO->mymodel.pdf_class[iclass] > 0.) && (FineProjectionData[img_id].class_entries[iclass] > 0) ) { - long unsigned orientation_num = FineProjectionData[img_id].class_entries[iclass]; + long unsigned orientation_num = FineProjectionData.class_entries[iclass]; if(orientation_num==0) continue; - long unsigned significant_num(FPCMasks[img_id][iclass].weightNum); + long unsigned significant_num(FPCMasks[iclass].weightNum); if(significant_num==0) continue; CTIC(accMLO->timer,"Diff2MakeKernel"); AccProjectorKernel projKernel = AccProjectorKernel::makeKernel( accMLO->bundle->projectors[iproj], - op.local_Minvsigma2[img_id].xdim, - op.local_Minvsigma2[img_id].ydim, - op.local_Minvsigma2[img_id].zdim, - op.local_Minvsigma2[img_id].xdim-1); + op.local_Minvsigma2.xdim, + op.local_Minvsigma2.ydim, + op.local_Minvsigma2.zdim, + op.local_Minvsigma2.xdim-1); CTOC(accMLO->timer,"Diff2MakeKernel"); // Use the constructed mask to construct a partial class-specific input - IndexedDataArray thisClassFinePassWeights(FinePassWeights[img_id],FPCMasks[img_id][iclass]); + IndexedDataArray thisClassFinePassWeights(FinePassWeights,FPCMasks[iclass]); CTIC(accMLO->timer,"Diff2CALL"); @@ -1502,8 +1799,8 @@ void getAllSquaredDifferencesFine( ~thisClassFinePassWeights.rot_id, ~thisClassFinePassWeights.rot_idx, ~thisClassFinePassWeights.trans_idx, - ~FPCMasks[img_id][iclass].jobOrigin, - ~FPCMasks[img_id][iclass].jobExtent, + ~FPCMasks[iclass].jobOrigin, + ~FPCMasks[iclass].jobExtent, ~thisClassFinePassWeights.weights, op, baseMLO, @@ -1514,32 +1811,72 @@ void getAllSquaredDifferencesFine( img_id, iclass, accMLO->classStreams[iclass], - FPCMasks[img_id][iclass].jobOrigin.getSize(), + FPCMasks[iclass].jobOrigin.getSize(), ((baseMLO->iter == 1 && baseMLO->do_firstiter_cc) || baseMLO->do_always_cc), accMLO->dataIs3D ); +// #ifdef _CUDA_ENABLED // DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +// #elif _HIP_ENABLED +// DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +// #endif CTOC(accMLO->timer,"Diff2CALL"); } // end if class significant +#if defined(_SYCL_ENABLED) && !defined(USE_ONEDPL) + FPCMasks[iclass].jobOrigin.freeDeviceIfSet(); + FPCMasks[iclass].jobOrigin.setAccType(accCPU); + FPCMasks[iclass].jobExtent.freeDeviceIfSet(); + FPCMasks[iclass].jobExtent.setAccType(accCPU); +#endif } // end loop iclass + #ifdef _HIP_ENABLED + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); + #elif _CUDA_ENABLED for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[exp_iclass])); DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + #elif _SYCL_ENABLED + if (accMLO->useStream()) + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + (accMLO->classStreams[exp_iclass])->waitAll(); + devAcc->waitAll(); + #ifndef USE_ONEDPL + FinePassWeights.rot_id.freeDeviceIfSet(); + FinePassWeights.rot_id.setAccType(accCPU); + + FinePassWeights.rot_idx.freeDeviceIfSet(); + FinePassWeights.rot_idx.setAccType(accCPU); + + FinePassWeights.trans_idx.freeDeviceIfSet(); + FinePassWeights.trans_idx.setAccType(accCPU); + + FinePassWeights.weights.cpToHost(); + FinePassWeights.weights.streamSync(); + FinePassWeights.weights.freeDeviceIfSet(); + FinePassWeights.weights.setAccType(accCPU); + #endif + #endif - FinePassWeights[img_id].setDataSize( newDataSize ); - - CTIC(accMLO->timer,"collect_data_1"); - if(baseMLO->adaptive_oversampling!=0) - { - op.min_diff2[img_id] = (RFLOAT) AccUtilities::getMinOnDevice(FinePassWeights[img_id].weights); - } - CTOC(accMLO->timer,"collect_data_1"); -// std::cerr << " fine pass minweight = " << op.min_diff2[img_id] << std::endl; }// end loop img_id + + // This was moved outside the loop over img_id SHWS26Jul2022.... + FinePassWeights.setDataSize( newDataSize ); + + CTIC(accMLO->timer,"collect_data_1"); + // SHWS6July2022: only search for smallest diff2 after all img_id have been summed + if(baseMLO->adaptive_oversampling!=0) + { + op.min_diff2 = (RFLOAT) AccUtilities::getMinOnDevice(FinePassWeights.weights); + } + CTOC(accMLO->timer,"collect_data_1"); + //std::cerr << " fine pass minweight = " << op.min_diff2 << std::endl; + #ifdef TIMING if (op.part_id == baseMLO->exp_my_first_part_id) baseMLO->timer.toc(baseMLO->TIMING_ESP_DIFF2); @@ -1555,31 +1892,56 @@ void convertAllSquaredDifferencesToWeights(unsigned exp_ipass, SamplingParameters &sp, MlOptimiser *baseMLO, MlClass *accMLO, - std::vector< IndexedDataArray > &PassWeights, - std::vector< std::vector< IndexedDataArrayMask > > &FPCMasks, + IndexedDataArray &PassWeights, + std::vector< IndexedDataArrayMask > &FPCMasks, AccPtr &Mweight, // FPCMasks = Fine-Pass Class-Masks AccPtrFactory ptrFactory, int ibody) { #ifdef TIMING if (op.part_id == baseMLO->exp_my_first_part_id) + { { if (exp_ipass == 0) baseMLO->timer.tic(baseMLO->TIMING_ESP_WEIGHT1); else baseMLO->timer.tic(baseMLO->TIMING_ESP_WEIGHT2); } #endif - RFLOAT my_sigma2_offset = (baseMLO->mymodel.nr_bodies > 1) ? + RFLOAT my_sigma2_offset_x, my_sigma2_offset_y, my_sigma2_offset_z; + if (baseMLO->offset_range_x > 0.) // after initialise() this implies also y/z ranges are > 0 + { + my_sigma2_offset_x = (baseMLO->offset_range_x * baseMLO->offset_range_x) / 9.; // The search ranges are 3 sigma wide + my_sigma2_offset_y = (baseMLO->offset_range_y * baseMLO->offset_range_y) / 9.; // The search ranges are 3 sigma wide + my_sigma2_offset_z = (baseMLO->offset_range_z * baseMLO->offset_range_z) / 9.; // The search ranges are 3 sigma wide + } + else + { + my_sigma2_offset_x = my_sigma2_offset_y = my_sigma2_offset_z = (baseMLO->mymodel.nr_bodies > 1) ? baseMLO->mymodel.sigma_offset_bodies[ibody]*baseMLO->mymodel.sigma_offset_bodies[ibody] : baseMLO->mymodel.sigma2_offset; + } +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + deviceStream_t devAcc = accMLO->getSyclDevice(); +#endif // Ready the "prior-containers" for all classes (remake every img_id) AccPtr pdf_orientation = ptrFactory.make((size_t)((sp.iclass_max-sp.iclass_min+1) * sp.nr_dir * sp.nr_psi)); AccPtr pdf_orientation_zeros = ptrFactory.make(pdf_orientation.getSize()); AccPtr pdf_offset = ptrFactory.make((size_t)((sp.iclass_max-sp.iclass_min+1)*sp.nr_trans)); AccPtr pdf_offset_zeros = ptrFactory.make(pdf_offset.getSize()); +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + pdf_orientation.setStreamAccType(devAcc); + pdf_orientation_zeros.setStreamAccType(devAcc); + + pdf_orientation.allAlloc(); + pdf_orientation_zeros.allAlloc(); + + pdf_offset.setStreamAccType(devAcc); + pdf_offset_zeros.setStreamAccType(devAcc); +#else pdf_orientation.accAlloc(); pdf_orientation_zeros.accAlloc(); +#endif pdf_offset.allAlloc(); pdf_offset_zeros.allAlloc(); @@ -1608,487 +1970,568 @@ void convertAllSquaredDifferencesToWeights(unsigned exp_ipass, pdfs.cpToDevice(); AccUtilities::initOrientations(pdfs, pdf_orientation, pdf_orientation_zeros); +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + pdf_orientation.cpToDevice(); + pdf_orientation_zeros.cpToDevice(); +#endif CTOC(accMLO->timer,"get_orient_priors"); if(exp_ipass==0 || baseMLO->adaptive_oversampling!=0) { - op.sum_weight.clear(); - op.sum_weight.resize(sp.nr_images, (RFLOAT)(sp.nr_images)); - op.max_weight.clear(); - op.max_weight.resize(sp.nr_images, (RFLOAT)-1); + op.sum_weight = 1.; // initialised to one for firstiter_cc! + op.max_weight = (RFLOAT)-1.; } if (exp_ipass==0) - op.Mcoarse_significant.resizeNoCp(1,1,sp.nr_images, XSIZE(op.Mweight)); + op.Mcoarse_significant.resizeNoCp(1,1,1, XSIZE(op.Mweight)); XFLOAT my_significant_weight; - op.significant_weight.clear(); - op.significant_weight.resize(sp.nr_images, 0.); + op.significant_weight = 0.; - // loop over all images inside this particle - for (int img_id = 0; img_id < sp.nr_images; img_id++) - { - int my_metadata_offset = op.metadata_offset + img_id; - RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id, img_id); - - RFLOAT old_offset_x, old_offset_y, old_offset_z; - - if (baseMLO->mymodel.nr_bodies > 1) - { - old_offset_x = old_offset_y = old_offset_z = 0.; - } - else - { - old_offset_x = XX(op.old_offset[img_id]); - old_offset_y = YY(op.old_offset[img_id]); - if (accMLO->dataIs3D) - old_offset_z = ZZ(op.old_offset[img_id]); - } - - if ((baseMLO->iter == 1 && baseMLO->do_firstiter_cc) || baseMLO->do_always_cc) - { - if(exp_ipass==0) - { - int nr_coarse_weights = (sp.iclass_max-sp.iclass_min+1)*sp.nr_images * sp.nr_dir * sp.nr_psi * sp.nr_trans; - PassWeights[img_id].weights.setAccPtr(&(~Mweight)[img_id*nr_coarse_weights]); - PassWeights[img_id].weights.setHostPtr(&Mweight[img_id*nr_coarse_weights]); - PassWeights[img_id].weights.setSize(nr_coarse_weights); - } - PassWeights[img_id].weights.doFreeHost=false; + RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id); - std::pair min_pair=AccUtilities::getArgMinOnDevice(PassWeights[img_id].weights); - PassWeights[img_id].weights.cpToHost(); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + RFLOAT old_offset_x(0.), old_offset_y(0.), old_offset_z(0.); - //Set all device-located weights to zero, and only the smallest one to 1. -#ifdef _CUDA_ENABLED - DEBUG_HANDLE_ERROR(cudaMemsetAsync(~(PassWeights[img_id].weights), 0.f, PassWeights[img_id].weights.getSize()*sizeof(XFLOAT),0)); + if (baseMLO->mymodel.nr_bodies > 1) + { + old_offset_x = old_offset_y = old_offset_z = 0.; + } + else + { + old_offset_x = XX(op.old_offset); + old_offset_y = YY(op.old_offset); + if (accMLO->shiftsIs3D) + old_offset_z = ZZ(op.old_offset); + } - XFLOAT unity=1; - DEBUG_HANDLE_ERROR(cudaMemcpyAsync( &(PassWeights[img_id].weights(min_pair.first) ), &unity, sizeof(XFLOAT), cudaMemcpyHostToDevice, 0)); + if ((baseMLO->iter == 1 && baseMLO->do_firstiter_cc) || baseMLO->do_always_cc) + { + if(exp_ipass==0) + { + int nr_coarse_weights = (sp.iclass_max-sp.iclass_min+1) * sp.nr_dir * sp.nr_psi * sp.nr_trans; +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + PassWeights.weights.setStreamAccType(devAcc); +#endif + PassWeights.weights.setAccPtr(&(~Mweight)[0]); + PassWeights.weights.setHostPtr(&Mweight[0]); + PassWeights.weights.setSize(nr_coarse_weights); + } + PassWeights.weights.doFreeHost=false; - PassWeights[img_id].weights.cpToHost(); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + std::pair min_pair=AccUtilities::getArgMinOnDevice(PassWeights.weights); + PassWeights.weights.cpToHost(); +#ifdef _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); #else - deviceInitValue(PassWeights[img_id].weights, (XFLOAT)0.0); - PassWeights[img_id].weights[min_pair.first] = (XFLOAT)1.0; -#endif - - my_significant_weight = 0.999; - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_NR_SIGN) = (RFLOAT) 1.; - if (exp_ipass==0) // TODO better memset, 0 => false , 1 => true - for (int ihidden = 0; ihidden < XSIZE(op.Mcoarse_significant); ihidden++) - if (DIRECT_A2D_ELEM(op.Mweight, img_id, ihidden) >= my_significant_weight) - DIRECT_A2D_ELEM(op.Mcoarse_significant, img_id, ihidden) = true; - else - DIRECT_A2D_ELEM(op.Mcoarse_significant, img_id, ihidden) = false; - else - { - std::pair max_pair = AccUtilities::getArgMaxOnDevice(PassWeights[img_id].weights); - op.max_index[img_id].fineIdx = PassWeights[img_id].ihidden_overs[max_pair.first]; - op.max_weight[img_id] = max_pair.second; - } - - } - else - { - - - long int sumRedSize=0; - for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) - sumRedSize+= (exp_ipass==0) ? ceilf((float)(sp.nr_dir*sp.nr_psi)/(float)SUMW_BLOCK_SIZE) : ceil((float)FPCMasks[img_id][exp_iclass].jobNum / (float)SUMW_BLOCK_SIZE); - - // loop through making translational priors for all classes this img_id - then copy all at once - then loop through kernel calls ( TODO: group kernel calls into one big kernel) - CTIC(accMLO->timer,"get_offset_priors"); - - for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) - { - RFLOAT myprior_x, myprior_y, myprior_z; - if (baseMLO->mymodel.nr_bodies > 1) - { - myprior_x = myprior_y = myprior_z = 0.; - } - else if (baseMLO->mymodel.ref_dim == 2 && !baseMLO->do_helical_refine) - { - myprior_x = XX(baseMLO->mymodel.prior_offset_class[exp_iclass]); - myprior_y = YY(baseMLO->mymodel.prior_offset_class[exp_iclass]); - } - else - { - myprior_x = XX(op.prior[img_id]); - myprior_y = YY(op.prior[img_id]); - if (accMLO->dataIs3D) - myprior_z = ZZ(op.prior[img_id]); - } - - for (unsigned long itrans = sp.itrans_min; itrans <= sp.itrans_max; itrans++) - { - - // If it is doing helical refinement AND Cartesian vector myprior has a length > 0, transform the vector to its helical coordinates - if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry)) - { - RFLOAT mypriors_len2 = myprior_x * myprior_x + myprior_y * myprior_y; - if (accMLO->dataIs3D) - mypriors_len2 += myprior_z * myprior_z; - - if (mypriors_len2 > 0.00001) - { - RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ROT); - RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_TILT); - RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PSI); - transformCartesianAndHelicalCoords(myprior_x, myprior_y, myprior_z, myprior_x, myprior_y, myprior_z, rot_deg, tilt_deg, psi_deg, (accMLO->dataIs3D) ? (3) : (2), CART_TO_HELICAL_COORDS); - } - } - // (For helical refinement) Now offset, old_offset, sampling.translations and myprior are all in helical coordinates - - // To speed things up, only calculate pdf_offset at the coarse sampling. - // That should not matter much, and that way one does not need to calculate all the OversampledTranslations - double pdf(0), pdf_zeros(0); - RFLOAT offset_x = old_offset_x + baseMLO->sampling.translations_x[itrans]; - RFLOAT offset_y = old_offset_y + baseMLO->sampling.translations_y[itrans]; - double tdiff2 = 0.; - - if ( (! baseMLO->do_helical_refine) || (baseMLO->ignore_helical_symmetry) || (accMLO->dataIs3D) ) - tdiff2 += (offset_x - myprior_x) * (offset_x - myprior_x); - tdiff2 += (offset_y - myprior_y) * (offset_y - myprior_y); - if (accMLO->dataIs3D) - { - RFLOAT offset_z = old_offset_z + baseMLO->sampling.translations_z[itrans]; - if ( (! baseMLO->do_helical_refine) || (baseMLO->ignore_helical_symmetry) ) - tdiff2 += (offset_z - myprior_z) * (offset_z - myprior_z); - } - - // As of version 3.1, sigma_offsets are in Angstroms! - tdiff2 *= my_pixel_size * my_pixel_size; - - // P(offset|sigma2_offset) - // This is the probability of the offset, given the model offset and variance. - if (my_sigma2_offset < 0.0001) - { - pdf_zeros = tdiff2 > 0.; - pdf = pdf_zeros ? 0. : 1.; + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +#endif - } - else - { - pdf_zeros = false; - pdf = tdiff2 / (-2. * my_sigma2_offset); - } + // This gives the non-reproducible results with oversampling==0 and do_firstiter_cc.... + //if (exp_ipass==0) + // for (int ihidden = 0; ihidden < XSIZE(op.Mcoarse_significant); ihidden++) + // std::cerr << ihidden << " " << PassWeights.weights[ihidden] <<" " << DIRECT_A1D_ELEM(op.Mweight, ihidden) << " best-idx= " << min_pair.first << " best= " << min_pair.second << std::endl; - pdf_offset_zeros[(exp_iclass-sp.iclass_min)*sp.nr_trans + itrans] = pdf_zeros; - pdf_offset [(exp_iclass-sp.iclass_min)*sp.nr_trans + itrans] = pdf; - } - } + //Set all device-located weights to zero, and only the smallest one to 1. +#ifdef _CUDA_ENABLED + DEBUG_HANDLE_ERROR(cudaMemsetAsync(~(PassWeights.weights), 0.f, PassWeights.weights.getSize()*sizeof(XFLOAT),0)); + + XFLOAT unity=1; + DEBUG_HANDLE_ERROR(cudaMemcpyAsync( &(PassWeights.weights(min_pair.first) ), &unity, sizeof(XFLOAT), cudaMemcpyHostToDevice, 0)); + + PassWeights.weights.cpToHost(); + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +#elif _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipMemsetAsync(~(PassWeights.weights), 0.f, PassWeights.weights.getSize()*sizeof(XFLOAT),0)); + + XFLOAT unity=1; + DEBUG_HANDLE_ERROR(hipMemcpyAsync( &(PassWeights.weights(min_pair.first) ), &unity, sizeof(XFLOAT), hipMemcpyHostToDevice, 0)); + + PassWeights.weights.cpToHost(); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +#elif defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + deviceInitValue(PassWeights.weights, (XFLOAT)0.0); + PassWeights.weights.setAccValueAt((XFLOAT)1.0, min_pair.first); + PassWeights.weights.cpToHost(); + PassWeights.weights.streamSync(); +#else + deviceInitValue(PassWeights.weights, (XFLOAT)0.0); + PassWeights.weights[min_pair.first] = (XFLOAT)1.0; +#endif - pdf_offset_zeros.cpToDevice(); - pdf_offset.cpToDevice(); - CTOC(accMLO->timer,"get_offset_priors"); - CTIC(accMLO->timer,"sumweight1"); + my_significant_weight = 0.999; + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_NR_SIGN) = (RFLOAT) 1.; + if (exp_ipass==0) // TODO better memset, 0 => false , 1 => true + for (int ihidden = 0; ihidden < XSIZE(op.Mcoarse_significant); ihidden++) + if (DIRECT_A1D_ELEM(op.Mweight, ihidden) >= my_significant_weight) + DIRECT_A1D_ELEM(op.Mcoarse_significant, ihidden) = true; + else + DIRECT_A1D_ELEM(op.Mcoarse_significant, ihidden) = false; + else + { + std::pair max_pair = AccUtilities::getArgMaxOnDevice(PassWeights.weights); + op.max_index.fineIdx = PassWeights.ihidden_overs[max_pair.first]; + op.max_weight = max_pair.second; + } - if(exp_ipass==0) - { - AccPtr ipartMweight( - Mweight, - img_id * op.Mweight.xdim + sp.nr_dir * sp.nr_psi * sp.nr_trans * sp.iclass_min, - (sp.iclass_max-sp.iclass_min+1) * sp.nr_dir * sp.nr_psi * sp.nr_trans); + } + else + { - pdf_offset.streamSync(); - AccUtilities::kernel_weights_exponent_coarse( - sp.iclass_max-sp.iclass_min+1, - pdf_orientation, - pdf_orientation_zeros, - pdf_offset, - pdf_offset_zeros, - ipartMweight, - (XFLOAT)op.min_diff2[img_id], - sp.nr_dir*sp.nr_psi, - sp.nr_trans); + long int sumRedSize=0; + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + sumRedSize+= (exp_ipass==0) ? ceilf((float)(sp.nr_dir*sp.nr_psi)/(float)SUMW_BLOCK_SIZE) : ceil((float)FPCMasks[exp_iclass].jobNum / (float)SUMW_BLOCK_SIZE); + // loop through making translational priors for all classes this img_id - then copy all at once - then loop through kernel calls ( TODO: group kernel calls into one big kernel) + CTIC(accMLO->timer,"get_offset_priors"); - XFLOAT weights_max = AccUtilities::getMaxOnDevice(ipartMweight); + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + { + RFLOAT myprior_x(0.), myprior_y(0.), myprior_z(0.); + if (baseMLO->mymodel.nr_bodies > 1) + { + myprior_x = myprior_y = myprior_z = 0.; + } + else if (baseMLO->mymodel.ref_dim == 2 && !baseMLO->do_helical_refine) + { + myprior_x = XX(baseMLO->mymodel.prior_offset_class[exp_iclass]); + myprior_y = YY(baseMLO->mymodel.prior_offset_class[exp_iclass]); + } + else + { + myprior_x = XX(op.prior); + myprior_y = YY(op.prior); + if (accMLO->shiftsIs3D) + myprior_z = ZZ(op.prior); + } + + for (unsigned long itrans = sp.itrans_min; itrans <= sp.itrans_max; itrans++) + { + + // If it is doing helical refinement AND Cartesian vector myprior has a length > 0, transform the vector to its helical coordinates + if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry)) + { + RFLOAT mypriors_len2 = myprior_x * myprior_x + myprior_y * myprior_y; + if (accMLO->shiftsIs3D) + mypriors_len2 += myprior_z * myprior_z; - /* - * Add 50 since we want to stay away from e^88, which approaches the single precision limit. - * We still want as high numbers as possible to utilize most of the single precision span. - * Dari - 201710 - */ - AccUtilities::kernel_exponentiate( ipartMweight, 50 - weights_max); + if (mypriors_len2 > 0.00001) + { + RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); + RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); + RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); + transformCartesianAndHelicalCoords(myprior_x, myprior_y, myprior_z, myprior_x, myprior_y, myprior_z, rot_deg, tilt_deg, psi_deg, + (accMLO->shiftsIs3D) ? (3) : (2), CART_TO_HELICAL_COORDS); + } + } + // (For helical refinement) Now offset, old_offset, sampling.translations and myprior are all in helical coordinates + + // To speed things up, only calculate pdf_offset at the coarse sampling. + // That should not matter much, and that way one does not need to calculate all the OversampledTranslations + double pdf(0), pdf_zeros(0); + RFLOAT offset_x = old_offset_x + baseMLO->sampling.translations_x[itrans]; + RFLOAT offset_y = old_offset_y + baseMLO->sampling.translations_y[itrans]; + double tdiff2 = 0.; + + if ( (! baseMLO->do_helical_refine) || (baseMLO->ignore_helical_symmetry) ) + tdiff2 += (offset_x - myprior_x) * (offset_x - myprior_x) / (-2. * my_sigma2_offset_x); + tdiff2 += (offset_y - myprior_y) * (offset_y - myprior_y) / (-2. * my_sigma2_offset_y); + if (accMLO->shiftsIs3D) + { + RFLOAT offset_z = old_offset_z + baseMLO->sampling.translations_z[itrans]; + if ( (! baseMLO->do_helical_refine) || (baseMLO->ignore_helical_symmetry) ) + tdiff2 += (offset_z - myprior_z) * (offset_z - myprior_z) / (-2. * my_sigma2_offset_z); + } - CTIC(accMLO->timer,"sort"); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + // As of version 3.1, sigma_offsets are in Angstroms! + tdiff2 *= my_pixel_size * my_pixel_size; - unsigned long ipart_length = (sp.iclass_max-sp.iclass_min+1) * sp.nr_dir * sp.nr_psi * sp.nr_trans; - size_t offset = img_id * op.Mweight.xdim + sp.nr_dir * sp.nr_psi * sp.nr_trans * sp.iclass_min; + // P(offset|sigma2_offset) + // This is the probability of the offset, given the model offset and variance. + if (my_sigma2_offset_x < 0.0001) + { + pdf_zeros = tdiff2 > 0.; + pdf = pdf_zeros ? 0. : 1.; - if (ipart_length > 1) - { - //Wrap the current ipart data in a new pointer - AccPtr unsorted_ipart( - Mweight, - offset, - ipart_length); + } + else + { + pdf_zeros = false; + pdf = tdiff2; + } - AccPtr filtered = ptrFactory.make((size_t)unsorted_ipart.getSize()); + pdf_offset_zeros[(exp_iclass-sp.iclass_min)*sp.nr_trans + itrans] = pdf_zeros; + pdf_offset [(exp_iclass-sp.iclass_min)*sp.nr_trans + itrans] = pdf; + } + } - CUSTOM_ALLOCATOR_REGION_NAME("CASDTW_SORTSUM"); + pdf_offset_zeros.cpToDevice(); + pdf_offset.cpToDevice(); - filtered.deviceAlloc(); + CTOC(accMLO->timer,"get_offset_priors"); + CTIC(accMLO->timer,"sumweight1"); -#ifdef DEBUG_CUDA - if (unsorted_ipart.getSize()==0) - ACC_PTR_DEBUG_FATAL("Unsorted array size zero.\n"); // Hopefully Impossible + if(exp_ipass==0) + { + AccPtr ipartMweight( + Mweight, sp.nr_dir * sp.nr_psi * sp.nr_trans * sp.iclass_min, + (sp.iclass_max-sp.iclass_min+1) * sp.nr_dir * sp.nr_psi * sp.nr_trans); + + pdf_offset.streamSync(); + + AccUtilities::kernel_weights_exponent_coarse( + sp.iclass_max-sp.iclass_min+1, + pdf_orientation, + pdf_orientation_zeros, + pdf_offset, + pdf_offset_zeros, + ipartMweight, + (XFLOAT)op.min_diff2, + sp.nr_dir*sp.nr_psi, + sp.nr_trans); + + + XFLOAT weights_max = AccUtilities::getMaxOnDevice(ipartMweight); + + /* + * Add 50 since we want to stay away from e^88, which approaches the single precision limit. + * We still want as high numbers as possible to utilize most of the single precision span. + * Dari - 201710 + */ + AccUtilities::kernel_exponentiate( ipartMweight, 50 - weights_max); + + CTIC(accMLO->timer,"sort"); +#ifdef _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +#else + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); #endif - size_t filteredSize = AccUtilities::filterGreaterZeroOnDevice(unsorted_ipart, filtered); - - if (filteredSize == 0) - { - std::cerr << std::endl; - std::cerr << " fn_img= " << sp.current_img << std::endl; - std::cerr << " img_id= " << img_id << " adaptive_fraction= " << baseMLO->adaptive_fraction << std::endl; - std::cerr << " min_diff2= " << op.min_diff2[img_id] << std::endl; - pdf_orientation.dumpAccToFile("error_dump_pdf_orientation"); - pdf_offset.dumpAccToFile("error_dump_pdf_offset"); - unsorted_ipart.dumpAccToFile("error_dump_filtered"); + unsigned long ipart_length = (sp.iclass_max-sp.iclass_min+1) * sp.nr_dir * sp.nr_psi * sp.nr_trans; + size_t offset = sp.nr_dir * sp.nr_psi * sp.nr_trans * sp.iclass_min; - std::cerr << "Dumped data: error_dump_pdf_orientation, error_dump_pdf_orientation and error_dump_unsorted." << std::endl; + if (ipart_length > 1) + { + //Wrap the current ipart data in a new pointer + AccPtr unsorted_ipart( + Mweight, + offset, + ipart_length); - CRITICAL(ERRFILTEREDZERO); // "filteredSize == 0" - } - filtered.setSize(filteredSize); - - AccPtr sorted = ptrFactory.make((size_t)filteredSize); - AccPtr cumulative_sum = ptrFactory.make((size_t)filteredSize); + AccPtr filtered = ptrFactory.make((size_t)unsorted_ipart.getSize()); +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + filtered.setStreamAccType(devAcc); +#endif - sorted.accAlloc(); - cumulative_sum.accAlloc(); + CUSTOM_ALLOCATOR_REGION_NAME("CASDTW_SORTSUM"); - AccUtilities::sortOnDevice(filtered, sorted); - AccUtilities::scanOnDevice(sorted, cumulative_sum); + filtered.accAlloc(); - CTOC(accMLO->timer,"sort"); +#if defined DEBUG_CUDA || defined DEBUG_HIP + if (unsorted_ipart.getSize()==0) + ACC_PTR_DEBUG_FATAL("Unsorted array size zero.\n"); // Hopefully Impossible +#endif + size_t filteredSize = AccUtilities::filterGreaterZeroOnDevice(unsorted_ipart, filtered); - op.sum_weight[img_id] = cumulative_sum.getAccValueAt(cumulative_sum.getSize() - 1); + if (filteredSize == 0) + { + std::cerr << std::endl; + std::cerr << " fn_img= " << sp.current_img << std::endl; + std::cerr << " adaptive_fraction= " << baseMLO->adaptive_fraction << std::endl; + std::cerr << " min_diff2= " << op.min_diff2 << std::endl; - long int my_nr_significant_coarse_samples; - size_t thresholdIdx = findThresholdIdxInCumulativeSum(cumulative_sum, - (1 - baseMLO->adaptive_fraction) * op.sum_weight[img_id]); + pdf_orientation.dumpAccToFile("error_dump_pdf_orientation"); + pdf_offset.dumpAccToFile("error_dump_pdf_offset"); + unsorted_ipart.dumpAccToFile("error_dump_filtered"); - my_nr_significant_coarse_samples = filteredSize - thresholdIdx; + std::cerr << "Dumped data: error_dump_pdf_orientation, error_dump_pdf_orientation and error_dump_unsorted." << std::endl; - if (my_nr_significant_coarse_samples == 0) - { - std::cerr << std::endl; - std::cerr << " fn_img= " << sp.current_img << std::endl; - std::cerr << " img_id= " << img_id << " adaptive_fraction= " << baseMLO->adaptive_fraction << std::endl; - std::cerr << " threshold= " << (1 - baseMLO->adaptive_fraction) * op.sum_weight[img_id] << " thresholdIdx= " << thresholdIdx << std::endl; - std::cerr << " op.sum_weight[img_id]= " << op.sum_weight[img_id] << std::endl; - std::cerr << " min_diff2= " << op.min_diff2[img_id] << std::endl; + CRITICAL(ERRFILTEREDZERO); // "filteredSize == 0" + } + filtered.setSize(filteredSize); - unsorted_ipart.dumpAccToFile("error_dump_unsorted"); - filtered.dumpAccToFile("error_dump_filtered"); - sorted.dumpAccToFile("error_dump_sorted"); - cumulative_sum.dumpAccToFile("error_dump_cumulative_sum"); + AccPtr sorted = ptrFactory.make((size_t)filteredSize); + AccPtr cumulative_sum = ptrFactory.make((size_t)filteredSize); +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + sorted.setStreamAccType(devAcc); + cumulative_sum.setStreamAccType(devAcc); +#endif - std::cerr << "Written error_dump_unsorted, error_dump_filtered, error_dump_sorted, and error_dump_cumulative_sum." << std::endl; + sorted.accAlloc(); + cumulative_sum.accAlloc(); - CRITICAL(ERRNOSIGNIFS); // "my_nr_significant_coarse_samples == 0" - } + AccUtilities::sortOnDevice(filtered, sorted); + AccUtilities::scanOnDevice(sorted, cumulative_sum); - if (baseMLO->maximum_significants > 0 && - my_nr_significant_coarse_samples > baseMLO->maximum_significants) - { - my_nr_significant_coarse_samples = baseMLO->maximum_significants; - thresholdIdx = filteredSize - my_nr_significant_coarse_samples; - } + CTOC(accMLO->timer,"sort"); - XFLOAT significant_weight = sorted.getAccValueAt(thresholdIdx); + op.sum_weight = cumulative_sum.getAccValueAt(cumulative_sum.getSize() - 1); - CTIC(accMLO->timer,"getArgMaxOnDevice"); - std::pair max_pair = AccUtilities::getArgMaxOnDevice(unsorted_ipart); - CTOC(accMLO->timer,"getArgMaxOnDevice"); - op.max_index[img_id].coarseIdx = max_pair.first; - op.max_weight[img_id] = max_pair.second; + long int my_nr_significant_coarse_samples; + size_t thresholdIdx = findThresholdIdxInCumulativeSum(cumulative_sum, + (1 - baseMLO->adaptive_fraction) * op.sum_weight); - // Store nr_significant_coarse_samples for this particle - // Don't do this for multibody, as it would be overwritten for each body, - // and we also use METADATA_NR_SIGN in the new safeguard for the gold-standard separation - if (baseMLO->mymodel.nr_bodies == 1) - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_NR_SIGN) = (RFLOAT) my_nr_significant_coarse_samples; + my_nr_significant_coarse_samples = filteredSize - thresholdIdx; - AccPtr Mcoarse_significant = ptrFactory.make(ipart_length); - Mcoarse_significant.setHostPtr(&op.Mcoarse_significant.data[offset]); + if (my_nr_significant_coarse_samples == 0) + { + std::cerr << std::endl; + std::cerr << " fn_img= " << sp.current_img << std::endl; + std::cerr << " adaptive_fraction= " << baseMLO->adaptive_fraction << std::endl; + std::cerr << " threshold= " << (1 - baseMLO->adaptive_fraction) * op.sum_weight << " thresholdIdx= " << thresholdIdx << std::endl; + std::cerr << " op.sum_weight[img_id]= " << op.sum_weight << std::endl; + std::cerr << " min_diff2= " << op.min_diff2 << std::endl; - CUSTOM_ALLOCATOR_REGION_NAME("CASDTW_SIG"); - Mcoarse_significant.deviceAlloc(); + unsorted_ipart.dumpAccToFile("error_dump_unsorted"); + filtered.dumpAccToFile("error_dump_filtered"); + sorted.dumpAccToFile("error_dump_sorted"); + cumulative_sum.dumpAccToFile("error_dump_cumulative_sum"); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); - arrayOverThreshold(unsorted_ipart, Mcoarse_significant, significant_weight); - Mcoarse_significant.cpToHost(); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); - } - else if (ipart_length == 1) - { - op.Mcoarse_significant.data[img_id * op.Mweight.xdim + sp.nr_dir * sp.nr_psi * sp.nr_trans * sp.iclass_min] = 1; - } - else - CRITICAL(ERRNEGLENGTH); - } - else - { + std::cerr << "Written error_dump_unsorted, error_dump_filtered, error_dump_sorted, and error_dump_cumulative_sum." << std::endl; + CRITICAL(ERRNOSIGNIFS); // "my_nr_significant_coarse_samples == 0" + } + + if (baseMLO->maximum_significants > 0 && + my_nr_significant_coarse_samples > baseMLO->maximum_significants) + { + my_nr_significant_coarse_samples = baseMLO->maximum_significants; + thresholdIdx = filteredSize - my_nr_significant_coarse_samples; + } + + XFLOAT significant_weight = sorted.getAccValueAt(thresholdIdx); + + CTIC(accMLO->timer,"getArgMaxOnDevice"); + std::pair max_pair = AccUtilities::getArgMaxOnDevice(unsorted_ipart); + CTOC(accMLO->timer,"getArgMaxOnDevice"); + op.max_index.coarseIdx = max_pair.first; + op.max_weight = max_pair.second; + + // Store nr_significant_coarse_samples for this particle + // Don't do this for multibody, as it would be overwritten for each body, + // and we also use METADATA_NR_SIGN in the new safeguard for the gold-standard separation + if (baseMLO->mymodel.nr_bodies == 1) + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_NR_SIGN) = (RFLOAT) my_nr_significant_coarse_samples; + + AccPtr Mcoarse_significant = ptrFactory.make(ipart_length); + Mcoarse_significant.setHostPtr(&op.Mcoarse_significant.data[offset]); + + CUSTOM_ALLOCATOR_REGION_NAME("CASDTW_SIG"); + Mcoarse_significant.deviceAlloc(); + +#ifdef _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); + arrayOverThreshold(unsorted_ipart, Mcoarse_significant, significant_weight); + Mcoarse_significant.cpToHost(); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +#elif _CUDA_ENABLED + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + arrayOverThreshold(unsorted_ipart, Mcoarse_significant, significant_weight); + Mcoarse_significant.cpToHost(); + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +#elif defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + unsorted_ipart.cpToHost(); + unsorted_ipart.streamSync(); + unsorted_ipart.setAccType(accCPU); + arrayOverThreshold(unsorted_ipart, Mcoarse_significant, significant_weight); +#else // ALTCPU + arrayOverThreshold(unsorted_ipart, Mcoarse_significant, significant_weight); +#endif + } + else if (ipart_length == 1) + { + op.Mcoarse_significant.data[sp.nr_dir * sp.nr_psi * sp.nr_trans * sp.iclass_min] = 1; + } + else + CRITICAL(ERRNEGLENGTH); + } + else + { + +#ifdef _HIP_ENABLED + for (int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +#elif _CUDA_ENABLED + for (int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +#elif defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + if (accMLO->useStream()) for (int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[exp_iclass])); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + (accMLO->classStreams[exp_iclass])->waitAll(); + devAcc->waitAll(); +#endif - XFLOAT weights_max = -std::numeric_limits::max(); + XFLOAT weights_max = std::numeric_limits::lowest(); - pdf_offset.streamSync(); + pdf_offset.streamSync(); - for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) // TODO could use classStreams - { - if ((baseMLO->mymodel.pdf_class[exp_iclass] > 0.) && (FPCMasks[img_id][exp_iclass].weightNum > 0) ) - { - // Use the constructed mask to build a partial (class-specific) input - // (until now, PassWeights has been an empty placeholder. We now create class-partials pointing at it, and start to fill it with stuff) - - IndexedDataArray thisClassPassWeights(PassWeights[img_id],FPCMasks[img_id][exp_iclass]); - - AccPtr pdf_orientation_class = ptrFactory.make(sp.nr_dir*sp.nr_psi), - pdf_offset_class = ptrFactory.make(sp.nr_trans); - AccPtr pdf_orientation_zeros_class = ptrFactory.make(sp.nr_dir*sp.nr_psi), - pdf_offset_zeros_class = ptrFactory.make(sp.nr_trans); - - pdf_orientation_class .setAccPtr(&((~pdf_orientation) [(exp_iclass-sp.iclass_min)*sp.nr_dir*sp.nr_psi])); - pdf_orientation_zeros_class.setAccPtr(&((~pdf_orientation_zeros)[(exp_iclass-sp.iclass_min)*sp.nr_dir*sp.nr_psi])); - - pdf_offset_class .setAccPtr(&((~pdf_offset) [(exp_iclass-sp.iclass_min)*sp.nr_trans])); - pdf_offset_zeros_class .setAccPtr(&((~pdf_offset_zeros) [(exp_iclass-sp.iclass_min)*sp.nr_trans])); - - thisClassPassWeights.weights.setStream(accMLO->classStreams[exp_iclass]); - - AccUtilities::kernel_exponentiate_weights_fine( - ~pdf_orientation_class, - ~pdf_orientation_zeros_class, - ~pdf_offset_class, - ~pdf_offset_zeros_class, - ~thisClassPassWeights.weights, - (XFLOAT)op.min_diff2[img_id], - sp.nr_oversampled_rot, - sp.nr_oversampled_trans, - ~thisClassPassWeights.rot_id, - ~thisClassPassWeights.trans_idx, - ~FPCMasks[img_id][exp_iclass].jobOrigin, - ~FPCMasks[img_id][exp_iclass].jobExtent, - FPCMasks[img_id][exp_iclass].jobNum, - accMLO->classStreams[exp_iclass]); - - XFLOAT m = AccUtilities::getMaxOnDevice(thisClassPassWeights.weights); - - if (m > weights_max) - weights_max = m; - } - } + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) // TODO could use classStreams + { + if ((baseMLO->mymodel.pdf_class[exp_iclass] > 0.) && (FPCMasks[exp_iclass].weightNum > 0) ) + { + // Use the constructed mask to build a partial (class-specific) input + // (until now, PassWeights has been an empty placeholder. We now create class-partials pointing at it, and start to fill it with stuff) + + IndexedDataArray thisClassPassWeights(PassWeights,FPCMasks[exp_iclass]); + + AccPtr pdf_orientation_class = ptrFactory.make(sp.nr_dir*sp.nr_psi), + pdf_offset_class = ptrFactory.make(sp.nr_trans); + AccPtr pdf_orientation_zeros_class = ptrFactory.make(sp.nr_dir*sp.nr_psi), + pdf_offset_zeros_class = ptrFactory.make(sp.nr_trans); +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + pdf_orientation_class.setStreamAccType(devAcc); + pdf_offset_class.setStreamAccType(devAcc); + pdf_orientation_zeros_class.setStreamAccType(devAcc); + pdf_offset_zeros_class.setStreamAccType(devAcc); +#endif - for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) // TODO could use classStreams - { - if ((baseMLO->mymodel.pdf_class[exp_iclass] > 0.) && (FPCMasks[img_id][exp_iclass].weightNum > 0) ) - { - IndexedDataArray thisClassPassWeights(PassWeights[img_id],FPCMasks[img_id][exp_iclass]); - - thisClassPassWeights.weights.setStream(accMLO->classStreams[exp_iclass]); - /* - * Add 50 since we want to stay away from e^88, which approaches the single precision limit. - * We still want as high numbers as possible to utilize most of the single precision span. - * Dari - 201710 - */ - AccUtilities::kernel_exponentiate( thisClassPassWeights.weights, 50 - weights_max ); - } - } - op.min_diff2[img_id] += 50 - weights_max; + pdf_orientation_class .setAccPtr(&((~pdf_orientation) [(exp_iclass-sp.iclass_min)*sp.nr_dir*sp.nr_psi])); + pdf_orientation_zeros_class.setAccPtr(&((~pdf_orientation_zeros)[(exp_iclass-sp.iclass_min)*sp.nr_dir*sp.nr_psi])); - for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[exp_iclass])); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + pdf_offset_class .setAccPtr(&((~pdf_offset) [(exp_iclass-sp.iclass_min)*sp.nr_trans])); + pdf_offset_zeros_class .setAccPtr(&((~pdf_offset_zeros) [(exp_iclass-sp.iclass_min)*sp.nr_trans])); - if(baseMLO->is_som_iter) { - op.sum_weight_class[img_id].resize(baseMLO->mymodel.nr_classes, 0); + thisClassPassWeights.weights.setStream(accMLO->classStreams[exp_iclass]); - for (unsigned long exp_iclass = sp.iclass_min; - exp_iclass <= sp.iclass_max; exp_iclass++) // TODO could use classStreams - { - if ((baseMLO->mymodel.pdf_class[exp_iclass] > 0.) && - (FPCMasks[img_id][exp_iclass].weightNum > 0)) { - IndexedDataArray thisClassPassWeights(PassWeights[img_id], FPCMasks[img_id][exp_iclass]); - op.sum_weight_class[img_id][exp_iclass] = AccUtilities::getSumOnDevice(thisClassPassWeights.weights); - } - } - } + AccUtilities::kernel_exponentiate_weights_fine( + ~pdf_orientation_class, + ~pdf_orientation_zeros_class, + ~pdf_offset_class, + ~pdf_offset_zeros_class, + ~thisClassPassWeights.weights, + (XFLOAT)op.min_diff2, + sp.nr_oversampled_rot, + sp.nr_oversampled_trans, + ~thisClassPassWeights.rot_id, + ~thisClassPassWeights.trans_idx, + ~FPCMasks[exp_iclass].jobOrigin, + ~FPCMasks[exp_iclass].jobExtent, + FPCMasks[exp_iclass].jobNum, + accMLO->classStreams[exp_iclass]); - PassWeights[img_id].weights.cpToHost(); // note that the host-pointer is shared: we're copying to Mweight. + XFLOAT m = AccUtilities::getMaxOnDevice(thisClassPassWeights.weights); + if (m > weights_max) + weights_max = m; + } + } + + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) // TODO could use classStreams + { + if ((baseMLO->mymodel.pdf_class[exp_iclass] > 0.) && (FPCMasks[exp_iclass].weightNum > 0) ) + { + IndexedDataArray thisClassPassWeights(PassWeights,FPCMasks[exp_iclass]); + + thisClassPassWeights.weights.setStream(accMLO->classStreams[exp_iclass]); + /* + * Add 50 since we want to stay away from e^88, which approaches the single precision limit. + * We still want as high numbers as possible to utilize most of the single precision span. + * Dari - 201710 + */ + AccUtilities::kernel_exponentiate( thisClassPassWeights.weights, 50 - weights_max ); + } + } + + op.min_diff2 += 50 - weights_max; +#ifdef _HIP_ENABLED + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +#elif _CUDA_ENABLED + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +#elif defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + if (accMLO->useStream()) + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + (accMLO->classStreams[exp_iclass])->waitAll(); + devAcc->waitAll(); +#endif - CTIC(accMLO->timer,"sort"); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); - size_t weightSize = PassWeights[img_id].weights.getSize(); + if(baseMLO->is_som_iter) { + op.sum_weight_class.resize(baseMLO->mymodel.nr_classes, 0); - AccPtr sorted = ptrFactory.make((size_t)weightSize); - AccPtr cumulative_sum = ptrFactory.make((size_t)weightSize); + for (unsigned long exp_iclass = sp.iclass_min; + exp_iclass <= sp.iclass_max; exp_iclass++) // TODO could use classStreams + { + if ((baseMLO->mymodel.pdf_class[exp_iclass] > 0.) && + (FPCMasks[exp_iclass].weightNum > 0)) { + IndexedDataArray thisClassPassWeights(PassWeights, FPCMasks[exp_iclass]); + op.sum_weight_class[exp_iclass] = AccUtilities::getSumOnDevice(thisClassPassWeights.weights); + } + } + } - CUSTOM_ALLOCATOR_REGION_NAME("CASDTW_FINE"); + PassWeights.weights.cpToHost(); // note that the host-pointer is shared: we're copying to Mweight. - sorted.accAlloc(); - cumulative_sum.accAlloc(); + CTIC(accMLO->timer,"sort"); +#ifdef _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +#elif _CUDA_ENABLED + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +#elif defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + devAcc->waitAll(); +#endif + size_t weightSize = PassWeights.weights.getSize(); + AccPtr sorted = ptrFactory.make((size_t)weightSize); + AccPtr cumulative_sum = ptrFactory.make((size_t)weightSize); +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + sorted.setStreamAccType(devAcc); + cumulative_sum.setStreamAccType(devAcc); +#endif - AccUtilities::sortOnDevice(PassWeights[img_id].weights, sorted); - AccUtilities::scanOnDevice(sorted, cumulative_sum); - CTOC(accMLO->timer,"sort"); + CUSTOM_ALLOCATOR_REGION_NAME("CASDTW_FINE"); - if(baseMLO->adaptive_oversampling!=0) - { - op.sum_weight[img_id] = cumulative_sum.getAccValueAt(cumulative_sum.getSize() - 1); + sorted.accAlloc(); + cumulative_sum.accAlloc(); - if (op.sum_weight[img_id]==0) - { - std::cerr << std::endl; - std::cerr << " fn_img= " << sp.current_img << std::endl; - std::cerr << " op.part_id= " << op.part_id << std::endl; - std::cerr << " img_id= " << img_id << std::endl; - std::cerr << " op.min_diff2[img_id]= " << op.min_diff2[img_id] << std::endl; - int group_id = baseMLO->mydata.getGroupId(op.part_id, img_id); - std::cerr << " group_id= " << group_id << std::endl; - int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, img_id); - std::cerr << " optics_group= " << optics_group << std::endl; - std::cerr << " ml_model.scale_correction[group_id]= " << baseMLO->mymodel.scale_correction[group_id] << std::endl; - std::cerr << " exp_significant_weight[img_id]= " << op.significant_weight[img_id] << std::endl; - std::cerr << " exp_max_weight[img_id]= " << op.max_weight[img_id] << std::endl; - std::cerr << " ml_model.sigma2_noise[optics_group]= " << baseMLO->mymodel.sigma2_noise[optics_group] << std::endl; - CRITICAL(ERRSUMWEIGHTZERO); //"op.sum_weight[img_id]==0" - } + AccUtilities::sortOnDevice(PassWeights.weights, sorted); + AccUtilities::scanOnDevice(sorted, cumulative_sum); + CTOC(accMLO->timer,"sort"); - size_t thresholdIdx = findThresholdIdxInCumulativeSum(cumulative_sum, (1 - baseMLO->adaptive_fraction) * op.sum_weight[img_id]); - my_significant_weight = sorted.getAccValueAt(thresholdIdx); + if(baseMLO->adaptive_oversampling!=0) + { + op.sum_weight = cumulative_sum.getAccValueAt(cumulative_sum.getSize() - 1); - CTIC(accMLO->timer,"getArgMaxOnDevice"); - std::pair max_pair = AccUtilities::getArgMaxOnDevice(PassWeights[img_id].weights); - CTOC(accMLO->timer,"getArgMaxOnDevice"); - op.max_index[img_id].fineIdx = PassWeights[img_id].ihidden_overs[max_pair.first]; - op.max_weight[img_id] = max_pair.second; - } - else - { - my_significant_weight = sorted.getAccValueAt(0); - } - } - CTOC(accMLO->timer,"sumweight1"); - } + if (op.sum_weight==0) + { + std::cerr << std::endl; + std::cerr << " fn_img= " << sp.current_img << std::endl; + std::cerr << " op.part_id= " << op.part_id << std::endl; + std::cerr << " op.min_diff2= " << op.min_diff2 << std::endl; + int group_id = baseMLO->mydata.getGroupId(op.part_id); + std::cerr << " group_id= " << group_id << std::endl; + int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id); + std::cerr << " optics_group= " << optics_group << std::endl; + std::cerr << " ml_model.scale_correction[group_id]= " << baseMLO->mymodel.scale_correction[group_id] << std::endl; + std::cerr << " exp_significant_weight= " << op.significant_weight << std::endl; + std::cerr << " exp_max_weight= " << op.max_weight << std::endl; + std::cerr << " ml_model.sigma2_noise[optics_group]= " << baseMLO->mymodel.sigma2_noise[optics_group] << std::endl; + CRITICAL(ERRSUMWEIGHTZERO); //"op.sum_weight[img_id]==0" + } - op.significant_weight[img_id] = (RFLOAT) my_significant_weight; - } // end loop img_id + size_t thresholdIdx = findThresholdIdxInCumulativeSum(cumulative_sum, (1 - baseMLO->adaptive_fraction) * op.sum_weight); + my_significant_weight = sorted.getAccValueAt(thresholdIdx); + + CTIC(accMLO->timer,"getArgMaxOnDevice"); + std::pair max_pair = AccUtilities::getArgMaxOnDevice(PassWeights.weights); + CTOC(accMLO->timer,"getArgMaxOnDevice"); + op.max_index.fineIdx = PassWeights.ihidden_overs[max_pair.first]; + op.max_weight = max_pair.second; + } + else + { + my_significant_weight = sorted.getAccValueAt(0); + } + } + CTOC(accMLO->timer,"sumweight1"); + } + op.significant_weight = (RFLOAT) my_significant_weight; #ifdef TIMING if (op.part_id == baseMLO->exp_my_first_part_id) @@ -2106,12 +2549,11 @@ template void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, MlOptimiser *baseMLO, MlClass *accMLO, - std::vector &FinePassWeights, - std::vector &ProjectionData, - std::vector > &FPCMasks, + IndexedDataArray &FinePassWeights, + ProjectionParams &ProjectionData, + std::vector &FPCMasks, AccPtrFactory ptrFactory, - int ibody, - std::vector< AccPtrBundle > &bundleSWS) + int ibody, AccPtrBundle &bundleSWS) { #ifdef TIMING if (op.part_id == baseMLO->exp_my_first_part_id) @@ -2122,59 +2564,32 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, // Re-do below because now also want unmasked images AND if (stricht_highres_exp >0.) then may need to resize std::vector > dummy; std::vector > > dummy2; - std::vector > exp_local_STMulti; - bool do_subtomo_correction = op.FstMulti.size() > 0 && NZYXSIZE(op.FstMulti[0]) > 0; - if (do_subtomo_correction) - exp_local_STMulti.resize(sp.nr_images); + MultidimArray exp_local_STMulti; + bool do_subtomo_correction = NZYXSIZE(op.FstMulti) > 0; baseMLO->precalculateShiftedImagesCtfsAndInvSigma2s(false, true, op.part_id, sp.current_oversampling, op.metadata_offset, // inserted SHWS 12112015 - sp.itrans_min, sp.itrans_max, op.Fimg, op.Fimg_nomask, op.Fctf, dummy2, dummy2, + sp.itrans_min, sp.itrans_max, op.Fimg, op.Fimg_nomask, op.Fctf, op.old_offset, dummy2, dummy2, op.local_Fctf, op.local_sqrtXi2, op.local_Minvsigma2, op.FstMulti, exp_local_STMulti); - // In doThreadPrecalculateShiftedImagesCtfsAndInvSigma2s() the origin of the op.local_Minvsigma2s was omitted. - // Set those back here - for (int img_id = 0; img_id < sp.nr_images; img_id++) - { - int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, img_id); - DIRECT_MULTIDIM_ELEM(op.local_Minvsigma2[img_id], 0) = 1. / (baseMLO->sigma2_fudge * DIRECT_A1D_ELEM(baseMLO->mymodel.sigma2_noise[optics_group], 0)); - } + int group_id = baseMLO->mydata.getGroupId(op.part_id); + int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id); + RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id); + int my_image_size = baseMLO->mydata.getOpticsImageSize(optics_group); + + // In doThreadPrecalculateShiftedImagesCtfsAndInvSigma2s() the origin of the op.local_Minvsigma2s was omitted. + // Set those back here + DIRECT_MULTIDIM_ELEM(op.local_Minvsigma2, 0) = 1. / (baseMLO->sigma2_fudge * DIRECT_A1D_ELEM(baseMLO->mymodel.sigma2_noise[optics_group], 0)); // For norm_correction and scale_correction of all images of this particle - std::vector exp_wsum_norm_correction; - std::vector exp_wsum_scale_correction_XA, exp_wsum_scale_correction_AA; - std::vector thr_wsum_signal_product_spectra, thr_wsum_reference_power_spectra; - exp_wsum_norm_correction.resize(sp.nr_images, 0.); - std::vector > thr_wsum_sigma2_noise, thr_wsum_ctf2, thr_wsum_stMulti; - - // for noise estimation (per image) - thr_wsum_sigma2_noise.resize(sp.nr_images); - thr_wsum_ctf2.resize(sp.nr_images); - thr_wsum_stMulti.resize(sp.nr_images); - - // For scale_correction - if (baseMLO->do_scale_correction) - { - exp_wsum_scale_correction_XA.resize(sp.nr_images); - exp_wsum_scale_correction_AA.resize(sp.nr_images); - thr_wsum_signal_product_spectra.resize(sp.nr_images); - thr_wsum_reference_power_spectra.resize(sp.nr_images); - } + RFLOAT exp_wsum_norm_correction = 0.; + RFLOAT exp_wsum_scale_correction_XA = 0., exp_wsum_scale_correction_AA = 0.; + RFLOAT thr_wsum_signal_product_spectra = 0., thr_wsum_reference_power_spectra = 0.; + MultidimArray thr_wsum_sigma2_noise, thr_wsum_ctf2, thr_wsum_stMulti; - // Possibly different array sizes in different optics groups! - for (int img_id = 0; img_id < sp.nr_images; img_id++) - { - int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, img_id); - thr_wsum_sigma2_noise[img_id].initZeros(baseMLO->image_full_size[optics_group]/2 + 1); - thr_wsum_stMulti[img_id].initZeros(baseMLO->image_full_size[optics_group]/2 + 1); - thr_wsum_ctf2[img_id].initZeros(baseMLO->image_full_size[optics_group]/2 + 1); - if (baseMLO->do_scale_correction) - { - exp_wsum_scale_correction_AA[img_id] = 0.; - exp_wsum_scale_correction_XA[img_id] = 0.; - thr_wsum_signal_product_spectra[img_id] = 0.; - thr_wsum_reference_power_spectra[img_id] = 0.; - } - } + thr_wsum_sigma2_noise.initZeros(baseMLO->image_full_size[optics_group]/2 + 1); + thr_wsum_ctf2.initZeros(baseMLO->image_full_size[optics_group]/2 + 1); + if (do_subtomo_correction) + thr_wsum_stMulti.initZeros(baseMLO->image_full_size[optics_group]/2 + 1); std::vector oversampled_translations_x, oversampled_translations_y, oversampled_translations_z; bool have_warned_small_scale = false; @@ -2182,14 +2597,14 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, // Make local copies of weighted sums (except BPrefs, which are too big) // so that there are not too many mutex locks below std::vector > thr_wsum_pdf_direction; - std::vector thr_wsum_norm_correction, thr_sumw_group, thr_wsum_pdf_class, thr_wsum_prior_offsetx_class, thr_wsum_prior_offsety_class; - RFLOAT thr_wsum_sigma2_offset; + std::vector thr_wsum_norm_correction, thr_wsum_pdf_class, thr_wsum_prior_offsetx_class, thr_wsum_prior_offsety_class; + RFLOAT thr_wsum_sigma2_offset, thr_sumw_group; MultidimArray thr_metadata, zeroArray; // wsum_pdf_direction is a 1D-array (of length sampling.NrDirections()) for each class zeroArray.initZeros(baseMLO->sampling.NrDirections()); thr_wsum_pdf_direction.resize(baseMLO->mymodel.nr_classes * baseMLO->mymodel.nr_bodies, zeroArray); // sumw_group is a RFLOAT for each group - thr_sumw_group.resize(sp.nr_images, 0.); + thr_sumw_group= 0.; // wsum_pdf_class is a RFLOAT for each class thr_wsum_pdf_class.resize(baseMLO->mymodel.nr_classes, 0.); if (baseMLO->mymodel.ref_dim == 2) @@ -2201,6 +2616,9 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, thr_wsum_sigma2_offset = 0.; CTOC(accMLO->timer,"store_init"); +#ifdef _SYCL_ENABLED + deviceStream_t devAcc = accMLO->getSyclDevice(); +#endif /*======================================================================================= COLLECT 2 AND SET METADATA =======================================================================================*/ @@ -2209,291 +2627,304 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, unsigned long nr_transes = sp.nr_trans*sp.nr_oversampled_trans; unsigned long nr_fake_classes = (sp.iclass_max-sp.iclass_min+1); unsigned long oversamples = sp.nr_oversampled_trans * sp.nr_oversampled_rot; - std::vector block_nums(sp.nr_images*nr_fake_classes); - - for (int img_id = 0; img_id < sp.nr_images; img_id++) - { - // here we introduce offsets for the oo_transes in an array as it is more efficient to - // copy one big array to/from GPU rather than four small arrays - size_t otrans_x = 0*(size_t)nr_fake_classes*nr_transes; - size_t otrans_y = 1*(size_t)nr_fake_classes*nr_transes; - size_t otrans_z = 2*(size_t)nr_fake_classes*nr_transes; - size_t otrans_x2y2z2 = 3*(size_t)nr_fake_classes*nr_transes; + std::vector block_nums(nr_fake_classes); - // Allocate space for all classes, so that we can pre-calculate data for all classes, copy in one operation, call kenrels on all classes, and copy back in one operation - AccPtr oo_otrans = ptrFactory.make((size_t)nr_fake_classes*nr_transes*4); + // here we introduce offsets for the oo_transes in an array as it is more efficient to + // copy one big array to/from GPU rather than four small arrays + size_t otrans_x = 0*(size_t)nr_fake_classes*nr_transes; + size_t otrans_y = 1*(size_t)nr_fake_classes*nr_transes; + size_t otrans_z = 2*(size_t)nr_fake_classes*nr_transes; + size_t otrans_x2y2z2 = 3*(size_t)nr_fake_classes*nr_transes; - oo_otrans.allAlloc(); + // Allocate space for all classes, so that we can pre-calculate data for all classes, copy in one operation, call kenrels on all classes, and copy back in one operation + AccPtr oo_otrans = ptrFactory.make((size_t)nr_fake_classes*nr_transes*4); - int sumBlockNum =0; - int my_metadata_offset = op.metadata_offset + img_id; - int group_id = baseMLO->mydata.getGroupId(op.part_id, img_id); - const int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, img_id); - RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id, img_id); + oo_otrans.allAlloc(); - CTIC(accMLO->timer,"collect_data_2_pre_kernel"); - for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) - { - unsigned long fake_class = exp_iclass-sp.iclass_min; // if we only have the third class to do, the third class will be the "first" we do, i.e. the "fake" first. - if ((baseMLO->mymodel.pdf_class[exp_iclass] == 0.) || (ProjectionData[img_id].class_entries[exp_iclass] == 0) ) - continue; - - // Use the constructed mask to construct a partial class-specific input - IndexedDataArray thisClassFinePassWeights(FinePassWeights[img_id],FPCMasks[img_id][exp_iclass]); - - // Re-define the job-partition of the indexedArray of weights so that the collect-kernel can work with it. - block_nums[nr_fake_classes*img_id + fake_class] = makeJobsForCollect(thisClassFinePassWeights, FPCMasks[img_id][exp_iclass], ProjectionData[img_id].orientation_num[exp_iclass]); + int sumBlockNum =0; - bundleSWS[img_id].pack(FPCMasks[img_id][exp_iclass].jobOrigin); - bundleSWS[img_id].pack(FPCMasks[img_id][exp_iclass].jobExtent); - - sumBlockNum+=block_nums[nr_fake_classes*img_id + fake_class]; - - RFLOAT myprior_x, myprior_y, myprior_z, old_offset_z; - RFLOAT old_offset_x = XX(op.old_offset[img_id]); - RFLOAT old_offset_y = YY(op.old_offset[img_id]); - - if (baseMLO->mymodel.ref_dim == 2 && baseMLO->mymodel.nr_bodies == 1) - { - myprior_x = XX(baseMLO->mymodel.prior_offset_class[exp_iclass]); - myprior_y = YY(baseMLO->mymodel.prior_offset_class[exp_iclass]); - } - else - { - myprior_x = XX(op.prior[img_id]); - myprior_y = YY(op.prior[img_id]); - if (baseMLO->mymodel.data_dim == 3) - { - myprior_z = ZZ(op.prior[img_id]); - old_offset_z = ZZ(op.old_offset[img_id]); - } - } + CTIC(accMLO->timer,"collect_data_2_pre_kernel"); + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + { + unsigned long fake_class = exp_iclass-sp.iclass_min; // if we only have the third class to do, the third class will be the "first" we do, i.e. the "fake" first. + if ((baseMLO->mymodel.pdf_class[exp_iclass] == 0.) || (ProjectionData.class_entries[exp_iclass] == 0) ) + continue; - /*====================================================== - COLLECT 2 - ======================================================*/ + // Use the constructed mask to construct a partial class-specific input + IndexedDataArray thisClassFinePassWeights(FinePassWeights,FPCMasks[exp_iclass]); - //Pregenerate oversampled translation objects for kernel-call - for (long int itrans = 0, iitrans = 0; itrans < sp.nr_trans; itrans++) - { - baseMLO->sampling.getTranslationsInPixel(itrans, baseMLO->adaptive_oversampling, my_pixel_size, - oversampled_translations_x, oversampled_translations_y, oversampled_translations_z, - (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry)); - for (long int iover_trans = 0; iover_trans < sp.nr_oversampled_trans; iover_trans++, iitrans++) - { - oo_otrans[otrans_x+fake_class*nr_transes+iitrans] = old_offset_x + oversampled_translations_x[iover_trans]; - oo_otrans[otrans_y+fake_class*nr_transes+iitrans] = old_offset_y + oversampled_translations_y[iover_trans]; - if (accMLO->dataIs3D) - oo_otrans[otrans_z+fake_class*nr_transes+iitrans] = old_offset_z + oversampled_translations_z[iover_trans]; - - // Calculate the vector length of myprior - RFLOAT mypriors_len2 = myprior_x * myprior_x + myprior_y * myprior_y; - if (accMLO->dataIs3D) - mypriors_len2 += myprior_z * myprior_z; - - // If it is doing helical refinement AND Cartesian vector myprior has a length > 0, transform the vector to its helical coordinates - if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) && (mypriors_len2 > 0.00001) ) - { - RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ROT); - RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_TILT); - RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PSI); - transformCartesianAndHelicalCoords(myprior_x, myprior_y, myprior_z, myprior_x, myprior_y, myprior_z, rot_deg, tilt_deg, psi_deg, (accMLO->dataIs3D) ? (3) : (2), CART_TO_HELICAL_COORDS); - } + // Re-define the job-partition of the indexedArray of weights so that the collect-kernel can work with it. + block_nums[fake_class] = makeJobsForCollect(thisClassFinePassWeights, FPCMasks[exp_iclass], ProjectionData.orientation_num[exp_iclass]); - if ( (! baseMLO->do_helical_refine) || (baseMLO->ignore_helical_symmetry) ) - RFLOAT diffx = myprior_x - oo_otrans[otrans_x+fake_class*nr_transes+iitrans]; - RFLOAT diffx = myprior_x - oo_otrans[otrans_x+fake_class*nr_transes+iitrans]; - RFLOAT diffy = myprior_y - oo_otrans[otrans_y+fake_class*nr_transes+iitrans]; - RFLOAT diffz = 0; - if (accMLO->dataIs3D) - diffz = myprior_z - (old_offset_z + oversampled_translations_z[iover_trans]); + bundleSWS.pack(FPCMasks[exp_iclass].jobOrigin); + bundleSWS.pack(FPCMasks[exp_iclass].jobExtent); - oo_otrans[otrans_x2y2z2+fake_class*nr_transes+iitrans] = diffx*diffx + diffy*diffy + diffz*diffz; - } - } - } + sumBlockNum+=block_nums[fake_class]; - bundleSWS[img_id].cpToDevice(); - oo_otrans.cpToDevice(); + RFLOAT myprior_x(0.), myprior_y(0.), myprior_z(0.), old_offset_z(0.); + RFLOAT old_offset_x = XX(op.old_offset); + RFLOAT old_offset_y = YY(op.old_offset); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + if (baseMLO->mymodel.ref_dim == 2 && baseMLO->mymodel.nr_bodies == 1) + { + myprior_x = XX(baseMLO->mymodel.prior_offset_class[exp_iclass]); + myprior_y = YY(baseMLO->mymodel.prior_offset_class[exp_iclass]); + } + else + { + myprior_x = XX(op.prior); + myprior_y = YY(op.prior); + if (accMLO->shiftsIs3D) + { + myprior_z = ZZ(op.prior); + old_offset_z = ZZ(op.old_offset); + } + } - // here we introduce offsets for the clases in an array as it is more efficient to - // copy one big array to/from GPU rather than four small arrays - size_t offsetx_class = 0*(size_t)sumBlockNum; - size_t offsety_class = 1*(size_t)sumBlockNum; - size_t offsetz_class = 2*(size_t)sumBlockNum; - size_t sigma2_offset = 3*(size_t)sumBlockNum; + /*====================================================== + COLLECT 2 + ======================================================*/ - AccPtr p_weights = ptrFactory.make((size_t)sumBlockNum); - AccPtr p_thr_wsum_prior_offsetxyz_class = ptrFactory.make((size_t)sumBlockNum*4); + //Pregenerate oversampled translation objects for kernel-call + for (long int itrans = 0, iitrans = 0; itrans < sp.nr_trans; itrans++) + { + baseMLO->sampling.getTranslationsInPixel(itrans, baseMLO->adaptive_oversampling, my_pixel_size, + oversampled_translations_x, oversampled_translations_y, oversampled_translations_z, + (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry)); + for (long int iover_trans = 0; iover_trans < sp.nr_oversampled_trans; iover_trans++, iitrans++) + { + + double zshift = 0.; + double xshift = old_offset_x + oversampled_translations_x[iover_trans]; + double yshift = old_offset_y + oversampled_translations_y[iover_trans]; + if (accMLO->shiftsIs3D) + zshift = old_offset_z + oversampled_translations_z[iover_trans]; + + oo_otrans[otrans_x+fake_class*nr_transes+iitrans] = xshift; + oo_otrans[otrans_y+fake_class*nr_transes+iitrans] = yshift; + if (accMLO->shiftsIs3D) + oo_otrans[otrans_z+fake_class*nr_transes+iitrans] = zshift; + + // Calculate the vector length of myprior + RFLOAT mypriors_len2 = myprior_x * myprior_x + myprior_y * myprior_y; + if (accMLO->shiftsIs3D) + mypriors_len2 += myprior_z * myprior_z; + + // If it is doing helical refinement AND Cartesian vector myprior has a length > 0, transform the vector to its helical coordinates + if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) && (mypriors_len2 > 0.00001) ) + { + RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); + RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); + RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); + transformCartesianAndHelicalCoords(myprior_x, myprior_y, myprior_z, myprior_x, myprior_y, myprior_z, rot_deg, tilt_deg, psi_deg, + (accMLO->shiftsIs3D) ? (3) : (2), CART_TO_HELICAL_COORDS); + } - p_weights.allAlloc(); - p_thr_wsum_prior_offsetxyz_class.allAlloc(); - CTOC(accMLO->timer,"collect_data_2_pre_kernel"); - int partial_pos=0; + RFLOAT diffx = 0.; + if ( (! baseMLO->do_helical_refine) || (baseMLO->ignore_helical_symmetry) ) + diffx = myprior_x - xshift; + RFLOAT diffy = myprior_y - yshift; + RFLOAT diffz = 0; + if (accMLO->shiftsIs3D) + diffz = myprior_z - zshift; - for (long int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) - { - long int fake_class = exp_iclass-sp.iclass_min; // if we only have the third class to do, the third class will be the "first" we do, i.e. the "fake" first. - if ((baseMLO->mymodel.pdf_class[exp_iclass] == 0.) || (ProjectionData[img_id].class_entries[exp_iclass] == 0) ) - continue; + oo_otrans[otrans_x2y2z2+fake_class*nr_transes+iitrans] = diffx*diffx + diffy*diffy + diffz*diffz; - // Use the constructed mask to construct a partial class-specific input - IndexedDataArray thisClassFinePassWeights(FinePassWeights[img_id],FPCMasks[img_id][exp_iclass]); + } + } + } - long int cpos=fake_class*nr_transes; - int block_num = block_nums[nr_fake_classes*img_id + fake_class]; + bundleSWS.cpToDevice(); + oo_otrans.cpToDevice(); - runCollect2jobs(block_num, - &(~oo_otrans)[otrans_x+cpos], // otrans-size -> make const - &(~oo_otrans)[otrans_y+cpos], // otrans-size -> make const - &(~oo_otrans)[otrans_z+cpos], // otrans-size -> make const - &(~oo_otrans)[otrans_x2y2z2+cpos], // otrans-size -> make const - ~thisClassFinePassWeights.weights, - (XFLOAT)op.significant_weight[img_id], - (XFLOAT)op.sum_weight[img_id], - sp.nr_trans, - sp.nr_oversampled_trans, - sp.nr_oversampled_rot, - oversamples, - (baseMLO->do_skip_align || baseMLO->do_skip_rotate ), - &(~p_weights)[partial_pos], - &(~p_thr_wsum_prior_offsetxyz_class)[offsetx_class+partial_pos], - &(~p_thr_wsum_prior_offsetxyz_class)[offsety_class+partial_pos], - &(~p_thr_wsum_prior_offsetxyz_class)[offsetz_class+partial_pos], - &(~p_thr_wsum_prior_offsetxyz_class)[sigma2_offset+partial_pos], - ~thisClassFinePassWeights.rot_idx, - ~thisClassFinePassWeights.trans_idx, - ~FPCMasks[img_id][exp_iclass].jobOrigin, - ~FPCMasks[img_id][exp_iclass].jobExtent, - accMLO->dataIs3D); - LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#ifdef _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +#else + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +#endif - partial_pos+=block_num; - } + // here we introduce offsets for the clases in an array as it is more efficient to + // copy one big array to/from GPU rather than four small arrays + size_t offsetx_class = 0*(size_t)sumBlockNum; + size_t offsety_class = 1*(size_t)sumBlockNum; + size_t offsetz_class = 2*(size_t)sumBlockNum; + size_t sigma2_offset = 3*(size_t)sumBlockNum; - CTIC(accMLO->timer,"collect_data_2_post_kernel"); - p_weights.cpToHost(); - p_thr_wsum_prior_offsetxyz_class.cpToHost(); + AccPtr p_weights = ptrFactory.make((size_t)sumBlockNum); + AccPtr p_thr_wsum_prior_offsetxyz_class = ptrFactory.make((size_t)sumBlockNum*4); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); - int iorient = 0; - partial_pos=0; - for (long int iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) - { - long int fake_class = iclass-sp.iclass_min; // if we only have the third class to do, the third class will be the "first" we do, i.e. the "fake" first. - if ((baseMLO->mymodel.pdf_class[iclass] == 0.) || (ProjectionData[img_id].class_entries[iclass] == 0) ) - continue; - int block_num = block_nums[nr_fake_classes*img_id + fake_class]; + p_weights.allAlloc(); + p_thr_wsum_prior_offsetxyz_class.allAlloc(); + CTOC(accMLO->timer,"collect_data_2_pre_kernel"); + int partial_pos=0; - for (long int n = partial_pos; n < partial_pos+block_num; n++) - { - iorient= FinePassWeights[img_id].rot_id[FPCMasks[img_id][iclass].jobOrigin[n-partial_pos]+FPCMasks[img_id][iclass].firstPos]; + for (long int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + { + long int fake_class = exp_iclass-sp.iclass_min; // if we only have the third class to do, the third class will be the "first" we do, i.e. the "fake" first. + if ((baseMLO->mymodel.pdf_class[exp_iclass] == 0.) || (ProjectionData.class_entries[exp_iclass] == 0) ) + continue; + + // Use the constructed mask to construct a partial class-specific input + IndexedDataArray thisClassFinePassWeights(FinePassWeights,FPCMasks[exp_iclass]); + + long int cpos=fake_class*nr_transes; + int block_num = block_nums[fake_class]; + + runCollect2jobs(block_num, + &(~oo_otrans)[otrans_x+cpos], // otrans-size -> make const + &(~oo_otrans)[otrans_y+cpos], // otrans-size -> make const + &(~oo_otrans)[otrans_z+cpos], // otrans-size -> make const + &(~oo_otrans)[otrans_x2y2z2+cpos], // otrans-size -> make const + ~thisClassFinePassWeights.weights, + (XFLOAT)op.significant_weight, + (XFLOAT)op.sum_weight, + sp.nr_trans, + sp.nr_oversampled_trans, + sp.nr_oversampled_rot, + oversamples, + (baseMLO->do_skip_align || baseMLO->do_skip_rotate ), + &(~p_weights)[partial_pos], + &(~p_thr_wsum_prior_offsetxyz_class)[offsetx_class+partial_pos], + &(~p_thr_wsum_prior_offsetxyz_class)[offsety_class+partial_pos], + &(~p_thr_wsum_prior_offsetxyz_class)[offsetz_class+partial_pos], + &(~p_thr_wsum_prior_offsetxyz_class)[sigma2_offset+partial_pos], + ~thisClassFinePassWeights.rot_idx, + ~thisClassFinePassWeights.trans_idx, + ~FPCMasks[exp_iclass].jobOrigin, + ~FPCMasks[exp_iclass].jobExtent, + accMLO->dataIs3D, +#ifdef _HIP_ENABLED + hipStreamPerThread); + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); +#else + cudaStreamPerThread); + LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#endif - long int mydir, idir=floor(iorient/sp.nr_psi); - if (baseMLO->mymodel.orientational_prior_mode == NOPRIOR) - mydir = idir; - else - mydir = op.pointer_dir_nonzeroprior[idir]; + partial_pos+=block_num; + } - // store partials according to indices of the relevant dimension - unsigned ithr_wsum_pdf_direction = baseMLO->mymodel.nr_bodies > 1 ? ibody : iclass; - DIRECT_MULTIDIM_ELEM(thr_wsum_pdf_direction[ithr_wsum_pdf_direction], mydir) += p_weights[n]; - thr_sumw_group[img_id] += p_weights[n]; - thr_wsum_pdf_class[iclass] += p_weights[n]; + CTIC(accMLO->timer,"collect_data_2_post_kernel"); + p_weights.cpToHost(); + p_thr_wsum_prior_offsetxyz_class.cpToHost(); - thr_wsum_sigma2_offset += my_pixel_size * my_pixel_size * p_thr_wsum_prior_offsetxyz_class[sigma2_offset+n]; +#ifdef _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); +#else + DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); +#endif + int iorient = 0; + partial_pos=0; + for (long int iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) + { + long int fake_class = iclass-sp.iclass_min; // if we only have the third class to do, the third class will be the "first" we do, i.e. the "fake" first. + if ((baseMLO->mymodel.pdf_class[iclass] == 0.) || (ProjectionData.class_entries[iclass] == 0) ) + continue; + int block_num = block_nums[fake_class]; - if (baseMLO->mymodel.ref_dim == 2) - { - thr_wsum_prior_offsetx_class[iclass] += my_pixel_size * p_thr_wsum_prior_offsetxyz_class[offsetx_class+n]; - thr_wsum_prior_offsety_class[iclass] += my_pixel_size * p_thr_wsum_prior_offsetxyz_class[offsety_class+n]; - } - } - partial_pos+=block_num; - } // end loop iclass - CTOC(accMLO->timer,"collect_data_2_post_kernel"); - } // end loop img_id + for (long int n = partial_pos; n < partial_pos+block_num; n++) + { + iorient= FinePassWeights.rot_id[FPCMasks[iclass].jobOrigin[n-partial_pos]+FPCMasks[iclass].firstPos]; + long int mydir, idir=floor(iorient/sp.nr_psi); + if (baseMLO->mymodel.orientational_prior_mode == NOPRIOR) + mydir = idir; + else + mydir = op.pointer_dir_nonzeroprior[idir]; + + // store partials according to indices of the relevant dimension + unsigned ithr_wsum_pdf_direction = baseMLO->mymodel.nr_bodies > 1 ? ibody : iclass; + DIRECT_MULTIDIM_ELEM(thr_wsum_pdf_direction[ithr_wsum_pdf_direction], mydir) += p_weights[n]; + thr_sumw_group += p_weights[n]; + thr_wsum_pdf_class[iclass] += p_weights[n]; + + thr_wsum_sigma2_offset += my_pixel_size * my_pixel_size * p_thr_wsum_prior_offsetxyz_class[sigma2_offset+n]; + + if (baseMLO->mymodel.ref_dim == 2) + { + thr_wsum_prior_offsetx_class[iclass] += my_pixel_size * p_thr_wsum_prior_offsetxyz_class[offsetx_class+n]; + thr_wsum_prior_offsety_class[iclass] += my_pixel_size * p_thr_wsum_prior_offsetxyz_class[offsety_class+n]; + } + } + partial_pos+=block_num; + } // end loop iclass + CTOC(accMLO->timer,"collect_data_2_post_kernel"); /*====================================================== SET METADATA ======================================================*/ std::vector< RFLOAT> oversampled_rot, oversampled_tilt, oversampled_psi; - for (long int img_id = 0; img_id < sp.nr_images; img_id++) - { - int my_metadata_offset = op.metadata_offset + img_id; - RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id, img_id); - - CTIC(accMLO->timer,"setMetadata"); - - if(baseMLO->adaptive_oversampling!=0) - op.max_index[img_id].fineIndexToFineIndices(sp); // set partial indices corresponding to the found max_index, to be used below - else - op.max_index[img_id].coarseIndexToCoarseIndices(sp); - - baseMLO->sampling.getTranslationsInPixel(op.max_index[img_id].itrans, baseMLO->adaptive_oversampling, my_pixel_size, - oversampled_translations_x, oversampled_translations_y, oversampled_translations_z, - (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry)); - - //TODO We already have rot, tilt and psi don't calculated them again - if(baseMLO->do_skip_align || baseMLO->do_skip_rotate) - baseMLO->sampling.getOrientations(sp.idir_min, sp.ipsi_min, baseMLO->adaptive_oversampling, oversampled_rot, oversampled_tilt, oversampled_psi, - op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior); - else - baseMLO->sampling.getOrientations(op.max_index[img_id].idir, op.max_index[img_id].ipsi, baseMLO->adaptive_oversampling, oversampled_rot, oversampled_tilt, oversampled_psi, - op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior); - - baseMLO->sampling.getOrientations(op.max_index[img_id].idir, op.max_index[img_id].ipsi, baseMLO->adaptive_oversampling, oversampled_rot, oversampled_tilt, oversampled_psi, - op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior); - - RFLOAT rot = oversampled_rot[op.max_index[img_id].ioverrot]; - RFLOAT tilt = oversampled_tilt[op.max_index[img_id].ioverrot]; - RFLOAT psi = oversampled_psi[op.max_index[img_id].ioverrot]; - - int icol_rot = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_ROT : 0 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - int icol_tilt = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_TILT : 1 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - int icol_psi = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_PSI : 2 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - int icol_xoff = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_XOFF : 3 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - int icol_yoff = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_YOFF : 4 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - int icol_zoff = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_ZOFF : 5 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; - - RFLOAT old_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_rot); - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_rot) = rot; - RFLOAT old_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_tilt); - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_tilt) = tilt; - RFLOAT old_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_psi); - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_psi) = psi; - - Matrix1D shifts(baseMLO->mymodel.data_dim); - - XX(shifts) = XX(op.old_offset[img_id]) + oversampled_translations_x[op.max_index[img_id].iovertrans]; - YY(shifts) = YY(op.old_offset[img_id]) + oversampled_translations_y[op.max_index[img_id].iovertrans]; - if (accMLO->dataIs3D) - { - ZZ(shifts) = ZZ(op.old_offset[img_id]) + oversampled_translations_z[op.max_index[img_id].iovertrans]; - } + CTIC(accMLO->timer,"setMetadata"); + + if(baseMLO->adaptive_oversampling!=0) + op.max_index.fineIndexToFineIndices(sp); // set partial indices corresponding to the found max_index, to be used below + else + op.max_index.coarseIndexToCoarseIndices(sp); + + baseMLO->sampling.getTranslationsInPixel(op.max_index.itrans, baseMLO->adaptive_oversampling, my_pixel_size, + oversampled_translations_x, oversampled_translations_y, oversampled_translations_z, + (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry)); + + //TODO We already have rot, tilt and psi don't calculated them again + if(baseMLO->do_skip_align || baseMLO->do_skip_rotate) + baseMLO->sampling.getOrientations(sp.idir_min, sp.ipsi_min, baseMLO->adaptive_oversampling, oversampled_rot, oversampled_tilt, oversampled_psi, + op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior); + else + baseMLO->sampling.getOrientations(op.max_index.idir, op.max_index.ipsi, baseMLO->adaptive_oversampling, oversampled_rot, oversampled_tilt, oversampled_psi, + op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior); + + baseMLO->sampling.getOrientations(op.max_index.idir, op.max_index.ipsi, baseMLO->adaptive_oversampling, oversampled_rot, oversampled_tilt, oversampled_psi, + op.pointer_dir_nonzeroprior, op.directions_prior, op.pointer_psi_nonzeroprior, op.psi_prior); + + RFLOAT rot = oversampled_rot[op.max_index.ioverrot]; + RFLOAT tilt = oversampled_tilt[op.max_index.ioverrot]; + RFLOAT psi = oversampled_psi[op.max_index.ioverrot]; + + int icol_rot = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_ROT : 0 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + int icol_tilt = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_TILT : 1 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + int icol_psi = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_PSI : 2 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + int icol_xoff = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_XOFF : 3 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + int icol_yoff = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_YOFF : 4 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + int icol_zoff = (baseMLO->mymodel.nr_bodies == 1) ? METADATA_ZOFF : 5 + METADATA_LINE_LENGTH_BEFORE_BODIES + (ibody) * METADATA_NR_BODY_PARAMS; + + RFLOAT old_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_rot); + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_rot) = rot; + RFLOAT old_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_tilt); + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_tilt) = tilt; + RFLOAT old_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_psi); + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_psi) = psi; + + int shiftdim = (accMLO->shiftsIs3D) ? 3 : 2 ; + Matrix1D shifts(shiftdim); + + XX(shifts) = XX(op.old_offset) + oversampled_translations_x[op.max_index.iovertrans]; + YY(shifts) = YY(op.old_offset) + oversampled_translations_y[op.max_index.iovertrans]; + if (accMLO->shiftsIs3D) + { + ZZ(shifts) = ZZ(op.old_offset) + oversampled_translations_z[op.max_index.iovertrans]; + } - // Use oldpsi-angle to rotate back the XX(exp_old_offset[img_id]) + oversampled_translations_x[iover_trans] and - if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) ) - transformCartesianAndHelicalCoords(shifts, shifts, old_rot, old_tilt, old_psi, HELICAL_TO_CART_COORDS); + // Use oldpsi-angle to rotate back the XX(exp_old_offset[img_id]) + oversampled_translations_x[iover_trans] and + if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) ) + transformCartesianAndHelicalCoords(shifts, shifts, old_rot, old_tilt, old_psi, HELICAL_TO_CART_COORDS); - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_xoff) = XX(shifts); - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_yoff) = YY(shifts); - if (accMLO->dataIs3D) - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, icol_zoff) = ZZ(shifts); + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_xoff) = XX(shifts); + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_yoff) = YY(shifts); + if (accMLO->shiftsIs3D) + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, icol_zoff) = ZZ(shifts); - if (ibody == 0) - { - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_CLASS) = (RFLOAT)op.max_index[img_id].iclass + 1; - RFLOAT pmax = op.max_weight[img_id]/op.sum_weight[img_id]; - if(pmax>1) //maximum normalised probability weight is (unreasonably) larger than unity - CRITICAL("Relion is finding a normalised probability greater than 1"); - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PMAX) = pmax; - } - CTOC(accMLO->timer,"setMetadata"); - } + if (ibody == 0) + { + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_CLASS) = (RFLOAT)op.max_index.iclass + 1; + RFLOAT pmax = op.max_weight/op.sum_weight; + if(pmax>1) //maximum normalised probability weight is (unreasonably) larger than unity + { + std::cerr << " op.max_weight= " << op.max_weight << " op.sum_weight= " << op.sum_weight << std::endl; + CRITICAL("Relion is finding a normalised probability greater than 1"); + } + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PMAX) = pmax; + } + CTOC(accMLO->timer,"setMetadata"); CTOC(accMLO->timer,"collect_data_2"); @@ -2505,28 +2936,28 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, { CTIC(accMLO->timer,"maximization"); + int group_id = baseMLO->mydata.getGroupId(op.part_id); + bool ctf_premultiplied = baseMLO->mydata.obsModel.getCtfPremultiplied(optics_group); + + long unsigned translation_num((sp.itrans_max - sp.itrans_min + 1) * sp.nr_oversampled_trans); + + size_t trans_x_offset = 0*(size_t)translation_num; + size_t trans_y_offset = 1*(size_t)translation_num; + size_t trans_z_offset = 2*(size_t)translation_num; + + AccPtr trans_xyz = ptrFactory.make((size_t)translation_num*3); +#ifdef _SYCL_ENABLED + trans_xyz.setStreamAccType(devAcc); +#endif + trans_xyz.allAlloc(); + for (int img_id = 0; img_id < sp.nr_images; img_id++) { - int my_metadata_offset = op.metadata_offset + img_id; - int group_id = baseMLO->mydata.getGroupId(op.part_id, img_id); - const int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, img_id); - RFLOAT my_pixel_size = baseMLO->mydata.getImagePixelSize(op.part_id, img_id); - bool ctf_premultiplied = baseMLO->mydata.obsModel.getCtfPremultiplied(optics_group); /*====================================================== TRANSLATIONS ======================================================*/ - long unsigned translation_num((sp.itrans_max - sp.itrans_min + 1) * sp.nr_oversampled_trans); - - size_t trans_x_offset = 0*(size_t)translation_num; - size_t trans_y_offset = 1*(size_t)translation_num; - size_t trans_z_offset = 2*(size_t)translation_num; - - AccPtr trans_xyz = ptrFactory.make((size_t)translation_num*3); - - trans_xyz.allAlloc(); - int j = 0; for (long int itrans = 0; itrans < (sp.itrans_max - sp.itrans_min + 1); itrans++) { @@ -2537,22 +2968,34 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, for (long int iover_trans = 0; iover_trans < oversampled_translations_x.size(); iover_trans++) { - RFLOAT xshift = 0., yshift = 0., zshift = 0.; + RFLOAT xshift(0.), yshift(0.), zshift(0.); xshift = oversampled_translations_x[iover_trans]; yshift = oversampled_translations_y[iover_trans]; - if (accMLO->dataIs3D) + if (accMLO->shiftsIs3D) zshift = oversampled_translations_z[iover_trans]; if ( (baseMLO->do_helical_refine) && (! baseMLO->ignore_helical_symmetry) ) { - RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_ROT); - RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_TILT); - RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PSI); - transformCartesianAndHelicalCoords(xshift, yshift, zshift, xshift, yshift, zshift, rot_deg, tilt_deg, psi_deg, (accMLO->dataIs3D) ? (3) : (2), HELICAL_TO_CART_COORDS); + RFLOAT rot_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); + RFLOAT tilt_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); + RFLOAT psi_deg = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); + transformCartesianAndHelicalCoords(xshift, yshift, zshift, xshift, yshift, zshift, rot_deg, tilt_deg, psi_deg, + (accMLO->shiftsIs3D) ? (3) : (2), HELICAL_TO_CART_COORDS); } - trans_xyz[trans_x_offset+j] = -2 * PI * xshift / (double)baseMLO->image_full_size[optics_group]; + if (op.is_tomo) + { + // op.old_offset was not yet applied for subtomos! + xshift += XX(op.old_offset); + yshift += YY(op.old_offset); + zshift += ZZ(op.old_offset); + baseMLO->mydata.getTranslationInTiltSeries(op.part_id, img_id, + xshift, yshift, zshift, + xshift, yshift, zshift); + } + + trans_xyz[trans_x_offset+j] = -2 * PI * xshift / (double)baseMLO->image_full_size[optics_group]; trans_xyz[trans_y_offset+j] = -2 * PI * yshift / (double)baseMLO->image_full_size[optics_group]; trans_xyz[trans_z_offset+j] = -2 * PI * zshift / (double)baseMLO->image_full_size[optics_group]; j ++; @@ -2581,7 +3024,9 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, size_t im_nomask_offset = 3*(size_t)image_size; AccPtr Fimgs = ptrFactory.make(4*(size_t)image_size); - +#ifdef _SYCL_ENABLED + Fimgs.setStreamAccType(devAcc); +#endif Fimgs.allAlloc(); for (unsigned long i = 0; i < image_size; i ++) @@ -2624,6 +3069,9 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, } AccPtr ctfs = ptrFactory.make((size_t)image_size); +#ifdef _SYCL_ENABLED + ctfs.setStreamAccType(devAcc); +#endif ctfs.allAlloc(); if (baseMLO->do_ctf_correction) @@ -2644,11 +3092,14 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, ======================================================*/ AccPtr Minvsigma2s = ptrFactory.make((size_t)image_size); +#ifdef _SYCL_ENABLED + Minvsigma2s.setStreamAccType(devAcc); +#endif Minvsigma2s.allAlloc(); if (baseMLO->do_map) for (unsigned long i = 0; i < image_size; i++) - Minvsigma2s[i] = op.local_Minvsigma2[img_id].data[i]; + Minvsigma2s[i] = op.local_Minvsigma2.data[i]; else for (unsigned long i = 0; i < image_size; i++) Minvsigma2s[i] = 1; @@ -2667,7 +3118,9 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, size_t sum_offset = 2*(size_t)(baseMLO->mymodel.nr_classes*image_size); AccPtr wdiff2s = ptrFactory.make(wdiff2s_buf); - +#ifdef _SYCL_ENABLED + wdiff2s.setStreamAccType(devAcc); +#endif wdiff2s.allAlloc(); wdiff2s.accInit(0); @@ -2676,31 +3129,44 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, CUSTOM_ALLOCATOR_REGION_NAME("BP_data"); // Loop from iclass_min to iclass_max to deal with seed generation in first iteration - AccPtr sorted_weights = ptrFactory.make((size_t)(ProjectionData[img_id].orientationNumAllClasses * translation_num)); + AccPtr sorted_weights = ptrFactory.make((size_t)(ProjectionData.orientationNumAllClasses * translation_num)); +#ifdef _SYCL_ENABLED + sorted_weights.setStreamAccType(devAcc); +#endif sorted_weights.allAlloc(); std::vector > eulers(baseMLO->mymodel.nr_classes, ptrFactory.make()); unsigned long classPos = 0; - + #ifdef _HIP_ENABLED + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); + #elif _CUDA_ENABLED for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[exp_iclass])); DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + #elif _SYCL_ENABLED + if (accMLO->useStream()) + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + (accMLO->classStreams[exp_iclass])->waitAll(); + devAcc->waitAll(); + #endif for (unsigned long iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) { - if((baseMLO->mymodel.pdf_class[iclass] == 0.) || (ProjectionData[img_id].class_entries[iclass] == 0)) + if((baseMLO->mymodel.pdf_class[iclass] == 0.) || (ProjectionData.class_entries[iclass] == 0)) continue; // Use the constructed mask to construct a partial class-specific input - IndexedDataArray thisClassFinePassWeights(FinePassWeights[img_id],FPCMasks[img_id][iclass]); + IndexedDataArray thisClassFinePassWeights(FinePassWeights,FPCMasks[iclass]); CTIC(accMLO->timer,"thisClassProjectionSetupCoarse"); // use "slice" constructor with class-specific parameters to retrieve a temporary ProjectionParams with data for this class - ProjectionParams thisClassProjectionData( ProjectionData[img_id], - ProjectionData[img_id].class_idx[iclass], - ProjectionData[img_id].class_idx[iclass]+ProjectionData[img_id].class_entries[iclass]); + ProjectionParams thisClassProjectionData( ProjectionData, + ProjectionData.class_idx[iclass], + ProjectionData.class_idx[iclass]+ProjectionData.class_entries[iclass]); - thisClassProjectionData.orientation_num[0] = ProjectionData[img_id].orientation_num[iclass]; + thisClassProjectionData.orientation_num[0] = ProjectionData.orientation_num[iclass]; CTOC(accMLO->timer,"thisClassProjectionSetupCoarse"); long unsigned orientation_num(thisClassProjectionData.orientation_num[0]); @@ -2726,12 +3192,22 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, eulers[iclass].setSize(orientation_num * 9); eulers[iclass].setStream(accMLO->classStreams[iclass]); +#ifdef _SYCL_ENABLED + eulers[iclass].setAccType(accSYCL); +#endif eulers[iclass].hostAlloc(); CTIC(accMLO->timer,"generateEulerMatricesProjector"); Matrix2D mag; - mag.initIdentity(3); + if (op.is_tomo) + { + mag = baseMLO->mydata.getRotationMatrix(op.part_id, img_id); + } + else + { + mag.initIdentity(3); + } mag = baseMLO->mydata.obsModel.applyAnisoMag(mag, optics_group); mag = baseMLO->mydata.obsModel.applyScaleDifference(mag, optics_group, baseMLO->mymodel.ori_size, baseMLO->mymodel.pixel_size); if (!mag.isIdentity()) @@ -2759,7 +3235,7 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, CTIC(accMLO->timer,"pre_wavg_map"); for (long unsigned i = 0; i < orientation_num*translation_num; i++) - sorted_weights[classPos+i] = -std::numeric_limits::max(); + sorted_weights[classPos+i] = std::numeric_limits::lowest(); for (long unsigned i = 0; i < thisClassFinePassWeights.weights.getSize(); i++) sorted_weights[classPos+(thisClassFinePassWeights.rot_idx[i]) * translation_num + thisClassFinePassWeights.trans_idx[i] ] @@ -2771,10 +3247,20 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, sorted_weights.cpToDevice(); // These syncs are necessary (for multiple ranks on the same GPU), and (assumed) low-cost. + #ifdef _HIP_ENABLED + for (unsigned long iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(accMLO->classStreams[iclass])); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); + #elif _CUDA_ENABLED for (unsigned long iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) DEBUG_HANDLE_ERROR(cudaStreamSynchronize(accMLO->classStreams[iclass])); - DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + #elif _SYCL_ENABLED + if (accMLO->useStream()) + for (unsigned long iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) + (accMLO->classStreams[iclass])->waitAll(); + devAcc->waitAll(); + #endif classPos = 0; for (unsigned long iclass = sp.iclass_min; iclass <= sp.iclass_max; iclass++) @@ -2783,20 +3269,20 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, if (baseMLO->mymodel.nr_bodies > 1) iproj = ibody; else iproj = iclass; - if((baseMLO->mymodel.pdf_class[iclass] == 0.) || (ProjectionData[img_id].class_entries[iclass] == 0)) + if((baseMLO->mymodel.pdf_class[iclass] == 0.) || (ProjectionData.class_entries[iclass] == 0)) continue; /*====================================================== KERNEL CALL ======================================================*/ - long unsigned orientation_num(ProjectionData[img_id].orientation_num[iclass]); + long unsigned orientation_num(ProjectionData.orientation_num[iclass]); AccProjectorKernel projKernel = AccProjectorKernel::makeKernel( accMLO->bundle->projectors[iproj], - op.local_Minvsigma2[img_id].xdim, - op.local_Minvsigma2[img_id].ydim, - op.local_Minvsigma2[img_id].zdim, - op.local_Minvsigma2[img_id].xdim-1); + op.local_Minvsigma2.xdim, + op.local_Minvsigma2.ydim, + op.local_Minvsigma2.zdim, + op.local_Minvsigma2.xdim-1); runWavgKernel( projKernel, @@ -2833,17 +3319,17 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, ======================================================*/ int nr_classes = baseMLO->mymodel.nr_classes; - std::vector class_sum_weight(nr_classes, baseMLO->is_som_iter ? 0 : op.sum_weight[img_id]); + std::vector class_sum_weight(nr_classes, baseMLO->is_som_iter ? 0 : op.sum_weight); if (baseMLO->is_som_iter) { - std::vector s = SomGraph::arg_sort(op.sum_weight_class[img_id], false); + std::vector s = SomGraph::arg_sort(op.sum_weight_class, false); unsigned bpu = s[0]; unsigned sbpu = s[1]; baseMLO->wsum_model.som.add_edge_activity(bpu, sbpu); - class_sum_weight[bpu] = op.sum_weight_class[img_id][bpu]; + class_sum_weight[bpu] = op.sum_weight_class[bpu]; thr_wsum_pdf_class[bpu] += 1; baseMLO->wsum_model.som.add_node_activity(bpu); baseMLO->mymodel.som.add_node_age(bpu); @@ -2853,7 +3339,7 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, for (int i = 0; i < weights.size(); i++) { unsigned idx = weights[i].first; float w = weights[i].second * baseMLO->som_neighbour_pull; - class_sum_weight[idx] = op.sum_weight_class[img_id][idx] / w; + class_sum_weight[idx] = op.sum_weight_class[idx] / w; thr_wsum_pdf_class[idx] += w; baseMLO->wsum_model.som.add_node_activity(idx, w); baseMLO->mymodel.som.add_node_age(idx, w); @@ -2872,20 +3358,20 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, if (baseMLO->mymodel.nr_bodies > 1) iproj = ibody; else iproj = iclass; - if((baseMLO->mymodel.pdf_class[iclass] == 0.) || (ProjectionData[img_id].class_entries[iclass] == 0)) + if((baseMLO->mymodel.pdf_class[iclass] == 0.) || (ProjectionData.class_entries[iclass] == 0)) continue; if ( baseMLO->is_som_iter && class_sum_weight[iclass] == 0) continue; - long unsigned orientation_num(ProjectionData[img_id].orientation_num[iclass]); + long unsigned orientation_num(ProjectionData.orientation_num[iclass]); AccProjectorKernel projKernel = AccProjectorKernel::makeKernel( accMLO->bundle->projectors[iproj], - op.local_Minvsigma2[img_id].xdim, - op.local_Minvsigma2[img_id].ydim, - op.local_Minvsigma2[img_id].zdim, - op.local_Minvsigma2[img_id].xdim - 1); + op.local_Minvsigma2.xdim, + op.local_Minvsigma2.ydim, + op.local_Minvsigma2.zdim, + op.local_Minvsigma2.xdim - 1); #ifdef TIMING if (op.part_id == baseMLO->exp_my_first_part_id) @@ -2911,12 +3397,12 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, ~Minvsigma2s, ~ctfs, translation_num, - (XFLOAT) op.significant_weight[img_id], - (XFLOAT) (baseMLO->is_som_iter ? class_sum_weight[iclass] : op.sum_weight[img_id]), + (XFLOAT) op.significant_weight, + (XFLOAT) (baseMLO->is_som_iter ? class_sum_weight[iclass] : op.sum_weight), ~eulers[iclass], - op.local_Minvsigma2[img_id].xdim, - op.local_Minvsigma2[img_id].ydim, - op.local_Minvsigma2[img_id].zdim, + op.local_Minvsigma2.xdim, + op.local_Minvsigma2.ydim, + op.local_Minvsigma2.zdim, orientation_num, accMLO->dataIs3D, (baseMLO->do_grad), @@ -2938,6 +3424,16 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, CUSTOM_ALLOCATOR_REGION_NAME("UNSET"); + #ifdef _HIP_ENABLED + // NOTE: We've never seen that this sync is necessary, but it is needed in principle, and + // its absence in other parts of the code has caused issues. It is also very low-cost. + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(accMLO->classStreams[exp_iclass])); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); + + wdiff2s.cpToHost(); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(hipStreamPerThread)); + #elif _CUDA_ENABLED // NOTE: We've never seen that this sync is necessary, but it is needed in principle, and // its absence in other parts of the code has caused issues. It is also very low-cost. for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) @@ -2946,12 +3442,21 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, wdiff2s.cpToHost(); DEBUG_HANDLE_ERROR(cudaStreamSynchronize(cudaStreamPerThread)); + #elif _SYCL_ENABLED + if (accMLO->useStream()) + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + (accMLO->classStreams[exp_iclass])->waitAll(); + devAcc->waitAll(); + + wdiff2s.cpToHost(); + wdiff2s.streamSync(); + #endif AAXA_pos=0; for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) { - if((baseMLO->mymodel.pdf_class[exp_iclass] == 0.) || (ProjectionData[img_id].class_entries[exp_iclass] == 0)) + if((baseMLO->mymodel.pdf_class[exp_iclass] == 0.) || (ProjectionData.class_entries[exp_iclass] == 0)) continue; for (long int j = 0; j < image_size; j++) { @@ -2959,8 +3464,8 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, if (ires > -1 && baseMLO->do_scale_correction && DIRECT_A1D_ELEM(baseMLO->mymodel.data_vs_prior_class[exp_iclass], ires) > 3.) { - exp_wsum_scale_correction_AA[img_id] += wdiff2s[AA_offset+AAXA_pos+j]; - exp_wsum_scale_correction_XA[img_id] += wdiff2s[XA_offset+AAXA_pos+j]; + exp_wsum_scale_correction_AA += wdiff2s[AA_offset+AAXA_pos+j]; + exp_wsum_scale_correction_XA += wdiff2s[XA_offset+AAXA_pos+j]; } } AAXA_pos += image_size; @@ -2971,8 +3476,9 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, int ires = DIRECT_MULTIDIM_ELEM(baseMLO->Mresol_fine[optics_group], j); if (ires > -1) { - thr_wsum_sigma2_noise[img_id].data[ires] += (RFLOAT) wdiff2s[sum_offset+j]; - exp_wsum_norm_correction[img_id] += (RFLOAT) wdiff2s[sum_offset+j]; //TODO could be gpu-reduced + // For multiple images, divide by sp.nr_images! + thr_wsum_sigma2_noise.data[ires] += (RFLOAT) wdiff2s[sum_offset+j]/(RFLOAT)sp.nr_images; + exp_wsum_norm_correction += (RFLOAT) wdiff2s[sum_offset+j]/(RFLOAT)sp.nr_images; //TODO could be gpu-reduced } } @@ -2986,163 +3492,156 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, // loop over all images inside this particle RFLOAT thr_avg_norm_correction = 0.; RFLOAT thr_sum_dLL = 0., thr_sum_Pmax = 0.; - for (int img_id = 0; img_id < sp.nr_images; img_id++) - { - int my_metadata_offset = op.metadata_offset + img_id; - int group_id = baseMLO->mydata.getGroupId(op.part_id, img_id); - const int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, img_id); - RFLOAT my_pixel_size = baseMLO->mydata.getOpticsPixelSize(optics_group); - int my_image_size = baseMLO->mydata.getOpticsImageSize(optics_group); + // If the current images were smaller than the original size, fill the rest of wsum_model.sigma2_noise with the power_class spectrum of the images + for (int img_id = 0; img_id < sp.nr_images; img_id++) + { - // If the current images were smaller than the original size, fill the rest of wsum_model.sigma2_noise with the power_class spectrum of the images - for (unsigned long ires = baseMLO->image_current_size[optics_group]/2 + 1; ires < baseMLO->image_full_size[optics_group]/2 + 1; ires++) - { - DIRECT_A1D_ELEM(thr_wsum_sigma2_noise[img_id], ires) += DIRECT_A1D_ELEM(op.power_img[img_id], ires); - // Also extend the weighted sum of the norm_correction - exp_wsum_norm_correction[img_id] += DIRECT_A1D_ELEM(op.power_img[img_id], ires); - } + for (unsigned long ires = baseMLO->image_current_size[optics_group] / 2 + 1; + ires < baseMLO->image_full_size[optics_group] / 2 + 1; ires++) + { + // For multiple images, divide by sp.nr_images! + DIRECT_A1D_ELEM(thr_wsum_sigma2_noise, ires) += DIRECT_A1D_ELEM(op.power_img[img_id], ires)/(RFLOAT)sp.nr_images; + // Also extend the weighted sum of the norm_correction + exp_wsum_norm_correction += DIRECT_A1D_ELEM(op.power_img[img_id], ires)/(RFLOAT)sp.nr_images; + } + } - // Store norm_correction - // Multiply by old value because the old norm_correction term was already applied to the image - if (baseMLO->do_norm_correction && baseMLO->mymodel.nr_bodies == 1) - { - RFLOAT old_norm_correction = DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_NORM); - old_norm_correction /= baseMLO->mymodel.avg_norm_correction; - // The factor two below is because exp_wsum_norm_correctiom is similar to sigma2_noise, which is the variance for the real/imag components - // The variance of the total image (on which one normalizes) is twice this value! - RFLOAT normcorr = old_norm_correction * sqrt(exp_wsum_norm_correction[img_id] * 2.); - thr_avg_norm_correction += normcorr; + // Store norm_correction + // Multiply by old value because the old norm_correction term was already applied to the image + if (baseMLO->do_norm_correction && baseMLO->mymodel.nr_bodies == 1) + { - // Now set the new norm_correction in the relevant position of exp_metadata - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_NORM) = normcorr; + RFLOAT old_norm_correction = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_NORM); + old_norm_correction /= baseMLO->mymodel.avg_norm_correction; + // The factor two below is because exp_wsum_norm_correctiom is similar to sigma2_noise, which is the variance for the real/imag components + // The variance of the total image (on which one normalizes) is twice this value! + RFLOAT normcorr = old_norm_correction * sqrt(exp_wsum_norm_correction * 2.); + thr_avg_norm_correction += normcorr; + // Now set the new norm_correction in the relevant position of exp_metadata + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_NORM) = normcorr; - // Print warning for strange norm-correction values - if (!((baseMLO->iter == 1 && baseMLO->do_firstiter_cc) || baseMLO->do_always_cc) && DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_NORM) > 10.) - { - std::cout << " WARNING: norm_correction= "<< DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_NORM) - << " for particle " << op.part_id << " in group " << group_id + 1 - << "; Are your groups large enough? Or is the reference on the correct greyscale?" << std::endl; - } + // Print warning for strange norm-correction values + if (!((baseMLO->iter == 1 && baseMLO->do_firstiter_cc) || baseMLO->do_always_cc) && DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_NORM) > 10.) + { + std::cout << " WARNING: norm_correction= "<< DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_NORM) + << " for particle " << op.part_id + << "; Are your groups large enough? Or is the reference on the correct greyscale?" << std::endl; + } - } + } - // Store weighted sums for scale_correction - if (baseMLO->do_scale_correction) - { - // Divide XA by the old scale_correction and AA by the square of that, because was incorporated into Fctf - exp_wsum_scale_correction_XA[img_id] /= baseMLO->mymodel.scale_correction[group_id]; - exp_wsum_scale_correction_AA[img_id] /= baseMLO->mymodel.scale_correction[group_id] * baseMLO->mymodel.scale_correction[group_id]; + // Store weighted sums for scale_correction + if (baseMLO->do_scale_correction) + { - thr_wsum_signal_product_spectra[img_id] += exp_wsum_scale_correction_XA[img_id]; - thr_wsum_reference_power_spectra[img_id] += exp_wsum_scale_correction_AA[img_id]; - } + // Divide XA by the old scale_correction and AA by the square of that, because was incorporated into Fctf + exp_wsum_scale_correction_XA /= baseMLO->mymodel.scale_correction[group_id]; + exp_wsum_scale_correction_AA /= baseMLO->mymodel.scale_correction[group_id] * baseMLO->mymodel.scale_correction[group_id]; - // Calculate DLL for each particle - RFLOAT logsigma2 = 0.; - RFLOAT remap_image_sizes = (baseMLO->mymodel.ori_size * baseMLO->mymodel.pixel_size) / (my_image_size * my_pixel_size); - FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(baseMLO->Mresol_fine[optics_group]) - { - int ires = DIRECT_MULTIDIM_ELEM(baseMLO->Mresol_fine[optics_group], n); - int ires_remapped = ROUND(remap_image_sizes * ires); - // Note there is no sqrt in the normalisation term because of the 2-dimensionality of the complex-plane - // Also exclude origin from logsigma2, as this will not be considered in the P-calculations - if (ires > 0 && ires_remapped < XSIZE(baseMLO->mymodel.sigma2_noise[optics_group])) - logsigma2 += log( 2. * PI * DIRECT_A1D_ELEM(baseMLO->mymodel.sigma2_noise[optics_group], ires_remapped)); - } - RFLOAT dLL; + thr_wsum_signal_product_spectra += exp_wsum_scale_correction_XA; + thr_wsum_reference_power_spectra += exp_wsum_scale_correction_AA; + } - if ((baseMLO->iter==1 && baseMLO->do_firstiter_cc) || baseMLO->do_always_cc) - dLL = -op.min_diff2[img_id]; - else - dLL = log(op.sum_weight[img_id]) - op.min_diff2[img_id] - logsigma2; + // Calculate DLL for each particle + RFLOAT logsigma2 = 0.; + RFLOAT remap_image_sizes = (baseMLO->mymodel.ori_size * baseMLO->mymodel.pixel_size) / (my_image_size * my_pixel_size); + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(baseMLO->Mresol_fine[optics_group]) + { + int ires = DIRECT_MULTIDIM_ELEM(baseMLO->Mresol_fine[optics_group], n); + int ires_remapped = ROUND(remap_image_sizes * ires); + // Note there is no sqrt in the normalisation term because of the 2-dimensionality of the complex-plane + // Also exclude origin from logsigma2, as this will not be considered in the P-calculations + if (ires > 0 && ires_remapped < XSIZE(baseMLO->mymodel.sigma2_noise[optics_group])) + logsigma2 += sp.nr_images * log( 2. * PI * DIRECT_A1D_ELEM(baseMLO->mymodel.sigma2_noise[optics_group], ires_remapped)); + } - // Store dLL of each image in the output array, and keep track of total sum - DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_DLL) = dLL; - thr_sum_dLL += dLL; + RFLOAT dLL; + if ((baseMLO->iter==1 && baseMLO->do_firstiter_cc) || baseMLO->do_always_cc) + dLL = -op.min_diff2/sp.nr_images; + else + dLL = log(op.sum_weight) - op.min_diff2 - logsigma2; - // Also store sum of Pmax - thr_sum_Pmax += DIRECT_A2D_ELEM(baseMLO->exp_metadata, my_metadata_offset, METADATA_PMAX); + // Store dLL of each image in the output array, and keep track of total sum + DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_DLL) = dLL; + thr_sum_dLL += dLL; + + // Also store sum of Pmax + thr_sum_Pmax += DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PMAX); - } // Now, inside a global_mutex, update the other weighted sums among all threads #pragma omp critical(AccMLO_global) - { - for (int img_id = 0; img_id < sp.nr_images; img_id++) - { - long int igroup = baseMLO->mydata.getGroupId(op.part_id, img_id); - int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id, img_id); + { + long int igroup = baseMLO->mydata.getGroupId(op.part_id); + int optics_group = baseMLO->mydata.getOpticsGroup(op.part_id); - if (baseMLO->mydata.obsModel.getCtfPremultiplied(optics_group)) - { - RFLOAT myscale = XMIPP_MAX(0.001, baseMLO->mymodel.scale_correction[igroup]); - FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(baseMLO->Mresol_fine[optics_group]) - { - int ires = DIRECT_MULTIDIM_ELEM(baseMLO->Mresol_fine[optics_group], n); - if (ires > -1) - DIRECT_MULTIDIM_ELEM(thr_wsum_ctf2[img_id], ires) += myscale * DIRECT_MULTIDIM_ELEM(op.local_Fctf[img_id], n); - } + if (baseMLO->mydata.obsModel.getCtfPremultiplied(optics_group)) { + RFLOAT myscale = XMIPP_MAX(0.001, baseMLO->mymodel.scale_correction[igroup]); + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(baseMLO->Mresol_fine[optics_group]) { + int ires = DIRECT_MULTIDIM_ELEM(baseMLO->Mresol_fine[optics_group], n); + if (ires > -1) + DIRECT_MULTIDIM_ELEM(thr_wsum_ctf2, ires) += + myscale * DIRECT_MULTIDIM_ELEM(op.local_Fctf[0], n); } - - if (do_subtomo_correction) - { - FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(baseMLO->Mresol_fine[optics_group]) - { - int ires = DIRECT_MULTIDIM_ELEM(baseMLO->Mresol_fine[optics_group], n); - if (ires > -1) - DIRECT_MULTIDIM_ELEM(thr_wsum_stMulti[img_id], ires) += DIRECT_MULTIDIM_ELEM(exp_local_STMulti[img_id], n); - } + } + + if (do_subtomo_correction) { + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(baseMLO->Mresol_fine[optics_group]) { + int ires = DIRECT_MULTIDIM_ELEM(baseMLO->Mresol_fine[optics_group], n); + if (ires > -1) + DIRECT_MULTIDIM_ELEM(thr_wsum_stMulti, ires) += DIRECT_MULTIDIM_ELEM( + exp_local_STMulti, n); } + } + + int my_image_size = baseMLO->mydata.getOpticsImageSize(optics_group); + RFLOAT my_pixel_size = baseMLO->mydata.getOpticsPixelSize(optics_group); + RFLOAT remap_image_sizes = + (baseMLO->mymodel.ori_size * baseMLO->mymodel.pixel_size) / (my_image_size * my_pixel_size); + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY1D(thr_wsum_sigma2_noise) { + int i_resam = ROUND(i * remap_image_sizes); + if (i_resam < XSIZE(baseMLO->wsum_model.sigma2_noise[optics_group])) { + DIRECT_A1D_ELEM(baseMLO->wsum_model.sigma2_noise[optics_group], i_resam) += DIRECT_A1D_ELEM( + thr_wsum_sigma2_noise, i); + DIRECT_A1D_ELEM(baseMLO->wsum_model.sumw_ctf2[optics_group], i_resam) += DIRECT_A1D_ELEM( + thr_wsum_ctf2, i); + + if (do_subtomo_correction) + DIRECT_A1D_ELEM(baseMLO->wsum_model.sumw_stMulti[optics_group], i_resam) += DIRECT_A1D_ELEM( + thr_wsum_stMulti, i); + } + } + baseMLO->wsum_model.sumw_group[optics_group] += thr_sumw_group; + if (baseMLO->do_scale_correction) { + baseMLO->wsum_model.wsum_signal_product[igroup] += thr_wsum_signal_product_spectra; + baseMLO->wsum_model.wsum_reference_power[igroup] += thr_wsum_reference_power_spectra; + } + for (int n = 0; n < baseMLO->mymodel.nr_classes; n++) { + baseMLO->wsum_model.pdf_class[n] += thr_wsum_pdf_class[n]; + if (baseMLO->mymodel.ref_dim == 2) { + XX(baseMLO->wsum_model.prior_offset_class[n]) += thr_wsum_prior_offsetx_class[n]; + YY(baseMLO->wsum_model.prior_offset_class[n]) += thr_wsum_prior_offsety_class[n]; + } + } - int my_image_size = baseMLO->mydata.getOpticsImageSize(optics_group); - RFLOAT my_pixel_size = baseMLO->mydata.getOpticsPixelSize(optics_group); - RFLOAT remap_image_sizes = (baseMLO->mymodel.ori_size * baseMLO->mymodel.pixel_size) / (my_image_size * my_pixel_size); - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY1D(thr_wsum_sigma2_noise[img_id]) - { - int i_resam = ROUND(i * remap_image_sizes); - if (i_resam < XSIZE(baseMLO->wsum_model.sigma2_noise[optics_group])) - { - DIRECT_A1D_ELEM(baseMLO->wsum_model.sigma2_noise[optics_group], i_resam) += DIRECT_A1D_ELEM(thr_wsum_sigma2_noise[img_id], i); - DIRECT_A1D_ELEM(baseMLO->wsum_model.sumw_ctf2[optics_group], i_resam) += DIRECT_A1D_ELEM(thr_wsum_ctf2[img_id], i); + for (int n = 0; n < baseMLO->mymodel.nr_classes * baseMLO->mymodel.nr_bodies; n++) { + if (!(baseMLO->do_skip_align || baseMLO->do_skip_rotate)) + baseMLO->wsum_model.pdf_direction[n] += thr_wsum_pdf_direction[n]; + } - if (do_subtomo_correction) - DIRECT_A1D_ELEM(baseMLO->wsum_model.sumw_stMulti[optics_group], i_resam) += DIRECT_A1D_ELEM(thr_wsum_stMulti[img_id], i); - } - } - baseMLO->wsum_model.sumw_group[optics_group] += thr_sumw_group[img_id]; - if (baseMLO->do_scale_correction) - { - baseMLO->wsum_model.wsum_signal_product[igroup] += thr_wsum_signal_product_spectra[img_id]; - baseMLO->wsum_model.wsum_reference_power[igroup] += thr_wsum_reference_power_spectra[img_id]; - } - } - for (int n = 0; n < baseMLO->mymodel.nr_classes; n++) - { - baseMLO->wsum_model.pdf_class[n] += thr_wsum_pdf_class[n]; - if (baseMLO->mymodel.ref_dim == 2) - { - XX(baseMLO->wsum_model.prior_offset_class[n]) += thr_wsum_prior_offsetx_class[n]; - YY(baseMLO->wsum_model.prior_offset_class[n]) += thr_wsum_prior_offsety_class[n]; - } - } + baseMLO->wsum_model.sigma2_offset += thr_wsum_sigma2_offset; - for (int n = 0; n < baseMLO->mymodel.nr_classes * baseMLO->mymodel.nr_bodies; n++) - { - if (!(baseMLO->do_skip_align || baseMLO->do_skip_rotate) ) - baseMLO->wsum_model.pdf_direction[n] += thr_wsum_pdf_direction[n]; - } + if (baseMLO->do_norm_correction && baseMLO->mymodel.nr_bodies == 1) + baseMLO->wsum_model.avg_norm_correction += thr_avg_norm_correction; - baseMLO->wsum_model.sigma2_offset += thr_wsum_sigma2_offset; - if (baseMLO->do_norm_correction && baseMLO->mymodel.nr_bodies == 1) - baseMLO->wsum_model.avg_norm_correction += thr_avg_norm_correction; + baseMLO->wsum_model.LL += thr_sum_dLL; + baseMLO->wsum_model.ave_Pmax += thr_sum_Pmax; - baseMLO->wsum_model.LL += thr_sum_dLL; - baseMLO->wsum_model.ave_Pmax += thr_sum_Pmax; - } + } // end pragma lock } // end if !do_skip_maximization CTOC(accMLO->timer,"store_post_gpu"); @@ -3171,7 +3670,7 @@ void accDoExpectationOneParticle(MlClass *myInstance, unsigned long part_id_sort long int part_id = baseMLO->mydata.sorted_idx[part_id_sorted]; sp.nr_images = baseMLO->mydata.numberOfImagesInParticle(part_id); - OptimisationParamters op(sp.nr_images, part_id); + OptimisationParamters op(sp.nr_images, part_id, baseMLO->mydata.is_tomo); if (baseMLO->mydata.is_3D) op.FstMulti.resize(sp.nr_images); @@ -3211,7 +3710,7 @@ void accDoExpectationOneParticle(MlClass *myInstance, unsigned long part_id_sort for (int ibody = 0; ibody < baseMLO->mymodel.nr_bodies; ibody++) { - OptimisationParamters op(sp.nr_images, part_id); + OptimisationParamters op(sp.nr_images, part_id, baseMLO->mydata.is_tomo); if (baseMLO->mydata.is_3D) op.FstMulti.resize(sp.nr_images); @@ -3219,12 +3718,11 @@ void accDoExpectationOneParticle(MlClass *myInstance, unsigned long part_id_sort if ( baseMLO->mymodel.nr_bodies > 1 && baseMLO->mymodel.keep_fixed_bodies[ibody] > 0) continue; - // Global exp_metadata array has metadata of all particles. Where does part_id start? for (long int iori = baseMLO->exp_my_first_part_id; iori <= baseMLO->exp_my_last_part_id; iori++) { if (iori == part_id_sorted) break; - op.metadata_offset += baseMLO->mydata.numberOfImagesInParticle(iori); + op.metadata_offset += 1; } #ifdef TIMING // Only time one thread @@ -3266,30 +3764,35 @@ baseMLO->timer.toc(baseMLO->TIMING_ESP_DIFF2_A); } // Initialise significant weight to minus one, so that all coarse sampling points will be handled in the first pass - op.significant_weight.resize(sp.nr_images, -1.); + op.significant_weight = -1.; // Only perform a second pass when using adaptive oversampling //int nr_sampling_passes = (baseMLO->adaptive_oversampling > 0) ? 2 : 1; // But on the gpu the data-structures are different between passes, so we need to make a symbolic pass to set the weights up for storeWS int nr_sampling_passes = 2; + // This will not work for maxCC! + if (baseMLO->adaptive_oversampling == 0 && (baseMLO->do_firstiter_cc || baseMLO->do_always_cc) ) + REPORT_ERROR("ERROR-SHWS23sep2022: GPU code will not work for maxCC without oversampling..."); /// -- This is a iframe-indexed vector, each entry of which is a dense data-array. These are replacements to using // Mweight in the sparse (Fine-sampled) pass, coarse is unused but created empty input for convert ( FIXME ) - std::vector CoarsePassWeights(1, ptrFactory); - std::vector FinePassWeights(sp.nr_images, ptrFactory); + IndexedDataArray CoarsePassWeights(ptrFactory); + IndexedDataArray FinePassWeights(ptrFactory); // -- This is a iframe-indexed vector, each entry of which is a class-indexed vector of masks, one for each // class in FinePassWeights - std::vector < std::vector > FinePassClassMasks(sp.nr_images, std::vector (baseMLO->mymodel.nr_classes, ptrFactory)); + std::vector FinePassClassMasks( std::vector (baseMLO->mymodel.nr_classes, ptrFactory)); // -- This is a iframe-indexed vector, each entry of which is parameters used in the projection-operations *after* the // coarse pass, declared here to keep scope to storeWS - std::vector < ProjectionParams > FineProjectionData(sp.nr_images, baseMLO->mymodel.nr_classes); + ProjectionParams FineProjectionData( baseMLO->mymodel.nr_classes); - std::vector < AccPtrBundle > bundleD2(sp.nr_images, ptrFactory.makeBundle()); - std::vector < AccPtrBundle > bundleSWS(sp.nr_images, ptrFactory.makeBundle()); + // See commented out print statements in convertAllSquaredDifferences.... + //if (baseMLO->adaptive_oversampling == 0 && baseMLO->do_firstiter_cc) + // REPORT_ERROR("ERROR; somehow oversampling==0 and firstiter_cc are giving non-reproducible results.... DEBUG later... (randomly changing with subsequent runs)"); - for (int ipass = 0; ipass < nr_sampling_passes; ipass++) + + for (int ipass = 0; ipass < nr_sampling_passes; ipass++) { CTIC(timer,"weightPass"); #ifdef TIMING @@ -3312,20 +3815,25 @@ if (thread_id == 0) baseMLO->timer.toc(baseMLO->TIMING_ESP_DIFF2_B); #endif - op.min_diff2.resize(sp.nr_images, 0); + op.min_diff2 = 0.; - if (ipass == 0) + if (ipass == 0) { unsigned long weightsPerPart(baseMLO->mymodel.nr_classes * sp.nr_dir * sp.nr_psi * sp.nr_trans * sp.nr_oversampled_rot * sp.nr_oversampled_trans); - op.Mweight.resizeNoCp(1,1,sp.nr_images, weightsPerPart); + op.Mweight.resizeNoCp(1,1,1, weightsPerPart); AccPtr Mweight = ptrFactory.make(); - Mweight.setSize(sp.nr_images * weightsPerPart); + Mweight.setSize(weightsPerPart); Mweight.setHostPtr(op.Mweight.data); +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + Mweight.setStreamAccType(myInstance->getSyclDevice()); +#endif Mweight.deviceAlloc(); - deviceInitValue(Mweight, -std::numeric_limits::max()); + deviceInitValue(Mweight, std::numeric_limits::lowest()); + // SHWS 7July2022: not entirely sure about how this works, but as I'm adding to the diff2 for loop over all img_id, this can no longer be a large negative value... + //deviceInitValue(Mweight, 0.); Mweight.streamSync(); CTIC(timer,"getAllSquaredDifferencesCoarse"); @@ -3334,7 +3842,7 @@ baseMLO->timer.toc(baseMLO->TIMING_ESP_DIFF2_B); CTIC(timer,"convertAllSquaredDifferencesToWeightsCoarse"); convertAllSquaredDifferencesToWeights(ipass, op, sp, baseMLO, myInstance, CoarsePassWeights, FinePassClassMasks, Mweight, ptrFactory, ibody); - CTOC(timer,"convertAllSquaredDifferencesToWeightsCoarse"); + CTOC(timer,"convertAllSquaredDifferencesToWeightsCoarse"); } else { @@ -3343,52 +3851,58 @@ baseMLO->timer.toc(baseMLO->TIMING_ESP_DIFF2_B); if (thread_id == 0) baseMLO->timer.tic(baseMLO->TIMING_ESP_DIFF2_D); #endif -// // -- go through all classes and generate projectionsetups for all classes - to be used in getASDF and storeWS below -- -// // the reason to do this globally is subtle - we want the orientation_num of all classes to estimate a largest possible -// // weight-array, which would be insanely much larger than necessary if we had to assume the worst. - for (int img_id = 0; img_id < sp.nr_images; img_id++) - { - FineProjectionData[img_id].orientationNumAllClasses = 0; - for (int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) - { - if(exp_iclass>0) - FineProjectionData[img_id].class_idx[exp_iclass] = FineProjectionData[img_id].rots.size(); - FineProjectionData[img_id].class_entries[exp_iclass] = 0; - - CTIC(timer,"generateProjectionSetup"); - FineProjectionData[img_id].orientationNumAllClasses += generateProjectionSetupFine( - op, - sp, - baseMLO, - exp_iclass, - FineProjectionData[img_id]); - CTOC(timer,"generateProjectionSetup"); + // -- go through all classes and generate projectionsetups for all classes - to be used in getASDF and storeWS below -- + // the reason to do this globally is subtle - we want the orientation_num of all classes to estimate a largest possible + // weight-array, which would be insanely much larger than necessary if we had to assume the worst. + FineProjectionData.orientationNumAllClasses = 0; + for (int exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + { + if (exp_iclass > 0) + FineProjectionData.class_idx[exp_iclass] = FineProjectionData.rots.size(); + FineProjectionData.class_entries[exp_iclass] = 0; + + CTIC(timer, "generateProjectionSetup"); + FineProjectionData.orientationNumAllClasses += generateProjectionSetupFine( + op, + sp, + baseMLO, + exp_iclass, + FineProjectionData); + CTOC(timer, "generateProjectionSetup"); - } - //set a maximum possible size for all weights (to be reduced by significance-checks) - size_t dataSize = FineProjectionData[img_id].orientationNumAllClasses*sp.nr_trans*sp.nr_oversampled_trans; - FinePassWeights[img_id].setDataSize(dataSize); - FinePassWeights[img_id].dual_alloc_all(); + } + + //set a maximum possible size for all weights (to be reduced by significance-checks) + // SHWS 6Jul2022: assume dataSize of FineProjectionData is the same for all img_id + size_t dataSize = FineProjectionData.orientationNumAllClasses*sp.nr_trans*sp.nr_oversampled_trans; +#ifdef _SYCL_ENABLED + FinePassWeights.setStreamAccType_all(myInstance->getSyclDevice(), accSYCL); + FinePassWeights.ihidden_overs.setAccType(accCPU); + assert(dataSize > 0); +#endif + FinePassWeights.setDataSize(dataSize); + FinePassWeights.dual_alloc_all(); - bundleD2[img_id].setSize(2*(FineProjectionData[img_id].orientationNumAllClasses*sp.nr_trans*sp.nr_oversampled_trans)*sizeof(unsigned long)); - bundleD2[img_id].allAlloc(); - } #ifdef TIMING // Only time one thread if (thread_id == 0) baseMLO->timer.toc(baseMLO->TIMING_ESP_DIFF2_D); #endif - CTIC(timer,"getAllSquaredDifferencesFine"); - getAllSquaredDifferencesFine(ipass, op, sp, baseMLO, myInstance, FinePassWeights, FinePassClassMasks, FineProjectionData, ptrFactory, ibody, bundleD2); + //AccPtrBundle bundleD2(ptrFactory.makeBundle()); + //bundleD2.setSize(2*(FineProjectionData.orientationNumAllClasses*sp.nr_trans*sp.nr_oversampled_trans)*sizeof(unsigned long)); + //bundleD2.allAlloc(); + + CTIC(timer,"getAllSquaredDifferencesFine"); + getAllSquaredDifferencesFine(ipass, op, sp, baseMLO, myInstance, FinePassWeights, FinePassClassMasks, FineProjectionData, ptrFactory, ibody); CTOC(timer,"getAllSquaredDifferencesFine"); - FinePassWeights[0].weights.cpToHost(); + FinePassWeights.weights.cpToHost(); AccPtr Mweight = ptrFactory.make(); //DUMMY CTIC(timer,"convertAllSquaredDifferencesToWeightsFine"); - convertAllSquaredDifferencesToWeights(ipass, op, sp, baseMLO, myInstance, FinePassWeights, FinePassClassMasks, Mweight, ptrFactory, ibody); - CTOC(timer,"convertAllSquaredDifferencesToWeightsFine"); + convertAllSquaredDifferencesToWeights(ipass, op, sp, baseMLO, myInstance, FinePassWeights, FinePassClassMasks, Mweight, ptrFactory, ibody); + CTOC(timer,"convertAllSquaredDifferencesToWeightsFine"); } @@ -3403,12 +3917,19 @@ baseMLO->timer.tic(baseMLO->TIMING_ESP_DIFF2_E); // For the reconstruction step use mymodel.current_size! // as of 3.1, no longer necessary? sp.current_image_size = baseMLO->mymodel.current_size; + AccPtrBundle bundleSWS(ptrFactory.makeBundle()); - for (unsigned long img_id = 0; img_id < sp.nr_images; img_id++) - { - bundleSWS[img_id].setSize(2*(FineProjectionData[img_id].orientationNumAllClasses)*sizeof(unsigned long)); - bundleSWS[img_id].allAlloc(); - } + bundleSWS.setSize(2*(FineProjectionData.orientationNumAllClasses)*sizeof(unsigned long)); + bundleSWS.allAlloc(); + +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + FinePassWeights.setAccType_all(accCPU); + for (unsigned long exp_iclass = sp.iclass_min; exp_iclass <= sp.iclass_max; exp_iclass++) + { + FinePassClassMasks[exp_iclass].jobOrigin.setAccType(accCPU); + FinePassClassMasks[exp_iclass].jobExtent.setAccType(accCPU); + } +#endif #ifdef TIMING // Only time one thread @@ -3419,10 +3940,7 @@ baseMLO->timer.toc(baseMLO->TIMING_ESP_DIFF2_E); storeWeightedSums(op, sp, baseMLO, myInstance, FinePassWeights, FineProjectionData, FinePassClassMasks, ptrFactory, ibody, bundleSWS); CTOC(timer,"storeWeightedSums"); - for (long int img_id = 0; img_id < sp.nr_images; img_id++) - { - FinePassWeights[img_id].dual_free_all(); - } + FinePassWeights.dual_free_all(); } CTOC(timer,"oneParticle"); diff --git a/src/acc/acc_projector.h b/src/acc/acc_projector.h index f6d415164..5ed822179 100644 --- a/src/acc/acc_projector.h +++ b/src/acc/acc_projector.h @@ -7,8 +7,11 @@ #include "src/acc/acc_ptr.h" //#include //#include "src/acc/cuda/cuda_kernels/cuda_device_utils.cuh" -#ifndef _CUDA_ENABLED +#ifdef ALTCPU #include +#elif _SYCL_ENABLED +#include "src/acc/sycl/sycl_virtual_dev.h" +using deviceStream_t = virtualSYCL*; #endif class AccProjector @@ -22,15 +25,23 @@ class AccProjector size_t allocaton_size; +#ifdef _SYCL_ENABLED + deviceStream_t devAcc; +#endif + #ifndef PROJECTOR_NO_TEXTURES XFLOAT *texArrayReal2D, *texArrayImag2D; - cudaArray_t *texArrayReal, *texArrayImag; - cudaTextureObject_t *mdlReal, *mdlImag; - + #ifdef _CUDA_ENABLED + cudaArray_t *texArrayReal, *texArrayImag; + cudaTextureObject_t *mdlReal, *mdlImag; + #elif _HIP_ENABLED + hipArray_t *texArrayReal, *texArrayImag; + hipTextureObject_t *mdlReal, *mdlImag; + #endif size_t pitch2D; #else -#ifdef _CUDA_ENABLED +#ifndef ALTCPU XFLOAT *mdlReal, *mdlImag; #else std::complex *mdlComplex; @@ -46,6 +57,9 @@ class AccProjector padding_factor(0), allocaton_size(0) { +#ifdef _SYCL_ENABLED + devAcc = nullptr; +#endif #ifndef PROJECTOR_NO_TEXTURES texArrayReal2D = 0; @@ -56,7 +70,7 @@ class AccProjector mdlImag = 0; pitch2D = 0; #else -#ifdef _CUDA_ENABLED +#ifndef ALTCPU mdlReal = 0; mdlImag = 0; #else @@ -67,14 +81,17 @@ class AccProjector } bool setMdlDim( +#ifdef _SYCL_ENABLED + deviceStream_t dev, +#endif int xdim, int ydim, int zdim, int inity, int initz, int maxr, XFLOAT paddingFactor); void initMdl(XFLOAT *real, XFLOAT *imag); void initMdl(Complex *data); -#ifndef _CUDA_ENABLED -void initMdl(std::complex *data); +#ifdef ALTCPU + void initMdl(std::complex *data); #endif void clear(); diff --git a/src/acc/acc_projector_impl.h b/src/acc/acc_projector_impl.h index 7f7f0f2f5..c5b0431d9 100644 --- a/src/acc/acc_projector_impl.h +++ b/src/acc/acc_projector_impl.h @@ -3,6 +3,9 @@ bool AccProjector::setMdlDim( +#ifdef _SYCL_ENABLED + deviceStream_t dev, +#endif int xdim, int ydim, int zdim, int inity, int initz, int maxr, XFLOAT paddingFactor) @@ -33,78 +36,156 @@ bool AccProjector::setMdlDim( padding_factor = paddingFactor; #ifndef PROJECTOR_NO_TEXTURES + #ifdef _CUDA_ENABLED + mdlReal = new cudaTextureObject_t(); + mdlImag = new cudaTextureObject_t(); - mdlReal = new cudaTextureObject_t(); - mdlImag = new cudaTextureObject_t(); + // create channel to describe data type (bits,bits,bits,bits,type) + cudaChannelFormatDesc desc; - // create channel to describe data type (bits,bits,bits,bits,type) - cudaChannelFormatDesc desc; + desc = cudaCreateChannelDesc(32, 0, 0, 0, cudaChannelFormatKindFloat); - desc = cudaCreateChannelDesc(32, 0, 0, 0, cudaChannelFormatKindFloat); + struct cudaResourceDesc resDesc_real, resDesc_imag; + struct cudaTextureDesc texDesc; + // -- Zero all data in objects handlers + memset(&resDesc_real, 0, sizeof(cudaResourceDesc)); + memset(&resDesc_imag, 0, sizeof(cudaResourceDesc)); + memset(&texDesc, 0, sizeof(cudaTextureDesc)); - struct cudaResourceDesc resDesc_real, resDesc_imag; - struct cudaTextureDesc texDesc; - // -- Zero all data in objects handlers - memset(&resDesc_real, 0, sizeof(cudaResourceDesc)); - memset(&resDesc_imag, 0, sizeof(cudaResourceDesc)); - memset(&texDesc, 0, sizeof(cudaTextureDesc)); + if(mdlZ!=0) // 3D model + { + texArrayReal = new cudaArray_t(); + texArrayImag = new cudaArray_t(); + + // -- make extents for automatic pitch:ing (aligment) of allocated 3D arrays + cudaExtent volumeSize = make_cudaExtent(mdlX, mdlY, mdlZ); - if(mdlZ!=0) // 3D model - { - texArrayReal = new cudaArray_t(); - texArrayImag = new cudaArray_t(); - // -- make extents for automatic pitch:ing (aligment) of allocated 3D arrays - cudaExtent volumeSize = make_cudaExtent(mdlX, mdlY, mdlZ); + // -- Allocate and copy data using very clever CUDA memcpy-functions + HANDLE_ERROR(cudaMalloc3DArray(texArrayReal, &desc, volumeSize)); + HANDLE_ERROR(cudaMalloc3DArray(texArrayImag, &desc, volumeSize)); + // -- Descriptors of the channel(s) in the texture(s) + resDesc_real.res.array.array = *texArrayReal; + resDesc_imag.res.array.array = *texArrayImag; + resDesc_real.resType = cudaResourceTypeArray; + resDesc_imag.resType = cudaResourceTypeArray; + } + else // 2D model + { + HANDLE_ERROR(cudaMallocPitch(&texArrayReal2D, &pitch2D, sizeof(XFLOAT)*mdlX,mdlY)); + HANDLE_ERROR(cudaMallocPitch(&texArrayImag2D, &pitch2D, sizeof(XFLOAT)*mdlX,mdlY)); + + // -- Descriptors of the channel(s) in the texture(s) + resDesc_real.resType = cudaResourceTypePitch2D; + resDesc_real.res.pitch2D.devPtr = texArrayReal2D; + resDesc_real.res.pitch2D.pitchInBytes = pitch2D; + resDesc_real.res.pitch2D.width = mdlX; + resDesc_real.res.pitch2D.height = mdlY; + resDesc_real.res.pitch2D.desc = desc; + // ------------------------------------------------- + resDesc_imag.resType = cudaResourceTypePitch2D; + resDesc_imag.res.pitch2D.devPtr = texArrayImag2D; + resDesc_imag.res.pitch2D.pitchInBytes = pitch2D; + resDesc_imag.res.pitch2D.width = mdlX; + resDesc_imag.res.pitch2D.height = mdlY; + resDesc_imag.res.pitch2D.desc = desc; + } - // -- Allocate and copy data using very clever CUDA memcpy-functions - HANDLE_ERROR(cudaMalloc3DArray(texArrayReal, &desc, volumeSize)); - HANDLE_ERROR(cudaMalloc3DArray(texArrayImag, &desc, volumeSize)); + // -- Decriptors of the texture(s) and methods used for reading it(them) -- + texDesc.filterMode = cudaFilterModeLinear; + texDesc.readMode = cudaReadModeElementType; + texDesc.normalizedCoords = false; - // -- Descriptors of the channel(s) in the texture(s) - resDesc_real.res.array.array = *texArrayReal; - resDesc_imag.res.array.array = *texArrayImag; - resDesc_real.resType = cudaResourceTypeArray; - resDesc_imag.resType = cudaResourceTypeArray; - } - else // 2D model - { - HANDLE_ERROR(cudaMallocPitch(&texArrayReal2D, &pitch2D, sizeof(XFLOAT)*mdlX,mdlY)); - HANDLE_ERROR(cudaMallocPitch(&texArrayImag2D, &pitch2D, sizeof(XFLOAT)*mdlX,mdlY)); - - // -- Descriptors of the channel(s) in the texture(s) - resDesc_real.resType = cudaResourceTypePitch2D; - resDesc_real.res.pitch2D.devPtr = texArrayReal2D; - resDesc_real.res.pitch2D.pitchInBytes = pitch2D; - resDesc_real.res.pitch2D.width = mdlX; - resDesc_real.res.pitch2D.height = mdlY; - resDesc_real.res.pitch2D.desc = desc; - // ------------------------------------------------- - resDesc_imag.resType = cudaResourceTypePitch2D; - resDesc_imag.res.pitch2D.devPtr = texArrayImag2D; - resDesc_imag.res.pitch2D.pitchInBytes = pitch2D; - resDesc_imag.res.pitch2D.width = mdlX; - resDesc_imag.res.pitch2D.height = mdlY; - resDesc_imag.res.pitch2D.desc = desc; - } + for(int n=0; n<3; n++) + texDesc.addressMode[n]=cudaAddressModeClamp; - // -- Decriptors of the texture(s) and methods used for reading it(them) -- - texDesc.filterMode = cudaFilterModeLinear; - texDesc.readMode = cudaReadModeElementType; - texDesc.normalizedCoords = false; + // -- Create texture object(s) + HANDLE_ERROR(cudaCreateTextureObject(mdlReal, &resDesc_real, &texDesc, NULL)); + HANDLE_ERROR(cudaCreateTextureObject(mdlImag, &resDesc_imag, &texDesc, NULL)); + #elif _HIP_ENABLED + mdlReal = new hipTextureObject_t(); + mdlImag = new hipTextureObject_t(); - for(int n=0; n<3; n++) - texDesc.addressMode[n]=cudaAddressModeClamp; + // create channel to describe data type (bits,bits,bits,bits,type) + hipChannelFormatDesc desc; + + desc = hipCreateChannelDesc(32, 0, 0, 0, hipChannelFormatKindFloat); + + struct hipResourceDesc resDesc_real, resDesc_imag; + struct hipTextureDesc texDesc; + // -- Zero all data in objects handlers + memset(&resDesc_real, 0, sizeof(hipResourceDesc)); + memset(&resDesc_imag, 0, sizeof(hipResourceDesc)); + memset(&texDesc, 0, sizeof(hipTextureDesc)); + + if(mdlZ!=0) // 3D model + { + texArrayReal = new hipArray_t(); + texArrayImag = new hipArray_t(); + + // -- make extents for automatic pitch:ing (aligment) of allocated 3D arrays + hipExtent volumeSize = make_hipExtent(mdlX, mdlY, mdlZ); + + + // -- Allocate and copy data using very clever HIP memcpy-functions + HANDLE_ERROR(hipMalloc3DArray(texArrayReal, &desc, volumeSize, hipArrayDefault)); + HANDLE_ERROR(hipMalloc3DArray(texArrayImag, &desc, volumeSize, hipArrayDefault)); + + // -- Descriptors of the channel(s) in the texture(s) + resDesc_real.res.array.array = *texArrayReal; + resDesc_imag.res.array.array = *texArrayImag; + resDesc_real.resType = hipResourceTypeArray; + resDesc_imag.resType = hipResourceTypeArray; + } + else // 2D model + { + HANDLE_ERROR(hipMallocPitch(reinterpret_cast(&texArrayReal2D), &pitch2D, sizeof(XFLOAT)*mdlX,mdlY)); + HANDLE_ERROR(hipMallocPitch(reinterpret_cast(&texArrayImag2D), &pitch2D, sizeof(XFLOAT)*mdlX,mdlY)); + + // -- Descriptors of the channel(s) in the texture(s) + resDesc_real.resType = hipResourceTypePitch2D; + resDesc_real.res.pitch2D.devPtr = texArrayReal2D; + resDesc_real.res.pitch2D.pitchInBytes = pitch2D; + resDesc_real.res.pitch2D.width = mdlX; + resDesc_real.res.pitch2D.height = mdlY; + resDesc_real.res.pitch2D.desc = desc; + // ------------------------------------------------- + resDesc_imag.resType = hipResourceTypePitch2D; + resDesc_imag.res.pitch2D.devPtr = texArrayImag2D; + resDesc_imag.res.pitch2D.pitchInBytes = pitch2D; + resDesc_imag.res.pitch2D.width = mdlX; + resDesc_imag.res.pitch2D.height = mdlY; + resDesc_imag.res.pitch2D.desc = desc; + } - // -- Create texture object(s) - HANDLE_ERROR(cudaCreateTextureObject(mdlReal, &resDesc_real, &texDesc, NULL)); - HANDLE_ERROR(cudaCreateTextureObject(mdlImag, &resDesc_imag, &texDesc, NULL)); + // -- Decriptors of the texture(s) and methods used for reading it(them) -- + if(mdlZ!=0) // 3D model + texDesc.filterMode = hipFilterModePoint; + else // 2D model + texDesc.filterMode = hipFilterModeLinear; + texDesc.readMode = hipReadModeElementType; + texDesc.normalizedCoords = false; + for(int n=0; n<3; n++) + texDesc.addressMode[n]=hipAddressModeClamp; + + // -- Create texture object(s) + HANDLE_ERROR(hipCreateTextureObject(mdlReal, &resDesc_real, &texDesc, NULL)); + HANDLE_ERROR(hipCreateTextureObject(mdlImag, &resDesc_imag, &texDesc, NULL)); + + #endif #else #ifdef _CUDA_ENABLED DEBUG_HANDLE_ERROR(cudaMalloc( (void**) &mdlReal, mdlXYZ * sizeof(XFLOAT))); DEBUG_HANDLE_ERROR(cudaMalloc( (void**) &mdlImag, mdlXYZ * sizeof(XFLOAT))); +#elif _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipMalloc( (void**) &mdlReal, mdlXYZ * sizeof(XFLOAT))); + DEBUG_HANDLE_ERROR(hipMalloc( (void**) &mdlImag, mdlXYZ * sizeof(XFLOAT))); +#elif _SYCL_ENABLED + devAcc = dev; + mdlReal = (XFLOAT*)devAcc->syclMalloc(mdlXYZ * sizeof(XFLOAT), syclMallocType::device, "mdlReal"); + mdlImag = (XFLOAT*)devAcc->syclMalloc(mdlXYZ * sizeof(XFLOAT), syclMallocType::device, "mdlImag"); #else mdlComplex = NULL; #endif @@ -114,13 +195,13 @@ bool AccProjector::setMdlDim( void AccProjector::initMdl(XFLOAT *real, XFLOAT *imag) { -#ifdef DEBUG_CUDA +#if defined DEBUG_CUDA || defined DEBUG_HIP if (mdlXYZ == 0) { printf("DEBUG_ERROR: Model dimensions must be set with setMdlDim before call to setMdlData."); CRITICAL(ERR_MDLDIM); } -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED || defined _SYCL_ENABLED if (mdlReal == NULL) { printf("DEBUG_ERROR: initMdl called before call to setMdlData."); @@ -136,6 +217,7 @@ void AccProjector::initMdl(XFLOAT *real, XFLOAT *imag) #endif #ifndef PROJECTOR_NO_TEXTURES + #ifdef _CUDA_ENABLED if(mdlZ!=0) // 3D model { // -- make extents for automatic pitching (aligment) of allocated 3D arrays @@ -156,10 +238,39 @@ void AccProjector::initMdl(XFLOAT *real, XFLOAT *imag) DEBUG_HANDLE_ERROR(cudaMemcpy2D(texArrayReal2D, pitch2D, real, sizeof(XFLOAT) * mdlX, sizeof(XFLOAT) * mdlX, mdlY, cudaMemcpyHostToDevice)); DEBUG_HANDLE_ERROR(cudaMemcpy2D(texArrayImag2D, pitch2D, imag, sizeof(XFLOAT) * mdlX, sizeof(XFLOAT) * mdlX, mdlY, cudaMemcpyHostToDevice)); } + #elif _HIP_ENABLED + if(mdlZ!=0) // 3D model + { + // -- make extents for automatic pitching (aligment) of allocated 3D arrays + hipMemcpy3DParms copyParams = {0}; + copyParams.extent = make_hipExtent(mdlX, mdlY, mdlZ); + copyParams.kind = hipMemcpyHostToDevice; + + // -- Copy data + copyParams.dstArray = *texArrayReal; + copyParams.srcPtr = make_hipPitchedPtr(real, mdlX * sizeof(XFLOAT), mdlY, mdlZ); + DEBUG_HANDLE_ERROR(hipMemcpy3D(©Params)); + copyParams.dstArray = *texArrayImag; + copyParams.srcPtr = make_hipPitchedPtr(imag, mdlX * sizeof(XFLOAT), mdlY, mdlZ); + DEBUG_HANDLE_ERROR(hipMemcpy3D(©Params)); + } + else // 2D model + { + DEBUG_HANDLE_ERROR(hipMemcpy2D(texArrayReal2D, pitch2D, real, sizeof(XFLOAT) * mdlX, sizeof(XFLOAT) * mdlX, mdlY, hipMemcpyHostToDevice)); + DEBUG_HANDLE_ERROR(hipMemcpy2D(texArrayImag2D, pitch2D, imag, sizeof(XFLOAT) * mdlX, sizeof(XFLOAT) * mdlX, mdlY, hipMemcpyHostToDevice)); + } + #endif #else #ifdef _CUDA_ENABLED DEBUG_HANDLE_ERROR(cudaMemcpy( mdlReal, real, mdlXYZ * sizeof(XFLOAT), cudaMemcpyHostToDevice)); DEBUG_HANDLE_ERROR(cudaMemcpy( mdlImag, imag, mdlXYZ * sizeof(XFLOAT), cudaMemcpyHostToDevice)); +#elif _HIP_ENABLED + DEBUG_HANDLE_ERROR(hipMemcpy( mdlReal, real, mdlXYZ * sizeof(XFLOAT), hipMemcpyHostToDevice)); + DEBUG_HANDLE_ERROR(hipMemcpy( mdlImag, imag, mdlXYZ * sizeof(XFLOAT), hipMemcpyHostToDevice)); +#elif _SYCL_ENABLED + devAcc->syclMemcpy(mdlReal, real, mdlXYZ * sizeof(XFLOAT)); + devAcc->syclMemcpy(mdlImag, imag, mdlXYZ * sizeof(XFLOAT)); + devAcc->waitAll(); #else std::complex *pData = mdlComplex; for(size_t i=0; i *data) { mdlComplex = data; // No copy needed - everyone shares the complex reference arrays @@ -181,11 +292,21 @@ void AccProjector::initMdl(std::complex *data) void AccProjector::initMdl(Complex *data) { +#ifdef _SYCL_ENABLED + XFLOAT *tmpReal = (XFLOAT*)devAcc->syclMalloc(mdlXYZ * sizeof(XFLOAT), syclMallocType::host); + XFLOAT *tmpImag = (XFLOAT*)devAcc->syclMalloc(mdlXYZ * sizeof(XFLOAT), syclMallocType::host); + if (nullptr == tmpReal || nullptr == tmpImag) + { + std::string str = "syclMalloc HOST error of size " + std::to_string(mdlXYZ * sizeof(XFLOAT)) + ".\n"; + ACC_PTR_DEBUG_FATAL(str.c_str()); + CRITICAL(RAMERR); + } +#else XFLOAT *tmpReal; XFLOAT *tmpImag; if (posix_memalign((void **)&tmpReal, MEM_ALIGN, mdlXYZ * sizeof(XFLOAT))) CRITICAL(RAMERR); if (posix_memalign((void **)&tmpImag, MEM_ALIGN, mdlXYZ * sizeof(XFLOAT))) CRITICAL(RAMERR); - +#endif for (size_t i = 0; i < mdlXYZ; i ++) { @@ -195,39 +316,68 @@ void AccProjector::initMdl(Complex *data) initMdl(tmpReal, tmpImag); +#ifdef _SYCL_ENABLED + devAcc->syclFree(tmpReal); + devAcc->syclFree(tmpImag); +#else free(tmpReal); free(tmpImag); +#endif } void AccProjector::clear() { -#ifdef _CUDA_ENABLED +#ifndef ALTCPU if (mdlReal != 0) { #ifndef PROJECTOR_NO_TEXTURES - cudaDestroyTextureObject(*mdlReal); - cudaDestroyTextureObject(*mdlImag); + #ifdef _CUDA_ENABLED + cudaDestroyTextureObject(*mdlReal); + cudaDestroyTextureObject(*mdlImag); + #elif _HIP_ENABLED + hipDestroyTextureObject(*mdlReal); + hipDestroyTextureObject(*mdlImag); + #endif delete mdlReal; delete mdlImag; if(mdlZ!=0) //3D case { + #ifdef _CUDA_ENABLED cudaFreeArray(*texArrayReal); cudaFreeArray(*texArrayImag); + #elif _HIP_ENABLED + hipFreeArray(*texArrayReal); + hipFreeArray(*texArrayImag); + #endif delete texArrayReal; delete texArrayImag; } else //2D case { + #ifdef _CUDA_ENABLED HANDLE_ERROR(cudaFree(texArrayReal2D)); HANDLE_ERROR(cudaFree(texArrayImag2D)); + #elif _HIP_ENABLED + HANDLE_ERROR(hipFree(texArrayReal2D)); + HANDLE_ERROR(hipFree(texArrayImag2D)); + #endif } texArrayReal = 0; texArrayImag = 0; #else + #ifdef _CUDA_ENABLED cudaFree(mdlReal); cudaFree(mdlImag); + #elif _HIP_ENABLED + hipFree(mdlReal); + hipFree(mdlImag); + #elif _SYCL_ENABLED + devAcc->waitAll(); + devAcc->syclFree(mdlReal); + devAcc->syclFree(mdlImag); + #endif #endif mdlReal = 0; mdlImag = 0; @@ -243,11 +393,11 @@ void AccProjector::clear() padding_factor = 0; allocaton_size = 0; -#else // ifdef CUDA +#else // ifdef CUDA or HIP if ((mdlComplex != NULL) && (externalFree == 0)) { delete [] mdlComplex; mdlComplex = NULL; } -#endif // ifdef CUDA +#endif // ifdef CUDA or HIP } diff --git a/src/acc/acc_projector_plan.h b/src/acc/acc_projector_plan.h index 2dbce2b4e..27e95c731 100644 --- a/src/acc/acc_projector_plan.h +++ b/src/acc/acc_projector_plan.h @@ -6,6 +6,10 @@ #include "src/healpix_sampling.h" #include #include +#ifdef _SYCL_ENABLED +#include "src/acc/sycl/sycl_virtual_dev.h" +using deviceStream_t = virtualSYCL*; +#endif class AccProjectorPlan { @@ -13,17 +17,33 @@ class AccProjectorPlan AccPtr< long unsigned> iorientclasses; AccPtr eulers; long unsigned orientation_num; +#ifdef _SYCL_ENABLED + deviceStream_t devAcc; +#endif AccProjectorPlan(): orientation_num(0) {}; - AccProjectorPlan(CudaCustomAllocator *allocator): + #ifdef _HIP_ENABLED + AccProjectorPlan(HipCustomAllocator *allocator): + #else + AccProjectorPlan(CudaCustomAllocator *allocator): + #endif iorientclasses(allocator), eulers(allocator), orientation_num(0) {}; + #ifdef _SYCL_ENABLED + AccProjectorPlan(deviceStream_t dev): + iorientclasses((CudaCustomAllocator*)0), + eulers((CudaCustomAllocator*)0), + orientation_num(0), + devAcc(dev) + {}; + #endif + //Copy constructor AccProjectorPlan( const AccProjectorPlan& other ): iorientclasses(other.iorientclasses), @@ -97,6 +117,10 @@ class AccProjectorPlan dummyRL, dummyRL); } +#ifdef _SYCL_ENABLED + void setSyclDevice(deviceStream_t dev); +#endif + void printTo(std::ostream &os); // print void clear(); diff --git a/src/acc/acc_projector_plan_impl.h b/src/acc/acc_projector_plan_impl.h index b1353c482..da117cf4b 100644 --- a/src/acc/acc_projector_plan_impl.h +++ b/src/acc/acc_projector_plan_impl.h @@ -1,6 +1,10 @@ #include "src/acc/acc_projector_plan.h" #include "src/acc/utilities.h" #include "src/time.h" +#ifdef _SYCL_ENABLED +#include "src/acc/sycl/sycl_virtual_dev.h" +using deviceStream_t = virtualSYCL*; +#endif //#define PP_TIMING #ifdef PP_TIMING @@ -85,6 +89,13 @@ void getOrientations(HealpixSampling &sampling, long int idir, long int ipsi, in } } +#ifdef _SYCL_ENABLED +void AccProjectorPlan::setSyclDevice(deviceStream_t dev) +{ + devAcc = dev; +} +#endif + void AccProjectorPlan::setup( HealpixSampling &sampling, std::vector &directions_prior, @@ -120,7 +131,6 @@ void AccProjectorPlan::setup( AccPtr alphas = eulers.make(nr_dir * nr_psi * nr_oversampled_rot * 9); AccPtr betas = eulers.make(nr_dir * nr_psi * nr_oversampled_rot * 9); AccPtr gammas = eulers.make(nr_dir * nr_psi * nr_oversampled_rot * 9); - AccPtr perturb = eulers.make((size_t)9); AccPtr adjustL = eulers.make((size_t)9); AccPtr adjustR = eulers.make((size_t)9); @@ -255,6 +265,9 @@ void AccProjectorPlan::setup( TIMING_TOC(TIMING_SAMPLING); iorientclasses.resizeHostCopy(orientation_num); +#if defined(_SYCL_ENABLED) && defined(USE_ONEDPL) + iorientclasses.setStreamAccType(devAcc); +#endif iorientclasses.putOnDevice(); eulers.resizeHostCopy(orientation_num * 9); @@ -392,6 +405,11 @@ void AccProjectorPlan::setup( ~eulers, orientation_num); } +#ifdef _SYCL_ENABLED + eulers.setStreamAccType(devAcc); + eulers.putOnDevice(); + eulers.streamSync(); +#endif TIMING_TOC(TIMING_TOP); } diff --git a/src/acc/acc_projectorkernel_impl.h b/src/acc/acc_projectorkernel_impl.h index c91a39a05..defe33d01 100644 --- a/src/acc/acc_projectorkernel_impl.h +++ b/src/acc/acc_projectorkernel_impl.h @@ -3,9 +3,13 @@ #ifndef PROJECTOR_NO_TEXTURES -#define PROJECTOR_PTR_TYPE cudaTextureObject_t + #ifdef _CUDA_ENABLED + #define PROJECTOR_PTR_TYPE cudaTextureObject_t + #elif _HIP_ENABLED + #define PROJECTOR_PTR_TYPE hipTextureObject_t + #endif #else -#define PROJECTOR_PTR_TYPE XFLOAT * + #define PROJECTOR_PTR_TYPE XFLOAT * #endif class AccProjectorKernel @@ -20,7 +24,7 @@ class AccProjectorKernel PROJECTOR_PTR_TYPE mdlReal; PROJECTOR_PTR_TYPE mdlImag; -#ifdef _CUDA_ENABLED +#ifndef ALTCPU PROJECTOR_PTR_TYPE mdlComplex; #else std::complex *mdlComplex; @@ -32,7 +36,7 @@ class AccProjectorKernel int mdlInitY, int mdlInitZ, XFLOAT padding_factor, int maxR, -#ifdef _CUDA_ENABLED +#ifndef ALTCPU PROJECTOR_PTR_TYPE mdlComplex #else std::complex *mdlComplex @@ -61,8 +65,8 @@ class AccProjectorKernel maxR(maxR), maxR2(maxR*maxR), maxR2_padded(maxR*maxR*padding_factor*padding_factor), mdlReal(mdlReal), mdlImag(mdlImag) { -#ifndef _CUDA_ENABLED -std::complex *pData = mdlComplex; +#ifdef ALTCPU + std::complex *pData = mdlComplex; for(size_t i=0; i<(size_t)mdlX * (size_t)mdlY * (size_t)mdlZ; i++) { std::complex arrayval(*mdlReal ++, *mdlImag ++); pData[i] = arrayval; @@ -70,7 +74,7 @@ std::complex *pData = mdlComplex; #endif }; -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED __device__ __forceinline__ #else #ifndef __INTEL_COMPILER @@ -112,9 +116,12 @@ std::complex *pData = mdlComplex; zp = -zp; } -#ifdef _CUDA_ENABLED -real = no_tex3D(mdlReal, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); +#if defined _CUDA_ENABLED || defined _HIP_ENABLED + real = no_tex3D(mdlReal, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); imag = - no_tex3D(mdlImag, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); +#elif _SYCL_ENABLED + real = syclKernels::no_tex3D(mdlReal, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); + imag = - syclKernels::no_tex3D(mdlImag, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); #else CpuKernels::complex3D(mdlComplex, real, imag, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); #endif @@ -154,7 +161,7 @@ real = no_tex3D(mdlReal, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); } } -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED __device__ __forceinline__ #else #ifndef __INTEL_COMPILER @@ -192,9 +199,12 @@ real = no_tex3D(mdlReal, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); zp = -zp; } - #ifdef _CUDA_ENABLED -real = no_tex3D(mdlReal, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); + #if defined _CUDA_ENABLED || defined _HIP_ENABLED + real = no_tex3D(mdlReal, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); imag = no_tex3D(mdlImag, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); + #elif _SYCL_ENABLED + real = syclKernels::no_tex3D(mdlReal, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); + imag = syclKernels::no_tex3D(mdlImag, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); #else CpuKernels::complex3D(mdlComplex, real, imag, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); #endif @@ -232,7 +242,7 @@ real = no_tex3D(mdlReal, xp, yp, zp, mdlX, mdlXY, mdlInitY, mdlInitZ); } } -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED __device__ __forceinline__ #else #ifndef __INTEL_COMPILER @@ -265,9 +275,12 @@ __device__ __forceinline__ yp = -yp; } - #ifdef _CUDA_ENABLED -real = no_tex2D(mdlReal, xp, yp, mdlX, mdlInitY); + #if defined _CUDA_ENABLED || defined _HIP_ENABLED + real = no_tex2D(mdlReal, xp, yp, mdlX, mdlInitY); imag = no_tex2D(mdlImag, xp, yp, mdlX, mdlInitY); + #elif _SYCL_ENABLED + real = syclKernels::no_tex2D(mdlReal, xp, yp, mdlX, mdlInitY); + imag = syclKernels::no_tex2D(mdlImag, xp, yp, mdlX, mdlInitY); #else CpuKernels::complex2D(mdlComplex, real, imag, xp, yp, mdlX, mdlInitY); #endif @@ -315,8 +328,8 @@ real = no_tex2D(mdlReal, xp, yp, mdlX, mdlInitY); *p.mdlReal, *p.mdlImag #else -#ifdef _CUDA_ENABLED -p.mdlReal, +#ifndef ALTCPU + p.mdlReal, p.mdlImag #else p.mdlComplex diff --git a/src/acc/acc_ptr.h b/src/acc/acc_ptr.h index bbf5186a5..9d4f45c13 100644 --- a/src/acc/acc_ptr.h +++ b/src/acc/acc_ptr.h @@ -8,7 +8,21 @@ #include "src/acc/cuda/custom_allocator.cuh" #include "src/acc/cuda/cuda_mem_utils.h" #include "src/acc/cuda/shortcuts.cuh" +#elif _HIP_ENABLED +#include "src/acc/hip/hip_settings.h" +#include +#include "src/acc/hip/custom_allocator.h" +#include "src/acc/hip/hip_mem_utils.h" +#include "src/acc/hip/shortcuts.h" +#elif _SYCL_ENABLED +#include +#include +#include +#include "src/acc/sycl/device_stubs.h" +#include "src/acc/sycl/sycl_settings.h" +#include "src/acc/sycl/sycl_virtual_dev.h" #else +#include "src/acc/cpu/device_stubs.h" #include "src/acc/cpu/cpu_settings.h" #endif @@ -34,7 +48,7 @@ static void HandleAccPtrDebugFatal( const char *err, const char *file, int line { fprintf(stderr, "DEBUG ERROR: %s in %s:%d\n", err, file, line ); fflush(stdout); -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) raise(SIGSEGV); #else CRITICAL(ERRGPUKERN); @@ -48,17 +62,21 @@ static void HandleAccPtrDebugInformational( const char *err, const char *file, i fflush(stdout); } -enum AccType {accUNSET, accCUDA, accCPU}; +enum AccType {accUNSET, accSYCL, accHIP, accCUDA, accCPU}; #ifdef _CUDA_ENABLED typedef cudaStream_t StreamType; typedef CudaCustomAllocator AllocatorType; typedef CudaCustomAllocator::Alloc AllocationType; +#elif _HIP_ENABLED +typedef hipStream_t StreamType; +typedef HipCustomAllocator AllocatorType; +typedef HipCustomAllocator::Alloc AllocationType; #else -typedef float StreamType; //Dummy type -typedef double AllocatorType; //Dummy type -typedef double AllocationType; //Dummy type +using StreamType = deviceStream_t; +using AllocatorType = double; //Dummy type +using AllocationType = double; //Dummy type #endif template @@ -74,6 +92,9 @@ class AccPtr size_t size; //Size used when copying data from and to device T *hPtr, *dPtr; //Host and device pointers bool doFreeDevice; //True if host or device needs to be freed +#ifdef _SYCL_ENABLED + bool isHostSYCL; // Check if host pointer is from sycl::malloc_host +#endif public: bool doFreeHost; //TODO make this private @@ -84,10 +105,15 @@ class AccPtr AccPtr(AllocatorType *allocator): size(0), hPtr(NULL), dPtr(NULL), doFreeHost(false), - doFreeDevice(false), allocator(allocator), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(allocator), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif {} @@ -97,6 +123,8 @@ class AccPtr doFreeDevice(false), allocator(allocator), alloc(NULL), stream(stream), #ifdef _CUDA_ENABLED accType(accCUDA) +#elif _HIP_ENABLED + accType(accHIP) #else accType(accCPU) #endif @@ -104,10 +132,15 @@ class AccPtr AccPtr(size_t size, AllocatorType *allocator): size(size), dPtr(NULL), doFreeHost(true), - doFreeDevice(false), allocator(allocator), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(allocator), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif { @@ -120,6 +153,8 @@ class AccPtr doFreeDevice(false), allocator(allocator), alloc(NULL), stream(stream), #ifdef _CUDA_ENABLED accType(accCUDA) +#elif _HIP_ENABLED + accType(accHIP) #else accType(accCPU) #endif @@ -130,30 +165,45 @@ class AccPtr AccPtr(T * h_start, size_t size, AllocatorType *allocator): size(size), hPtr(h_start), dPtr(NULL), doFreeHost(false), - doFreeDevice(false), allocator(allocator), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(allocator), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif {} AccPtr(T * h_start, size_t size, StreamType stream, AllocatorType *allocator): size(size), hPtr(h_start), dPtr(NULL), doFreeHost(false), - doFreeDevice(false), allocator(allocator), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(allocator), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif {} AccPtr(T * h_start, T * d_start, size_t size, AllocatorType *allocator): size(size), hPtr(h_start), dPtr(d_start), doFreeHost(false), - doFreeDevice(false), allocator(allocator), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(allocator), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif {} @@ -163,6 +213,8 @@ class AccPtr doFreeDevice(false), allocator(allocator), alloc(NULL), stream(stream), #ifdef _CUDA_ENABLED accType(accCUDA) +#elif _HIP_ENABLED + accType(accHIP) #else accType(accCPU) #endif @@ -174,10 +226,15 @@ class AccPtr AccPtr(): size(0), hPtr(NULL), dPtr(NULL), doFreeHost(false), - doFreeDevice(false), allocator(NULL), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(NULL), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif {} @@ -187,6 +244,10 @@ class AccPtr doFreeDevice(false), allocator(NULL), alloc(NULL), stream(stream), #ifdef _CUDA_ENABLED accType(accCUDA) +#elif _HIP_ENABLED + accType(accHIP) +#elif _SYCL_ENABLED + accType(accSYCL) #else accType(accCPU) #endif @@ -194,10 +255,15 @@ class AccPtr AccPtr(size_t size): size(size), dPtr(NULL), doFreeHost(true), - doFreeDevice(false), allocator(NULL), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(NULL), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif { @@ -210,6 +276,8 @@ class AccPtr doFreeDevice(false), allocator(NULL), alloc(NULL), stream(stream), #ifdef _CUDA_ENABLED accType(accCUDA) +#elif _HIP_ENABLED + accType(accHIP) #else accType(accCPU) #endif @@ -220,30 +288,45 @@ class AccPtr AccPtr(T * h_start, size_t size): size(size), hPtr(h_start), dPtr(NULL), doFreeHost(false), - doFreeDevice(false), allocator(NULL), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(NULL), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif {} AccPtr(T * h_start, size_t size, StreamType stream): size(size), hPtr(h_start), dPtr(NULL), doFreeHost(false), - doFreeDevice(false), allocator(NULL), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(NULL), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif {} AccPtr(T * h_start, T * d_start, size_t size): size(size), hPtr(h_start), dPtr(d_start), doFreeHost(false), - doFreeDevice(false), allocator(NULL), alloc(NULL), stream(cudaStreamPerThread), + doFreeDevice(false), allocator(NULL), alloc(NULL), #ifdef _CUDA_ENABLED + stream(cudaStreamPerThread), accType(accCUDA) +#elif _HIP_ENABLED + stream(hipStreamPerThread), + accType(accHIP) #else + stream(cudaStreamPerThread), accType(accCPU) #endif {} @@ -253,6 +336,8 @@ class AccPtr doFreeDevice(false), allocator(NULL), alloc(NULL), stream(stream), #ifdef _CUDA_ENABLED accType(accCUDA) +#elif _HIP_ENABLED + accType(accHIP) #else accType(accCPU) #endif @@ -284,15 +369,32 @@ class AccPtr accType = accT; } +#ifdef _SYCL_ENABLED + void setStreamAccType(StreamType s, AccType accT = accSYCL) + { + stream = s; + accType = accT; + } +#endif + void markReadyEvent() { #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if (alloc == NULL) ACC_PTR_DEBUG_FATAL("markReadyEvent called on null allocation.\n"); -#endif + #endif + alloc->markReadyEvent(stream); + } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if (alloc == NULL) + ACC_PTR_DEBUG_FATAL("markReadyEvent called on null allocation.\n"); + #endif alloc->markReadyEvent(stream); } #endif @@ -306,17 +408,47 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if(size==0) ACC_PTR_DEBUG_FATAL("deviceAlloc called with size == 0"); if (doFreeDevice) ACC_PTR_DEBUG_FATAL("Device double allocation.\n"); -#endif + #endif + doFreeDevice = true; + + alloc = allocator->alloc(size * sizeof(T)); + dPtr = (T*) alloc->getPtr(); + } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if(size==0) + ACC_PTR_DEBUG_FATAL("deviceAlloc called with size == 0"); + if (doFreeDevice) + ACC_PTR_DEBUG_FATAL("Device double allocation.\n"); + #endif doFreeDevice = true; alloc = allocator->alloc(size * sizeof(T)); dPtr = (T*) alloc->getPtr(); } +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(size > 0); + assert(doFreeDevice == false); + + doFreeDevice = true; + dPtr = (T*)(stream->syclMalloc(size * sizeof(T), syclMallocType::device)); + + if (dPtr == nullptr) + { + std::string str = "syclMalloc DEVICE error of size " + std::to_string(size * sizeof(T)) + ".\n"; + ACC_PTR_DEBUG_FATAL(str.c_str()); + CRITICAL(RAMERR); + } + } #endif } @@ -334,16 +466,38 @@ class AccPtr */ void hostAlloc() { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if(size==0) ACC_PTR_DEBUG_FATAL("deviceAlloc called with size == 0"); if (doFreeHost) ACC_PTR_DEBUG_FATAL("Host double allocation.\n"); #endif doFreeHost = true; +#ifdef _SYCL_ENABLED + if(accType == accSYCL) + { + hPtr = (T*)(stream->syclMalloc(size * sizeof(T), syclMallocType::host)); + if(hPtr == nullptr) + { + std::string str = "devSYCL(" + std::to_string(reinterpret_cast(stream)) + " : " + stream->getName() + ")\n"; + str += "syclMalloc HOST error of size " + std::to_string(size * sizeof(T)) + ".\n"; + + ACC_PTR_DEBUG_FATAL(str.c_str()); + CRITICAL(RAMERR); + } + isHostSYCL = true; + } + else + { + if(posix_memalign((void **)&hPtr, MEM_ALIGN, sizeof(T) * size)) + CRITICAL(RAMERR); + isHostSYCL = false; + } +#else // TODO - alternatively, this could be aligned std::vector if(posix_memalign((void **)&hPtr, MEM_ALIGN, sizeof(T) * size)) CRITICAL(RAMERR); +#endif } /** @@ -370,7 +524,7 @@ class AccPtr void accAlloc() { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) deviceAlloc(); else hostAlloc(); @@ -378,7 +532,7 @@ class AccPtr void accAlloc(size_t newSize) { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) deviceAlloc(newSize); else hostAlloc(newSize); @@ -387,17 +541,37 @@ class AccPtr // Allocate storage of a new size for the array void resizeHost(size_t newSize) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (size==0) ACC_PTR_DEBUG_INFO("Resizing from size zero (permitted).\n"); #endif // TODO - alternatively, this could be aligned std::vector T* newArr; +#ifdef _SYCL_ENABLED + if(accType == accSYCL) + { + newArr = (T*)(stream->syclMalloc(newSize * sizeof(T), syclMallocType::host)); + if(newArr == nullptr) + { + std::string str = "syclMalloc HOST in resizeHost error of size " + std::to_string(newSize * sizeof(T)) + ".\n"; + ACC_PTR_DEBUG_FATAL(str.c_str()); + CRITICAL(RAMERR); + } + isHostSYCL = true; + } + else + { + if(posix_memalign((void **)&newArr, MEM_ALIGN, sizeof(T) * newSize)) + CRITICAL(RAMERR); + isHostSYCL = false; + } +#else if(posix_memalign((void **)&newArr, MEM_ALIGN, sizeof(T) * newSize)) CRITICAL(RAMERR); +#endif memset( newArr, 0x0, sizeof(T) * newSize); -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (dPtr!=NULL) ACC_PTR_DEBUG_FATAL("resizeHost: Resizing host with present device allocation.\n"); if (newSize==0) @@ -412,14 +586,34 @@ class AccPtr // Resize retaining as much of the original contents as possible void resizeHostCopy(size_t newSize) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) // if (size==0) // ACC_PTR_DEBUG_INFO("Resizing from size zero (permitted).\n"); #endif // TODO - alternatively, this could be aligned std::vector T* newArr; +#ifdef _SYCL_ENABLED + if(accType == accSYCL) + { + newArr = (T*)(stream->syclMalloc(newSize * sizeof(T), syclMallocType::host)); + if(newArr == nullptr) + { + std::string str = "syclMalloc HOST in resizeHostCopy error of size " + std::to_string(newSize * sizeof(T)) + ".\n"; + ACC_PTR_DEBUG_FATAL(str.c_str()); + CRITICAL(RAMERR); + } + isHostSYCL = true; + } + else + { + if(posix_memalign((void **)&newArr, MEM_ALIGN, sizeof(T) * newSize)) + CRITICAL(RAMERR); + isHostSYCL = false; + } +#else if(posix_memalign((void **)&newArr, MEM_ALIGN, sizeof(T) * newSize)) CRITICAL(RAMERR); +#endif // Copy in what we can from the original matrix if ((size > 0) && (hPtr != NULL)) @@ -443,7 +637,7 @@ class AccPtr memset( newArr, 0x0, sizeof(T) * newSize); } -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (dPtr!=NULL) ACC_PTR_DEBUG_FATAL("resizeHostCopy: Resizing host with present device allocation.\n"); if (newSize==0) @@ -463,12 +657,29 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("Memset requested before allocation in deviceInit().\n"); -#endif + #endif cudaMemInit( dPtr, value, size, stream); } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if (dPtr == NULL) + ACC_PTR_DEBUG_FATAL("Memset requested before allocation in deviceInit().\n"); + #endif + hipMemInit( dPtr, value, size, stream); + } +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(dPtr != NULL); + assert(value == 0); + + stream->syclMemset(dPtr, value, size * sizeof(T)); + } #endif } @@ -477,7 +688,7 @@ class AccPtr */ void hostInit(int value) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (hPtr == NULL) ACC_PTR_DEBUG_FATAL("Memset requested before allocation in hostInit().\n"); #endif @@ -489,7 +700,7 @@ class AccPtr */ void accInit(int value) { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) deviceInit(value); else hostInit(value); @@ -501,7 +712,7 @@ class AccPtr void allInit(int value) { hostInit(value); - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) deviceInit(value); } @@ -513,14 +724,33 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("cpToDevice() called before allocation.\n"); if (hPtr == NULL) ACC_PTR_DEBUG_FATAL("NULL host pointer in cpToDevice().\n"); -#endif + #endif CudaShortcuts::cpyHostToDevice(hPtr, dPtr, size, stream); } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if (dPtr == NULL) + ACC_PTR_DEBUG_FATAL("cpToDevice() called before allocation.\n"); + if (hPtr == NULL) + ACC_PTR_DEBUG_FATAL("NULL host pointer in cpToDevice().\n"); + #endif + HipShortcuts::cpyHostToDevice(hPtr, dPtr, size, stream); + } +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(dPtr != NULL); + assert(hPtr != NULL); + + stream->syclMemcpy(dPtr, hPtr, size * sizeof(T)); + } #endif } @@ -529,9 +759,9 @@ class AccPtr */ void cpToDevice(T * hostPtr) { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (hostPtr == NULL) ACC_PTR_DEBUG_FATAL("Null-pointer given in cpToDevice(hostPtr).\n"); #endif @@ -568,14 +798,33 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("cp_to_host() called before device allocation.\n"); if (hPtr == NULL) ACC_PTR_DEBUG_FATAL("NULL host pointer in cp_to_host().\n"); -#endif + #endif cudaCpyDeviceToHost(dPtr, hPtr, size, stream); } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if (dPtr == NULL) + ACC_PTR_DEBUG_FATAL("cp_to_host() called before device allocation.\n"); + if (hPtr == NULL) + ACC_PTR_DEBUG_FATAL("NULL host pointer in cp_to_host().\n"); + #endif + hipCpyDeviceToHost(dPtr, hPtr, size, stream); + } +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(dPtr != NULL); + assert(hPtr != NULL); + + stream->syclMemcpy(hPtr, dPtr, size * sizeof(T)); + } #endif } @@ -587,14 +836,33 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("cp_to_host(thisSize) called before device allocation.\n"); if (hPtr == NULL) ACC_PTR_DEBUG_FATAL("NULL host pointer in cp_to_host(thisSize).\n"); -#endif + #endif cudaCpyDeviceToHost(dPtr, hPtr, thisSize, stream); } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if (dPtr == NULL) + ACC_PTR_DEBUG_FATAL("cp_to_host(thisSize) called before device allocation.\n"); + if (hPtr == NULL) + ACC_PTR_DEBUG_FATAL("NULL host pointer in cp_to_host(thisSize).\n"); + #endif + hipCpyDeviceToHost(dPtr, hPtr, thisSize, stream); + } +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(dPtr != NULL); + assert(hPtr != NULL); + + stream->syclMemcpy(hPtr, dPtr, thisSize * sizeof(T)); + } #endif } @@ -606,14 +874,33 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("cp_to_host(hstPtr, thisSize) called before device allocation.\n"); if (hstPtr == NULL) ACC_PTR_DEBUG_FATAL("NULL host pointer in cp_to_host(hstPtr, thisSize).\n"); -#endif + #endif cudaCpyDeviceToHost(dPtr, hstPtr, thisSize, stream); } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if (dPtr == NULL) + ACC_PTR_DEBUG_FATAL("cp_to_host(hstPtr, thisSize) called before device allocation.\n"); + if (hstPtr == NULL) + ACC_PTR_DEBUG_FATAL("NULL host pointer in cp_to_host(hstPtr, thisSize).\n"); + #endif + hipCpyDeviceToHost(dPtr, hstPtr, thisSize, stream); + } +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(dPtr != NULL); + assert(hstPtr != NULL); + + stream->syclMemcpy(hstPtr, dPtr, thisSize * sizeof(T)); + } #endif } @@ -625,14 +912,28 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("cp_to_host_on_stream(s) called before device allocation.\n"); if (hPtr == NULL) ACC_PTR_DEBUG_FATAL("NULL host pointer in cp_to_host_on_stream(s).\n"); -#endif + #endif cudaCpyDeviceToHost(dPtr, hPtr, size, s); } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if (dPtr == NULL) + ACC_PTR_DEBUG_FATAL("cp_to_host_on_stream(s) called before device allocation.\n"); + if (hPtr == NULL) + ACC_PTR_DEBUG_FATAL("NULL host pointer in cp_to_host_on_stream(s).\n"); + #endif + hipCpyDeviceToHost(dPtr, hPtr, size, s); + } +#elif _SYCL_ENABLED + if (accType == accSYCL) + ACC_PTR_DEBUG_FATAL("cpToHostOnStream(StreamType s) does not work on SYCL.\n"); #endif } @@ -644,12 +945,29 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if (dstDevPtr == NULL) ACC_PTR_DEBUG_FATAL("NULL-pointer given in cpOnDevice(dstDevPtr).\n"); -#endif + #endif CudaShortcuts::cpyDeviceToDevice(dPtr, dstDevPtr, size, stream); } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if (dstDevPtr == NULL) + ACC_PTR_DEBUG_FATAL("NULL-pointer given in cpOnDevice(dstDevPtr).\n"); + #endif + HipShortcuts::cpyDeviceToDevice(dPtr, dstDevPtr, size, stream); + } +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(dPtr != NULL); + assert(dstDevPtr != NULL); + + stream->syclMemcpy(dstDevPtr, dPtr, size * sizeof(T)); + } #endif } @@ -658,7 +976,7 @@ class AccPtr */ void cpOnHost(T * dstDevPtr) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (dstDevPtr == NULL) ACC_PTR_DEBUG_FATAL("NULL-pointer given in cp_on_host(dstDevPtr).\n"); if (hPtr == NULL) @@ -669,7 +987,7 @@ class AccPtr void cpOnAcc(T * dstDevPtr) { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) cpOnDevice(dstDevPtr); else cpOnHost(dstDevPtr); @@ -677,7 +995,7 @@ class AccPtr void cpOnAcc(AccPtr &devPtr) { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) cpOnDevice(devPtr.dPtr); else cpOnHost(devPtr.hPtr); @@ -688,7 +1006,7 @@ class AccPtr */ const T& operator[](size_t idx) const { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (hPtr == NULL) ACC_PTR_DEBUG_FATAL("const operator[] called with NULL host pointer.\n"); #endif @@ -700,7 +1018,7 @@ class AccPtr */ T& operator[](size_t idx) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (hPtr == NULL) ACC_PTR_DEBUG_FATAL("operator[] called with NULL host pointer.\n"); #endif @@ -712,7 +1030,7 @@ class AccPtr */ T& operator()(size_t idx) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("operator(idx) called with NULL acc pointer.\n"); #endif @@ -725,7 +1043,7 @@ class AccPtr */ const T& operator()(size_t idx) const { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("operator(idx) called with NULL acc pointer.\n"); #endif @@ -739,9 +1057,9 @@ class AccPtr { // TODO - this could cause considerable confusion given the above operators. But it // also simplifies code that uses it. What to do... - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("operator() called with NULL device pointer.\n"); #endif @@ -749,7 +1067,7 @@ class AccPtr } else { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (hPtr == NULL) ACC_PTR_DEBUG_FATAL("operator() called with NULL host pointer.\n"); #endif @@ -761,9 +1079,9 @@ class AccPtr { // TODO - this could cause considerable confusion given the above operators. But it // also simplifies code that uses it. What to do... - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if ( dPtr == 0) ACC_PTR_DEBUG_FATAL("DEBUG_WARNING: \"kernel cast\" on null device pointer.\n"); #endif @@ -771,7 +1089,7 @@ class AccPtr } else { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if ( hPtr == 0) ACC_PTR_DEBUG_FATAL("DEBUG_WARNING: \"kernel cast\" on null host pointer.\n"); #endif @@ -784,6 +1102,12 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) DEBUG_HANDLE_ERROR(cudaStreamSynchronize(stream)); +#elif _HIP_ENABLED + if (accType == accHIP) + DEBUG_HANDLE_ERROR(hipStreamSynchronize(stream)); +#elif _SYCL_ENABLED + if (accType == accSYCL) + stream->waitAll(); #endif } @@ -798,6 +1122,26 @@ class AccPtr return value; } else +#elif _HIP_ENABLED + if (accType == accHIP) + { + T value; + hipCpyDeviceToHost(&dPtr[idx], &value, 1, stream); + streamSync(); + return value; + } + else +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(dPtr != NULL); + assert(idx < size); + T value; + stream->syclMemcpy(&value, &dPtr[idx], sizeof(T)); + streamSync(); + return value; + } + else #endif return hPtr[idx]; } @@ -812,11 +1156,58 @@ class AccPtr streamSync(); return value; } +#elif _HIP_ENABLED + if (accType == accHIP) + { + T value; + hipCpyDeviceToHost(&dPtr[idx], &value, 1, stream); + streamSync(); + return value; + } +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(dPtr != NULL); + assert(idx < size); + T value; + stream->syclMemcpy(&value, &dPtr[idx], sizeof(T)); + streamSync(); + return value; + } #else return NULL; #endif } + void setAccValueAt(T value, size_t idx) + { +#ifdef _CUDA_ENABLED + if (accType == accCUDA) + { + CudaShortcuts::cpyHostToDevice(&value, &dPtr[idx], sizeof(T), stream); + streamSync(); + } + else +#elif _HIP_ENABLED + if (accType == accHIP) + { + HipShortcuts::cpyHostToDevice(&value, &dPtr[idx], sizeof(T), stream); + streamSync(); + } + else +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + assert(dPtr != NULL); + assert(idx < size); + stream->syclMemcpy(&dPtr[idx], &value, sizeof(T)); + streamSync(); + } + else +#endif + hPtr[idx] = value; + } + void dumpDeviceToFile(std::string fileName) { @@ -835,6 +1226,36 @@ class AccPtr delete [] tmp; } else +#elif _HIP_ENABLED + if (accType == accHIP) + { + T *tmp = new T[size]; + hipCpyDeviceToHost(dPtr, tmp, size, stream); + + std::ofstream f; + f.open(fileName.c_str()); + streamSync(); + for (unsigned i = 0; i < size; i ++) + f << tmp[i] << std::endl; + f.close(); + delete [] tmp; + } + else +#elif _SYCL_ENABLED + if (accType == accSYCL) + { + T *tmp = new T[size]; + stream->syclMemcpy(tmp, dPtr, size * sizeof(T)); + + std::ofstream f; + f.open(fileName.c_str()); + streamSync(); + for (unsigned i = 0; i < size; i ++) + f << tmp[i] << std::endl; + f.close(); + delete [] tmp; + } + else #endif { std::ofstream f; @@ -855,7 +1276,7 @@ class AccPtr void dumpAccToFile(std::string fileName) { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) dumpDeviceToFile(fileName); else dumpHostToFile(fileName); @@ -869,10 +1290,10 @@ class AccPtr #ifdef _CUDA_ENABLED if (accType == accCUDA) { -#ifdef DEBUG_CUDA + #ifdef DEBUG_CUDA if (dPtr == NULL) ACC_PTR_DEBUG_FATAL("Free device memory was called on NULL pointer in free_device().\n"); -#endif + #endif doFreeDevice = false; if (alloc->getReadyEvent() == 0) @@ -882,6 +1303,34 @@ class AccPtr // DEBUG_HANDLE_ERROR(cudaFree(dPtr)); + dPtr = NULL; + } +#elif _HIP_ENABLED + if (accType == accHIP) + { + #ifdef DEBUG_HIP + if (dPtr == NULL) + ACC_PTR_DEBUG_FATAL("Free device memory was called on NULL pointer in free_device().\n"); + #endif + doFreeDevice = false; + + if (alloc->getReadyEvent() == 0) + alloc->markReadyEvent(stream); + alloc->doFreeWhenReady(); + alloc = NULL; + +// DEBUG_HANDLE_ERROR(hipFree(dPtr)); + + dPtr = NULL; + } +#elif _SYCL_ENABLED +// if (accType == accSYCL) + { + assert(dPtr != NULL); + + stream->waitAll(); + stream->syclFree(dPtr); + doFreeDevice = false; dPtr = NULL; } #endif @@ -892,13 +1341,22 @@ class AccPtr */ void freeHost() { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (hPtr == NULL) ACC_PTR_DEBUG_FATAL("free_host() called on NULL pointer.\n"); #endif doFreeHost = false; if (NULL != hPtr) +#ifdef _SYCL_ENABLED + { + if(isHostSYCL) + stream->syclFree(hPtr); + else + free(hPtr); + } +#else free(hPtr); +#endif hPtr = NULL; } @@ -982,7 +1440,7 @@ class AccPtr T *getAccPtr() { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) return dPtr; else return hPtr; @@ -1001,7 +1459,7 @@ class AccPtr void setDevicePtr(T *ptr) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (doFreeDevice) ACC_PTR_DEBUG_FATAL("Device pointer set without freeing the old one.\n"); #endif @@ -1010,7 +1468,7 @@ class AccPtr void setDevicePtr(const AccPtr &ptr) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (ptr.dPtr == NULL) ACC_PTR_DEBUG_FATAL("Device pointer is not set.\n"); #endif @@ -1019,7 +1477,7 @@ class AccPtr void setHostPtr(T *ptr) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (doFreeHost) ACC_PTR_DEBUG_FATAL("Host pointer set without freeing the old one.\n"); #endif @@ -1028,7 +1486,7 @@ class AccPtr void setHostPtr(const AccPtr &ptr) { -#ifdef DEBUG_CUDA +#if defined(DEBUG_CUDA) || defined(DEBUG_HIP) if (ptr.hPtr == NULL) ACC_PTR_DEBUG_FATAL("Host pointer is not set.\n"); #endif @@ -1037,7 +1495,7 @@ class AccPtr void setAccPtr(const AccPtr &ptr) { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) setDevicePtr(ptr.hPtr); else setHostPtr(ptr.hPtr); @@ -1045,7 +1503,7 @@ class AccPtr void setAccPtr(T *ptr) { - if (accType == accCUDA) + if (accType == accCUDA || accType == accHIP || accType == accSYCL) setDevicePtr(ptr); else setHostPtr(ptr); @@ -1096,55 +1554,64 @@ class AccPtrBundle: public AccPtr setSize(size); } +#ifdef _SYCL_ENABLED + AccPtrBundle(StreamType dev): + AccPtr(dev), current_packed_pos(0) + {} +#endif + template void pack(AccPtr &ptr) { -#ifdef _CUDA_ENABLED - #ifdef DEBUG_CUDA - if (current_packed_pos + ptr.getSize() > size) - ACC_PTR_DEBUG_FATAL("Packing exceeds bundle total size.\n"); - if (hPtr == NULL) - ACC_PTR_DEBUG_FATAL("Pack called on null host pointer.\n"); + if (accType == accCUDA || accType == accHIP || accType == accSYCL) + { + #if defined DEBUG_CUDA || defined DEBUG_HIP + if (current_packed_pos + ptr.getSize() > size) + ACC_PTR_DEBUG_FATAL("Packing exceeds bundle total size.\n"); + if (hPtr == NULL) + ACC_PTR_DEBUG_FATAL("Pack called on null host pointer.\n"); #endif - if (ptr.getHostPtr() != NULL) - memcpy ( &hPtr[current_packed_pos], ptr.getHostPtr(), ptr.getSize() * sizeof(T)); - ptr.freeHostIfSet(); - ptr.setHostPtr((T*) &hPtr[current_packed_pos]); - ptr.setDevicePtr((T*) &dPtr[current_packed_pos]); + if (ptr.getHostPtr() != NULL) + memcpy ( &hPtr[current_packed_pos], ptr.getHostPtr(), ptr.getSize() * sizeof(T)); + ptr.freeHostIfSet(); + ptr.setHostPtr((T*) &hPtr[current_packed_pos]); + ptr.setDevicePtr((T*) &dPtr[current_packed_pos]); - current_packed_pos += ptr.getSize() * sizeof(T); -#else - if (ptr.getHostPtr() == NULL) - ptr.hostAlloc(); -#endif + current_packed_pos += ptr.getSize() * sizeof(T); + } + else + { + if (ptr.getHostPtr() == NULL) + ptr.hostAlloc(); + } } //Overwrite allocation methods and block for no device void allAlloc() { -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED || defined _SYCL_ENABLED AccPtr::allAlloc(); #endif } void allAlloc(size_t size) { -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED || defined _SYCL_ENABLED AccPtr::allAlloc(size); #endif } void hostAlloc() { -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED || defined _SYCL_ENABLED AccPtr::hostAlloc(); #endif } void hostAlloc(size_t size) { -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED || defined _SYCL_ENABLED AccPtr::hostAlloc(size); #endif } @@ -1168,31 +1635,70 @@ class AccPtrFactory allocator(NULL), stream(0), accType(accT) {} +#ifdef _SYCL_ENABLED + AccPtrFactory(StreamType s): + allocator(NULL), stream(s), accType(accSYCL) + {} +#else AccPtrFactory(AllocatorType *alloc): +#ifdef _CUDA_ENABLED allocator(alloc), stream(0), accType(accCUDA) +#elif _HIP_ENABLED + allocator(alloc), stream(0), accType(accHIP) +#else + allocator(alloc), stream(0), accType(accCPU) +#endif {} AccPtrFactory(AllocatorType *alloc, StreamType s): +#ifdef _CUDA_ENABLED allocator(alloc), stream(s), accType(accCUDA) +#elif _HIP_ENABLED + allocator(alloc), stream(s), accType(accHIP) +#else + allocator(alloc), stream(0), accType(accCPU) +#endif {} +#endif template AccPtr make() { - AccPtr ptr(stream, allocator); - ptr.setAccType(accType); + if (accType == accSYCL) + { + AccPtr ptr(stream); + ptr.setAccType(accType); - return ptr; + return ptr; + } + else + { + AccPtr ptr(stream, allocator); + ptr.setAccType(accType); + + return ptr; + } } template AccPtr make(size_t size) { - AccPtr ptr(stream, allocator); - ptr.setAccType(accType); - ptr.setSize(size); - - return ptr; + if (accType == accSYCL) + { + AccPtr ptr(stream); + ptr.setAccType(accType); + ptr.setSize(size); + + return ptr; + } + else + { + AccPtr ptr(stream, allocator); + ptr.setAccType(accType); + ptr.setSize(size); + + return ptr; + } } @@ -1208,10 +1714,20 @@ class AccPtrFactory AccPtrBundle makeBundle() { - AccPtrBundle bundle(stream, allocator); - bundle.setAccType(accType); + if (accType == accSYCL) + { + AccPtrBundle bundle(stream, 0); + bundle.setAccType(accType); - return bundle; + return bundle; + } + else + { + AccPtrBundle bundle(stream, allocator); + bundle.setAccType(accType); + + return bundle; + } } AccPtrBundle makeBundle(size_t size) @@ -1221,6 +1737,25 @@ class AccPtrFactory return bundle; } + +#ifdef _SYCL_ENABLED + template + AccPtr make(StreamType dev) + { + AccPtr ptr(dev); + ptr.setAccType(accType); + + return ptr; + } + + AccPtrBundle makeBundle(StreamType dev) + { + AccPtrBundle bundle(dev); + bundle.setAccType(accType); + + return bundle; + } +#endif }; #endif diff --git a/src/acc/cpu/cpu_backprojector.cpp b/src/acc/cpu/cpu_backprojector.cpp index f7165d57f..2bce42710 100644 --- a/src/acc/cpu/cpu_backprojector.cpp +++ b/src/acc/cpu/cpu_backprojector.cpp @@ -2,7 +2,7 @@ #include #include #include -#include "src/acc/cpu/cuda_stubs.h" +#include "src/acc/cpu/device_stubs.h" #include "src/acc/acc_ptr.h" #include "src/acc/acc_projector.h" diff --git a/src/acc/cpu/cpu_helper_functions.cpp b/src/acc/cpu/cpu_helper_functions.cpp index 5fb204b51..c891bb56d 100644 --- a/src/acc/cpu/cpu_helper_functions.cpp +++ b/src/acc/cpu/cpu_helper_functions.cpp @@ -1,7 +1,7 @@ #ifdef ALTCPU // Make sure we build for CPU -#include "src/acc/cpu/cuda_stubs.h" +#include "src/acc/cpu/device_stubs.h" #include "src/acc/acc_ptr.h" #include "src/acc/acc_projector.h" diff --git a/src/acc/cpu/cpu_helper_functions.h b/src/acc/cpu/cpu_helper_functions.h index e6b91654b..fc0de5cea 100644 --- a/src/acc/cpu/cpu_helper_functions.h +++ b/src/acc/cpu/cpu_helper_functions.h @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include "src/complex.h" #include "src/parallel.h" @@ -80,43 +82,75 @@ void window_fourier_transform( template static T getMin(T *data, size_t size) { - T min = data[0]; - for(size_t i=1; i::max(); +#if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp simd reduction(min:minv) +#endif + for(size_t i=0; i static T getMax(T *data, size_t size) { - T max = data[0]; - for(size_t i=1; i max ? data[i] : max; - - return max; + T maxv = std::numeric_limits::lowest(); +#if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp simd reduction(max:maxv) +#endif + for(size_t i=0; i maxv ? data[i] : maxv; + + return maxv; } template static T getSum(T *data, size_t size) { - T sum = data[0]; - for(size_t i=1; i(0); +#if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp simd reduction(+:sum) +#endif + for(size_t i=0; i +inline void min_loc(std::pair *out, std::pair *in) +{ + if (out->second > in->second) + { + out->first = in->first; + out->second = in->second; + } +} + +template +inline void max_loc(std::pair *out, std::pair *in) +{ + if (out->second < in->second) + { + out->first = in->first; + out->second = in->second; + } +} + template static std::pair getArgMin(T *data, size_t size) { - std::pair pair; - pair.first = 0; - pair.second = data[0]; - - for(size_t i=1; i pair {-1, std::numeric_limits::max()}; +#if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp declare reduction(minloc: std::pair: min_loc(&omp_out, &omp_in)) \ + initializer(omp_priv = {-1, std::numeric_limits::max()}) + #pragma omp simd reduction(minloc:pair) +#endif + for(size_t i=0; i getArgMin(T *data, size_t size) template static std::pair getArgMax(T *data, size_t size) { - std::pair pair; - pair.first = 0; - pair.second = data[0]; - - for(size_t i=1; i pair.second) { + std::pair pair {-1, std::numeric_limits::lowest()}; +#if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp declare reduction(maxloc: std::pair: max_loc(&omp_out, &omp_in)) \ + initializer(omp_priv = {-1, std::numeric_limits::lowest()}) + #pragma omp simd reduction(maxloc:pair) +#endif + for(size_t i=0; i pair.second) + { pair.first = i; pair.second = data[i]; } diff --git a/src/acc/cpu/cpu_kernels/diff2.h b/src/acc/cpu/cpu_kernels/diff2.h index 992c7243b..a28a39814 100644 --- a/src/acc/cpu/cpu_kernels/diff2.h +++ b/src/acc/cpu/cpu_kernels/diff2.h @@ -544,7 +544,7 @@ inline void diff2_coarse( diffi[j] = 0.0; #if _OPENMP > 201307 // For OpenMP 4.5 and later - #pragma omp simd reduction(+:diffi[:eulers_per_block]) + #pragma omp simd reduction(+:diffi) #endif for (int tid=0; tid::max(); //large negative number + g_weights[pos+itrans] = std::numeric_limits::lowest(); else g_weights[pos+itrans] = g_pdf_orientation[ix] + g_pdf_offset[c_itrans] + min_diff2 - g_weights[pos+itrans]; } @@ -335,148 +334,6 @@ void cpu_translate3D(T * g_image_in, } } -template -#ifndef __INTEL_COMPILER -__attribute__((always_inline)) -inline -#endif -void centerFFT_2D( int batch_size, - size_t pixel_start, - size_t pixel_end, - T *img_in, - size_t image_size, - int xdim, - int ydim, - int xshift, - int yshift) -{ -#ifdef DEBUG_CUDA - if (image_size > (size_t)std::numeric_limits::max()) - ACC_PTR_DEBUG_INFO("centerFFT_2D: image_size > std::numeric_limits::max()"); - if (image_size*(size_t)batch_size > (size_t)std::numeric_limits::max()) - ACC_PTR_DEBUG_INFO("centerFFT_2D: image_size*batch_size > std::numeric_limits::max()"); - if (pixel_end > image_size) - ACC_PTR_DEBUG_INFO("centerFFT_2D: pixel_end > image_size"); -#endif - size_t pix_start = pixel_start; - size_t pix_end = pixel_end; - for(int batch=0; batch( int batch_size, - size_t pixel_start, - size_t pixel_end, - float *img_in, - size_t image_size, - int xdim, - int ydim, - int xshift, - int yshift); -template void centerFFT_2D( int batch_size, - size_t pixel_start, - size_t pixel_end, - double *img_in, - size_t image_size, - int xdim, - int ydim, - int xshift, - int yshift); - - -template -#ifndef __INTEL_COMPILER -__attribute__((always_inline)) -inline -#endif -void centerFFT_3D( int batch_size, - size_t pixel_start, - size_t pixel_end, - T *img_in, - size_t image_size, - int xdim, - int ydim, - int zdim, - int xshift, - int yshift, - int zshift) -{ -#ifdef DEBUG_CUDA - if (image_size > (size_t)std::numeric_limits::max()) - ACC_PTR_DEBUG_INFO("centerFFT_3D: image_size > std::numeric_limits::max()"); - if (image_size*(size_t)batch_size > (size_t)std::numeric_limits::max()) - ACC_PTR_DEBUG_INFO("centerFFT_3D: image_size*batch_size > std::numeric_limits::max()"); - if (pixel_end > image_size) - ACC_PTR_DEBUG_INFO("centerFFT_3D: pixel_end > image_size"); -#endif - size_t pix_start = pixel_start; - size_t pix_end = pixel_end; - int xydim = xdim*ydim; - for(int batch=0; batch( int batch_size, - size_t pixel_start, - size_t pixel_end, - float *img_in, - size_t image_size, - int xdim, - int ydim, - int zdim, - int xshift, - int yshift, - int zshift); -template void centerFFT_3D( int batch_size, - size_t pixel_start, - size_t pixel_end, - double *img_in, - size_t image_size, - int xdim, - int ydim, - int zdim, - int xshift, - int yshift, - int zshift); - /* TODO - if create optimized CPU version of autopicker * All these functions need to be converted to use internal loops rather than * block and thread indices to operate like other active functions seen in this file @@ -806,6 +663,22 @@ void cpu_kernel_multi( T *A, for (size_t i = 0; i < image_size; i ++) OUT[i] = A[i]*B[i]*S; } + +template +void cpu_kernel_add( + T *A, + T S, + size_t size +) +{ +#ifdef DEBUG_CUDA + if (size < 0) + ACC_PTR_DEBUG_INFO("cpu_kernel_add: image_size < 0"); +#endif + for (size_t i = 0; i < size; i ++) + A[i] += S; +} + /* void batch_multi( int blockIdx_x, int blockIdx_y, @@ -1055,8 +928,8 @@ template void CpuKernels::cpu_translate2D(XFLOAT *, template void CpuKernels::cpu_translate3D(XFLOAT *, XFLOAT *, size_t, int, int, int, int, int, int); -template void CpuKernels::cpu_kernel_multi( XFLOAT *, - XFLOAT, size_t); +template void CpuKernels::cpu_kernel_multi( XFLOAT *, XFLOAT, size_t); +template void CpuKernels::cpu_kernel_add( XFLOAT *, XFLOAT, size_t); template void CpuKernels::cpu_kernel_make_eulers_3D(int, int, XFLOAT *, XFLOAT *, XFLOAT *, XFLOAT *, unsigned long, XFLOAT *, XFLOAT *); diff --git a/src/acc/cpu/cpu_kernels/helper.h b/src/acc/cpu/cpu_kernels/helper.h index a3072b77a..cf56ba120 100644 --- a/src/acc/cpu/cpu_kernels/helper.h +++ b/src/acc/cpu/cpu_kernels/helper.h @@ -36,8 +36,7 @@ void weights_exponent_coarse( T diff2 = g_weights[idx]; if( diff2 < g_min_diff2 || g_pdf_orientation_zeros[iorient] || g_pdf_offset_zeros[itrans]) - // TODO - replace with lowest() when C++11 is supported - g_weights[idx] = -std::numeric_limits::max(); //large negative number + g_weights[idx] = std::numeric_limits::lowest(); else g_weights[idx] = g_pdf_orientation[iorient] + g_pdf_offset[itrans] + g_min_diff2 - diff2; } @@ -239,30 +238,6 @@ void cpu_translate3D(T * g_image_in, int dz); //---------------------------------------------------------------------------- -template -void centerFFT_2D( int batch_size, - size_t pixel_start, - size_t pixel_end, - T *img_in, - size_t image_size, - int xdim, - int ydim, - int xshift, - int yshift); - -template -void centerFFT_3D( int batch_size, - size_t pixel_start, - size_t pixel_end, - T *img_in, - size_t image_size, - int xdim, - int ydim, - int zdim, - int xshift, - int yshift, - int zshift); -//---------------------------------------------------------------------------- /*void probRatio( int blockIdx_x, int threadIdx_x, XFLOAT *d_Mccf, @@ -359,17 +334,17 @@ void batch_convol_B(int blockIdx_x, ACCCOMPLEX *d_A, ACCCOMPLEX *d_B, size_t image_size); -|* +*/ +/* * Multiplies scalar array A by a scalar S * * OUT[i] = A[i]*S - *| + */ template void cpu_kernel_multi( T *A, T *OUT, T S, size_t image_size); -*/ /* * In place multiplies scalar array A by a scalar S * @@ -390,6 +365,17 @@ void cpu_kernel_multi( T *A, T *OUT, T S, size_t image_size); +/* + * In place add scalar S to scalar array A + * + * A[i] = A[i]*S + */ +template +void cpu_kernel_add( + T *A, + T S, + size_t size +); /* void finalizeMstddev( int blockIdx_x, int threadIdx_x, diff --git a/src/acc/cpu/cpu_kernels/wavg.h.orig b/src/acc/cpu/cpu_kernels/wavg.h.orig deleted file mode 100644 index a57ff277c..000000000 --- a/src/acc/cpu/cpu_kernels/wavg.h.orig +++ /dev/null @@ -1,385 +0,0 @@ -#ifndef WAVG_KERNEL_H_ -#define WAVG_KERNEL_H_ - -#include -#include -#include - -#include "src/acc/cpu/cpu_settings.h" -#include "src/acc/acc_projector.h" -#include "src/acc/cpu/cpu_kernels/cpu_utils.h" -#include "src/acc/cpu/cpu_kernels/helper.h" - -namespace CpuKernels -{ - -// sincos lookup table optimization. Function translatePixel calls -// sincos(x*tx + y*ty). We precompute 2D lookup tables for x and y directions. -// The first dimension is x or y pixel index, and the second dimension is x or y -// translation index. Since sin(a+B) = sin(A) * cos(B) + cos(A) * sin(B), and -// cos(A+B) = cos(A) * cos(B) - sin(A) * sin(B), we can use lookup table to -// compute sin(x*tx + y*ty) and cos(x*tx + y*ty). -<<<<<<< HEAD -template -#ifndef __INTEL_COMPILER -__attribute__((always_inline)) -inline -#endif -======= -template ->>>>>>> intel-opt -void wavg_ref3D( - XFLOAT * RESTRICT g_eulers, - AccProjectorKernel &projector, - unsigned long image_size, - unsigned long orientation_num, -#ifdef DEBUG_CUDA - XFLOAT * RESTRICT _g_img_real, -#else - XFLOAT * RESTRICT g_img_real, -#endif - XFLOAT * RESTRICT g_img_imag, - XFLOAT * RESTRICT g_trans_x, - XFLOAT * RESTRICT g_trans_y, - XFLOAT * RESTRICT g_trans_z, - XFLOAT * RESTRICT g_weights, - XFLOAT * RESTRICT g_ctfs, - XFLOAT * RESTRICT g_wdiff2s_parts, - XFLOAT * RESTRICT g_wdiff2s_AA, - XFLOAT * RESTRICT g_wdiff2s_XA, - unsigned long trans_num, - XFLOAT weight_norm, - XFLOAT significant_weight, - XFLOAT part_scale) -{ -#ifdef DEBUG_CUDA - checkedArray g_img_real; - g_img_real.initCheckedArray(_g_img_real); -#endif - // pre-compute sin and cos for x and y direction - int xSize = projector.imgX; - int ySize = projector.imgY; - XFLOAT sin_x[trans_num][xSize], cos_x[trans_num][xSize]; - XFLOAT sin_y[trans_num][ySize], cos_y[trans_num][ySize]; - - computeSincosLookupTable2D(trans_num, g_trans_x, g_trans_y, xSize, ySize, - &sin_x[0][0], &cos_x[0][0], - &sin_y[0][0], &cos_y[0][0]); - - // Set up other arrays - XFLOAT ref_real[xSize], ref_imag[xSize]; - XFLOAT img_real[xSize], img_imag[xSize]; - XFLOAT ctfs[xSize]; - XFLOAT wdiff2s_parts[xSize]; - XFLOAT wdiff2s_XA [xSize]; - XFLOAT wdiff2s_AA [xSize]; - - for(unsigned long bid=0; bid projector.maxR) { - if (iy >= ySize - projector.maxR) - y = iy - ySize; - else { - // handle special case for one pixel - xstart = projector.maxR; - xend = xstart + 1; - } - } - - for(int x = xstart; x < xend; x++) { - img_real[x] = g_img_real[pixel + x]; - } - - for(int x = xstart; x < xend; x++) { - img_imag[x] = g_img_imag[pixel + x]; - } - - if (REFCTF) { - for(int x = xstart; x < xend; x++) - ctfs[x] = g_ctfs[pixel + x]; - } - - for(int x = xstart; x < xend; x++) { - wdiff2s_parts[x] = g_wdiff2s_parts[pixel + x]; - } - - for(int x = xstart; x < xend; x++) { - wdiff2s_XA[x] = g_wdiff2s_XA[pixel + x]; - } - - for(int x = xstart; x < xend; x++) { - wdiff2s_AA[x] = g_wdiff2s_AA[pixel + x]; - } - - #pragma omp simd - for(int x = xstart; x < xend; x++) { - if(REF3D) - projector.project3Dmodel(x, y, e0, e1, e3, e4, e6, e7, - ref_real[x], ref_imag[x]); - else - projector.project2Dmodel(x, y, e0, e1, e3, e4, - ref_real[x], ref_imag[x]); - if (REFCTF) - { - ref_real[x] *= ctfs[x]; - ref_imag[x] *= ctfs[x]; - } - else { - ref_real[x] *= part_scale; - ref_imag[x] *= part_scale; - } - } - - for (unsigned long itrans = 0; itrans < trans_num; itrans++) { - XFLOAT weight = g_weights[bid * trans_num + itrans]; - if (weight < significant_weight) - continue; - - weight *= weight_norm_inverse; - XFLOAT trans_cos_y, trans_sin_y; - if ( y < 0) { - trans_cos_y = cos_y[itrans][-y]; - trans_sin_y = -sin_y[itrans][-y]; - } - else { - trans_cos_y = cos_y[itrans][y]; - trans_sin_y = sin_y[itrans][y]; - } - - XFLOAT *trans_cos_x = &cos_x[itrans][0]; - XFLOAT *trans_sin_x = &sin_x[itrans][0]; - -#pragma omp simd - for(int x = xstart; x < xend; x++) { - - XFLOAT ss = trans_sin_x[x] * trans_cos_y + trans_cos_x[x] * trans_sin_y; - XFLOAT cc = trans_cos_x[x] * trans_cos_y - trans_sin_x[x] * trans_sin_y; - - XFLOAT trans_real = cc * img_real[x] - ss * img_imag[x]; - XFLOAT trans_imag = cc * img_imag[x] + ss * img_real[x]; - - /* - XFLOAT trans_real, trans_imag; - translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], - img_real[x], img_imag[x], trans_real, trans_imag); - - where translatePixel is: - int x, int y, XFLOAT tx, XFLOAT ty, - XFLOAT &real, XFLOAT &imag, XFLOAT &tReal, XFLOAT &tImag - sincosf( x * tx + y * ty , &s, &c ); - tReal = c * real - s * imag; - tImag = c * imag + s * real; - */ - - XFLOAT diff_real = ref_real[x] - trans_real; - XFLOAT diff_imag = ref_imag[x] - trans_imag; - - wdiff2s_parts[x] += weight * (diff_real * diff_real + diff_imag * diff_imag); - wdiff2s_XA [x] += weight * (ref_real[x] * trans_real + ref_imag[x] * trans_imag); - wdiff2s_AA [x] += weight * (ref_real[x] * ref_real[x] + ref_imag[x] * ref_imag[x] ); - } - } // for itrans - - // Update the globals once - for(int x = xstart; x < xend; x++) { - g_wdiff2s_parts[pixel + x] = wdiff2s_parts[x]; - } - for(int x = xstart; x < xend; x++) { - g_wdiff2s_XA [pixel + x] = wdiff2s_XA[x]; - } - for(int x = xstart; x < xend; x++) { - g_wdiff2s_AA [pixel + x] = wdiff2s_AA[x]; - } - - pixel += (unsigned long)xSize; - } // y direction - } // bid -} - -<<<<<<< HEAD -template -#ifndef __INTEL_COMPILER -__attribute__((always_inline)) -inline -#endif -======= -template ->>>>>>> intel-opt -void wavg_3D( - XFLOAT * RESTRICT g_eulers, - AccProjectorKernel &projector, - unsigned long image_size, - unsigned long orientation_num, -#ifdef DEBUG_CUDA - XFLOAT * RESTRICT _g_img_real, -#else - XFLOAT * RESTRICT g_img_real, -#endif - XFLOAT * RESTRICT g_img_imag, - XFLOAT * RESTRICT g_trans_x, - XFLOAT * RESTRICT g_trans_y, - XFLOAT * RESTRICT g_trans_z, - XFLOAT * RESTRICT g_weights, - XFLOAT * RESTRICT g_ctfs, - XFLOAT * RESTRICT g_wdiff2s_parts, - XFLOAT * RESTRICT g_wdiff2s_AA, - XFLOAT * RESTRICT g_wdiff2s_XA, - unsigned long trans_num, - XFLOAT weight_norm, - XFLOAT significant_weight, - XFLOAT part_scale) -{ -#ifdef DEBUG_CUDA - checkedArray g_img_real; - g_img_real.initCheckedArray(_g_img_real); -#endif - // pre-compute sin and cos for x and y direction - int xSize = projector.imgX; - int ySize = projector.imgY; - int zSize = projector.imgZ; - XFLOAT sin_x[trans_num][xSize], cos_x[trans_num][xSize]; - XFLOAT sin_y[trans_num][ySize], cos_y[trans_num][ySize]; - XFLOAT sin_z[trans_num][zSize], cos_z[trans_num][zSize]; - - computeSincosLookupTable3D(trans_num, g_trans_x, g_trans_y, g_trans_z, - xSize, ySize, zSize, - &sin_x[0][0], &cos_x[0][0], - &sin_y[0][0], &cos_y[0][0], - &sin_z[0][0], &cos_z[0][0]); - - // Set up other arrays - XFLOAT ref_real[xSize], ref_imag[xSize]; - XFLOAT img_real[xSize], img_imag[xSize]; - - for(unsigned long bid=0; bid projector.maxR) - { - if (z >= zSize - projector.maxR) - z = z - zSize; - else { - xstart_z = projector.maxR; - xend_z = xstart_z + 1; - } - } - - for(int iy = 0; iy < ySize; iy++) { - int xstart_y = xstart_z, xend_y = xend_z; - int y = iy; - if (iy > projector.maxR) { - if (iy >= ySize - projector.maxR) - y = iy - ySize; - else { - xstart_y = projector.maxR; - xend_y = xstart_y + 1; - } - } - - #pragma omp simd - for(int x = xstart_y; x < xend_y; x++) { - projector.project3Dmodel(x, y, z, e0, e1, e2, e3, e4, e5, e6, e7, e8, - ref_real[x], ref_imag[x]); - if (REFCTF) - { - ref_real[x] *= g_ctfs[pixel + x]; - ref_imag[x] *= g_ctfs[pixel + x]; - } - else { - ref_real[x] *= part_scale; - ref_imag[x] *= part_scale; - } - - img_real[x] = g_img_real[pixel + x]; - img_imag[x] = g_img_imag[pixel + x]; - } - - for (unsigned long itrans = 0; itrans < trans_num; itrans++) { - XFLOAT weight = g_weights[bid * trans_num + itrans]; - if (weight < significant_weight) - continue; - - weight *= weight_norm_inverse; - XFLOAT trans_cos_z, trans_sin_z; - if ( z < 0) { - trans_cos_z = cos_z[itrans][-z]; - trans_sin_z = -sin_z[itrans][-z]; - } - else { - trans_cos_z = cos_z[itrans][z]; - trans_sin_z = sin_z[itrans][z]; - } - - XFLOAT trans_cos_y, trans_sin_y; - if ( y < 0) { - trans_cos_y = cos_y[itrans][-y]; - trans_sin_y = -sin_y[itrans][-y]; - } - else { - trans_cos_y = cos_y[itrans][y]; - trans_sin_y = sin_y[itrans][y]; - } - - XFLOAT *trans_cos_x = &cos_x[itrans][0]; - XFLOAT *trans_sin_x = &sin_x[itrans][0]; - - for(int x = xstart_y; x < xend_y; x++) { - // TODO check the math - XFLOAT s = trans_sin_x[x] * trans_cos_y + trans_cos_x[x] * trans_sin_y; - XFLOAT c = trans_cos_x[x] * trans_cos_y - trans_sin_x[x] * trans_sin_y; - - XFLOAT ss = s * trans_cos_z + c * trans_sin_z; - XFLOAT cc = c * trans_cos_z - s * trans_sin_z; - - XFLOAT trans_real = cc * img_real[x] - ss * img_imag[x]; - XFLOAT trans_imag = cc * img_imag[x] + ss * img_real[x]; - /* - XFLOAT trans_real, trans_imag; - translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], - img_real[x], img_imag[x], trans_real, trans_imag); - - where translatePixel is: - sincosf( x * tx + y * ty , &s, &c ); - tReal = c * real - s * imag; - tImag = c * imag + s * real; - */ - XFLOAT diff_real = ref_real[x] - trans_real; - XFLOAT diff_imag = ref_imag[x] - trans_imag; - - g_wdiff2s_parts[pixel + x] += weight * (diff_real * diff_real + diff_imag * diff_imag); - g_wdiff2s_XA [pixel + x] += weight * (ref_real[x] * trans_real + ref_imag[x] * trans_imag); - g_wdiff2s_AA [pixel + x] += weight * (ref_real[x] * ref_real[x] + ref_imag[x] * ref_imag[x] ); - } - } // for itrans - - pixel += (unsigned long)xSize; - } // y direction - } // z direction - } // bid -} - -} // end of namespace CpuKernels - -#endif /* WAVG_KERNEL_H_ */ diff --git a/src/acc/cpu/cpu_ml_optimiser.cpp b/src/acc/cpu/cpu_ml_optimiser.cpp index 7e2265c08..8c59f1281 100644 --- a/src/acc/cpu/cpu_ml_optimiser.cpp +++ b/src/acc/cpu/cpu_ml_optimiser.cpp @@ -24,7 +24,7 @@ #ifdef ALTCPU // Make sure we build for CPU -#include "src/acc/cpu/cuda_stubs.h" +#include "src/acc/cpu/device_stubs.h" #include "src/ml_optimiser.h" @@ -120,49 +120,53 @@ void MlDataBundle::setup(MlOptimiser *baseMLO) coarseProjectionPlans.resize(nr_classes); //Can we pre-generate projector plan and corresponding euler matrices for all particles - if (!baseMLO->do_skip_align && !baseMLO->do_skip_rotate && !baseMLO->do_auto_refine && baseMLO->mymodel.orientational_prior_mode == NOPRIOR) - for (int iclass = 0; iclass < nr_classes; iclass++) + if (baseMLO->do_skip_align || baseMLO->do_skip_rotate || baseMLO->do_auto_refine || baseMLO->mymodel.orientational_prior_mode != NOPRIOR || baseMLO->mydata.is_tomo) + generateProjectionPlanOnTheFly = true; + else + generateProjectionPlanOnTheFly = false; + + for (int iclass = 0; iclass < nr_classes; iclass++) + { + //If doing predefined projector plan at all and is this class significant + if (!generateProjectionPlanOnTheFly && baseMLO->mymodel.pdf_class[iclass] > 0.) { - //If doing predefined projector plan at all and is this class significant - if (baseMLO->mymodel.pdf_class[iclass] > 0.) - { - std::vector exp_pointer_dir_nonzeroprior; - std::vector exp_pointer_psi_nonzeroprior; - std::vector exp_directions_prior; - std::vector exp_psi_prior; - - long unsigned itrans_max = baseMLO->sampling.NrTranslationalSamplings() - 1; - long unsigned nr_idir = baseMLO->sampling.NrDirections(0, &exp_pointer_dir_nonzeroprior); - long unsigned nr_ipsi = baseMLO->sampling.NrPsiSamplings(0, &exp_pointer_psi_nonzeroprior ); - - coarseProjectionPlans[iclass].setup( - baseMLO->sampling, - exp_directions_prior, - exp_psi_prior, - exp_pointer_dir_nonzeroprior, - exp_pointer_psi_nonzeroprior, - NULL, //Mcoarse_significant - baseMLO->mymodel.pdf_class, - baseMLO->mymodel.pdf_direction, - nr_idir, - nr_ipsi, - 0, //idir_min - nr_idir - 1, //idir_max - 0, //ipsi_min - nr_ipsi - 1, //ipsi_max - 0, //itrans_min - itrans_max, - 0, //current_oversampling - 1, //nr_oversampled_rot - iclass, - true, //coarse - !IS_NOT_INV, - baseMLO->do_skip_align, - baseMLO->do_skip_rotate, - baseMLO->mymodel.orientational_prior_mode - ); - } + std::vector exp_pointer_dir_nonzeroprior; + std::vector exp_pointer_psi_nonzeroprior; + std::vector exp_directions_prior; + std::vector exp_psi_prior; + + long unsigned itrans_max = baseMLO->sampling.NrTranslationalSamplings() - 1; + long unsigned nr_idir = baseMLO->sampling.NrDirections(0, &exp_pointer_dir_nonzeroprior); + long unsigned nr_ipsi = baseMLO->sampling.NrPsiSamplings(0, &exp_pointer_psi_nonzeroprior ); + + coarseProjectionPlans[iclass].setup( + baseMLO->sampling, + exp_directions_prior, + exp_psi_prior, + exp_pointer_dir_nonzeroprior, + exp_pointer_psi_nonzeroprior, + NULL, //Mcoarse_significant + baseMLO->mymodel.pdf_class, + baseMLO->mymodel.pdf_direction, + nr_idir, + nr_ipsi, + 0, //idir_min + nr_idir - 1, //idir_max + 0, //ipsi_min + nr_ipsi - 1, //ipsi_max + 0, //itrans_min + itrans_max, + 0, //current_oversampling + 1, //nr_oversampled_rot + iclass, + true, //coarse + !IS_NOT_INV, + baseMLO->do_skip_align, + baseMLO->do_skip_rotate, + baseMLO->mymodel.orientational_prior_mode + ); } + } }; diff --git a/src/acc/cpu/cpu_ml_optimiser.h b/src/acc/cpu/cpu_ml_optimiser.h index 132f97784..b2f8eb470 100644 --- a/src/acc/cpu/cpu_ml_optimiser.h +++ b/src/acc/cpu/cpu_ml_optimiser.h @@ -23,12 +23,21 @@ class MlDataBundle { public: + //The CPU accelerated projector set std::vector< AccProjector > projectors; + + //The CPU accelerated back-projector set std::vector< AccBackprojector > backprojectors; + + //Used for precalculations of projection setup + bool generateProjectionPlanOnTheFly; std::vector< AccProjectorPlan > coarseProjectionPlans; void setup(MlOptimiser *baseMLO); + MlDataBundle() : generateProjectionPlanOnTheFly {false} + {} + ~MlDataBundle() { projectors.clear(); @@ -36,6 +45,7 @@ class MlDataBundle } }; + class MlOptimiserCpu { public: @@ -49,6 +59,7 @@ class MlOptimiserCpu bool refIs3D; bool dataIs3D; + bool shiftsIs3D; int thread_id; @@ -69,75 +80,28 @@ class MlOptimiserCpu transformer2(baseMLOptimiser->mymodel.data_dim), refIs3D(baseMLO->mymodel.ref_dim == 3), dataIs3D(baseMLO->mymodel.data_dim == 3), + shiftsIs3D(baseMLO->mymodel.data_dim == 3 || baseMLO->mydata.is_tomo), #ifdef TIMING_FILES timer(timing_fnm), #endif - generateProjectionPlanOnTheFly(false), + generateProjectionPlanOnTheFly(b->generateProjectionPlanOnTheFly), thread_id(-1), bundle(b), classStreams(0) { - //Can we pre-generate projector plan and corresponding euler matrices for all particles - if (baseMLO->do_skip_align || baseMLO->do_skip_rotate || baseMLO->do_auto_refine || baseMLO->mymodel.orientational_prior_mode != NOPRIOR) - generateProjectionPlanOnTheFly = true; - else - generateProjectionPlanOnTheFly = false; }; void resetData(); void expectationOneParticle(unsigned long my_ori_particle, int thread_id); - CudaCustomAllocator *getAllocator() + void *getAllocator() { - return ((CudaCustomAllocator *)0); + return nullptr; }; ~MlOptimiserCpu() {} }; - -/* -class ApplyFoo { - float *const my_a; -public: - void operator()( const blocked_range& r ) const { - float *a = my_a; - for( size_t i=r.begin(); i!=r.end(); ++i ) - Foo(a[i]); - } - ApplyFoo( float a[] ) : - my_a(a) - {} -}; - -// Called as follows: -// tbb::parallel_for(tbb::blocked_range(my_first_ori_particle, my_last_ori_particle+1), -// cpuThreadExpectationSomeParticles(this)); -class cpuThreadExpectationSomeParticles { - MlOptimiser *const my_optimiser; -public: - void operator()( const tbb::blocked_range& r ) const { - MlOptimiser *mloptimiser = my_optimiser; - MlOptimiser::CpuOptimiserType::reference ref = mloptimiser->tbbCpuOptimiser.local(); - MlOptimiserCpu *cpuOptimiser = (MlOptimiserCpu *)ref; - if(cpuOptimiser == NULL) - { - cpuOptimiser = new MlOptimiserCpu(mloptimiser, "cpu_optimiser"); - cpuOptimiser->resetData(); - cpuOptimiser->setupFixedSizedObjects(); - cpuOptimiser->setupTunableSizedObjects(); - ref = cpuOptimiser; - } - for( size_t i=r.begin(); i!=r.end(); ++i ) - { - cpuOptimiser->expectationOneParticle(i); - } - } - cpuThreadExpectationSomeParticles( MlOptimiser *optimiser ) : - my_optimiser(optimiser) - {} -}; - */ #endif diff --git a/src/acc/cpu/cpu_projector.cpp b/src/acc/cpu/cpu_projector.cpp index 20bf7480e..c145bd604 100644 --- a/src/acc/cpu/cpu_projector.cpp +++ b/src/acc/cpu/cpu_projector.cpp @@ -1,7 +1,7 @@ #include #include -#include "src/acc/cpu/cuda_stubs.h" +#include "src/acc/cpu/device_stubs.h" #include "src/acc/acc_ptr.h" #include "src/acc/acc_projector.h" diff --git a/src/acc/cpu/cpu_projector_plan.cpp b/src/acc/cpu/cpu_projector_plan.cpp index 017920c05..171b4d330 100644 --- a/src/acc/cpu/cpu_projector_plan.cpp +++ b/src/acc/cpu/cpu_projector_plan.cpp @@ -1,7 +1,7 @@ #ifdef ALTCPU // Make sure we build for CPU -#include "src/acc/cpu/cuda_stubs.h" +#include "src/acc/cpu/device_stubs.h" #include "src/acc/settings.h" #include "src/time.h" @@ -20,4 +20,4 @@ #include "src/acc/acc_projector_plan_impl.h" -#endif \ No newline at end of file +#endif diff --git a/src/acc/cpu/cpu_settings.h b/src/acc/cpu/cpu_settings.h index 1f89fe7ff..517cc026d 100644 --- a/src/acc/cpu/cpu_settings.h +++ b/src/acc/cpu/cpu_settings.h @@ -11,32 +11,44 @@ // COARSE DIFF ------------------------- -#define D2C_BLOCK_SIZE_2D 256 -#define D2C_EULERS_PER_BLOCK_2D 16 - -#define D2C_BLOCK_SIZE_REF3D 256 -#define D2C_EULERS_PER_BLOCK_REF3D 16 - -#define D2C_BLOCK_SIZE_DATA3D 64 -#define D2C_EULERS_PER_BLOCK_DATA3D 32 +#define PREFETCH_FRACTION_3D 4 //4 +#define PREFETCH_FRACTION_2D 2 //2 + +#define D2C_BLOCK_SIZE_2D 256 //256 +#define D2C_EULERS_PER_BLOCK_2D 16 //16 +#define D2C_BLOCK_SIZE_REF3D 256 //256 +#define D2C_EULERS_PER_BLOCK_REF3D 16 //16 +#define D2C_BLOCK_SIZE_DATA3D 64 //64 +#define D2C_EULERS_PER_BLOCK_DATA3D 32 //32 + +#define D2C_CC_BLOCK_SIZE_2D 256 //256 +#define D2C_CC_EULERS_PER_BLOCK_2D 16 //16 +#define D2C_CC_BLOCK_SIZE_REF3D 256 //256 +#define D2C_CC_EULERS_PER_BLOCK_REF3D 16 //16 +#define D2C_CC_BLOCK_SIZE_DATA3D 64 //64 +#define D2C_CC_EULERS_PER_BLOCK_DATA3D 32 //32 // ------------------------------------- // FINE DIFF --------------------------- -#define D2F_BLOCK_SIZE_2D 8 -#define D2F_CHUNK_2D 7 +#define D2F_BLOCK_SIZE_2D 8 //8 +#define D2F_CC_BLOCK_SIZE_2D 8 //8 +#define D2F_CHUNK_2D 7 //7 -#define D2F_BLOCK_SIZE_REF3D 8 -#define D2F_CHUNK_REF3D 7 +#define D2F_BLOCK_SIZE_REF3D 8 //8 +#define D2F_CC_BLOCK_SIZE_REF3D 8 //8 +#define D2F_CHUNK_REF3D 7 //7 -#define D2F_BLOCK_SIZE_DATA3D 8 -#define D2F_CHUNK_DATA3D 4 +#define D2F_BLOCK_SIZE_DATA3D 8 //8 +#define D2F_CC_BLOCK_SIZE_DATA3D 8 //8 +#define D2F_CHUNK_DATA3D 4 //4 // ------------------------------------- // WAVG -------------------------------- -#define WAVG_BLOCK_SIZE_DATA3D 512 -#define WAVG_BLOCK_SIZE 256 +#define WAVG_BLOCK_SIZE_DATA3D 512 //512 +#define WAVG_BLOCK_SIZE_REF3D 512 //512 +#define WAVG_BLOCK_SIZE_2D 256 //256 // ------------------------------------- @@ -59,9 +71,9 @@ #define BACKPROJECTION4_BLOCK_SIZE 64 #define BACKPROJECTION4_GROUP_SIZE 16 #define BACKPROJECTION4_PREFETCH_COUNT 3 -#define BP_2D_BLOCK_SIZE 128 -#define BP_REF3D_BLOCK_SIZE 128 -#define BP_DATA3D_BLOCK_SIZE 640 +#define BP_2D_BLOCK_SIZE 128 //128 +#define BP_REF3D_BLOCK_SIZE 128 //128 +#define BP_DATA3D_BLOCK_SIZE 512 //640 #define REF_GROUP_SIZE 3 // -- Number of references to be treated per block -- // This applies to wavg and reduces global memory diff --git a/src/acc/cpu/cuda_stubs.h b/src/acc/cpu/cuda_stubs.h deleted file mode 100644 index 79a50b2e5..000000000 --- a/src/acc/cpu/cuda_stubs.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef CUDA_STUBS_H -#define CUDA_STUBS_H - -#undef CUDA -typedef float cudaStream_t; -typedef double CudaCustomAllocator; -typedef int dim3; -#define cudaStreamPerThread 0 -#define CUSTOM_ALLOCATOR_REGION_NAME( name ) //Do nothing -#define LAUNCH_PRIVATE_ERROR(func, status) -#define LAUNCH_HANDLE_ERROR( err ) -#define DEBUG_HANDLE_ERROR( err ) -#define HANDLE_ERROR( err ) -#endif \ No newline at end of file diff --git a/src/acc/cpu/device_stubs.h b/src/acc/cpu/device_stubs.h new file mode 100644 index 000000000..2f1e1a826 --- /dev/null +++ b/src/acc/cpu/device_stubs.h @@ -0,0 +1,27 @@ +#ifndef DEVICE_STUBS_H +#define DEVICE_STUBS_H + +#undef CUDA +#undef _CUDA_ENABLED +#undef HIP +#undef _HIP_ENABLED + +using dim3 = int; +using deviceStream_t = float; +using deviceCustomAllocator = double; + +using cudaStream_t = float; +using CudaCustomAllocator = double; +#define cudaStreamPerThread 0 + +using hipStream_t = float; +using HipCustomAllocator = double; +#define hipStreamPerThread 0 + +#define CUSTOM_ALLOCATOR_REGION_NAME( name ) //Do nothing +#define LAUNCH_PRIVATE_ERROR(func, status) +#define LAUNCH_HANDLE_ERROR( err ) +#define DEBUG_HANDLE_ERROR( err ) +#define HANDLE_ERROR( err ) + +#endif diff --git a/src/acc/cuda/cuda_kernels/diff2.cuh b/src/acc/cuda/cuda_kernels/diff2.cuh index 8a88565c0..f2e80206a 100644 --- a/src/acc/cuda/cuda_kernels/diff2.cuh +++ b/src/acc/cuda/cuda_kernels/diff2.cuh @@ -324,11 +324,8 @@ __global__ void cuda_kernel_diff2_fine( if (tid < trans_num) { s_outs[tid]=s[tid*block_sz]+sum_init; - } - if (tid < trans_num) - { iy=d_job_idx[bid]+tid; - g_diff2s[iy] = s_outs[tid]; + g_diff2s[iy] += s_outs[tid]; } } } @@ -454,9 +451,12 @@ __global__ void cuda_kernel_diff2_CC_coarse( __syncthreads(); } #ifdef ACC_DOUBLE_PRECISION - g_diff2s[iorient * translation_num + itrans] = - ( s_weight[0] / sqrt(s_norm[0])); + cuda_atomic_add(&g_diff2s[iorient * translation_num + itrans], - ( s_weight[0] / (block_sz * sqrt(s_norm[0]))) ); + //g_diff2s[iorient * translation_num + itrans] += - ( s_weight[0] / sqrt(s_norm[0])); #else - g_diff2s[iorient * translation_num + itrans] = - ( s_weight[0] / sqrtf(s_norm[0])); + cuda_atomic_add(&g_diff2s[iorient * translation_num + itrans], - ( s_weight[0] / (block_sz * sqrtf(s_norm[0]))) ); + //g_diff2s[iorient * translation_num + itrans] += - ( s_weight[0] / sqrtf(s_norm[0])); + #endif } @@ -606,7 +606,7 @@ __global__ void cuda_kernel_diff2_CC_fine( if (tid < trans_num) { iy=d_job_idx[bid]+tid; - g_diff2s[iy] = s_outs[tid]; + g_diff2s[iy] += s_outs[tid]; } } } diff --git a/src/acc/cuda/cuda_kernels/helper.cu b/src/acc/cuda/cuda_kernels/helper.cu index a33187dc9..ff449dae7 100644 --- a/src/acc/cuda/cuda_kernels/helper.cu +++ b/src/acc/cuda/cuda_kernels/helper.cu @@ -961,8 +961,8 @@ __global__ void cuda_kernel_allweights_to_mweights( ) { size_t idx = blockIdx.x * block_size + threadIdx.x; - if (idx < orientation_num*translation_num) - d_mweights[d_iorient[idx/translation_num] * translation_num + idx%translation_num] = + if (idx < orientation_num*translation_num) + d_mweights[d_iorient[idx/translation_num] * translation_num + idx%translation_num] = d_allweights[idx/translation_num * translation_num + idx%translation_num]; // TODO - isn't this just d_allweights[idx + idx%translation_num]? Really? } diff --git a/src/acc/cuda/cuda_kernels/helper.cuh b/src/acc/cuda/cuda_kernels/helper.cuh index 5cf81e07f..9651d342d 100644 --- a/src/acc/cuda/cuda_kernels/helper.cuh +++ b/src/acc/cuda/cuda_kernels/helper.cuh @@ -466,6 +466,21 @@ __global__ void cuda_kernel_multi( A[pixel] = A[pixel]*S; } +/* + * In place add scalar S to scalar array A + * + * A[i] = A[i]*S + */ +template +__global__ void cuda_kernel_add( + T *A, + T S, + int size) +{ + int pixel = threadIdx.x + blockIdx.x * blockDim.x; + if (pixel < size) + A[pixel] += S; +} } /* diff --git a/src/acc/cuda/cuda_ml_optimiser.cu b/src/acc/cuda/cuda_ml_optimiser.cu index 2f9e9ee7f..89ac0cef3 100644 --- a/src/acc/cuda/cuda_ml_optimiser.cu +++ b/src/acc/cuda/cuda_ml_optimiser.cu @@ -95,11 +95,14 @@ void MlDeviceBundle::setupFixedSizedObjects() HANDLE_ERROR(cudaSetDevice(device_id)); //Can we pre-generate projector plan and corresponding euler matrices for all particles - if (baseMLO->do_skip_align || baseMLO->do_skip_rotate || baseMLO->do_auto_refine || baseMLO->mymodel.orientational_prior_mode != NOPRIOR) + if (baseMLO->do_skip_align || baseMLO->do_skip_rotate || baseMLO->do_auto_refine || baseMLO->mymodel.orientational_prior_mode != NOPRIOR || baseMLO->mydata.is_tomo) generateProjectionPlanOnTheFly = true; else generateProjectionPlanOnTheFly = false; + //std::cerr << "DEBUG: setting generateProjectionPlanOnTheFly = true;" << std::endl; + //generateProjectionPlanOnTheFly = true; + unsigned nr_proj = baseMLO->mymodel.PPref.size(); unsigned nr_bproj = baseMLO->wsum_model.BPref.size(); diff --git a/src/acc/cuda/cuda_ml_optimiser.h b/src/acc/cuda/cuda_ml_optimiser.h index e9f91329b..2a44a2f8c 100644 --- a/src/acc/cuda/cuda_ml_optimiser.h +++ b/src/acc/cuda/cuda_ml_optimiser.h @@ -91,6 +91,7 @@ class MlOptimiserCuda bool refIs3D; bool dataIs3D; + bool shiftsIs3D; int device_id; @@ -113,6 +114,7 @@ class MlOptimiserCuda transformer2(cudaStreamPerThread, bundle->allocator, baseMLOptimiser->mymodel.data_dim), refIs3D(baseMLO->mymodel.ref_dim == 3), dataIs3D(baseMLO->mymodel.data_dim == 3), + shiftsIs3D(baseMLO->mymodel.data_dim == 3 || baseMLO->mydata.is_tomo), bundle(bundle), device_id(bundle->device_id), #ifdef TIMING_FILES diff --git a/src/acc/cuda/custom_allocator.cuh b/src/acc/cuda/custom_allocator.cuh index 773276601..c8f49dff6 100644 --- a/src/acc/cuda/custom_allocator.cuh +++ b/src/acc/cuda/custom_allocator.cuh @@ -20,6 +20,7 @@ #include "src/parallel.h" #ifdef CUSTOM_ALLOCATOR_MEMGUARD +#include "src/acc/cuda/shortcuts.cuh" #include #include #endif @@ -535,7 +536,7 @@ public: _printState(); fflush(stdout); - CRITICAL(ERRCUDACAOOM); + CRITICAL(ERRGPUCAOOM); } } diff --git a/src/acc/hip/custom_allocator.h b/src/acc/hip/custom_allocator.h new file mode 100644 index 000000000..7cc954407 --- /dev/null +++ b/src/acc/hip/custom_allocator.h @@ -0,0 +1,662 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_CUSTOM_ALLOCATOR_H_ +#define HIP_CUSTOM_ALLOCATOR_H_ +// This is where custom allocator should be. Commented out for now, to avoid double declaration. + +#ifdef _HIP_ENABLED +#include "src/acc/hip/hip_settings.h" +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "src/macros.h" +#include "src/error.h" +#include "src/parallel.h" + +#ifdef CUSTOM_ALLOCATOR_MEMGUARD +#include +#include +#endif + +#ifdef DUMP_CUSTOM_ALLOCATOR_ACTIVITY +#define CUSTOM_ALLOCATOR_REGION_NAME( name ) (fprintf(stderr, "\n%s", name)) +#else +#define CUSTOM_ALLOCATOR_REGION_NAME( name ) //Do nothing +#endif + + +class HipCustomAllocator +{ + + typedef unsigned char BYTE; + + const static unsigned GUARD_SIZE = 4; + const static BYTE GUARD_VALUE = 145; + const static int ALLOC_RETRY = 500; + +public: + + class Alloc + { + friend class HipCustomAllocator; + + private: + Alloc *prev, *next; + BYTE *ptr; + size_t size; + bool free; + hipEvent_t readyEvent; //Event record used for auto free + bool freeWhenReady; + + +#ifdef CUSTOM_ALLOCATOR_MEMGUARD + BYTE *guardPtr; + void *backtrace[20]; + size_t backtraceSize; +#endif + + Alloc(): + prev(NULL), next(NULL), + ptr(NULL), + size(0), + free(0), + readyEvent(0), + freeWhenReady(false) + {} + + ~Alloc() + { + prev = NULL; + next = NULL; + ptr = NULL; + + if (readyEvent != 0) + DEBUG_HANDLE_ERROR(hipEventDestroy(readyEvent)); + } + + public: + inline + BYTE *getPtr() { return ptr; } + + inline + size_t getSize() { return size; } + + inline + bool isFree() { return free; } + + inline + hipEvent_t getReadyEvent() { return readyEvent; } + + inline + void markReadyEvent(hipStream_t stream = 0) + { + //TODO add a debug warning if event already set + DEBUG_HANDLE_ERROR(hipEventCreate(&readyEvent)); + DEBUG_HANDLE_ERROR(hipEventRecord(readyEvent, stream)); + } + + inline + void doFreeWhenReady() { freeWhenReady = true; } + }; + +private: + + Alloc *first; + size_t totalSize; + size_t alignmentSize; + + bool cache; + + omp_lock_t mutex; + + + //Look for the first suited space + Alloc *_getFirstSuitedFree(size_t size) + { + Alloc *a = first; + //If not the last and too small or not free go to next allocation region + while (a != NULL && ( a->size <= size || ! a->free ) ) + a = a->next; + + return a; + } + + //Free allocs with recorded ready events + bool _syncReadyEvents() + { + bool somethingReady(false); + Alloc *a = first; + + while (a != NULL) + { + if (! a->free && a->freeWhenReady && a->readyEvent != 0) + { + DEBUG_HANDLE_ERROR(hipEventSynchronize(a->readyEvent)); + somethingReady = true; + } + + a = a->next; + } + + return somethingReady; + } + + //Free allocs with recorded ready events + bool _freeReadyAllocs() + { + bool somethingFreed(false); + Alloc *next = first; + Alloc *curr; + + while (next != NULL) + { + curr = next; + next = curr->next; + + if (! curr->free && curr->freeWhenReady && curr->readyEvent != 0) + { + hipError_t e = hipEventQuery(curr->readyEvent); + + if (e == hipSuccess) + { + _free(curr); + next = first; //List modified, restart + somethingFreed = true; + } + else if (e != hipErrorNotReady) + { + _printState(); + HandleError( e, __FILE__, __LINE__ ); + } + } + } + return somethingFreed; + } + + size_t _getTotalFreeSpace() + { + if (cache) + { + size_t total = 0; + Alloc *a = first; + + while (a != NULL) + { + if (a->free) + total += a->size; + a = a->next; + } + + return total; + } + else + { + size_t free, total; + DEBUG_HANDLE_ERROR(hipMemGetInfo( &free, &total )); + return free; + } + } + + size_t _getTotalUsedSpace() + { + size_t total = 0; + Alloc *a = first; + + while (a != NULL) + { + if (!a->free) + total += a->size; + a = a->next; + } + + return total; + } + + size_t _getNumberOfAllocs() + { + + size_t total = 0; + Alloc *a = first; + + while (a != NULL) + { + if (!a->free) + total ++; + a = a->next; + } + + return total; + } + + size_t _getLargestContinuousFreeSpace() + { + if (cache) + { + size_t largest = 0; + Alloc *a = first; + + while (a != NULL) + { + if (a->free && a->size > largest) + largest = a->size; + a = a->next; + } + + return largest; + } + else + return _getTotalFreeSpace(); + } + + void _printState() + { + size_t total = 0; + Alloc *a = first; + + while (a != NULL) + { + total += a->size; + if (a->free) + printf("[%luB] ", (unsigned long) a->size); + else if (a->freeWhenReady) + printf("<%luB> ", (unsigned long) a->size); + else + printf("(%luB) ", (unsigned long) a->size); + + a = a->next; + } + + printf("= %luB\n", (unsigned long) total); + fflush(stdout); + } + + void _free(Alloc* a) + { +// printf("free: %u ", a->size); +// _printState(); + + +#ifdef CUSTOM_ALLOCATOR_MEMGUARD + size_t guardCount = a->size - (a->guardPtr - a->ptr); + BYTE *guards = new BYTE[guardCount]; + hipStream_t stream = 0; + HipShortcuts::cpyDeviceToHost( a->guardPtr, guards, guardCount, stream); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(stream)); + for (int i = 0; i < guardCount; i ++) + if (guards[i] != GUARD_VALUE) + { + fprintf (stderr, "ERROR: CORRUPTED BYTE GUARDS DETECTED\n"); + + char ** messages = backtrace_symbols(a->backtrace, a->backtraceSize); + + // skip first stack frame (points here) + for (int i = 1; i < a->backtraceSize && messages != NULL; ++i) + { + char *mangled_name = 0, *offset_begin = 0, *offset_end = 0; + + // find parantheses and +address offset surrounding mangled name + for (char *p = messages[i]; *p; ++p) + { + if (*p == '(') + { + mangled_name = p; + } + else if (*p == '+') + { + offset_begin = p; + } + else if (*p == ')') + { + offset_end = p; + break; + } + } + + // if the line could be processed, attempt to demangle the symbol + if (mangled_name && offset_begin && offset_end && + mangled_name < offset_begin) + { + *mangled_name++ = '\0'; + *offset_begin++ = '\0'; + *offset_end++ = '\0'; + + int status; + char * real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status); + + // if demangling is successful, output the demangled function name + if (status == 0) + { + std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " + << real_name << "+" << offset_begin << offset_end + << std::endl; + + } + // otherwise, output the mangled function name + else + { + std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " + << mangled_name << "+" << offset_begin << offset_end + << std::endl; + } +// free(real_name); + } + // otherwise, print the whole line + else + { + std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl; + } + } + std::cerr << std::endl; + +// free(messages); + + exit(EXIT_FAILURE); + } + delete[] guards; +#endif + + a->free = true; + + if (cache) + { + //Previous neighbor is free, concatenate + if ( a->prev != NULL && a->prev->free) + { + //Resize and set pointer + a->size += a->prev->size; + a->ptr = a->prev->ptr; + + //Fetch secondary neighbor + Alloc *ppL = a->prev->prev; + + //Remove primary neighbor + if (ppL == NULL) //If the previous is first in chain + first = a; + else + ppL->next = a; + + delete a->prev; + + //Attach secondary neighbor + a->prev = ppL; + } + + //Next neighbor is free, concatenate + if ( a->next != NULL && a->next->free) + { + //Resize and set pointer + a->size += a->next->size; + + //Fetch secondary neighbor + Alloc *nnL = a->next->next; + + //Remove primary neighbor + if (nnL != NULL) + nnL->prev = a; + delete a->next; + + //Attach secondary neighbor + a->next = nnL; + } + } + else + { + DEBUG_HANDLE_ERROR(hipFree( a->ptr )); + a->ptr = NULL; + + if ( a->prev != NULL) + a->prev->next = a->next; + else + first = a->next; //This is the first link + + if ( a->next != NULL) + a->next->prev = a->prev; + + delete a; + } + }; + + void _setup() + { + first = new Alloc(); + first->prev = NULL; + first->next = NULL; + first->size = totalSize; + first->free = true; + + if (totalSize > 0) + { + HANDLE_ERROR(hipMalloc( (void**) &(first->ptr), totalSize)); + cache = true; + } + else + cache = false; + } + + void _clear() + { + if (first->ptr != NULL) + DEBUG_HANDLE_ERROR(hipFree( first->ptr )); + + first->ptr = NULL; + + Alloc *a = first, *nL; + + while (a != NULL) + { + nL = a->next; + delete a; + a = nL; + } + } + +public: + + HipCustomAllocator(size_t size, size_t alignmentSize): + totalSize(size), alignmentSize(alignmentSize), first(0), cache(true) + { + _setup(); + + omp_init_lock(&mutex); + } + + void resize(size_t size) + { + Lock ml(&mutex); + _clear(); + totalSize = size; + _setup(); + } + + + Alloc* alloc(size_t requestedSize) + { + Lock ml(&mutex); + + _freeReadyAllocs(); + +// printf("alloc: %u ", size); +// _printState(); + + size_t size = requestedSize; + +#ifdef CUSTOM_ALLOCATOR_MEMGUARD + //Ad byte-guards + size += alignmentSize * GUARD_SIZE; //Ad an integer multiple of alignment size as byte guard size +#endif + +#ifdef DUMP_CUSTOM_ALLOCATOR_ACTIVITY + fprintf(stderr, " %.4f", 100.*(float)size/(float)totalSize); +#endif + + Alloc *newAlloc(NULL); + + if (cache) + { + size = alignmentSize*ceilf( (float)size / (float)alignmentSize) ; //To prevent miss-aligned memory + + Alloc *curAlloc = _getFirstSuitedFree(size); + + //If out of memory + if (curAlloc == NULL) + { + #ifdef DEBUG_HIP + size_t spaceDiff = _getTotalFreeSpace(); + #endif + //Try to recover before throwing error + for (int i = 0; i <= ALLOC_RETRY; i ++) + { + if (_syncReadyEvents() && _freeReadyAllocs()) + { + curAlloc = _getFirstSuitedFree(size); //Is there space now? + if (curAlloc != NULL) + break; //Success + } + else + usleep(10000); // 10 ms, Order of magnitude of largest kernels + } + #ifdef DEBUG_HIP + spaceDiff = _getTotalFreeSpace() - spaceDiff; + printf("DEBUG_INFO: Out of memory handled by waiting for unfinished tasks, which freed %lu B.\n", spaceDiff); + #endif + + //Did we manage to recover? + if (curAlloc == NULL) + { + printf("ERROR: HipCustomAllocator out of memory\n [requestedSpace: %lu B]\n [largestContinuousFreeSpace: %lu B]\n [totalFreeSpace: %lu B]\n", + (unsigned long) size, (unsigned long) _getLargestContinuousFreeSpace(), (unsigned long) _getTotalFreeSpace()); + + _printState(); + + fflush(stdout); + CRITICAL(ERRGPUCAOOM); + } + } + + if (curAlloc->size == size) + { + curAlloc->free = false; + newAlloc = curAlloc; + } + else //Or curAlloc->size is smaller than size + { + //Setup new pointer + newAlloc = new Alloc(); + newAlloc->next = curAlloc; + newAlloc->ptr = curAlloc->ptr; + newAlloc->size = size; + newAlloc->free = false; + + //Modify old pointer + curAlloc->ptr = &(curAlloc->ptr[size]); + curAlloc->size -= size; + + //Insert new allocation region into chain + if(curAlloc->prev == NULL) //If the first allocation region + first = newAlloc; + else + curAlloc->prev->next = newAlloc; + newAlloc->prev = curAlloc->prev; + newAlloc->next = curAlloc; + curAlloc->prev = newAlloc; + } + } + else + { + newAlloc = new Alloc(); + newAlloc->size = size; + newAlloc->free = false; + DEBUG_HANDLE_ERROR(hipMalloc( (void**) &(newAlloc->ptr), size)); + + //Just add to start by replacing first + newAlloc->next = first; + first->prev = newAlloc; + first = newAlloc; + } + +#ifdef CUSTOM_ALLOCATOR_MEMGUARD + newAlloc->backtraceSize = backtrace(newAlloc->backtrace, 20); + newAlloc->guardPtr = newAlloc->ptr + requestedSize; + hipStream_t stream = 0; + HipShortcuts::memInit( newAlloc->guardPtr, GUARD_VALUE, size - requestedSize, stream); //TODO switch to specialized stream + DEBUG_HANDLE_ERROR(hipStreamSynchronize(stream)); +#endif + + return newAlloc; + }; + + ~HipCustomAllocator() + { + { + Lock ml(&mutex); + _clear(); + } + omp_destroy_lock(&mutex); + } + + //Thread-safe wrapper functions + + void free(Alloc* a) + { + Lock ml(&mutex); + _free(a); + } + + void syncReadyEvents() + { + Lock ml(&mutex); + _syncReadyEvents(); + } + + void freeReadyAllocs() + { + Lock ml(&mutex); + _freeReadyAllocs(); + } + + size_t getTotalFreeSpace() + { + Lock ml(&mutex); + size_t size = _getTotalFreeSpace(); + return size; + } + + size_t getTotalUsedSpace() + { + Lock ml(&mutex); + size_t size = _getTotalUsedSpace(); + return size; + } + + size_t getNumberOfAllocs() + { + Lock ml(&mutex); + size_t size = _getNumberOfAllocs(); + return size; + } + + size_t getLargestContinuousFreeSpace() + { + Lock ml(&mutex); + size_t size = _getLargestContinuousFreeSpace(); + return size; + } + + void printState() + { + Lock ml(&mutex); + _printState(); + } +}; +// + +#endif //HIP_CUSTOM_ALLOCATOR_H_ diff --git a/src/acc/hip/hip_autopicker.h b/src/acc/hip/hip_autopicker.h new file mode 100644 index 000000000..c4faf4385 --- /dev/null +++ b/src/acc/hip/hip_autopicker.h @@ -0,0 +1,102 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_AUTOPICKER_H_ +#define HIP_AUTOPICKER_H_ + +#include "src/mpi.h" +#include "src/autopicker.h" +#include "src/autopicker_mpi.h" +#include "src/projector.h" +#include "src/complex.h" +#include "src/image.h" + +#include "src/acc/hip/hip_mem_utils.h" +#include "src/acc/acc_projector.h" +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_fft.h" +#include "src/acc/hip/hip_benchmark_utils.h" + +#include + +#ifdef ACC_DOUBLE_PRECISION +#define XFLOAT double +#else +#define XFLOAT float +#endif + +class AutoPickerHip +{ +private: + + MpiNode *node; + +public: + + AutoPicker *basePckr; + + HipCustomAllocator *allocator; + HipFFT micTransformer; + HipFFT hipTransformer1; + HipFFT hipTransformer2; + + std::vector< AccProjector > projectors; + + //Class streams ( for concurrent scheduling of class-specific kernels) + std::vector< hipStream_t > classStreams; + + int device_id; + + bool have_warned_batching; + + //MlDeviceBundle *devBundle; + +#ifdef TIMING_FILES + relion_timer timer; +#endif + + AutoPickerHip(AutoPicker *basePicker, const char * timing_fnm); + AutoPickerHip(AutoPickerMpi *basePicker, const char * timing_fnm); + + void setupProjectors(); + + void run(); + + void autoPickOneMicrograph(FileName &fn_mic, long int imic); + + void calculateStddevAndMeanUnderMask(AccPtr< ACCCOMPLEX > &d_Fmic, + AccPtr< ACCCOMPLEX > &d_Fmic2, + AccPtr< ACCCOMPLEX > &d_Fmsk, + int nr_nonzero_pixels_mask, AccPtr< XFLOAT > &d_Mstddev, + AccPtr< XFLOAT > &d_Mmean, + size_t x, size_t y, size_t mic_size, size_t workSize); + + ~AutoPickerHip() + { + for (int i = 0; i < classStreams.size(); i++) + HANDLE_ERROR(hipStreamDestroy(classStreams[i])); + } + +//private: + +// // Uses Roseman2003 formulae to calculate stddev under the mask through FFTs +// // The FFTs of the micrograph (Fmic), micrograph-squared (Fmic2) and the mask (Fmsk) need to be provided at downsize_mic +// // The putput (Mstddev) will be at (binned) micrograph_size +// void calculateStddevAndMeanUnderMask(const MultidimArray &Fmic, const MultidimArray &Fmic2, +// MultidimArray &Fmsk, int nr_nonzero_pixels_mask, MultidimArray &Mstddev, MultidimArray &Mmean); +// +// // Peak search for all pixels above a given threshold in the map +// void peakSearch(const MultidimArray &Mccf, const MultidimArray &Mpsi, const MultidimArray &Mstddev, int iref, int skip_side, std::vector &peaks); +// +// // Now prune the coordinates: within min_particle_distance: all peaks are the same cluster +// // From each cluster, take the single peaks with the highest ccf +// // If then, there is another peaks at a distance of at least min_particle_distance: take that one as well, and so forth... +// void prunePeakClusters(std::vector &peaks, int min_distance); +// +// +// // Only keep those peaks that are at the given distance apart from each other +// void removeTooCloselyNeighbouringPeaks(std::vector &peaks, int min_distance); + +}; + +#endif /* HIP_AUTOPICKER_H_ */ diff --git a/src/acc/hip/hip_autopicker.hip.cpp b/src/acc/hip/hip_autopicker.hip.cpp new file mode 100644 index 000000000..f5c7d8925 --- /dev/null +++ b/src/acc/hip/hip_autopicker.hip.cpp @@ -0,0 +1,1160 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#undef ALTCPU +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "src/ml_optimiser.h" +#include "src/acc/acc_ptr.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/hip/hip_kernels/helper.h" +#include "src/acc/hip/hip_mem_utils.h" +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_benchmark_utils.h" +#include "src/acc/hip/hip_fft.h" + +#include "src/macros.h" +#include "src/error.h" + +#ifdef HIP_FORCESTL +#include "src/acc/hip/hip_utils_stl.h" +#else +#include "src/acc/hip/hip_utils_cub.h" +#endif + +#include "src/acc/utilities.h" +#include "src/acc/acc_helper_functions.h" + +#include "src/acc/hip/hip_autopicker.h" + + +AutoPickerHip::AutoPickerHip(AutoPicker *basePicker, const char * timing_fnm) : + node(NULL), + basePckr(basePicker), + allocator(new HipCustomAllocator(0, 1)), + micTransformer(0, allocator), + hipTransformer1(0, allocator), +#ifdef TIMING_FILES + timer(timing_fnm), +#endif + hipTransformer2(0, allocator) + +{ + projectors.resize(basePckr->Mrefs.size()); + have_warned_batching=false; + /*====================================================== + DEVICE SETTINGS + ======================================================*/ + device_id = basePicker->device_id; + int devCount; + HANDLE_ERROR(hipGetDeviceCount(&devCount)); + + if(device_id >= devCount) + { + //std::cerr << " using device_id=" << device_id << " (device no. " << device_id+1 << ") which is higher than the available number of devices=" << devCount << std::endl; + CRITICAL(ERR_GPUID); + } + else + HANDLE_ERROR(hipSetDevice(device_id)); +}; + +AutoPickerHip::AutoPickerHip(AutoPickerMpi *basePicker, const char * timing_fnm) : + basePckr(basePicker), + allocator(new HipCustomAllocator(0, 1)), + micTransformer(0, allocator), + hipTransformer1(0, allocator), +#ifdef TIMING_FILES + timer(timing_fnm), +#endif + hipTransformer2(0, allocator) + +{ + node = basePicker->getNode(); + basePicker->verb = (node->isLeader()) ? 1 : 0; + + projectors.resize(basePckr->Mrefs.size()); + have_warned_batching=false; + /*====================================================== + DEVICE SETTINGS + ======================================================*/ + device_id = basePicker->device_id; + int devCount; + HANDLE_ERROR(hipGetDeviceCount(&devCount)); + + if(device_id >= devCount) + { + //std::cerr << " using device_id=" << device_id << " (device no. " << device_id+1 << ") which is higher than the available number of devices=" << devCount << std::endl; + CRITICAL(ERR_GPUID); + } + else + HANDLE_ERROR(hipSetDevice(device_id)); +}; + +void AutoPickerHip::run() +{ + long int my_first_micrograph, my_last_micrograph, my_nr_micrographs; + if(node!=NULL) + { + // Each node does part of the work + divide_equally(basePckr->fn_micrographs.size(), node->size, node->rank, my_first_micrograph, my_last_micrograph); + } + else + { + my_first_micrograph = 0; + my_last_micrograph = basePckr->fn_micrographs.size() - 1; + } + my_nr_micrographs = my_last_micrograph - my_first_micrograph + 1; + + int barstep; + if (basePckr->verb > 0) + { + std::cout << " Autopicking ..." << std::endl; + init_progress_bar(my_nr_micrographs); + barstep = XMIPP_MAX(1, my_nr_micrographs / 60); + } + + if (!basePckr->do_read_fom_maps) + { + CTIC(timer,"setupProjectors"); + for (int iref = 0; iref < (basePckr->Mrefs.size()); iref++) + { + projectors[iref].setMdlDim( + basePckr->PPref[iref].data.xdim, + basePckr->PPref[iref].data.ydim, + basePckr->PPref[iref].data.zdim, + basePckr->PPref[iref].data.yinit, + basePckr->PPref[iref].data.zinit, + basePckr->PPref[iref].r_max, + basePckr->PPref[iref].padding_factor); + projectors[iref].initMdl(&(basePckr->PPref[iref].data.data[0])); + } + CTOC(timer,"setupProjectors"); + } + + FileName fn_olddir=""; + + for (long int imic = my_first_micrograph; imic <= my_last_micrograph; imic++) + { + if (basePckr->verb > 0 && imic % barstep == 0) + progress_bar(imic); + + + // Check new-style outputdirectory exists and make it if not! + FileName fn_dir = basePckr->getOutputRootName(basePckr->fn_micrographs[imic]); + fn_dir = fn_dir.beforeLastOf("/"); + if (fn_dir != fn_olddir) + { + // Make a Particles directory + mktree(fn_dir); + fn_olddir = fn_dir; + } +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_A5); +#endif + autoPickOneMicrograph(basePckr->fn_micrographs[imic], imic); + } +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_A5); +#endif + if (basePckr->verb > 0) + progress_bar(my_nr_micrographs); + + hipDeviceReset(); + +} + +void AutoPickerHip::calculateStddevAndMeanUnderMask(AccPtr< ACCCOMPLEX > &d_Fmic, + AccPtr< ACCCOMPLEX > &d_Fmic2, + AccPtr< ACCCOMPLEX > &d_Fmsk, + int nr_nonzero_pixels_mask, AccPtr< XFLOAT > &d_Mstddev, + AccPtr< XFLOAT > &d_Mmean, + size_t x, size_t y, size_t mic_size, size_t workSize) +{ + hipTransformer2.setSize(workSize,workSize,1); + + deviceInitValue(d_Mstddev, (XFLOAT)0.); + + RFLOAT normfft = (RFLOAT)(mic_size * mic_size) / (RFLOAT)nr_nonzero_pixels_mask; + + AccPtr< ACCCOMPLEX > d_Fcov = d_Fmic.make< ACCCOMPLEX >(); + d_Fcov.deviceAlloc(d_Fmic.getSize()); + + CTIC(timer,"PRE-multi_0"); + int Bsize( (int) ceilf(( float)d_Fmic.getSize()/(float)BLOCK_SIZE)); + hipLaunchKernelGGL(hip_kernel_convol_B, dim3(Bsize), dim3(BLOCK_SIZE), 0, 0, + ~d_Fmic, + ~d_Fmsk, + ~d_Fcov, + d_Fmic.getSize()); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"PRE-multi_0"); + + CTIC(timer,"PRE-window_0"); + windowFourierTransform2( + d_Fcov, + hipTransformer2.fouriers, + x, y, 1, + workSize/2+1, workSize, 1); + CTOC(timer,"PRE-window_0"); + + CTIC(timer,"PRE-Transform_0"); + hipTransformer2.backward(); + CTOC(timer,"PRE-Transform_0"); + + Bsize = ( (int) ceilf(( float)hipTransformer2.reals.getSize()/(float)BLOCK_SIZE)); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_multi), dim3(Bsize), dim3(BLOCK_SIZE), 0, 0, + ~hipTransformer2.reals, + ~hipTransformer2.reals, + (XFLOAT) normfft, + hipTransformer2.reals.getSize()); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTIC(timer,"PRE-multi_1"); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_multi), dim3(Bsize), dim3(BLOCK_SIZE), 0, 0, + ~hipTransformer2.reals, + ~hipTransformer2.reals, + ~d_Mstddev, + (XFLOAT) -1, + hipTransformer2.reals.getSize()); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"PRE-multi_1"); + + CTIC(timer,"PRE-CenterFFT_0"); + runCenterFFT(hipTransformer2.reals, + (int)hipTransformer2.xSize, + (int)hipTransformer2.ySize, + false, + 1); + CTOC(timer,"PRE-CenterFFT_0"); + + hipTransformer2.reals.cpOnAcc(d_Mmean); //TODO remove the need for this + + CTIC(timer,"PRE-multi_2"); + Bsize = ( (int) ceilf(( float)d_Fmsk.getSize()/(float)BLOCK_SIZE)); + hipLaunchKernelGGL(hip_kernel_convol_A, dim3(Bsize), dim3(BLOCK_SIZE), 0, 0, ~d_Fmsk, + ~d_Fmic2, + ~d_Fcov, + d_Fmsk.getSize()); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"PRE-multi_2"); + + + CTIC(timer,"PRE-window_1"); + windowFourierTransform2( + d_Fcov, + hipTransformer2.fouriers, + x, y, 1, + workSize/2+1, workSize, 1); + CTOC(timer,"PRE-window_1"); + + + CTIC(timer,"PRE-Transform_1"); + hipTransformer2.backward(); + CTOC(timer,"PRE-Transform_1"); + + CTIC(timer,"PRE-multi_3"); + Bsize = ( (int) ceilf(( float)d_Mstddev.getSize()/(float)BLOCK_SIZE)); + hipLaunchKernelGGL(hip_kernel_finalizeMstddev, dim3(Bsize), dim3(BLOCK_SIZE), 0, 0, + ~d_Mstddev, + ~hipTransformer2.reals, + normfft, + d_Mstddev.getSize()); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"PRE-multi_3"); + + CTIC(timer,"PRE-CenterFFT_1"); + runCenterFFT(d_Mstddev, + (int)workSize, + (int)workSize, + false, + 1); + CTOC(timer,"PRE-CenterFFT_1"); + +} + +void AutoPickerHip::autoPickOneMicrograph(FileName &fn_mic, long int imic) +{ + Image Imic; + MultidimArray Faux, Faux2, Fmic; + MultidimArray Maux, Mstddev, Mmean, Mstddev2, Mavg, Mccf_best, Mpsi_best, Fctf, Mccf_best_combined, Mpsi_best_combined; + MultidimArray Mclass_best_combined; + + AccPtr d_Mccf_best(basePckr->workSize*basePckr->workSize, allocator); + AccPtr d_Mpsi_best(basePckr->workSize*basePckr->workSize, allocator); + d_Mccf_best.deviceAlloc(); + d_Mpsi_best.deviceAlloc(); + + // Always use the same random seed + init_random_generator(basePckr->random_seed + imic); + + RFLOAT sum_ref_under_circ_mask, sum_ref2_under_circ_mask; + int my_skip_side = basePckr->autopick_skip_side + basePckr->particle_size/2; + CTF ctf; + + int Npsi = 360 / basePckr->psi_sampling; + + int min_distance_pix = ROUND(basePckr->min_particle_distance / basePckr->angpix); + XFLOAT scale = (XFLOAT)basePckr->workSize / (XFLOAT)basePckr->micrograph_size; + + // Read in the micrograph +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_A6); +#endif + CTIC(timer,"readMicrograph"); + Imic.read(fn_mic); + CTOC(timer,"readMicrograph"); + CTIC(timer,"setXmippOrigin_0"); + Imic().setXmippOrigin(); + CTOC(timer,"setXmippOrigin_0"); +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_A6); +#endif + + // Let's just check the square size again.... + RFLOAT my_size, my_xsize, my_ysize; + my_xsize = XSIZE(Imic()); + my_ysize = YSIZE(Imic()); + my_size = (my_xsize != my_ysize) ? XMIPP_MAX(my_xsize, my_ysize) : my_xsize; + if (basePckr->extra_padding > 0) + my_size += 2 * basePckr->extra_padding; + + if (my_size != basePckr->micrograph_size || my_xsize != basePckr->micrograph_xsize || my_ysize != basePckr->micrograph_ysize) + { + Imic().printShape(); + std::cerr << " micrograph_size= " << basePckr->micrograph_size << " micrograph_xsize= " << basePckr->micrograph_xsize << " micrograph_ysize= " << basePckr->micrograph_ysize << std::endl; + REPORT_ERROR("AutoPicker::autoPickOneMicrograph ERROR: No differently sized micrographs are allowed in one run, sorry you will have to run separately for each size..."); + } + + if(!basePckr->do_read_fom_maps) + { + CTIC(timer,"setSize_micTr"); + micTransformer.setSize(basePckr->micrograph_size, basePckr->micrograph_size, 1,1); + CTOC(timer,"setSize_micTr"); + + CTIC(timer,"setSize_hipTr"); + hipTransformer1.setSize(basePckr->workSize,basePckr->workSize, 1, Npsi, FFTW_BACKWARD); + CTOC(timer,"setSize_hipTr"); + } + HANDLE_ERROR(hipDeviceSynchronize()); + + if(hipTransformer1.batchSize.size()>1 && !have_warned_batching) + { + have_warned_batching = true; + std::cerr << std::endl << "*-----------------------------WARNING------------------------------------------------*"<< std::endl; + std::cerr << "With the current settings the GPU memory is imposing a soft limit on your performace," << std::endl; + std::cerr << "since one or more micrographs has to use (at least " << hipTransformer1.batchSize.size() << ") batches of orientations to "<< std::endl; + std::cerr << "achieve the total requested " << Npsi << " orientations. Consider using" << std::endl; + std::cerr << "\t higher --ang" << std::endl; + std::cerr << "\t harder --shrink" << std::endl; + std::cerr << "\t higher --lowpass with --shrink 0" << std::endl; + std::cerr << "*------------------------------------------------------------------------------------*"<< std::endl; + } + + // Set mean to zero and stddev to 1 to prevent numerical problems with one-sweep stddev calculations.... + RFLOAT avg0, stddev0, minval0, maxval0; +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_A7); +#endif + CTIC(timer,"computeStats"); + Imic().computeStats(avg0, stddev0, minval0, maxval0); + CTOC(timer,"computeStats"); +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_A7); +#endif + CTIC(timer,"middlePassFilter"); + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Imic()) + { + // Remove pixel values that are too far away from the mean + if ( ABS(DIRECT_MULTIDIM_ELEM(Imic(), n) - avg0) / stddev0 > basePckr->outlier_removal_zscore) + DIRECT_MULTIDIM_ELEM(Imic(), n) = avg0; + + DIRECT_MULTIDIM_ELEM(Imic(), n) = (DIRECT_MULTIDIM_ELEM(Imic(), n) - avg0) / stddev0; + } + CTOC(timer,"middlePassFilter"); + + if (basePckr->micrograph_xsize != basePckr->micrograph_size || basePckr->micrograph_ysize != basePckr->micrograph_size) + { + CTIC(timer,"rewindow"); + // Window non-square micrographs to be a square with the largest side + rewindow(Imic, basePckr->micrograph_size); + CTOC(timer,"rewindow"); + CTIC(timer,"gaussNoiseOutside"); + // Fill region outside the original window with white Gaussian noise to prevent all-zeros in Mstddev + FOR_ALL_ELEMENTS_IN_ARRAY2D(Imic()) + { + if (i < FIRST_XMIPP_INDEX(basePckr->micrograph_ysize) + || i > LAST_XMIPP_INDEX(basePckr->micrograph_ysize) + || j < FIRST_XMIPP_INDEX(basePckr->micrograph_xsize) + || j > LAST_XMIPP_INDEX(basePckr->micrograph_xsize) ) + A2D_ELEM(Imic(), i, j) = rnd_gaus(0.,1.); + } + CTOC(timer,"gaussNoiseOutside"); + } + +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_A8); +#endif + CTIC(timer,"CTFread"); + // Read in the CTF information if needed + if (basePckr->do_ctf) + { + // Search for this micrograph in the metadata table + FOR_ALL_OBJECTS_IN_METADATA_TABLE(basePckr->MDmic) + { + FileName fn_tmp; + basePckr->MDmic.getValue(EMDL_MICROGRAPH_NAME, fn_tmp); + if (fn_tmp==fn_mic) + { + ctf.readByGroup(basePckr->MDmic, &basePckr->obsModel); + Fctf.resize(basePckr->workSize,basePckr->workSize/2+1); + ctf.getFftwImage(Fctf, basePckr->micrograph_size, basePckr->micrograph_size, basePckr->angpix, false, false, basePckr->intact_ctf_first_peak, true); + break; + } + } + } + CTOC(timer,"CTFread"); +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_A8); +#endif +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_A9); +#endif + CTIC(timer,"mccfResize"); + Mccf_best.resize(basePckr->workSize,basePckr->workSize); + CTOC(timer,"mccfResize"); + CTIC(timer,"mpsiResize"); + Mpsi_best.resize(basePckr->workSize,basePckr->workSize); + CTOC(timer,"mpsiResize"); +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_A9); +#endif + AccPtr< ACCCOMPLEX > d_Fmic(allocator); + AccPtr d_Mmean(allocator); + AccPtr d_Mstddev(allocator); + +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_B1); +#endif + RFLOAT normfft = (RFLOAT)(basePckr->micrograph_size*basePckr->micrograph_size) / (RFLOAT)basePckr->nr_pixels_circular_mask;; + if (basePckr->do_read_fom_maps) + { + CTIC(timer,"readFromFomMaps_0"); + FileName fn_tmp=basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_stddevNoise.spi"; + Image It; + It.read(fn_tmp); + if (basePckr->autopick_helical_segments) + Mstddev2 = It(); + else + Mstddev = It(); + fn_tmp=basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_avgNoise.spi"; + It.read(fn_tmp); + if (basePckr->autopick_helical_segments) + Mavg = It(); + else + Mmean = It(); + CTOC(timer,"readFromFomMaps_0"); + } + else + { + /* + * Squared difference FOM: + * Sum ( (X-mu)/sig - A )^2 = + * = Sum((X-mu)/sig)^2 - 2 Sum (A*(X-mu)/sig) + Sum(A)^2 + * = (1/sig^2)*Sum(X^2) - (2*mu/sig^2)*Sum(X) + (mu^2/sig^2)*Sum(1) - (2/sig)*Sum(AX) + (2*mu/sig)*Sum(A) + Sum(A^2) + * + * However, the squared difference with an "empty" ie all-zero reference is: + * Sum ( (X-mu)/sig)^2 + * + * The ratio of the probabilities thereby becomes: + * P(ref) = 1/sqrt(2pi) * exp (( (X-mu)/sig - A )^2 / -2 ) // assuming sigma = 1! + * P(zero) = 1/sqrt(2pi) * exp (( (X-mu)/sig )^2 / -2 ) + * + * P(ref)/P(zero) = exp(( (X-mu)/sig - A )^2 / -2) / exp ( ( (X-mu)/sig )^2 / -2) + * = exp( (- (2/sig)*Sum(AX) + (2*mu/sig)*Sum(A) + Sum(A^2)) / - 2 ) + * + * Therefore, I do not need to calculate (X-mu)/sig beforehand!!! + * + */ + + CTIC(timer,"Imic_insert"); + for(int i = 0; i< Imic().nzyxdim ; i++) + micTransformer.reals[i] = (XFLOAT) Imic().data[i]; + micTransformer.reals.cpToDevice(); + CTOC(timer,"Imic_insert"); + + + CTIC(timer,"runCenterFFT_0"); + runCenterFFT(micTransformer.reals, micTransformer.xSize, micTransformer.ySize, true, 1); + CTOC(timer,"runCenterFFT_0"); + + + CTIC(timer,"FourierTransform_0"); + micTransformer.forward(); + int FMultiBsize = ( (int) ceilf(( float)micTransformer.fouriers.getSize()*2/(float)BLOCK_SIZE)); + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_multi), dim3(FMultiBsize), dim3(BLOCK_SIZE), 0, 0, + (XFLOAT*)~micTransformer.fouriers, + (XFLOAT)1/((XFLOAT)(micTransformer.reals.getSize())), + micTransformer.fouriers.getSize()*2); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"FourierTransform_0"); + + if (basePckr->highpass > 0.) + { + CTIC(timer,"highpass"); + micTransformer.fouriers.streamSync(); + lowPassFilterMapGPU( micTransformer.fouriers, + (size_t)1, + micTransformer.yFSize, + micTransformer.xFSize, + XSIZE(Imic()), + basePckr->lowpass, + basePckr->highpass, + basePckr->angpix, + 2, + true); //false = lowpass, true=highpass + micTransformer.fouriers.streamSync(); + micTransformer.backward(); + micTransformer.reals.streamSync(); + CTOC(timer,"highpass"); + } + + CTIC(timer,"F_cp"); + AccPtr< ACCCOMPLEX > Ftmp(allocator); + Ftmp.setSize(micTransformer.fouriers.getSize()); + Ftmp.deviceAlloc(); + micTransformer.fouriers.cpOnAcc(Ftmp); + CTOC(timer,"F_cp"); + + // Also calculate the FFT of the squared micrograph + CTIC(timer,"SquareImic"); + + hipLaunchKernelGGL(hip_kernel_square, dim3(FMultiBsize), dim3(BLOCK_SIZE), 0, 0, + ~micTransformer.reals, + micTransformer.reals.getSize()); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"SquareImic"); + + CTIC(timer,"FourierTransform_1"); + + micTransformer.forward(); + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_multi), dim3(FMultiBsize), dim3(BLOCK_SIZE), 0, 0, + (XFLOAT*)~micTransformer.fouriers, + (XFLOAT)1/((XFLOAT)(micTransformer.reals.getSize())), + micTransformer.fouriers.getSize()*2); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"FourierTransform_1"); + + // The following calculate mu and sig under the solvent area at every position in the micrograph + CTIC(timer,"calculateStddevAndMeanUnderMask"); + + d_Mstddev.deviceAlloc(basePckr->workSize*basePckr->workSize); + d_Mmean.deviceAlloc(basePckr->workSize*basePckr->workSize); + + if (basePckr->autopick_helical_segments) + { + + AccPtr< ACCCOMPLEX > d_Fmsk2(basePckr->Favgmsk.nzyxdim, allocator); + AccPtr d_Mavg(allocator); + AccPtr d_Mstddev2(allocator); + + d_Fmsk2.deviceAlloc(); + d_Mavg.deviceAlloc(basePckr->workSize*basePckr->workSize); + d_Mstddev2.deviceAlloc(basePckr->workSize*basePckr->workSize); + + //TODO Do this only once further up in scope + for(int i = 0; i< d_Fmsk2.getSize() ; i++) + { + d_Fmsk2[i].x = basePckr->Favgmsk.data[i].real; + d_Fmsk2[i].y = basePckr->Favgmsk.data[i].imag; + } + d_Fmsk2.cpToDevice(); + d_Fmsk2.streamSync(); + + calculateStddevAndMeanUnderMask(Ftmp, micTransformer.fouriers, d_Fmsk2, basePckr->nr_pixels_avg_mask, d_Mstddev2, d_Mavg, micTransformer.xFSize, micTransformer.yFSize, basePckr->micrograph_size, basePckr->workSize); + + d_Mstddev2.hostAlloc(); + d_Mstddev2.cpToHost(); + d_Mstddev2.streamSync(); + Mstddev2.resizeNoCp(1, basePckr->workSize, basePckr->workSize); + for(int i = 0; i < d_Mstddev2.getSize() ; i ++) + Mstddev2.data[i] = d_Mstddev2[i]; + + d_Mavg.hostAlloc(); + d_Mavg.cpToHost(); + d_Mavg.streamSync(); + Mavg.resizeNoCp(1, basePckr->workSize, basePckr->workSize); + for(int i = 0; i < d_Mavg.getSize() ; i ++) + Mavg.data[i] = d_Mavg[i]; + + } + + //TODO Do this only once further up in scope + AccPtr< ACCCOMPLEX > d_Fmsk(basePckr->Finvmsk.nzyxdim, allocator); + d_Fmsk.deviceAlloc(); + for(int i = 0; i< d_Fmsk.getSize() ; i++) + { + d_Fmsk[i].x = basePckr->Finvmsk.data[i].real; + d_Fmsk[i].y = basePckr->Finvmsk.data[i].imag; + } + d_Fmsk.cpToDevice(); + d_Fmsk.streamSync(); + + calculateStddevAndMeanUnderMask(Ftmp, micTransformer.fouriers, d_Fmsk, basePckr->nr_pixels_circular_invmask, d_Mstddev, d_Mmean, micTransformer.xFSize, micTransformer.yFSize, basePckr->micrograph_size, basePckr->workSize); + + + //TODO remove this + d_Mstddev.hostAlloc(); + d_Mstddev.cpToHost(); + d_Mstddev.streamSync(); + + Mstddev.resizeNoCp(1, basePckr->workSize, basePckr->workSize); + + //TODO put this in a kernel + for(int i = 0; i < d_Mstddev.getSize() ; i ++) + { + Mstddev.data[i] = d_Mstddev[i]; + if (d_Mstddev[i] > (XFLOAT)1E-10) + d_Mstddev[i] = 1 / d_Mstddev[i]; + else + d_Mstddev[i] = 1; + } + + d_Mstddev.cpToDevice(); + d_Mstddev.streamSync(); + + d_Mmean.hostAlloc(); + d_Mmean.cpToHost(); + d_Mmean.streamSync(); + Mmean.resizeNoCp(1, basePckr->workSize, basePckr->workSize); + for(int i = 0; i < d_Mmean.getSize() ; i ++) + Mmean.data[i] = d_Mmean[i]; + + CTOC(timer,"calculateStddevAndMeanUnderMask"); + + // From now on use downsized Fmic, as the cross-correlation with the references can be done at lower resolution + CTIC(timer,"windowFourierTransform_0"); + + d_Fmic.setSize((basePckr->workSize/2+1)*(basePckr->workSize)); + d_Fmic.deviceAlloc(); + windowFourierTransform2( + Ftmp, + d_Fmic, + basePckr->micrograph_size/2+1, basePckr->micrograph_size, 1, //Input dimensions + basePckr->workSize/2+1, basePckr->workSize, 1 //Output dimensions + ); + CTOC(timer,"windowFourierTransform_0"); + + if (basePckr->do_write_fom_maps) + { + CTIC(timer,"writeToFomMaps"); + // TMP output + FileName fn_tmp=basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_stddevNoise.spi"; + Image It; + It() = (basePckr->autopick_helical_segments) ? Mstddev2 : Mstddev; + It.write(fn_tmp); + fn_tmp=basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_avgNoise.spi"; + It() = (basePckr->autopick_helical_segments) ? Mavg : Mmean; + It.write(fn_tmp); + CTOC(timer,"writeToFomMaps"); + } + + }// end if do_read_fom_maps + + // Now start looking for the peaks of all references + // Clear the output vector with all peaks + CTIC(timer,"initPeaks"); + std::vector peaks; + peaks.clear(); + CTOC(timer,"initPeaks"); +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_B1); +#endif + + if (basePckr->autopick_helical_segments) + { + if (basePckr->do_read_fom_maps) + { + FileName fn_tmp; + Image It_float; + Image It_int; + + fn_tmp = basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_combinedCCF.spi"; + It_float.read(fn_tmp); + Mccf_best_combined = It_float(); + + if (basePckr->do_amyloid) + { + fn_tmp = basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_combinedPSI.spi"; + It_float.read(fn_tmp); + Mpsi_best_combined = It_float(); + } + else + { + fn_tmp = basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_combinedCLASS.spi"; + It_int.read(fn_tmp); + Mclass_best_combined = It_int(); + } + } + else + { + Mccf_best_combined.clear(); + Mccf_best_combined.resize(basePckr->workSize, basePckr->workSize); + Mccf_best_combined.initConstant(-99.e99); + Mclass_best_combined.clear(); + Mclass_best_combined.resize(basePckr->workSize, basePckr->workSize); + Mclass_best_combined.initConstant(-1); + Mpsi_best_combined.clear(); + Mpsi_best_combined.resize(basePckr->workSize, basePckr->workSize); + Mpsi_best_combined.initConstant(-99.e99); + } + } + + AccPtr< XFLOAT > d_ctf(Fctf.nzyxdim, allocator); + d_ctf.deviceAlloc(); + if(basePckr->do_ctf) + { + for(int i = 0; i< d_ctf.getSize() ; i++) + d_ctf[i]=Fctf.data[i]; + d_ctf.cpToDevice(); + } + + for (int iref = 0; iref < basePckr->Mrefs.size(); iref++) + { + + CTIC(timer,"OneReference"); + RFLOAT expected_Pratio; // the expectedFOM for this (ctf-corrected) reference + if (basePckr->do_read_fom_maps) + { +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_B2); +#endif + if (!basePckr->autopick_helical_segments) + { + CTIC(timer,"readFromFomMaps"); + FileName fn_tmp; + Image It; + + fn_tmp.compose(basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_ref", iref,"_bestCCF.spi"); + It.read(fn_tmp); + Mccf_best = It(); + It.MDMainHeader.getValue(EMDL_IMAGE_STATS_MAX, expected_Pratio); // Retrieve expected_Pratio from the header of the image + + fn_tmp.compose(basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_ref", iref,"_bestPSI.spi"); + It.read(fn_tmp); + Mpsi_best = It(); + CTOC(timer,"readFromFomMaps"); + } +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_B2); +#endif + + } //end else if do_read_fom_maps + else + { +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_B3); +#endif + CTIC(timer,"mccfInit"); + deviceInitValue(d_Mccf_best, (XFLOAT)-LARGE_NUMBER); + CTOC(timer,"mccfInit"); + AccProjectorKernel projKernel = AccProjectorKernel::makeKernel( + projectors[iref], + (int)basePckr->workSize/2+1, + (int)basePckr->workSize, + 1, // Zdim, always 1 in autopicker. + (int)basePckr->workSize/2+1 -1 ); + + int FauxStride = (basePckr->workSize/2+1)*basePckr->workSize; + +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_B4); +#endif + CTIC(timer,"SingleProjection"); + dim3 blocks((int)ceilf((float)FauxStride/(float)BLOCK_SIZE),1); + if(basePckr->do_ctf) + { + hipLaunchKernelGGL(hip_kernel_rotateAndCtf, dim3(blocks), dim3(BLOCK_SIZE), 0, 0, + ~hipTransformer1.fouriers, + ~d_ctf, + 0, + projKernel, + 0 + ); + } + else + { + hipLaunchKernelGGL(hip_kernel_rotateOnly, dim3(blocks), dim3(BLOCK_SIZE), 0, 0, + ~hipTransformer1.fouriers, + 0, + projKernel, + 0 + ); + } + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"SingleProjection"); +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_B4); +#endif + /* + * FIRST PSI WAS USED FOR PREP CALCS - THIS IS NOW A DEDICATED SECTION + * ------------------------------------------------------------------- + */ + + CTIC(timer,"PREP_CALCS"); + +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_B5); +#endif + // Sjors 20April2016: The calculation for sum_ref_under_circ_mask, etc below needs to be done on original micrograph_size! + CTIC(timer,"windowFourierTransform_FP"); + windowFourierTransform2(hipTransformer1.fouriers, + micTransformer.fouriers, + basePckr->workSize/2+1, basePckr->workSize, 1, //Input dimensions + basePckr->micrograph_size/2+1, basePckr->micrograph_size, 1 //Output dimensions + ); + CTOC(timer,"windowFourierTransform_FP"); + + CTIC(timer,"inverseFourierTransform_FP"); + micTransformer.backward(); + CTOC(timer,"inverseFourierTransform_FP"); + + CTIC(timer,"runCenterFFT_FP"); + runCenterFFT(micTransformer.reals, + (int)micTransformer.xSize, + (int)micTransformer.ySize, + false, + 1); + CTOC(timer,"runCenterFFT_FP"); + + micTransformer.reals.cpToHost(); + + Maux.resizeNoCp(1,basePckr->micrograph_size, basePckr->micrograph_size); + + micTransformer.reals.streamSync(); + for (int i = 0; i < micTransformer.reals.getSize() ; i ++) + Maux.data[i] = micTransformer.reals[i]; + + CTIC(timer,"setXmippOrigin_FP_0"); + Maux.setXmippOrigin(); + CTOC(timer,"setXmippOrigin_FP_0"); + // TODO: check whether I need CenterFFT(Maux, false) + // Sjors 20apr2016: checked, somehow not needed. + + sum_ref_under_circ_mask = 0.; + sum_ref2_under_circ_mask = 0.; + RFLOAT suma2 = 0.; + RFLOAT sumn = 1.; + MultidimArray Mctfref(basePckr->particle_size, basePckr->particle_size); + CTIC(timer,"setXmippOrigin_FP_1"); + Mctfref.setXmippOrigin(); + CTOC(timer,"setXmippOrigin_FP_1"); + CTIC(timer,"suma_FP"); + FOR_ALL_ELEMENTS_IN_ARRAY2D(Mctfref) // only loop over smaller Mctfref, but take values from large Maux! + { + if (i*i + j*j < basePckr->particle_radius2) + { + suma2 += A2D_ELEM(Maux, i, j) * A2D_ELEM(Maux, i, j); + suma2 += 2. * A2D_ELEM(Maux, i, j) * rnd_gaus(0., 1.); + sum_ref_under_circ_mask += A2D_ELEM(Maux, i, j); + sum_ref2_under_circ_mask += A2D_ELEM(Maux, i, j) * A2D_ELEM(Maux, i, j); + sumn += 1.; + } + } + sum_ref_under_circ_mask /= sumn; + sum_ref2_under_circ_mask /= sumn; + expected_Pratio = exp(suma2 / (2. * sumn)); + + CTOC(timer,"suma_FP"); + CTOC(timer,"PREP_CALCS"); + + // for all batches + CTIC(timer,"AllPsi"); + int startPsi(0); + for (int psiIter = 0; psiIter < hipTransformer1.batchIters; psiIter++) // psi-batches for possible memory-limits + { + + CTIC(timer,"Projection"); + dim3 blocks((int)ceilf((float)FauxStride/(float)BLOCK_SIZE),hipTransformer1.batchSize[psiIter]); + if(basePckr->do_ctf) + { + hipLaunchKernelGGL(hip_kernel_rotateAndCtf, dim3(blocks), dim3(BLOCK_SIZE), 0, 0, + ~hipTransformer1.fouriers, + ~d_ctf, + DEG2RAD(basePckr->psi_sampling), + projKernel, + startPsi + ); + } + else + { + hipLaunchKernelGGL(hip_kernel_rotateOnly, dim3(blocks), dim3(BLOCK_SIZE), 0, 0, + ~hipTransformer1.fouriers, + DEG2RAD(basePckr->psi_sampling), + projKernel, + startPsi + ); + } + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"Projection"); + + // Now multiply template and micrograph to calculate the cross-correlation + CTIC(timer,"convol"); + dim3 blocks2( (int) ceilf(( float)FauxStride/(float)BLOCK_SIZE),hipTransformer1.batchSize[psiIter]); + hipLaunchKernelGGL(hip_kernel_batch_convol_A, dim3(blocks2), dim3(BLOCK_SIZE), 0, 0, + ~hipTransformer1.fouriers, + ~d_Fmic, + FauxStride); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + CTOC(timer,"convol"); + + CTIC(timer,"HipInverseFourierTransform_1"); + hipTransformer1.backward(); + HANDLE_ERROR(hipDeviceSynchronize()); + CTOC(timer,"HipInverseFourierTransform_1"); + + + CTIC(timer,"runCenterFFT_1"); + runCenterFFT(hipTransformer1.reals, + (int)hipTransformer1.xSize, + (int)hipTransformer1.ySize, + false, + hipTransformer1.batchSize[psiIter]); + CTOC(timer,"runCenterFFT_1"); + // Calculate ratio of prabilities P(ref)/P(zero) + // Keep track of the best values and their corresponding iref and psi + // ------------------------------------------------------------------ + // So now we already had precalculated: Mdiff2 = 1/sig*Sum(X^2) - 2/sig*Sum(X) + mu^2/sig*Sum(1) + // Still to do (per reference): - 2/sig*Sum(AX) + 2*mu/sig*Sum(A) + Sum(A^2) + CTIC(timer,"probRatio"); + HANDLE_ERROR(hipDeviceSynchronize()); + dim3 PR_blocks(ceilf((float)(hipTransformer1.reals.getSize()/hipTransformer1.batchSize[psiIter])/(float)PROBRATIO_BLOCK_SIZE)); + hipLaunchKernelGGL(hip_kernel_probRatio, dim3(PR_blocks), dim3(PROBRATIO_BLOCK_SIZE), 0, 0, + ~d_Mccf_best, + ~d_Mpsi_best, + ~hipTransformer1.reals, + ~d_Mmean, + ~d_Mstddev, + hipTransformer1.reals.getSize()/hipTransformer1.batchSize[0], + (XFLOAT) -2*normfft, + (XFLOAT) 2*sum_ref_under_circ_mask, + (XFLOAT) sum_ref2_under_circ_mask, + (XFLOAT) expected_Pratio, + hipTransformer1.batchSize[psiIter], + startPsi, + Npsi + ); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + startPsi += hipTransformer1.batchSize[psiIter]; + CTOC(timer,"probRatio"); + + } // end for psi-batches + CTOC(timer,"AllPsi"); +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_B6); +#endif +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_B7); +#endif + CTIC(timer,"output"); + d_Mccf_best.cpToHost(); + d_Mpsi_best.cpToHost(); + d_Mccf_best.streamSync(); + for (int i = 0; i < Mccf_best.nzyxdim; i ++) + { + Mccf_best.data[i] = d_Mccf_best[i]; + Mpsi_best.data[i] = d_Mpsi_best[i]; + } + CTOC(timer,"output"); + + if (basePckr->do_write_fom_maps && !basePckr->autopick_helical_segments) + { + CTIC(timer,"writeFomMaps"); + // TMP output + FileName fn_tmp; + Image It; + It() = Mccf_best; + // Store expected_Pratio in the header of the image.. + It.MDMainHeader.setValue(EMDL_IMAGE_STATS_MAX, expected_Pratio); // Store expected_Pratio in the header of the image + fn_tmp.compose(basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_ref", iref,"_bestCCF.spi"); + It.write(fn_tmp); + + It() = Mpsi_best; + fn_tmp.compose(basePckr->getOutputRootName(fn_mic)+"_"+basePckr->fn_out+"_ref", iref,"_bestPSI.spi"); + It.write(fn_tmp); + CTOC(timer,"writeFomMaps"); + + } // end if do_write_fom_maps +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_B7); +#endif +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_B3); +#endif + } // end if do_read_fom_maps + + + //TODO FIX HELICAL SEGMENTS SUPPORT + if (basePckr->autopick_helical_segments) + { + if (!basePckr->do_read_fom_maps) + { + // Combine Mccf_best and Mpsi_best from all refs + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Mccf_best) + { + RFLOAT new_ccf = DIRECT_MULTIDIM_ELEM(Mccf_best, n); + RFLOAT old_ccf = DIRECT_MULTIDIM_ELEM(Mccf_best_combined, n); + if (new_ccf > old_ccf) + { + DIRECT_MULTIDIM_ELEM(Mccf_best_combined, n) = new_ccf; + if (basePckr->do_amyloid) + DIRECT_MULTIDIM_ELEM(Mpsi_best_combined, n) = DIRECT_MULTIDIM_ELEM(Mpsi_best, n); + else + DIRECT_MULTIDIM_ELEM(Mclass_best_combined, n) = iref; + } + } + } + } + else + { +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_B8); +#endif + // Now that we have Mccf_best and Mpsi_best, get the peaks + std::vector my_ref_peaks; + CTIC(timer,"setXmippOriginX3"); + Mstddev.setXmippOrigin(); + Mmean.setXmippOrigin(); + Mccf_best.setXmippOrigin(); + Mpsi_best.setXmippOrigin(); + CTOC(timer,"setXmippOriginX3"); + + CTIC(timer,"peakSearch"); + basePckr->peakSearch(Mccf_best, Mpsi_best, Mstddev, Mmean, iref, my_skip_side, my_ref_peaks, scale); + CTOC(timer,"peakSearch"); + + CTIC(timer,"peakPrune"); + basePckr->prunePeakClusters(my_ref_peaks, min_distance_pix, scale); + CTOC(timer,"peakPrune"); + + CTIC(timer,"peakInsert"); + // append the peaks of this reference to all the other peaks + peaks.insert(peaks.end(), my_ref_peaks.begin(), my_ref_peaks.end()); + CTOC(timer,"peakInsert"); + CTOC(timer,"OneReference"); +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_B8); +#endif + + } + } // end for iref + + if (basePckr->autopick_helical_segments) + { + if (basePckr->do_write_fom_maps) + { + FileName fn_tmp; + Image It_float; + Image It_int; + + It_float() = Mccf_best_combined; + fn_tmp = basePckr->getOutputRootName(fn_mic) + "_" + basePckr->fn_out + "_combinedCCF.spi"; + It_float.write(fn_tmp); + + if (basePckr->do_amyloid) + { + It_float() = Mpsi_best_combined; + fn_tmp = basePckr->getOutputRootName(fn_mic) + "_" + basePckr->fn_out + "_combinedPSI.spi"; + It_float.write(fn_tmp); + } + else + { + It_int() = Mclass_best_combined; + fn_tmp = basePckr->getOutputRootName(fn_mic) + + "_" + basePckr->fn_out + "_combinedCLASS.spi"; + It_int.write(fn_tmp); + } + } // end if do_write_fom_maps + + RFLOAT thres = basePckr->min_fraction_expected_Pratio; + int peak_r_min = 1; + std::vector ccf_peak_list; + std::vector > tube_coord_list, tube_track_list; + std::vector tube_len_list; + MultidimArray Mccfplot; + + Mccf_best_combined.setXmippOrigin(); + Mpsi_best_combined.setXmippOrigin(); + Mstddev2.setXmippOrigin(); + Mavg.setXmippOrigin(); + Mclass_best_combined.setXmippOrigin(); + if (basePckr->do_amyloid) + { + basePckr->pickAmyloids(Mccf_best_combined, Mpsi_best_combined, Mstddev2, Mavg, thres, basePckr->amyloid_max_psidiff, fn_mic, basePckr->fn_out, + (basePckr->helical_tube_diameter / basePckr->angpix), basePckr->autopick_skip_side, scale); + } + else + { + basePckr->pickCCFPeaks(Mccf_best_combined, Mstddev2, Mavg, Mclass_best_combined, thres, peak_r_min, (basePckr->particle_diameter / basePckr->angpix), + ccf_peak_list, Mccfplot, my_skip_side, scale); + basePckr->extractHelicalTubes(ccf_peak_list, tube_coord_list, tube_len_list, tube_track_list, + (basePckr->particle_diameter / basePckr->angpix), basePckr->helical_tube_curvature_factor_max, + (basePckr->min_particle_distance / basePckr->angpix), (basePckr->helical_tube_diameter / basePckr->angpix), scale); + basePckr->exportHelicalTubes(Mccf_best_combined, Mccfplot, Mclass_best_combined, + tube_coord_list, tube_track_list, tube_len_list, + fn_mic, basePckr->fn_out, + (basePckr->particle_diameter / basePckr->angpix), + (basePckr->helical_tube_length_min / basePckr->angpix), + my_skip_side, scale); + } + + + if ((basePckr->do_write_fom_maps || basePckr->do_read_fom_maps) && !basePckr->do_amyloid) + { + FileName fn_tmp; + Image It; + + It() = Mccfplot; + fn_tmp = basePckr->getOutputRootName(fn_mic) + "_" + basePckr->fn_out + "_combinedPLOT.spi"; + It.write(fn_tmp); + } + } + else + { +#ifdef TIMING + basePckr->timer.tic(basePckr->TIMING_B9); +#endif + //Now that we have done all references, prune the list again... + CTIC(timer,"finalPeakPrune"); + basePckr->prunePeakClusters(peaks, min_distance_pix, scale); + CTOC(timer,"finalPeakPrune"); + + // And remove all too close neighbours + basePckr->removeTooCloselyNeighbouringPeaks(peaks, min_distance_pix, scale); + + // Write out a STAR file with the coordinates + MetaDataTable MDout; + for (int ipeak =0; ipeak < peaks.size(); ipeak++) + { + MDout.addObject(); + MDout.setValue(EMDL_IMAGE_COORD_X, (RFLOAT)(peaks[ipeak].x)/scale); + MDout.setValue(EMDL_IMAGE_COORD_Y, (RFLOAT)(peaks[ipeak].y)/scale); + MDout.setValue(EMDL_PARTICLE_CLASS, peaks[ipeak].ref + 1); // start counting at 1 + MDout.setValue(EMDL_PARTICLE_AUTOPICK_FOM, peaks[ipeak].fom); + MDout.setValue(EMDL_ORIENT_PSI, peaks[ipeak].psi); + } + FileName fn_tmp = basePckr->getOutputRootName(fn_mic) + "_" + basePckr->fn_out + ".star"; + MDout.write(fn_tmp); +#ifdef TIMING + basePckr->timer.toc(basePckr->TIMING_B9); +#endif + } + +} diff --git a/src/acc/hip/hip_backprojector.hip.cpp b/src/acc/hip/hip_backprojector.hip.cpp new file mode 100644 index 000000000..00bc83fb7 --- /dev/null +++ b/src/acc/hip/hip_backprojector.hip.cpp @@ -0,0 +1,10 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#include +#include +#include "src/acc/hip/hip_settings.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/acc_projector.h" + +#include "src/acc/acc_backprojector_impl.h" diff --git a/src/acc/hip/hip_benchmark_utils.h b/src/acc/hip/hip_benchmark_utils.h new file mode 100644 index 000000000..aa7829067 --- /dev/null +++ b/src/acc/hip/hip_benchmark_utils.h @@ -0,0 +1,75 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_BENCHMARK_UTILS_H_ +#define HIP_BENCHMARK_UTILS_H_ + +//Non-concurrent benchmarking tools (only for Linux) + +#include +#include +#include +#include +#include +#include +#include + +#ifdef TIMING_FILES +#define CTIC(timer,timing) (timer.hip_cpu_tic(timing)) +#define CTOC(timer,timing) (timer.hip_cpu_toc(timing)) +#define GTIC(timer,timing) (timer.hip_gpu_tic(timing)) +#define GTOC(timer,timing) (timer.hip_gpu_toc(timing)) +#define GATHERGPUTIMINGS(timer) (timer.hip_gpu_printtictoc()) +#elif defined HIP_PROFILING + #include + #define CTIC(timer,timing) (roctxRangePush(timing)) + #define CTOC(timer,timing) (roctxRangePop()) + #define GTIC(timer,timing) + #define GTOC(timer,timing) + #define GATHERGPUTIMINGS(timer) +#else + #define CTIC(timer,timing) + #define CTOC(timer,timing) + #define GTIC(timer,timing) + #define GTOC(timer,timing) + #define GATHERGPUTIMINGS(timer) +#endif + +class relion_timer +{ + +public: + +std::vector hip_cpu_benchmark_identifiers; +std::vector hip_cpu_benchmark_start_times; +FILE *hip_cpu_benchmark_fPtr; + +std::vector hip_gpu_benchmark_identifiers; +std::vector hip_gpu_benchmark_start_times; +std::vector hip_gpu_benchmark_stop_times; +FILE *hip_gpu_benchmark_fPtr; + +relion_timer(std::string fnm) +{ + std::stringstream fnm_cpu, fnm_gpu; + fnm_cpu << "output/" << fnm << "_cpu.dat"; + hip_cpu_benchmark_fPtr = fopen(fnm_cpu.str().c_str(),"a"); + fnm_gpu << "output/" << fnm << "_gpu.dat"; + hip_gpu_benchmark_fPtr = fopen(fnm_gpu.str().c_str(),"a"); +} + +int hip_benchmark_find_id(std::string id, std::vector v); + +void hip_cpu_tic(std::string id); + +void hip_cpu_toc(std::string id); + +void hip_gpu_tic(std::string id); + +void hip_gpu_toc(std::string id); + +void hip_gpu_printtictoc(); + +}; + +#endif /* HIP_BENCHMARK_UTILS_H_ */ diff --git a/src/acc/hip/hip_benchmark_utils.hip.cpp b/src/acc/hip/hip_benchmark_utils.hip.cpp new file mode 100644 index 000000000..5337a8794 --- /dev/null +++ b/src/acc/hip/hip_benchmark_utils.hip.cpp @@ -0,0 +1,120 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ + +#include "src/acc/hip/hip_benchmark_utils.h" + +//Non-concurrent benchmarking tools (only for Linux) +#include +#include +#include +#include + +#include "src/macros.h" +#include "src/error.h" + +int relion_timer::hip_benchmark_find_id(std::string id, std::vector v) +{ + for (unsigned i = 0; i < v.size(); i++) + if (v[i] == id) + return i; + return -1; +} + + +void relion_timer::hip_cpu_tic(std::string id) +{ + if (hip_benchmark_find_id(id, hip_cpu_benchmark_identifiers) == -1) + { + hip_cpu_benchmark_identifiers.push_back(id); + hip_cpu_benchmark_start_times.push_back(clock()); + } + else + { + printf("DEBUG_ERROR: Provided identifier '%s' already exists in call to hip_cpu_tic.\n", id.c_str()); + CRITICAL(ERRCTIC); + } +} + +void relion_timer::hip_cpu_toc(std::string id) +{ + int idx = hip_benchmark_find_id(id, hip_cpu_benchmark_identifiers); + if (idx == -1) + { + printf("DEBUG_ERROR: Provided identifier '%s' not found in call to hip_cpu_toc.\n", id.c_str()); + //exit( EXIT_FAILURE ); + } + else + { + clock_t t = clock() - hip_cpu_benchmark_start_times[idx]; + hip_cpu_benchmark_identifiers.erase(hip_cpu_benchmark_identifiers.begin()+idx); + hip_cpu_benchmark_start_times.erase(hip_cpu_benchmark_start_times.begin()+idx); + fprintf(hip_cpu_benchmark_fPtr,"%06.2f ms ......", (float)t / CLOCKS_PER_SEC * 1000.); + for (int i = 1; i < hip_cpu_benchmark_identifiers.size(); i++) + fprintf(hip_cpu_benchmark_fPtr,"......"); + fprintf(hip_cpu_benchmark_fPtr," %s\n", id.c_str()); +// printf(,"%s \t %.2f ms\n", id.c_str(), (float)t / CLOCKS_PER_SEC * 1000.); + } +} + +void relion_timer::hip_gpu_tic(std::string id) +{ + if (hip_benchmark_find_id(id, hip_gpu_benchmark_identifiers) == -1) + { + hipEvent_t start, stop; + hipEventCreate(&start); + hipEventCreate(&stop); + hipEventRecord(start, 0); + hip_gpu_benchmark_identifiers.push_back(id); + hip_gpu_benchmark_start_times.push_back(start); + hip_gpu_benchmark_stop_times.push_back(stop); + } + else + { + printf("DEBUG_ERROR: Provided identifier '%s' already exists in call to hip_gpu_tic.\n", + id.c_str()); + CRITICAL(ERRGTIC); + } +} + +void relion_timer::hip_gpu_toc(std::string id) +{ + int idx = hip_benchmark_find_id(id, hip_gpu_benchmark_identifiers); + if (idx == -1) + { + printf("DEBUG_ERROR: Provided identifier '%s' not found in call to hip_gpu_tac.\n", + id.c_str()); + CRITICAL(ERRGTOC); + } + else + { + hipEventRecord(hip_gpu_benchmark_stop_times[idx], 0); + hipEventSynchronize(hip_gpu_benchmark_stop_times[idx]); + } +} + +void relion_timer::hip_gpu_printtictoc() +{ + if (hip_gpu_benchmark_identifiers.size() == 0) + { + printf("DEBUG_ERROR: There were no identifiers found in the list, on call to hip_gpu_toc.\n"); + CRITICAL(ERRTPC); + } + else + { + float time; + for (int idx = 0; idx < hip_gpu_benchmark_identifiers.size(); idx ++) + { + hipEventElapsedTime(&time, hip_gpu_benchmark_start_times[idx], + hip_gpu_benchmark_stop_times[idx]); + hipEventDestroy(hip_gpu_benchmark_start_times[idx]); + hipEventDestroy(hip_gpu_benchmark_stop_times[idx]); + fprintf(hip_gpu_benchmark_fPtr,"%.2f ms \t %s\n", + time, hip_gpu_benchmark_identifiers[idx].c_str()); + } + + hip_gpu_benchmark_identifiers.clear(); + hip_gpu_benchmark_start_times.clear(); + hip_gpu_benchmark_stop_times.clear(); + } +} diff --git a/src/acc/hip/hip_fft.h b/src/acc/hip/hip_fft.h new file mode 100644 index 000000000..bf195d28e --- /dev/null +++ b/src/acc/hip/hip_fft.h @@ -0,0 +1,337 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_FFT_H_ +#define HIP_FFT_H_ + +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_mem_utils.h" +#include +#include + +#ifdef DEBUG_HIP +#define HANDLE_HIPFFT_ERROR( err ) (HipfftHandleError( err, __FILE__, __LINE__ )) +#else +#define HANDLE_HIPFFT_ERROR( err ) (err) //Do nothing +#endif +static void HipfftHandleError( hipfftResult err, const char *file, int line ) +{ + if (err != HIPFFT_SUCCESS) + { + fprintf(stderr, "Hipfft error in file '%s' in line %i : %s.\n", + __FILE__, __LINE__, "error" ); +#ifdef DEBUG_HIP + raise(SIGSEGV); +#else + CRITICAL(ERRGPUKERN); +#endif + } +} + +class HipFFT +{ + bool planSet; +public: +#ifdef ACC_DOUBLE_PRECISION + AccPtr reals; + AccPtr fouriers; +#else + AccPtr reals; + AccPtr fouriers; +#endif + hipfftHandle hipfftPlanForward, hipfftPlanBackward; + int direction; + int dimension, idist, odist, istride, ostride; + int inembed[3]; + int onembed[3]; + size_t xSize,ySize,zSize,xFSize,yFSize,zFSize; + std::vector< int > batchSize; + HipCustomAllocator *CFallocator; + int batchSpace, batchIters, reqN; + + HipFFT(hipStream_t stream, HipCustomAllocator *allocator, int transformDimension = 2): + reals(stream, allocator), + fouriers(stream, allocator), + hipfftPlanForward(0), + hipfftPlanBackward(0), + direction(0), + dimension((int)transformDimension), + idist(0), + odist(0), + istride(1), + ostride(1), + inembed(), + onembed(), + planSet(false), + xSize(0), ySize(0), zSize(0), + xFSize(0), yFSize(0), zFSize(0), + batchSize(1,1), + reqN(1), + CFallocator(allocator) + {}; + + void setAllocator(HipCustomAllocator *allocator) + { + reals.setAllocator(allocator); + fouriers.setAllocator(allocator); + CFallocator = allocator; + } + + size_t estimate(int batch) + { + size_t needed(0); + + size_t biggness(0); + +#ifdef ACC_DOUBLE_PRECISION + if(direction<=0) + { + HANDLE_HIPFFT_ERROR( hipfftEstimateMany(dimension, inembed, inembed, istride, idist, onembed, ostride, odist, HIPFFT_D2Z, batch, &biggness)); + needed += biggness; + } + if(direction>=0) + { + HANDLE_HIPFFT_ERROR( hipfftEstimateMany(dimension, inembed, onembed, ostride, odist, inembed, istride, idist, HIPFFT_Z2D, batch, &biggness)); + needed += biggness; + } +#else + if(direction<=0) + { + HANDLE_HIPFFT_ERROR( hipfftEstimateMany(dimension, inembed, inembed, istride, idist, onembed, ostride, odist, HIPFFT_R2C, batch, &biggness)); + needed += biggness; + } + if(direction>=0) + { + HANDLE_HIPFFT_ERROR( hipfftEstimateMany(dimension, inembed, onembed, ostride, odist, inembed, istride, idist, HIPFFT_C2R, batch, &biggness)); + needed += biggness; + } +#endif + size_t res = needed + (size_t)odist*(size_t)batch*sizeof(XFLOAT)*(size_t)2 + (size_t)idist*(size_t)batch*sizeof(XFLOAT); + + return res; + } + + void setSize(size_t x, size_t y, size_t z, int batch = 1, int setDirection = 0) + { + + /* Optional direction input restricts transformer to + * forwards or backwards tranformation only, + * which reduces memory requirements, especially + * for large batches of simulatanous transforms. + * + * FFTW_FORWARDS === -1 + * FFTW_BACKWARDS === +1 + * + * The default direction is 0 === forwards AND backwards + */ + + int checkDim; + if(z>1) + checkDim=3; + else if(y>1) + checkDim=2; + else + checkDim=1; + if(checkDim != dimension) + CRITICAL(ERRHIPFFTDIM); + + if( !( (setDirection==-1)||(setDirection==0)||(setDirection==1) ) ) + { + std::cerr << "*ERROR : Setting a hip transformer direction to non-defined value" << std::endl; + CRITICAL(ERRHIPFFTDIR); + } + + direction = setDirection; + + if (x == xSize && y == ySize && z == zSize && batch == reqN && planSet) + return; + + clear(); + + batchSize.resize(1); + batchSize[0] = batch; + reqN = batch; + + xSize = x; + ySize = y; + zSize = z; + + xFSize = x/2 + 1; + yFSize = y; + zFSize = z; + + idist = zSize*ySize*xSize; + odist = zSize*ySize*(xSize/2+1); + istride = 1; + ostride = 1; + + if(dimension==3) + { + inembed[0] = zSize; + inembed[1] = ySize; + inembed[2] = xSize; + onembed[0] = zFSize; + onembed[1] = yFSize; + onembed[2] = xFSize; + } + else if(dimension==2) + { + inembed[0] = ySize; + inembed[1] = xSize; + onembed[0] = yFSize; + onembed[1] = xFSize; + } + else + { + inembed[0] = xSize; + onembed[0] = xFSize; + } + + size_t needed, avail, total; + needed = estimate(batchSize[0]); + DEBUG_HANDLE_ERROR(hipMemGetInfo( &avail, &total )); + +// std::cout << std::endl << "needed = "; +// printf("%15zu\n", needed); +// std::cout << "avail = "; +// printf("%15zu\n", avail); + + // Check if there is enough memory + // + // --- TO HOLD TEMPORARY DATA DURING TRANSFORMS --- + // + // If there isn't, find how many there ARE space for and loop through them in batches. + + if(needed>avail) + { + batchIters = 2; + batchSpace = CEIL((double) batch / (double)batchIters); + needed = estimate(batchSpace); + + while(needed>avail && batchSpace>1) + { + batchIters++; + batchSpace = CEIL((double) batch / (double)batchIters); + needed = estimate(batchSpace); + } + + if(batchIters>1) + { + batchIters = (int)((float)batchIters*1.1 + 1); + batchSpace = CEIL((double) batch / (double)batchIters); + needed = estimate(batchSpace); + } + + batchSize.assign(batchIters,batchSpace); // specify batchIters of batches, each with batchSpace orientations + batchSize[batchIters-1] = batchSpace - (batchSpace*batchIters - batch); // set last to care for remainder. + + if(needed>avail) + CRITICAL(ERRFFTMEMLIM); + +// std::cerr << std::endl << "NOTE: Having to use " << batchIters << " batches of orientations "; +// std::cerr << "to achieve the total requested " << batch << " orientations" << std::endl; +// std::cerr << "( this could affect performance, consider using " << std::endl; +// std::cerr << "\t higher --ang" << std::endl; +// std::cerr << "\t harder --shrink" << std::endl; +// std::cerr << "\t higher --lopass with --shrink 0" << std::endl; + + } + else + { + batchIters = 1; + batchSpace = batch; + } + + reals.setSize(idist*batchSize[0]); + reals.deviceAlloc(); + reals.hostAlloc(); + + fouriers.setSize(odist*batchSize[0]); + fouriers.deviceAlloc(); + fouriers.hostAlloc(); + +// DEBUG_HANDLE_ERROR(hipMemGetInfo( &avail, &total )); +// needed = estimate(batchSize[0], fudge); + +// std::cout << "after alloc: " << std::endl << std::endl << "needed = "; +// printf("%15li\n", needed); +// std::cout << "avail = "; +// printf("%15li\n", avail); + +#ifdef ACC_DOUBLE_PRECISION + if(direction<=0) + { + HANDLE_HIPFFT_ERROR( hipfftPlanMany(&hipfftPlanForward, dimension, inembed, inembed, istride, idist, onembed, ostride, odist, HIPFFT_D2Z, batchSize[0])); + HANDLE_HIPFFT_ERROR( hipfftSetStream(hipfftPlanForward, fouriers.getStream())); + } + if(direction>=0) + { + HANDLE_HIPFFT_ERROR( hipfftPlanMany(&hipfftPlanBackward, dimension, inembed, onembed, ostride, odist, inembed, istride, idist, HIPFFT_Z2D, batchSize[0])); + HANDLE_HIPFFT_ERROR( hipfftSetStream(hipfftPlanBackward, reals.getStream())); + } + planSet = true; + } + + void forward() + { HANDLE_HIPFFT_ERROR( hipfftExecD2Z(hipfftPlanForward, ~reals, ~fouriers) ); } + + void backward() + { HANDLE_HIPFFT_ERROR( hipfftExecZ2D(hipfftPlanBackward, ~fouriers, ~reals) ); } + + void backward(AccPtr &dst) + { HANDLE_HIPFFT_ERROR( hipfftExecZ2D(hipfftPlanBackward, ~fouriers, ~dst) ); } +#else + if(direction<=0) + { + HANDLE_HIPFFT_ERROR( hipfftPlanMany(&hipfftPlanForward, dimension, inembed, inembed, istride, idist, onembed, ostride, odist, HIPFFT_R2C, batchSize[0])); + HANDLE_HIPFFT_ERROR( hipfftSetStream(hipfftPlanForward, fouriers.getStream())); + } + if(direction>=0) + { + HANDLE_HIPFFT_ERROR( hipfftPlanMany(&hipfftPlanBackward, dimension, inembed, onembed, ostride, odist, inembed, istride, idist, HIPFFT_C2R, batchSize[0])); + HANDLE_HIPFFT_ERROR( hipfftSetStream(hipfftPlanBackward, reals.getStream())); + } + planSet = true; + } + + void forward() + { + if(direction==1) + { + std::cout << "trying to execute a forward plan for a hipFFT transformer which is backwards-only" << std::endl; + CRITICAL(ERRHIPFFTDIRF); + } + HANDLE_HIPFFT_ERROR( hipfftExecR2C(hipfftPlanForward, ~reals, ~fouriers) ); + } + + void backward() + { + if(direction==-1) + { + std::cout << "trying to execute a backwards plan for a hipFFT transformer which is forwards-only" << std::endl; + CRITICAL(ERRHIPFFTDIRR); + } + HANDLE_HIPFFT_ERROR( hipfftExecC2R(hipfftPlanBackward, ~fouriers, ~reals) ); + } + +#endif + + void clear() + { + if(planSet) + { + reals.freeIfSet(); + fouriers.freeIfSet(); + if(direction<=0) + HANDLE_HIPFFT_ERROR(hipfftDestroy(hipfftPlanForward)); + if(direction>=0) + HANDLE_HIPFFT_ERROR(hipfftDestroy(hipfftPlanBackward)); + planSet = false; + } + } + + ~HipFFT() + {clear();} +}; + +#endif //HIP_FFT_H_ diff --git a/src/acc/hip/hip_helper_functions.hip.cpp b/src/acc/hip/hip_helper_functions.hip.cpp new file mode 100644 index 000000000..ad0f4583e --- /dev/null +++ b/src/acc/hip/hip_helper_functions.hip.cpp @@ -0,0 +1,32 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#undef ALTCPU +#include +#include "src/ml_optimiser.h" +#include "src/acc/acc_ptr.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_fft.h" +#include "src/acc/hip/hip_kernels/hip_device_utils.h" + +#ifdef HIP_FORCESTL +#include "src/acc/hip/hip_utils_stl.h" +#else +#include "src/acc/hip/hip_utils_cub.h" +#endif + +#include "src/acc/utilities.h" +#include "src/acc/acc_helper_functions.h" +#include "src/acc/hip/hip_kernels/BP.h" +#include "src/macros.h" +#include "src/error.h" + +#include "src/acc/acc_ml_optimiser.h" +#include "src/acc/hip/hip_ml_optimiser.h" +#include "src/acc/acc_helper_functions.h" + + +#include "src/acc/acc_helper_functions_impl.h" diff --git a/src/acc/hip/hip_kernels/BP.h b/src/acc/hip/hip_kernels/BP.h new file mode 100644 index 000000000..73c666a29 --- /dev/null +++ b/src/acc/hip/hip_kernels/BP.h @@ -0,0 +1,828 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_BP_KERNELS_H_ +#define HIP_BP_KERNELS_H_ + +#include +#include +#include +#include +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_kernels/hip_device_utils.h" + +/* + * BP KERNELS + */ + +template < bool CTF_PREMULTIPLIED > +__global__ void hip_kernel_backproject2D( + XFLOAT *g_img_real, + XFLOAT *g_img_imag, + XFLOAT *g_trans_x, + XFLOAT *g_trans_y, + XFLOAT* g_weights, + XFLOAT* g_Minvsigma2s, + XFLOAT* g_ctfs, + unsigned long translation_num, + XFLOAT significant_weight, + XFLOAT weight_norm, + XFLOAT *g_eulers, + XFLOAT *g_model_real, + XFLOAT *g_model_imag, + XFLOAT *g_model_weight, + int max_r, + int max_r2, + XFLOAT padding_factor, + unsigned img_x, + unsigned img_y, + unsigned img_xy, + unsigned mdl_x, + int mdl_inity) +{ + unsigned tid = threadIdx.x; + unsigned img = blockIdx.x; + + int img_y_half = img_y / 2; + int max_r2_out = max_r2 * padding_factor * padding_factor; + + __shared__ XFLOAT s_eulers[4]; + + XFLOAT minvsigma2, ctf, img_real, img_imag, Fweight, real, imag, weight; + + if (tid == 0) + s_eulers[0] = g_eulers[img*9+0] * padding_factor; + else if (tid == 1) + s_eulers[1] = g_eulers[img*9+1] * padding_factor; + else if (tid == 2) + s_eulers[2] = g_eulers[img*9+3] * padding_factor; + else if (tid == 3) + s_eulers[3] = g_eulers[img*9+4] * padding_factor; + + __syncthreads(); + + int pixel_pass_num(ceilf((float)img_xy/(float)BP_2D_BLOCK_SIZE)); + + for (unsigned pass = 0; pass < pixel_pass_num; pass++) + { + unsigned pixel = (pass * BP_2D_BLOCK_SIZE) + tid; + + if (pixel >= img_xy) + continue; + + int x = pixel % img_x; + int y = (int)floorf( (float)pixel / (float)img_x); + + if (y > img_y_half) + { + y -= img_y; + } + + //WAVG + minvsigma2 = __ldg(&g_Minvsigma2s[pixel]); + ctf = __ldg(&g_ctfs[pixel]); + img_real = __ldg(&g_img_real[pixel]); + img_imag = __ldg(&g_img_imag[pixel]); + Fweight = (XFLOAT) 0.0; + real = (XFLOAT) 0.0; + imag = (XFLOAT) 0.0; + + XFLOAT temp_real, temp_imag; + + for (unsigned long itrans = 0; itrans < translation_num; itrans++) + { + weight = g_weights[img * translation_num + itrans]; + + if (weight >= significant_weight) + { + if(CTF_PREMULTIPLIED) + { + weight = (weight / weight_norm) * minvsigma2; + Fweight += weight * ctf; // SHWS 13feb2020: from now on when ctf_premultiplied, the ctf array actually contains ctf^2! + } + else + { + weight = (weight / weight_norm) * ctf * minvsigma2; + Fweight += weight * ctf; + } + + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], img_real, img_imag, temp_real, temp_imag); + + real += temp_real * weight; + imag += temp_imag * weight; + + } + } + + if (Fweight > (XFLOAT) 0.0) + { + + // Get logical coordinates in the 3D map + XFLOAT xp = (s_eulers[0] * x + s_eulers[1] * y ); + XFLOAT yp = (s_eulers[2] * x + s_eulers[3] * y ); + + // Only consider pixels that are projected inside the allowed circle in output coordinates. + // --JZ, Nov. 26th 2018 + if ( ( xp * xp + yp * yp ) > max_r2_out ) + continue; + + // Only asymmetric half is stored + if (xp < 0) + { + // Get complex conjugated hermitian symmetry pair + xp = -xp; + yp = -yp; + imag = -imag; + } + + int x0 = floorf(xp); + XFLOAT fx = xp - x0; + int x1 = x0 + 1; + + int y0 = floorf(yp); + XFLOAT fy = yp - y0; + y0 -= mdl_inity; + int y1 = y0 + 1; + + XFLOAT mfx = (XFLOAT) 1.0 - fx; + XFLOAT mfy = (XFLOAT) 1.0 - fy; + + XFLOAT dd00 = mfy * mfx; + XFLOAT dd01 = mfy * fx; + XFLOAT dd10 = fy * mfx; + XFLOAT dd11 = fy * fx; + + hip_atomic_add(&g_model_real [y0 * mdl_x + x0], dd00 * real); + hip_atomic_add(&g_model_imag [y0 * mdl_x + x0], dd00 * imag); + hip_atomic_add(&g_model_weight[y0 * mdl_x + x0], dd00 * Fweight); + + hip_atomic_add(&g_model_real [y0 * mdl_x + x1], dd01 * real); + hip_atomic_add(&g_model_imag [y0 * mdl_x + x1], dd01 * imag); + hip_atomic_add(&g_model_weight[y0 * mdl_x + x1], dd01 * Fweight); + + hip_atomic_add(&g_model_real [y1 * mdl_x + x0], dd10 * real); + hip_atomic_add(&g_model_imag [y1 * mdl_x + x0], dd10 * imag); + hip_atomic_add(&g_model_weight[y1 * mdl_x + x0], dd10 * Fweight); + + hip_atomic_add(&g_model_real [y1 * mdl_x + x1], dd11 * real); + hip_atomic_add(&g_model_imag [y1 * mdl_x + x1], dd11 * imag); + hip_atomic_add(&g_model_weight[y1 * mdl_x + x1], dd11 * Fweight); + } + } +} + +template < bool DATA3D, bool CTF_PREMULTIPLIED > +__global__ void hip_kernel_backproject3D( + XFLOAT *g_img_real, + XFLOAT *g_img_imag, + XFLOAT *g_trans_x, + XFLOAT *g_trans_y, + XFLOAT *g_trans_z, + XFLOAT* g_weights, + XFLOAT* g_Minvsigma2s, + XFLOAT* g_ctfs, + unsigned long translation_num, + XFLOAT significant_weight, + XFLOAT weight_norm, + XFLOAT *g_eulers, + XFLOAT *g_model_real, + XFLOAT *g_model_imag, + XFLOAT *g_model_weight, + int max_r, + int max_r2, + XFLOAT padding_factor, + unsigned img_x, + unsigned img_y, + unsigned img_z, + unsigned img_xyz, + unsigned mdl_x, + unsigned mdl_y, + int mdl_inity, + int mdl_initz) +{ + unsigned tid = threadIdx.x; + unsigned img = blockIdx.x; + + int img_y_half = img_y / 2; + int img_z_half = img_z / 2; + + int max_r2_vol = max_r2 * padding_factor * padding_factor; + + __shared__ XFLOAT s_eulers[9]; + XFLOAT minvsigma2, ctf, img_real, img_imag, Fweight, real, imag, weight; + + if (tid < 9) + s_eulers[tid] = g_eulers[img*9+tid]; + + __syncthreads(); + + int pixel_pass_num(0); + if(DATA3D) + pixel_pass_num = (ceilf((float)img_xyz/(float)BP_DATA3D_BLOCK_SIZE)); + else + pixel_pass_num = (ceilf((float)img_xyz/(float)BP_REF3D_BLOCK_SIZE)); + + for (unsigned pass = 0; pass < pixel_pass_num; pass++) + { + unsigned pixel(0); + if(DATA3D) + pixel = (pass * BP_DATA3D_BLOCK_SIZE) + tid; + else + pixel = (pass * BP_REF3D_BLOCK_SIZE) + tid; + + if (pixel >= img_xyz) + continue; + + int x,y,z,xy; + + if(DATA3D) + { + z = floorfracf(pixel, img_x*img_y); + xy = pixel % (img_x*img_y); + x = xy % img_x; + y = floorfracf( xy, img_x); + + if (z > img_z_half) + { + z = z - img_z; + + if(x==0) + continue; + } + } + else + { + x = pixel % img_x; + y = floorfracf( pixel , img_x); + } + if (y > img_y_half) + { + y = y - img_y; + } + + //WAVG + minvsigma2 = __ldg(&g_Minvsigma2s[pixel]); + ctf = __ldg(&g_ctfs[pixel]); + img_real = __ldg(&g_img_real[pixel]); + img_imag = __ldg(&g_img_imag[pixel]); + Fweight = (XFLOAT) 0.0; + real = (XFLOAT) 0.0; + imag = (XFLOAT) 0.0; + + XFLOAT temp_real, temp_imag; + + for (unsigned long itrans = 0; itrans < translation_num; itrans++) + { + weight = g_weights[img * translation_num + itrans]; + + if (weight >= significant_weight) + { + if(CTF_PREMULTIPLIED) + { + weight = (weight / weight_norm) * minvsigma2; + Fweight += weight * ctf; // SHWS 13feb2020: from now on when ctf_premultiplied, the ctf array actually contains ctf^2! + } + else + { + weight = (weight / weight_norm) * ctf * minvsigma2; + Fweight += weight * ctf; + } + + if(DATA3D) + translatePixel(x, y, z, g_trans_x[itrans], g_trans_y[itrans], g_trans_z[itrans], img_real, img_imag, temp_real, temp_imag); + else + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], img_real, img_imag, temp_real, temp_imag); + + real += temp_real * weight; + imag += temp_imag * weight; + } + } + + //BP + if (Fweight > (XFLOAT) 0.0) + { + // Get logical coordinates in the 3D map + + XFLOAT xp,yp,zp; + if(DATA3D) + { + xp = (s_eulers[0] * x + s_eulers[1] * y + s_eulers[2] * z) * padding_factor; + yp = (s_eulers[3] * x + s_eulers[4] * y + s_eulers[5] * z) * padding_factor; + zp = (s_eulers[6] * x + s_eulers[7] * y + s_eulers[8] * z) * padding_factor; + } + else + { + xp = (s_eulers[0] * x + s_eulers[1] * y ) * padding_factor; + yp = (s_eulers[3] * x + s_eulers[4] * y ) * padding_factor; + zp = (s_eulers[6] * x + s_eulers[7] * y ) * padding_factor; + } + + // Only consider pixels that are projected inside the sphere in output coordinates. + // --JZ, Oct. 18. 2018 + if ( ( xp * xp + yp * yp + zp * zp ) > max_r2_vol) + continue; + + // Only asymmetric half is stored + if (xp < (XFLOAT) 0.0) + { + // Get complex conjugated hermitian symmetry pair + xp = -xp; + yp = -yp; + zp = -zp; + imag = -imag; + } + + int x0 = floorf(xp); + XFLOAT fx = xp - x0; + int x1 = x0 + 1; + + int y0 = floorf(yp); + XFLOAT fy = yp - y0; + y0 -= mdl_inity; + int y1 = y0 + 1; + + int z0 = floorf(zp); + XFLOAT fz = zp - z0; + z0 -= mdl_initz; + int z1 = z0 + 1; + + XFLOAT mfx = (XFLOAT)1.0 - fx; + XFLOAT mfy = (XFLOAT)1.0 - fy; + XFLOAT mfz = (XFLOAT)1.0 - fz; + + XFLOAT dd000 = mfz * mfy * mfx; + + hip_atomic_add(&g_model_real [z0 * mdl_x * mdl_y + y0 * mdl_x + x0], dd000 * real); + hip_atomic_add(&g_model_imag [z0 * mdl_x * mdl_y + y0 * mdl_x + x0], dd000 * imag); + hip_atomic_add(&g_model_weight[z0 * mdl_x * mdl_y + y0 * mdl_x + x0], dd000 * Fweight); + + XFLOAT dd001 = mfz * mfy * fx; + + hip_atomic_add(&g_model_real [z0 * mdl_x * mdl_y + y0 * mdl_x + x1], dd001 * real); + hip_atomic_add(&g_model_imag [z0 * mdl_x * mdl_y + y0 * mdl_x + x1], dd001 * imag); + hip_atomic_add(&g_model_weight[z0 * mdl_x * mdl_y + y0 * mdl_x + x1], dd001 * Fweight); + + XFLOAT dd010 = mfz * fy * mfx; + + hip_atomic_add(&g_model_real [z0 * mdl_x * mdl_y + y1 * mdl_x + x0], dd010 * real); + hip_atomic_add(&g_model_imag [z0 * mdl_x * mdl_y + y1 * mdl_x + x0], dd010 * imag); + hip_atomic_add(&g_model_weight[z0 * mdl_x * mdl_y + y1 * mdl_x + x0], dd010 * Fweight); + + XFLOAT dd011 = mfz * fy * fx; + + hip_atomic_add(&g_model_real [z0 * mdl_x * mdl_y + y1 * mdl_x + x1], dd011 * real); + hip_atomic_add(&g_model_imag [z0 * mdl_x * mdl_y + y1 * mdl_x + x1], dd011 * imag); + hip_atomic_add(&g_model_weight[z0 * mdl_x * mdl_y + y1 * mdl_x + x1], dd011 * Fweight); + + XFLOAT dd100 = fz * mfy * mfx; + + hip_atomic_add(&g_model_real [z1 * mdl_x * mdl_y + y0 * mdl_x + x0], dd100 * real); + hip_atomic_add(&g_model_imag [z1 * mdl_x * mdl_y + y0 * mdl_x + x0], dd100 * imag); + hip_atomic_add(&g_model_weight[z1 * mdl_x * mdl_y + y0 * mdl_x + x0], dd100 * Fweight); + + XFLOAT dd101 = fz * mfy * fx; + + hip_atomic_add(&g_model_real [z1 * mdl_x * mdl_y + y0 * mdl_x + x1], dd101 * real); + hip_atomic_add(&g_model_imag [z1 * mdl_x * mdl_y + y0 * mdl_x + x1], dd101 * imag); + hip_atomic_add(&g_model_weight[z1 * mdl_x * mdl_y + y0 * mdl_x + x1], dd101 * Fweight); + + XFLOAT dd110 = fz * fy * mfx; + + hip_atomic_add(&g_model_real [z1 * mdl_x * mdl_y + y1 * mdl_x + x0], dd110 * real); + hip_atomic_add(&g_model_imag [z1 * mdl_x * mdl_y + y1 * mdl_x + x0], dd110 * imag); + hip_atomic_add(&g_model_weight[z1 * mdl_x * mdl_y + y1 * mdl_x + x0], dd110 * Fweight); + + XFLOAT dd111 = fz * fy * fx; + + hip_atomic_add(&g_model_real [z1 * mdl_x * mdl_y + y1 * mdl_x + x1], dd111 * real); + hip_atomic_add(&g_model_imag [z1 * mdl_x * mdl_y + y1 * mdl_x + x1], dd111 * imag); + hip_atomic_add(&g_model_weight[z1 * mdl_x * mdl_y + y1 * mdl_x + x1], dd111 * Fweight); + + } + } +} + + +template < bool DATA3D, bool CTF_PREMULTIPLIED > +__global__ void hip_kernel_backproject3D_SGD( + AccProjectorKernel projector, + XFLOAT *g_img_real, + XFLOAT *g_img_imag, + XFLOAT *g_trans_x, + XFLOAT *g_trans_y, + XFLOAT *g_trans_z, + XFLOAT* g_weights, + XFLOAT* g_Minvsigma2s, + XFLOAT* g_ctfs, + unsigned long translation_num, + XFLOAT significant_weight, + XFLOAT weight_norm, + XFLOAT *g_eulers, + XFLOAT *g_model_real, + XFLOAT *g_model_imag, + XFLOAT *g_model_weight, + int max_r, + int max_r2, + XFLOAT padding_factor, + unsigned img_x, + unsigned img_y, + unsigned img_z, + unsigned img_xyz, + unsigned mdl_x, + unsigned mdl_y, + int mdl_inity, + int mdl_initz) +{ + unsigned tid = threadIdx.x; + unsigned img = blockIdx.x; + + int img_y_half = img_y / 2; + int img_z_half = img_z / 2; + + int max_r2_vol = max_r2 * padding_factor * padding_factor; + + __shared__ XFLOAT s_eulers[9]; + XFLOAT minvsigma2, ctf, img_real, img_imag, Fweight, real, imag, weight; + + if (tid < 9) + s_eulers[tid] = g_eulers[img*9+tid]; + + __syncthreads(); + + int pixel_pass_num(0); + if(DATA3D) + pixel_pass_num = (ceilf((float)img_xyz/(float)BP_DATA3D_BLOCK_SIZE)); + else + pixel_pass_num = (ceilf((float)img_xyz/(float)BP_REF3D_BLOCK_SIZE)); + + for (unsigned pass = 0; pass < pixel_pass_num; pass++) + { + unsigned pixel(0); + if(DATA3D) + pixel = (pass * BP_DATA3D_BLOCK_SIZE) + tid; + else + pixel = (pass * BP_REF3D_BLOCK_SIZE) + tid; + + if (pixel >= img_xyz) + continue; + + int x,y,z,xy; + + if(DATA3D) + { + z = floorfracf(pixel, img_x*img_y); + xy = pixel % (img_x*img_y); + x = xy % img_x; + y = floorfracf( xy, img_x); + + if (z > img_z_half) + { + z = z - img_z; + + if(x==0) + continue; + } + } + else + { + x = pixel % img_x; + y = floorfracf( pixel , img_x); + } + if (y > img_y_half) + { + y = y - img_y; + } + + XFLOAT ref_real = (XFLOAT) 0.0; + XFLOAT ref_imag = (XFLOAT) 0.0; + + if(DATA3D) + projector.project3Dmodel( + x,y,z, + s_eulers[0], s_eulers[1], s_eulers[2], + s_eulers[3], s_eulers[4], s_eulers[5], + s_eulers[6], s_eulers[7], s_eulers[8], + ref_real, ref_imag); + else + projector.project3Dmodel( + x,y, + s_eulers[0], s_eulers[1], + s_eulers[3], s_eulers[4], + s_eulers[6], s_eulers[7], + ref_real, ref_imag); + + //WAVG + minvsigma2 = __ldg(&g_Minvsigma2s[pixel]); + ctf = __ldg(&g_ctfs[pixel]); + img_real = __ldg(&g_img_real[pixel]); + img_imag = __ldg(&g_img_imag[pixel]); + Fweight = (XFLOAT) 0.0; + real = (XFLOAT) 0.0; + imag = (XFLOAT) 0.0; + ref_real *= ctf; + ref_imag *= ctf; + + XFLOAT temp_real, temp_imag; + + for (unsigned long itrans = 0; itrans < translation_num; itrans++) + { + weight = g_weights[img * translation_num + itrans]; + + if (weight >= significant_weight) + { + if(CTF_PREMULTIPLIED) + { + weight = (weight / weight_norm) * minvsigma2; + Fweight += weight * ctf; // SHWS 13feb2020: from now on when ctf_premultiplied, the ctf array actually contains ctf^2! + } + else + { + weight = (weight / weight_norm) * ctf * minvsigma2; + Fweight += weight * ctf; + } + if(DATA3D) + translatePixel(x, y, z, g_trans_x[itrans], g_trans_y[itrans], g_trans_z[itrans], img_real, img_imag, temp_real, temp_imag); + else + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], img_real, img_imag, temp_real, temp_imag); + + real += (temp_real-ref_real) * weight; + imag += (temp_imag-ref_imag) * weight; + } + } + + //BP + if (Fweight > (XFLOAT) 0.0) + { + // Get logical coordinates in the 3D map + + XFLOAT xp,yp,zp; + if(DATA3D) + { + xp = (s_eulers[0] * x + s_eulers[1] * y + s_eulers[2] * z) * padding_factor; + yp = (s_eulers[3] * x + s_eulers[4] * y + s_eulers[5] * z) * padding_factor; + zp = (s_eulers[6] * x + s_eulers[7] * y + s_eulers[8] * z) * padding_factor; + } + else + { + xp = (s_eulers[0] * x + s_eulers[1] * y ) * padding_factor; + yp = (s_eulers[3] * x + s_eulers[4] * y ) * padding_factor; + zp = (s_eulers[6] * x + s_eulers[7] * y ) * padding_factor; + } + + // Only consider pixels that are projected inside the sphere in output coordinates. + // --JZ, Nov. 26th 2018 + if ( ( xp * xp + yp * yp + zp * zp ) > max_r2_vol) + continue; + + // Only asymmetric half is stored + if (xp < (XFLOAT) 0.0) + { + // Get complex conjugated hermitian symmetry pair + xp = -xp; + yp = -yp; + zp = -zp; + imag = -imag; + } + + int x0 = floorf(xp); + XFLOAT fx = xp - x0; + int x1 = x0 + 1; + + int y0 = floorf(yp); + XFLOAT fy = yp - y0; + y0 -= mdl_inity; + int y1 = y0 + 1; + + int z0 = floorf(zp); + XFLOAT fz = zp - z0; + z0 -= mdl_initz; + int z1 = z0 + 1; + + XFLOAT mfx = (XFLOAT)1.0 - fx; + XFLOAT mfy = (XFLOAT)1.0 - fy; + XFLOAT mfz = (XFLOAT)1.0 - fz; + + XFLOAT dd000 = mfz * mfy * mfx; + + hip_atomic_add(&g_model_real [z0 * mdl_x * mdl_y + y0 * mdl_x + x0], dd000 * real); + hip_atomic_add(&g_model_imag [z0 * mdl_x * mdl_y + y0 * mdl_x + x0], dd000 * imag); + hip_atomic_add(&g_model_weight[z0 * mdl_x * mdl_y + y0 * mdl_x + x0], dd000 * Fweight); + + XFLOAT dd001 = mfz * mfy * fx; + + hip_atomic_add(&g_model_real [z0 * mdl_x * mdl_y + y0 * mdl_x + x1], dd001 * real); + hip_atomic_add(&g_model_imag [z0 * mdl_x * mdl_y + y0 * mdl_x + x1], dd001 * imag); + hip_atomic_add(&g_model_weight[z0 * mdl_x * mdl_y + y0 * mdl_x + x1], dd001 * Fweight); + + XFLOAT dd010 = mfz * fy * mfx; + + hip_atomic_add(&g_model_real [z0 * mdl_x * mdl_y + y1 * mdl_x + x0], dd010 * real); + hip_atomic_add(&g_model_imag [z0 * mdl_x * mdl_y + y1 * mdl_x + x0], dd010 * imag); + hip_atomic_add(&g_model_weight[z0 * mdl_x * mdl_y + y1 * mdl_x + x0], dd010 * Fweight); + + XFLOAT dd011 = mfz * fy * fx; + + hip_atomic_add(&g_model_real [z0 * mdl_x * mdl_y + y1 * mdl_x + x1], dd011 * real); + hip_atomic_add(&g_model_imag [z0 * mdl_x * mdl_y + y1 * mdl_x + x1], dd011 * imag); + hip_atomic_add(&g_model_weight[z0 * mdl_x * mdl_y + y1 * mdl_x + x1], dd011 * Fweight); + + XFLOAT dd100 = fz * mfy * mfx; + + hip_atomic_add(&g_model_real [z1 * mdl_x * mdl_y + y0 * mdl_x + x0], dd100 * real); + hip_atomic_add(&g_model_imag [z1 * mdl_x * mdl_y + y0 * mdl_x + x0], dd100 * imag); + hip_atomic_add(&g_model_weight[z1 * mdl_x * mdl_y + y0 * mdl_x + x0], dd100 * Fweight); + + XFLOAT dd101 = fz * mfy * fx; + + hip_atomic_add(&g_model_real [z1 * mdl_x * mdl_y + y0 * mdl_x + x1], dd101 * real); + hip_atomic_add(&g_model_imag [z1 * mdl_x * mdl_y + y0 * mdl_x + x1], dd101 * imag); + hip_atomic_add(&g_model_weight[z1 * mdl_x * mdl_y + y0 * mdl_x + x1], dd101 * Fweight); + + XFLOAT dd110 = fz * fy * mfx; + + hip_atomic_add(&g_model_real [z1 * mdl_x * mdl_y + y1 * mdl_x + x0], dd110 * real); + hip_atomic_add(&g_model_imag [z1 * mdl_x * mdl_y + y1 * mdl_x + x0], dd110 * imag); + hip_atomic_add(&g_model_weight[z1 * mdl_x * mdl_y + y1 * mdl_x + x0], dd110 * Fweight); + + XFLOAT dd111 = fz * fy * fx; + + hip_atomic_add(&g_model_real [z1 * mdl_x * mdl_y + y1 * mdl_x + x1], dd111 * real); + hip_atomic_add(&g_model_imag [z1 * mdl_x * mdl_y + y1 * mdl_x + x1], dd111 * imag); + hip_atomic_add(&g_model_weight[z1 * mdl_x * mdl_y + y1 * mdl_x + x1], dd111 * Fweight); + + } + } +} + + +template < bool CTF_PREMULTIPLIED > +__global__ void hip_kernel_backproject2D_SGD( + AccProjectorKernel projector, + XFLOAT *g_img_real, + XFLOAT *g_img_imag, + XFLOAT *g_trans_x, + XFLOAT *g_trans_y, + XFLOAT* g_weights, + XFLOAT* g_Minvsigma2s, + XFLOAT* g_ctfs, + unsigned long translation_num, + XFLOAT significant_weight, + XFLOAT weight_norm, + XFLOAT *g_eulers, + XFLOAT *g_model_real, + XFLOAT *g_model_imag, + XFLOAT *g_model_weight, + int max_r, + int max_r2, + XFLOAT padding_factor, + unsigned img_x, + unsigned img_y, + unsigned img_xy, + unsigned mdl_x, + int mdl_inity) +{ + unsigned tid = threadIdx.x; + unsigned img = blockIdx.x; + + int img_y_half = img_y / 2; + int max_r2_out = max_r2 * padding_factor * padding_factor; + + __shared__ XFLOAT s_eulers[4]; + + XFLOAT minvsigma2, ctf, img_real, img_imag, Fweight, real, imag, weight; + + if (tid == 0) + s_eulers[0] = g_eulers[img*9+0]; + else if (tid == 1) + s_eulers[1] = g_eulers[img*9+1]; + else if (tid == 2) + s_eulers[2] = g_eulers[img*9+3]; + else if (tid == 3) + s_eulers[3] = g_eulers[img*9+4]; + + __syncthreads(); + + int pixel_pass_num(ceilf((float)img_xy/(float)BP_2D_BLOCK_SIZE)); + + for (unsigned pass = 0; pass < pixel_pass_num; pass++) + { + unsigned pixel = (pass * BP_2D_BLOCK_SIZE) + tid; + + if (pixel >= img_xy) + continue; + + int x = pixel % img_x; + int y = (int)floorf( (float)pixel / (float)img_x); + + if (y > img_y_half) + { + y -= img_y; + } + + //WAVG + minvsigma2 = __ldg(&g_Minvsigma2s[pixel]); + ctf = __ldg(&g_ctfs[pixel]); + img_real = __ldg(&g_img_real[pixel]); + img_imag = __ldg(&g_img_imag[pixel]); + Fweight = (XFLOAT) 0.0; + real = (XFLOAT) 0.0; + imag = (XFLOAT) 0.0; + + XFLOAT temp_real, temp_imag; + + XFLOAT ref_real = (XFLOAT) 0.0; + XFLOAT ref_imag = (XFLOAT) 0.0; + + projector.project2Dmodel( + x,y, + s_eulers[0], s_eulers[1], + s_eulers[2], s_eulers[3], + ref_real, ref_imag + ); + ref_real *= ctf; + ref_imag *= ctf; + + for (unsigned long itrans = 0; itrans < translation_num; itrans++) + { + weight = g_weights[img * translation_num + itrans]; + + if (weight >= significant_weight) + { + if(CTF_PREMULTIPLIED) + { + weight = (weight / weight_norm) * minvsigma2; + Fweight += weight * ctf; // SHWS 13feb2020: from now on when ctf_premultiplied, the ctf array actually contains ctf^2! + } + else + { + weight = (weight / weight_norm) * ctf * minvsigma2; + Fweight += weight * ctf; + } + + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], img_real, img_imag, temp_real, temp_imag); + + real += (temp_real-ref_real) * weight; + imag += (temp_imag-ref_imag) * weight; + } + } + + if (Fweight > (XFLOAT) 0.0) + { + + // Get logical coordinates in the 3D map + XFLOAT xp = (s_eulers[0] * x + s_eulers[1] * y ) * padding_factor; + XFLOAT yp = (s_eulers[2] * x + s_eulers[3] * y ) * padding_factor; + + // Only consider pixels that are projected inside the allowed circle in output coordinates. + // --JZ, Nov. 26th 2018 + if ( ( xp * xp + yp * yp ) > max_r2_out ) + continue; + + // Only asymmetric half is stored + if (xp < 0) + { + // Get complex conjugated hermitian symmetry pair + xp = -xp; + yp = -yp; + imag = -imag; + } + + int x0 = floorf(xp); + XFLOAT fx = xp - x0; + int x1 = x0 + 1; + + int y0 = floorf(yp); + XFLOAT fy = yp - y0; + y0 -= mdl_inity; + int y1 = y0 + 1; + + XFLOAT mfx = (XFLOAT) 1.0 - fx; + XFLOAT mfy = (XFLOAT) 1.0 - fy; + + XFLOAT dd00 = mfy * mfx; + XFLOAT dd01 = mfy * fx; + XFLOAT dd10 = fy * mfx; + XFLOAT dd11 = fy * fx; + + hip_atomic_add(&g_model_real [y0 * mdl_x + x0], dd00 * real); + hip_atomic_add(&g_model_imag [y0 * mdl_x + x0], dd00 * imag); + hip_atomic_add(&g_model_weight[y0 * mdl_x + x0], dd00 * Fweight); + + hip_atomic_add(&g_model_real [y0 * mdl_x + x1], dd01 * real); + hip_atomic_add(&g_model_imag [y0 * mdl_x + x1], dd01 * imag); + hip_atomic_add(&g_model_weight[y0 * mdl_x + x1], dd01 * Fweight); + + hip_atomic_add(&g_model_real [y1 * mdl_x + x0], dd10 * real); + hip_atomic_add(&g_model_imag [y1 * mdl_x + x0], dd10 * imag); + hip_atomic_add(&g_model_weight[y1 * mdl_x + x0], dd10 * Fweight); + + hip_atomic_add(&g_model_real [y1 * mdl_x + x1], dd11 * real); + hip_atomic_add(&g_model_imag [y1 * mdl_x + x1], dd11 * imag); + hip_atomic_add(&g_model_weight[y1 * mdl_x + x1], dd11 * Fweight); + } + } +} + +#endif /* HIP_BP_KERNELS_H_ */ diff --git a/src/acc/hip/hip_kernels/diff2.h b/src/acc/hip/hip_kernels/diff2.h new file mode 100644 index 000000000..56a55f739 --- /dev/null +++ b/src/acc/hip/hip_kernels/diff2.h @@ -0,0 +1,615 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_DIFF2_KERNELS_H_ +#define HIP_DIFF2_KERNELS_H_ + +#include +#include +#include +#include +#include "src/acc/acc_projector.h" +#include "src/acc/acc_projectorkernel_impl.h" +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_kernels/hip_device_utils.h" + + +/* + * DIFFERNECE-BASED KERNELS + */ + +/* + * Assuming block_sz % prefetch_fraction == 0 and prefetch_fraction < block_sz + * Assuming block_sz % eulers_per_block == 0 + * Assuming eulers_per_block * 3 < block_sz + */ +template +__global__ void hip_kernel_diff2_coarse( + XFLOAT *g_eulers, + XFLOAT *trans_x, + XFLOAT *trans_y, + XFLOAT *trans_z, + XFLOAT *g_real, + XFLOAT *g_imag, + AccProjectorKernel projector, + XFLOAT *g_corr, + XFLOAT *g_diff2s, + int translation_num, + int image_size + ) +{ + int tid = threadIdx.x; + + //Prefetch euler matrices + __shared__ XFLOAT s_eulers[eulers_per_block * 9]; + + int max_block_pass_euler( ceilfracf(eulers_per_block*9, block_sz) * block_sz); + + for (int i = tid; i < max_block_pass_euler; i += block_sz) + if (i < eulers_per_block * 9) + s_eulers[i] = g_eulers[blockIdx.x * eulers_per_block * 9 + i]; + + + //Setup variables + __shared__ XFLOAT s_ref_real[block_sz/prefetch_fraction * eulers_per_block]; + __shared__ XFLOAT s_ref_imag[block_sz/prefetch_fraction * eulers_per_block]; + + __shared__ XFLOAT s_real[block_sz]; + __shared__ XFLOAT s_imag[block_sz]; + __shared__ XFLOAT s_corr[block_sz]; + + XFLOAT diff2s[eulers_per_block] = {0.f}; + + XFLOAT tx = trans_x[tid%translation_num]; + XFLOAT ty = trans_y[tid%translation_num]; + XFLOAT tz = trans_z[tid%translation_num]; + + //Step through data + int max_block_pass_pixel( ceilfracf(image_size,block_sz) * block_sz ); + + for (int init_pixel = 0; init_pixel < max_block_pass_pixel; init_pixel += block_sz/prefetch_fraction) + { + __syncthreads(); + + //Prefetch block-fraction-wise + if(init_pixel + tid/prefetch_fraction < image_size) + { + int x,y,z,xy; + if(DATA3D) + { + z = floorfracf(init_pixel + tid/prefetch_fraction, projector.imgX*projector.imgY); + xy = (init_pixel + tid/prefetch_fraction) % (projector.imgX*projector.imgY); + x = xy % projector.imgX; + y = floorfracf( xy, projector.imgX); + if (z > projector.maxR) + z -= projector.imgZ; + } + else + { + x = ( init_pixel + tid/prefetch_fraction) % projector.imgX; + y = floorfracf( init_pixel + tid/prefetch_fraction , projector.imgX); + } + if (y > projector.maxR) + y -= projector.imgY; + + #pragma unroll + for (int i = tid%prefetch_fraction; i < eulers_per_block; i += prefetch_fraction) + { + if(DATA3D) // if DATA3D, then REF3D as well. + projector.project3Dmodel( + x,y,z, + s_eulers[i*9 ], + s_eulers[i*9+1], + s_eulers[i*9+2], + s_eulers[i*9+3], + s_eulers[i*9+4], + s_eulers[i*9+5], + s_eulers[i*9+6], + s_eulers[i*9+7], + s_eulers[i*9+8], + s_ref_real[eulers_per_block * (tid/prefetch_fraction) + i], + s_ref_imag[eulers_per_block * (tid/prefetch_fraction) + i]); + else if(REF3D) + projector.project3Dmodel( + x,y, + s_eulers[i*9 ], + s_eulers[i*9+1], + s_eulers[i*9+3], + s_eulers[i*9+4], + s_eulers[i*9+6], + s_eulers[i*9+7], + s_ref_real[eulers_per_block * (tid/prefetch_fraction) + i], + s_ref_imag[eulers_per_block * (tid/prefetch_fraction) + i]); + else + projector.project2Dmodel( + x,y, + s_eulers[i*9 ], + s_eulers[i*9+1], + s_eulers[i*9+3], + s_eulers[i*9+4], + s_ref_real[eulers_per_block * (tid/prefetch_fraction) + i], + s_ref_imag[eulers_per_block * (tid/prefetch_fraction) + i]); + } + } + + //Prefetch block-wise + if (init_pixel % block_sz == 0 && init_pixel + tid < image_size) + { + s_real[tid] = g_real[init_pixel + tid]; + s_imag[tid] = g_imag[init_pixel + tid]; + s_corr[tid] = g_corr[init_pixel + tid] / 2; + } + + __syncthreads(); + + if (tid/translation_num < block_sz/translation_num) // NOTE int division A/B==C/B !=> A==C + for (int i = tid / translation_num; + i < block_sz/prefetch_fraction; + i += block_sz/translation_num) + { + if((init_pixel + i) >= image_size) break; + + int x,y,z,xy; + if(DATA3D) + { + z = floorfracf( init_pixel + i , projector.imgX*projector.imgY); //TODO optimize index extraction. + xy = ( init_pixel + i ) % (projector.imgX*projector.imgY); + x = xy % projector.imgX; + y = floorfracf( xy, projector.imgX); + if (z > projector.maxR) + z -= projector.imgZ; + } + else + { + x = ( init_pixel + i ) % projector.imgX; + y = floorfracf( init_pixel + i , projector.imgX); + } + if (y > projector.maxR) + y -= projector.imgY; + + XFLOAT real, imag; + + if(DATA3D) + translatePixel(x, y, z, tx, ty, tz, s_real[i + init_pixel % block_sz], s_imag[i + init_pixel % block_sz], real, imag); + else + translatePixel(x, y, tx, ty, s_real[i + init_pixel % block_sz], s_imag[i + init_pixel % block_sz], real, imag); + + + #pragma unroll + for (int j = 0; j < eulers_per_block; j ++) + { + XFLOAT diff_real = s_ref_real[eulers_per_block * i + j] - real; + XFLOAT diff_imag = s_ref_imag[eulers_per_block * i + j] - imag; + diff2s[j] += (diff_real * diff_real + diff_imag * diff_imag) * s_corr[i + init_pixel % block_sz]; + } + } + } + + //Set global + #pragma unroll + for (int i = 0; i < eulers_per_block; i ++) + hip_atomic_add(&g_diff2s[(blockIdx.x * eulers_per_block + i) * translation_num + tid % translation_num], diff2s[i]); +} + + +template +__global__ void hip_kernel_diff2_fine( + XFLOAT *g_eulers, + XFLOAT *g_imgs_real, + XFLOAT *g_imgs_imag, + XFLOAT *trans_x, + XFLOAT *trans_y, + XFLOAT *trans_z, + AccProjectorKernel projector, + XFLOAT *g_corr_img, + XFLOAT *g_diff2s, + unsigned image_size, + XFLOAT sum_init, + unsigned long orientation_num, + unsigned long translation_num, + unsigned long todo_blocks, + unsigned long *d_rot_idx, + unsigned long *d_trans_idx, + unsigned long *d_job_idx, + unsigned long *d_job_num + ) +{ + unsigned long bid = blockIdx.x; + unsigned long tid = threadIdx.x; + +// // Specialize BlockReduce for a 1D block of 128 threads on type XFLOAT +// typedef hipcub::BlockReduce BlockReduce; +// // Allocate shared memory for BlockReduce +// __shared__ typename BlockReduce::TempStorage temp_storage; + + unsigned long pixel; + XFLOAT ref_real, ref_imag, + shifted_real, shifted_imag, + diff_real, diff_imag; + + __shared__ XFLOAT s[block_sz*chunk_sz]; //We MAY have to do up to chunk_sz translations in each block + __shared__ XFLOAT s_outs[chunk_sz]; + // inside the padded 2D orientation gri +// if( bid < todo_blocks ) // we only need to make + { + unsigned trans_num = (unsigned)d_job_num[bid]; //how many transes we have for this rot + for (int itrans=0; itrans projector.maxR) + { + if (z >= projector.imgZ - projector.maxR) + z = z - projector.imgZ; + else + x = projector.maxR; + } + } + else + { + x = pixel % projector.imgX; + y = floorfracf( pixel , projector.imgX); + } + if (y > projector.maxR) + { + if (y >= projector.imgY - projector.maxR) + y = y - projector.imgY; + else + x = projector.maxR; + } + + if(DATA3D) + projector.project3Dmodel( + x,y,z, + __ldg(&g_eulers[ix*9 ]), __ldg(&g_eulers[ix*9+1]), __ldg(&g_eulers[ix*9+2]), + __ldg(&g_eulers[ix*9+3]), __ldg(&g_eulers[ix*9+4]), __ldg(&g_eulers[ix*9+5]), + __ldg(&g_eulers[ix*9+6]), __ldg(&g_eulers[ix*9+7]), __ldg(&g_eulers[ix*9+8]), + ref_real, ref_imag); + else if(REF3D) + projector.project3Dmodel( + x,y, + __ldg(&g_eulers[ix*9 ]), __ldg(&g_eulers[ix*9+1]), + __ldg(&g_eulers[ix*9+3]), __ldg(&g_eulers[ix*9+4]), + __ldg(&g_eulers[ix*9+6]), __ldg(&g_eulers[ix*9+7]), + ref_real, ref_imag); + else + projector.project2Dmodel( + x,y, + __ldg(&g_eulers[ix*9 ]), __ldg(&g_eulers[ix*9+1]), + __ldg(&g_eulers[ix*9+3]), __ldg(&g_eulers[ix*9+4]), + ref_real, ref_imag); + + for (int itrans=0; itrans0; j/=2) + { + if(tid +__global__ void hip_kernel_diff2_CC_coarse( + XFLOAT *g_eulers, + XFLOAT *g_imgs_real, + XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, + XFLOAT *g_trans_y, + XFLOAT *g_trans_z, + AccProjectorKernel projector, + XFLOAT *g_corr_img, + XFLOAT *g_diff2s, + unsigned translation_num, + int image_size, + XFLOAT exp_local_sqrtXi2 + ) +{ + + int iorient = blockIdx.x; + int itrans = blockIdx.y; + int tid = threadIdx.x; + + __shared__ XFLOAT s_weight[block_sz]; + s_weight[tid] = (XFLOAT)0.0; + __shared__ XFLOAT s_norm[block_sz]; + s_norm[tid] = (XFLOAT)0.0; + + XFLOAT real, imag, ref_real, ref_imag; + + XFLOAT e0,e1,e2,e3,e4,e5,e6,e7,e8; + e0 = __ldg(&g_eulers[iorient*9 ]); + e1 = __ldg(&g_eulers[iorient*9+1]); + e2 = __ldg(&g_eulers[iorient*9+2]); + e3 = __ldg(&g_eulers[iorient*9+3]); + e4 = __ldg(&g_eulers[iorient*9+4]); + e5 = __ldg(&g_eulers[iorient*9+5]); + e6 = __ldg(&g_eulers[iorient*9+6]); + e7 = __ldg(&g_eulers[iorient*9+7]); + e8 = __ldg(&g_eulers[iorient*9+8]); + + __syncthreads(); + + unsigned pixel_pass_num( ceilfracf(image_size,block_sz) ); + for (unsigned pass = 0; pass < pixel_pass_num; pass++) + { + unsigned pixel = (pass * block_sz) + tid; + + if(pixel < image_size) + { + int x,y,z,xy; + if(DATA3D) + { + z = floorfracf(pixel, projector.imgX*projector.imgY); + xy = pixel % (projector.imgX*projector.imgY); + x = xy % projector.imgX; + y = floorfracf( xy, projector.imgX); + if (z > projector.maxR) + { + if (z >= projector.imgZ - projector.maxR) + z = z - projector.imgZ; + else + x = projector.maxR; + } + } + else + { + x = pixel % projector.imgX; + y = floorfracf( pixel , projector.imgX); + } + if (y > projector.maxR) + { + if (y >= projector.imgY - projector.maxR) + y = y - projector.imgY; + else + x = projector.maxR; + } + + if(DATA3D) + projector.project3Dmodel( + x,y,z, + e0,e1,e2,e3,e4,e5,e6,e7,e8, + ref_real, ref_imag); + else if(REF3D) + projector.project3Dmodel( + x,y, + e0,e1,e3,e4,e6,e7, + ref_real, ref_imag); + else + projector.project2Dmodel( + x,y, + e0,e1,e3,e4, + ref_real, ref_imag); + + if(DATA3D) + translatePixel(x, y, z, g_trans_x[itrans], g_trans_y[itrans], g_trans_z[itrans], g_imgs_real[pixel], g_imgs_imag[pixel], real, imag); + else + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], g_imgs_real[pixel], g_imgs_imag[pixel], real, imag); + + s_weight[tid] += (ref_real * real + ref_imag * imag) * __ldg(&g_corr_img[pixel]); + s_norm[tid] += (ref_real * ref_real + ref_imag * ref_imag ) * __ldg(&g_corr_img[pixel]); + } + __syncthreads(); + } + + + for(int j=(block_sz/2); j>0; j/=2) + { + if(tid +__global__ void hip_kernel_diff2_CC_fine( + XFLOAT *g_eulers, + XFLOAT *g_imgs_real, + XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, + XFLOAT *g_trans_y, + XFLOAT *g_trans_z, + AccProjectorKernel projector, + XFLOAT *g_corr_img, + XFLOAT *g_diff2s, + unsigned image_size, + XFLOAT sum_init, + XFLOAT exp_local_sqrtXi2, + unsigned long orientation_num, + unsigned long translation_num, + unsigned long todo_blocks, + unsigned long *d_rot_idx, + unsigned long *d_trans_idx, + unsigned long *d_job_idx, + unsigned long *d_job_num + ) +{ + int bid = blockIdx.y * gridDim.x + blockIdx.x; + int tid = threadIdx.x; + +// // Specialize BlockReduce for a 1D block of 128 threads on type XFLOAT +// typedef hipcub::BlockReduce BlockReduce; +// // Allocate shared memory for BlockReduce +// __shared__ typename BlockReduce::TempStorage temp_storage; + + int pixel; + XFLOAT ref_real, ref_imag, shifted_real, shifted_imag; + + __shared__ XFLOAT s[block_sz*chunk_sz]; //We MAY have to do up to chunk_sz translations in each block + __shared__ XFLOAT s_cc[block_sz*chunk_sz]; + __shared__ XFLOAT s_outs[chunk_sz]; + + if( bid < todo_blocks ) // we only need to make + { + unsigned trans_num = d_job_num[bid]; //how many transes we have for this rot + for (int itrans=0; itrans projector.maxR) + { + if (z >= projector.imgZ - projector.maxR) + z = z - projector.imgZ; + else + x = projector.maxR; + } + } + else + { + x = pixel % projector.imgX; + y = floorfracf( pixel , projector.imgX); + } + + if (y > projector.maxR) + { + if (y >= projector.imgY - projector.maxR) + y = y - projector.imgY; + else + x = projector.maxR; + } + + if(DATA3D) + projector.project3Dmodel( + x,y,z, + __ldg(&g_eulers[ix*9 ]), __ldg(&g_eulers[ix*9+1]), __ldg(&g_eulers[ix*9+2]), + __ldg(&g_eulers[ix*9+3]), __ldg(&g_eulers[ix*9+4]), __ldg(&g_eulers[ix*9+5]), + __ldg(&g_eulers[ix*9+6]), __ldg(&g_eulers[ix*9+7]), __ldg(&g_eulers[ix*9+8]), + ref_real, ref_imag); + else if(REF3D) + projector.project3Dmodel( + x,y, + __ldg(&g_eulers[ix*9 ]), __ldg(&g_eulers[ix*9+1]), + __ldg(&g_eulers[ix*9+3]), __ldg(&g_eulers[ix*9+4]), + __ldg(&g_eulers[ix*9+6]), __ldg(&g_eulers[ix*9+7]), + ref_real, ref_imag); + else + projector.project2Dmodel( + x,y, + __ldg(&g_eulers[ix*9 ]), __ldg(&g_eulers[ix*9+1]), + __ldg(&g_eulers[ix*9+3]), __ldg(&g_eulers[ix*9+4]), + ref_real, ref_imag); + + for (int itrans=0; itrans0; j/=2) + { + if(tid +#include +#include +#include +#include +#include +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_kernels/hip_device_utils.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_projectorkernel_impl.h" + +template +__global__ void hip_kernel_weights_exponent_coarse( + T *g_pdf_orientation, + bool *g_pdf_orientation_zeros, + T *g_pdf_offset, + bool *g_pdf_offset_zeros, + T *g_weights, + T g_min_diff2, + int nr_coarse_orient, + int nr_coarse_trans, + int max_idx) +{ + int bid = blockIdx.x; + int tid = threadIdx.x; + + int idx = bid*SUMW_BLOCK_SIZE+tid; + + if(idx < max_idx) + { + int itrans = idx % nr_coarse_trans; + int iorient = (idx - itrans) / nr_coarse_trans; + + T diff2 = g_weights[idx]; + if( diff2 < g_min_diff2 || g_pdf_orientation_zeros[iorient] || g_pdf_offset_zeros[itrans]) + g_weights[idx] = -99e99; //large negative number + else + g_weights[idx] = g_pdf_orientation[iorient] + g_pdf_offset[itrans] + g_min_diff2 - diff2; + } +} + +template +__global__ void hip_kernel_exponentiate( + T *g_array, + T add, + size_t size) +{ + int idx = threadIdx.x + blockIdx.x*BLOCK_SIZE; + if(idx < size) + { + T a = g_array[idx] + add; +#ifdef ACC_DOUBLE_PRECISION + if (a < -700.) + g_array[idx] = 0.f; + else + g_array[idx] = exp(a); +#else + if (a < -88.f) + g_array[idx] = 0.; + else + g_array[idx] = expf(a); +#endif + } +} + +template +__global__ void hip_kernel_collect2jobs( XFLOAT *g_oo_otrans_x, // otrans-size -> make const + XFLOAT *g_oo_otrans_y, // otrans-size -> make const + XFLOAT *g_oo_otrans_z, // otrans-size -> make const + XFLOAT *g_myp_oo_otrans_x2y2z2, // otrans-size -> make const + XFLOAT *g_i_weights, + XFLOAT op_significant_weight, // TODO Put in const + XFLOAT op_sum_weight, // TODO Put in const + int coarse_trans, + int oversamples_trans, + int oversamples_orient, + int oversamples, + bool do_ignore_pdf_direction, + XFLOAT *g_o_weights, + XFLOAT *g_thr_wsum_prior_offsetx_class, + XFLOAT *g_thr_wsum_prior_offsety_class, + XFLOAT *g_thr_wsum_prior_offsetz_class, + XFLOAT *g_thr_wsum_sigma2_offset, + unsigned long *d_rot_idx, + unsigned long *d_trans_idx, + unsigned long *d_job_idx, + unsigned long *d_job_num + ) +{ + // blockid + int bid = blockIdx.x; + //threadid + int tid = threadIdx.x; + + extern __shared__ XFLOAT buffer[]; + + XFLOAT * s_o_weights = &buffer[ 0]; + XFLOAT * s_thr_wsum_sigma2_offset = &buffer[ SUMW_BLOCK_SIZE]; + XFLOAT * s_thr_wsum_prior_offsetx_class = &buffer[2*SUMW_BLOCK_SIZE]; + XFLOAT * s_thr_wsum_prior_offsety_class = &buffer[3*SUMW_BLOCK_SIZE]; + XFLOAT * s_thr_wsum_prior_offsetz_class(0); + + if(DATA3D) + s_thr_wsum_prior_offsetz_class = &buffer[4*SUMW_BLOCK_SIZE]; + + s_o_weights[tid] = (XFLOAT)0.0; + s_thr_wsum_sigma2_offset[tid] = (XFLOAT)0.0; + + s_thr_wsum_prior_offsetx_class[tid] = (XFLOAT)0.0; + s_thr_wsum_prior_offsety_class[tid] = (XFLOAT)0.0; + if(DATA3D) + s_thr_wsum_prior_offsety_class[tid] = (XFLOAT)0.0; + + long int pos = d_job_idx[bid]; + int job_size = d_job_num[bid]; + pos += tid; // pos is updated to be thread-resolved + + int pass_num = ceilfracf(job_size,SUMW_BLOCK_SIZE); + __syncthreads(); + for (int pass = 0; pass < pass_num; pass++, pos+=SUMW_BLOCK_SIZE) // loop the available warps enough to complete all translations for this orientation + { + if ((pass*SUMW_BLOCK_SIZE+tid)= op_significant_weight ) //TODO Might be slow (divergent threads) + weight /= op_sum_weight; + else + weight = (XFLOAT)0.0; + + s_o_weights[tid] += weight; + s_thr_wsum_sigma2_offset[tid] += weight * g_myp_oo_otrans_x2y2z2[iy]; + s_thr_wsum_prior_offsetx_class[tid] += weight * g_oo_otrans_x[iy]; + s_thr_wsum_prior_offsety_class[tid] += weight * g_oo_otrans_y[iy]; + if(DATA3D) + s_thr_wsum_prior_offsetz_class[tid] += weight * g_oo_otrans_z[iy]; + + } + } + __syncthreads(); + // Reduction of all treanslations this orientation + for(int j=(SUMW_BLOCK_SIZE/2); j>0; j/=2) + { + if(tid +__global__ void hip_kernel_translate2D( T * g_image_in, + T * g_image_out, + int image_size, + int xdim, + int ydim, + int dx, + int dy) +{ + int tid = threadIdx.x; + int bid = blockIdx.x; + + int x,y,xp,yp; + int pixel=tid + bid*BLOCK_SIZE; + int new_pixel; + + if(pixel=0 && xp>=0 && yp=0 && new_pixel +__global__ void hip_kernel_translate3D( T * g_image_in, + T * g_image_out, + int image_size, + int xdim, + int ydim, + int zdim, + int dx, + int dy, + int dz) +{ + int tid = threadIdx.x; + int bid = blockIdx.x; + + int x,y,z,xp,yp,zp,xy; + int voxel=tid + bid*BLOCK_SIZE; + int new_voxel; + + int xydim = xdim*ydim; + + if(voxel=0 && yp>=0 && xp>=0 && zp=0 && new_voxel +__global__ void hip_kernel_multi( T *A, + T *OUT, + T S, + int image_size) +{ + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + if(pixel +__global__ void hip_kernel_multi( T *A, + T S, + int image_size) +{ + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + if(pixel +__global__ void hip_kernel_add( T *A, + T S, + int image_size) +{ + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + if(pixel +__global__ void hip_kernel_multi( T *A, + T *B, + T *OUT, + T S, + int image_size) +{ + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + if(pixel +__global__ void hip_kernel_cast( + T1 *IN, + T2 *OUT, + int size) +{ + int pixel = threadIdx.x + blockIdx.x*BLOCK_SIZE; + if(pixel +__global__ void hip_kernel_frequencyPass( + ACCCOMPLEX *A, + long int ori_size, + size_t Xdim, + size_t Ydim, + size_t Zdim, + XFLOAT edge_low, + XFLOAT edge_width, + XFLOAT edge_high, + XFLOAT angpix, + int image_size) +{ + int texel = threadIdx.x + blockIdx.x*BLOCK_SIZE; + + int z = texel / (Xdim*Ydim); + int xy = (texel - z*Xdim*Ydim); + int y = xy / Xdim; + + int xp = xy - y*Xdim; + + int zp = ( z lows are dead + { + A[texel].x = 0.; + A[texel].y = 0.; + } + else if (res < edge_high) //highpass => medium lows are almost dead + { + XFLOAT mul = 0.5 - 0.5 * cos( PI * (res-edge_low)/edge_width); + A[texel].x *= mul; + A[texel].y *= mul; + } + } + else //lowpass + { + if (res > edge_high) //lowpass => highs are dead + { + A[texel].x = 0.; + A[texel].y = 0.; + } + else if (res > edge_low) //lowpass => medium highs are almost dead + { + XFLOAT mul = 0.5 + 0.5 * cos( PI * (res-edge_low)/edge_width); + A[texel].x *= mul; + A[texel].y *= mul; + } + } + } +} + +template +__global__ void hip_kernel_powerClass( ACCCOMPLEX * g_image, + XFLOAT * g_spectrum, + int image_size, + int spectrum_size, + int xdim, + int ydim, + int zdim, + int res_limit, + XFLOAT * g_highres_Xi2) +{ + int tid = threadIdx.x; + int bid = blockIdx.x; + + XFLOAT normFaux; + __shared__ XFLOAT s_highres_Xi2[POWERCLASS_BLOCK_SIZE]; + s_highres_Xi2[tid] = (XFLOAT)0.; + + int x,y,xy,d; + int xydim = xdim*ydim; + int voxel=tid + bid*POWERCLASS_BLOCK_SIZE; + bool coords_in_range(true); + + if(voxel0.f) && (ires=res_limit) + s_highres_Xi2[tid] = normFaux; + } + } + + // Reduce the higres_Xi2-values for all threads. (I tried a straight atomic-write: for 128 threads it was ~3x slower) + __syncthreads(); + for(int j=(POWERCLASS_BLOCK_SIZE/2); j>0.f; j/=2) + { + if(tid +__global__ void hip_kernel_make_eulers_2D( + XFLOAT *alphas, + XFLOAT *eulers, + unsigned orientation_num); + +template +__global__ void hip_kernel_make_eulers_3D( + XFLOAT *alphas, + XFLOAT *betas, + XFLOAT *gammas, + XFLOAT *eulers, + unsigned orientation_num, + XFLOAT *L, + XFLOAT *R); + +#define INIT_VALUE_BLOCK_SIZE 512 +template< typename T> +__global__ void hip_kernel_init_complex_value( + T *data, + XFLOAT value, + size_t size) +{ + size_t idx = blockIdx.x * INIT_VALUE_BLOCK_SIZE + threadIdx.x; + if (idx < size) + { + data[idx].x = value; + data[idx].y = value; + } +} + +template< typename T> +__global__ void hip_kernel_init_value( + T *data, + T value, + size_t size) +{ + size_t idx = blockIdx.x * INIT_VALUE_BLOCK_SIZE + threadIdx.x; + if (idx < size) + data[idx] = value; +} + +#define WEIGHT_MAP_BLOCK_SIZE 512 +__global__ void hip_kernel_allweights_to_mweights( + unsigned long * d_iorient, + XFLOAT * d_allweights, + XFLOAT * d_mweights, + unsigned long orientation_num, + unsigned long translation_num, + int block_size + ); + +#define OVER_THRESHOLD_BLOCK_SIZE 512 +template< typename T> +__global__ void hip_kernel_array_over_threshold( + T *data, + bool *passed, + T threshold, + size_t size) +{ + size_t idx = blockIdx.x * OVER_THRESHOLD_BLOCK_SIZE + threadIdx.x; + if (idx < size) + { + if (data[idx] >= threshold) + passed[idx] = true; + else + passed[idx] = false; + } +} + +#define FIND_IN_CUMULATIVE_BLOCK_SIZE 512 +template< typename T> +__global__ void hip_kernel_find_threshold_idx_in_cumulative( + T *data, + T threshold, + size_t size_m1, //data size minus 1 + size_t *idx) +{ + size_t i = blockIdx.x * FIND_IN_CUMULATIVE_BLOCK_SIZE + threadIdx.x; + if (i < size_m1 && data[i] <= threshold && threshold < data[i+1]) + idx[0] = i+1; +} + +#define WINDOW_FT_BLOCK_SIZE 128 +template +__global__ void hip_kernel_window_fourier_transform( + XFLOAT *g_in_real, + XFLOAT *g_in_imag, + XFLOAT *g_out_real, + XFLOAT *g_out_imag, + unsigned iX, unsigned iY, unsigned iZ, unsigned iYX, //Input dimensions + unsigned oX, unsigned oY, unsigned oZ, unsigned oYX, //Output dimensions + unsigned max_idx, + unsigned max_r2 = 0 + ) +{ + unsigned n = threadIdx.x + WINDOW_FT_BLOCK_SIZE * blockIdx.x; + long int image_offset = oX*oY*oZ*blockIdx.y; + if (n >= max_idx) return; + + int k, i, kp, ip, jp; + + if (check_max_r2) + { + k = n / (iX * iY); + i = (n % (iX * iY)) / iX; + + kp = k < iX ? k : k - iZ; + ip = i < iX ? i : i - iY; + jp = n % iX; + + if (kp*kp + ip*ip + jp*jp > max_r2) + return; + } + else + { + k = n / (oX * oY); + i = (n % (oX * oY)) / oX; + + kp = k < oX ? k : k - oZ; + ip = i < oX ? i : i - oY; + jp = n % oX; + } + + g_out_real[(kp < 0 ? kp + oZ : kp) * oYX + (ip < 0 ? ip + oY : ip)*oX + jp + image_offset] = g_in_real[(kp < 0 ? kp + iZ : kp)*iYX + (ip < 0 ? ip + iY : ip)*iX + jp + image_offset]; + g_out_imag[(kp < 0 ? kp + oZ : kp) * oYX + (ip < 0 ? ip + oY : ip)*oX + jp + image_offset] = g_in_imag[(kp < 0 ? kp + iZ : kp)*iYX + (ip < 0 ? ip + iY : ip)*iX + jp + image_offset]; +} + +#define WINDOW_FT_BLOCK_SIZE 128 +template +__global__ void hip_kernel_window_fourier_transform( + ACCCOMPLEX *g_in, + ACCCOMPLEX *g_out, + size_t iX, size_t iY, size_t iZ, size_t iYX, //Input dimensions + size_t oX, size_t oY, size_t oZ, size_t oYX, //Output dimensions + size_t max_idx, + size_t max_r2 = 0 + ) +{ + size_t n = threadIdx.x + WINDOW_FT_BLOCK_SIZE * blockIdx.x; + size_t oOFF = oX*oY*oZ*blockIdx.y; + size_t iOFF = iX*iY*iZ*blockIdx.y; + if (n >= max_idx) return; + + long int k, i, kp, ip, jp; + + if (check_max_r2) + { + k = n / (iX * iY); + i = (n % (iX * iY)) / iX; + + kp = k < iX ? k : k - iZ; + ip = i < iX ? i : i - iY; + jp = n % iX; + + if (kp*kp + ip*ip + jp*jp > max_r2) + return; + } + else + { + k = n / (oX * oY); + i = (n % (oX * oY)) / oX; + + kp = k < oX ? k : k - oZ; + ip = i < oX ? i : i - oY; + jp = n % oX; + } + + long int in_idx = (kp < 0 ? kp + iZ : kp) * iYX + (ip < 0 ? ip + iY : ip)*iX + jp; + long int out_idx = (kp < 0 ? kp + oZ : kp) * oYX + (ip < 0 ? ip + oY : ip)*oX + jp; + g_out[out_idx + oOFF] = g_in[in_idx + iOFF]; +} + +#define NEAREST_NEIGHBOUR 0 +#define TRILINEAR 1 +__global__ void hip_kernel_griddingCorrect(RFLOAT *vol, int interpolator, RFLOAT rrval, RFLOAT r_min_nn, + size_t iX, size_t iY, size_t iZ); + +template +__global__ void hip_kernel_window_transform( + T *d_in, T *d_out, + int iszX, int iszY, int iszZ, //Input dimensions + int oftX, int oftY, int oftZ, int oszX, int oszY, int oszZ //Output dimensions + ) +{ + int idx = blockIdx.x*blockDim.x + threadIdx.x; + int idy = blockIdx.y*blockDim.y + threadIdx.y; + int idz = blockIdx.z*blockDim.z + threadIdx.z; + + if(idx < oszX && idy < oszY && idz =oftX) && (idx=oftY) && (idy=oftZ) && (idz +__global__ void hip_kernel_centerFFT_2D(T *img_in, + int image_size, + int xdim, + int ydim, + int xshift, + int yshift) +{ + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + long int image_offset = image_size*blockIdx.y; + + if(pixel<(image_size/2)) + { + int y = floorf((XFLOAT)pixel/(XFLOAT)xdim); + int x = pixel % xdim; // also = pixel - y*xdim, but this depends on y having been calculated, i.e. serial evaluation + + int xp = (x + xshift + xdim)%xdim; + int yp = (y + yshift + ydim)%ydim; + int n_pixel = yp*xdim + xp; + + T buffer = img_in[image_offset + n_pixel]; + img_in[image_offset + n_pixel] = img_in[image_offset + pixel]; + img_in[image_offset + pixel] = buffer; + } +} +template __global__ void hip_kernel_centerFFT_2D(double*, int, int, int, int, int); +template __global__ void hip_kernel_centerFFT_2D(float*, int, int, int, int, int); + +template +__global__ void hip_kernel_centerFFT_3D(T *img_in, + int image_size, + int xdim, + int ydim, + int zdim, + int xshift, + int yshift, + int zshift) +{ + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + long int image_offset = image_size*blockIdx.y; + + int xydim = xdim*ydim; + if(pixel<(image_size/2)) + { + int z = floorf((XFLOAT)pixel/(XFLOAT)(xydim)); + int xy = pixel % xydim; + int y = floorf((XFLOAT)xy/(XFLOAT)xdim); + int x = xy % xdim; + + int xp = (x + xshift + xdim)%xdim; + int yp = (y + yshift + ydim)%ydim; + int zp = (z + zshift + zdim)%zdim; + + int n_pixel = zp*xydim + yp*xdim + xp; + + T buffer = img_in[image_offset + n_pixel]; + img_in[image_offset + n_pixel] = img_in[image_offset + pixel]; + img_in[image_offset + pixel] = buffer; + } +} + +template __global__ void hip_kernel_centerFFT_3D(double*, int, int, int, int, int, int, int); +template __global__ void hip_kernel_centerFFT_3D(float*, int, int, int, int, int, int, int); + +template +__global__ void hip_kernel_centerFFTbySign(T *img_in, + int xdim, + int ydim, + int zdim) +{ + int x = threadIdx.x + blockIdx.x*blockDim.x; + int y = threadIdx.y + blockIdx.y*blockDim.y; + int z = threadIdx.z + blockIdx.z*blockDim.z; + + int pixel = z*xdim*ydim + y*xdim + x; + if(x(double2*, int, int, int); +template __global__ void hip_kernel_centerFFTbySign(float2*, int, int, int); + +#if !defined(__HIP_ARCH__) || __HIP_ARCH__ != gfx906 +#else +__device__ double atomicAdd(double* address, double val) +{ + unsigned long long int* address_as_ull = (unsigned long long int*)address; + unsigned long long int old = *address_as_ull, assumed; + do { + assumed = old; + old = atomicCAS(address_as_ull, assumed, + __double_as_longlong(val + __longlong_as_double(assumed))); + } while (assumed != old); + return __longlong_as_double(old); +} +#endif + +template +__global__ void hip_kernel_calcPowerSpectrum(T *dFaux, int padoridim, T *ddata, int data_sz, RFLOAT *dpower_spectrum, RFLOAT *dcounter, + int max_r2, int min_r2, RFLOAT normfft, RFLOAT padding_factor, RFLOAT weight, + RFLOAT *dfourier_mask, int fx, int fy, int fz, bool do_fourier_mask) +{ + int idx = blockIdx.x*blockDim.x + threadIdx.x; + int idy = blockIdx.y*blockDim.y + threadIdx.y; + int idz = blockIdx.z*blockDim.z + threadIdx.z; + int XSIZE = padoridim/2+1; + int dx, dxy; + dx = (data_sz/2+1); + dxy = (blockDim.z != 1)? data_sz*dx:0; + + if(idx +#include + +/// Needed explicit template instantiations +template __global__ void hip_kernel_make_eulers_2D(XFLOAT *, + XFLOAT *, unsigned); +template __global__ void hip_kernel_make_eulers_2D(XFLOAT *, + XFLOAT *, unsigned); + +template __global__ void hip_kernel_make_eulers_3D(XFLOAT *, + XFLOAT *, XFLOAT *, XFLOAT *, unsigned, XFLOAT *, XFLOAT *); +template __global__ void hip_kernel_make_eulers_3D(XFLOAT *, + XFLOAT *, XFLOAT *, XFLOAT *, unsigned, XFLOAT *, XFLOAT *); +template __global__ void hip_kernel_make_eulers_3D(XFLOAT *, + XFLOAT *, XFLOAT *, XFLOAT *, unsigned, XFLOAT *, XFLOAT *); +template __global__ void hip_kernel_make_eulers_3D(XFLOAT *, + XFLOAT *, XFLOAT *, XFLOAT *, unsigned, XFLOAT *, XFLOAT *); +template __global__ void hip_kernel_make_eulers_3D(XFLOAT *, + XFLOAT *, XFLOAT *, XFLOAT *, unsigned, XFLOAT *, XFLOAT *); +template __global__ void hip_kernel_make_eulers_3D(XFLOAT *, + XFLOAT *, XFLOAT *, XFLOAT *, unsigned, XFLOAT *, XFLOAT *); +template __global__ void hip_kernel_make_eulers_3D(XFLOAT *, + XFLOAT *, XFLOAT *, XFLOAT *, unsigned, XFLOAT *, XFLOAT *); +template __global__ void hip_kernel_make_eulers_3D(XFLOAT *, + XFLOAT *, XFLOAT *, XFLOAT *, unsigned, XFLOAT *, XFLOAT *); + +/* + * This draft of a kernel assumes input that has jobs which have a single orientation and sequential translations within each job. + * + */ +__global__ void hip_kernel_exponentiate_weights_fine( + XFLOAT *g_pdf_orientation, + bool *g_pdf_orientation_zeros, + XFLOAT *g_pdf_offset, + bool *g_pdf_offset_zeros, + XFLOAT *g_weights, + XFLOAT min_diff2, + int oversamples_orient, + int oversamples_trans, + unsigned long *d_rot_id, + unsigned long *d_trans_idx, + unsigned long *d_job_idx, + unsigned long *d_job_num, + long int job_num) +{ + // blockid + int bid = blockIdx.x; + //threadid + int tid = threadIdx.x; + + long int jobid = bid*SUMW_BLOCK_SIZE+tid; + + if (jobid=xdim) + y -= (xdim-1)*2; //assuming square input images (particles) + + int ires = rintf(sqrtf(x*x + y*y)); +#if defined(ACC_DOUBLE_PRECISION) + XFLOAT scale = 0.; + if(ires=xdim) + z -= (xdim-1)*2; //assuming square input images (particles) + if(y>=xdim) + y -= (xdim-1)*2; //assuming square input images (particles) + + + int ires = rintf(sqrtf(x*x + y*y + z*z)); +#if defined(ACC_DOUBLE_PRECISION) + XFLOAT scale = 0.; + if(ires radius_p) + { + partial_sum[tid] += (XFLOAT)1.0; + partial_sum_bg[tid] += img_pixels[tid]; + } + else + { +#if defined(ACC_DOUBLE_PRECISION) + raisedcos = 0.5 + 0.5 * cospi( (radius_p - r) / cosine_width ); +#else + raisedcos = 0.5f + 0.5f * cospif((radius_p - r) / cosine_width ); +#endif + partial_sum[tid] += raisedcos; + partial_sum_bg[tid] += raisedcos * img_pixels[tid]; + } + } + } + } + + __syncthreads(); + for(int j=(SOFTMASK_BLOCK_SIZE/2); j>0; j/=2) + { + if(tid radius_p) + img_pixels[tid]=sum_bg_total; + else + { +#if defined(ACC_DOUBLE_PRECISION) + raisedcos = 0.5 + 0.5 * cospi( (radius_p - r) / cosine_width ); +#else + raisedcos = 0.5f + 0.5f * cospif((radius_p - r) / cosine_width ); +#endif + img_pixels[tid]= img_pixels[tid]*(1-raisedcos) + sum_bg_total*raisedcos; + + } + vol[texel]=img_pixels[tid]; + } + + } +} + +__global__ void hip_kernel_softMaskBackgroundValue( XFLOAT *vol, + long int vol_size, + long int xdim, + long int ydim, + long int zdim, + long int xinit, + long int yinit, + long int zinit, + XFLOAT radius, + XFLOAT radius_p, + XFLOAT cosine_width, + XFLOAT *g_sum, + XFLOAT *g_sum_bg) +{ + + int tid = threadIdx.x; + int bid = blockIdx.x; + +// vol.setXmippOrigin(); // sets xinit=xdim , also for y z + XFLOAT r, raisedcos; + int x,y,z; + __shared__ XFLOAT img_pixels[SOFTMASK_BLOCK_SIZE]; + __shared__ XFLOAT partial_sum[SOFTMASK_BLOCK_SIZE]; + __shared__ XFLOAT partial_sum_bg[SOFTMASK_BLOCK_SIZE]; + + long int texel_pass_num = ceilfracf(vol_size,SOFTMASK_BLOCK_SIZE*gridDim.x); + int texel = bid*SOFTMASK_BLOCK_SIZE*texel_pass_num + tid; + + partial_sum[tid]=(XFLOAT)0.0; + partial_sum_bg[tid]=(XFLOAT)0.0; + + for (int pass = 0; pass < texel_pass_num; pass++, texel+=SOFTMASK_BLOCK_SIZE) // loop the available warps enough to complete all translations for this orientation + { + if(texel radius_p) + { + partial_sum[tid] += (XFLOAT)1.0; + partial_sum_bg[tid] += img_pixels[tid]; + } + else + { +#if defined(ACC_DOUBLE_PRECISION) + raisedcos = 0.5 + 0.5 * cospi( (radius_p - r) / cosine_width ); +#else + raisedcos = 0.5f + 0.5f * cospif((radius_p - r) / cosine_width ); +#endif + partial_sum[tid] += raisedcos; + partial_sum_bg[tid] += raisedcos * img_pixels[tid]; + } + } + } + + hip_atomic_add(&g_sum[tid] , partial_sum[tid]); + hip_atomic_add(&g_sum_bg[tid], partial_sum_bg[tid]); +} + + +__global__ void hip_kernel_cosineFilter( XFLOAT *vol, + long int vol_size, + long int xdim, + long int ydim, + long int zdim, + long int xinit, + long int yinit, + long int zinit, + bool do_noise, + XFLOAT *noise, + XFLOAT radius, + XFLOAT radius_p, + XFLOAT cosine_width, + XFLOAT bg_value) +{ + + int tid = threadIdx.x; + int bid = blockIdx.x; + +// vol.setXmippOrigin(); // sets xinit=xdim , also for y z + XFLOAT r, raisedcos, defVal; + int x,y,z; + __shared__ XFLOAT img_pixels[SOFTMASK_BLOCK_SIZE]; + + long int texel_pass_num = ceilfracf(vol_size,SOFTMASK_BLOCK_SIZE*gridDim.x); + int texel = bid*SOFTMASK_BLOCK_SIZE*texel_pass_num + tid; + + defVal = bg_value; + for (int pass = 0; pass < texel_pass_num; pass++, texel+=SOFTMASK_BLOCK_SIZE) // loop the available warps enough to complete all translations for this orientation + { + if(texel radius_p) + img_pixels[tid]=defVal; + else + { +#if defined(ACC_DOUBLE_PRECISION) + raisedcos = 0.5 + 0.5 * cospi( (radius_p - r) / cosine_width ); +#else + raisedcos = 0.5f + 0.5f * cospif((radius_p - r) / cosine_width ); +#endif + img_pixels[tid]= img_pixels[tid]*(1-raisedcos) + defVal*raisedcos; + + } + vol[texel]=img_pixels[tid]; + } + + } +} + +__global__ void hip_kernel_probRatio( XFLOAT *d_Mccf, + XFLOAT *d_Mpsi, + XFLOAT *d_Maux, + XFLOAT *d_Mmean, + XFLOAT *d_Mstddev, + int image_size, + XFLOAT normfft, + XFLOAT sum_ref_under_circ_mask, + XFLOAT sum_ref2_under_circ_mask, + XFLOAT expected_Pratio, + int NpsiThisBatch, + int startPsi, + int totalPsis) +{ + /* PLAN TO: + * + * 1) Pre-filter + * d_Mstddev[i] = 1 / (2*d_Mstddev[i]) ( if d_Mstddev[pixel] > 1E-10 ) + * d_Mstddev[i] = 1 ( else ) + * + * 2) Set + * sum_ref2_under_circ_mask /= 2. + * + * 3) Total expression becomes + * diff2 = ( exp(k) - 1.f ) / (expected_Pratio - 1.f) + * where + * k = (normfft * d_Maux[pixel] + d_Mmean[pixel] * sum_ref_under_circ_mask)*d_Mstddev[i] + sum_ref2_under_circ_mask + * + */ + + int pixel = threadIdx.x + blockIdx.x*(int)PROBRATIO_BLOCK_SIZE; + if(pixel (XFLOAT)1E-10) + diff2 *= d_Mstddev[pixel]; + diff2 += sum_ref2_under_circ_mask; + +#if defined(ACC_DOUBLE_PRECISION) + diff2 = exp(-diff2 / 2.); // exponentiate to reflect the Gaussian error model. sigma=1 after normalization, 0.4=1/sqrt(2pi) +#else + diff2 = expf(-diff2 / 2.f); +#endif + + // Store fraction of (1 - probability-ratio) wrt (1 - expected Pratio) + diff2 = (diff2 - (XFLOAT)1.0) / (expected_Pratio - (XFLOAT)1.0); + if (diff2 > Kccf) + { + Kccf = diff2; + Kpsi = (startPsi + psi)*(360/totalPsis); + } + } + d_Mccf[pixel] = Kccf; + if (Kpsi >= 0.) + d_Mpsi[pixel] = Kpsi; + } +} + +__global__ void hip_kernel_rotateOnly( ACCCOMPLEX *d_Faux, + XFLOAT psi, + AccProjectorKernel projector, + int startPsi + ) +{ + int proj = blockIdx.y; + int image_size=projector.imgX*projector.imgY; + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + if(pixel projector.maxR) + { + if (y >= projector.imgY - projector.maxR) + y = y - projector.imgY; + else + x = projector.maxR; + } + + XFLOAT sa, ca; + #if defined(ACC_DOUBLE_PRECISION) + sincos((proj+startPsi)*psi, &sa, &ca); + #else + sincosf((proj+startPsi)*psi, &sa, &ca); + #endif + ACCCOMPLEX val; + + projector.project2Dmodel( x,y, + ca, + -sa, + sa, + ca, + val.x,val.y); + + long int out_pixel = proj*image_size + pixel; + + d_Faux[out_pixel].x =val.x; + d_Faux[out_pixel].y =val.y; + } +} + +__global__ void hip_kernel_rotateAndCtf( ACCCOMPLEX *d_Faux, + XFLOAT *d_ctf, + XFLOAT psi, + AccProjectorKernel projector, + int startPsi + ) +{ + int proj = blockIdx.y; + int image_size=projector.imgX*projector.imgY; + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + if(pixel projector.maxR) + { + if (y >= projector.imgY - projector.maxR) + y = y - projector.imgY; + else + x = projector.maxR; + } + + XFLOAT sa, ca; + #if defined(ACC_DOUBLE_PRECISION) + sincos((proj+startPsi)*psi, &sa, &ca); + #else + sincosf((proj+startPsi)*psi, &sa, &ca); + #endif + ACCCOMPLEX val; + + projector.project2Dmodel( x,y, + ca, + -sa, + sa, + ca, + val.x,val.y); + + long int out_pixel = proj*image_size + pixel; + + d_Faux[out_pixel].x =val.x*d_ctf[pixel]; + d_Faux[out_pixel].y =val.y*d_ctf[pixel]; + + } +} + + +__global__ void hip_kernel_convol_A( ACCCOMPLEX *d_A, + ACCCOMPLEX *d_B, + int image_size) +{ + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + if(pixel 0) + Mstddev[pixel] = sqrt(temp); + else + Mstddev[pixel] = 0; + } +} + +__global__ void hip_kernel_square( + XFLOAT *A, + int image_size) +{ + int pixel = threadIdx.x + blockIdx.x*blockDim.x; + if(pixel +__global__ void hip_kernel_make_eulers_2D( + XFLOAT *alphas, + XFLOAT *eulers, + unsigned orientation_num) +{ + unsigned oid = blockIdx.x * blockDim.x + threadIdx.x; //Orientation id + + if (oid >= orientation_num) + return; + + XFLOAT ca, sa; + XFLOAT a = alphas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + +#ifdef ACC_DOUBLE_PRECISION + sincos(a, &sa, &ca); +#else + sincosf(a, &sa, &ca); +#endif + + if(!invert) + { + eulers[9 * oid + 0] = ca;//00 + eulers[9 * oid + 1] = sa;//01 + eulers[9 * oid + 2] = 0 ;//02 + eulers[9 * oid + 3] =-sa;//10 + eulers[9 * oid + 4] = ca;//11 + eulers[9 * oid + 5] = 0 ;//12 + eulers[9 * oid + 6] = 0 ;//20 + eulers[9 * oid + 7] = 0 ;//21 + eulers[9 * oid + 8] = 1 ;//22 + } + else + { + eulers[9 * oid + 0] = ca;//00 + eulers[9 * oid + 1] =-sa;//10 + eulers[9 * oid + 2] = 0 ;//20 + eulers[9 * oid + 3] = sa;//01 + eulers[9 * oid + 4] = ca;//11 + eulers[9 * oid + 5] = 0 ;//21 + eulers[9 * oid + 6] = 0 ;//02 + eulers[9 * oid + 7] = 0 ;//12 + eulers[9 * oid + 8] = 1 ;//22 + } +} + +template +__global__ void hip_kernel_make_eulers_3D( + XFLOAT *alphas, + XFLOAT *betas, + XFLOAT *gammas, + XFLOAT *eulers, + unsigned orientation_num, + XFLOAT *L, + XFLOAT *R) +{ + XFLOAT a(0.f),b(0.f),g(0.f), A[9],B[9]; + XFLOAT ca, sa, cb, sb, cg, sg, cc, cs, sc, ss; + + unsigned oid = blockIdx.x * blockDim.x + threadIdx.x; //Orientation id + + if (oid >= orientation_num) + return; + + for (int i = 0; i < 9; i ++) + B[i] = (XFLOAT) 0.f; + + a = alphas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + b = betas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + g = gammas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + +#ifdef ACC_DOUBLE_PRECISION + sincos(a, &sa, &ca); + sincos(b, &sb, &cb); + sincos(g, &sg, &cg); +#else + sincosf(a, &sa, &ca); + sincosf(b, &sb, &cb); + sincosf(g, &sg, &cg); +#endif + + cc = cb * ca; + cs = cb * sa; + sc = sb * ca; + ss = sb * sa; + + A[0] = ( cg * cc - sg * sa);//00 + A[1] = ( cg * cs + sg * ca);//01 + A[2] = (-cg * sb ) ;//02 + A[3] = (-sg * cc - cg * sa);//10 + A[4] = (-sg * cs + cg * ca);//11 + A[5] = ( sg * sb ) ;//12 + A[6] = ( sc ) ;//20 + A[7] = ( ss ) ;//21 + A[8] = ( cb ) ;//22 + + if (doR) + { + for (int i = 0; i < 9; i++) + B[i] = 0.f; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) + B[i * 3 + j] += A[i * 3 + k] * R[k * 3 + j]; + } + else + for (int i = 0; i < 9; i++) + B[i] = A[i]; + + if (doL) + { + if (doR) + for (int i = 0; i < 9; i++) + A[i] = B[i]; + + for (int i = 0; i < 9; i++) + B[i] = 0.f; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) + B[i * 3 + j] += L[i * 3 + k] * A[k * 3 + j]; + } + + if(invert) + { + + if (doL) // this could have anisotropy, so inverse neq transpose!!! + { + XFLOAT det; + det = B[0] * (B[4] * B[8] - B[7] * B[5]) + - B[1] * (B[3] * B[8] - B[6] * B[5]) + + B[2] * (B[3] * B[7] - B[6] * B[4]); + + eulers[9 * oid + 0] = (B[4] * B[8] - B[7] * B[5]) / det; + eulers[9 * oid + 1] = (B[7] * B[2] - B[1] * B[8]) / det; + eulers[9 * oid + 2] = (B[1] * B[5] - B[4] * B[2]) / det; + eulers[9 * oid + 3] = (B[5] * B[6] - B[8] * B[3]) / det; + eulers[9 * oid + 4] = (B[8] * B[0] - B[2] * B[6]) / det; + eulers[9 * oid + 5] = (B[2] * B[3] - B[5] * B[0]) / det; + eulers[9 * oid + 6] = (B[3] * B[7] - B[6] * B[4]) / det; + eulers[9 * oid + 7] = (B[6] * B[1] - B[0] * B[7]) / det; + eulers[9 * oid + 8] = (B[0] * B[4] - B[3] * B[1]) / det; + } + else + { + + eulers[9 * oid + 0] = B[0];//00 + eulers[9 * oid + 1] = B[3];//01 + eulers[9 * oid + 2] = B[6];//02 + eulers[9 * oid + 3] = B[1];//10 + eulers[9 * oid + 4] = B[4];//11 + eulers[9 * oid + 5] = B[7];//12 + eulers[9 * oid + 6] = B[2];//20 + eulers[9 * oid + 7] = B[5];//21 + eulers[9 * oid + 8] = B[8];//22 + } + } + else + { + eulers[9 * oid + 0] = B[0];//00 + eulers[9 * oid + 1] = B[1];//10 + eulers[9 * oid + 2] = B[2];//20 + eulers[9 * oid + 3] = B[3];//01 + eulers[9 * oid + 4] = B[4];//11 + eulers[9 * oid + 5] = B[5];//21 + eulers[9 * oid + 6] = B[6];//02 + eulers[9 * oid + 7] = B[7];//12 + eulers[9 * oid + 8] = B[8];//22 + } +} + +__global__ void hip_kernel_allweights_to_mweights( + unsigned long * d_iorient, + XFLOAT * d_allweights, + XFLOAT * d_mweights, + unsigned long orientation_num, + unsigned long translation_num, + int block_size + ) +{ + size_t idx = blockIdx.x * block_size + threadIdx.x; + if (idx < orientation_num*translation_num) + d_mweights[d_iorient[idx/translation_num] * translation_num + idx%translation_num] = + d_allweights[idx/translation_num * translation_num + idx%translation_num]; + // TODO - isn't this just d_allweights[idx + idx%translation_num]? Really? +} + +__global__ void hip_kernel_initOrientations(RFLOAT *pdfs, XFLOAT *pdf_orientation, bool *pdf_orientation_zeros, size_t sz) +{ + int idx = blockIdx.x*blockDim.x + threadIdx.x; + if(idx < sz){ + pdf_orientation_zeros[idx] = (pdfs[idx] == 0); + if (pdfs[idx] == 0) + pdf_orientation[idx] = 0.f; + else + pdf_orientation[idx] = log(pdfs[idx]); + } +} + +__global__ void hip_kernel_griddingCorrect(RFLOAT *vol, int interpolator, RFLOAT rrval, RFLOAT r_min_nn, + size_t iX, size_t iY, size_t iZ) +{ + int idx = blockIdx.x*blockDim.x + threadIdx.x; + int idy = blockIdx.y*blockDim.y + threadIdx.y; + int idz = blockIdx.z*blockDim.z + threadIdx.z; + if(idx 0.) + { + RFLOAT rval = r / rrval; + RFLOAT sinc = sin(PI * rval) / ( PI * rval); + if (interpolator==NEAREST_NEIGHBOUR && r_min_nn == 0.) + vol[idz*iX*iY + idy*iX + idx] /= sinc; + else if (interpolator==TRILINEAR || (interpolator==NEAREST_NEIGHBOUR && r_min_nn > 0) ) + vol[idz*iX*iY + idy*iX + idx] /= sinc * sinc; + } + } +} + +__global__ void hip_kernel_updatePowerSpectrum(RFLOAT *dcounter, RFLOAT *dpower_spectrum, int sz) +{ + int idx = blockIdx.x*blockDim.x + threadIdx.x; + if(idx +#include "src/acc/hip/hip_settings.h" + +#ifdef ACC_DOUBLE_PRECISION +__device__ inline double hip_atomic_add(double* address, double val) +{ + unsigned long long int* address_as_ull = (unsigned long long int*)address; + unsigned long long int old = *address_as_ull, assumed; + do + { + assumed = old; + old = atomicCAS(address_as_ull, assumed, __double_as_longlong(val + __longlong_as_double(assumed))); + } + while (assumed != old); // Note: uses integer comparison to avoid hang in case of NaN (since NaN != NaN) + return __longlong_as_double(old); +} +#else +__device__ inline void hip_atomic_add(float* address, float value) +{ + // atomicAddNoRet(address,value); + // unsafeAtomicAdd(address,value); + atomicAdd(address,value); +} +#endif + + +/* + * For the following functions always use fast, low-precision intrinsics + */ + +template< typename T1, typename T2 > +static inline +__device__ int floorfracf(T1 a, T2 b) +{ +// return __float2int_rd(__fdividef( (float)a, (float)b ) ); + return (int)(a/b); +} + +template< typename T1, typename T2 > +static inline +__device__ int ceilfracf(T1 a, T2 b) +{ +// return __float2int_ru(__fdividef( (float)a, (float)b ) ); + return (int)(a/b + 1); +} + +static inline +__device__ XFLOAT no_tex2D(XFLOAT* mdl, XFLOAT xp, XFLOAT yp, int mdlX, int mdlInitY) +{ + int x0 = floorf(xp); + XFLOAT fx = xp - x0; + int x1 = x0 + 1; + + int y0 = floorf(yp); + XFLOAT fy = yp - y0; + y0 -= mdlInitY; + int y1 = y0 + 1; + + //----------------------------- + XFLOAT d00 = mdl[y0*mdlX+x0]; + XFLOAT d01 = mdl[y0*mdlX+x1]; + XFLOAT d10 = mdl[y1*mdlX+x0]; + XFLOAT d11 = mdl[y1*mdlX+x1]; + //----------------------------- + XFLOAT dx0 = d00 + (d01 - d00)*fx; + XFLOAT dx1 = d10 + (d11 - d10)*fx; + //----------------------------- + + return dx0 + (dx1 - dx0)*fy; +} + +static inline +__device__ XFLOAT no_tex3D(XFLOAT* mdl, XFLOAT xp, XFLOAT yp, XFLOAT zp, int mdlX, int mdlXY, int mdlInitY, int mdlInitZ) +{ + int x0 = floorf(xp); + XFLOAT fx = xp - x0; + int x1 = x0 + 1; + + int y0 = floorf(yp); + XFLOAT fy = yp - y0; + y0 -= mdlInitY; + int y1 = y0 + 1; + + int z0 = floorf(zp); + XFLOAT fz = zp - z0; + z0 -= mdlInitZ; + int z1 = z0 + 1; + + XFLOAT d000 = mdl[z0*mdlXY+y0*mdlX+x0]; + XFLOAT d001 = mdl[z0*mdlXY+y0*mdlX+x1]; + XFLOAT d010 = mdl[z0*mdlXY+y1*mdlX+x0]; + XFLOAT d011 = mdl[z0*mdlXY+y1*mdlX+x1]; + XFLOAT d100 = mdl[z1*mdlXY+y0*mdlX+x0]; + XFLOAT d101 = mdl[z1*mdlXY+y0*mdlX+x1]; + XFLOAT d110 = mdl[z1*mdlXY+y1*mdlX+x0]; + XFLOAT d111 = mdl[z1*mdlXY+y1*mdlX+x1]; + //----------------------------- + XFLOAT dx00 = d000 + (d001 - d000)*fx; + XFLOAT dx01 = d100 + (d101 - d100)*fx; + XFLOAT dx10 = d010 + (d011 - d010)*fx; + XFLOAT dx11 = d110 + (d111 - d110)*fx; + //----------------------------- + XFLOAT dxy0 = dx00 + (dx10 - dx00)*fy; + XFLOAT dxy1 = dx01 + (dx11 - dx01)*fy; + //----------------------------- + return dxy0 + (dxy1 - dxy0)*fz; +} + +__device__ __forceinline__ void translatePixel( + int x, + int y, + XFLOAT tx, + XFLOAT ty, + XFLOAT &real, + XFLOAT &imag, + XFLOAT &tReal, + XFLOAT &tImag) +{ + XFLOAT s, c; +#ifdef ACC_DOUBLE_PRECISION + __sincos( x * tx + y * ty , &s, &c ); +#else + __sincosf( x * tx + y * ty , &s, &c ); +#endif + + tReal = c * real - s * imag; + tImag = c * imag + s * real; +} + +__device__ __forceinline__ void translatePixel( + int x, + int y, + int z, + XFLOAT tx, + XFLOAT ty, + XFLOAT tz, + XFLOAT &real, + XFLOAT &imag, + XFLOAT &tReal, + XFLOAT &tImag) +{ + XFLOAT s, c; +#ifdef ACC_DOUBLE_PRECISION + __sincos( x * tx + y * ty + z * tz, &s, &c ); +#else + __sincosf( x * tx + y * ty + z * tz, &s, &c ); +#endif + + tReal = c * real - s * imag; + tImag = c * imag + s * real; +} + +inline __device__ float2 operator*(float2 a, float b) +{ + return make_float2(a.x * b, a.y * b); +} + +inline __device__ double2 operator*(double2 a, double b) +{ + return make_double2(a.x * b, a.y * b); +} + +template< typename T> +__global__ void hip_kernel_init_complex_value( + T *data, + XFLOAT value, + size_t size, + int block_size) +{ + size_t idx = blockIdx.x * block_size + threadIdx.x; + if (idx < size) + { + data[idx].x = value; + data[idx].y = value; + } +} + +template< typename T> +__global__ void hip_kernel_init_value( + T *data, + T value, + size_t size, + int block_size) +{ + size_t idx = blockIdx.x * block_size + threadIdx.x; + if (idx < size) + data[idx] = value; +} + +template< typename T> +__global__ void hip_kernel_array_over_threshold( + T *data, + bool *passed, + T threshold, + size_t size, + int block_size) +{ + size_t idx = blockIdx.x * block_size + threadIdx.x; + if (idx < size) + { + if (data[idx] >= threshold) + passed[idx] = true; + else + passed[idx] = false; + } +} + +template< typename T> +__global__ void hip_kernel_find_threshold_idx_in_cumulative( + T *data, + T threshold, + size_t size_m1, //data size minus 1 + size_t *idx, + int block_size) +{ + size_t i = blockIdx.x * block_size + threadIdx.x; + if (i < size_m1 && data[i] <= threshold && threshold < data[i+1]) + idx[0] = i+1; +} + +template +__global__ void hip_kernel_window_fourier_transform( + XFLOAT *g_in_real, + XFLOAT *g_in_imag, + XFLOAT *g_out_real, + XFLOAT *g_out_imag, + unsigned iX, unsigned iY, unsigned iZ, unsigned iYX, //Input dimensions + unsigned oX, unsigned oY, unsigned oZ, unsigned oYX, //Output dimensions + unsigned max_idx, + int block_size, + unsigned max_r2 = 0 + ) +{ + unsigned n = threadIdx.x + block_size * blockIdx.x; + long int image_offset = oX*oY*oZ*blockIdx.y; + if (n >= max_idx) return; + + int k, i, kp, ip, jp; + + if (check_max_r2) + { + k = n / (iX * iY); + i = (n % (iX * iY)) / iX; + + kp = k < iX ? k : k - iZ; + ip = i < iX ? i : i - iY; + jp = n % iX; + + if (kp*kp + ip*ip + jp*jp > max_r2) + return; + } + else + { + k = n / (oX * oY); + i = (n % (oX * oY)) / oX; + + kp = k < oX ? k : k - oZ; + ip = i < oX ? i : i - oY; + jp = n % oX; + } + + g_out_real[(kp < 0 ? kp + oZ : kp) * oYX + (ip < 0 ? ip + oY : ip)*oX + jp + image_offset] = g_in_real[(kp < 0 ? kp + iZ : kp)*iYX + (ip < 0 ? ip + iY : ip)*iX + jp + image_offset]; + g_out_imag[(kp < 0 ? kp + oZ : kp) * oYX + (ip < 0 ? ip + oY : ip)*oX + jp + image_offset] = g_in_imag[(kp < 0 ? kp + iZ : kp)*iYX + (ip < 0 ? ip + iY : ip)*iX + jp + image_offset]; +} + +template +__global__ void hip_kernel_window_fourier_transform( + ACCCOMPLEX *g_in, + ACCCOMPLEX *g_out, + size_t iX, size_t iY, size_t iZ, size_t iYX, //Input dimensions + size_t oX, size_t oY, size_t oZ, size_t oYX, //Output dimensions + size_t max_idx, + int block_size, + size_t max_r2 = 0 + ) +{ + size_t n = threadIdx.x + block_size * blockIdx.x; + size_t oOFF = oX*oY*oZ*blockIdx.y; + size_t iOFF = iX*iY*iZ*blockIdx.y; + if (n >= max_idx) return; + + long int k, i, kp, ip, jp; + + if (check_max_r2) + { + k = n / (iX * iY); + i = (n % (iX * iY)) / iX; + + kp = k < iX ? k : k - iZ; + ip = i < iX ? i : i - iY; + jp = n % iX; + + if (kp*kp + ip*ip + jp*jp > max_r2) + return; + } + else + { + k = n / (oX * oY); + i = (n % (oX * oY)) / oX; + + kp = k < oX ? k : k - oZ; + ip = i < oX ? i : i - oY; + jp = n % oX; + } + + long int in_idx = (kp < 0 ? kp + iZ : kp) * iYX + (ip < 0 ? ip + iY : ip)*iX + jp; + long int out_idx = (kp < 0 ? kp + oZ : kp) * oYX + (ip < 0 ? ip + oY : ip)*oX + jp; + g_out[out_idx + oOFF] = g_in[in_idx + iOFF]; +} +#endif diff --git a/src/acc/hip/hip_kernels/wavg.h b/src/acc/hip/hip_kernels/wavg.h new file mode 100644 index 000000000..02b38c368 --- /dev/null +++ b/src/acc/hip/hip_kernels/wavg.h @@ -0,0 +1,156 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_WAVG_KERNEL_H_ +#define HIP_WAVG_KERNEL_H_ + +#include +#include +#include +#include +#include "src/acc/acc_projector.h" +#include "src/acc/acc_projectorkernel_impl.h" +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_kernels/hip_device_utils.h" + +template +__global__ void hip_kernel_wavg( + XFLOAT *g_eulers, + AccProjectorKernel projector, + unsigned image_size, + unsigned long orientation_num, + XFLOAT *g_img_real, + XFLOAT *g_img_imag, + XFLOAT *g_trans_x, + XFLOAT *g_trans_y, + XFLOAT *g_trans_z, + XFLOAT* g_weights, + XFLOAT* g_ctfs, + XFLOAT *g_wdiff2s_parts, + XFLOAT *g_wdiff2s_AA, + XFLOAT *g_wdiff2s_XA, + unsigned long translation_num, + XFLOAT weight_norm, + XFLOAT significant_weight, + XFLOAT part_scale) +{ + XFLOAT ref_real, ref_imag, img_real, img_imag, trans_real, trans_imag; + + int bid = blockIdx.x; //block ID + int tid = threadIdx.x; + + extern __shared__ XFLOAT buffer[]; + + unsigned pass_num(ceilfracf(image_size,block_sz)),pixel; + XFLOAT * s_wdiff2s_parts = &buffer[0]; + XFLOAT * s_sumXA = &buffer[block_sz]; + XFLOAT * s_sumA2 = &buffer[2*block_sz]; + XFLOAT * s_eulers = &buffer[3*block_sz]; + + if (tid < 9) + s_eulers[tid] = g_eulers[bid*9+tid]; + __syncthreads(); + for (unsigned pass = 0; pass < pass_num; pass++) // finish a reference proj in each block + { + s_wdiff2s_parts[tid] = 0.0f; + s_sumXA[tid] = 0.0f; + s_sumA2[tid] = 0.0f; + + pixel = pass * block_sz + tid; + + if(pixel projector.maxR) + { + if (z >= projector.imgZ - projector.maxR) + z = z - projector.imgZ; + else + x = projector.maxR; + } + } + else + { + x = pixel % projector.imgX; + y = floorfracf( pixel , projector.imgX); + } + if (y > projector.maxR) + { + if (y >= projector.imgY - projector.maxR) + y = y - projector.imgY; + else + x = projector.maxR; + } + + if(DATA3D) + projector.project3Dmodel( + x,y,z, + s_eulers[0], s_eulers[1], s_eulers[2], + s_eulers[3], s_eulers[4], s_eulers[5], + s_eulers[6], s_eulers[7], s_eulers[8], + ref_real, ref_imag); + else if(REF3D) + projector.project3Dmodel( + x,y, + s_eulers[0], s_eulers[1], + s_eulers[3], s_eulers[4], + s_eulers[6], s_eulers[7], + ref_real, ref_imag); + else + projector.project2Dmodel( + x,y, + s_eulers[0], s_eulers[1], + s_eulers[3], s_eulers[4], + ref_real, ref_imag); + + if (REFCTF) + { + ref_real *= __ldg(&g_ctfs[pixel]); + ref_imag *= __ldg(&g_ctfs[pixel]); + } + else + { + ref_real *= part_scale; + ref_imag *= part_scale; + } + + img_real = __ldg(&g_img_real[pixel]); + img_imag = __ldg(&g_img_imag[pixel]); + + for (unsigned long itrans = 0; itrans < translation_num; itrans++) + { + XFLOAT weight = __ldg(&g_weights[bid * translation_num + itrans]); + + if (weight >= significant_weight) + { + weight /= weight_norm; + + if(DATA3D) + translatePixel(x, y, z, g_trans_x[itrans], g_trans_y[itrans], g_trans_z[itrans], img_real, img_imag, trans_real, trans_imag); + else + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], img_real, img_imag, trans_real, trans_imag); + + XFLOAT diff_real = ref_real - trans_real; + XFLOAT diff_imag = ref_imag - trans_imag; + + s_wdiff2s_parts[tid] += weight * (diff_real*diff_real + diff_imag*diff_imag); + + s_sumXA[tid] += weight * ( ref_real * trans_real + ref_imag * trans_imag); + s_sumA2[tid] += weight * ( ref_real*ref_real + ref_imag*ref_imag ); + } + } + + hip_atomic_add(&g_wdiff2s_XA[pixel], s_sumXA[tid]); + hip_atomic_add(&g_wdiff2s_AA[pixel], s_sumA2[tid]); + hip_atomic_add(&g_wdiff2s_parts[pixel], s_wdiff2s_parts[tid]); + } + } +} + +#endif /* HIP_WAVG_KERNEL_H_ */ diff --git a/src/acc/hip/hip_mem_utils.h b/src/acc/hip/hip_mem_utils.h new file mode 100644 index 000000000..1aec009d9 --- /dev/null +++ b/src/acc/hip/hip_mem_utils.h @@ -0,0 +1,91 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_DEVICE_MEM_UTILS_H_ +#define HIP_DEVICE_MEM_UTILS_H_ + +#ifdef _HIP_ENABLED +#include +#include +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/custom_allocator.h" +#endif + + +#include +#include +#include +#include +#include +#include +#include + +#include "src/complex.h" + +// Forward definition +template class AccPtr; + +/** + * Print hip device memory info + */ +static void hipPrintMemInfo() +{ + size_t free; + size_t total; + DEBUG_HANDLE_ERROR(hipMemGetInfo( &free, &total )); + float free_hr(free/(1024.*1024.)); + float total_hr(total/(1024.*1024.)); + printf( "free %.2fMiB, total %.2fMiB, used %.2fMiB\n", + free_hr, total_hr, total_hr - free_hr); +} + +template< typename T> +static inline +void hipCpyHostToDevice( T *h_ptr, T *d_ptr, size_t size) +{ + DEBUG_HANDLE_ERROR(hipMemcpy( d_ptr, h_ptr, size * sizeof(T), hipMemcpyHostToDevice)); +}; + +template< typename T> +static inline +void hipCpyHostToDevice( T *h_ptr, T *d_ptr, size_t size, hipStream_t &stream) +{ + DEBUG_HANDLE_ERROR(hipMemcpyAsync( d_ptr, h_ptr, size * sizeof(T), hipMemcpyHostToDevice, stream)); +}; + +template< typename T> +static inline +void hipCpyDeviceToHost( T *d_ptr, T *h_ptr, size_t size) +{ + DEBUG_HANDLE_ERROR(hipMemcpy( h_ptr, d_ptr, size * sizeof(T), hipMemcpyDeviceToHost)); +}; + +template< typename T> +static inline +void hipCpyDeviceToHost( T *d_ptr, T *h_ptr, size_t size, hipStream_t &stream) +{ + DEBUG_HANDLE_ERROR(hipMemcpyAsync( h_ptr, d_ptr, size * sizeof(T), hipMemcpyDeviceToHost, stream)); +}; + +template< typename T> +static inline +void hipCpyDeviceToDevice( T *src, T *des, size_t size, hipStream_t stream) +{ + DEBUG_HANDLE_ERROR(hipMemcpyAsync( des, src, size * sizeof(T), hipMemcpyDeviceToDevice, stream)); +}; + +template< typename T> +static inline +void hipMemInit( T *ptr, T value, size_t size) +{ + DEBUG_HANDLE_ERROR(hipMemset( ptr, value, size * sizeof(T))); +}; + +template< typename T> +static inline +void hipMemInit( T *ptr, T value, size_t size, hipStream_t &stream) +{ + DEBUG_HANDLE_ERROR(hipMemsetAsync( ptr, value, size * sizeof(T), stream)); +}; + +#endif diff --git a/src/acc/hip/hip_ml_optimiser.h b/src/acc/hip/hip_ml_optimiser.h new file mode 100644 index 000000000..ff237c7e2 --- /dev/null +++ b/src/acc/hip/hip_ml_optimiser.h @@ -0,0 +1,150 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_ML_OPTIMISER_H_ +#define HIP_ML_OPTIMISER_H_ + +#include "src/mpi.h" +#include "src/ml_optimiser.h" +#include "src/acc/hip/hip_mem_utils.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/hip/hip_fft.h" +#include "src/acc/hip/hip_benchmark_utils.h" +#include +//#include + +#include "src/acc/acc_ml_optimiser.h" +#include "src/acc/acc_ptr.h" + +class MlDeviceBundle +{ +public: + + //The HIP accelerated projector set + std::vector< AccProjector > projectors; + + //The HIP accelerated back-projector set + std::vector< AccBackprojector > backprojectors; + + //Used for precalculations of projection setup + HipCustomAllocator *allocator; + + //Used for precalculations of projection setup + bool generateProjectionPlanOnTheFly; + std::vector< AccProjectorPlan > coarseProjectionPlans; + + MlOptimiser *baseMLO; + + int device_id; + + int rank_shared_count; + + bool haveWarnedRefinementMem; + + MlDeviceBundle(MlOptimiser *baseMLOptimiser): + baseMLO(baseMLOptimiser), + generateProjectionPlanOnTheFly(false), + rank_shared_count(1), + device_id(-1), + haveWarnedRefinementMem(false), + allocator(NULL) + {}; + + void setDevice(int did) + { + device_id = did; + } + + size_t checkFixedSizedObjects(int shares); + void setupFixedSizedObjects(); + void setupTunableSizedObjects(size_t allocationSize); + + void syncAllBackprojects() + { + DEBUG_HANDLE_ERROR(hipDeviceSynchronize()); + } + + + ~MlDeviceBundle() + { + projectors.clear(); + backprojectors.clear(); + coarseProjectionPlans.clear(); + //Delete this lastly + delete allocator; + } + +}; +class MlOptimiserHip +{ +public: + // transformer as holder for reuse of fftw_plans + FourierTransformer transformer; + + //Class streams ( for concurrent scheduling of class-specific kernels) + std::vector< hipStream_t > classStreams; + hipStream_t defaultStream = 0; + hipError_t errorStatus; + + HipFFT transformer1; + HipFFT transformer2; + + MlOptimiser *baseMLO; + + bool refIs3D; + bool dataIs3D; + bool shiftsIs3D; + + int device_id; + + MlDeviceBundle *bundle; + + //Used for precalculations of projection setup + HipCustomAllocator *allocator; + + //Used for precalculations of projection setup + bool generateProjectionPlanOnTheFly; + + +#ifdef TIMING_FILES + relion_timer timer; +#endif + + MlOptimiserHip(MlOptimiser *baseMLOptimiser, MlDeviceBundle* bundle, const char * timing_fnm) : + baseMLO(baseMLOptimiser), + transformer1(hipStreamPerThread, bundle->allocator, baseMLOptimiser->mymodel.data_dim), + transformer2(hipStreamPerThread, bundle->allocator, baseMLOptimiser->mymodel.data_dim), + refIs3D(baseMLO->mymodel.ref_dim == 3), + dataIs3D(baseMLO->mymodel.data_dim == 3), + shiftsIs3D(baseMLO->mymodel.data_dim == 3 || baseMLO->mydata.is_tomo), + bundle(bundle), + device_id(bundle->device_id), +#ifdef TIMING_FILES + timer(timing_fnm), +#endif + errorStatus((hipError_t)0), + allocator(bundle->allocator), + generateProjectionPlanOnTheFly(bundle->generateProjectionPlanOnTheFly) + {}; + + void resetData(); + + void doThreadExpectationSomeParticles(int thread_id); + + ~MlOptimiserHip() + { + for (int i = 0; i < classStreams.size(); i++) + if (classStreams[i] != NULL) + HANDLE_ERROR(hipStreamDestroy(classStreams[i])); + } + + HipCustomAllocator *getAllocator() + { + return (bundle->allocator); + }; + +}; + +#endif diff --git a/src/acc/hip/hip_ml_optimiser.hip.cpp b/src/acc/hip/hip_ml_optimiser.hip.cpp new file mode 100644 index 000000000..4fcfa5e86 --- /dev/null +++ b/src/acc/hip/hip_ml_optimiser.hip.cpp @@ -0,0 +1,298 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#undef ALTCPU +#include +#include +#include +#include +#include +#include +#include +#include "src/ml_optimiser.h" +#include +#include +#include + +#include "src/acc/acc_ptr.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/hip/hip_benchmark_utils.h" +#include "src/acc/hip/hip_kernels/helper.h" +#include "src/acc/hip/hip_kernels/diff2.h" +#include "src/acc/hip/hip_kernels/wavg.h" +#include "src/acc/hip/hip_mem_utils.h" +#include "src/acc/hip/hip_fft.h" +#include "src/acc/data_types.h" +#include "src/complex.h" +#include "src/helix.h" +#include "src/error.h" +#include +#include "src/parallel.h" +#include +#include + +#ifdef HIP_FORCESTL +#include "src/acc/hip/hip_utils_stl.h" +#else +#include "src/acc/hip/hip_utils_cub.h" +#endif + +#include "src/acc/utilities.h" +#include "src/acc/utilities_impl.h" + +#include "src/acc/acc_ml_optimiser.h" +#include "src/acc/hip/hip_ml_optimiser.h" +#include "src/acc/acc_helper_functions.h" +#include "src/acc/acc_ml_optimiser_impl.h" + +// ------------------------------- Some explicit template instantiations +template __global__ void HipKernels::hip_kernel_translate2D(XFLOAT *, + XFLOAT*, int, int, int, int, int); + +template __global__ void HipKernels::hip_kernel_translate3D(XFLOAT *, + XFLOAT *, int, int, int, int, int, int, int); + +template __global__ void hip_kernel_multi( XFLOAT *, + XFLOAT *, XFLOAT, int); + +template __global__ void HipKernels::hip_kernel_multi( XFLOAT *, + XFLOAT, int); + +template __global__ void hip_kernel_multi( XFLOAT *, XFLOAT *, + XFLOAT *, XFLOAT, int); + +// ---------------------------------------------------------------------- + +// High-level HIP objects + +size_t MlDeviceBundle::checkFixedSizedObjects(int shares) +{ + int devCount; + size_t BoxLimit; + HANDLE_ERROR(hipGetDeviceCount(&devCount)); + if(device_id >= devCount) + CRITICAL(ERR_GPUID); + + HANDLE_ERROR(hipSetDevice(device_id)); + + size_t free(0), total(0); + DEBUG_HANDLE_ERROR(hipMemGetInfo( &free, &total )); + float margin(1.05); + BoxLimit = pow(free/(margin*2.5*sizeof(XFLOAT)*((float)shares)),(1/3.0)) / ((float) baseMLO->mymodel.padding_factor); + //size_t BytesNeeded = ((float)shares)*margin*2.5*sizeof(XFLOAT)*pow((baseMLO->mymodel.ori_size*baseMLO->mymodel.padding_factor),3); + + return(BoxLimit); +} +void MlDeviceBundle::setupFixedSizedObjects() +{ + int devCount; + HANDLE_ERROR(hipGetDeviceCount(&devCount)); + if(device_id >= devCount) + { + //std::cerr << " using device_id=" << device_id << " (device no. " << device_id+1 << ") which is higher than the available number of devices=" << devCount << std::endl; + CRITICAL(ERR_GPUID); + } + else + HANDLE_ERROR(hipSetDevice(device_id)); + + //Can we pre-generate projector plan and corresponding euler matrices for all particles + if (baseMLO->do_skip_align || baseMLO->do_skip_rotate || baseMLO->do_auto_refine || baseMLO->mymodel.orientational_prior_mode != NOPRIOR || baseMLO->mydata.is_tomo) + generateProjectionPlanOnTheFly = true; + else + generateProjectionPlanOnTheFly = false; + + unsigned nr_proj = baseMLO->mymodel.PPref.size(); + unsigned nr_bproj = baseMLO->wsum_model.BPref.size(); + + projectors.resize(nr_proj); + backprojectors.resize(nr_bproj); + + /*====================================================== + PROJECTOR AND BACKPROJECTOR + ======================================================*/ + + for (int imodel = 0; imodel < nr_proj; imodel++) + { + projectors[imodel].setMdlDim( + baseMLO->mymodel.PPref[imodel].data.xdim, + baseMLO->mymodel.PPref[imodel].data.ydim, + baseMLO->mymodel.PPref[imodel].data.zdim, + baseMLO->mymodel.PPref[imodel].data.yinit, + baseMLO->mymodel.PPref[imodel].data.zinit, + baseMLO->mymodel.PPref[imodel].r_max, + baseMLO->mymodel.PPref[imodel].padding_factor); + + projectors[imodel].initMdl(baseMLO->mymodel.PPref[imodel].data.data); + + } + + for (int imodel = 0; imodel < nr_bproj; imodel++) + { + backprojectors[imodel].setMdlDim( + baseMLO->wsum_model.BPref[imodel].data.xdim, + baseMLO->wsum_model.BPref[imodel].data.ydim, + baseMLO->wsum_model.BPref[imodel].data.zdim, + baseMLO->wsum_model.BPref[imodel].data.yinit, + baseMLO->wsum_model.BPref[imodel].data.zinit, + baseMLO->wsum_model.BPref[imodel].r_max, + baseMLO->wsum_model.BPref[imodel].padding_factor); + + backprojectors[imodel].initMdl(); + } + + /*====================================================== + CUSTOM ALLOCATOR + ======================================================*/ + + int memAlignmentSize; + hipDeviceGetAttribute ( &memAlignmentSize, hipDeviceAttributeTextureAlignment, device_id ); + allocator = new HipCustomAllocator(0, memAlignmentSize); +} + +void MlDeviceBundle::setupTunableSizedObjects(size_t allocationSize) +{ + unsigned nr_models = baseMLO->mymodel.nr_classes; + int devCount; + HANDLE_ERROR(hipGetDeviceCount(&devCount)); + if(device_id >= devCount) + { + //std::cerr << " using device_id=" << device_id << " (device no. " << device_id+1 << ") which is higher than the available number of devices=" << devCount << std::endl; + CRITICAL(ERR_GPUID); + } + else + HANDLE_ERROR(hipSetDevice(device_id)); + + /*====================================================== + CUSTOM ALLOCATOR + ======================================================*/ +#ifdef DEBUG_HIP + printf("DEBUG: Total GPU allocation size set to %zu MB on device id %d.\n", allocationSize / (1000*1000), device_id); +#endif +#ifndef HIP_NO_CUSTOM_ALLOCATION + allocator->resize(allocationSize); +#endif + + + /*====================================================== + PROJECTION PLAN + ======================================================*/ + + coarseProjectionPlans.resize(nr_models, allocator); + + for (int iclass = 0; iclass < nr_models; iclass++) + { + //If doing predefined projector plan at all and is this class significant + if (!generateProjectionPlanOnTheFly && baseMLO->mymodel.pdf_class[iclass] > 0.) + { + std::vector exp_pointer_dir_nonzeroprior; + std::vector exp_pointer_psi_nonzeroprior; + std::vector exp_directions_prior; + std::vector exp_psi_prior; + + long unsigned itrans_max = baseMLO->sampling.NrTranslationalSamplings() - 1; + long unsigned nr_idir = baseMLO->sampling.NrDirections(0, &exp_pointer_dir_nonzeroprior); + long unsigned nr_ipsi = baseMLO->sampling.NrPsiSamplings(0, &exp_pointer_psi_nonzeroprior ); + + coarseProjectionPlans[iclass].setup( + baseMLO->sampling, + exp_directions_prior, + exp_psi_prior, + exp_pointer_dir_nonzeroprior, + exp_pointer_psi_nonzeroprior, + NULL, //Mcoarse_significant + baseMLO->mymodel.pdf_class, + baseMLO->mymodel.pdf_direction, + nr_idir, + nr_ipsi, + 0, //idir_min + nr_idir - 1, //idir_max + 0, //ipsi_min + nr_ipsi - 1, //ipsi_max + 0, //itrans_min + itrans_max, + 0, //current_oversampling + 1, //nr_oversampled_rot + iclass, + true, //coarse + !IS_NOT_INV, + baseMLO->do_skip_align, + baseMLO->do_skip_rotate, + baseMLO->mymodel.orientational_prior_mode + ); + } + } +}; + +void MlOptimiserHip::resetData() +{ + int devCount; + HANDLE_ERROR(hipGetDeviceCount(&devCount)); + if(device_id >= devCount) + { + //std::cerr << " using device_id=" << device_id << " (device no. " << device_id+1 << ") which is higher than the available number of devices=" << devCount << std::endl; + CRITICAL(ERR_GPUID); + } + else + HANDLE_ERROR(hipSetDevice(device_id)); + + unsigned nr_classes = baseMLO->mymodel.nr_classes; + + classStreams.resize(nr_classes, 0); + for (int i = 0; i < nr_classes; i++) + HANDLE_ERROR(hipStreamCreate(&classStreams[i])); //HANDLE_ERROR(hipStreamCreateWithFlags(&classStreams[i],hipStreamNonBlocking)); + + transformer1.clear(); + transformer2.clear(); +}; + +void MlOptimiserHip::doThreadExpectationSomeParticles(int thread_id) +{ +#ifdef TIMING + // Only time one thread + if (thread_id == 0) + baseMLO->timer.tic(baseMLO->TIMING_ESP_THR); +#endif +// CTOC(hipMLO->timer,"interParticle"); + + int devCount; + HANDLE_ERROR(hipGetDeviceCount(&devCount)); + if(device_id >= devCount) + { + //std::cerr << " using device_id=" << device_id << " (device no. " << device_id+1 << ") which is higher than the available number of devices=" << devCount << std::endl; + CRITICAL(ERR_GPUID); + } + else + DEBUG_HANDLE_ERROR(hipSetDevice(device_id)); + //std::cerr << " calling on device " << device_id << std::endl; + //put mweight allocation here + size_t first_ipart = 0, last_ipart = 0; + + while (baseMLO->exp_ipart_ThreadTaskDistributor->getTasks(first_ipart, last_ipart)) + { + CTIC(timer,"oneTask"); + for (long unsigned ipart = first_ipart; ipart <= last_ipart; ipart++) + { +#ifdef TIMING + // Only time one thread + if (thread_id == 0) + baseMLO->timer.tic(baseMLO->TIMING_ESP_DIFF2_A); +#endif + + AccPtrFactory ptrFactory(allocator, hipStreamPerThread); + accDoExpectationOneParticle(this, baseMLO->exp_my_first_part_id + ipart, thread_id, ptrFactory); + + } + CTOC(timer,"oneTask"); + } + +// CTIC(hipMLO->timer,"interParticle"); +// exit(0); + +#ifdef TIMING + // Only time one thread + if (thread_id == 0) + baseMLO->timer.toc(baseMLO->TIMING_ESP_THR); +#endif +} diff --git a/src/acc/hip/hip_projector.hip.cpp b/src/acc/hip/hip_projector.hip.cpp new file mode 100644 index 000000000..382ec4bb0 --- /dev/null +++ b/src/acc/hip/hip_projector.hip.cpp @@ -0,0 +1,7 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#include "src/acc/acc_projector.h" +#include + +#include "src/acc/acc_projector_impl.h" diff --git a/src/acc/hip/hip_projector_plan.hip.cpp b/src/acc/hip/hip_projector_plan.hip.cpp new file mode 100644 index 000000000..f4f743c1d --- /dev/null +++ b/src/acc/hip/hip_projector_plan.hip.cpp @@ -0,0 +1,18 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#include "src/acc/acc_projector_plan.h" +#include "src/time.h" +#include + +#ifdef _HIP_ENABLED + #ifdef HIP_FORCESTL + #include "src/acc/hip/hip_utils_stl.h" + #else + #include "src/acc/hip/hip_utils_cub.h" + #endif +#endif + +#include "src/acc/utilities.h" + +#include "src/acc/acc_projector_plan_impl.h" diff --git a/src/acc/hip/hip_settings.h b/src/acc/hip/hip_settings.h new file mode 100644 index 000000000..ccadce531 --- /dev/null +++ b/src/acc/hip/hip_settings.h @@ -0,0 +1,158 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_SETTINGS_H_ +#define HIP_SETTINGS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "src/macros.h" +#include "src/error.h" + +#include + +// Required compute capability +#define HIP_CC_MAJOR 5 +#define HIP_CC_MINOR 0 + +#define LAUNCH_CHECK +#define HIP_BENCHMARK_OLD true + +// Error handling ---------------------- + +#ifdef LAUNCH_CHECK +#define LAUNCH_HANDLE_ERROR( err ) (LaunchHandleError( err, __FILE__, __LINE__ )) +#define LAUNCH_PRIVATE_ERROR(func, status) { \ + (status) = (func); \ + LAUNCH_HANDLE_ERROR(status); \ + } +#else +#define LAUNCH_HANDLE_ERROR( err ) (err) //Do nothing +#define LAUNCH_PRIVATE_ERROR( err ) (err) //Do nothing +#endif + +#ifdef DEBUG_HIP +#define DEBUG_HANDLE_ERROR( err ) (HandleError( err, __FILE__, __LINE__ )) +#define DEBUG_PRIVATE_ERROR(func, status) { \ + (status) = (func); \ + DEBUG_HANDLE_ERROR(status); \ + } +#else +#define DEBUG_HANDLE_ERROR( err ) (err) //Do nothing +#define DEBUG_PRIVATE_ERROR( err ) (err) //Do nothing +#endif + +#define HANDLE_ERROR( err ) (HandleError( err, __FILE__, __LINE__ )) +#define PRIVATE_ERROR(func, status) { \ + (status) = (func); \ + HANDLE_ERROR(status); \ + } + +static void HandleError( hipError_t err, const char *file, int line ) +{ + + if (err != hipSuccess) + { + fprintf(stderr, "ERROR: %s in %s at line %d (error-code %d)\n", + hipGetErrorString( err ), file, line, err ); + fflush(stdout); +#ifdef DEBUG_HIP + raise(SIGSEGV); +#else + CRITICAL(ERRGPUKERN); +#endif + } +} + +#ifdef LAUNCH_CHECK +static void LaunchHandleError( hipError_t err, const char *file, int line ) +{ + + if (err != hipSuccess) + { + printf( "KERNEL_ERROR: %s in %s at line %d (error-code %d)\n", + hipGetErrorString( err ), file, line, err ); + fflush(stdout); + CRITICAL(ERRGPUKERN); + } +} +#endif + + +// GENERAL ----------------------------- +#define MAX_RESOL_SHARED_MEM 32 +#define BLOCK_SIZE 128 +// ------------------------------------- + + +// COARSE DIFF ------------------------- +#define D2C_BLOCK_SIZE_2D 512 +#define D2C_EULERS_PER_BLOCK_2D 4 + +#define D2C_BLOCK_SIZE_REF3D 128 +#define D2C_EULERS_PER_BLOCK_REF3D 16 + +#define D2C_BLOCK_SIZE_DATA3D 64 +#define D2C_EULERS_PER_BLOCK_DATA3D 32 +// ------------------------------------- + + +// FINE DIFF --------------------------- +#define D2F_BLOCK_SIZE_2D 512 //256 +#define D2F_CHUNK_2D 7 + +#define D2F_BLOCK_SIZE_REF3D 256 +#define D2F_CHUNK_REF3D 7 + +#define D2F_BLOCK_SIZE_DATA3D 512 +#define D2F_CHUNK_DATA3D 4 +// ------------------------------------- + + +// WAVG -------------------------------- +#define WAVG_BLOCK_SIZE_DATA3D 512 +#define WAVG_BLOCK_SIZE 512 //256 +// ------------------------------------- + + +// MISC -------------------------------- +#define SUMW_BLOCK_SIZE 32 +#define SOFTMASK_BLOCK_SIZE 128 +#define CFTT_BLOCK_SIZE 128 +#define PROBRATIO_BLOCK_SIZE 128 +#define POWERCLASS_BLOCK_SIZE 128 +#define PROJDIFF_CHUNK_SIZE 14 +// ------------------------------------- + +// RANDOMIZATION ----------------------- +#define RND_BLOCK_NUM 64 +#define RND_BLOCK_SIZE 32 +// ------------------------------------- + + +#define BACKPROJECTION4_BLOCK_SIZE 64 +#define BACKPROJECTION4_GROUP_SIZE 16 +#define BACKPROJECTION4_PREFETCH_COUNT 3 +#define BP_2D_BLOCK_SIZE 512 //128 +#define BP_REF3D_BLOCK_SIZE 1024 //128 +#define BP_DATA3D_BLOCK_SIZE 640 + + +#define REF_GROUP_SIZE 3 // -- Number of references to be treated per block -- + // This applies to wavg and reduces global memory + // accesses roughly proportionally, but scales shared + // memory usage by allocating + // ( 6*REF_GROUP_SIZE + 4 ) * BLOCK_SIZE XFLOATS. // DEPRECATED + +#define NR_CLASS_MUTEXES 5 + +//The approximate minimum amount of memory each process occupies on a device (in MBs) +#define GPU_THREAD_MEMORY_OVERHEAD_MB 200 + +#endif /* HIP_SETTINGS_H_ */ diff --git a/src/acc/hip/hip_utils_cub.h b/src/acc/hip/hip_utils_cub.h new file mode 100644 index 000000000..73ec4d2e5 --- /dev/null +++ b/src/acc/hip/hip_utils_cub.h @@ -0,0 +1,357 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_UTILS_CUB_H_ +#define HIP_UTILS_CUB_H_ + +#include +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_mem_utils.h" +#include +#include +#include +// Because thrust uses CUB, thrust defines CubLog and CUB tries to redefine it, +// resulting in warnings. This avoids those warnings. +#if(defined(CubLog) && defined(__HIP_ARCH__) && (__HIP_ARCH__<= gfx906)) // Intetionally force a warning for new arch + #undef CubLog +#endif + +//#define CUB_NS_QUALIFIER ::cub // for compatibility with CUDA 11.5 +#include +#include +#include +#include + +namespace HipKernels +{ +template +static std::pair getArgMaxOnDevice(AccPtr &ptr) +{ +#ifdef DEBUG_HIP +if (ptr.getSize() == 0) + printf("DEBUG_WARNING: getArgMaxOnDevice called with pointer of zero size.\n"); +if (ptr.getDevicePtr() == NULL) + printf("DEBUG_WARNING: getArgMaxOnDevice called with null device pointer.\n"); +if (ptr.getAllocator() == NULL) + printf("DEBUG_WARNING: getArgMaxOnDevice called with null allocator.\n"); +#endif + AccPtr > max_pair(1, ptr.getStream(), ptr.getAllocator()); + max_pair.deviceAlloc(); + size_t temp_storage_size = 0; + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::ArgMax( NULL, temp_storage_size, ~ptr, ~max_pair, ptr.getSize())); + + if(temp_storage_size==0) + temp_storage_size=1; + + HipCustomAllocator::Alloc* alloc = ptr.getAllocator()->alloc(temp_storage_size); + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::ArgMax( alloc->getPtr(), temp_storage_size, ~ptr, ~max_pair, ptr.getSize(), ptr.getStream())); + + max_pair.cpToHost(); + ptr.streamSync(); + + ptr.getAllocator()->free(alloc); + + std::pair pair; + pair.first = max_pair[0].key; + pair.second = max_pair[0].value; + + return pair; +} + +template +static std::pair getArgMinOnDevice(AccPtr &ptr) +{ +#ifdef DEBUG_HIP +if (ptr.getSize() == 0) + printf("DEBUG_WARNING: getArgMinOnDevice called with pointer of zero size.\n"); +if (ptr.getDevicePtr() == NULL) + printf("DEBUG_WARNING: getArgMinOnDevice called with null device pointer.\n"); +if (ptr.getAllocator() == NULL) + printf("DEBUG_WARNING: getArgMinOnDevice called with null allocator.\n"); +#endif + AccPtr > min_pair(1, ptr.getStream(), ptr.getAllocator()); + min_pair.deviceAlloc(); + size_t temp_storage_size = 0; + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::ArgMin( NULL, temp_storage_size, ~ptr, ~min_pair, ptr.getSize())); + + if(temp_storage_size==0) + temp_storage_size=1; + + HipCustomAllocator::Alloc* alloc = ptr.getAllocator()->alloc(temp_storage_size); + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::ArgMin( alloc->getPtr(), temp_storage_size, ~ptr, ~min_pair, ptr.getSize(), ptr.getStream())); + + min_pair.cpToHost(); + ptr.streamSync(); + + ptr.getAllocator()->free(alloc); + + std::pair pair; + pair.first = min_pair[0].key; + pair.second = min_pair[0].value; + + return pair; +} + +template +static T getMaxOnDevice(AccPtr &ptr) +{ +#ifdef DEBUG_HIP +if (ptr.getSize() == 0) + printf("DEBUG_ERROR: getMaxOnDevice called with pointer of zero size.\n"); +if (ptr.getDevicePtr() == NULL) + printf("DEBUG_ERROR: getMaxOnDevice called with null device pointer.\n"); +if (ptr.getAllocator() == NULL) + printf("DEBUG_ERROR: getMaxOnDevice called with null allocator.\n"); +#endif + AccPtr max_val(1, ptr.getStream(), ptr.getAllocator()); + max_val.deviceAlloc(); + size_t temp_storage_size = 0; + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::Max( NULL, temp_storage_size, ~ptr, ~max_val, ptr.getSize())); + + if(temp_storage_size==0) + temp_storage_size=1; + + HipCustomAllocator::Alloc* alloc = ptr.getAllocator()->alloc(temp_storage_size); + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::Max( alloc->getPtr(), temp_storage_size, ~ptr, ~max_val, ptr.getSize(), ptr.getStream())); + + max_val.cpToHost(); + ptr.streamSync(); + + ptr.getAllocator()->free(alloc); + + return max_val[0]; +} + +template +static T getMinOnDevice(AccPtr &ptr) +{ +#ifdef DEBUG_HIP +if (ptr.getSize() == 0) + printf("DEBUG_ERROR: getMinOnDevice called with pointer of zero size.\n"); +if (ptr.getDevicePtr() == NULL) + printf("DEBUG_ERROR: getMinOnDevice called with null device pointer.\n"); +if (ptr.getAllocator() == NULL) + printf("DEBUG_ERROR: getMinOnDevice called with null allocator.\n"); +#endif + AccPtr min_val(1, ptr.getStream(), ptr.getAllocator()); + min_val.deviceAlloc(); + size_t temp_storage_size = 0; + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::Min( NULL, temp_storage_size, ~ptr, ~min_val, ptr.getSize())); + + if(temp_storage_size==0) + temp_storage_size=1; + + HipCustomAllocator::Alloc* alloc = ptr.getAllocator()->alloc(temp_storage_size); + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::Min( alloc->getPtr(), temp_storage_size, ~ptr, ~min_val, ptr.getSize(), ptr.getStream())); + + min_val.cpToHost(); + ptr.streamSync(); + + ptr.getAllocator()->free(alloc); + + return min_val[0]; +} + +template +static T getSumOnDevice(AccPtr &ptr) +{ +#ifdef DEBUG_HIP +if (ptr.getSize() == 0) + printf("DEBUG_ERROR: getSumOnDevice called with pointer of zero size.\n"); +if (ptr.getDevicePtr() == NULL) + printf("DEBUG_ERROR: getSumOnDevice called with null device pointer.\n"); +if (ptr.getAllocator() == NULL) + printf("DEBUG_ERROR: getSumOnDevice called with null allocator.\n"); +#endif + AccPtr val(1, ptr.getStream(), ptr.getAllocator()); + val.deviceAlloc(); + size_t temp_storage_size = 0; + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::Sum( NULL, temp_storage_size, ~ptr, ~val, ptr.getSize())); + + if(temp_storage_size==0) + temp_storage_size=1; + + HipCustomAllocator::Alloc* alloc = ptr.getAllocator()->alloc(temp_storage_size); + + DEBUG_HANDLE_ERROR(hipcub::DeviceReduce::Sum( alloc->getPtr(), temp_storage_size, ~ptr, ~val, ptr.getSize(), ptr.getStream())); + + val.cpToHost(); + ptr.streamSync(); + + ptr.getAllocator()->free(alloc); + + return val[0]; +} + +template +static void sortOnDevice(AccPtr &in, AccPtr &out) +{ +#ifdef DEBUG_HIP +if (in.getSize() == 0 || out.getSize() == 0) + printf("DEBUG_ERROR: sortOnDevice called with pointer of zero size.\n"); +if (in.getDevicePtr() == NULL || out.getDevicePtr() == NULL) + printf("DEBUG_ERROR: sortOnDevice called with null device pointer.\n"); +if (in.getAllocator() == NULL) + printf("DEBUG_ERROR: sortOnDevice called with null allocator.\n"); +#endif + size_t temp_storage_size = 0; + + hipStream_t stream = in.getStream(); + + DEBUG_HANDLE_ERROR(hipcub::DeviceRadixSort::SortKeys( NULL, temp_storage_size, ~in, ~out, in.getSize())); + + if(temp_storage_size==0) + temp_storage_size=1; + + HipCustomAllocator::Alloc* alloc = in.getAllocator()->alloc(temp_storage_size); + + DEBUG_HANDLE_ERROR(hipcub::DeviceRadixSort::SortKeys( alloc->getPtr(), temp_storage_size, ~in, ~out, in.getSize(), 0, sizeof(T) * 8, stream)); + + alloc->markReadyEvent(stream); + alloc->doFreeWhenReady(); +} + +template +static void sortDescendingOnDevice(AccPtr &in, AccPtr &out) +{ +#ifdef DEBUG_HIP +if (in.getSize() == 0 || out.getSize() == 0) + printf("DEBUG_ERROR: sortDescendingOnDevice called with pointer of zero size.\n"); +if (in.getDevicePtr() == NULL || out.getDevicePtr() == NULL) + printf("DEBUG_ERROR: sortDescendingOnDevice called with null device pointer.\n"); +if (in.getAllocator() == NULL) + printf("DEBUG_ERROR: sortDescendingOnDevice called with null allocator.\n"); +#endif + size_t temp_storage_size = 0; + + hipStream_t stream = in.getStream(); + + DEBUG_HANDLE_ERROR(hipcub::DeviceRadixSort::SortKeysDescending( NULL, temp_storage_size, ~in, ~out, in.getSize())); + + if(temp_storage_size==0) + temp_storage_size=1; + + HipCustomAllocator::Alloc* alloc = in.getAllocator()->alloc(temp_storage_size); + + DEBUG_HANDLE_ERROR(hipcub::DeviceRadixSort::SortKeysDescending( alloc->getPtr(), temp_storage_size, ~in, ~out, in.getSize(), 0, sizeof(T) * 8, stream)); + + alloc->markReadyEvent(stream); + alloc->doFreeWhenReady(); + +} + +class AllocatorThrustWrapper +{ +public: + // just allocate bytes + typedef char value_type; + std::vector allocs; + HipCustomAllocator *allocator; + + AllocatorThrustWrapper(HipCustomAllocator *allocator): + allocator(allocator) + {} + + ~AllocatorThrustWrapper() + { + for (int i = 0; i < allocs.size(); i ++) + allocator->free(allocs[i]); + } + + char* allocate(std::ptrdiff_t num_bytes) + { + HipCustomAllocator::Alloc* alloc = allocator->alloc(num_bytes); + allocs.push_back(alloc); + return (char*) alloc->getPtr(); + } + + void deallocate(char* ptr, size_t n) + { + //TODO fix this (works fine without it though) /Dari + } +}; + +template +struct MoreThanCubOpt +{ + T compare; + MoreThanCubOpt(T compare) : compare(compare) {} + __device__ __forceinline__ + bool operator()(const T &a) const { + return (a > compare); + } +}; + +template +static int filterOnDevice(AccPtr &in, AccPtr &out, SelectOp select_op) +{ +#ifdef DEBUG_HIP +if (in.getSize() == 0 || out.getSize() == 0) + printf("DEBUG_ERROR: filterOnDevice called with pointer of zero size.\n"); +if (in.getDevicePtr() == NULL || out.getDevicePtr() == NULL) + printf("DEBUG_ERROR: filterOnDevice called with null device pointer.\n"); +if (in.getAllocator() == NULL) + printf("DEBUG_ERROR: filterOnDevice called with null allocator.\n"); +#endif + size_t temp_storage_size = 0; + + hipStream_t stream = in.getStream(); + + AccPtr num_selected_out(1, stream, in.getAllocator()); + num_selected_out.deviceAlloc(); + + DEBUG_HANDLE_ERROR(hipcub::DeviceSelect::If(NULL, temp_storage_size, ~in, ~out, ~num_selected_out, in.getSize(), select_op, stream)); + + if(temp_storage_size==0) + temp_storage_size=1; + + HipCustomAllocator::Alloc* alloc = in.getAllocator()->alloc(temp_storage_size); + + DEBUG_HANDLE_ERROR(hipcub::DeviceSelect::If(alloc->getPtr(), temp_storage_size, ~in, ~out, ~num_selected_out, in.getSize(), select_op, stream)); + + num_selected_out.cpToHost(); + DEBUG_HANDLE_ERROR(hipStreamSynchronize(stream)); + + in.getAllocator()->free(alloc); + return num_selected_out[0]; +} + +template +static void scanOnDevice(AccPtr &in, AccPtr &out) +{ +#ifdef DEBUG_HIP +if (in.getSize() == 0 || out.getSize() == 0) + printf("DEBUG_ERROR: scanOnDevice called with pointer of zero size.\n"); +if (in.getDevicePtr() == NULL || out.getDevicePtr() == NULL) + printf("DEBUG_ERROR: scanOnDevice called with null device pointer.\n"); +if (in.getAllocator() == NULL) + printf("DEBUG_ERROR: scanOnDevice called with null allocator.\n"); +#endif + size_t temp_storage_size = 0; + + hipStream_t stream = in.getStream(); + + DEBUG_HANDLE_ERROR(hipcub::DeviceScan::InclusiveSum( NULL, temp_storage_size, ~in, ~out, in.getSize())); + + if(temp_storage_size==0) + temp_storage_size=1; + + HipCustomAllocator::Alloc* alloc = in.getAllocator()->alloc(temp_storage_size); + + DEBUG_HANDLE_ERROR(hipcub::DeviceScan::InclusiveSum( alloc->getPtr(), temp_storage_size, ~in, ~out, in.getSize(), stream)); + + alloc->markReadyEvent(stream); + alloc->doFreeWhenReady(); +} + +} // namespace HipKernels +#endif //HIP_UTILS_CUB_H_ diff --git a/src/acc/hip/shortcuts.h b/src/acc/hip/shortcuts.h new file mode 100644 index 000000000..cc5224deb --- /dev/null +++ b/src/acc/hip/shortcuts.h @@ -0,0 +1,74 @@ +/* Portions of this code are under: + Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. +*/ +#ifndef HIP_SHORTCUTS_H_ +#define HIP_SHORTCUTS_H_ + +namespace HipShortcuts +{ + +/** + * Print hip device memory info + */ +static void printMemInfo() +{ + size_t free; + size_t total; + DEBUG_HANDLE_ERROR(hipMemGetInfo( &free, &total )); + float free_hr(free/(1024.*1024.)); + float total_hr(total/(1024.*1024.)); + printf( "free %.2fMiB, total %.2fMiB, used %.2fMiB\n", + free_hr, total_hr, total_hr - free_hr); +} + +template< typename T> +static inline +void cpyHostToDevice( T *h_ptr, T *d_ptr, size_t size) +{ + DEBUG_HANDLE_ERROR(hipMemcpy( d_ptr, h_ptr, size * sizeof(T), hipMemcpyHostToDevice)); +}; + +template< typename T> +static inline +void cpyHostToDevice( T *h_ptr, T *d_ptr, size_t size, hipStream_t stream) +{ + DEBUG_HANDLE_ERROR(hipMemcpyAsync( d_ptr, h_ptr, size * sizeof(T), hipMemcpyHostToDevice, stream)); +}; + +template< typename T> +static inline +void cpyDeviceToHost( T *d_ptr, T *h_ptr, size_t size) +{ + DEBUG_HANDLE_ERROR(hipMemcpy( h_ptr, d_ptr, size * sizeof(T), hipMemcpyDeviceToHost)); +}; + +template< typename T> +static inline +void cpyDeviceToHost( T *d_ptr, T *h_ptr, size_t size, hipStream_t &stream) +{ + DEBUG_HANDLE_ERROR(hipMemcpyAsync( h_ptr, d_ptr, size * sizeof(T), hipMemcpyDeviceToHost, stream)); +}; + +template< typename T> +static inline +void cpyDeviceToDevice( T *src, T *des, size_t size, hipStream_t &stream) +{ + DEBUG_HANDLE_ERROR(hipMemcpyAsync( des, src, size * sizeof(T), hipMemcpyDeviceToDevice, stream)); +}; + +template< typename T> +static inline +void memInit( T *ptr, T value, size_t size) +{ + DEBUG_HANDLE_ERROR(hipMemset( ptr, value, size * sizeof(T))); +}; + +template< typename T> +static inline +void memInit( T *ptr, T value, size_t size, hipStream_t &stream) +{ + DEBUG_HANDLE_ERROR(hipMemsetAsync( ptr, value, size * sizeof(T), stream)); +}; + +} +#endif //HIP_SHORTCUTS_H_ diff --git a/src/acc/settings.h b/src/acc/settings.h index 7c6af704d..36892d925 100644 --- a/src/acc/settings.h +++ b/src/acc/settings.h @@ -5,23 +5,38 @@ #ifdef ACC_DOUBLE_PRECISION #define XFLOAT double - #ifndef _CUDA_ENABLED -typedef struct{ XFLOAT x; XFLOAT y;} double2; + #if !defined(_CUDA_ENABLED) && !defined(_HIP_ENABLED) + typedef struct{ XFLOAT x; XFLOAT y;} double2; #endif #define ACCCOMPLEX double2 #else #define XFLOAT float - #ifndef _CUDA_ENABLED + #if !defined(_CUDA_ENABLED) && !defined(_HIP_ENABLED) typedef struct{ XFLOAT x; XFLOAT y;} float2; #endif #define ACCCOMPLEX float2 #endif -#ifdef ALTCPU - #ifndef _CUDA_ENABLED -typedef float cudaStream_t; - typedef double CudaCustomAllocator; - #define cudaStreamPerThread 0 - #endif + +#ifdef _CUDA_ENABLED + #define accGPUGetDeviceCount cudaGetDeviceCount + #define accGPUDeviceProp cudaDeviceProp + #define accGPUGetDeviceProperties cudaGetDeviceProperties + #define accGPUDeviceSynchronize cudaDeviceSynchronize + #define accGPUSetDevice cudaSetDevice + #define accGPUMemGetInfo cudaMemGetInfo + #define MlOptimiserAccGPU MlOptimiserCuda + #define AutoPickerAccGPU AutoPickerCuda +#elif _HIP_ENABLED + #define accGPUGetDeviceCount hipGetDeviceCount + #define accGPUDeviceProp hipDeviceProp_t + #define accGPUGetDeviceProperties hipGetDeviceProperties + #define accGPUDeviceSynchronize hipDeviceSynchronize + #define accGPUSetDevice hipSetDevice + #define accGPUMemGetInfo hipMemGetInfo + #define MlOptimiserAccGPU MlOptimiserHip + #define AutoPickerAccGPU AutoPickerHip +#elif _SYCL_ENABLED + #define MlOptimiserAccGPU MlOptimiserSYCL #endif #endif /* ACC_SETTINGS_H_ */ diff --git a/src/acc/sycl/device_stubs.h b/src/acc/sycl/device_stubs.h new file mode 100644 index 000000000..4f87df6a6 --- /dev/null +++ b/src/acc/sycl/device_stubs.h @@ -0,0 +1,30 @@ +#ifndef DEVICE_STUBS_H +#define DEVICE_STUBS_H + +#undef CUDA +#undef _CUDA_ENABLED +#undef HIP +#undef _HIP_ENABLED + +#include +#include "src/acc/sycl/sycl_virtual_dev.h" + +using dim3 = int; +using deviceStream_t = virtualSYCL*; +using deviceCustomAllocator = double; + +using cudaStream_t = virtualSYCL*; +using CudaCustomAllocator = double; +#define cudaStreamPerThread 0 + +using hipStream_t = virtualSYCL*; +using HipCustomAllocator = double; +#define hipStreamPerThread 0 + +#define CUSTOM_ALLOCATOR_REGION_NAME( name ) //Do nothing +#define LAUNCH_PRIVATE_ERROR(func, status) +#define LAUNCH_HANDLE_ERROR( err ) +#define DEBUG_HANDLE_ERROR( err ) +#define HANDLE_ERROR( err ) + +#endif diff --git a/src/acc/sycl/mkl_fft.h b/src/acc/sycl/mkl_fft.h new file mode 100644 index 000000000..30fb00b8b --- /dev/null +++ b/src/acc/sycl/mkl_fft.h @@ -0,0 +1,187 @@ +#ifndef MKL_FFT_H_ +#define MKL_FFT_H_ + +#include +#include +#include +#include "src/complex.h" + +extern std::mutex fft_mutex; + +class MklFFT +{ + bool planSet; +public: + AccPtr reals; + AccPtr fouriers; + + int direction; + int dimension; + size_t xSize,ySize,zSize,xFSize,yFSize,zFSize; + +#ifdef ACC_DOUBLE_PRECISION + /* fftw Forward plan */ + fftw_plan fPlanForward; + + /* fftw Backward plan */ + fftw_plan fPlanBackward; + +#else + /* fftw Forward plan */ + fftwf_plan fPlanForward; + + /* fftw Backward plan */ + fftwf_plan fPlanBackward; +#endif + + MklFFT(int transformDimension = 2): + direction {0}, + dimension {transformDimension}, + planSet {false}, + xSize {0}, ySize{0}, zSize{0} + { + fPlanForward = fPlanBackward = NULL; + }; + + void setSize(size_t x, size_t y, size_t z, int setDirection = 0) + { + /* Optional direction input restricts transformer to + * forwards or backwards tranformation only, + * which reduces memory requirements, especially + * for large batches of simulatanous transforms. + * + * FFTW_FORWARDS === -1 + * FFTW_BACKWARDS === +1 + * + * The default direction is 0 === forwards AND backwards + */ + + int checkDim; + if(z>1) + checkDim=3; + else if(y>1) + checkDim=2; + else + checkDim=1; + if(checkDim != dimension) + REPORT_ERROR("You are trying to change the dimesion of a MklFFT transformer, which is not allowed"); + + if( !( (setDirection==-1)||(setDirection==0)||(setDirection==1) ) ) + { + std::cerr << "*ERROR : Setting a MklFFT transformer direction to non-defined value" << std::endl; + return; + } + + direction = setDirection; + + if( x == xSize && y == ySize && z == zSize && planSet) + return; + + clear(); + + xSize = x; + ySize = y; + zSize = z; + + xFSize = x/2 + 1; + yFSize = y; + zFSize = z; + + if ((xSize * ySize * zSize)==0) + ACC_PTR_DEBUG_FATAL("Reals array resized to size zero.\n"); +// reals.resizeHostCopy(xSize * ySize * zSize); + reals.freeHostIfSet(); + reals.setSize(xSize * ySize * zSize); + reals.hostAlloc(); + + if ((xFSize * yFSize * zFSize)==0) + ACC_PTR_DEBUG_FATAL("Fouriers array resized to size zero.\n"); +// fouriers.resizeHostCopy(xFSize * yFSize * zFSize); + fouriers.freeHostIfSet(); + fouriers.setSize(xFSize * yFSize * zFSize); + fouriers.hostAlloc(); + + int N[3]; + if(dimension == 1) + N[0] = xSize; + else if(dimension == 2){ + N[0] = ySize; N[1] = xSize; + } + else { + N[0] = zSize; N[1] = ySize; N[2] = xSize; + } + + { + std::lock_guard lock(fft_mutex); +#ifdef ACC_DOUBLE_PRECISION + fPlanForward = fftw_plan_dft_r2c(dimension, N, reals(), + (fftw_complex*) fouriers(), FFTW_ESTIMATE); + fPlanBackward = fftw_plan_dft_c2r(dimension, N, + (fftw_complex*) fouriers(), reals(), + FFTW_ESTIMATE); + +#else + fPlanForward = fftwf_plan_dft_r2c(dimension, N, reals(), + (fftwf_complex*) fouriers(), FFTW_ESTIMATE); + fPlanBackward = fftwf_plan_dft_c2r(dimension, N, + (fftwf_complex*) fouriers(), reals(), FFTW_ESTIMATE); +#endif + planSet = true; + } + } + + void forward() + { + if(direction==1) + { + std::cout << "trying to execute a forward plan for a MKL FFT transformer which is backwards-only" << std::endl; + return; + } +#ifdef ACC_DOUBLE_PRECISION + fftw_execute_dft_r2c(fPlanForward, reals(), (fftw_complex*) fouriers()); +#else + fftwf_execute_dft_r2c(fPlanForward, reals(), (fftwf_complex*) fouriers()); +#endif + + } + + void backward() + { + if(direction==-1) + { + std::cout << "trying to execute a backwards plan for a MKL FFT transformer which is forwards-only" << std::endl; + return; + } + +#ifdef ACC_DOUBLE_PRECISION + fftw_execute_dft_c2r(fPlanBackward, (fftw_complex*) fouriers(), reals()); +#else + fftwf_execute_dft_c2r(fPlanBackward, (fftwf_complex*) fouriers(), reals()); +#endif + } + + void clear() + { + reals.freeIfSet(); + fouriers.freeIfSet(); + + if (planSet) + { + std::lock_guard lock(fft_mutex); +#ifdef ACC_DOUBLE_PRECISION + fftw_destroy_plan(fPlanForward); + fftw_destroy_plan(fPlanBackward); +#else + fftwf_destroy_plan(fPlanForward); + fftwf_destroy_plan(fPlanBackward); +#endif + fPlanForward = fPlanBackward = NULL; + planSet = false; + } + } + + ~MklFFT() + { clear(); } +}; + +#endif diff --git a/src/acc/sycl/sycl_backprojector.cpp b/src/acc/sycl/sycl_backprojector.cpp new file mode 100644 index 000000000..abb2ee96b --- /dev/null +++ b/src/acc/sycl/sycl_backprojector.cpp @@ -0,0 +1,23 @@ +#ifdef _SYCL_ENABLED + +#include +#include +#include + +#include "src/acc/sycl/device_stubs.h" + +#include "src/acc/acc_ptr.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/sycl/sycl_benchmark_utils.h" +#include "src/acc/sycl/sycl_helper_functions.h" +#include "src/acc/utilities.h" +#include "src/acc/data_types.h" + +#include "src/acc/acc_helper_functions.h" +#include "src/acc/sycl/sycl_settings.h" + +#include "src/acc/acc_backprojector_impl.h" + +#endif diff --git a/src/acc/sycl/sycl_benchmark_utils.h b/src/acc/sycl/sycl_benchmark_utils.h new file mode 100644 index 000000000..79159c903 --- /dev/null +++ b/src/acc/sycl/sycl_benchmark_utils.h @@ -0,0 +1,12 @@ +#ifndef SYCL_BENCHMARK_UTILS_H_ +#define SYCL_BENCHMARK_UTILS_H_ + +//TODO: Is this needed? + +#define CTIC(timer,timing) +#define CTOC(timer,timing) +#define GTIC(timer,timing) +#define GTOC(timer,timing) +#define GATHERGPUTIMINGS(timer) + +#endif /* CUDA_BENCHMARK_UTILS_H_ */ diff --git a/src/acc/sycl/sycl_dev.cpp b/src/acc/sycl/sycl_dev.cpp new file mode 100644 index 000000000..f839a3b59 --- /dev/null +++ b/src/acc/sycl/sycl_dev.cpp @@ -0,0 +1,1096 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "src/acc/sycl/sycl_dev.h" + +#define PRINT_DEV_INFO(sd, x) std::cout << " "#x": " << sd.get_info() << std::endl +#define PRINT_DEV_INFO2020(sd, x) std::cout << " "#x": " << sd.has(sycl::aspect::x) << std::endl + +sycl::async_handler exceptionHandler = [](sycl::exception_list exceptions) +{ + for (const std::exception_ptr &e : exceptions) + { + try + { + std::rethrow_exception(e); + } + catch (const sycl::exception &e) + { + std::cerr << "Caught asynchronous SYCL exception:\n" << e.what() << "\n" << std::flush; + } + } +}; + +devSYCL::devSYCL(const bool isInOrder, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + if (isInOrder) + { + _devQ = new sycl::queue(sycl::default_selector_v, _exHandler, sycl::property::queue::in_order{}); + _queueType = syclQueueType::inOrder; + } + else + { + _devQ = new sycl::queue(sycl::default_selector_v, _exHandler); + _queueType = syclQueueType::outOfOrder; + } + } + catch (const sycl::exception &e) + { + std::cerr << "There is no available SYCL device.\n" << e.what() << std::endl; + std::terminate(); + } + + _devD = _devQ->get_device(); + _devC = _devQ->get_context(); +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + auto d = _devD; + auto pf = d.get_platform(); + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _isFP64Supported = d.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + cardID = -1; + deviceID = -1; + stackID = -1; + nStack = -1; + sliceID = -1; + nSlice = -1; + _computeIndex = -1; + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} + +devSYCL::devSYCL(sycl::device &d, int id, const bool isInOrder, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + if (isInOrder) + { + _devQ = new sycl::queue(d, _exHandler, sycl::property::queue::in_order{}); + _queueType = syclQueueType::inOrder; + } + else + { + _devQ = new sycl::queue(d, _exHandler); + _queueType = syclQueueType::outOfOrder; + } + } + catch (const sycl::exception &e) + { + std::cerr << "Provided SYCL device failed\n" << e.what() << std::endl; + std::terminate(); + } + + _devD = d; + _devC = _devQ->get_context(); +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + _isFP64Supported = d.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + deviceID = id; + _computeIndex = -1; + auto pf = d.get_platform(); + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} + +devSYCL::devSYCL(sycl::device &d, const syclQueueType qType, int id, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + switch (qType) + { + case syclQueueType::inOrder : + _devQ = new sycl::queue(d, _exHandler, sycl::property::queue::in_order{}); + break; + + case syclQueueType::enableProfiling : + _devQ = new sycl::queue(d, _exHandler, sycl::property::queue::enable_profiling{}); + break; + + case syclQueueType::outOfOrder : + default : + _devQ = new sycl::queue(d, _exHandler); + break; + } + } + catch (const sycl::exception &e) + { + std::cerr << "Provided SYCL device failed\n" << e.what() << std::endl; + std::terminate(); + } + + _devD = d; + _devC = _devQ->get_context(); + _queueType = qType; +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + _isFP64Supported = d.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + deviceID = id; + _computeIndex = -1; + auto pf = d.get_platform(); + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} + +devSYCL::devSYCL(sycl::context &c, sycl::device &d, int id, const bool isInOrder, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + if (isInOrder) + { + _devQ = new sycl::queue(c, d, _exHandler, sycl::property::queue::in_order{}); + _queueType = syclQueueType::inOrder; + } + else + { + _devQ = new sycl::queue(c, d, _exHandler); + _queueType = syclQueueType::outOfOrder; + } + } + catch (const sycl::exception &e) + { + std::cerr << "Provided SYCL device failed\n" << e.what() << std::endl; + std::terminate(); + } + _devD = d; + _devC = c; +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + _isFP64Supported = d.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + deviceID = id; + _computeIndex = -1; + auto pf = d.get_platform(); + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} + +devSYCL::devSYCL(sycl::context &c, sycl::device &d, const syclQueueType qType, int id, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + switch (qType) + { + case syclQueueType::inOrder : + _devQ = new sycl::queue(c, d, _exHandler, sycl::property::queue::in_order{}); + break; + + case syclQueueType::enableProfiling : + _devQ = new sycl::queue(c, d, _exHandler, sycl::property::queue::enable_profiling{}); + break; + + case syclQueueType::outOfOrder : + default : + _devQ = new sycl::queue(c, d, _exHandler); + break; + } + } + catch (const sycl::exception &e) + { + std::cerr << "Provided SYCL device failed\n" << e.what() << std::endl; + std::terminate(); + } + _devD = d; + _devC = c; + _queueType = qType; +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + _isFP64Supported = d.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + deviceID = id; + _computeIndex = -1; + auto pf = d.get_platform(); + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} + +#ifdef SYCL_EXT_INTEL_QUEUE_INDEX +devSYCL::devSYCL(sycl::device &d, const int qIndex, int id, const bool isInOrder, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + if (isInOrder) + { + sycl::property_list q_prop {sycl::property::queue::in_order{}, sycl::ext::intel::property::queue::compute_index{qIndex}}; + _devQ = new sycl::queue(d, _exHandler, q_prop); + _queueType = syclQueueType::inOrder; + } + else + { + sycl::property_list q_prop {sycl::ext::intel::property::queue::compute_index{qIndex}}; + _devQ = new sycl::queue(d, _exHandler, q_prop); + _queueType = syclQueueType::outOfOrder; + } + } + catch (const sycl::exception &e) + { + std::cerr << "Provided SYCL device failed\n" << e.what() << std::endl; + std::terminate(); + } + + _devD = d; + _devC = _devQ->get_context(); +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + _isFP64Supported = d.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + _computeIndex = qIndex; + + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + deviceID = id; + auto pf = d.get_platform(); + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} + +devSYCL::devSYCL(sycl::context &c, sycl::device &d, const int qIndex, int id, const bool isInOrder, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + if (isInOrder) + { + sycl::property_list q_prop {sycl::property::queue::in_order{}, sycl::ext::intel::property::queue::compute_index{qIndex}}; + _devQ = new sycl::queue(c, d, _exHandler, q_prop); + _queueType = syclQueueType::inOrder; + } + else + { + sycl::property_list q_prop {sycl::ext::intel::property::queue::compute_index{qIndex}}; + _devQ = new sycl::queue(c, d, _exHandler, q_prop); + _queueType = syclQueueType::outOfOrder; + } + } + catch (const sycl::exception &e) + { + std::cerr << "Provided SYCL device failed\n" << e.what() << std::endl; + std::terminate(); + } + _devD = d; + _devC = c; +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + _isFP64Supported = d.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + _computeIndex = qIndex; + + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + deviceID = id; + auto pf = d.get_platform(); + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} + +devSYCL::devSYCL(sycl::context &c, sycl::device &d, const int qIndex, const syclQueueType qType, int id, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + switch (qType) + { + case syclQueueType::inOrder : + { + sycl::property_list q_prop {sycl::property::queue::in_order{}, sycl::ext::intel::property::queue::compute_index{qIndex}}; + _devQ = new sycl::queue(c, d, _exHandler, q_prop); + } + break; + + case syclQueueType::enableProfiling : + { + sycl::property_list q_prop {sycl::property::queue::enable_profiling{}, sycl::ext::intel::property::queue::compute_index{qIndex}}; + _devQ = new sycl::queue(c, d, _exHandler, q_prop); + } + break; + + case syclQueueType::outOfOrder : + default : + { + sycl::property_list q_prop {sycl::ext::intel::property::queue::compute_index{qIndex}}; + _devQ = new sycl::queue(c, d, _exHandler, q_prop); + } + break; + } + } + catch (const sycl::exception &e) + { + std::cerr << "Provided SYCL device failed\n" << e.what() << std::endl; + std::terminate(); + } + _devD = d; + _devC = c; + _queueType = qType; +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + _isFP64Supported = d.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + _computeIndex = qIndex; + + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + deviceID = id; + auto pf = d.get_platform(); + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} +#endif + +devSYCL::devSYCL(const syclDeviceType dev, int id, const bool isInOrder, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + switch (dev) + { + case syclDeviceType::gpu : + if (isInOrder) + { + _devQ = new sycl::queue(sycl::gpu_selector_v, _exHandler, sycl::property::queue::in_order{}); + _queueType = syclQueueType::inOrder; + } + else + { + _devQ = new sycl::queue(sycl::gpu_selector_v, _exHandler); + _queueType = syclQueueType::outOfOrder; + } + break; + + case syclDeviceType::cpu : + _devQ = new sycl::queue(sycl::cpu_selector_v, _exHandler); + _queueType = syclQueueType::outOfOrder; + break; + + case syclDeviceType::host : + case syclDeviceType::fpga : + default: + _devQ = new sycl::queue(sycl::default_selector_v, _exHandler); + _queueType = syclQueueType::outOfOrder; + break; + } + } + catch (const sycl::exception &e) + { + std::cerr << "There is no available SYCL device.\n" << e.what() << std::endl; + std::terminate(); + } + + _devD = _devQ->get_device(); + _devC = _devQ->get_context(); +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + _isFP64Supported = _devD.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + _computeIndex = -1; + + auto d = _devD; + auto pf = d.get_platform(); + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + deviceID = id; + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} + +devSYCL::devSYCL(const syclBackendType be, const syclDeviceType dev, int id, const bool isInOrder, const bool isAsync) +{ + _exHandler = exceptionHandler; + + try + { + switch (dev) + { + case syclDeviceType::gpu : + if (isInOrder) + { + _devQ = new sycl::queue(sycl::gpu_selector_v, _exHandler, sycl::property::queue::in_order{}); + _queueType = syclQueueType::inOrder; + } + else + { + _devQ = new sycl::queue(sycl::gpu_selector_v, _exHandler); + _queueType = syclQueueType::outOfOrder; + } + break; + + case syclDeviceType::cpu : + _devQ = new sycl::queue(sycl::cpu_selector_v, _exHandler); + _queueType = syclQueueType::outOfOrder; + break; + + case syclDeviceType::host : + case syclDeviceType::fpga : + default: + _devQ = new sycl::queue(sycl::default_selector_v, _exHandler); + _queueType = syclQueueType::outOfOrder; + break; + } + } + catch (const sycl::exception &e) + { + std::cerr << "There is no available SYCL device.\n" << e.what() << std::endl; + std::terminate(); + } + + _devD = _devQ->get_device(); + _devC = _devQ->get_context(); +#ifdef USE_ONEDPL + _devicePolicy = oneapi::dpl::execution::make_device_policy(*_devQ); +#endif + _isFP64Supported = _devD.has(sycl::aspect::fp64); + _isAsyncQueue = isAsync; + _computeIndex = -1; + + auto d = _devD; + auto pf = d.get_platform(); + const auto isizes = d.get_info>(); + maxItem[0] = isizes[0]; + maxItem[1] = isizes[1]; + maxItem[2] = isizes[2]; + maxGroup = d.get_info(); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + auto groups = d.get_info>(); + maxWorkGroup[0] = groups[0]; + maxWorkGroup[1] = groups[1]; + maxWorkGroup[2] = groups[2]; + maxGlobalWorkGroup = d.get_info(); +#else + maxWorkGroup[0] = maxWorkGroup[1] = maxWorkGroup[2] = maxGlobalWorkGroup = std::numeric_limits::max; +#endif + globalMem = d.get_info(); + localMem = d.get_info(); + maxUnit = d.get_info(); + deviceID = id; + deviceName = d.get_info() + " (" + d.get_info() + ") / " + pf.get_info(); + _prev_submission = sycl::event(); + _event.push_back(sycl::event()); +} + +devSYCL::devSYCL(const devSYCL &q) +: _devQ {q._devQ}, _devD {q._devD}, _devC {q._devC}, deviceName {q.deviceName}, + _isAsyncQueue {q._isAsyncQueue}, _isFP64Supported {q._isFP64Supported}, _computeIndex {q._computeIndex}, + cardID {q.cardID}, deviceID {q.deviceID}, stackID {q.stackID}, nStack {q.nStack}, sliceID {q.sliceID}, nSlice {q.nSlice}, + _queueType {q._queueType}, maxItem {q.maxItem}, maxGroup {q.maxGroup}, maxWorkGroup {q.maxWorkGroup}, maxGlobalWorkGroup {q.maxGlobalWorkGroup}, maxUnit {q.maxUnit}, + globalMem {q.globalMem}, localMem {q.localMem}, _prev_submission {sycl::event()}, _event {sycl::event()} +#ifdef USE_ONEDPL + , _devicePolicy {q._devicePolicy} +#endif +{} + +devSYCL::devSYCL(const devSYCL *q) +: _devQ {q->_devQ}, _devD {q->_devD}, _devC {q->_devC}, deviceName {q->deviceName}, + _isAsyncQueue {q->_isAsyncQueue}, _isFP64Supported {q->_isFP64Supported}, _computeIndex {q->_computeIndex}, + cardID {q->cardID}, deviceID {q->deviceID}, stackID {q->stackID}, nStack {q->nStack}, sliceID {q->sliceID}, nSlice {q->nSlice}, + _queueType {q->_queueType}, maxItem {q->maxItem}, maxGroup {q->maxGroup}, maxWorkGroup {q->maxWorkGroup}, maxGlobalWorkGroup {q->maxGlobalWorkGroup}, maxUnit {q->maxUnit}, + globalMem {q->globalMem}, localMem {q->localMem}, _prev_submission {sycl::event()}, _event {sycl::event()} +#ifdef USE_ONEDPL + , _devicePolicy {q->_devicePolicy} +#endif +{} + +devSYCL::~devSYCL() +{ + waitAll(); + delete _devQ; +} + +void* devSYCL::syclMalloc(const size_t bytes, const syclMallocType type, const char *name) +{ + assert(bytes > 0); + void* ptr; + switch(type) + { + case syclMallocType::shared : + ptr = sycl::malloc_shared(bytes, *_devQ); + break; + case syclMallocType::device : + ptr = sycl::malloc_device(bytes, *_devQ); + break; + case syclMallocType::host : + ptr = sycl::malloc_host(bytes, *_devQ); + break; + default : + return nullptr; + } + assert(ptr != nullptr); + + return ptr; +} + +void devSYCL::syclFree(void *ptr) +{ + if (ptr) + sycl::free(ptr, *_devQ); +} + +void devSYCL::syclMemcpy(void *dest, const void *src, const size_t bytes) +{ + assert(dest != nullptr); + assert( src != nullptr); + assert(bytes > 0); + + pushEvent(_devQ->memcpy(dest, src, bytes)); +} + +void devSYCL::syclMemcpyAfterWaitAll(void *dest, const void *src, const size_t bytes) +{ + assert(dest != nullptr); + assert( src != nullptr); + assert(bytes > 0); + + waitAll(); + pushEvent(_devQ->memcpy(dest, src, bytes)); +} + +void devSYCL::syclMemset(void *ptr, const int value, const size_t bytes) +{ + assert(ptr != nullptr); + assert(bytes > 0); + + pushEvent(_devQ->memset(ptr, value, bytes)); +} + +void devSYCL::syclMemsetAfterWaitAll(void *ptr, const int value, const size_t bytes) +{ + assert(ptr != nullptr); + assert(bytes > 0); + + waitAll(); + pushEvent(_devQ->memset(ptr, value, bytes)); +} + +void devSYCL::syclPrefetch(void *ptr, const size_t bytes) +{ + assert(ptr != nullptr); + assert(bytes > 0); + + pushEvent(_devQ->prefetch(ptr, bytes)); +} + +void devSYCL::syclPrefetchAfterWaitAll(void *ptr, const size_t bytes) +{ + assert(ptr != nullptr); + assert(bytes > 0); + + waitAll(); + pushEvent(_devQ->prefetch(ptr, bytes)); +} + +void devSYCL::syclFillInt32(void *ptr, const std::int32_t value, const size_t count) +{ + return syclFill(ptr, value, count); +} + +void devSYCL::syclFillInt32AfterWaitAll(void *ptr, const std::int32_t value, const size_t count) +{ + return syclFillAfterWaitAll(ptr, value, count); +} + +void devSYCL::syclFillUint32(void *ptr, const std::uint32_t value, const size_t count) +{ + return syclFill(ptr, value, count); +} + +void devSYCL::syclFillUint32AfterWaitAll(void *ptr, const std::uint32_t value, const size_t count) +{ + return syclFillAfterWaitAll(ptr, value, count); +} + +void devSYCL::syclFillInt64(void *ptr, const std::int64_t value, const size_t count) +{ + return syclFill(ptr, value, count); +} + +void devSYCL::syclFillInt64AfterWaitAll(void *ptr, const std::int64_t value, const size_t count) +{ + return syclFillAfterWaitAll(ptr, value, count); +} + +void devSYCL::syclFillUint64(void *ptr, const std::uint64_t value, const size_t count) +{ + return syclFill(ptr, value, count); +} + +void devSYCL::syclFillUint64AfterWaitAll(void *ptr, const std::uint64_t value, const size_t count) +{ + return syclFillAfterWaitAll(ptr, value, count); +} + +void devSYCL::syclFillXfloat(void *ptr, const XFLOAT value, const size_t count) +{ + return syclFill(ptr, value, count); +} + +void devSYCL::syclFillXfloatAfterWaitAll(void *ptr, const XFLOAT value, const size_t count) +{ + return syclFillAfterWaitAll(ptr, value, count); +} + +void devSYCL::syclFillRfloat(void *ptr, const RFLOAT value, const size_t count) +{ + return syclFill(ptr, value, count); +} + +void devSYCL::syclFillRfloatAfterWaitAll(void *ptr, const RFLOAT value, const size_t count) +{ + return syclFillAfterWaitAll(ptr, value, count); +} + +void devSYCL::syclFillFloat(void *ptr, const float value, const size_t count) +{ + return syclFill(ptr, value, count); +} + +void devSYCL::syclFillFloatAfterWaitAll(void *ptr, const float value, const size_t count) +{ + return syclFillAfterWaitAll(ptr, value, count); +} + +void devSYCL::syclFillDouble(void *ptr, const double value, const size_t count) +{ + return syclFill(ptr, value, count); +} + +void devSYCL::syclFillDoubleAfterWaitAll(void *ptr, const double value, const size_t count) +{ + return syclFillAfterWaitAll(ptr, value, count); +} + +void devSYCL::waitAll() +{ + if (_queueType == syclQueueType::inOrder) + { + _prev_submission.wait_and_throw(); + _prev_submission = sycl::event(); + } + else + { + sycl::event::wait_and_throw(_event); + _event.clear(); + _event.push_back(sycl::event()); + } +} + +void devSYCL::reCalculateRange(sycl::range<3> &wg, const sycl::range<3> &wi) +{ + if (wg[2] > wi[2]*maxWorkGroup[2]) + { + auto wg12 = wg[1]*wg[2]; + wg[2] = wi[2] * maxWorkGroup[2]; + wg[1] = wg12 % wg[2] == 0 ? wg12 / wg[2] : wg12 / wg[2] + 1; + } + if (wg[1] > wi[1]*maxWorkGroup[1]) + { + auto wg01 = wg[0]*wg[1]; + wg[1] = wi[1] * maxWorkGroup[1]; + wg[0] = wg01 % wg[1] == 0 ? wg01 / wg[1] : wg01 / wg[1] + 1; + } +#if 0 + if (wg[0] > wi[0]*maxWorkGroup[0]) + { + std::cerr << "The number of work-groups are too big. Please increase work-item.\n"; + } +#endif +} + +std::string devSYCL::getName() +{ + std::stringstream ss; + + if (cardID < 0) + ss << "[" << deviceID << "] " << deviceName; + else if (nStack > 1) + { + if (nSlice < 0) + ss << "[" << deviceID << "] Card[" << cardID << "]Stack[" << stackID << "] / " << deviceName; + else if (sliceID >= 0) + ss << "[" << deviceID << "] Card[" << cardID << "]Stack[" << stackID << "]{" << sliceID << "} / " << deviceName; + else if (sliceID < 0) + ss << "[" << deviceID << "] Card[" << cardID << "]Stack[" << stackID << "]{0-" << nSlice-1 << "} / " << deviceName; + } + else + { + if (nSlice < 0) + ss << "[" << deviceID << "] Card[" << cardID << "] / " << deviceName; + else if (sliceID >= 0) + ss << "[" << deviceID << "] Card[" << cardID << "]{" << sliceID << "} / " << deviceName; + else if (sliceID < 0) + ss << "[" << deviceID << "] Card[" << cardID << "]{0-" << nSlice-1 << "} / " << deviceName; + } + + return ss.str(); +} + +void devSYCL::printDeviceInfo(bool printAll) +{ + auto d = _devQ->get_device(); + PRINT_DEV_INFO(d, name); + PRINT_DEV_INFO(d, version); + PRINT_DEV_INFO(d, driver_version); + PRINT_DEV_INFO(d, partition_max_sub_devices); + + PRINT_DEV_INFO(d, max_compute_units); +#ifdef SYCL_EXT_ONEAPI_MAX_WORK_GROUP_QUERY + std::cout << " Max global number of work-groups: " << maxGlobalWorkGroup << std::endl; + std::cout << " Max number of work-groups: " << maxWorkGroup[0] << " x " << maxWorkGroup[1] << " x " << maxWorkGroup[2] << std::endl; +#endif + PRINT_DEV_INFO(d, max_work_group_size); + PRINT_DEV_INFO(d, max_work_item_dimensions); + std::cout << " max_work_item_sizes: " << maxItem[0] << " x " << maxItem[1] << " x " << maxItem[2] << std::endl; + PRINT_DEV_INFO(d, max_num_sub_groups); + const auto subGroups = d.get_info(); + std::cout << " sub_group_sizes: "; + for (auto &subg : subGroups) + std::cout << subg << " "; + std::cout << std::endl; + + PRINT_DEV_INFO(d, max_mem_alloc_size); + PRINT_DEV_INFO(d, global_mem_cache_line_size); + PRINT_DEV_INFO(d, global_mem_cache_size); + PRINT_DEV_INFO(d, global_mem_size); + PRINT_DEV_INFO(d, local_mem_size); + + if (printAll) + { + PRINT_DEV_INFO(d, profiling_timer_resolution); + PRINT_DEV_INFO2020(d, queue_profiling); + + PRINT_DEV_INFO(d, preferred_vector_width_int); + PRINT_DEV_INFO(d, preferred_vector_width_long); + PRINT_DEV_INFO(d, preferred_vector_width_half); + PRINT_DEV_INFO(d, preferred_vector_width_float); + PRINT_DEV_INFO(d, preferred_vector_width_double); + PRINT_DEV_INFO(d, native_vector_width_int); + PRINT_DEV_INFO(d, native_vector_width_long); + PRINT_DEV_INFO(d, native_vector_width_half); + PRINT_DEV_INFO(d, native_vector_width_float); + PRINT_DEV_INFO(d, native_vector_width_double); + + PRINT_DEV_INFO2020(d, usm_device_allocations); + PRINT_DEV_INFO2020(d, usm_host_allocations); + + const auto domains = d.get_info(); + std::cout << " partition_affinity_domain:"; + for (auto& domain : domains) + { + switch(domain) + { + case sycl::info::partition_affinity_domain::numa : + std::cout << " numa"; + break; + case sycl::info::partition_affinity_domain::L1_cache : + std::cout << " L1_cache"; + break; + case sycl::info::partition_affinity_domain::L2_cache : + std::cout << " L2_cache"; + break; + case sycl::info::partition_affinity_domain::L3_cache : + std::cout << " L3_cache"; + break; + default : + break; + } + } + std::cout << std::endl; + } + + const auto hconfigs = d.get_info(); + std::cout << " half_fp_config:"; + for (auto& hconfig : hconfigs) + { + switch(hconfig) + { + case sycl::info::fp_config::fma : + std::cout << " fma"; + break; + case sycl::info::fp_config::denorm : + std::cout << " denorm"; + break; + case sycl::info::fp_config::inf_nan : + std::cout << " inf_nan"; + break; + case sycl::info::fp_config::round_to_nearest : + std::cout << " round_to_nearest"; + break; + case sycl::info::fp_config::round_to_zero : + std::cout << " round_to_zero"; + break; + case sycl::info::fp_config::round_to_inf : + std::cout << " round_to_inf"; + break; + case sycl::info::fp_config::correctly_rounded_divide_sqrt : + std::cout << " correctly_rounded_divide_sqrt"; + break; + case sycl::info::fp_config::soft_float : + std::cout << " soft_float"; + break; + default : + break; + } + } + std::cout << std::endl; + + const auto sconfigs = d.get_info(); + std::cout << " single_fp_config:"; + for (auto& sconfig : sconfigs) + { + switch(sconfig) + { + case sycl::info::fp_config::fma : + std::cout << " fma"; + break; + case sycl::info::fp_config::denorm : + std::cout << " denorm"; + break; + case sycl::info::fp_config::inf_nan : + std::cout << " inf_nan"; + break; + case sycl::info::fp_config::round_to_nearest : + std::cout << " round_to_nearest"; + break; + case sycl::info::fp_config::round_to_zero : + std::cout << " round_to_zero"; + break; + case sycl::info::fp_config::round_to_inf : + std::cout << " round_to_inf"; + break; + case sycl::info::fp_config::correctly_rounded_divide_sqrt : + std::cout << " correctly_rounded_divide_sqrt"; + break; + case sycl::info::fp_config::soft_float : + std::cout << " soft_float"; + break; + default : + break; + } + } + std::cout << std::endl; + + const auto dconfigs = d.get_info(); + std::cout << " double_fp_config:"; + for (auto& dconfig : dconfigs) + { + switch(dconfig) + { + case sycl::info::fp_config::fma : + std::cout << " fma"; + break; + case sycl::info::fp_config::denorm : + std::cout << " denorm"; + break; + case sycl::info::fp_config::inf_nan : + std::cout << " inf_nan"; + break; + case sycl::info::fp_config::round_to_nearest : + std::cout << " round_to_nearest"; + break; + case sycl::info::fp_config::round_to_zero : + std::cout << " round_to_zero"; + break; + case sycl::info::fp_config::round_to_inf : + std::cout << " round_to_inf"; + break; + case sycl::info::fp_config::correctly_rounded_divide_sqrt : + std::cout << " correctly_rounded_divide_sqrt"; + break; + case sycl::info::fp_config::soft_float : + std::cout << " soft_float"; + break; + default : + break; + } + } + std::cout << "\n"; +} diff --git a/src/acc/sycl/sycl_dev.h b/src/acc/sycl/sycl_dev.h new file mode 100644 index 000000000..cc68ace48 --- /dev/null +++ b/src/acc/sycl/sycl_dev.h @@ -0,0 +1,210 @@ +#ifndef _SYCL_DEVICE_RELION_H +#define _SYCL_DEVICE_RELION_H + +#include "src/acc/sycl/sycl_virtual_dev.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_ONEDPL + #include + #include + #include + #include + #include +#endif + +class devSYCL : public virtualSYCL +{ +public: + devSYCL(const devSYCL &q); + devSYCL(const devSYCL *q); + + devSYCL(const bool isInOrder = false, const bool isAsync = false); + devSYCL(sycl::device &d, int id, const bool isInOrder = false, const bool isAsync = false); + devSYCL(sycl::device &d, const syclQueueType qType, int id, const bool isAsync = false); + devSYCL(sycl::context &c, sycl::device &d, int id, const bool isInOrder = false, const bool isAsync = false); + devSYCL(sycl::context &c, sycl::device &d, const syclQueueType qType, int id, const bool isAsync = false); + devSYCL(const syclDeviceType dev, int id, const bool isInOrder = false, const bool isAsync = false); + devSYCL(const syclBackendType be, const syclDeviceType dev, int id, const bool isInOrder = false, const bool isAsync = false); +#ifdef SYCL_EXT_INTEL_QUEUE_INDEX + devSYCL(sycl::device &d, const int qIndex, int id, const bool isInOrder = false, const bool isAsync = false); + devSYCL(sycl::context &c, sycl::device &d, const int qIndex, int id, const bool isInOrder = false, const bool isAsync = false); + devSYCL(sycl::context &c, sycl::device &d, const int qIndex, const syclQueueType qType, int id, const bool isAsync = false); +#endif + ~devSYCL(); + + void pushEvent(const sycl::event &evt) + { + if (_queueType == syclQueueType::inOrder) + _prev_submission = evt; + else + _event.push_back(evt); + } + + template + sycl::event syclSubmitAsync(T&& f) + { + _event.push_back(_devQ->submit(f)); + return getLastEvent(); + } + + template + sycl::event syclSubmitSync(T&& f) + { + _devQ->submit(f).wait_and_throw(); + return sycl::event(); + } + + template + sycl::event syclSubmitSeq(T&& f) + { + _prev_submission = _devQ->submit(f); + return _prev_submission; + } + + template + sycl::event syclSubmit(T&& f) + { + if (_queueType == syclQueueType::inOrder) + return syclSubmitSeq(f); + else + { + if (_isAsyncQueue) + return syclSubmitAsync(f); + else + return syclSubmitSync(f); + } + } + + template + T* syclMalloc(const size_t count, const syclMallocType type = syclMallocType::device, const char *name = "") + { return static_cast(syclMalloc(count * sizeof(T), type, name)); } + + void* syclMalloc(const size_t bytes, const syclMallocType type, const char *name = "") override; + + void syclFree(void *ptr) override; + + void syclMemcpy(void *dest, const void *src, const size_t bytes) override; + void syclMemcpyAfterWaitAll(void *dest, const void *src, const size_t bytes) override; + + void syclMemset(void *ptr, const int value, const size_t bytes) override; + void syclMemsetAfterWaitAll(void *ptr, const int value, const size_t bytes) override; + + template + void syclFill(void *ptr, const T &pattern, const size_t count) + { + assert( ptr != nullptr); + assert(count > 0); + + pushEvent(_devQ->fill(ptr, pattern, count)); + } + + template + void syclFillAfterWaitAll(void *ptr, const T &pattern, const size_t count) + { + assert( ptr != nullptr); + assert(count > 0); + + waitAll(); + pushEvent(_devQ->fill(ptr, pattern, count)); + } + + void syclFillInt32(void *ptr, const std::int32_t value, const size_t count) override; + void syclFillInt32AfterWaitAll(void *ptr, const std::int32_t value, const size_t count) override; + void syclFillUint32(void *ptr, const std::uint32_t value, const size_t count) override; + void syclFillUint32AfterWaitAll(void *ptr, const std::uint32_t value, const size_t count) override; + + void syclFillInt64(void *ptr, const std::int64_t value, const size_t count) override; + void syclFillInt64AfterWaitAll(void *ptr, const std::int64_t value, const size_t count) override; + void syclFillUint64(void *ptr, const std::uint64_t value, const size_t count) override; + void syclFillUint64AfterWaitAll(void *ptr, const std::uint64_t value, const size_t count) override; + + void syclFillXfloat(void *ptr, const XFLOAT value, const size_t count) override; + void syclFillXfloatAfterWaitAll(void *ptr, const XFLOAT value, const size_t count) override; + void syclFillRfloat(void *ptr, const RFLOAT value, const size_t count) override; + void syclFillRfloatAfterWaitAll(void *ptr, const RFLOAT value, const size_t count) override; + + void syclFillFloat(void *ptr, const float value, const size_t count) override; + void syclFillFloatAfterWaitAll(void *ptr, const float value, const size_t count) override; + void syclFillDouble(void *ptr, const double value, const size_t count) override; + void syclFillDoubleAfterWaitAll(void *ptr, const double value, const size_t count) override; + + void syclPrefetch(void *ptr, const size_t bytes) override; + void syclPrefetchAfterWaitAll(void *ptr, const size_t bytes) override; + + void printDeviceInfo(bool printAll = false) override; + + void waitAll() override; + + bool isAsyncQueue() const override { return _isAsyncQueue; } + bool canSupportFP64() const override { return _isFP64Supported; } + syclQueueType getSyclQueueType() const override { return _queueType; } +#ifdef SYCL_EXT_INTEL_QUEUE_INDEX + int getComputeIndex() const { return _computeIndex; } +#else + int getComputeIndex() const { return -1; } +#endif + + std::string getName() override; + int getDeviceID() const override { return deviceID; } + void setDeviceID(int id) override { deviceID = id; } + int getCardID() const override { return cardID; } + void setCardID(int id) override { cardID = id; } + int getStackID() const override { return stackID; } + void setStackID(int id) override { stackID = id; } + int getNumStack() const override { return nStack; } + void setNumStack(int n) override { nStack = n; } + int getSliceID() const override { return sliceID; } + void setSliceID(int id) override { sliceID = id; } + int getNumSlice() const override { return nSlice; } + void setNumSlice(int n) override { nSlice = n; } + + void reCalculateRange(sycl::range<3> &wg, const sycl::range<3> &wi); + + sycl::queue* getQueue() { return _devQ; } + sycl::device& getDevice() { return _devD; } + sycl::context& getContext() { return _devC; } + sycl::event& getLastEvent() { return _event.back(); } + sycl::event& getPrevSubmission() { return _prev_submission; } + std::vector& getEventList() { return _event; } +#ifdef USE_ONEDPL + oneapi::dpl::execution::device_policy<>& getDevicePolicy() { return _devicePolicy; } +#endif + + std::array maxItem; // Maximum work item size in each 3-dimension + size_t maxGroup; // Maximum work item size per group + uint64_t localMem; // Maximum local memory size per group + uint64_t globalMem; // Maximum device memory size per queue + uint32_t maxUnit; // Maximum compute units + std::array maxWorkGroup; // Maximum number of work-groups that can be submitted in each dimension + size_t maxGlobalWorkGroup; // Maximum number of work-groups that can be submitted across all the dimensions + +private: + syclQueueType _queueType; + sycl::queue *_devQ; + sycl::device _devD; + sycl::context _devC; + sycl::async_handler _exHandler; + std::vector _event; + sycl::event _prev_submission; + std::string deviceName; + int cardID; + int deviceID; + int stackID; + int nStack; + int sliceID; + int nSlice; + bool _isAsyncQueue; + bool _isFP64Supported; + int _computeIndex; +#ifdef USE_ONEDPL + oneapi::dpl::execution::device_policy<> _devicePolicy; +#endif +}; + +#endif diff --git a/src/acc/sycl/sycl_helper_functions.cpp b/src/acc/sycl/sycl_helper_functions.cpp new file mode 100644 index 000000000..de0fdf695 --- /dev/null +++ b/src/acc/sycl/sycl_helper_functions.cpp @@ -0,0 +1,24 @@ +#ifdef _SYCL_ENABLED + +// Make sure we build for SYCL +#include "src/acc/sycl/device_stubs.h" + +#include "src/acc/acc_ptr.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/sycl/sycl_benchmark_utils.h" +#include "src/acc/sycl/sycl_helper_functions.h" +#include "src/acc/sycl/sycl_kernels/helper.h" +#include "src/acc/sycl/sycl_kernels/diff2_impl.h" +#include "src/acc/sycl/sycl_kernels/wavg_impl.h" +#include "src/acc/sycl/sycl_kernels/BP_impl.h" +#include "src/acc/utilities.h" +#include "src/acc/data_types.h" + +#include "src/acc/acc_helper_functions.h" +#include "src/acc/sycl/sycl_settings.h" + +#include "src/acc/acc_helper_functions_impl.h" + +#endif // _SYCL_ENABLED diff --git a/src/acc/sycl/sycl_helper_functions.h b/src/acc/sycl/sycl_helper_functions.h new file mode 100644 index 000000000..0e39d6d99 --- /dev/null +++ b/src/acc/sycl/sycl_helper_functions.h @@ -0,0 +1,199 @@ +#ifndef SYCL_HELPER_FUNCTIONS_H_ +#define SYCL_HELPER_FUNCTIONS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "src/complex.h" +#include "src/parallel.h" +#include "src/acc/sycl/sycl_ml_optimiser.h" +#include "src/acc/sycl/sycl_benchmark_utils.h" +#include "src/acc/acc_projector.h" +#include "src/acc/sycl/sycl_kernels/sycl_utils.h" + +namespace syclKernels +{ +#define WINDOW_FT_BLOCK_SIZE 128 +template +void window_fourier_transform( + size_t grid_dim, + size_t Npsi, + size_t block_size, + ACCCOMPLEX *g_in, + ACCCOMPLEX *g_out, + size_t iX, size_t iY, size_t iZ, size_t iYX, //Input dimensions + size_t oX, size_t oY, size_t oZ, size_t oYX, //Output dimensions + size_t max_idx, + size_t max_r2 = 0 + ) +{ + for(size_t blk=0; blk= max_idx) return; + + long int k, i, kp, ip, jp; + + if (check_max_r2) + { + k = n / (iX * iY); + i = (n % (iX * iY)) / iX; + + kp = k < iX ? k : k - iZ; + ip = i < iX ? i : i - iY; + jp = n % iX; + + if (kp*kp + ip*ip + jp*jp > max_r2) + return; + } + else + { + k = n / (oX * oY); + i = (n % (oX * oY)) / oX; + + kp = k < oX ? k : k - oZ; + ip = i < oX ? i : i - oY; + jp = n % oX; + } + + long int in_idx = (kp < 0 ? kp + iZ : kp) * iYX + (ip < 0 ? ip + iY : ip)*iX + jp; + long int out_idx = (kp < 0 ? kp + oZ : kp) * oYX + (ip < 0 ? ip + oY : ip)*oX + jp; + g_out[out_idx + oOFF] = g_in[in_idx + iOFF]; + } // for tid + } // for psi + } // for blk +} + +// may need to change to parallel reduce if it becomes the bottle neck. +template +static T getMin(T *data, size_t size) +{ +// return *std::min_element(std::execution::unseq, data, data+size); + T minv = std::numeric_limits::max(); +#if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp simd reduction(min:minv) +#endif + for (size_t i = 0; i < size; i++) + if (data[i] < minv) minv = data[i]; + return minv; +} + +// may need to change to parallel reduce if it becomes the bottle neck. +template +static T getMax(T *data, size_t size) +{ +// return *std::max_element(std::execution::unseq, data, data+size); + T maxv = std::numeric_limits::lowest(); +#if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp simd reduction(max:maxv) +#endif + for (size_t i = 0; i < size; i++) + if (data[i] > maxv) maxv = data[i]; + return maxv; +} + +template +static T getSum(T *data, size_t size) +{ +// return std::reduce(std::execution::unseq, data, data+size); + T sum = static_cast(0); +#if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp simd reduction(+:sum) +#endif + for (size_t i = 0; i < size; i++) + sum += data[i]; + return sum; +} + +template +inline void min_loc(std::pair *out, std::pair *in) +{ + if (out->second > in->second) + { + out->first = in->first; + out->second = in->second; + } +} + +template +inline void max_loc(std::pair *out, std::pair *in) +{ + if (out->second < in->second) + { + out->first = in->first; + out->second = in->second; + } +} + +template +static std::pair getArgMin(T *data, size_t size) +{ +#if 0 + auto dist = std::min_element(std::execution::unseq, data, data+size); + + std::pair pair; + pair.first = std::distance(data, dist); + pair.second = data[pair.first]; + return pair; +#else + std::pair pair {-1, std::numeric_limits::max()}; + #if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp declare reduction(minloc: std::pair: min_loc(&omp_out, &omp_in)) \ + initializer(omp_priv = {-1, std::numeric_limits::max()}) + #pragma omp simd reduction(minloc:pair) + #endif + for(size_t i=0; i +static std::pair getArgMax(T *data, size_t size) +{ +#if 0 + auto dist = std::max_element(std::execution::unseq, data, data+size); + + std::pair pair; + pair.first = std::distance(data, dist); + pair.second = data[pair.first]; + return pair; +#else + std::pair pair {-1, std::numeric_limits::lowest()}; + #if _OPENMP >= 201307 // For OpenMP 4.0 and later + #pragma omp declare reduction(maxloc: std::pair: max_loc(&omp_out, &omp_in)) \ + initializer(omp_priv = {-1, std::numeric_limits::lowest()}) + #pragma omp simd reduction(maxloc:pair) + #endif + for(size_t i=0; i pair.second) + { + pair.first = i; + pair.second = data[i]; + } + + return pair; +#endif +} + +} // Namespace syclKernels + +#endif //CPU_HELPER_FUNCTIONS_H_ + diff --git a/src/acc/sycl/sycl_kernels/BP.h b/src/acc/sycl/sycl_kernels/BP.h new file mode 100644 index 000000000..2b99481b8 --- /dev/null +++ b/src/acc/sycl/sycl_kernels/BP.h @@ -0,0 +1,50 @@ +#ifndef BP_KERNELS_H_ +#define BP_KERNELS_H_ + +#include "src/acc/acc_backprojector.h" + +namespace syclKernels +{ + +template +#ifndef __INTEL_COMPILER +__attribute__((always_inline)) +#endif +inline +void backproject2D( + unsigned long imageCount, int block_size, + AccProjectorKernel &projector, + XFLOAT *g_img_real, XFLOAT *g_img_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, + XFLOAT *g_weights, XFLOAT *g_Minvsigma2s, XFLOAT *g_ctfs, + unsigned long translation_num, XFLOAT significant_weight, XFLOAT weight_norm, + XFLOAT *g_eulers, + XFLOAT *g_model_real, XFLOAT *g_model_imag, XFLOAT *g_model_weight, + int max_r, int max_r2, XFLOAT padding_factor, + unsigned img_x, unsigned img_y, unsigned img_xy, unsigned mdl_x, + int mdl_inity, size_t mdl_xyz, + virtualSYCL *devAcc); + +template +#ifndef __INTEL_COMPILER +__attribute__((always_inline)) +#endif +inline +void backproject3D( + unsigned long imageCount, int block_size, + AccProjectorKernel &projector, + XFLOAT *g_img_real, XFLOAT *g_img_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + XFLOAT *g_weights, XFLOAT *g_Minvsigma2s, XFLOAT *g_ctfs, + unsigned long translation_num, XFLOAT significant_weight, XFLOAT weight_norm, + XFLOAT *g_eulers, + XFLOAT *g_model_real, XFLOAT *g_model_imag, XFLOAT *g_model_weight, + int max_r, int max_r2, XFLOAT padding_factor, + unsigned img_x, unsigned img_y, unsigned img_z, unsigned img_xyz, + unsigned mdl_x, unsigned mdl_y, int mdl_inity, + int mdl_initz, size_t mdl_xyz, + virtualSYCL *devAcc); + +} // namespace + +#endif diff --git a/src/acc/sycl/sycl_kernels/BP_gpu.h b/src/acc/sycl/sycl_kernels/BP_gpu.h new file mode 100644 index 000000000..e537b5510 --- /dev/null +++ b/src/acc/sycl/sycl_kernels/BP_gpu.h @@ -0,0 +1,407 @@ +#ifndef BP_GPU_KERNELS_H_ +#define BP_GPU_KERNELS_H_ + +#include +#include + +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/sycl/sycl_settings.h" +#include "src/acc/sycl/sycl_kernels/helper.h" +#include "src/acc/sycl/sycl_kernels/helper_gpu.h" + +namespace syclGpuKernels +{ + +template +void sycl_kernel_backproject2D( + sycl::nd_item<3> nit, AccProjectorKernel &projector, + XFLOAT *g_img_real, XFLOAT *g_img_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, + XFLOAT *g_weights, XFLOAT *g_Minvsigma2s, XFLOAT *g_ctfs, + int trans_num, XFLOAT significant_weight, XFLOAT weight_norm, + XFLOAT *g_eulers, XFLOAT *g_model_real, + XFLOAT *g_model_imag, XFLOAT *g_model_weight, + int max_r, int max_r2, XFLOAT padding_factor, + int img_x, int img_y, int img_xy, int mdl_x, int mdl_inity, + XFLOAT *s_eulers) +{ + const int tid = nit.get_local_id(2); + const int img = nit.get_group_linear_id(); + + const int img_y_half = img_y / 2; + const XFLOAT max_r2_out = sycl::floor(max_r2 * padding_factor * padding_factor); + const XFLOAT inv_weight_norm = 1.0f / weight_norm; + + if (tid == 0) + s_eulers[0] = g_eulers[img*9 + 0]; + else if (tid == 1) + s_eulers[1] = g_eulers[img*9 + 1]; + else if (tid == 2) + s_eulers[2] = g_eulers[img*9 + 3]; + else if (tid == 3) + s_eulers[3] = g_eulers[img*9 + 4]; + + __group_barrier(nit); + + const int pixel_pass_num = img_xy/BP_2D_BLOCK_SIZE + 1; + for (int pass = 0; pass < pixel_pass_num; pass++) + { + const int pixel = pass*BP_2D_BLOCK_SIZE + tid; + + if (pixel >= img_xy) + continue; + + int x = pixel % img_x; + int y = pixel / img_x; + if (y > img_y_half) + y -= img_y; + + XFLOAT minvsigma2 = g_Minvsigma2s[pixel]; + XFLOAT img_real = g_img_real[pixel]; + XFLOAT img_imag = g_img_imag[pixel]; + XFLOAT ctf = g_ctfs[pixel]; + + XFLOAT ref_real = 0.0f; + XFLOAT ref_imag = 0.0f; + if (SGD) + { + projector.project2Dmodel(x, y, s_eulers[0], s_eulers[1], s_eulers[2], s_eulers[3], ref_real, ref_imag); + ref_real *= ctf; + ref_imag *= ctf; + } + + XFLOAT Fweight = 0.0f; + XFLOAT real = 0.0f; + XFLOAT imag = 0.0f; + for (int itrans = 0; itrans < trans_num; itrans++) + { + XFLOAT weight = g_weights[img*trans_num + itrans]; + + if (weight >= significant_weight) + { + if (CTF_PREMULTIPLIED) + weight = weight * inv_weight_norm * minvsigma2; + else + weight = weight * inv_weight_norm * ctf * minvsigma2; + + Fweight += weight * ctf; // SHWS 13feb2020: from now on when ctf_premultiplied, the ctf array actually contains ctf^2! + + XFLOAT temp_real, temp_imag; + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], img_real, img_imag, temp_real, temp_imag); + + if (SGD) + { + real += (temp_real - ref_real) * weight; + imag += (temp_imag - ref_imag) * weight; + } + else + { + real += temp_real * weight; + imag += temp_imag * weight; + } + } + } + + if (Fweight > 0.0f) + { + // Get logical coordinates in the 3D map + XFLOAT xp = (s_eulers[0]*x + s_eulers[1]*y) * padding_factor; + XFLOAT yp = (s_eulers[2]*x + s_eulers[3]*y) * padding_factor; + + // Only consider pixels that are projected inside the allowed circle in output coordinates. + // --JZ, Nov. 26th 2018 + if ((xp*xp + yp*yp) > max_r2_out) // TODO: This is very subtle and make big difference for some cases + continue; + + // Only asymmetric half is stored + if (xp < 0.0f) + { + // Get complex conjugated hermitian symmetry pair + xp = -xp; + yp = -yp; + imag = -imag; + } + + int x0 = sycl::floor(xp); + XFLOAT fx = xp - x0; + + int y0 = sycl::floor(yp); + XFLOAT fy = yp - y0; + y0 -= mdl_inity; + + int offset1 = y0*mdl_x + x0; + int offset2 = offset1 + 1; + int offset3 = offset1 + mdl_x; + int offset4 = offset1 + mdl_x + 1; + + XFLOAT mfx = 1.0f - fx; + XFLOAT mfy = 1.0f - fy; + + XFLOAT dd00 = mfy * mfx; + XFLOAT dd01 = mfy * fx; + XFLOAT dd10 = fy * mfx; + XFLOAT dd11 = fy * fx; + + atomic_ref_dev( g_model_real [offset1] ).fetch_add(dd00 * real); + atomic_ref_dev( g_model_imag [offset1] ).fetch_add(dd00 * imag); + atomic_ref_dev( g_model_weight[offset1] ).fetch_add(dd00 * Fweight); + + atomic_ref_dev( g_model_real [offset2] ).fetch_add(dd01 * real); + atomic_ref_dev( g_model_imag [offset2] ).fetch_add(dd01 * imag); + atomic_ref_dev( g_model_weight[offset2] ).fetch_add(dd01 * Fweight); + + atomic_ref_dev( g_model_real [offset3] ).fetch_add(dd10 * real); + atomic_ref_dev( g_model_imag [offset3] ).fetch_add(dd10 * imag); + atomic_ref_dev( g_model_weight[offset3] ).fetch_add(dd10 * Fweight); + + atomic_ref_dev( g_model_real [offset4] ).fetch_add(dd11 * real); + atomic_ref_dev( g_model_imag [offset4] ).fetch_add(dd11 * imag); + atomic_ref_dev( g_model_weight[offset4] ).fetch_add(dd11 * Fweight); + } + } +} + +template +void sycl_kernel_backproject3D( + sycl::nd_item<3> nit, AccProjectorKernel &projector, + XFLOAT *g_img_real, XFLOAT *g_img_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + XFLOAT *g_weights, XFLOAT *g_Minvsigma2s, XFLOAT *g_ctfs, + int trans_num, XFLOAT significant_weight, XFLOAT weight_norm, + XFLOAT *g_eulers, + XFLOAT *g_model_real, XFLOAT *g_model_imag, XFLOAT *g_model_weight, + int max_r, int max_r2, XFLOAT padding_factor, int img_x, + int img_y, int img_z, int img_xyz, int mdl_x, + int mdl_y, int mdl_inity, int mdl_initz, + XFLOAT *s_eulers) +{ + const int tid = nit.get_local_id(2); + const int img = nit.get_group_linear_id(); + + const int img_y_half = img_y / 2; + const int img_z_half = img_z / 2; + const XFLOAT max_r2_vol = sycl::floor(max_r2 * padding_factor * padding_factor); + const XFLOAT inv_weight_norm = 1.0f / weight_norm; + + if (tid < 9) + s_eulers[tid] = g_eulers[img*9 + tid]; + + __group_barrier(nit); + + int pixel_pass_num; + if (DATA3D) + pixel_pass_num = img_xyz/BP_DATA3D_BLOCK_SIZE + 1; + else + pixel_pass_num = img_xyz/BP_REF3D_BLOCK_SIZE + 1; + + for (int pass = 0; pass < pixel_pass_num; pass++) + { + int pixel; + if (DATA3D) + pixel = pass*BP_DATA3D_BLOCK_SIZE + tid; + else + pixel = pass*BP_REF3D_BLOCK_SIZE + tid; + + if (pixel >= img_xyz) + continue; + + int x, y, z, xy; + if (DATA3D) + { + z = pixel / (img_x*img_y); + xy = pixel % (img_x*img_y); + x = xy % img_x; + y = xy / img_x; + + if (z > img_z_half) + { + z = z - img_z; + + if (x == 0) + continue; + } + } + else + { + x = pixel % img_x; + y = pixel / img_x; + } + if (y > img_y_half) + y = y - img_y; + + //WAVG + XFLOAT minvsigma2 = g_Minvsigma2s[pixel]; + XFLOAT img_real = g_img_real[pixel]; + XFLOAT img_imag = g_img_imag[pixel]; + XFLOAT ctf = g_ctfs[pixel]; + + XFLOAT ref_real = 0.0f; + XFLOAT ref_imag = 0.0f; + if (SGD) + { + if (DATA3D) + projector.project3Dmodel( + x, y, z, + s_eulers[0], s_eulers[1], s_eulers[2], + s_eulers[3], s_eulers[4], s_eulers[5], + s_eulers[6], s_eulers[7], s_eulers[8], + ref_real, ref_imag); + else + projector.project3Dmodel( + x, y, + s_eulers[0], s_eulers[1], + s_eulers[3], s_eulers[4], + s_eulers[6], s_eulers[7], + ref_real, ref_imag); + + ref_real *= ctf; + ref_imag *= ctf; + } + + XFLOAT Fweight = 0.0f; + XFLOAT real = 0.0f; + XFLOAT imag = 0.0f; + for (int itrans = 0; itrans < trans_num; itrans++) + { + XFLOAT weight = g_weights[img*trans_num + itrans]; + + if (weight >= significant_weight) + { + if (CTF_PREMULTIPLIED) + weight = weight * inv_weight_norm * minvsigma2; + else + weight = weight * inv_weight_norm * ctf * minvsigma2; + + Fweight += weight * ctf; // SHWS 13feb2020: from now on when ctf_premultiplied, the ctf array actually contains ctf^2! + + XFLOAT temp_real, temp_imag; + if (DATA3D) + translatePixel(x, y, z, g_trans_x[itrans], g_trans_y[itrans], g_trans_z[itrans], img_real, img_imag, temp_real, temp_imag); + else + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], img_real, img_imag, temp_real, temp_imag); + + if (SGD) + { + real += (temp_real - ref_real) * weight; + imag += (temp_imag - ref_imag) * weight; + } + else + { + real += temp_real * weight; + imag += temp_imag * weight; + } + } + } + + //BP + if (Fweight > 0.0f) + { + // Get logical coordinates in the 3D map + XFLOAT xp, yp, zp; + if (DATA3D) + { + xp = (s_eulers[0]*x + s_eulers[1]*y + s_eulers[2]*z) * padding_factor; + yp = (s_eulers[3]*x + s_eulers[4]*y + s_eulers[5]*z) * padding_factor; + zp = (s_eulers[6]*x + s_eulers[7]*y + s_eulers[8]*z) * padding_factor; + } + else + { + xp = (s_eulers[0]*x + s_eulers[1]*y) * padding_factor; + yp = (s_eulers[3]*x + s_eulers[4]*y) * padding_factor; + zp = (s_eulers[6]*x + s_eulers[7]*y) * padding_factor; + } + + // Only consider pixels that are projected inside the sphere in output coordinates. + // --JZ, Oct. 18. 2018 + if ((xp*xp + yp*yp + zp*zp) > max_r2_vol) // TODO: This is very subtle and make big difference for some cases + continue; + + // Only asymmetric half is stored + if (xp < 0.0f) + { + // Get complex conjugated hermitian symmetry pair + xp = -xp; + yp = -yp; + zp = -zp; + imag = -imag; + } + + int x0 = sycl::floor(xp); + XFLOAT fx = xp - x0; + + int y0 = sycl::floor(yp); + XFLOAT fy = yp - y0; + y0 -= mdl_inity; + + int z0 = sycl::floor(zp); + XFLOAT fz = zp - z0; + z0 -= mdl_initz; + + int offset1 = z0*mdl_x*mdl_y + y0*mdl_x + x0; + int offset2 = offset1 + 1; + int offset3 = offset1 + mdl_x; + int offset4 = offset1 + mdl_x + 1; + int offset5 = offset1 + mdl_x*mdl_y; + int offset6 = offset1 + mdl_x*mdl_y + 1; + int offset7 = offset1 + mdl_x*mdl_y + mdl_x; + int offset8 = offset1 + mdl_x*mdl_y + mdl_x + 1; + + XFLOAT mfx = 1.0f - fx; + XFLOAT mfy = 1.0f - fy; + XFLOAT mfz = 1.0f - fz; + + XFLOAT dd000 = mfz * mfy * mfx; + + atomic_ref_dev( g_model_real [offset1] ).fetch_add(dd000 * real); + atomic_ref_dev( g_model_imag [offset1] ).fetch_add(dd000 * imag); + atomic_ref_dev( g_model_weight[offset1] ).fetch_add(dd000 * Fweight); + + XFLOAT dd001 = mfz * mfy * fx; + + atomic_ref_dev( g_model_real [offset2] ).fetch_add(dd001 * real); + atomic_ref_dev( g_model_imag [offset2] ).fetch_add(dd001 * imag); + atomic_ref_dev( g_model_weight[offset2] ).fetch_add(dd001 * Fweight); + + XFLOAT dd010 = mfz * fy * mfx; + + atomic_ref_dev( g_model_real [offset3] ).fetch_add(dd010 * real); + atomic_ref_dev( g_model_imag [offset3] ).fetch_add(dd010 * imag); + atomic_ref_dev( g_model_weight[offset3] ).fetch_add(dd010 * Fweight); + + XFLOAT dd011 = mfz * fy * fx; + + atomic_ref_dev( g_model_real [offset4] ).fetch_add(dd011 * real); + atomic_ref_dev( g_model_imag [offset4] ).fetch_add(dd011 * imag); + atomic_ref_dev( g_model_weight[offset4] ).fetch_add(dd011 * Fweight); + + XFLOAT dd100 = fz * mfy * mfx; + + atomic_ref_dev( g_model_real [offset5] ).fetch_add(dd100 * real); + atomic_ref_dev( g_model_imag [offset5] ).fetch_add(dd100 * imag); + atomic_ref_dev( g_model_weight[offset5] ).fetch_add(dd100 * Fweight); + + XFLOAT dd101 = fz * mfy * fx; + + atomic_ref_dev( g_model_real [offset6] ).fetch_add(dd101 * real); + atomic_ref_dev( g_model_imag [offset6] ).fetch_add(dd101 * imag); + atomic_ref_dev( g_model_weight[offset6] ).fetch_add(dd101 * Fweight); + + XFLOAT dd110 = fz * fy * mfx; + + atomic_ref_dev( g_model_real [offset7] ).fetch_add(dd110 * real); + atomic_ref_dev( g_model_imag [offset7] ).fetch_add(dd110 * imag); + atomic_ref_dev( g_model_weight[offset7] ).fetch_add(dd110 * Fweight); + + XFLOAT dd111 = fz * fy * fx; + + atomic_ref_dev( g_model_real [offset8] ).fetch_add(dd111 * real); + atomic_ref_dev( g_model_imag [offset8] ).fetch_add(dd111 * imag); + atomic_ref_dev( g_model_weight[offset8] ).fetch_add(dd111 * Fweight); + } + } +} + +} // namespace + +#endif diff --git a/src/acc/sycl/sycl_kernels/BP_impl.h b/src/acc/sycl/sycl_kernels/BP_impl.h new file mode 100644 index 000000000..1d384ef38 --- /dev/null +++ b/src/acc/sycl/sycl_kernels/BP_impl.h @@ -0,0 +1,145 @@ +#ifndef BP_IMPL_KERNELS_H_ +#define BP_IMPL_KERNELS_H_ + + +#include +#include +#include +#include +#include +#include + +#include "src/acc/acc_backprojector.h" +#include "src/acc/sycl/sycl_dev.h" +#include "src/acc/sycl/sycl_kernels/sycl_utils.h" +#include "src/acc/sycl/sycl_kernels/BP_gpu.h" + +namespace syclKernels +{ + +template +__attribute__((always_inline)) +inline +void backproject2D( + unsigned long imageCount, int block_size, + AccProjectorKernel &projector, + XFLOAT *g_img_real, XFLOAT *g_img_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, + XFLOAT *g_weights, XFLOAT *g_Minvsigma2s, XFLOAT *g_ctfs, + unsigned long trans_num, XFLOAT significant_weight, XFLOAT weight_norm, + XFLOAT *g_eulers, + XFLOAT *g_model_real, XFLOAT *g_model_imag, XFLOAT *g_model_weight, + int max_r, int max_r2, XFLOAT padding_factor, + int img_x, int img_y, unsigned img_xy, int mdl_x, + int mdl_inity, int mdl_y, + virtualSYCL *devAcc) +{ + devSYCL *dGPU = dynamic_cast(devAcc); + assert( trans_num <= std::numeric_limits::max()); + assert( img_xy <= std::numeric_limits::max()); + assert(mdl_x*mdl_y <= std::numeric_limits::max()); + assert( imageCount <= std::numeric_limits::max()); + assert( imageCount <= dGPU->maxWorkGroup[1]); + assert( block_size <= dGPU->maxItem[2]); + + sycl::range<3> wi (1,1,block_size); + sycl::range<3> wg (1,imageCount,block_size); + dGPU->reCalculateRange(wg, wi); + auto event = dGPU->syclSubmit + ( + [&](sycl::handler &cgh) // + { + using namespace sycl; + // accessors to device memory + local_accessor s_eulers_acc(range<1>(4), cgh); + + cgh.parallel_for + ( + nd_range<3>(wg, wi), [=](nd_item<3> nit) + #if defined(__INTEL_LLVM_COMPILER) && defined(INTEL_SG_SIZE) + [[intel::reqd_sub_group_size(INTEL_SG_SIZE)]] + #endif + { + syclGpuKernels::sycl_kernel_backproject2D + ( // + nit, const_cast(projector), + g_img_real, g_img_imag, g_trans_x, g_trans_y, + g_weights, g_Minvsigma2s, g_ctfs, + static_cast(trans_num), significant_weight, weight_norm, + g_eulers, g_model_real, g_model_imag, g_model_weight, + max_r, max_r2, padding_factor, + img_x, img_y, static_cast(img_xy), + mdl_x, mdl_inity, + s_eulers_acc.get_pointer() + ); // End of sycl_kernel_backproject2D + } // End of cgh.parallel_for Lamda function + ); // End of cgh.parallel_for + } // End of dGPU->syclSubmit Lamda function + ); // End of dGPU->syclSubmit +} + +template +__attribute__((always_inline)) +inline +void backproject3D( + unsigned long imageCount, int block_size, + AccProjectorKernel &projector, + XFLOAT *g_img_real, XFLOAT *g_img_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + XFLOAT *g_weights, XFLOAT *g_Minvsigma2s, XFLOAT *g_ctfs, + unsigned long trans_num, XFLOAT significant_weight, XFLOAT weight_norm, + XFLOAT *g_eulers, + XFLOAT *g_model_real, XFLOAT *g_model_imag, XFLOAT *g_model_weight, + int max_r, int max_r2, XFLOAT padding_factor, + int img_x, int img_y, int img_z, unsigned img_xyz, + int mdl_x, int mdl_y, int mdl_inity, + int mdl_initz, size_t mdl_xyz, + virtualSYCL *devAcc) +{ + devSYCL *dGPU = dynamic_cast(devAcc); + assert( trans_num <= std::numeric_limits::max()); + assert( img_xyz <= std::numeric_limits::max()); + assert( mdl_xyz <= std::numeric_limits::max()); + assert(imageCount <= std::numeric_limits::max()); + assert(imageCount <= dGPU->maxWorkGroup[1]); + assert(block_size <= dGPU->maxItem[2]); + + sycl::range<3> wi (1,1,block_size); + sycl::range<3> wg (1,imageCount,block_size); + dGPU->reCalculateRange(wg, wi); + auto event = dGPU->syclSubmit + ( + [&](sycl::handler &cgh) // + { + using namespace sycl; + // accessors to device memory + local_accessor s_eulers_acc(range<1>(9), cgh); + + cgh.parallel_for + ( + nd_range<3>(wg, wi), [=](nd_item<3> nit) + #if defined(__INTEL_LLVM_COMPILER) && defined(INTEL_SG_SIZE) + [[intel::reqd_sub_group_size(INTEL_SG_SIZE)]] + #endif + { + syclGpuKernels::sycl_kernel_backproject3D + ( // + nit, const_cast(projector), + g_img_real, g_img_imag, g_trans_x, g_trans_y, g_trans_z, + g_weights, g_Minvsigma2s, g_ctfs, + static_cast(trans_num), significant_weight, weight_norm, + g_eulers, g_model_real, g_model_imag, g_model_weight, + max_r, max_r2, padding_factor, + img_x, img_y, img_z, static_cast(img_xyz), + mdl_x, mdl_y, mdl_inity, mdl_initz, + s_eulers_acc.get_pointer() + ); // End of sycl_kernel_backproject3D + } // End of cgh.parallel_for Lamda function + ); // End of cgh.parallel_for + } // End of dGPU->syclSubmit Lamda function + ); // End of dGPU->syclSubmit +} + +} // namespace + +#endif diff --git a/src/acc/sycl/sycl_kernels/diff2.h b/src/acc/sycl/sycl_kernels/diff2.h new file mode 100644 index 000000000..46edd7ae5 --- /dev/null +++ b/src/acc/sycl/sycl_kernels/diff2.h @@ -0,0 +1,84 @@ +#ifndef DIFF2_KERNELS_H_ +#define DIFF2_KERNELS_H_ + +#include "src/acc/sycl/sycl_virtual_dev.h" + +class AccProjectorKernel; + +namespace syclKernels +{ + template +#ifdef __INTEL_COMPILER + inline +#else + __attribute__((always_inline)) inline +#endif + void diff2_coarse( + unsigned long grid_size, + XFLOAT *g_eulers, XFLOAT *trans_x, XFLOAT *trans_y, XFLOAT *trans_z, + XFLOAT *g_real, XFLOAT *g_imag, + AccProjectorKernel &projector, + XFLOAT *g_corr, XFLOAT *g_diff2s, + unsigned long translation_num, unsigned long image_size, + unsigned long orientation_num, virtualSYCL *devAcc + ); + + template +#ifdef __INTEL_COMPILER + inline +#else + __attribute__((always_inline)) inline +#endif + void diff2_fine( + unsigned long grid_size, + XFLOAT *g_eulers, XFLOAT *g_imgs_real, XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + AccProjectorKernel &projector, + XFLOAT *g_corr_img, XFLOAT *g_diff2s, + unsigned long image_size, XFLOAT sum_init, + unsigned long orientation_num, unsigned long translation_num, unsigned long num_jobs, + unsigned long *d_rot_idx, unsigned long *d_trans_idx, + unsigned long *d_job_idx, unsigned long *d_job_num, + size_t d_size, virtualSYCL *devAcc + ); + +/* + * CROSS-CORRELATION-BASED KERNELS + */ + template +#ifdef __INTEL_COMPILER + inline +#else + __attribute__((always_inline)) inline +#endif + void diff2_CC_coarse( + unsigned long grid_size, + XFLOAT *g_eulers, XFLOAT *g_imgs_real, XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, + AccProjectorKernel &projector, + XFLOAT *g_corr_img, XFLOAT *g_diff2s, + unsigned long trans_num, unsigned long image_size, XFLOAT exp_local_sqrtXi2, + unsigned long orientation_num, virtualSYCL *devAcc + ); + + template +#ifdef __INTEL_COMPILER + inline +#else + __attribute__((always_inline)) inline +#endif + void diff2_CC_fine( + unsigned long grid_size, + XFLOAT *g_eulers, XFLOAT *g_imgs_real, XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + AccProjectorKernel &projector, + XFLOAT *g_corr_img, XFLOAT *g_diff2s, + unsigned long image_size, XFLOAT sum_init, XFLOAT exp_local_sqrtXi2, + unsigned long orientation_num, unsigned long translation_num, unsigned long num_jobs, + unsigned long *d_rot_idx, unsigned long *d_trans_idx, + unsigned long *d_job_idx, unsigned long *d_job_num, + size_t d_size, virtualSYCL *devAcc + ); +} // end of namespace syclKernels + +#endif /* DIFF2_KERNELS_H_ */ diff --git a/src/acc/sycl/sycl_kernels/diff2_gpu.h b/src/acc/sycl/sycl_kernels/diff2_gpu.h new file mode 100644 index 000000000..931a8356b --- /dev/null +++ b/src/acc/sycl/sycl_kernels/diff2_gpu.h @@ -0,0 +1,526 @@ +#ifndef DIFF2_GPU_KERNELS_H_ +#define DIFF2_GPU_KERNELS_H_ + +#include +#include +#include +#include +#include + +#include "src/acc/acc_projector.h" +#include "src/acc/sycl/sycl_settings.h" +#include "src/acc/sycl/sycl_kernels/sycl_utils.h" +#include "src/acc/sycl/sycl_kernels/helper.h" +#include "src/acc/sycl/sycl_kernels/helper_gpu.h" + +namespace syclGpuKernels +{ + +/* + * DIFFERENCE-BASED KERNELS + */ +template +void sycl_kernel_diff2_coarse( + sycl::nd_item<3> nit, AccProjectorKernel &projector, + XFLOAT *g_eulers, XFLOAT *trans_x, XFLOAT *trans_y, XFLOAT *trans_z, + XFLOAT *g_real, XFLOAT *g_imag, XFLOAT *g_corr, XFLOAT *g_diff2s, + int trans_num, int image_size, + XFLOAT *s_eulers, XFLOAT *s_ref_real, XFLOAT *s_ref_imag, + XFLOAT *s_real, XFLOAT *s_imag, XFLOAT *s_corr + ) +{ + const int tid = nit.get_local_id(2); + const int blockid = nit.get_group_linear_id(); + + const int xSize = projector.imgX; + const int ySize = projector.imgY; + const int zSize = projector.imgZ; + const int maxR = projector.maxR; + + const int max_block_pass_euler {((eulers_per_block*9)/block_sz + 1) * block_sz}; + for (int i = tid; i < max_block_pass_euler; i += block_sz) + if (i < eulers_per_block * 9) + s_eulers[i] = g_eulers[blockid*eulers_per_block*9 + i]; + + + XFLOAT diff2s[eulers_per_block] {0.0f}; + + const XFLOAT tx {trans_x[tid % trans_num]}; + const XFLOAT ty {trans_y[tid % trans_num]}; + const XFLOAT tz {trans_z[tid % trans_num]}; + + //Step through data + const int max_block_pass_pixel {(image_size/block_sz + 1) * block_sz}; + for (int init_pixel = 0; init_pixel < max_block_pass_pixel; init_pixel += block_sz/prefetch_fraction) + { + __group_barrier(nit); + + //Prefetch block-fraction-wise + if (init_pixel + tid/prefetch_fraction < image_size) + { + int x, y, z, xy; + if (DATA3D) + { + z = (init_pixel + tid/prefetch_fraction) / (xSize*ySize); + xy = (init_pixel + tid/prefetch_fraction) % (xSize*ySize); + x = xy % xSize; + y = xy / xSize; + if (z > maxR) + z -= zSize; + } + else + { + x = (init_pixel + tid/prefetch_fraction) % xSize; + y = (init_pixel + tid/prefetch_fraction) / xSize; + } + if (y > maxR) + y -= ySize; + + for (int e = tid%prefetch_fraction; e < eulers_per_block; e += prefetch_fraction) + { + if (DATA3D) // if DATA3D, then REF3D as well. + { + projector.project3Dmodel( + x, y, z, + s_eulers[e*9 ], s_eulers[e*9+1], s_eulers[e*9+2], + s_eulers[e*9+3], s_eulers[e*9+4], s_eulers[e*9+5], + s_eulers[e*9+6], s_eulers[e*9+7], s_eulers[e*9+8], + s_ref_real[eulers_per_block * (tid/prefetch_fraction) + e], + s_ref_imag[eulers_per_block * (tid/prefetch_fraction) + e]); + } + else if (REF3D) + { + projector.project3Dmodel( + x,y, + s_eulers[e*9 ], s_eulers[e*9+1], s_eulers[e*9+3], + s_eulers[e*9+4], s_eulers[e*9+6], s_eulers[e*9+7], + s_ref_real[eulers_per_block * (tid/prefetch_fraction) + e], + s_ref_imag[eulers_per_block * (tid/prefetch_fraction) + e]); + } + else + { + projector.project2Dmodel( + x,y, + s_eulers[e*9 ], s_eulers[e*9+1], s_eulers[e*9+3], s_eulers[e*9+4], + s_ref_real[eulers_per_block * (tid/prefetch_fraction) + e], + s_ref_imag[eulers_per_block * (tid/prefetch_fraction) + e]); + } + } + } + + //Prefetch block-wise + if (init_pixel % block_sz == 0 && init_pixel + tid < image_size) + { + s_real[tid] = g_real[init_pixel + tid]; + s_imag[tid] = g_imag[init_pixel + tid]; + s_corr[tid] = g_corr[init_pixel + tid] / 2.0f; + } + __group_barrier(nit); + + if (tid/trans_num < block_sz/trans_num) // NOTE int division A/B==C/B !=> A==C + { + for (int pix = tid/trans_num; pix < block_sz/prefetch_fraction; pix += block_sz/trans_num) + { + if ((init_pixel + pix) >= image_size) break; + + int x, y, z, xy; + if (DATA3D) + { + z = (init_pixel + pix) / (xSize*ySize); + xy = (init_pixel + pix) % (xSize*ySize); + x = xy % xSize; + y = xy / ySize; + if (z > maxR) + z -= zSize; + } + else + { + x = (init_pixel + pix) % xSize; + y = (init_pixel + pix) / xSize; + } + if (y > maxR) + y -= ySize; + + XFLOAT real, imag; + if (DATA3D) + { +// translatePixel(x, y, z, tx, ty, tz, s_real[pix+init_pixel%block_sz], s_imag[pix+init_pixel%block_sz], real, imag); + XFLOAT val {x*tx + y*ty + z*tz}; + XFLOAT s {sycl::native::sin(val)}; + XFLOAT c {sycl::native::cos(val)}; + real = c * s_real[pix + init_pixel%block_sz] - s * s_imag[pix + init_pixel%block_sz]; + imag = c * s_imag[pix + init_pixel%block_sz] + s * s_real[pix + init_pixel%block_sz]; + } + else + { +// translatePixel(x, y, tx, ty, s_real[pix+init_pixel%block_sz], s_imag[pix+init_pixel%block_sz], real, imag); + XFLOAT val {x*tx + y*ty}; + XFLOAT s {sycl::native::sin(val)}; + XFLOAT c {sycl::native::cos(val)}; + real = c*s_real[pix + init_pixel%block_sz] - s*s_imag[pix + init_pixel%block_sz]; + imag = c*s_imag[pix + init_pixel%block_sz] + s*s_real[pix + init_pixel%block_sz]; + } + + for (int e = 0; e < eulers_per_block; e++) + { + XFLOAT diff_real {s_ref_real[eulers_per_block*pix + e] - real}; + XFLOAT diff_imag {s_ref_imag[eulers_per_block*pix + e] - imag}; + diff2s[e] += (diff_real*diff_real + diff_imag*diff_imag) * s_corr[pix + init_pixel%block_sz]; + } + } // for over i = tid/trans_num + } // if < image_size + } // for over init_pixel + + //Set global + for (int e = 0; e < eulers_per_block; e++) + atomic_ref_wg( g_diff2s[blockid*eulers_per_block*trans_num + e*trans_num + tid%trans_num] ).fetch_add(diff2s[e]); +} + +template +void sycl_kernel_diff2_fine( + sycl::nd_item<3> nit, AccProjectorKernel &projector, + XFLOAT *g_eulers, XFLOAT *g_imgs_real, XFLOAT *g_imgs_imag, + XFLOAT *trans_x, XFLOAT *trans_y, XFLOAT *trans_z, + XFLOAT *g_corr_img, XFLOAT *g_diff2s, + int image_size, XFLOAT sum_init, int todo_blocks, + unsigned long *d_rot_idx, unsigned long *d_trans_idx, + unsigned long *d_job_idx, unsigned long *d_job_num, + XFLOAT *s) +{ + const int bid = nit.get_group_linear_id(); + const int tid = nit.get_local_id(2); + + const int xSize = projector.imgX; + const int ySize = projector.imgY; + const int zSize = projector.imgZ; + const int maxR = projector.maxR; + + if (bid < todo_blocks ) // we only need to make + { + int trans_num = static_cast(d_job_num[bid]); //how many transes we have for this rot + for (int itrans = 0; itrans < trans_num; itrans++) + s[itrans*block_sz + tid] = 0.0f; + + // index of comparison + int ix = static_cast(d_rot_idx[d_job_idx[bid]]); + int pass_num {image_size/block_sz + 1}; + + for (int pass = 0; pass < pass_num; pass++) // finish an entire ref image each block + { + int pixel = pass*block_sz + tid; + + if (pixel < image_size) + { + int x, y, z, xy; + if (DATA3D) + { + z = pixel / (xSize*ySize); + xy = pixel % (xSize*ySize); + x = xy % xSize; + y = xy / xSize; + if (z > maxR) + { + if (z >= zSize - maxR) + z = z - zSize; + else + x = maxR; + } + } + else + { + x = pixel % xSize; + y = pixel / xSize; + } + + if (y > maxR) + { + if (y >= ySize - maxR) + y = y - ySize; + else + x = maxR; + } + + XFLOAT ref_real, ref_imag; + if (DATA3D) + projector.project3Dmodel( + x, y, z, + g_eulers[ix*9 ], g_eulers[ix*9 + 1], g_eulers[ix*9 + 2], + g_eulers[ix*9 + 3], g_eulers[ix*9 + 4], g_eulers[ix*9 + 5], + g_eulers[ix*9 + 6], g_eulers[ix*9 + 7], g_eulers[ix*9 + 8], + ref_real, ref_imag); + else if (REF3D) + projector.project3Dmodel( + x, y, + g_eulers[ix*9 ], g_eulers[ix*9 + 1], g_eulers[ix*9 + 3], + g_eulers[ix*9 + 4], g_eulers[ix*9 + 6], g_eulers[ix*9 + 7], + ref_real, ref_imag); + else + projector.project2Dmodel( + x, y, + g_eulers[ix*9 ], g_eulers[ix*9 + 1], + g_eulers[ix*9 + 3], g_eulers[ix*9 + 4], + ref_real, ref_imag); + + for (int itrans = 0; itrans < trans_num; itrans++) // finish all translations in each partial pass + { + int iy = static_cast(d_trans_idx[d_job_idx[bid]]) + itrans; + + XFLOAT shifted_real, shifted_imag; + if (DATA3D) + translatePixel(x, y, z, trans_x[iy], trans_y[iy], trans_z[iy], g_imgs_real[pixel], g_imgs_imag[pixel], shifted_real, shifted_imag); + else + translatePixel(x, y, trans_x[iy], trans_y[iy], g_imgs_real[pixel], g_imgs_imag[pixel], shifted_real, shifted_imag); + + XFLOAT diff_real = ref_real - shifted_real; + XFLOAT diff_imag = ref_imag - shifted_imag; + s[itrans*block_sz + tid] += (diff_real*diff_real + diff_imag*diff_imag) * 0.5f * g_corr_img[pixel]; + } + } + } + __group_barrier(nit); + +// TODO: reduction can be used + for (int j = block_sz/2; j > 0; j /= 2) + { + if (tid < j) + for (int itrans = 0; itrans < trans_num; itrans++) // finish all translations in each partial pass + s[itrans*block_sz + tid] += s[itrans*block_sz + tid + j]; + + __group_barrier(nit); + } + + if (tid < trans_num) + g_diff2s[d_job_idx[bid] + tid] += s[tid * block_sz] + sum_init; + } +} + + +/* + * CROSS-CORRELATION-BASED KERNELS + */ +template +void sycl_kernel_diff2_CC_coarse( + sycl::nd_item<3> nit, AccProjectorKernel &projector, + XFLOAT *g_eulers, XFLOAT *g_imgs_real, XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + XFLOAT *g_corr_img, XFLOAT *g_diff2, + int trans_num, int image_size, + XFLOAT *s_weight, XFLOAT *s_norm + ) +{ + const int iorient = nit.get_group(0); + const int itrans = nit.get_group(1); + const int tid = nit.get_local_id(2); + + const int xSize = projector.imgX; + const int ySize = projector.imgY; + const int zSize = projector.imgZ; + const int maxR = projector.maxR; + + const XFLOAT e0 = g_eulers[iorient * 9 ]; + const XFLOAT e1 = g_eulers[iorient * 9 + 1]; + const XFLOAT e2 = g_eulers[iorient * 9 + 2]; + const XFLOAT e3 = g_eulers[iorient * 9 + 3]; + const XFLOAT e4 = g_eulers[iorient * 9 + 4]; + const XFLOAT e5 = g_eulers[iorient * 9 + 5]; + const XFLOAT e6 = g_eulers[iorient * 9 + 6]; + const XFLOAT e7 = g_eulers[iorient * 9 + 7]; + const XFLOAT e8 = g_eulers[iorient * 9 + 8]; + + s_weight[tid] = 0.0f; + s_norm[tid] = 0.0f; + + const int pixel_pass_num {image_size/block_sz + 1}; + for (int pass = 0; pass < pixel_pass_num; pass++) + { + const int pixel {pass*block_sz + tid}; + if (pixel < image_size) + { + int x, y, z, xy; + if (DATA3D) + { + z = pixel / (xSize*ySize); + xy = pixel % (xSize*ySize); + x = xy % xSize; + y = xy / xSize; + if (z > maxR) + { + if (z >= zSize - maxR) + z = z - zSize; + else + x = maxR; + } + } + else + { + x = pixel % xSize; + y = pixel / xSize; + } + if (y > maxR) + { + if (y >= ySize - maxR) + y = y - ySize; + else + x = maxR; + } + + XFLOAT ref_real, ref_imag; + if (DATA3D) + projector.project3Dmodel(x, y, z, e0, e1, e2, e3, e4, e5, e6, e7, e8, ref_real, ref_imag); + else if (REF3D) + projector.project3Dmodel(x, y, e0, e1, e3, e4, e6, e7, ref_real, ref_imag); + else + projector.project2Dmodel(x, y, e0, e1, e3, e4, ref_real, ref_imag); + + XFLOAT real, imag; + if (DATA3D) + translatePixel(x, y, z, g_trans_x[itrans], g_trans_y[itrans], g_trans_z[itrans], g_imgs_real[pixel], g_imgs_imag[pixel], real, imag); + else + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], g_imgs_real[pixel], g_imgs_imag[pixel], real, imag); + + s_weight[tid] += (ref_real * real + ref_imag * imag) * g_corr_img[pixel]; + s_norm [tid] += (ref_real * ref_real + ref_imag * ref_imag) * g_corr_img[pixel]; + } + } + __group_barrier(nit); + +// TODO: This could be optimized using sub_group collective and s_weight and s_norm can be thread private + for (int j = block_sz/2; j > 0; j /= 2) + { + if (tid < j) + { + s_weight[tid] += s_weight[tid+j]; + s_norm[tid] += s_norm[tid+j]; + } + __group_barrier(nit); + } + + if (tid == 0) + g_diff2[iorient*trans_num + itrans] += -s_weight[0] / sycl::native::sqrt(s_norm[0]); +} + +template +void sycl_kernel_diff2_CC_fine( + sycl::nd_item<3> nit, AccProjectorKernel &projector, + XFLOAT *g_eulers, XFLOAT *g_imgs_real, XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + XFLOAT *g_corr_img, XFLOAT *g_diff2s, + int image_size, XFLOAT sum_init, int todo_blocks, + unsigned long *d_rot_idx, unsigned long *d_trans_idx, + unsigned long *d_job_idx, unsigned long *d_job_num, + XFLOAT *s, XFLOAT *s_cc) +{ + const int bid = nit.get_group_linear_id(); + const int tid = nit.get_local_id(2); + + const int xSize = projector.imgX; + const int ySize = projector.imgY; + const int zSize = projector.imgZ; + const int maxR = projector.maxR; + + if (bid < todo_blocks) // we only need to make + { + int trans_num = static_cast(d_job_num[bid]); //how many transes we have for this rot + for (int itrans = 0; itrans < trans_num; itrans++) + { + s[ itrans*block_sz + tid] = 0.0f; + s_cc[itrans*block_sz + tid] = 0.0f; + } + + // index of comparison + int ix = static_cast(d_rot_idx[d_job_idx[bid]]); + int pass_num {image_size/block_sz + 1}; + for (int pass = 0; pass < pass_num; pass++) // finish an entire ref image each block + { + int pixel = pass*block_sz + tid; + + if (pixel < image_size) + { + int x, y, z, xy; + if (DATA3D) + { + z = pixel / (xSize*ySize); + xy = pixel % (xSize*ySize); + x = xy % xSize; + y = xy / xSize; + if (z > maxR) + { + if (z >= zSize - maxR) + z = z - zSize; + else + x = maxR; + } + } + else + { + x = pixel % xSize; + y = pixel / xSize; + } + + if (y > maxR) + { + if (y >= ySize - maxR) + y = y - ySize; + else + x = maxR; + } + + XFLOAT ref_real, ref_imag; + if (DATA3D) + projector.project3Dmodel( + x, y, z, + g_eulers[ix*9 ], g_eulers[ix*9 + 1], g_eulers[ix*9 + 2], + g_eulers[ix*9 + 3], g_eulers[ix*9 + 4], g_eulers[ix*9 + 5], + g_eulers[ix*9 + 6], g_eulers[ix*9 + 7], g_eulers[ix*9 + 8], + ref_real, ref_imag); + else if (REF3D) + projector.project3Dmodel( + x, y, + g_eulers[ix*9 ], g_eulers[ix*9 + 1], g_eulers[ix*9 + 3], + g_eulers[ix*9 + 4], g_eulers[ix*9 + 6], g_eulers[ix*9 + 7], + ref_real, ref_imag); + else + projector.project2Dmodel( + x, y, + g_eulers[ix*9 ], g_eulers[ix*9 + 1], + g_eulers[ix*9 + 3], g_eulers[ix*9 + 4], + ref_real, ref_imag); + + for (int itrans = 0; itrans < trans_num; itrans++) // finish all translations in each partial pass + { + int iy = static_cast(d_trans_idx[d_job_idx[bid]]) + itrans; + + XFLOAT shifted_real, shifted_imag; + if(DATA3D) + translatePixel(x, y, z, g_trans_x[iy], g_trans_y[iy], g_trans_z[iy], g_imgs_real[pixel], g_imgs_imag[pixel], shifted_real, shifted_imag); + else + translatePixel(x, y, g_trans_x[iy], g_trans_y[iy], g_imgs_real[pixel], g_imgs_imag[pixel], shifted_real, shifted_imag); + + s [itrans*block_sz + tid] += (ref_real*shifted_real + ref_imag*shifted_imag) * g_corr_img[pixel]; + s_cc[itrans*block_sz + tid] += (ref_real* ref_real + ref_imag* ref_imag) * g_corr_img[pixel]; + } + } + } + __group_barrier(nit); + + for (int j = block_sz/2; j > 0; j /= 2) + { + if (tid < j) + { + for (int itrans = 0; itrans < trans_num; itrans++) // finish all translations in each partial pass + { + s[ itrans*block_sz + tid] += s[ itrans*block_sz + tid + j]; + s_cc[itrans*block_sz + tid] += s_cc[itrans*block_sz + tid + j]; + } + } + __group_barrier(nit); + } + + if (tid < trans_num) + g_diff2s[d_job_idx[bid] + tid] += -s[tid * block_sz] / sycl::native::sqrt(s_cc[tid * block_sz]); + } +} + +} // end of namespace syclKernels + +#endif /* DIFF2_GPU_KERNELS_H_ */ diff --git a/src/acc/sycl/sycl_kernels/diff2_impl.h b/src/acc/sycl/sycl_kernels/diff2_impl.h new file mode 100644 index 000000000..25bd36524 --- /dev/null +++ b/src/acc/sycl/sycl_kernels/diff2_impl.h @@ -0,0 +1,268 @@ +#ifndef DIFF2_IMPL_KERNELS_H_ +#define DIFF2_IMPL_KERNELS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "src/acc/acc_projectorkernel_impl.h" +#include "src/acc/sycl/sycl_dev.h" +#include "src/acc/sycl/sycl_kernels/sycl_utils.h" +#include "src/acc/sycl/sycl_kernels/diff2_gpu.h" + +namespace syclKernels +{ + template + __attribute__((always_inline)) + inline + void diff2_coarse( + unsigned long grid_size, + XFLOAT *g_eulers, XFLOAT *trans_x, XFLOAT *trans_y, XFLOAT *trans_z, + XFLOAT *g_real, XFLOAT *g_imag, + AccProjectorKernel &projector, + XFLOAT *g_corr, XFLOAT *g_diff2s, + unsigned long trans_num, unsigned long image_size, + virtualSYCL *devAcc + ) + { + devSYCL *dGPU = dynamic_cast(devAcc); + assert( trans_num <= std::numeric_limits::max()); + assert(image_size <= std::numeric_limits::max()); + assert( grid_size <= std::numeric_limits::max()); + assert( grid_size <= dGPU->maxWorkGroup[1]); + assert( block_sz <= dGPU->maxItem[2]); + assert(eulers_per_block*9 + 2*(block_sz/prefetch_fraction*eulers_per_block) + 3*block_sz <= dGPU->localMem); + + sycl::range<3> wi (1,1,block_sz); + sycl::range<3> wg (1,grid_size,block_sz); + dGPU->reCalculateRange(wg, wi); + auto event = dGPU->syclSubmit + ( + [&](sycl::handler &cgh) // + { + using namespace sycl; + // accessors to device memory + local_accessor s_eulers_acc(range<1>(eulers_per_block*9), cgh); + local_accessor s_ref_real_acc(range<1>(block_sz/prefetch_fraction*eulers_per_block), cgh); + local_accessor s_ref_imag_acc(range<1>(block_sz/prefetch_fraction*eulers_per_block), cgh); + local_accessor s_real_acc(range<1>(block_sz), cgh); + local_accessor s_imag_acc(range<1>(block_sz), cgh); + local_accessor s_corr_acc(range<1>(block_sz), cgh); + + cgh.parallel_for + ( + nd_range<3>(wg, wi), [=](nd_item<3> nit) + #if defined(__INTEL_LLVM_COMPILER) && defined(INTEL_SG_SIZE) + [[intel::reqd_sub_group_size(INTEL_SG_SIZE)]] + #endif + { + syclGpuKernels::sycl_kernel_diff2_coarse + ( // + nit, + const_cast(projector), // Why const_cast is needed for compilation? + g_eulers, trans_x, trans_y, trans_z, g_real, g_imag, + g_corr, g_diff2s, + static_cast(trans_num), static_cast(image_size), + s_eulers_acc.get_pointer(), + s_ref_real_acc.get_pointer(), + s_ref_imag_acc.get_pointer(), + s_real_acc.get_pointer(), + s_imag_acc.get_pointer(), + s_corr_acc.get_pointer() + ); // End of sycl_kernel_diff2_coarse + } // End of cgh.parallel_for Lamda function + ); // End of cgh.parallel_for + } // End of dGPU->syclSubmit Lamda function + ); // End of dGPU->syclSubmit + } + + template + __attribute__((always_inline)) + inline + void diff2_fine( + unsigned long grid_size, + XFLOAT *g_eulers, XFLOAT *g_imgs_real, XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + AccProjectorKernel &projector, + XFLOAT *g_corr_img, XFLOAT *g_diff2s, + unsigned long image_size, XFLOAT sum_init, + unsigned long orient_num, unsigned long trans_num, unsigned long num_jobs, + unsigned long *d_rot_idx, unsigned long *d_trans_idx, + unsigned long *d_job_idx, unsigned long *d_job_num, + virtualSYCL *devAcc + ) + { + devSYCL *dGPU = dynamic_cast(devAcc); + assert(orient_num <= std::numeric_limits::max()); + assert( trans_num <= std::numeric_limits::max()); + assert(image_size <= std::numeric_limits::max()); + assert( num_jobs <= std::numeric_limits::max()); + assert( grid_size <= std::numeric_limits::max()); + assert( grid_size <= dGPU->maxWorkGroup[1]); + assert( block_sz <= dGPU->maxItem[2]); + assert(block_sz * chunk_sz <= dGPU->localMem); + + sycl::range<3> wi (1,1,block_sz); + sycl::range<3> wg (1,grid_size,block_sz); + dGPU->reCalculateRange(wg, wi); + auto event = dGPU->syclSubmit + ( + [&](sycl::handler &cgh) // + { + using namespace sycl; + // accessors to device memory + local_accessor s_acc(range<1>(block_sz * chunk_sz), cgh); + + cgh.parallel_for + ( + nd_range<3>(wg, wi), [=](nd_item<3> nit) + #if defined(__INTEL_LLVM_COMPILER) && defined(INTEL_SG_SIZE) + [[intel::reqd_sub_group_size(INTEL_SG_SIZE)]] + #endif + { + syclGpuKernels::sycl_kernel_diff2_fine + ( // + nit, + const_cast(projector), + g_eulers, g_imgs_real, g_imgs_imag, + g_trans_x, g_trans_y, g_trans_z, + g_corr_img, g_diff2s, + static_cast(image_size), sum_init, + static_cast(num_jobs), + d_rot_idx, d_trans_idx, d_job_idx, d_job_num, + s_acc.get_pointer() + ); // End of sycl_kernel_diff2_fine + } // End of cgh.parallel_for Lamda function + ); // End of cgh.parallel_for + } // End of dGPU->syclSubmit Lamda function + ); // End of dGPU->syclSubmit + } + + +/* + * CROSS-CORRELATION-BASED KERNELS + */ + template + __attribute__((always_inline)) + inline + void diff2_CC_coarse( + unsigned long grid_size, + XFLOAT *g_eulers, XFLOAT *g_imgs_real, XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + AccProjectorKernel &projector, + XFLOAT *g_corr_img, XFLOAT *g_diff2s, + unsigned long trans_num, unsigned long image_size, XFLOAT exp_local_sqrtXi2, + virtualSYCL *devAcc + ) + { + devSYCL *dGPU = dynamic_cast(devAcc); + assert(grid_size*trans_num <= std::numeric_limits::max()); + assert(image_size <= std::numeric_limits::max()); + assert( grid_size <= dGPU->maxWorkGroup[0]); + assert( trans_num <= dGPU->maxWorkGroup[1]); + assert( block_sz <= dGPU->maxItem[2]); + assert(2*block_sz <= dGPU->localMem); + + sycl::range<3> wi {1,1,block_sz}; + sycl::range<3> wg {grid_size,trans_num,block_sz}; + auto event = dGPU->syclSubmit + ( + [&](sycl::handler &cgh) // + { + using namespace sycl; + // accessors to device memory + local_accessor s_weight_acc(range<1>(block_sz), cgh); + local_accessor s_norm_acc(range<1>(block_sz), cgh); + + cgh.parallel_for + ( + nd_range<3>(wg, wi), [=](nd_item<3> nit) + #if defined(__INTEL_LLVM_COMPILER) && defined(INTEL_SG_SIZE) + [[intel::reqd_sub_group_size(INTEL_SG_SIZE)]] + #endif + { + syclGpuKernels::sycl_kernel_diff2_CC_coarse + ( // + nit, + const_cast(projector), // Why const_cast is needed for compilation? + g_eulers, g_imgs_real, g_imgs_imag, g_trans_x, g_trans_y, g_trans_z, + g_corr_img, g_diff2s, + static_cast(trans_num), static_cast(image_size), + s_weight_acc.get_pointer(), + s_norm_acc.get_pointer() + ); // End of sycl_kernel_diff2_CC_coarse + } // End of cgh.parallel_for Lamda function + ); // End of cgh.parallel_for + } // End of dGPU->syclSubmit Lamda function + ); // End of dGPU->syclSubmit + } + + template + __attribute__((always_inline)) + inline + void diff2_CC_fine( + unsigned long grid_size, + XFLOAT *g_eulers, XFLOAT *g_imgs_real, XFLOAT *g_imgs_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + AccProjectorKernel &projector, + XFLOAT *g_corr_img, XFLOAT *g_diff2s, + unsigned long image_size, XFLOAT sum_init, XFLOAT exp_local_sqrtXi2, + unsigned long orient_num, unsigned long trans_num, unsigned long num_jobs, + unsigned long *d_rot_idx, unsigned long *d_trans_idx, + unsigned long *d_job_idx, unsigned long *d_job_num, + virtualSYCL *devAcc + ) + { + devSYCL *dGPU = dynamic_cast(devAcc); + assert(orient_num <= std::numeric_limits::max()); + assert( trans_num <= std::numeric_limits::max()); + assert(image_size <= std::numeric_limits::max()); + assert( num_jobs <= std::numeric_limits::max()); + assert( grid_size <= std::numeric_limits::max()); + assert( grid_size <= dGPU->maxWorkGroup[1]); + assert( block_sz <= dGPU->maxItem[2]); + assert(2 * block_sz * chunk_sz <= dGPU->localMem); + + sycl::range<3> wi (1,1,block_sz); + sycl::range<3> wg (1,grid_size,block_sz); + dGPU->reCalculateRange(wg, wi); + auto event = dGPU->syclSubmit + ( + [&](sycl::handler &cgh) // + { + using namespace sycl; + // accessors to device memory + local_accessor s_acc(range<1>(block_sz * chunk_sz), cgh); + local_accessor s_cc_acc(range<1>(block_sz * chunk_sz), cgh); + + cgh.parallel_for + ( + nd_range<3>(wg, wi), [=](nd_item<3> nit) + #if defined(__INTEL_LLVM_COMPILER) && defined(INTEL_SG_SIZE) + [[intel::reqd_sub_group_size(INTEL_SG_SIZE)]] + #endif + { + syclGpuKernels::sycl_kernel_diff2_CC_fine + ( // + nit, + const_cast(projector), + g_eulers, g_imgs_real, g_imgs_imag, + g_trans_x, g_trans_y, g_trans_z, + g_corr_img, g_diff2s, + static_cast(image_size), sum_init, static_cast(num_jobs), + d_rot_idx, d_trans_idx, d_job_idx, d_job_num, + s_acc.get_pointer(), s_cc_acc.get_pointer() + ); // End of sycl_kernel_diff2_CC_fine + } // End of cgh.parallel_for Lamda function + ); // End of cgh.parallel_for + } // End of dGPU->syclSubmit Lamda function + ); // End of dGPU->syclSubmit + } + +} // end of namespace syclKernels + +#endif /* DIFF2_IMPL_KERNELS_H_ */ diff --git a/src/acc/sycl/sycl_kernels/helper.cpp b/src/acc/sycl/sycl_kernels/helper.cpp new file mode 100644 index 000000000..e6778fd2c --- /dev/null +++ b/src/acc/sycl/sycl_kernels/helper.cpp @@ -0,0 +1,582 @@ +#include "src/acc/sycl/device_stubs.h" + +#include "src/acc/acc_ptr.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/sycl/sycl_benchmark_utils.h" +#include "src/acc/sycl/sycl_helper_functions.h" +#include "src/acc/sycl/sycl_kernels/helper.h" +#include "src/acc/utilities.h" +#include "src/acc/data_types.h" + +#include "src/acc/acc_helper_functions.h" + +#include "src/acc/sycl/sycl_kernels/sycl_utils.h" + +namespace syclKernels +{ + +/* + * This draft of a kernel assumes input that has jobs which have a single orientation and sequential translations within each job. + * + */ +void exponentiate_weights_fine( + XFLOAT *g_pdf_orientation, + bool *g_pdf_orientation_zeros, + XFLOAT *g_pdf_offset, + bool *g_pdf_offset_zeros, + XFLOAT *g_weights, + XFLOAT min_diff2, + unsigned long oversamples_orient, + unsigned long oversamples_trans, + unsigned long *d_rot_id, + unsigned long *d_trans_idx, + unsigned long *d_job_idx, + unsigned long *d_job_num, + long int job_num) +{ + for (long int jobid=0; jobid::lowest(); //large negative number + else + g_weights[pos+itrans] = g_pdf_orientation[ix] + g_pdf_offset[c_itrans] + min_diff2 - g_weights[pos+itrans]; + } + } +} + +void RNDnormalDitributionComplexWithPowerModulation2D(ACCCOMPLEX *Image, size_t xdim, XFLOAT *spectra) +{ + size_t x,y,size; + size = xdim*((xdim-1)*2); + for(size_t i=0; i=xdim) + y -= (xdim-1)*2; + x = i % xdim; + + int ires = (int)(sqrtf(x*x + y*y)); + + if(ires=xdim) + z -= (xdim-1)*2; //assuming square input images (particles) + if(y>=xdim) + y -= (xdim-1)*2; //assuming square input images (particles) + + int ires = (int)(sqrtf(x*x + y*y + z*z)); + + if(ires radius_p) + { + g_sum[tid] += (XFLOAT)1.0; + g_sum_bg[tid] += img_pixels; + } + else + { + #if defined(ACC_DOUBLE_PRECISION) + raisedcos = 0.5 + 0.5 * cos ( (radius_p - r) / cosine_width * M_PI); + #else + raisedcos = 0.5 + 0.5 * cosf( (radius_p - r) / cosine_width * M_PI); + #endif + g_sum[tid] += raisedcos; + g_sum_bg[tid] += raisedcos * img_pixels; + } + } + } + } // tid + } // bid +} + +void cosineFilter( int block_dim, + int block_size, + XFLOAT *vol, + long int vol_size, + long int xdim, + long int ydim, + long int zdim, + long int xinit, + long int yinit, + long int zinit, + bool do_noise, + XFLOAT *noise, + XFLOAT radius, + XFLOAT radius_p, + XFLOAT cosine_width, + XFLOAT bg_value) +{ + for(int bid=0; bid radius_p) + img_pixels=defVal; + else + { + #if defined(ACC_DOUBLE_PRECISION) + raisedcos = 0.5 + 0.5 * cos ( (radius_p - r) / cosine_width * M_PI); + #else + raisedcos = 0.5 + 0.5 * cosf( (radius_p - r) / cosine_width * M_PI); + #endif + img_pixels= img_pixels*(1-raisedcos) + defVal*raisedcos; + + } + vol[texel]=img_pixels; + } + } + } // tid + } // bid +} + +template +__attribute__((always_inline)) +inline +void sycl_translate2D(T *g_image_in, + T *g_image_out, + size_t image_size, + int xdim, + int ydim, + int dx, + int dy) +{ + int x,y,xp,yp; + size_t new_pixel; + + for(size_t pixel=0; pixel=0 && xp>=0 && yp=0 && new_pixel +__attribute__((always_inline)) +inline +void sycl_translate3D(T *g_image_in, + T *g_image_out, + size_t image_size, + int xdim, + int ydim, + int zdim, + int dx, + int dy, + int dz) +{ + int x,y,z,xp,yp,zp,xy; + size_t new_voxel; + + for(size_t voxel=0; voxel=0 && yp>=0 && xp>=0 && zp=0 && new_voxel +void sycl_kernel_multi( T *A, + T *OUT, + T S, + size_t image_size) +{ + for (size_t i = 0; i < image_size; i ++) + OUT[i] = A[i]*S; +} + +template +void sycl_kernel_multi( T *A, + T S, + size_t image_size) +{ + for (size_t i = 0; i < image_size; i ++) + A[i] *= S; +} + +template +void sycl_kernel_multi( T *A, + T *B, + T *OUT, + T S, + size_t image_size) +{ + for (size_t i = 0; i < image_size; i ++) + OUT[i] = A[i]*B[i]*S; +} + +template +void sycl_kernel_add( + T *A, + T S, + size_t size +) +{ + for (size_t i = 0; i < size; i ++) + A[i] += S; +} + +template +__attribute__((always_inline)) +inline +void sycl_kernel_make_eulers_2D(int grid_size, int block_size, + XFLOAT *alphas, + XFLOAT *eulers, + unsigned long orientation_num) +{ + for(int blockIdx_x=0; blockIdx_x<(int)(grid_size); blockIdx_x++) { + for(int threadIdx_x=0; threadIdx_x= orientation_num) + return; + + XFLOAT ca, sa; + XFLOAT a = alphas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + +#ifdef ACC_DOUBLE_PRECISION + sincos(a, &sa, &ca); +#else + sincosf(a, &sa, &ca); +#endif + + if(!invert) + { + eulers[9 * oid + 0] = ca;//00 + eulers[9 * oid + 1] = sa;//01 + eulers[9 * oid + 2] = 0 ;//02 + eulers[9 * oid + 3] =-sa;//10 + eulers[9 * oid + 4] = ca;//11 + eulers[9 * oid + 5] = 0 ;//12 + eulers[9 * oid + 6] = 0 ;//20 + eulers[9 * oid + 7] = 0 ;//21 + eulers[9 * oid + 8] = 1 ;//22 + } + else + { + eulers[9 * oid + 0] = ca;//00 + eulers[9 * oid + 1] =-sa;//10 + eulers[9 * oid + 2] = 0 ;//20 + eulers[9 * oid + 3] = sa;//01 + eulers[9 * oid + 4] = ca;//11 + eulers[9 * oid + 5] = 0 ;//21 + eulers[9 * oid + 6] = 0 ;//02 + eulers[9 * oid + 7] = 0 ;//12 + eulers[9 * oid + 8] = 1 ;//22 + } + } // threadIdx_x + } // blockIdx_x +} + +template +__attribute__((always_inline)) +inline +void sycl_kernel_make_eulers_3D(int grid_size, int block_size, + XFLOAT *alphas, + XFLOAT *betas, + XFLOAT *gammas, + XFLOAT *eulers, + unsigned long orientation_num, + XFLOAT *L, + XFLOAT *R) +{ + for(int blockIdx_x=0; blockIdx_x<(int)(grid_size); blockIdx_x++) { + for(int threadIdx_x=0; threadIdx_x= orientation_num) + return; + + for (int i = 0; i < 9; i ++) + B[i] = (XFLOAT) 0.f; + + a = alphas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + b = betas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + g = gammas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + +#ifdef ACC_DOUBLE_PRECISION + sincos(a, &sa, &ca); + sincos(b, &sb, &cb); + sincos(g, &sg, &cg); +#else + sincosf(a, &sa, &ca); + sincosf(b, &sb, &cb); + sincosf(g, &sg, &cg); +#endif + + cc = cb * ca; + cs = cb * sa; + sc = sb * ca; + ss = sb * sa; + + A[0] = ( cg * cc - sg * sa);//00 + A[1] = ( cg * cs + sg * ca);//01 + A[2] = (-cg * sb ) ;//02 + A[3] = (-sg * cc - cg * sa);//10 + A[4] = (-sg * cs + cg * ca);//11 + A[5] = ( sg * sb ) ;//12 + A[6] = ( sc ) ;//20 + A[7] = ( ss ) ;//21 + A[8] = ( cb ) ;//22 + + if (doR) + { + for (int i = 0; i < 9; i++) + B[i] = 0.f; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) + B[i * 3 + j] += A[i * 3 + k] * R[k * 3 + j]; + } + else + for (int i = 0; i < 9; i++) + B[i] = A[i]; + + if (doL) + { + if (doR) + for (int i = 0; i < 9; i++) + A[i] = B[i]; + + for (int i = 0; i < 9; i++) + B[i] = 0.f; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) + B[i * 3 + j] += L[i * 3 + k] * A[k * 3 + j]; + } + + if(invert) + { + if (doL) // this could have anisotropy, so inverse neq transpose!!! + { + XFLOAT det; + det = B[0] * (B[4] * B[8] - B[7] * B[5]) + - B[1] * (B[3] * B[8] - B[6] * B[5]) + + B[2] * (B[3] * B[7] - B[6] * B[4]); + + eulers[9 * oid + 0] = (B[4] * B[8] - B[7] * B[5]) / det; + eulers[9 * oid + 1] = (B[7] * B[2] - B[1] * B[8]) / det; + eulers[9 * oid + 2] = (B[1] * B[5] - B[4] * B[2]) / det; + eulers[9 * oid + 3] = (B[5] * B[6] - B[8] * B[3]) / det; + eulers[9 * oid + 4] = (B[8] * B[0] - B[2] * B[6]) / det; + eulers[9 * oid + 5] = (B[2] * B[3] - B[5] * B[0]) / det; + eulers[9 * oid + 6] = (B[3] * B[7] - B[6] * B[4]) / det; + eulers[9 * oid + 7] = (B[6] * B[1] - B[0] * B[7]) / det; + eulers[9 * oid + 8] = (B[0] * B[4] - B[3] * B[1]) / det; + } + else + { + eulers[9 * oid + 0] = B[0];//00 + eulers[9 * oid + 1] = B[3];//01 + eulers[9 * oid + 2] = B[6];//02 + eulers[9 * oid + 3] = B[1];//10 + eulers[9 * oid + 4] = B[4];//11 + eulers[9 * oid + 5] = B[7];//12 + eulers[9 * oid + 6] = B[2];//20 + eulers[9 * oid + 7] = B[5];//21 + eulers[9 * oid + 8] = B[8];//22 + } + } + else + { + eulers[9 * oid + 0] = B[0];//00 + eulers[9 * oid + 1] = B[1];//10 + eulers[9 * oid + 2] = B[2];//20 + eulers[9 * oid + 3] = B[3];//01 + eulers[9 * oid + 4] = B[4];//11 + eulers[9 * oid + 5] = B[5];//21 + eulers[9 * oid + 6] = B[6];//02 + eulers[9 * oid + 7] = B[7];//12 + eulers[9 * oid + 8] = B[8];//22 + } + } // threadIdx_x + } // blockIdx_x +} + +} // end of namespace syclKernels + + +// ------------------------------- Some explicit template instantiations +template void syclKernels::sycl_translate2D(XFLOAT *, + XFLOAT *, size_t, int, int, int, int); + +template void syclKernels::sycl_translate3D(XFLOAT *, + XFLOAT *, size_t, int, int, int, int, int, int); + +template void syclKernels::sycl_kernel_multi( XFLOAT *, XFLOAT, size_t); +template void syclKernels::sycl_kernel_add( XFLOAT *, XFLOAT, size_t); + +template void syclKernels::sycl_kernel_make_eulers_3D(int, int, + XFLOAT *, XFLOAT *, XFLOAT *, XFLOAT *, unsigned long, XFLOAT *, XFLOAT *); +template void syclKernels::sycl_kernel_make_eulers_3D(int, int, + XFLOAT *, XFLOAT *, XFLOAT *, XFLOAT *, unsigned long, XFLOAT *, XFLOAT *); +template void syclKernels::sycl_kernel_make_eulers_3D(int, int, + XFLOAT *, XFLOAT *, XFLOAT *, XFLOAT *, unsigned long, XFLOAT *, XFLOAT *); +template void syclKernels::sycl_kernel_make_eulers_3D(int, int, + XFLOAT *, XFLOAT *, XFLOAT *, XFLOAT *, unsigned long, XFLOAT *, XFLOAT *); +template void syclKernels::sycl_kernel_make_eulers_3D(int, int, + XFLOAT *, XFLOAT *, XFLOAT *, XFLOAT *, unsigned long, XFLOAT *, XFLOAT *); +template void syclKernels::sycl_kernel_make_eulers_3D(int, int, + XFLOAT *, XFLOAT *, XFLOAT *, XFLOAT *, unsigned long, XFLOAT *, XFLOAT *); +template void syclKernels::sycl_kernel_make_eulers_3D(int, int, + XFLOAT *, XFLOAT *, XFLOAT *, XFLOAT *, unsigned long, XFLOAT *, XFLOAT *); +template void syclKernels::sycl_kernel_make_eulers_3D(int, int, + XFLOAT *, XFLOAT *, XFLOAT *, XFLOAT *, unsigned long, XFLOAT *, XFLOAT *); + +template void syclKernels::sycl_kernel_make_eulers_2D(int, int, + XFLOAT *, XFLOAT *, unsigned long); +template void syclKernels::sycl_kernel_make_eulers_2D(int, int, + XFLOAT *, XFLOAT *, unsigned long); +// ---------------------------------------------------------------------- + diff --git a/src/acc/sycl/sycl_kernels/helper.h b/src/acc/sycl/sycl_kernels/helper.h new file mode 100644 index 000000000..0d3058855 --- /dev/null +++ b/src/acc/sycl/sycl_kernels/helper.h @@ -0,0 +1,576 @@ +#ifndef SYCL_HELPER_KERNELS_H_ +#define SYCL_HELPER_KERNELS_H_ + +#include +#include +#include +#include + +#include "src/macros.h" +#include "src/acc/sycl/sycl_settings.h" +#include "src/acc/sycl/sycl_kernels/sycl_utils.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_projectorkernel_impl.h" + +namespace syclKernels +{ + +template +__attribute__((always_inline)) +inline +void weights_exponent_coarse( + T *g_pdf_orientation, + bool *g_pdf_orientation_zeros, + T *g_pdf_offset, + bool *g_pdf_offset_zeros, + T *g_weights, + T g_min_diff2, + unsigned long nr_coarse_orient, + unsigned long nr_coarse_trans, + size_t max_idx) +{ + for (size_t idx = 0; idx < max_idx; idx++) + { + unsigned long itrans = idx % nr_coarse_trans; + unsigned long iorient = (idx - itrans) / nr_coarse_trans; + + T diff2 = g_weights[idx]; + if( diff2 < g_min_diff2 || g_pdf_orientation_zeros[iorient] || g_pdf_offset_zeros[itrans]) + g_weights[idx] = std::numeric_limits::lowest(); //large negative number + else + g_weights[idx] = g_pdf_orientation[iorient] + g_pdf_offset[itrans] + g_min_diff2 - diff2; + } +} + + +template +__attribute__((always_inline)) +inline +void exponentiate( + T *g_array, + T add, + size_t size) +{ + for (size_t idx = 0; idx < size; idx++) + { + T a = g_array[idx] + add; +#ifdef ACC_DOUBLE_PRECISION + if (a < -700.) + g_array[idx] = 0.; +#else + if (a < -88.f) + g_array[idx] = 0.f; +#endif + else + g_array[idx] = std::exp(a); + } +} + +template +__attribute__((always_inline)) +inline +void collect2jobs( int grid_size, + int block_size, + XFLOAT *g_oo_otrans_x, // otrans-size -> make const + XFLOAT *g_oo_otrans_y, // otrans-size -> make const + XFLOAT *g_oo_otrans_z, // otrans-size -> make const + XFLOAT *g_myp_oo_otrans_x2y2z2, // otrans-size -> make const + XFLOAT *g_i_weights, + XFLOAT op_significant_weight, // TODO Put in const + XFLOAT op_sum_weight, // TODO Put in const + unsigned long coarse_trans, + unsigned long oversamples_trans, + unsigned long oversamples_orient, + unsigned long oversamples, + bool do_ignore_pdf_direction, + XFLOAT *g_o_weights, + XFLOAT *g_thr_wsum_prior_offsetx_class, + XFLOAT *g_thr_wsum_prior_offsety_class, + XFLOAT *g_thr_wsum_prior_offsetz_class, + XFLOAT *g_thr_wsum_sigma2_offset, + unsigned long *d_rot_idx, + unsigned long *d_trans_idx, + unsigned long *d_job_idx, + unsigned long *d_job_num + ) +{ + // block id + for (int bid=0; bid < grid_size; bid++) { + + XFLOAT s_o_weights[block_size]; + XFLOAT s_thr_wsum_sigma2_offset[block_size];; + XFLOAT s_thr_wsum_prior_offsetx_class[block_size]; + XFLOAT s_thr_wsum_prior_offsety_class[block_size]; + XFLOAT s_thr_wsum_prior_offsetz_class[block_size]; + + unsigned long pos = d_job_idx[bid]; + unsigned long job_size = d_job_num[bid]; + + int pass_num = ceilfracf(job_size,block_size); + + for(int tid=0; tid= op_significant_weight ) //TODO Might be slow (divergent threads) + weight /= op_sum_weight; + else + weight = (XFLOAT)0.0; + + s_o_weights[tid] += weight; + s_thr_wsum_prior_offsetx_class[tid] += weight * g_oo_otrans_x[iy]; + s_thr_wsum_prior_offsety_class[tid] += weight * g_oo_otrans_y[iy]; + s_thr_wsum_sigma2_offset[tid] += weight * g_myp_oo_otrans_x2y2z2[iy]; + } + } + } + + for(int tid=1; tid +void sycl_translate2D(T *g_image_in, + T *g_image_out, + size_t image_size, + int xdim, + int ydim, //not used + int dx, + int dy); + +template +void sycl_translate3D(T *g_image_in, + T *g_image_out, + size_t image_size, + int xdim, + int ydim, + int zdim, //not used + int dx, + int dy, + int dz); + +//---------------------------------------------------------------------------- +/* + * Multiplies scalar array A by a scalar S + * + * OUT[i] = A[i]*S + */ +template +void sycl_kernel_multi( T *A, + T *OUT, + T S, + size_t image_size); +/* + * In place multiplies scalar array A by a scalar S + * + * A[i] = A[i]*S + */ +template +void sycl_kernel_multi( T *A, + T S, + size_t image_size); +/* + * Multiplies scalar array A by scalar array B and a scalar S, pixel-by-pixel + * + * OUT[i] = A[i]*B[i]*S + */ +template +void sycl_kernel_multi( T *A, + T *B, + T *OUT, + T S, + size_t image_size); + +/* + * In place add scalar S to scalar array A + * + * A[i] = A[i]*S + */ +template +void sycl_kernel_add( + T *A, + T S, + size_t size +); + +template +__attribute__((always_inline)) +inline +void kernel_frequencyPass( int grid_size, int block_size, + ACCCOMPLEX *A, + long int ori_size, + size_t Xdim, + size_t Ydim, + size_t Zdim, + XFLOAT edge_low, + XFLOAT edge_width, + XFLOAT edge_high, + XFLOAT angpix, + size_t image_size) +{ + // TODO - why not a single loop over image_size pixels? + for(int blk=0; blk lows are dead + { + A[texel].x = 0.; + A[texel].y = 0.; + } + else if (res < edge_high) //highpass => medium lows are almost dead + { + XFLOAT mul = 0.5 - 0.5 * cos( PI * (res-edge_low)/edge_width); + A[texel].x *= mul; + A[texel].y *= mul; + } + } + else //lowpass + { + if (res > edge_high) //lowpass => highs are dead + { + A[texel].x = 0.; + A[texel].y = 0.; + } + else if (res > edge_low) //lowpass => medium highs are almost dead + { + XFLOAT mul = 0.5 + 0.5 * cos( PI * (res-edge_low)/edge_width); + A[texel].x *= mul; + A[texel].y *= mul; + } + } + } + } // tid + } // blk +} + +template +__attribute__((always_inline)) +inline +void powerClass(int gridSize, + ACCCOMPLEX *g_image, + XFLOAT *g_spectrum, + size_t image_size, + size_t spectrum_size, + int xdim, + int ydim, + int zdim, + int res_limit, + XFLOAT *g_highres_Xi2) +{ + for(int bid=0; bid=res_limit) + s_highres_Xi2[tid] += normFaux; + } + } + } + + for(int tid=1; tid +void sycl_kernel_make_eulers_2D(int grid_size, int block_size, + XFLOAT *alphas, + XFLOAT *eulers, + unsigned long orientation_num); + +template +void sycl_kernel_make_eulers_3D(int grid_size, int block_size, + XFLOAT *alphas, + XFLOAT *betas, + XFLOAT *gammas, + XFLOAT *eulers, + unsigned long orientation_num, + XFLOAT *L, + XFLOAT *R); + +} // end of namespace syclKernels + +#endif /* SYCL_HELPER_KERNELS_H_ */ diff --git a/src/acc/sycl/sycl_kernels/helper_gpu.h b/src/acc/sycl/sycl_kernels/helper_gpu.h new file mode 100644 index 000000000..934abd0ae --- /dev/null +++ b/src/acc/sycl/sycl_kernels/helper_gpu.h @@ -0,0 +1,1046 @@ +#ifndef HELPER_GPU_KERNELS_H_ +#define HELPER_GPU_KERNELS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_ONEDPL +// Needs oneDPL (https://github.com/oneapi-src/oneDPL) + #include + #include + #include + #include + #include +#endif + +#include "src/macros.h" +#include "src/acc/sycl/sycl_settings.h" +#include "src/acc/sycl/sycl_dev.h" +#include "src/acc/sycl/sycl_kernels/sycl_utils.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_projectorkernel_impl.h" + +namespace syclGpuKernels +{ +using atomic_ref_dev = sycl::atomic_ref; +using atomic_ref_wg = sycl::atomic_ref; +using atomic_ref_sg = sycl::atomic_ref; + +template +constexpr void +__group_barrier(I __item) +{ +#if 0 +// group_barrier is too slow! + sycl::group_barrier(__item.get_group(), sycl::memory_scope::work_group); +#else + __item.barrier(sycl::access::fence_space::local_space); +#endif +} + +template< typename T> +void InitValue(T *data, const T& value, const size_t count, virtualSYCL *devAcc) +{ + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + Q->fill(data, value, count).wait_and_throw(); +} + +template +static T getSumOnDevice(T *data, const size_t count, virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + T ret; + #if defined(USE_ONEDPL) && !defined(USE_LESS_ONEDPL) + ret = dpl::reduce(dynamic_cast(devAcc)->getDevicePolicy(), data, data+count, static_cast(0)); + #else + { + using namespace sycl; + auto buf = buffer{&ret, range{1}}; + Q->submit([&](handler &cgh) + { + auto reductionSum = reduction(buf, cgh, plus{}, property::reduction::initialize_to_identity{}); + cgh.parallel_for(range<1>{count}, reductionSum, [=](id<1> idx, auto& sum) + { + sum += data[idx]; + }); + }).wait_and_throw(); + } + #endif + return ret; +} + +template +static T getMinOnDevice(T *data, const size_t count, virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + T val; + #if defined(USE_ONEDPL) && !defined(USE_LESS_ONEDPL) + auto iter = dpl::min_element(dynamic_cast(devAcc)->getDevicePolicy(), data, data+count); + auto dist = dpl::distance(data, iter); + + Q->memcpy(&val, data+dist, sizeof(T)).wait_and_throw(); + #else + val = std::numeric_limits::max(); + { + using namespace sycl; + auto buf = buffer{&val, range{1}}; + Q->submit([&](handler &cgh) + { + auto reductionMin = reduction(buf, cgh, minimum{}); + cgh.parallel_for(range<1>{count}, reductionMin, [=](id<1> idx, auto& vmin) + { + vmin.combine(data[idx]); + }); + }).wait_and_throw(); + } + #endif + return val; +} + +template +static T getMaxOnDevice(T *data, const size_t count, virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + T val; + #if defined(USE_ONEDPL) && !defined(USE_LESS_ONEDPL) + auto iter = dpl::max_element(dynamic_cast(devAcc)->getDevicePolicy(), data, data+count); + auto dist = dpl::distance(data, iter); + + Q->memcpy(&val, data+dist, sizeof(T)).wait_and_throw(); + #else + val = std::numeric_limits::lowest(); + { + using namespace sycl; + auto buf = buffer{&val, range{1}}; + Q->submit([&](handler &cgh) + { + auto reductionMax = reduction(buf, cgh, maximum{}); + cgh.parallel_for(range<1>{count}, reductionMax, [=](id<1> idx, auto& vmax) + { + vmax.combine(data[idx]); + }); + }).wait_and_throw(); + } + #endif + return val; +} + +template +using minloc = sycl::minimum>; + +template +static std::pair getArgMinOnDevice(T *data, const size_t count, virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + std::pair pair; + #if defined(USE_ONEDPL) && !defined(USE_LESS_ONEDPL) + auto iter = dpl::min_element(dynamic_cast(devAcc)->getDevicePolicy(), data, data+count); + auto dist = dpl::distance(data, iter); + + pair.first = dist; + Q->memcpy(&pair.second, data+dist, sizeof(T)).wait_and_throw(); + #else + const std::pair identity { std::numeric_limits::max(), std::numeric_limits::max() }; + std::pair res { identity }; + { + using namespace sycl; + auto buf = buffer{&res, range{1}}; + Q->submit([&](handler &cgh) + { + auto reductionMin = reduction(buf, cgh, identity, minloc{}); + cgh.parallel_for(range<1>{count}, reductionMin, [=](id<1> idx, auto& vmin) + { + std::pair temp { data[idx], idx }; + vmin.combine(temp); + }); + }).wait_and_throw(); + } + + pair.first = res.second; + pair.second = res.first; + #endif + return pair; +} + +template +using maxloc = sycl::maximum>; + +template +static std::pair getArgMaxOnDevice(T *data, const size_t count, virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + std::pair pair; + #if defined(USE_ONEDPL) && !defined(USE_LESS_ONEDPL) + auto iter = dpl::max_element(dynamic_cast(devAcc)->getDevicePolicy(), data, data+count); + auto dist = dpl::distance(data, iter); + + pair.first = dist; + Q->memcpy(&pair.second, data+dist, sizeof(T)).wait_and_throw(); + #else + const std::pair identity { std::numeric_limits::lowest(), std::numeric_limits::lowest() }; + std::pair res { identity }; + { + using namespace sycl; + auto buf = buffer{&res, range{1}}; + Q->submit([&](handler &cgh) + { + auto reductionMax = reduction(buf, cgh, identity, maxloc{}); + cgh.parallel_for(range<1>{count}, reductionMax, [=](id<1> idx, auto& vmax) + { + std::pair temp { data[idx], idx }; + vmax.combine(temp); + }); + }).wait_and_throw(); + } + + pair.first = res.second; + pair.second = res.first; + #endif + return pair; +} + +template +static size_t filterGreaterZeroOnDevice(T *in, const size_t count, T *out, virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + size_t dist; + #if defined(USE_ONEDPL) && !defined(USE_LESS_ONEDPL) + auto iter = dpl::copy_if(dynamic_cast(devAcc)->getDevicePolicy(), in, in+count, out, [=](T &v){return (v>static_cast(0));}); + dist = dpl::distance(out, iter); + #else + dist = 0; + { + using namespace sycl; + using atomic_ref_count = atomic_ref; + + auto buf = buffer{&dist, range{1}}; + + Q->submit([&](handler &cgh) + { + auto ncopy = accessor{buf, cgh, read_write}; + cgh.parallel_for(range<1>(count), [=](id<1> idx) + { + if (in[idx] > (T)0) + { + auto i = atomic_ref_count(ncopy[0]).fetch_add(1); + out[i] = in[idx]; + } + }); + }).wait_and_throw(); + } + #endif + return dist; +} + +template +static void sortOnDevice(T *in, const size_t count, T *out, virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + #ifdef USE_ONEDPL + Q->memcpy(out, in, count *sizeof(T)).wait_and_throw(); + dpl::sort(dynamic_cast(devAcc)->getDevicePolicy(), out, out+count); + #endif +} + +template +static void scanOnDevice(T *in, const size_t count, T *out, virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + #ifdef USE_MORE_ONEDPL +// This has conflict with DisableIndirectAccess=1 +// DisableIndirectAccess=1 will cause incorrecxt result + dpl::inclusive_scan(dynamic_cast(devAcc)->getDevicePolicy(), in, in+count, out); + #else + { +// This requires SYCL2020 for joint_inclusive_scan + using namespace sycl; + + const std::array maxItem = dynamic_cast(devAcc)->maxItem; + const size_t runWorkItem = (count > maxItem[2]) ? maxItem[2] : count; + const size_t G = (count%runWorkItem == 0) ? count/runWorkItem : count/runWorkItem + 1; + const size_t WG = (G <= maxItem[1]) ? G : maxItem[1]; + const size_t ND = (G <= maxItem[1]) ? 1 : ((G%WG == 0) ? G/WG : G/WG + 1); + + Q->submit([&](handler& cgh) + { + cgh.parallel_for(nd_range<3>(range<3>(ND,WG,runWorkItem), range<3>(1,1,runWorkItem)), [=](nd_item<3> it) + { + auto g = it.get_group(); + joint_inclusive_scan(g, in, in+count, out, sycl::plus{}); + }); + }).wait_and_throw(); + } + #endif +} + +static void sycl_kernel_exponentiate_weights_fine( + XFLOAT *g_pdf_orientation, + bool *g_pdf_orientation_zeros, + XFLOAT *g_pdf_offset, + bool *g_pdf_offset_zeros, + XFLOAT *g_weights, + const XFLOAT min_diff2, + const unsigned long oversamples_orient, + const unsigned long oversamples_trans, + unsigned long *d_rot_id, + unsigned long *d_trans_idx, + unsigned long *d_job_idx, + unsigned long *d_job_num, + const long job_num, + virtualSYCL *devAcc) +{ + assert(job_num <= std::numeric_limits::max()); + auto dev = dynamic_cast(devAcc); + + dev->syclSubmit([&](sycl::handler &cgh) + { + using namespace sycl; + + cgh.parallel_for(range<1>(job_num), [=](id<1> jobid) + { + unsigned long pos = d_job_idx[jobid]; + // index of comparison + unsigned long ix = d_rot_id [pos]; // each thread gets its own orient... + unsigned long iy = d_trans_idx[pos]; // ...and it's starting trans... + int in = static_cast(d_job_num[jobid]); // ...AND the number of translations to go through + + for (int itrans=0; itrans < in; itrans++, iy++) + { + unsigned long c_itrans = ( iy - (iy % oversamples_trans))/ oversamples_trans; + + if( g_weights[pos+itrans] < min_diff2 || g_pdf_orientation_zeros[ix] || g_pdf_offset_zeros[c_itrans]) + g_weights[pos+itrans] = -99e99; //large negative number + else + g_weights[pos+itrans] = g_pdf_orientation[ix] + g_pdf_offset[c_itrans] + min_diff2 - g_weights[pos+itrans]; + } + }); + }).wait_and_throw(); +} + + #ifdef ACC_DOUBLE_PRECISION + # define KERNEL_EXP_VALUE (-700.0) + #else + # define KERNEL_EXP_VALUE (-88.0f) + #endif +template +static void sycl_kernel_exponentiate( + T *g_array, + const T add, + const size_t count, + virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + #ifdef USE_MORE_ONEDPL + dpl::transform(dynamic_cast(devAcc)->getDevicePolicy(), g_array, g_array+count, g_array, [=](T v) { v+=add; v = (v(0) : sycl::native::exp(v); return v;}); + #else + Q->submit([&](sycl::handler &cgh) + { + using namespace sycl; + + cgh.parallel_for(range<1>(count), [=](id<1> idx) + { + if (idx < count) + { + T a = g_array[idx] + add; + if (a < KERNEL_EXP_VALUE) + g_array[idx] = static_cast(0); + else + g_array[idx] = sycl::native::exp(a); + } + }); + }).wait_and_throw(); + #endif +} + +template +static void sycl_kernel_weights_exponent_coarse( + T *g_pdf_orientation, + bool *g_pdf_orientation_zeros, + T *g_pdf_offset, + bool *g_pdf_offset_zeros, + T *g_weights, + const T min_diff2, + const int nr_coarse_orient, + const int nr_coarse_trans, + const unsigned long max_idx, + virtualSYCL *devAcc) +{ + assert(max_idx <= std::numeric_limits::max()); + auto dev = dynamic_cast(devAcc); + + dev->syclSubmit([&](sycl::handler &cgh) + { + using namespace sycl; + + cgh.parallel_for(range<1>(max_idx), [=](id<1> idx) + { + const int itrans = idx % nr_coarse_trans; + const int iorient = (idx - itrans) / nr_coarse_trans; + + T diff2 = g_weights[idx]; + if( diff2 < min_diff2 || g_pdf_orientation_zeros[iorient] || g_pdf_offset_zeros[itrans]) + g_weights[idx] = -99e99; //large negative number + else + g_weights[idx] = g_pdf_orientation[iorient] + g_pdf_offset[itrans] + min_diff2 - diff2; + }); + }).wait_and_throw(); +} + +template +inline void collect2jobs( int grid_size, + int block_size, + XFLOAT *g_oo_otrans_x, // otrans-size -> make const + XFLOAT *g_oo_otrans_y, // otrans-size -> make const + XFLOAT *g_oo_otrans_z, // otrans-size -> make const + XFLOAT *g_myp_oo_otrans_x2y2z2, // otrans-size -> make const + XFLOAT *g_i_weights, + XFLOAT op_significant_weight, // TODO Put in const + XFLOAT op_sum_weight, // TODO Put in const + unsigned long coarse_trans, + unsigned long oversamples_trans, + unsigned long oversamples_orient, + unsigned long oversamples, + bool do_ignore_pdf_direction, + XFLOAT *g_o_weights, + XFLOAT *g_thr_wsum_prior_offsetx_class, + XFLOAT *g_thr_wsum_prior_offsety_class, + XFLOAT *g_thr_wsum_prior_offsetz_class, + XFLOAT *g_thr_wsum_sigma2_offset, + unsigned long *d_rot_idx, + unsigned long *d_trans_idx, + unsigned long *d_job_idx, + unsigned long *d_job_num + ) +{ + // block id + for (int bid=0; bid < grid_size; bid++) { + + XFLOAT s_o_weights[block_size]; + XFLOAT s_thr_wsum_sigma2_offset[block_size];; + XFLOAT s_thr_wsum_prior_offsetx_class[block_size]; + XFLOAT s_thr_wsum_prior_offsety_class[block_size]; + XFLOAT s_thr_wsum_prior_offsetz_class[block_size]; + + unsigned long pos = d_job_idx[bid]; + unsigned long job_size = d_job_num[bid]; + + int pass_num = job_size/block_size + 1; + + for(int tid=0; tid= op_significant_weight ) //TODO Might be slow (divergent threads) + weight /= op_sum_weight; + else + weight = (XFLOAT)0.0; + + s_o_weights[tid] += weight; + s_thr_wsum_prior_offsetx_class[tid] += weight * g_oo_otrans_x[iy]; + s_thr_wsum_prior_offsety_class[tid] += weight * g_oo_otrans_y[iy]; + s_thr_wsum_sigma2_offset[tid] += weight * g_myp_oo_otrans_x2y2z2[iy]; + } + } + } + + for(int tid=1; tid +void sycl_gpu_translate2D(T *g_image_in, + T *g_image_out, + size_t image_size, + int xdim, + int ydim, //not used + int dx, + int dy, + virtualSYCL *devAcc) +{ + assert(image_size <= std::numeric_limits::max()); + auto dev = dynamic_cast(devAcc); + + dev->syclSubmit([&](sycl::handler &cgh) + { + cgh.parallel_for(sycl::range<1>(image_size), [=](sycl::id<1> pixel) + { + int x = pixel % xdim; + int y = (pixel-x) / xdim; + + int xp = x + dx; + int yp = y + dy; + + if( yp>=0 && xp>=0 && yp=0 && new_pixel +void sycl_gpu_translate3D(T *g_image_in, + T *g_image_out, + size_t image_size, + int xdim, + int ydim, + int zdim, //not used + int dx, + int dy, + int dz, + virtualSYCL *devAcc) +{ + assert(image_size <= std::numeric_limits::max()); + auto dev = dynamic_cast(devAcc); + + dev->syclSubmit([&](sycl::handler &cgh) + { + cgh.parallel_for(sycl::range<1>(image_size), [=](sycl::id<1> voxel) + { + int xydim = xdim*ydim; + + int z = voxel / xydim; + int zp = z + dz; + + int xy = voxel % xydim; + int y = xy / xdim; + int yp = y + dy; + + int x = xy % xdim; + int xp = x + dx; + + if( zp>=0 && yp>=0 && xp>=0 && zp=0 && new_voxel +void sycl_gpu_kernel_multi( T *A, + T *OUT, + T S, + size_t image_size, + virtualSYCL *devAcc) +{ + assert(image_size <= std::numeric_limits::max()); + auto dev = dynamic_cast(devAcc); + + dev->syclSubmit([&](sycl::handler &cgh) + { + cgh.parallel_for(sycl::range<1>(image_size), [=](sycl::id<1> idx) + { + OUT[idx] = A[idx]*S; + }); + }).wait_and_throw(); +} +/* + * In place multiplies scalar array A by a scalar S + * + * A[i] = A[i]*S + */ +template +void sycl_gpu_kernel_multi( T *A, + T S, + size_t image_size, + virtualSYCL *devAcc) +{ + assert(image_size <= std::numeric_limits::max()); + auto dev = dynamic_cast(devAcc); + + dev->syclSubmit([&](sycl::handler &cgh) + { + cgh.parallel_for(sycl::range<1>(image_size), [=](sycl::id<1> idx) + { + A[idx] *= S; + }); + }).wait_and_throw(); +} +/* + * Multiplies scalar array A by scalar array B and a scalar S, pixel-by-pixel + * + * OUT[i] = A[i]*B[i]*S + */ +template +void sycl_gpu_kernel_multi( T *A, + T *B, + T *OUT, + T S, + size_t image_size, + virtualSYCL *devAcc) +{ + assert(image_size <= std::numeric_limits::max()); + auto dev = dynamic_cast(devAcc); + + dev->syclSubmit([&](sycl::handler &cgh) + { + cgh.parallel_for(sycl::range<1>(image_size), [=](sycl::id<1> idx) + { + OUT[idx] = A[idx]*B[idx]*S; + }); + }).wait_and_throw(); +} + +/* + * In place add scalar S to scalar array A + * + * A[i] = A[i]*S + */ +template +void sycl_gpu_kernel_add( + T *A, + T S, + size_t size, + virtualSYCL *devAcc +) +{ + assert(size <= std::numeric_limits::max()); + auto dev = dynamic_cast(devAcc); + + dev->syclSubmit([&](sycl::handler &cgh) + { + cgh.parallel_for(sycl::range<1>(size), [=](sycl::id<1> idx) + { + A[idx] += S; + }); + }).wait_and_throw(); +} + +template +inline void kernel_frequencyPass( int grid_size, int block_size, + ACCCOMPLEX *A, + long int ori_size, + size_t Xdim, + size_t Ydim, + size_t Zdim, + XFLOAT edge_low, + XFLOAT edge_width, + XFLOAT edge_high, + XFLOAT angpix, + size_t image_size) +{ + // TODO - why not a single loop over image_size pixels? + for(int blk=0; blk lows are dead + { + A[texel].x = 0.; + A[texel].y = 0.; + } + else if (res < edge_high) //highpass => medium lows are almost dead + { + XFLOAT mul = 0.5 - 0.5 * std::cos( PI * (res-edge_low)/edge_width); + A[texel].x *= mul; + A[texel].y *= mul; + } + } + else //lowpass + { + if (res > edge_high) //lowpass => highs are dead + { + A[texel].x = 0.; + A[texel].y = 0.; + } + else if (res > edge_low) //lowpass => medium highs are almost dead + { + XFLOAT mul = 0.5 + 0.5 * std::cos( PI * (res-edge_low)/edge_width); + A[texel].x *= mul; + A[texel].y *= mul; + } + } + } + } // tid + } // blk +} + +template +inline void powerClass(int gridSize, + ACCCOMPLEX *g_image, + XFLOAT *g_spectrum, + size_t image_size, + size_t spectrum_size, + int xdim, + int ydim, + int zdim, + int res_limit, + XFLOAT *g_highres_Xi2) +{ + for(int bid=0; bid(std::sqrt(static_cast(d)) + 0.5); + if((ires=res_limit) + s_highres_Xi2[tid] += normFaux; + } + } + } + + for(int tid=1; tid sycl_sincos(XFLOAT val) +{ + return std::make_pair(sycl::native::sin(val), sycl::native::cos(val)); +} + +inline void translatePixel( + int x, + int y, + XFLOAT tx, + XFLOAT ty, + XFLOAT real, + XFLOAT imag, + XFLOAT &tReal, + XFLOAT &tImag) +{ + XFLOAT v = x * tx + y * ty; + XFLOAT s = sycl::native::sin(v); + XFLOAT c = sycl::native::cos(v); + + tReal = c * real - s * imag; + tImag = c * imag + s * real; +} + +inline void translatePixel( + int x, + int y, + int z, + XFLOAT tx, + XFLOAT ty, + XFLOAT tz, + XFLOAT real, + XFLOAT imag, + XFLOAT &tReal, + XFLOAT &tImag) +{ + XFLOAT v = x * tx + y * ty + z * tz; + XFLOAT s = sycl::native::sin(v); + XFLOAT c = sycl::native::cos(v); + + tReal = c * real - s * imag; + tImag = c * imag + s * real; +} + +// sincos lookup table optimization. Function translatePixel calls +// sincos(x*tx + y*ty). We precompute 2D lookup tables for x and y directions. +// The first dimension is x or y pixel index, and the second dimension is x or y +// translation index. Since sin(a+B) = sin(A) * cos(B) + cos(A) * sin(B), and +// cos(A+B) = cos(A) * cos(B) - sin(A) * sin(B), we can use lookup table to +// compute sin(x*tx + y*ty) and cos(x*tx + y*ty). +inline void computeSincosLookupTable2D(unsigned long trans_num, + XFLOAT *trans_x, + XFLOAT *trans_y, + int xSize, + int ySize, + XFLOAT *sin_x, + XFLOAT *cos_x, + XFLOAT *sin_y, + XFLOAT *cos_y) +{ + for(unsigned long i=0; i +void sycl_kernel_make_eulers_2D(int grid_size, int block_size, + XFLOAT *alphas, + XFLOAT *eulers, + unsigned long orientation_num); + +template +void sycl_kernel_make_eulers_3D(int grid_size, int block_size, + XFLOAT *alphas, + XFLOAT *betas, + XFLOAT *gammas, + XFLOAT *eulers, + unsigned long orientation_num, + XFLOAT *L, + XFLOAT *R); + +template< typename T> +size_t findThresholdIdxInCumulativeSum(T *data, const size_t count, T threshold, virtualSYCL *devAcc) +{ + assert(count <= std::numeric_limits::max()); + sycl::queue *Q = dynamic_cast(devAcc)->getQueue(); + size_t dist; + #ifdef USE_MORE_ONEDPL + auto iter = dpl::find_if(dynamic_cast(devAcc)->getDevicePolicy(), data, data+count, [=](T &v){return (v>threshold);}); + dist = dpl::distance(data, iter); + #else + dist = 0; + { + using namespace sycl; + auto buf = buffer{&dist, range{1}}; + + Q->submit([&](handler &cgh) + { + auto loc = accessor{buf, cgh, write_only}; + cgh.parallel_for(range<1>(count-1), [=](id<1> idx) + { + if (data[idx] <= threshold && data[idx+1] > threshold) + loc[0] = idx+1; + }); + }).wait_and_throw(); + } + #endif + return dist; +} + +static void sycl_kernel_allweights_to_mweights(unsigned long *d_iorient, XFLOAT *d_allweights, XFLOAT *d_mweights, unsigned long orientation_num, unsigned long translation_num, int block_size, virtualSYCL *devAcc) +{ + assert(orientation_num*translation_num <= std::numeric_limits::max()); + auto dev = dynamic_cast(devAcc); + sycl::event prev; + bool isAsync = dev->isAsyncQueue(); + if (isAsync) + prev = dev->getLastEvent(); + + auto ev = dev->syclSubmit([&](sycl::handler &cgh) + { + using namespace sycl; + if (isAsync) + cgh.depends_on(prev); + + cgh.parallel_for(range<1>(orientation_num*translation_num), [=](id<1> idx) + { + d_mweights[d_iorient[idx/translation_num] * translation_num + idx%translation_num] + = + d_allweights[idx/translation_num * translation_num + idx%translation_num]; + }); + }); + dev->pushEvent(ev); +} + +} // end of namespace syclGpuKernels + +#endif /* HELPER_KERNELS_H_ */ diff --git a/src/acc/sycl/sycl_kernels/sycl_utils.h b/src/acc/sycl/sycl_kernels/sycl_utils.h new file mode 100644 index 000000000..79a4e7781 --- /dev/null +++ b/src/acc/sycl/sycl_kernels/sycl_utils.h @@ -0,0 +1,289 @@ +#ifndef SYCL_UTILITIES_H +#define SYCL_UTILITIES_H + +#include +#include +#include +#include +#include + +#include "src/macros.h" +#include "src/acc/sycl/sycl_settings.h" + +namespace syclKernels +{ + +template< typename T1, typename T2 > +static inline +int floorfracf(T1 a, T2 b) +{ + return static_cast(a/b); +} + +template< typename T1, typename T2 > +static inline +int ceilfracf(T1 a, T2 b) +{ + return static_cast(a/b + 1); +} + +__attribute__((always_inline)) +static inline +XFLOAT no_tex2D(XFLOAT *mdl, XFLOAT xp, XFLOAT yp, int mdlX, int mdlInitY) +{ + int x0 = sycl::floor(xp); + XFLOAT fx = xp - x0; + + int y0 = sycl::floor(yp); + XFLOAT fy = yp - y0; + y0 -= mdlInitY; + + int offset1 = (y0 * mdlX + x0); + int offset2 = offset1 + 1; + int offset3 = offset1 + mdlX; + int offset4 = offset1 + mdlX + 1; + //----------------------------- + XFLOAT d00 = mdl[offset1]; + XFLOAT d01 = mdl[offset2]; + XFLOAT d10 = mdl[offset3]; + XFLOAT d11 = mdl[offset4]; + //----------------------------- + XFLOAT dx0 = d00 + (d01 - d00)*fx; + XFLOAT dx1 = d10 + (d11 - d10)*fx; + //----------------------------- + return dx0 + (dx1 - dx0)*fy; +} + +// 2D linear interpolation for complex data that interleaves real and +// imaginary data, rather than storing them in a separate array +__attribute__((always_inline)) +inline +static void complex2D(std::complex *mdlComplex, XFLOAT &real, XFLOAT &imag, + XFLOAT xp, XFLOAT yp, int mdlX, int mdlInitY) +{ + int x0 = sycl::floor(xp); + XFLOAT fx = xp - x0; + + int y0 = sycl::floor(yp); + XFLOAT fy = yp - y0; + y0 -= mdlInitY; + + int offset1 = (y0 * mdlX + x0); + int offset2 = offset1 + 1; + int offset3 = offset1 + mdlX; + int offset4 = offset1 + mdlX + 1; + //----------------------------- + XFLOAT d00[2] {mdlComplex[offset1].real(), mdlComplex[offset1].imag()}; + XFLOAT d01[2] {mdlComplex[offset2].real(), mdlComplex[offset2].imag()}; + XFLOAT d10[2] {mdlComplex[offset3].real(), mdlComplex[offset3].imag()}; + XFLOAT d11[2] {mdlComplex[offset4].real(), mdlComplex[offset4].imag()}; + //----------------------------- + XFLOAT dx0[2] {d00[0] + (d01[0] - d00[0]) * fx, d00[1] + (d01[1] - d00[1]) * fx}; + XFLOAT dx1[2] {d10[0] + (d11[0] - d10[0]) * fx, d10[1] + (d11[1] - d10[1]) * fx}; + //----------------------------- + real = dx0[0] + (dx1[0] - dx0[0])*fy; + imag = dx0[1] + (dx1[1] - dx0[1])*fy; +} + +__attribute__((always_inline)) +static inline +XFLOAT no_tex3D( + XFLOAT *mdl, + XFLOAT xp, XFLOAT yp, XFLOAT zp, + int mdlX, int mdlXY, int mdlInitY, int mdlInitZ) +{ + int x0 = sycl::floor(xp); + XFLOAT fx = xp - x0; + + int y0 = sycl::floor(yp); + XFLOAT fy = yp - y0; + y0 -= mdlInitY; + + int z0 = sycl::floor(zp); + XFLOAT fz = zp - z0; + z0 -= mdlInitZ; + + int offset1 = (z0*mdlXY+y0*mdlX+x0); + int offset2 = offset1 + 1; + int offset3 = offset1 + mdlX; + int offset4 = offset1 + mdlX + 1; + int offset5 = offset1 + mdlXY; + int offset6 = offset1 + mdlXY + 1; + int offset7 = offset1 + mdlX + mdlXY; + int offset8 = offset1 + mdlX + mdlXY + 1; + //----------------------------- + XFLOAT d000 = mdl[offset1]; + XFLOAT d001 = mdl[offset2]; + XFLOAT d010 = mdl[offset3]; + XFLOAT d011 = mdl[offset4]; + XFLOAT d100 = mdl[offset5]; + XFLOAT d101 = mdl[offset6]; + XFLOAT d110 = mdl[offset7]; + XFLOAT d111 = mdl[offset8]; + //----------------------------- + XFLOAT dx00 = d000 + (d001 - d000)*fx; + XFLOAT dx01 = d100 + (d101 - d100)*fx; + XFLOAT dx10 = d010 + (d011 - d010)*fx; + XFLOAT dx11 = d110 + (d111 - d110)*fx; + //----------------------------- + XFLOAT dxy0 = dx00 + (dx10 - dx00)*fy; + XFLOAT dxy1 = dx01 + (dx11 - dx01)*fy; + //----------------------------- + return dxy0 + (dxy1 - dxy0)*fz; +} + +// 3D linear interpolation for complex data that interleaves real and +// imaginary data, rather than storing them in a separate array +__attribute__((always_inline)) +inline +static void complex3D( + std::complex *mdlComplex, + XFLOAT &real, XFLOAT &imag, + XFLOAT xp, XFLOAT yp, XFLOAT zp, int mdlX, int mdlXY, int mdlInitY, int mdlInitZ + ) +{ + int x0 = sycl::floor(xp); + XFLOAT fx = xp - x0; + + int y0 = sycl::floor(yp); + XFLOAT fy = yp - y0; + y0 -= mdlInitY; + + int z0 = sycl::floor(zp); + XFLOAT fz = zp - z0; + z0 -= mdlInitZ; + + int offset1 = (z0*mdlXY+y0*mdlX+x0); + int offset2 = offset1 + 1; + int offset3 = offset1 + mdlX; + int offset4 = offset1 + mdlX + 1; + int offset5 = offset1 + mdlXY; + int offset6 = offset1 + mdlXY + 1; + int offset7 = offset1 + mdlX + mdlXY; + int offset8 = offset1 + mdlX + mdlXY + 1; + + XFLOAT d000[2] {mdlComplex[offset1].real(), mdlComplex[offset1].imag()}; + XFLOAT d001[2] {mdlComplex[offset2].real(), mdlComplex[offset2].imag()}; + XFLOAT d010[2] {mdlComplex[offset3].real(), mdlComplex[offset3].imag()}; + XFLOAT d011[2] {mdlComplex[offset4].real(), mdlComplex[offset4].imag()}; + XFLOAT d100[2] {mdlComplex[offset5].real(), mdlComplex[offset5].imag()}; + XFLOAT d101[2] {mdlComplex[offset6].real(), mdlComplex[offset6].imag()}; + XFLOAT d110[2] {mdlComplex[offset7].real(), mdlComplex[offset7].imag()}; + XFLOAT d111[2] {mdlComplex[offset8].real(), mdlComplex[offset8].imag()}; + //----------------------------- + XFLOAT dx00[2] {d000[0] + (d001[0] - d000[0])*fx, d000[1] + (d001[1] - d000[1])*fx}; + XFLOAT dx01[2] {d100[0] + (d101[0] - d100[0])*fx, d100[1] + (d101[1] - d100[1])*fx}; + XFLOAT dx10[2] {d010[0] + (d011[0] - d010[0])*fx, d010[1] + (d011[1] - d010[1])*fx}; + XFLOAT dx11[2] {d110[0] + (d111[0] - d110[0])*fx, d110[1] + (d111[1] - d110[1])*fx}; + //----------------------------- + XFLOAT dxy0[2] {dx00[0] + (dx10[0] - dx00[0])*fy, dx00[1] + (dx10[1] - dx00[1])*fy}; + XFLOAT dxy1[2] {dx01[0] + (dx11[0] - dx01[0])*fy, dx01[1] + (dx11[1] - dx01[1])*fy}; + //----------------------------- + real = dxy0[0] + (dxy1[0] - dxy0[0])*fz; + imag = dxy0[1] + (dxy1[1] - dxy0[1])*fz; +} + +// From https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon +template +bool almostEqual(const T x, const T y, const int ulp) +{ + // the machine epsilon has to be scaled to the magnitude of the values used + // and multiplied by the desired precision in ULPs (units in the last place) + return std::fabs(x-y) <= std::numeric_limits::epsilon() * std::fabs(x+y) * ulp + // unless the result is subnormal + || std::fabs(x-y) < std::numeric_limits::min(); +} + +// Get some ideas from https://bitbashing.io/comparing-floats.html +template +int ULP(const T a, const T b) +{ + if (a == b) return 0; + + const auto max = std::numeric_limits::max(); + if ((! std::isfinite(a)) || (! std::isfinite(b))) + return max; + + if (sizeof(T) == 4) + { + int32_t ia, ib; + std::memcpy(&ia, &a, sizeof(T)); + std::memcpy(&ib, &b, sizeof(T)); + + // Don't compare differently-signed floats. + if ((ia < 0) != (ib < 0)) return max; + + // Return the absolute value of the distance in ULPs. + return std::abs(ia - ib); + } + else if (sizeof(T) == 8) + { + int64_t ia, ib; + std::memcpy(&ia, &a, sizeof(T)); + std::memcpy(&ib, &b, sizeof(T)); + + // Don't compare differently-signed floats. + if ((ia < 0) != (ib < 0)) + return max; + + // Return the absolute value of the distance in ULPs. + return static_cast(std::abs(ia - ib)); + } + else + return max; +} + +template +static bool checkFinite(const T *ptr, const size_t sz, const char *name) +{ + for (size_t i = 0; i < sz; i++) + { + if (! std::isfinite(ptr[i])) + { + std::cerr << name << " has inf or nan\n"; + return false; + } + } + return true; +} + +template +static bool checkDifference(const T *ptrA, const T *ptrB, const size_t sz, const char *name, const int mULP = 16, const int maxcount = 20) +{ + int count = 0; + for (size_t i = 0; i < sz; i++) + { + // Consider up to mULP(=16 by default) ULPs difference as identical + if (! almostEqual(ptrA[i], ptrB[i], mULP)) + { + if (count < maxcount) + { + int ulp = ULP(ptrA[i], ptrB[i]); + std::cerr << name << " has difference at [" << i << "]: " << ptrA[i] << " != " << ptrB[i] << " \t[ulp=" << ULP(ptrA[i], ptrB[i]) << "]" << std::endl; + } + else + return false; + + count++; + } + } + return true; +} + +template +static size_t countLargerThanNumber(const T *ptrA, const size_t sz, const T val) +{ + size_t count = 0; + for (size_t i = 0; i < sz; i++) + { + // Consider up to 10240 ULP(somewhat large!)difference as identical + if (ptrA[i] >= val) + count++; + } + std::cerr << count << " / " << sz << std::endl; + return count; +} + +} // end of namespace syclKernels + +#endif //SYCL_UTILITIES_H diff --git a/src/acc/sycl/sycl_kernels/wavg.h b/src/acc/sycl/sycl_kernels/wavg.h new file mode 100644 index 000000000..b5cac7932 --- /dev/null +++ b/src/acc/sycl/sycl_kernels/wavg.h @@ -0,0 +1,40 @@ +#ifndef WAVG_KERNEL_H_ +#define WAVG_KERNEL_H_ + +#include "src/acc/sycl/sycl_virtual_dev.h" + +class AccProjectorKernel; + +namespace syclKernels +{ + +template +#ifdef __INTEL_COMPILER +inline +#else +__attribute__((always_inline)) inline +#endif +void wavg( + XFLOAT *g_eulers, + AccProjectorKernel &projector, + unsigned long image_size, + unsigned long orientation_num, + XFLOAT *g_img_real, + XFLOAT *g_img_imag, + XFLOAT *g_trans_x, + XFLOAT *g_trans_y, + XFLOAT *g_trans_z, + XFLOAT *g_weights, + XFLOAT *g_ctfs, + XFLOAT *g_wdiff2s_parts, + XFLOAT *g_wdiff2s_AA, + XFLOAT *g_wdiff2s_XA, + unsigned long trans_num, + XFLOAT weight_norm, + XFLOAT significant_weight, + XFLOAT part_scale, + virtualSYCL *devACC); + +} // end of namespace syclKernels + +#endif /* WAVG_KERNEL_H_ */ diff --git a/src/acc/sycl/sycl_kernels/wavg_gpu.h b/src/acc/sycl/sycl_kernels/wavg_gpu.h new file mode 100644 index 000000000..583fdf28c --- /dev/null +++ b/src/acc/sycl/sycl_kernels/wavg_gpu.h @@ -0,0 +1,152 @@ +#ifndef WAVG_GPU_KERNEL_H_ +#define WAVG_GPU_KERNEL_H_ + +#include +#include +#include +#include + +#include "src/acc/acc_projector.h" +#include "src/acc/sycl/sycl_settings.h" +#include "src/acc/sycl/sycl_kernels/sycl_utils.h" +#include "src/acc/sycl/sycl_kernels/helper.h" +#include "src/acc/sycl/sycl_kernels/helper_gpu.h" + +namespace syclGpuKernels +{ + +template +void sycl_kernel_wavg( + sycl::nd_item<3> nit, AccProjectorKernel &projector, + XFLOAT *g_eulers, int image_size, int orientation_num, + XFLOAT *g_img_real, XFLOAT *g_img_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + XFLOAT *g_weights, XFLOAT *g_ctfs, + XFLOAT *g_wdiff2s_parts, XFLOAT *g_wdiff2s_AA, XFLOAT *g_wdiff2s_XA, + int translation_num, XFLOAT weight_norm, XFLOAT significant_weight, XFLOAT part_scale, + XFLOAT *s_parts, XFLOAT *s_sumAA, XFLOAT *s_sumXA, XFLOAT *s_eulers + ) +{ + const int bid = nit.get_group_linear_id(); + const int tid = nit.get_local_id(2); + + const int xSize = projector.imgX; + const int ySize = projector.imgY; + const int zSize = projector.imgZ; + const int maxR = projector.maxR; + + const XFLOAT inv_weight_norm = 1.0f / weight_norm; + + if (tid < 9) + s_eulers[tid] = g_eulers[bid*9+tid]; + + __group_barrier(nit); + + int pass_num {image_size/block_sz + 1}; + for (int pass = 0; pass < pass_num; pass++) // finish a reference proj in each block + { + s_parts[tid] = 0.0f; + s_sumXA[tid] = 0.0f; + s_sumAA[tid] = 0.0f; + + int pixel = pass*block_sz + tid; + if (pixel < image_size) + { + int x, y, z, xy; + if (DATA3D) + { + z = pixel / (xSize*ySize); + xy = pixel % (xSize*ySize); + x = xy % xSize; + y = xy / xSize; + if (z > maxR) + { + if (z >= zSize - maxR) + z = z - zSize; + else + x = maxR; + } + } + else + { + x = pixel % xSize; + y = pixel / xSize; + } + if (y > maxR) + { + if (y >= ySize - maxR) + y = y - ySize; + else + x = maxR; + } + + XFLOAT ref_real, ref_imag; + if (DATA3D) + projector.project3Dmodel( + x,y,z, + s_eulers[0], s_eulers[1], s_eulers[2], + s_eulers[3], s_eulers[4], s_eulers[5], + s_eulers[6], s_eulers[7], s_eulers[8], + ref_real, ref_imag); + else if (REF3D) + projector.project3Dmodel( + x,y, + s_eulers[0], s_eulers[1], + s_eulers[3], s_eulers[4], + s_eulers[6], s_eulers[7], + ref_real, ref_imag); + else + projector.project2Dmodel( + x,y, + s_eulers[0], s_eulers[1], + s_eulers[3], s_eulers[4], + ref_real, ref_imag); + + if (REFCTF) + { + ref_real *= g_ctfs[pixel]; + ref_imag *= g_ctfs[pixel]; + } + else + { + ref_real *= part_scale; + ref_imag *= part_scale; + } + + XFLOAT img_real = g_img_real[pixel]; + XFLOAT img_imag = g_img_imag[pixel]; + for (int itrans = 0; itrans < translation_num; itrans++) + { + XFLOAT weight = g_weights[bid*translation_num + itrans]; + + if (weight >= significant_weight) + { + weight *= inv_weight_norm; + + XFLOAT trans_real, trans_imag; + if (DATA3D) + translatePixel(x, y, z, g_trans_x[itrans], g_trans_y[itrans], g_trans_z[itrans], img_real, img_imag, trans_real, trans_imag); + else + translatePixel(x, y, g_trans_x[itrans], g_trans_y[itrans], img_real, img_imag, trans_real, trans_imag); + + XFLOAT diff_real = ref_real - trans_real; + XFLOAT diff_imag = ref_imag - trans_imag; + + s_parts[tid] += weight * (diff_real*diff_real + diff_imag*diff_imag); + s_sumXA[tid] += weight * ( ref_real*trans_real + ref_imag*trans_imag); + s_sumAA[tid] += weight * ( ref_real*ref_real + ref_imag*ref_imag ); + } + } + + g_wdiff2s_XA[pixel] += s_sumXA[tid]; + g_wdiff2s_AA[pixel] += s_sumAA[tid]; +// atomic_ref_dev( g_wdiff2s_XA[pixel] ).fetch_add(s_sumXA[tid]); +// atomic_ref_dev( g_wdiff2s_AA[pixel] ).fetch_add(s_sumAA[tid]); + atomic_ref_dev( g_wdiff2s_parts[pixel] ).fetch_add(s_parts[tid]); + } + } +} + +} // end of namespace syclGpuKernels + +#endif /* WAVG_GPU_KERNEL_H_ */ diff --git a/src/acc/sycl/sycl_kernels/wavg_impl.h b/src/acc/sycl/sycl_kernels/wavg_impl.h new file mode 100644 index 000000000..b43f8e191 --- /dev/null +++ b/src/acc/sycl/sycl_kernels/wavg_impl.h @@ -0,0 +1,86 @@ +#ifndef WAVG_IMPL_KERNEL_H_ +#define WAVG_IMPL_KERNEL_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "src/acc/acc_projectorkernel_impl.h" +#include "src/acc/sycl/sycl_dev.h" +#include "src/acc/sycl/sycl_kernels/sycl_utils.h" +#include "src/acc/sycl/sycl_kernels/wavg_gpu.h" + +namespace syclKernels +{ + + template + __attribute__((always_inline)) + inline + void wavg( + XFLOAT *g_eulers, + AccProjectorKernel &projector, + unsigned long image_size, unsigned long orient_num, + XFLOAT *g_img_real, XFLOAT *g_img_imag, + XFLOAT *g_trans_x, XFLOAT *g_trans_y, XFLOAT *g_trans_z, + XFLOAT *g_weights, XFLOAT *g_ctfs, + XFLOAT *g_wdiff2s_parts, XFLOAT *g_wdiff2s_AA, XFLOAT *g_wdiff2s_XA, + unsigned long trans_num, XFLOAT weight_norm, + XFLOAT significant_weight, XFLOAT part_scale, + virtualSYCL *devAcc) + { + devSYCL *dGPU = dynamic_cast(devAcc); + assert( trans_num <= std::numeric_limits::max()); + assert( image_size <= std::numeric_limits::max()); + assert( orient_num <= std::numeric_limits::max()); + assert( orient_num <= dGPU->maxWorkGroup[1]); + assert( block_sz <= dGPU->maxItem[2]); + assert(block_sz*3 + 9 <= dGPU->localMem); + + sycl::range<3> wi (1,1,block_sz); + sycl::range<3> wg (1,orient_num,block_sz); + dGPU->reCalculateRange(wg, wi); + auto event = dGPU->syclSubmit + ( + [&](sycl::handler &cgh) // + { + using namespace sycl; + // accessors to device memory + local_accessor s_parts_acc(range<1>(block_sz), cgh); + local_accessor s_sumAA_acc(range<1>(block_sz), cgh); + local_accessor s_sumXA_acc(range<1>(block_sz), cgh); + local_accessor s_eulers_acc(range<1>(9), cgh); + + cgh.parallel_for + ( + nd_range<3>(wg, wi), [=](nd_item<3> nit) + #if defined(__INTEL_LLVM_COMPILER) && defined(INTEL_SG_SIZE) + [[intel::reqd_sub_group_size(INTEL_SG_SIZE)]] + #endif + { + syclGpuKernels::sycl_kernel_wavg + ( // + nit, + const_cast(projector), // Why const_cast is needed for compilation? + g_eulers, static_cast(image_size), static_cast(orient_num), + g_img_real, g_img_imag, g_trans_x, g_trans_y, g_trans_z, + g_weights, g_ctfs, + g_wdiff2s_parts, g_wdiff2s_AA, g_wdiff2s_XA, + static_cast(trans_num), weight_norm, significant_weight, part_scale, + s_parts_acc.get_pointer(), + s_sumAA_acc.get_pointer(), + s_sumXA_acc.get_pointer(), + s_eulers_acc.get_pointer() + ); // End of sycl_kernel_wavg + } // End of cgh.parallel_for Lamda function + ); // End of cgh.parallel_for + } // End of dGPU->syclSubmit Lamda function + ); // End of dGPU->syclSubmit + } + +} // end of namespace syclKernels + +#endif /* WAVG_IMPL_KERNEL_H_ */ diff --git a/src/acc/sycl/sycl_ml_optimiser.cpp b/src/acc/sycl/sycl_ml_optimiser.cpp new file mode 100644 index 000000000..7a24bac6a --- /dev/null +++ b/src/acc/sycl/sycl_ml_optimiser.cpp @@ -0,0 +1,767 @@ +#ifdef _SYCL_ENABLED +// A large amount of this code is direct from cuda_ml_optimizer and so could +// be shared (but possibly with difficulty since it is enough different that +// we either need a lot of #ifdefs, or a lot of macros/some other mechanism to +// abstract the differences). The biggest differences are the type of memory +// objects used (std::vector vs. CudaGlobalPtr and CudaCustomAllocator), the +// lack of transfers to/from the device, and on-device operations (which are +// replaced by loops/function calls). +// +// CudaFFT has been replaced with lib FFTW, if RELION is configured with mix +// precision, both single and double precision FFTW are linked into RELION. +// Install fftw-static.x86_64 and fftw-static.i686 to get the libraries without +// having to pull them at build time. Over time we hope to replace FFTW with +// MKL. +// +// NOTE: Since the GPU code was ported back to CPU there may be additional +// changes made in the CUDA code which may not have made it here. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "src/acc/sycl/device_stubs.h" +#include "src/ml_optimiser.h" +#include "src/acc/acc_ptr.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/sycl/sycl_benchmark_utils.h" +#include "src/acc/sycl/sycl_helper_functions.h" +#include "src/acc/sycl/mkl_fft.h" +#include "src/acc/data_types.h" +#include "src/parallel.h" + +#include "src/acc/utilities.h" +#include "src/acc/utilities_impl.h" + +#include "src/acc/acc_helper_functions.h" +#include "src/acc/acc_ml_optimiser.h" +#include "src/acc/acc_ml_optimiser_impl.h" +#include "src/acc/sycl/sycl_ml_optimiser.h" + +#include "src/acc/sycl/sycl_dev.h" + +#define PRINT_SYCL_INFO(x) std::cout << " "#x": " << device.get_info() << std::endl + +#ifdef SYCL_EXT_INTEL_CSLICE +template +bool contains(RangeTy &&Range, const ElemTy &Elem) { + return std::find(Range.begin(), Range.end(), Elem) != Range.end(); +} + +bool isPartitionableByCSlice(sycl::device &Dev) { + return contains(Dev.get_info(), sycl::info::partition_property::ext_intel_partition_by_cslice); +} +#else +bool isPartitionableByCSlice(sycl::device &Dev) { + return false; +} +#endif + +#ifdef SYCL_EXT_INTEL_QUEUE_INDEX +int numPartitionableByQueueIndex(sycl::device &Dev) { + return Dev.get_info(); +} +#else +int numPartitionableByQueueIndex(sycl::device &Dev) { + return 1; +} +#endif + +std::mutex fft_mutex; + +MlSyclDataBundle::MlSyclDataBundle(virtualSYCL *dev) : generateProjectionPlanOnTheFly {false}, _devAcc {dev} +{ +} + +MlSyclDataBundle::~MlSyclDataBundle() +{ + projectors.clear(); + backprojectors.clear(); + coarseProjectionPlans.clear(); +// delete _devAcc; +} + +void MlSyclDataBundle::setup(MlOptimiser *baseMLO) +{ + /*====================================================== + PROJECTOR AND BACKPROJECTOR + ======================================================*/ + + unsigned nr_proj = baseMLO->mymodel.PPref.size(); + unsigned nr_bproj = baseMLO->wsum_model.BPref.size(); + + projectors.resize(nr_proj); + backprojectors.resize(nr_bproj); + + //Loop over classes + for (int imodel = 0; imodel < nr_proj; imodel++) + { + projectors[imodel].setMdlDim( + _devAcc, + baseMLO->mymodel.PPref[imodel].data.xdim, + baseMLO->mymodel.PPref[imodel].data.ydim, + baseMLO->mymodel.PPref[imodel].data.zdim, + baseMLO->mymodel.PPref[imodel].data.yinit, + baseMLO->mymodel.PPref[imodel].data.zinit, + baseMLO->mymodel.PPref[imodel].r_max, + baseMLO->mymodel.PPref[imodel].padding_factor); + + projectors[imodel].initMdl(baseMLO->mymodel.PPref[imodel].data.data); + } + + for (int imodel = 0; imodel < nr_bproj; imodel++) + { + backprojectors[imodel].setMdlDim( + _devAcc, + baseMLO->wsum_model.BPref[imodel].data.xdim, + baseMLO->wsum_model.BPref[imodel].data.ydim, + baseMLO->wsum_model.BPref[imodel].data.zdim, + baseMLO->wsum_model.BPref[imodel].data.yinit, + baseMLO->wsum_model.BPref[imodel].data.zinit, + baseMLO->wsum_model.BPref[imodel].r_max, + baseMLO->wsum_model.BPref[imodel].padding_factor); + + backprojectors[imodel].initMdl(); + } + + /*====================================================== + PROJECTION PLAN + ======================================================*/ + + unsigned nr_classes = baseMLO->mymodel.nr_classes; + coarseProjectionPlans.resize(nr_classes); + + //Can we pre-generate projector plan and corresponding euler matrices for all particles + if (baseMLO->do_skip_align || baseMLO->do_skip_rotate || baseMLO->do_auto_refine || baseMLO->mymodel.orientational_prior_mode != NOPRIOR || baseMLO->mydata.is_tomo) + generateProjectionPlanOnTheFly = true; + else + generateProjectionPlanOnTheFly = false; + + for (int iclass = 0; iclass < nr_classes; iclass++) + { + //If doing predefined projector plan at all and is this class significant + if (!generateProjectionPlanOnTheFly && baseMLO->mymodel.pdf_class[iclass] > 0.) + { + std::vector exp_pointer_dir_nonzeroprior; + std::vector exp_pointer_psi_nonzeroprior; + std::vector exp_directions_prior; + std::vector exp_psi_prior; + + long unsigned itrans_max = baseMLO->sampling.NrTranslationalSamplings() - 1; + long unsigned nr_idir = baseMLO->sampling.NrDirections(0, &exp_pointer_dir_nonzeroprior); + long unsigned nr_ipsi = baseMLO->sampling.NrPsiSamplings(0, &exp_pointer_psi_nonzeroprior ); + +#ifdef _SYCL_ENABLED + coarseProjectionPlans[iclass].setSyclDevice(_devAcc); +#endif + coarseProjectionPlans[iclass].setup( + baseMLO->sampling, + exp_directions_prior, + exp_psi_prior, + exp_pointer_dir_nonzeroprior, + exp_pointer_psi_nonzeroprior, + NULL, //Mcoarse_significant + baseMLO->mymodel.pdf_class, + baseMLO->mymodel.pdf_direction, + nr_idir, + nr_ipsi, + 0, //idir_min + nr_idir - 1, //idir_max + 0, //ipsi_min + nr_ipsi - 1, //ipsi_max + 0, //itrans_min + itrans_max, + 0, //current_oversampling + 1, //nr_oversampled_rot + iclass, + true, //coarse + !IS_NOT_INV, + baseMLO->do_skip_align, + baseMLO->do_skip_rotate, + baseMLO->mymodel.orientational_prior_mode + ); + } + } +} + +MlOptimiserSYCL::MlOptimiserSYCL(MlOptimiser *baseMLOptimiser, MlSyclDataBundle *b, const bool isStream, const char *timing_fnm) : + baseMLO {baseMLOptimiser}, + bundle {b}, + transformer1 {baseMLOptimiser->mymodel.data_dim}, + transformer2 {baseMLOptimiser->mymodel.data_dim}, + refIs3D {baseMLO->mymodel.ref_dim == 3}, + dataIs3D {baseMLO->mymodel.data_dim == 3}, + shiftsIs3D {baseMLO->mymodel.data_dim == 3 || baseMLO->mydata.is_tomo}, + generateProjectionPlanOnTheFly {bundle->generateProjectionPlanOnTheFly}, + _useStream {isStream} +{ + if (baseMLOptimiser == nullptr) + _devAcc = nullptr; + else + setupDevice(); +} + +MlOptimiserSYCL::~MlOptimiserSYCL() +{ + if (_useStream) + for (auto q : classStreams) + delete q; + + classStreams.clear(); +#ifndef USE_EXISTING_SYCL_DEVICE + if (_devAcc != nullptr) + delete _devAcc; +#endif +} + +std::vector MlOptimiserSYCL::getDevices(const syclDeviceType select, const std::tuple syclOpt, const syclBackendType BE, const bool verbose) +{ + bool isSubSub, isInOrderQueue, isAsyncSubmission; + std::tie(isSubSub, isInOrderQueue, isAsyncSubmission) = syclOpt; + std::vector selectedDevices; + + if (select == syclDeviceType::gpu) + { + int nDevice = 0, nCard = 0; + auto devices = sycl::device::get_devices(sycl::info::device_type::gpu); + for (auto &device : devices) + { + auto plName = device.get_platform().get_info(); + auto beType = device.get_backend(); + if ( (BE == syclBackendType::levelZero && beType == sycl::backend::ext_oneapi_level_zero) + || (BE == syclBackendType::CUDA && beType == sycl::backend::ext_oneapi_cuda) + || (BE == syclBackendType::HIP && beType == sycl::backend::ext_oneapi_hip) + || (BE == syclBackendType::openCL && beType == sycl::backend::opencl) ) + { + auto ndev = device.get_info(); + if (ndev > 1) + { + auto subs = device.create_sub_devices(sycl::info::partition_affinity_domain::numa); + auto ctx = sycl::context(subs); + int nStack = 0; + for (auto &sub : subs) + { +#if defined(SYCL_EXT_INTEL_CSLICE) || defined(SYCL_EXT_INTEL_QUEUE_INDEX) + #ifdef SYCL_EXT_INTEL_CSLICE + if (isSubSub && isPartitionableByCSlice(sub)) + { + auto subsubs = sub.create_sub_devices(); + auto ctxctx = sycl::context(subsubs); + int nSlice = 0; + for (auto &subsub : subsubs) + { + selectedDevices.push_back(new devSYCL(ctxctx, subsub, nDevice++, isInOrderQueue, isAsyncSubmission)); + auto addedDevice = dynamic_cast(selectedDevices.back()); + addedDevice->setCardID(nCard); + addedDevice->setStackID(nStack); + addedDevice->setNumStack(subs.size()); + addedDevice->setSliceID(nSlice++); + addedDevice->setNumSlice(1); + if (verbose) + { + std::cout << std::string(80, '*') << std::endl; + std::cout << "Created SYCL device is " << addedDevice->getName() << std::endl; + std::cout << "maxComputeUnit= " << addedDevice->maxUnit << ", maxWorkGroupSize= " << addedDevice->maxGroup << ", globalMemSize= " << addedDevice->globalMem << std::endl; + addedDevice->printDeviceInfo(); + std::cout << "\n"; + } + } + } + #else + int maxQindex = numPartitionableByQueueIndex(sub); + if (isSubSub && maxQindex > 1) + { + int nSlice = 0; + for (int i = 0; i < maxQindex; i++) + { + selectedDevices.push_back(new devSYCL(ctx, sub, i, nDevice++, isInOrderQueue, isAsyncSubmission)); + auto addedDevice = dynamic_cast(selectedDevices.back()); + addedDevice->setCardID(nCard); + addedDevice->setStackID(nStack); + addedDevice->setNumStack(subs.size()); + addedDevice->setSliceID(nSlice++); + addedDevice->setNumSlice(1); + if (verbose) + { + std::cout << std::string(80, '*') << std::endl; + std::cout << "Created SYCL device is " << addedDevice->getName() << std::endl; + std::cout << "maxComputeUnit= " << addedDevice->maxUnit << ", maxWorkGroupSize= " << addedDevice->maxGroup << ", globalMemSize= " << addedDevice->globalMem << std::endl; + addedDevice->printDeviceInfo(); + std::cout << "\n"; + } + } + } + #endif + else +#endif + { + int numSlice = -1; +#if defined(SYCL_EXT_INTEL_CSLICE) || defined(SYCL_EXT_INTEL_QUEUE_INDEX) + #ifdef SYCL_EXT_INTEL_CSLICE + if (isSubSub && isPartitionableByCSlice(sub)) + { + auto subsubs = sub.create_sub_devices(); + if (subsubs.size() > 1) + numSlice = subsubs.size(); + } + #else + if (isSubSub && numPartitionableByQueueIndex(sub) > 1) + numSlice = numPartitionableByQueueIndex(sub); + #endif +#endif + selectedDevices.push_back(new devSYCL(ctx, sub, nDevice++, isInOrderQueue, isAsyncSubmission)); + auto addedDevice = dynamic_cast(selectedDevices.back()); + addedDevice->setCardID(nCard); + addedDevice->setStackID(nStack); + addedDevice->setNumStack(subs.size()); + addedDevice->setSliceID(-1); + addedDevice->setNumSlice(numSlice); + if (verbose) + { + std::cout << std::string(80, '*') << std::endl; + std::cout << "Created SYCL device is " << addedDevice->getName() << std::endl; + std::cout << "maxComputeUnit= " << addedDevice->maxUnit << ", maxWorkGroupSize= " << addedDevice->maxGroup << ", globalMemSize= " << addedDevice->globalMem << std::endl; + addedDevice->printDeviceInfo(); + std::cout << "\n"; + } + } + nStack++; + } + } + else + { +#if defined(SYCL_EXT_INTEL_CSLICE) || defined(SYCL_EXT_INTEL_QUEUE_INDEX) + #ifdef SYCL_EXT_INTEL_CSLICE + if (isSubSub && isPartitionableByCSlice(device)) + { + auto subsubs = device.create_sub_devices(); + auto ctxctx = sycl::context(subsubs); + int nSlice = 0; + for (auto &subsub : subsubs) + { + selectedDevices.push_back(new devSYCL(ctxctx, subsub, nDevice++, isInOrderQueue, isAsyncSubmission)); + auto addedDevice = dynamic_cast(selectedDevices.back()); + addedDevice->setCardID(nCard); + addedDevice->setStackID(0); + addedDevice->setNumStack(1); + addedDevice->setSliceID(nSlice++); + addedDevice->setNumSlice(1); + if (verbose) + { + std::cout << std::string(80, '*') << std::endl; + std::cout << "Created SYCL device is " << addedDevice->getName() << std::endl; + std::cout << "maxComputeUnit= " << addedDevice->maxUnit << ", maxWorkGroupSize= " << addedDevice->maxGroup << ", globalMemSize= " << addedDevice->globalMem << std::endl; + addedDevice->printDeviceInfo(); + std::cout << "\n"; + } + } + } + #else + int maxQindex = numPartitionableByQueueIndex(device); + if (isSubSub && maxQindex > 1) + { + sycl::context ctx; + int nSlice = 0; + for (int i = 0; i < maxQindex; i++) + { + if (i == 0) + selectedDevices.push_back(new devSYCL(device, i, nDevice++, isInOrderQueue, isAsyncSubmission)); + else + selectedDevices.push_back(new devSYCL(ctx, device, i, nDevice++, isInOrderQueue, isAsyncSubmission)); + auto addedDevice = dynamic_cast(selectedDevices.back()); + if (i == 0) + ctx = addedDevice->getContext(); + addedDevice->setCardID(nCard); + addedDevice->setStackID(0); + addedDevice->setNumStack(1); + addedDevice->setSliceID(nSlice++); + addedDevice->setNumSlice(1); + if (verbose) + { + std::cout << std::string(80, '*') << std::endl; + std::cout << "Created SYCL device is " << addedDevice->getName() << std::endl; + std::cout << "maxComputeUnit= " << addedDevice->maxUnit << ", maxWorkGroupSize= " << addedDevice->maxGroup << ", globalMemSize= " << addedDevice->globalMem << std::endl; + addedDevice->printDeviceInfo(); + std::cout << "\n"; + } + } + } + #endif + else +#endif + { + int numSlice = -1; +#if defined(SYCL_EXT_INTEL_CSLICE) || defined(SYCL_EXT_INTEL_QUEUE_INDEX) + #ifdef SYCL_EXT_INTEL_CSLICE + if (isSubSub && isPartitionableByCSlice(device)) + { + auto subsubs = device.create_sub_devices(); + if (subsubs.size() > 1) + numSlice = subsubs.size(); + } + #else + if (isSubSub && numPartitionableByQueueIndex(device) > 1) + numSlice = numPartitionableByQueueIndex(device); + #endif +#endif + selectedDevices.push_back(new devSYCL(device, nDevice++, isInOrderQueue, isAsyncSubmission)); + auto addedDevice = dynamic_cast(selectedDevices.back()); + addedDevice->setCardID(nCard); + addedDevice->setStackID(0); + addedDevice->setNumStack(1); + addedDevice->setSliceID(-1); + addedDevice->setNumSlice(numSlice); + if (verbose) + { + std::cout << std::string(80, '*') << std::endl; + std::cout << "Created SYCL device is " << addedDevice->getName() << std::endl; + std::cout << "maxComputeUnit= " << addedDevice->maxUnit << ", maxWorkGroupSize= " << addedDevice->maxGroup << ", globalMemSize= " << addedDevice->globalMem << std::endl; + addedDevice->printDeviceInfo(); + std::cout << "\n"; + } + } + } + nCard++; + } + } +#ifdef ACC_DOUBLE_PRECISION + bool isFP64 = true; + for (auto &select : selectedDevices) + { + if (! dynamic_cast(select)->canSupportFP64()) + { + isFP64 = false; + break; + } + } + if (! isFP64) + { + for (auto select : selectedDevices) + delete dynamic_cast(select); + + selectedDevices.clear(); + std::cerr << "Double-Precision for Accelerator is requested but there is device which cannot support FP64.\n"; + } +#endif + } + else if (select == syclDeviceType::cpu) + { + int nDevice = 0; + auto devices = sycl::device::get_devices(sycl::info::device_type::cpu); + for (auto &device : devices) + { +/* +// TODO: For heterogeneous run, is this necessary? + auto ndev = device.get_info(); + if (ndev > 1) + { + auto subs = device.create_sub_devices(sycl::info::partition_affinity_domain::numa); + for (auto &sub : subs) + selectedDevices.push_back(new devSYCL(sub, nDevice++, isInOrderQueue, isAsyncSubmission)); + } + else +*/ + selectedDevices.push_back(new devSYCL(device, nDevice++, isInOrderQueue, isAsyncSubmission)); + auto addedDevice = dynamic_cast(selectedDevices.back()); + addedDevice->setCardID(-1); + addedDevice->setStackID(-1); + addedDevice->setNumStack(-1); + addedDevice->setSliceID(-1); + addedDevice->setNumSlice(-1); + if (verbose) + { + std::cout << std::string(80, '*') << std::endl; + std::cout << "Created SYCL device is " << addedDevice->getName() << std::endl; + std::cout << "maxComputeUnit= " << addedDevice->maxUnit << ", maxWorkGroupSize= " << addedDevice->maxGroup << ", globalMemSize= " << addedDevice->globalMem << std::endl; + addedDevice->printDeviceInfo(); + std::cout << "\n"; + } + } + } + else + std::cerr << "Only GPU and CPU devices are supported.\n"; + + return selectedDevices; +} + +void MlOptimiserSYCL::checkDevices() +{ + std::vector devices = sycl::device::get_devices(); + if (devices.size() == 0) + REPORT_ERROR("NO SYCL devices are found"); + + for (auto& device : devices) + { + std::cout << "\nPlatform: " << device.get_platform().get_info() << std::endl; + + PRINT_SYCL_INFO(name); +/* + if (! device.is_host()) PRINT_SYCL_INFO(max_clock_frequency); + PRINT_SYCL_INFO(version); + PRINT_SYCL_INFO(driver_version); + PRINT_SYCL_INFO(partition_max_sub_devices); + PRINT_SYCL_INFO(profiling_timer_resolution); + PRINT_SYCL_INFO(queue_profiling); + + PRINT_SYCL_INFO(max_compute_units); + PRINT_SYCL_INFO(max_work_group_size); + PRINT_SYCL_INFO(max_work_item_dimensions); + const auto isizes = device.get_info(); + std::cout << " max_work_item_sizes: " << isizes[0] << " x " << isizes[1] << " x " << isizes[2] << std::endl; + + PRINT_SYCL_INFO(preferred_vector_width_int); + PRINT_SYCL_INFO(preferred_vector_width_long); + PRINT_SYCL_INFO(preferred_vector_width_half); + PRINT_SYCL_INFO(preferred_vector_width_float); + PRINT_SYCL_INFO(preferred_vector_width_double); + PRINT_SYCL_INFO(native_vector_width_int); + PRINT_SYCL_INFO(native_vector_width_long); + PRINT_SYCL_INFO(native_vector_width_half); + PRINT_SYCL_INFO(native_vector_width_float); + PRINT_SYCL_INFO(native_vector_width_double); + + PRINT_SYCL_INFO(max_mem_alloc_size); + PRINT_SYCL_INFO(global_mem_cache_line_size); + PRINT_SYCL_INFO(global_mem_cache_size); + PRINT_SYCL_INFO(global_mem_size); + PRINT_SYCL_INFO(local_mem_size); + + PRINT_SYCL_INFO(image_support); + PRINT_SYCL_INFO(max_read_image_args); + PRINT_SYCL_INFO(max_write_image_args); + PRINT_SYCL_INFO(image2d_max_height); + PRINT_SYCL_INFO(image2d_max_width); + PRINT_SYCL_INFO(image3d_max_height); + PRINT_SYCL_INFO(image3d_max_width); + PRINT_SYCL_INFO(image3d_max_depth); + PRINT_SYCL_INFO(image_max_buffer_size); + PRINT_SYCL_INFO(image_max_array_size); + PRINT_SYCL_INFO(max_samplers); + PRINT_SYCL_INFO(max_parameter_size); + + const auto domains = device.get_info(); + std::cout << " partition_affinity_domain:"; + for (auto& domain : domains) + { + switch(domain) + { + case sycl::info::partition_affinity_domain::numa : + std::cout << " numa"; + break; + case sycl::info::partition_affinity_domain::L1_cache : + std::cout << " L1_cache"; + break; + case sycl::info::partition_affinity_domain::L2_cache : + std::cout << " L2_cache"; + break; + case sycl::info::partition_affinity_domain::L3_cache : + std::cout << " L3_cache"; + break; + default : + break; + } + } + std::cout << std::endl; + + const auto hconfigs = device.get_info(); + std::cout << " half_fp_config:"; + for (auto& hconfig : hconfigs) + { + switch(hconfig) + { + case sycl::info::fp_config::fma : + std::cout << " fma"; + break; + case sycl::info::fp_config::denorm : + std::cout << " denorm"; + break; + case sycl::info::fp_config::inf_nan : + std::cout << " inf_nan"; + break; + case sycl::info::fp_config::round_to_nearest : + std::cout << " round_to_nearest"; + break; + case sycl::info::fp_config::round_to_zero : + std::cout << " round_to_zero"; + break; + case sycl::info::fp_config::round_to_inf : + std::cout << " round_to_inf"; + break; + case sycl::info::fp_config::correctly_rounded_divide_sqrt : + std::cout << " correctly_rounded_divide_sqrt"; + break; + case sycl::info::fp_config::soft_float : + std::cout << " soft_float"; + break; + default : + break; + } + } + std::cout << std::endl; + + const auto sconfigs = device.get_info(); + std::cout << " single_fp_config:"; + for (auto& sconfig : sconfigs) + { + switch(sconfig) + { + case sycl::info::fp_config::fma : + std::cout << " fma"; + break; + case sycl::info::fp_config::denorm : + std::cout << " denorm"; + break; + case sycl::info::fp_config::inf_nan : + std::cout << " inf_nan"; + break; + case sycl::info::fp_config::round_to_nearest : + std::cout << " round_to_nearest"; + break; + case sycl::info::fp_config::round_to_zero : + std::cout << " round_to_zero"; + break; + case sycl::info::fp_config::round_to_inf : + std::cout << " round_to_inf"; + break; + case sycl::info::fp_config::correctly_rounded_divide_sqrt : + std::cout << " correctly_rounded_divide_sqrt"; + break; + case sycl::info::fp_config::soft_float : + std::cout << " soft_float"; + break; + default : + break; + } + } + std::cout << std::endl; + + const auto dconfigs = device.get_info(); + std::cout << " double_fp_config:"; + for (auto& dconfig : dconfigs) + { + switch(dconfig) + { + case sycl::info::fp_config::fma : + std::cout << " fma"; + break; + case sycl::info::fp_config::denorm : + std::cout << " denorm"; + break; + case sycl::info::fp_config::inf_nan : + std::cout << " inf_nan"; + break; + case sycl::info::fp_config::round_to_nearest : + std::cout << " round_to_nearest"; + break; + case sycl::info::fp_config::round_to_zero : + std::cout << " round_to_zero"; + break; + case sycl::info::fp_config::round_to_inf : + std::cout << " round_to_inf"; + break; + case sycl::info::fp_config::correctly_rounded_divide_sqrt : + std::cout << " correctly_rounded_divide_sqrt"; + break; + case sycl::info::fp_config::soft_float : + std::cout << " soft_float"; + break; + default : + break; + } + } +*/ + std::cout << std::endl; + } +} + +void MlOptimiserSYCL::setupDevice() +{ +#ifdef USE_EXISTING_SYCL_DEVICE + _devAcc = new devSYCL(dynamic_cast(bundle->getSyclDevice())); +#else +// This will create separate device queue while the above is using existing queue + auto dev = dynamic_cast(bundle->getSyclDevice()); + auto qType = dev->getSyclQueueType(); + auto isAsync = dev->isAsyncQueue(); + auto computeIndex = dev->getComputeIndex(); + auto q = dev->getQueue(); + auto c = q->get_context(); + auto d = q->get_device(); + + #ifdef SYCL_EXT_INTEL_QUEUE_INDEX + if (computeIndex >= 0) + _devAcc = new devSYCL(c, d, computeIndex, qType, bundle->getSyclDevice()->getDeviceID(), isAsync); + else + #endif + _devAcc = new devSYCL(c, d, qType, bundle->getSyclDevice()->getDeviceID(), isAsync); + + _devAcc->setCardID(bundle->getSyclDevice()->getCardID()); + _devAcc->setStackID(bundle->getSyclDevice()->getStackID()); + _devAcc->setNumStack(bundle->getSyclDevice()->getNumStack()); + _devAcc->setSliceID(bundle->getSyclDevice()->getSliceID()); + _devAcc->setNumSlice(bundle->getSyclDevice()->getNumSlice()); +// _devAcc->printDeviceInfo(); +#endif +} + +void MlOptimiserSYCL::resetData() +{ + transformer1.clear(); + transformer2.clear(); + + for (int i = 0; i < baseMLO->mymodel.nr_classes; i++) + { + if (_useStream) + { + auto dev = dynamic_cast(_devAcc); + auto isAsync = dev->isAsyncQueue(); + auto computeIndex = dev->getComputeIndex(); + auto q = dev->getQueue(); + auto c = q->get_context(); + auto d = q->get_device(); + + #ifdef SYCL_EXT_INTEL_QUEUE_INDEX + if (computeIndex >= 0) + classStreams.push_back(new devSYCL(c, d, computeIndex, i, true, isAsync)); + #endif + else + classStreams.push_back(new devSYCL(c, d, i, true, isAsync)); + + classStreams.back()->setCardID(dynamic_cast(_devAcc)->getCardID()); + classStreams.back()->setStackID(dynamic_cast(_devAcc)->getStackID()); + classStreams.back()->setNumStack(dynamic_cast(_devAcc)->getNumStack()); + classStreams.back()->setSliceID(dynamic_cast(_devAcc)->getSliceID()); + classStreams.back()->setNumSlice(dynamic_cast(_devAcc)->getNumSlice()); + } + else + classStreams.push_back(_devAcc); + } +} + +void MlOptimiserSYCL::expectationOneParticle(unsigned long my_part_id, const int thread_id) +{ + AccPtrFactory ptrFactory(AccType::accCPU); + accDoExpectationOneParticle(this, my_part_id, thread_id, ptrFactory); +} + +void MlOptimiserSYCL::doThreadExpectationSomeParticles(const int thread_id) +{ + size_t first_ipart = 0, last_ipart = 0; + while (baseMLO->exp_ipart_ThreadTaskDistributor->getTasks(first_ipart, last_ipart)) + { + for (long unsigned ipart = first_ipart; ipart <= last_ipart; ipart++) + { + expectationOneParticle(baseMLO->exp_my_first_part_id + ipart, thread_id); + } + } +} +#endif // _SYCL_ENABLED diff --git a/src/acc/sycl/sycl_ml_optimiser.h b/src/acc/sycl/sycl_ml_optimiser.h new file mode 100644 index 000000000..63efa679d --- /dev/null +++ b/src/acc/sycl/sycl_ml_optimiser.h @@ -0,0 +1,100 @@ +// For the SYCL version, this is essentially a mix of +// cuda_ml_optimiser.h and cpu_ml_optimiser.h. +// Note the the SYCL implementation defines the floating point precision used +// for XFLOAT using ACC_DOUBLE_PRECISION (ACC_DOUBLE_PRECISION is also used +// for the equivalent purpose throughout the code) +#ifndef SYCL_ML_OPTIMISER_H_ +#define SYCL_ML_OPTIMISER_H_ + +#include +#include +#include +#include + +#include "src/mpi.h" +#include "src/ml_optimiser.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/sycl/mkl_fft.h" +#include "src/acc/sycl/sycl_benchmark_utils.h" + +#include "src/acc/acc_ml_optimiser.h" +#include "src/acc/acc_ptr.h" + +#include "src/acc/sycl/sycl_virtual_dev.h" + +class MlSyclDataBundle +{ +public: + //The SYCL accelerated projector set + std::vector< AccProjector > projectors; + + //The SYCL accelerated back-projector set + std::vector< AccBackprojector > backprojectors; + + //Used for precalculations of projection setup + bool generateProjectionPlanOnTheFly; + std::vector< AccProjectorPlan > coarseProjectionPlans; + + void setup(MlOptimiser *baseMLO); + void syncAllBackprojects() { _devAcc->waitAll(); } + virtualSYCL* getSyclDevice() { return _devAcc; } + + MlSyclDataBundle(virtualSYCL *dev); + ~MlSyclDataBundle(); + +private: + virtualSYCL *_devAcc; +}; + +class MlOptimiserSYCL +{ +public: + MlOptimiser *baseMLO; + MlSyclDataBundle *bundle; + + // transformer as holder for reuse of fftw_plans + FourierTransformer transformer; + + MklFFT transformer1; + MklFFT transformer2; + + bool refIs3D; + bool dataIs3D; + bool shiftsIs3D; + + int threadID; + + std::vector classStreams; + + static void checkDevices(); + static std::vector getDevices(const syclDeviceType select, const std::tuple syclOpt, const syclBackendType BE = syclBackendType::levelZero, const bool verbose = true); + + virtualSYCL* getSyclDevice() { return _devAcc; } + bool useStream() const { return _useStream; } + + //Used for precalculations of projection setup + bool generateProjectionPlanOnTheFly; + + void setupDevice(); + + void resetData(); + + void expectationOneParticle(unsigned long my_part_id, const int thread_id); + void doThreadExpectationSomeParticles(const int thread_id); + + void* getAllocator() + { + return nullptr; + }; + + MlOptimiserSYCL(MlOptimiser *baseMLOptimiser, MlSyclDataBundle *b, const bool isStream, const char *timing_fnm); + + ~MlOptimiserSYCL(); + +private: + virtualSYCL *_devAcc; + bool _useStream; +}; +#endif diff --git a/src/acc/sycl/sycl_projector.cpp b/src/acc/sycl/sycl_projector.cpp new file mode 100644 index 000000000..d6d8e4478 --- /dev/null +++ b/src/acc/sycl/sycl_projector.cpp @@ -0,0 +1,23 @@ +#ifdef _SYCL_ENABLED + +#include +#include +#include + +#include "src/acc/sycl/device_stubs.h" + +#include "src/acc/acc_ptr.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/acc_projector_plan.h" +#include "src/acc/sycl/sycl_benchmark_utils.h" +#include "src/acc/sycl/sycl_helper_functions.h" +#include "src/acc/utilities.h" +#include "src/acc/data_types.h" + +#include "src/acc/acc_helper_functions.h" +#include "src/acc/sycl/sycl_settings.h" + +#include "src/acc/acc_projector_impl.h" + +#endif diff --git a/src/acc/sycl/sycl_projector_plan.cpp b/src/acc/sycl/sycl_projector_plan.cpp new file mode 100644 index 000000000..eaab76880 --- /dev/null +++ b/src/acc/sycl/sycl_projector_plan.cpp @@ -0,0 +1,20 @@ +#ifdef _SYCL_ENABLED + +// Make sure we build for SYCL +#include "src/acc/sycl/device_stubs.h" + +#include "src/acc/settings.h" +#include "src/time.h" +#include "src/ml_optimiser.h" + +#include "src/acc/acc_ptr.h" +#include "src/acc/acc_projector.h" +#include "src/acc/acc_backprojector.h" +#include "src/acc/sycl/sycl_helper_functions.h" +#include "src/acc/data_types.h" +#include "src/acc/utilities.h" +#include "src/acc/acc_projector_plan.h" + +#include "src/acc/acc_projector_plan_impl.h" + +#endif diff --git a/src/acc/sycl/sycl_settings.h b/src/acc/sycl/sycl_settings.h new file mode 100644 index 000000000..f3ef8561b --- /dev/null +++ b/src/acc/sycl/sycl_settings.h @@ -0,0 +1,87 @@ +#ifndef SYCL_SETTINGS_H_ +#define SYCL_SETTINGS_H_ + +#include "src/acc/settings.h" + +// TODO: Need to optimized for specific SYCL device + +// GENERAL ----------------------------- +#define MAX_RESOL_SHARED_MEM 32 +#define BLOCK_SIZE 128 +// ------------------------------------- + + +// COARSE DIFF ------------------------- +#define PREFETCH_FRACTION_3D 2 //4 +#define PREFETCH_FRACTION_2D 1 //2 + +#define D2C_BLOCK_SIZE_2D 1024 //512 +#define D2C_EULERS_PER_BLOCK_2D 1 //4 +#define D2C_BLOCK_SIZE_REF3D 256 //128 +#define D2C_EULERS_PER_BLOCK_REF3D 4 //16 +#define D2C_BLOCK_SIZE_DATA3D 64 //64 +#define D2C_EULERS_PER_BLOCK_DATA3D 32 //32 + +#define D2C_CC_BLOCK_SIZE_2D 1024 //512 +#define D2C_CC_EULERS_PER_BLOCK_2D 1 //4 +#define D2C_CC_BLOCK_SIZE_REF3D 64 //128 +#define D2C_CC_EULERS_PER_BLOCK_REF3D 1 //16 +#define D2C_CC_BLOCK_SIZE_DATA3D 64 //64 +#define D2C_CC_EULERS_PER_BLOCK_DATA3D 32 //32 +// ------------------------------------- + +// FINE DIFF --------------------------- +#define D2F_BLOCK_SIZE_2D 256 //256 +#define D2F_CC_BLOCK_SIZE_2D 256 //256 +#define D2F_CHUNK_2D 7 + +#define D2F_BLOCK_SIZE_REF3D 256 //256 +#define D2F_CC_BLOCK_SIZE_REF3D 256 //256 +#define D2F_CHUNK_REF3D 7 + +#define D2F_BLOCK_SIZE_DATA3D 512 //512 +#define D2F_CC_BLOCK_SIZE_DATA3D 512 //512 +#define D2F_CHUNK_DATA3D 4 +// ------------------------------------- + + +// WAVG -------------------------------- +#define WAVG_BLOCK_SIZE_DATA3D 512 //512 +#define WAVG_BLOCK_SIZE_REF3D 512 //256 +#define WAVG_BLOCK_SIZE_2D 256 //256 +// ------------------------------------- + + +// MISC -------------------------------- +#define SUMW_BLOCK_SIZE 32 +#define SOFTMASK_BLOCK_SIZE 128 +#define CFTT_BLOCK_SIZE 128 +#define PROBRATIO_BLOCK_SIZE 128 +#define POWERCLASS_BLOCK_SIZE 128 +#define PROJDIFF_CHUNK_SIZE 14 + +// ------------------------------------- + +// RANDOMIZATION ----------------------- +#define RND_BLOCK_NUM 64 +#define RND_BLOCK_SIZE 32 +// ------------------------------------- + + +#define BACKPROJECTION4_BLOCK_SIZE 64 +#define BACKPROJECTION4_GROUP_SIZE 16 +#define BACKPROJECTION4_PREFETCH_COUNT 3 +#define BP_2D_BLOCK_SIZE 128 //128 +#define BP_REF3D_BLOCK_SIZE 256 //128 +#define BP_DATA3D_BLOCK_SIZE 512 //640 + +#define REF_GROUP_SIZE 3 // -- Number of references to be treated per block -- + // This applies to wavg and reduces global memory + // accesses roughly proportionally, but scales shared + // memory usage by allocating + // ( 6*REF_GROUP_SIZE + 4 ) * BLOCK_SIZE XFLOATS. // DEPRECATED + +#define RESTRICT + + +#endif /* CPU_SETTINGS_H_ */ diff --git a/src/acc/sycl/sycl_virtual_dev.h b/src/acc/sycl/sycl_virtual_dev.h new file mode 100644 index 000000000..a5027cf94 --- /dev/null +++ b/src/acc/sycl/sycl_virtual_dev.h @@ -0,0 +1,80 @@ +#ifndef _SYCL_VIRTUAL_DEVICE_RELION_H +#define _SYCL_VIRTUAL_DEVICE_RELION_H + +#include +#include +#include "src/acc/settings.h" + +enum class syclMallocType {shared, device, host}; +enum class syclBackendType {openCL, levelZero, CUDA, HIP, host}; +enum class syclDeviceType {gpu, cpu, fpga, host}; +enum class syclDeviceID {cpuAVX2, cpuAVX512, intelATS, intelARC, intelPVC}; +enum class syclQueueType {outOfOrder, inOrder, enableProfiling}; + +class virtualSYCL +{ +public: + virtual ~virtualSYCL() = default; + + virtual void* syclMalloc(const size_t bytes, const syclMallocType type, const char *name = "") = 0; + + virtual void syclFree(void *ptr) = 0; + + virtual void syclMemcpy(void *dest, const void *src, const size_t bytes) = 0; + virtual void syclMemcpyAfterWaitAll(void *dest, const void *src, const size_t bytes) = 0; + + virtual void syclMemset(void *ptr, const int value, const size_t bytes) = 0; + virtual void syclMemsetAfterWaitAll(void *ptr, const int value, const size_t bytes) = 0; + + virtual void syclFillInt32(void *ptr, const std::int32_t value, const size_t count) = 0; + virtual void syclFillInt32AfterWaitAll(void *ptr, const std::int32_t value, const size_t count) = 0; + virtual void syclFillUint32(void *ptr, const std::uint32_t value, const size_t count) = 0; + virtual void syclFillUint32AfterWaitAll(void *ptr, const std::uint32_t value, const size_t count) = 0; + + virtual void syclFillInt64(void *ptr, const std::int64_t value, const size_t count) = 0; + virtual void syclFillInt64AfterWaitAll(void *ptr, const std::int64_t value, const size_t count) = 0; + virtual void syclFillUint64(void *ptr, const std::uint64_t value, const size_t count) = 0; + virtual void syclFillUint64AfterWaitAll(void *ptr, const std::uint64_t value, const size_t count) = 0; + + virtual void syclFillXfloat(void *ptr, const XFLOAT value, const size_t count) = 0; + virtual void syclFillXfloatAfterWaitAll(void *ptr, const XFLOAT value, const size_t count) = 0; + virtual void syclFillRfloat(void *ptr, const RFLOAT value, const size_t count) = 0; + virtual void syclFillRfloatAfterWaitAll(void *ptr, const RFLOAT value, const size_t count) = 0; + + virtual void syclFillFloat(void *ptr, const float value, const size_t count) = 0; + virtual void syclFillFloatAfterWaitAll(void *ptr, const float value, const size_t count) = 0; + virtual void syclFillDouble(void *ptr, const double value, const size_t count) = 0; + virtual void syclFillDoubleAfterWaitAll(void *ptr, const double value, const size_t count) = 0; + + virtual void syclPrefetch(void *ptr, const size_t bytes) = 0; + virtual void syclPrefetchAfterWaitAll(void *ptr, const size_t bytes) = 0; + + virtual bool isAsyncQueue() const = 0; + virtual bool canSupportFP64() const = 0; + virtual syclQueueType getSyclQueueType() const = 0; + + virtual std::string getName() = 0; + virtual int getDeviceID() const = 0; + virtual void setDeviceID(int id) = 0; + virtual int getCardID() const = 0; + virtual void setCardID(int id) = 0; + virtual int getStackID() const = 0; + virtual void setStackID(int id) = 0; + virtual int getNumStack() const = 0; + virtual void setNumStack(int id) = 0; + virtual int getSliceID() const = 0; + virtual void setSliceID(int id) = 0; + virtual int getNumSlice() const = 0; + virtual void setNumSlice(int id) = 0; + + virtual void waitAll() = 0; + + virtual void printDeviceInfo(bool printAll) = 0; +}; + +struct virtualSyclPtr +{ + virtualSYCL *dev; +}; + +#endif diff --git a/src/acc/utilities.h b/src/acc/utilities.h index e3d4ebffd..0f1cd95ae 100644 --- a/src/acc/utilities.h +++ b/src/acc/utilities.h @@ -9,11 +9,34 @@ #include "src/acc/cuda/cuda_kernels/wavg.cuh" #include "src/acc/cuda/cuda_kernels/diff2.cuh" #include "src/acc/cuda/cuda_fft.h" +using deviceStream_t = cudaStream_t; +#elif _HIP_ENABLED +#include "src/acc/hip/hip_kernels/helper.h" +#include "src/acc/hip/hip_kernels/wavg.h" +#include "src/acc/hip/hip_kernels/diff2.h" +#include "src/acc/hip/hip_fft.h" +using deviceStream_t = hipStream_t; +#elif _SYCL_ENABLED +#include +#include "src/acc/sycl/sycl_virtual_dev.h" +#include "src/acc/sycl/sycl_kernels/helper.h" +#include "src/acc/sycl/sycl_kernels/helper_gpu.h" +#include "src/acc/sycl/sycl_kernels/wavg.h" +#include "src/acc/sycl/sycl_kernels/diff2.h" +#include "src/acc/sycl/device_stubs.h" #else +#include +#include +#include "src/acc/cpu/device_stubs.h" #include "src/acc/cpu/cpu_kernels/helper.h" #include "src/acc/cpu/cpu_kernels/wavg.h" #include "src/acc/cpu/cpu_kernels/diff2.h" #endif +#ifdef USE_IPP +#include +#include "ipps.h" +#include "ipps_l.h" +#endif void dump_array(char *name, bool *ptr, size_t size); void dump_array(char *name, int *ptr, size_t size); @@ -33,12 +56,29 @@ namespace AccUtilities template static void multiply(int block_size, AccDataTypes::Image &ptr, T value) { -#ifdef _CUDA_ENABLED int BSZ = ( (int) ceilf(( float)ptr.getSize() /(float)block_size)); +#ifdef _CUDA_ENABLED CudaKernels::cuda_kernel_multi<<>>( ptr(), value, ptr.getSize()); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_multi), dim3(BSZ), dim3(block_size), 0, ptr.getStream(), + ptr(), + value, + ptr.getSize()); +#elif _SYCL_ENABLED + if (ptr.getAccType() == accSYCL) + syclGpuKernels::sycl_gpu_kernel_multi( + ptr.getDevicePtr(), + value, + ptr.getSize(), + ptr.getStream()); + else + syclKernels::sycl_kernel_multi( + ptr.getHostPtr(), + value, + ptr.getSize()); #else CpuKernels::cpu_kernel_multi( ptr(), @@ -48,10 +88,27 @@ int BSZ = ( (int) ceilf(( float)ptr.getSize() /(float)block_size)); } template -static void multiply(int MultiBsize, int block_size, cudaStream_t stream, T *array, T value, size_t size) +static void multiply(int MultiBsize, int block_size, deviceStream_t stream, T *array, T value, size_t size) { #ifdef _CUDA_ENABLED -CudaKernels::cuda_kernel_multi<<>>( + CudaKernels::cuda_kernel_multi<<>>( + array, + value, + size); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_multi), dim3(MultiBsize), dim3(block_size), 0, stream, + array, + value, + size); +#elif _SYCL_ENABLED + if (stream != nullptr) + syclGpuKernels::sycl_gpu_kernel_multi( + array, + value, + size, + stream); + else + syclKernels::sycl_kernel_multi( array, value, size); @@ -63,6 +120,44 @@ CudaKernels::cuda_kernel_multi<<>>( #endif } +template +static void add(int block_size, deviceStream_t stream, T *array, T value, size_t size) +{ + size_t MultiBsize = ( (size_t) ceilf((float)size/(float)BLOCK_SIZE)); +#ifdef _CUDA_ENABLED + CudaKernels::cuda_kernel_add<<>>( + array, + value, + size + ); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_add), dim3(MultiBsize), dim3(block_size), 0, stream, + array, + value, + size); +#elif _SYCL_ENABLED + if (stream != nullptr) + syclGpuKernels::sycl_gpu_kernel_add( + array, + value, + size, + stream + ); + else + syclKernels::sycl_kernel_add( + array, + value, + size + ); +#else + CpuKernels::cpu_kernel_add( + array, + value, + size + ); +#endif +} + template static void translate(int block_size, AccDataTypes::Image &in, @@ -73,9 +168,9 @@ static void translate(int block_size, { if(in.getAccPtr()==out.getAccPtr()) CRITICAL(ERRUNSAFEOBJECTREUSE); + #ifdef _CUDA_ENABLED int BSZ = ( (int) ceilf(( float)in.getxyz() /(float)block_size)); - if (in.is3D()) { CudaKernels::cuda_kernel_translate3D<<>>( @@ -100,6 +195,81 @@ else dx, dy); } +#elif _HIP_ENABLED +int BSZ = ( (int) ceilf(( float)in.getxyz() /(float)block_size)); +if (in.is3D()) +{ + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_translate3D), dim3(BSZ), dim3(block_size), 0, in.getStream(), + in(), + out(), + in.getxyz(), + in.getx(), + in.gety(), + in.getz(), + dx, + dy, + dz); +} +else +{ + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_translate2D), dim3(BSZ), dim3(block_size), 0, in.getStream(), + in(), + out(), + in.getxyz(), + in.getx(), + in.gety(), + dx, + dy); +} +#elif _SYCL_ENABLED +if (in.is3D()) +{ + if (in.getAccType() == accSYCL && out.getAccType() == accSYCL) + syclGpuKernels::sycl_gpu_translate3D( + in.getDevicePtr(), + out.getDevicePtr(), + in.getxyz(), + in.getx(), + in.gety(), + in.getz(), + dx, + dy, + dz, + in.getStream()); + else + syclKernels::sycl_translate3D( + in.getHostPtr(), + out.getHostPtr(), + in.getxyz(), + in.getx(), + in.gety(), + in.getz(), + dx, + dy, + dz); +} +else +{ + if (in.getAccType() == accSYCL && out.getAccType() == accSYCL) + syclGpuKernels::sycl_gpu_translate2D( + in.getDevicePtr(), + out.getDevicePtr(), + in.getxyz(), + in.getx(), + in.gety(), + dx, + dy, + in.getStream()); + else + syclKernels::sycl_translate2D( + in.getHostPtr(), + out.getHostPtr(), + in.getxyz(), + in.getx(), + in.gety(), + dx, + dy); +} #else if (in.is3D()) { @@ -128,88 +298,237 @@ else #endif } +#if defined ALTCPU || defined _SYCL_ENABLED +template +static size_t filterGreaterZeroOnHost(T *in, const size_t sz, T *out) +{ + size_t outindex = 0; + for(size_t i=0; i (T)0.0) + out[outindex++] = in[i]; + + return outindex; +} + +#ifdef USE_IPP + #ifdef ACC_DOUBLE_PRECISION +static void sortDoubleIPP(double *out, const size_t arr_size) +{ + if (arr_size <= static_cast(std::numeric_limits::max())) + { + int new_size = static_cast(arr_size); + int pBufferSize; + if (ippStsNoErr == ippsSortRadixGetBufferSize(new_size, ipp64f, &pBufferSize)) + { + Ipp8u *pBuffer = ippsMalloc_8u(pBufferSize); + IppStatus ret = ippsSortRadixAscend_64f_I(out, new_size, pBuffer); + ippsFree(pBuffer); + } + } + else + { + IppSizeL pBufferSize; + if (ippStsNoErr == ippsSortRadixGetBufferSize_L(arr_size, ipp64f, &pBufferSize)) + { + Ipp8u *pBuffer = ippsMalloc_8u_L(pBufferSize); + IppStatus ret = ippsSortRadixAscend_64f_I_L(out, arr_size, pBuffer); + ippsFree(pBuffer); + } + } +} + #else +static void sortFloatIPP(float *out, const size_t arr_size) +{ + if (arr_size <= static_cast(std::numeric_limits::max())) + { + int new_size = static_cast(arr_size); + int pBufferSize; + if (ippStsNoErr == ippsSortRadixGetBufferSize(new_size, ipp32f, &pBufferSize)) + { + Ipp8u *pBuffer = ippsMalloc_8u(pBufferSize); + IppStatus ret = ippsSortRadixAscend_32f_I(out, new_size, pBuffer); + ippsFree(pBuffer); + } + } + else + { + IppSizeL pBufferSize; + if (ippStsNoErr == ippsSortRadixGetBufferSize_L(arr_size, ipp32f, &pBufferSize)) + { + Ipp8u *pBuffer = ippsMalloc_8u_L(pBufferSize); + IppStatus ret = ippsSortRadixAscend_32f_I_L(out, arr_size, pBuffer); + ippsFree(pBuffer); + } + } +} + #endif // End of #ifdef ACC_DOUBLE_PRECISION +#endif // End of #ifdef USE_IPP + +template +static void sortOnHost(T *in, const size_t sz, T *out) +{ + memcpy(out, in, sz * sizeof(T)); + #ifdef USE_IPP + #ifdef ACC_DOUBLE_PRECISION + if (std::is_same::value && sz > 256) + sortDoubleIPP(out, sz); + #else + if (std::is_same::value && sz > 256) + sortFloatIPP(out, sz); + #endif + else + #endif + std::sort(out, out+sz); +} + +template +static void scanOnHost(T *in, const size_t sz, T *out) +{ + T sum = 0.0; + #if _OPENMP >= 201811 // For OpenMP 5.0 and later + #pragma omp simd reduction(inscan, +:sum) + #endif + for(size_t i=0; i= 201811 // For OpenMP 5.0 and later + #pragma omp scan inclusive(sum) + #endif + out[i] = sum; + } +} + +template< typename T> +void InitComplexValueOnHost(T *data, const XFLOAT value, const size_t sz) +{ + for(size_t i=0; i +void InitValueOnHost(T *data, const T value, const size_t sz) +{ + for (size_t i=0; i < sz; i++) + data[i] = value; +} +#endif // End of defined ALTCPU || defined _SYCL_ENABLED + template static T getSumOnDevice(AccPtr &ptr) { -#ifdef _CUDA_ENABLED -return CudaKernels::getSumOnDevice(ptr); -#else -#ifdef DEBUG_CUDA +#if defined DEBUG_CUDA || defined DEBUG_HIP if (ptr.getSize() == 0) printf("DEBUG_ERROR: getSumOnDevice called with pointer of zero size.\n"); if (ptr.getHostPtr() == NULL) printf("DEBUG_ERROR: getSumOnDevice called with null device pointer.\n"); #endif - size_t size = ptr.getSize(); - T sum = 0; - for (size_t i=0; i(ptr); +#elif _HIP_ENABLED + return HipKernels::getSumOnDevice(ptr); +#elif _SYCL_ENABLED + if (ptr.getAccType() == accSYCL) + return syclGpuKernels::getSumOnDevice(ptr.getDevicePtr(), ptr.getSize(), ptr.getStream()); + else + return syclKernels::getSum(ptr.getHostPtr(), ptr.getSize()); +#else + return CpuKernels::getSum(ptr.getHostPtr(), ptr.getSize()); #endif } template static T getMinOnDevice(AccPtr &ptr) { -#ifdef _CUDA_ENABLED -return CudaKernels::getMinOnDevice(ptr); -#else -#ifdef DEBUG_CUDA +#if defined DEBUG_CUDA || defined DEBUG_HIP if (ptr.getSize() == 0) printf("DEBUG_ERROR: getMinOnDevice called with pointer of zero size.\n"); if (ptr.getHostPtr() == NULL) printf("DEBUG_ERROR: getMinOnDevice called with null device pointer.\n"); #endif - return CpuKernels::getMin(ptr(), ptr.getSize()); +#ifdef _CUDA_ENABLED + return CudaKernels::getMinOnDevice(ptr); +#elif _HIP_ENABLED + return HipKernels::getMinOnDevice(ptr); +#elif _SYCL_ENABLED + if (ptr.getAccType() == accSYCL) + return syclGpuKernels::getMinOnDevice(ptr.getDevicePtr(), ptr.getSize(), ptr.getStream()); + else + return syclKernels::getMin(ptr.getHostPtr(), ptr.getSize()); +#else + return CpuKernels::getMin(ptr.getHostPtr(), ptr.getSize()); #endif } template static T getMaxOnDevice(AccPtr &ptr) { -#ifdef _CUDA_ENABLED -return CudaKernels::getMaxOnDevice(ptr); -#else -#ifdef DEBUG_CUDA +#if defined DEBUG_CUDA || defined DEBUG_HIP if (ptr.getSize() == 0) printf("DEBUG_ERROR: getMaxOnDevice called with pointer of zero size.\n"); if (ptr.getHostPtr() == NULL) printf("DEBUG_ERROR: getMaxOnDevice called with null device pointer.\n"); #endif - return CpuKernels::getMax(ptr(), ptr.getSize()); +#ifdef _CUDA_ENABLED + return CudaKernels::getMaxOnDevice(ptr); +#elif _HIP_ENABLED + return HipKernels::getMaxOnDevice(ptr); +#elif _SYCL_ENABLED + if (ptr.getAccType() == accSYCL) + return syclGpuKernels::getMaxOnDevice(ptr.getDevicePtr(), ptr.getSize(), ptr.getStream()); + else + return syclKernels::getMax(ptr.getHostPtr(), ptr.getSize()); +#else + return CpuKernels::getMax(ptr.getHostPtr(), ptr.getSize()); #endif } template static std::pair getArgMinOnDevice(AccPtr &ptr) { -#ifdef _CUDA_ENABLED -return CudaKernels::getArgMinOnDevice(ptr); -#else -#ifdef DEBUG_CUDA +#if defined DEBUG_CUDA || defined DEBUG_HIP if (ptr.getSize() == 0) printf("DEBUG_ERROR: getArgMinOnDevice called with pointer of zero size.\n"); if (ptr.getHostPtr() == NULL) printf("DEBUG_ERROR: getArgMinOnDevice called with null device pointer.\n"); #endif - return CpuKernels::getArgMin(ptr(), ptr.getSize()); +#ifdef _CUDA_ENABLED + return CudaKernels::getArgMinOnDevice(ptr); +#elif _HIP_ENABLED + return HipKernels::getArgMinOnDevice(ptr); +#elif _SYCL_ENABLED + if (ptr.getAccType() == accSYCL) + return syclGpuKernels::getArgMinOnDevice(ptr.getDevicePtr(), ptr.getSize(), ptr.getStream()); + else + return syclKernels::getArgMin(ptr.getHostPtr(), ptr.getSize()); +#else + return CpuKernels::getArgMin(ptr.getHostPtr(), ptr.getSize()); #endif } template static std::pair getArgMaxOnDevice(AccPtr &ptr) { -#ifdef _CUDA_ENABLED -return CudaKernels::getArgMaxOnDevice(ptr); -#else -#ifdef DEBUG_CUDA +#if defined DEBUG_CUDA || defined DEBUG_HIP if (ptr.getSize() == 0) printf("DEBUG_ERROR: getArgMaxOnDevice called with pointer of zero size.\n"); if (ptr.getHostPtr() == NULL) printf("DEBUG_ERROR: getArgMaxOnDevice called with null device pointer.\n"); #endif - return CpuKernels::getArgMax(ptr(), ptr.getSize()); +#ifdef _CUDA_ENABLED + return CudaKernels::getArgMaxOnDevice(ptr); +#elif _HIP_ENABLED + return HipKernels::getArgMaxOnDevice(ptr); +#elif _SYCL_ENABLED + if (ptr.getAccType() == accSYCL) + return syclGpuKernels::getArgMaxOnDevice(ptr.getDevicePtr(), ptr.getSize(), ptr.getStream()); + else + return syclKernels::getArgMax(ptr.getHostPtr(), ptr.getSize()); +#else + return CpuKernels::getArgMax(ptr.getHostPtr(), ptr.getSize()); #endif } @@ -217,30 +536,18 @@ template static int filterGreaterZeroOnDevice(AccPtr &in, AccPtr &out) { #ifdef _CUDA_ENABLED -CudaKernels::MoreThanCubOpt moreThanOpt(0.); + CudaKernels::MoreThanCubOpt moreThanOpt(0.); return CudaKernels::filterOnDevice(in, out, moreThanOpt); +#elif _HIP_ENABLED + HipKernels::MoreThanCubOpt moreThanOpt(0.); + return HipKernels::filterOnDevice(in, out, moreThanOpt); +#elif _SYCL_ENABLED + if (in.getAccType() == accSYCL) + return syclGpuKernels::filterGreaterZeroOnDevice(in.getDevicePtr(), in.getSize(), out.getDevicePtr(), in.getStream()); + else + return filterGreaterZeroOnHost(in.getHostPtr(), in.getSize(), out.getHostPtr()); #else - size_t arr_size = in.getSize(); - size_t filt_size = 0; - size_t outindex = 0; - // Find how many entries the output array will have - for(size_t i=0; i (T)0.0) - filt_size++; - } -#ifdef DEBUG_CUDA - if (filt_size==0) - ACC_PTR_DEBUG_FATAL("filterGreaterZeroOnDevice - No filtered values greater than 0.\n"); -#endif - out.resizeHost(filt_size); - // Now populate output array - for(size_t i=0; i (T)0.0) { - out[outindex] = in[i]; - outindex++; - } - return filt_size; + return filterGreaterZeroOnHost(in.getHostPtr(), in.getSize(), out.getHostPtr()); #endif } @@ -248,15 +555,16 @@ template static void sortOnDevice(AccPtr &in, AccPtr &out) { #ifdef _CUDA_ENABLED -CudaKernels::sortOnDevice(in, out); + CudaKernels::sortOnDevice(in, out); +#elif _HIP_ENABLED + HipKernels::sortOnDevice(in, out); +#elif _SYCL_ENABLED + if (in.getAccType() == accSYCL) + syclGpuKernels::sortOnDevice(in.getDevicePtr(), in.getSize(), out.getDevicePtr(), in.getStream()); + else + sortOnHost(in.getHostPtr(), in.getSize(), out.getHostPtr()); #else - //TODO - convert ACCPTR to store data as vector so we don't need to make - //an extra copies here. For now, nasty hack - size_t arr_size = in.getSize(); - std::vector sortVector(in(), in() + in.getSize()); - sort(sortVector.begin(), sortVector.end()); - for (size_t i=0; i < arr_size; i++) - out[i] = sortVector[i]; + sortOnHost(in.getHostPtr(), in.getSize(), out.getHostPtr()); #endif } @@ -264,15 +572,16 @@ template static void scanOnDevice(AccPtr &in, AccPtr &out) { #ifdef _CUDA_ENABLED -CudaKernels::scanOnDevice(in, out); + CudaKernels::scanOnDevice(in, out); +#elif _HIP_ENABLED + HipKernels::scanOnDevice(in, out); +#elif _SYCL_ENABLED + if (in.getAccType() == accSYCL) + syclGpuKernels::scanOnDevice(in.getDevicePtr(), in.getSize(), out.getDevicePtr(), in.getStream()); + else + scanOnHost(in.getHostPtr(), in.getSize(), out.getHostPtr()); #else - T sum = 0.0; - size_t arr_size = in.getSize(); - for(size_t i=0; i(in.getHostPtr(), in.getSize(), out.getHostPtr()); #endif } @@ -324,7 +633,8 @@ void powerClass(int in_gridSize, int ydim, int zdim, int res_limit, - XFLOAT *g_highres_Xi2) + XFLOAT *g_highres_Xi2, + deviceStream_t stream) { #ifdef _CUDA_ENABLED dim3 grid_size(in_gridSize); @@ -337,6 +647,28 @@ dim3 grid_size(in_gridSize); zdim, res_limit, g_highres_Xi2); +#elif _HIP_ENABLED +dim3 grid_size(in_gridSize); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_powerClass), dim3(grid_size), dim3(in_blocksize), 0, stream, g_image, + g_spectrum, + image_size, + spectrum_size, + xdim, + ydim, + zdim, + res_limit, + g_highres_Xi2); +#elif _SYCL_ENABLED + syclKernels::powerClass(in_gridSize, + g_image, + g_spectrum, + image_size, + spectrum_size, + xdim, + ydim, + zdim, + res_limit, + g_highres_Xi2); #else CpuKernels::powerClass(in_gridSize, g_image, @@ -354,16 +686,24 @@ dim3 grid_size(in_gridSize); template void acc_make_eulers_2D(int grid_size, int block_size, - cudaStream_t stream, + deviceStream_t stream, XFLOAT *alphas, XFLOAT *eulers, unsigned long orientation_num) { #ifdef _CUDA_ENABLED -cuda_kernel_make_eulers_2D<<>>( + cuda_kernel_make_eulers_2D<<>>( alphas, eulers, orientation_num); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_make_eulers_2D), dim3(grid_size), dim3(block_size), 0, stream, + alphas, + eulers, + orientation_num); +#elif _SYCL_ENABLED + syclKernels::sycl_kernel_make_eulers_2D(grid_size, block_size, + alphas, eulers, orientation_num); #else CpuKernels::cpu_kernel_make_eulers_2D(grid_size, block_size, alphas, eulers, orientation_num); @@ -372,7 +712,7 @@ cuda_kernel_make_eulers_2D<<>>( template void acc_make_eulers_3D(int grid_size, int block_size, - cudaStream_t stream, + deviceStream_t stream, XFLOAT *alphas, XFLOAT *betas, XFLOAT *gammas, @@ -390,6 +730,24 @@ cuda_kernel_make_eulers_3D<<>>( orientation_num, L, R); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_make_eulers_3D), dim3(grid_size), dim3(block_size), 0, stream, + alphas, + betas, + gammas, + eulers, + orientation_num, + L, + R); +#elif _SYCL_ENABLED + syclKernels::sycl_kernel_make_eulers_3D(grid_size, block_size, + alphas, + betas, + gammas, + eulers, + orientation_num, + L, + R); #else CpuKernels::cpu_kernel_make_eulers_3D(grid_size, block_size, alphas, @@ -402,26 +760,32 @@ cuda_kernel_make_eulers_3D<<>>( #endif } -#ifdef _CUDA_ENABLED -#define INIT_VALUE_BLOCK_SIZE 512 +#if defined _CUDA_ENABLED || defined _HIP_ENABLED + #define INIT_VALUE_BLOCK_SIZE 512 #endif template< typename T> void InitComplexValue(AccPtr &data, XFLOAT value) { #ifdef _CUDA_ENABLED -int grid_size = ceil((float)(data.getSize())/(float)INIT_VALUE_BLOCK_SIZE); + int grid_size = ceil((float)(data.getSize())/(float)INIT_VALUE_BLOCK_SIZE); cuda_kernel_init_complex_value<<< grid_size, INIT_VALUE_BLOCK_SIZE, 0, data.getStream() >>>( ~data, value, data.getSize(), INIT_VALUE_BLOCK_SIZE); +#elif _HIP_ENABLED + int grid_size = ceil((float)(data.getSize())/(float)INIT_VALUE_BLOCK_SIZE); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_init_complex_value), dim3(grid_size), dim3(INIT_VALUE_BLOCK_SIZE), 0, data.getStream() , + ~data, + value, + data.getSize(), INIT_VALUE_BLOCK_SIZE); +#elif _SYCL_ENABLED + if (data.getAccType() == accSYCL) + syclGpuKernels::InitValue(&((~data)[0].x), value, 2*data.getSize(), data.getStream()); + else + InitComplexValueOnHost(data.getHostPtr(), value, data.getSize()); #else - size_t Size = data.getSize(); - for(size_t i=0; i(data.getHostPtr(), value, data.getSize()); #endif } @@ -429,17 +793,28 @@ template< typename T> void InitValue(AccPtr &data, T value) { #ifdef _CUDA_ENABLED -int grid_size = ceil((float)data.getSize()/(float)INIT_VALUE_BLOCK_SIZE); + int grid_size = ceil((float)data.getSize()/(float)INIT_VALUE_BLOCK_SIZE); cuda_kernel_init_value<<< grid_size, INIT_VALUE_BLOCK_SIZE, 0, data.getStream() >>>( ~data, value, data.getSize(), INIT_VALUE_BLOCK_SIZE); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + int grid_size = ceil((float)data.getSize()/(float)INIT_VALUE_BLOCK_SIZE); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_init_value), dim3(grid_size), dim3(INIT_VALUE_BLOCK_SIZE), 0, data.getStream() , + ~data, + value, + data.getSize(), + INIT_VALUE_BLOCK_SIZE); + LAUNCH_HANDLE_ERROR(hipGetLastError()); +#elif _SYCL_ENABLED + if (data.getAccType() == accSYCL) + syclGpuKernels::InitValue(data.getDevicePtr(), value, data.getSize(), data.getStream()); + else + InitValueOnHost(data.getHostPtr(), value, data.getSize()); #else - size_t Size = data.getSize(); - for (size_t i=0; i < Size; i++) - data[i] = value; + InitValueOnHost(data.getHostPtr(), value, data.getSize()); #endif } @@ -447,22 +822,33 @@ template< typename T> void InitValue(AccPtr &data, T value, size_t Size) { #ifdef _CUDA_ENABLED -int grid_size = ceil((float)Size/(float)INIT_VALUE_BLOCK_SIZE); + int grid_size = ceil((float)Size/(float)INIT_VALUE_BLOCK_SIZE); cuda_kernel_init_value<<< grid_size, INIT_VALUE_BLOCK_SIZE, 0, data.getStream() >>>( ~data, value, Size, INIT_VALUE_BLOCK_SIZE); +#elif _HIP_ENABLED + int grid_size = ceil((float)Size/(float)INIT_VALUE_BLOCK_SIZE); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_init_value), dim3(grid_size), dim3(INIT_VALUE_BLOCK_SIZE), 0, data.getStream() , + ~data, + value, + Size, + INIT_VALUE_BLOCK_SIZE); +#elif _SYCL_ENABLED + if (data.getAccType() == accSYCL) + syclGpuKernels::InitValue(data.getDevicePtr(), value, Size, data.getStream()); + else + InitValueOnHost(data.getHostPtr(), value, Size); #else - for (size_t i=0; i < Size; i++) - data[i] = value; + InitValueOnHost(data.getHostPtr(), value, Size); #endif } void initOrientations(AccPtr &pdfs, AccPtr &pdf_orientation, AccPtr &pdf_orientation_zeros); void centerFFT_2D(int grid_size, int batch_size, int block_size, - cudaStream_t stream, + deviceStream_t stream, XFLOAT *img_in, size_t image_size, int xdim, @@ -480,7 +866,7 @@ void centerFFT_2D(int grid_size, int batch_size, int block_size, int yshift); void centerFFT_3D(int grid_size, int batch_size, int block_size, - cudaStream_t stream, + deviceStream_t stream, XFLOAT *img_in, size_t image_size, int xdim, @@ -490,10 +876,9 @@ void centerFFT_3D(int grid_size, int batch_size, int block_size, int yshift, int zshift); - template void frequencyPass(int grid_size, int block_size, - cudaStream_t stream, + deviceStream_t stream, ACCCOMPLEX *A, long int ori_size, size_t Xdim, @@ -506,7 +891,7 @@ void frequencyPass(int grid_size, int block_size, size_t image_size) { #ifdef _CUDA_ENABLED -dim3 blocks(grid_size); + dim3 blocks(grid_size); cuda_kernel_frequencyPass<<>>( A, ori_size, @@ -518,6 +903,31 @@ dim3 blocks(grid_size); edge_high, angpix, image_size); +#elif _HIP_ENABLED + dim3 blocks(grid_size); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_frequencyPass), dim3(blocks), dim3(block_size), 0, stream, + A, + ori_size, + Xdim, + Ydim, + Zdim, + edge_low, + edge_width, + edge_high, + angpix, + image_size); +#elif _SYCL_ENABLED + syclKernels::kernel_frequencyPass(grid_size, block_size, + A, + ori_size, + Xdim, + Ydim, + Zdim, + edge_low, + edge_width, + edge_high, + angpix, + image_size); #else CpuKernels::kernel_frequencyPass(grid_size, block_size, A, @@ -553,7 +963,7 @@ void kernel_wavg( XFLOAT weight_norm, XFLOAT significant_weight, XFLOAT part_scale, - cudaStream_t stream) + deviceStream_t stream) { #ifdef _CUDA_ENABLED //We only want as many blocks as there are chunks of orientations to be treated @@ -579,6 +989,49 @@ void kernel_wavg( weight_norm, significant_weight, part_scale); +#elif _HIP_ENABLED + dim3 block_dim = orientation_num;//ceil((float)orientation_num/(float)REF_GROUP_SIZE); + + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_wavg), dim3(block_dim), dim3(block_sz), (3*block_sz+9)*sizeof(XFLOAT), stream, + g_eulers, + projector, + image_size, + orientation_num, + g_img_real, + g_img_imag, + g_trans_x, + g_trans_y, + g_trans_z, + g_weights, + g_ctfs, + g_wdiff2s_parts, + g_wdiff2s_AA, + g_wdiff2s_XA, + translation_num, + weight_norm, + significant_weight, + part_scale); +#elif _SYCL_ENABLED + syclKernels::wavg( + g_eulers, + projector, + image_size, + orientation_num, + g_img_real, + g_img_imag, + g_trans_x, + g_trans_y, + g_trans_z, + g_weights, + g_ctfs, + g_wdiff2s_parts, + g_wdiff2s_AA, + g_wdiff2s_XA, + translation_num, + weight_norm, + significant_weight, + part_scale, + stream); #else if (DATA3D) { @@ -642,11 +1095,11 @@ void diff2_coarse( XFLOAT *g_diff2s, unsigned long translation_num, unsigned long image_size, - cudaStream_t stream + deviceStream_t stream ) { #ifdef _CUDA_ENABLED -cuda_kernel_diff2_coarse + cuda_kernel_diff2_coarse <<>>( g_eulers, trans_x, @@ -659,10 +1112,8 @@ cuda_kernel_diff2_coarse( - grid_size, +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_diff2_coarse), dim3(grid_size), dim3(block_size), 0, stream, g_eulers, trans_x, trans_y, @@ -673,11 +1124,9 @@ cuda_kernel_diff2_coarse( + image_size); +#elif _SYCL_ENABLED + syclKernels::diff2_coarse( grid_size, g_eulers, trans_x, @@ -689,9 +1138,11 @@ cuda_kernel_diff2_coarse( + image_size, + stream + ); +#else + CpuKernels::diff2_coarse( grid_size, g_eulers, trans_x, @@ -703,8 +1154,8 @@ cuda_kernel_diff2_coarse <<>>( g_eulers, @@ -743,6 +1194,38 @@ dim3 CCblocks(grid_size,translation_num); translation_num, image_size, exp_local_sqrtXi2); +#elif _HIP_ENABLED + dim3 CCblocks(grid_size,translation_num); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_diff2_CC_coarse), dim3(CCblocks), dim3(block_size), 0, stream, + g_eulers, + g_imgs_real, + g_imgs_imag, + g_trans_x, + g_trans_y, + g_trans_z, + projector, + g_corr_img, + g_diff2s, + translation_num, + image_size, + exp_local_sqrtXi2); +#elif _SYCL_ENABLED + syclKernels::diff2_CC_coarse( + grid_size, + g_eulers, + g_imgs_real, + g_imgs_imag, + g_trans_x, + g_trans_y, + g_trans_z, + projector, + g_corr_img, + g_diff2s, + translation_num, + image_size, + exp_local_sqrtXi2, + stream + ); #else if (DATA3D) CpuKernels::diff2_CC_coarse_3D( @@ -798,12 +1281,12 @@ void diff2_fine( unsigned long *d_trans_idx, unsigned long *d_job_idx, unsigned long *d_job_num, - cudaStream_t stream + deviceStream_t stream ) { #ifdef _CUDA_ENABLED -dim3 block_dim = grid_size; - cuda_kernel_diff2_fine + dim3 block_dim = grid_size; + cuda_kernel_diff2_fine <<>>( g_eulers, g_imgs_real, @@ -823,6 +1306,50 @@ dim3 block_dim = grid_size; d_trans_idx, d_job_idx, d_job_num); +#elif _HIP_ENABLED + dim3 block_dim = grid_size; + int dynamic_mem_size = chunk_sz * ((block_sz+warpSize-1)/warpSize) * sizeof(XFLOAT); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_diff2_fine), dim3(block_dim), dim3(block_size), dynamic_mem_size, stream, + g_eulers, + g_imgs_real, + g_imgs_imag, + trans_x, + trans_y, + trans_z, + projector, + g_corr_img, // in these non-CC kernels this is effectively an adjusted MinvSigma2 + g_diff2s, + image_size, + sum_init, + orientation_num, + translation_num, + todo_blocks, //significant_num, + d_rot_idx, + d_trans_idx, + d_job_idx, + d_job_num); +#elif _SYCL_ENABLED + syclKernels::diff2_fine( + grid_size, + g_eulers, + g_imgs_real, + g_imgs_imag, + trans_x, + trans_y, + trans_z, + projector, + g_corr_img, // in these non-CC kernels this is effectively an adjusted MinvSigma2 + g_diff2s, + image_size, + sum_init, + orientation_num, + translation_num, + todo_blocks, //significant_num, + d_rot_idx, + d_trans_idx, + d_job_idx, + d_job_num, + stream); #else // TODO - make use of orientation_num, translation_num,todo_blocks on // CPU side if CUDA starts to use @@ -894,11 +1421,11 @@ void diff2_CC_fine( unsigned long *d_trans_idx, unsigned long *d_job_idx, unsigned long *d_job_num, - cudaStream_t stream + deviceStream_t stream ) { #ifdef _CUDA_ENABLED -dim3 block_dim = grid_size; + dim3 block_dim = grid_size; cuda_kernel_diff2_CC_fine <<>>( g_eulers, @@ -920,6 +1447,51 @@ dim3 block_dim = grid_size; d_trans_idx, d_job_idx, d_job_num); +#elif _HIP_ENABLED + dim3 block_dim = grid_size; + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_diff2_CC_fine), dim3(block_dim), dim3(block_size), 0, stream, + g_eulers, + g_imgs_real, + g_imgs_imag, + g_trans_x, + g_trans_y, + g_trans_z, + projector, + g_corr_img, + g_diff2s, + image_size, + sum_init, + exp_local_sqrtXi2, + orientation_num, + translation_num, + todo_blocks, + d_rot_idx, + d_trans_idx, + d_job_idx, + d_job_num); +#elif _SYCL_ENABLED + syclKernels::diff2_CC_fine( + grid_size, + g_eulers, + g_imgs_real, + g_imgs_imag, + g_trans_x, + g_trans_y, + g_trans_z, + projector, + g_corr_img, + g_diff2s, + image_size, + sum_init, + exp_local_sqrtXi2, + orientation_num, + translation_num, + todo_blocks, + d_rot_idx, + d_trans_idx, + d_job_idx, + d_job_num, + stream); #else // TODO - Make use of orientation_num, translation_num, todo_blocks on // CPU side if CUDA starts to use @@ -984,7 +1556,7 @@ void kernel_weights_exponent_coarse( long int block_num = ceilf( ((double)nr_coarse_orient*nr_coarse_trans*num_classes) / (double)SUMW_BLOCK_SIZE ); #ifdef _CUDA_ENABLED -cuda_kernel_weights_exponent_coarse + cuda_kernel_weights_exponent_coarse <<>>( ~g_pdf_orientation, ~g_pdf_orientation_zeros, @@ -995,6 +1567,41 @@ cuda_kernel_weights_exponent_coarse nr_coarse_orient, nr_coarse_trans, nr_coarse_orient*nr_coarse_trans*num_classes); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_weights_exponent_coarse), dim3(block_num), dim3(SUMW_BLOCK_SIZE), 0, g_Mweight.getStream(), + ~g_pdf_orientation, + ~g_pdf_orientation_zeros, + ~g_pdf_offset, + ~g_pdf_offset_zeros, + ~g_Mweight, + g_min_diff2, + nr_coarse_orient, + nr_coarse_trans, + nr_coarse_orient*nr_coarse_trans*num_classes); +#elif _SYCL_ENABLED + if (g_Mweight.getAccType() == accSYCL) + syclGpuKernels::sycl_kernel_weights_exponent_coarse( + g_pdf_orientation.getDevicePtr(), + g_pdf_orientation_zeros.getDevicePtr(), + g_pdf_offset.getDevicePtr(), + g_pdf_offset_zeros.getDevicePtr(), + g_Mweight.getDevicePtr(), + g_min_diff2, + nr_coarse_orient, + nr_coarse_trans, + ((size_t)nr_coarse_orient)*((size_t)nr_coarse_trans)*((size_t)num_classes), + g_Mweight.getStream()); + else + syclKernels::weights_exponent_coarse( + ~g_pdf_orientation, + ~g_pdf_orientation_zeros, + ~g_pdf_offset, + ~g_pdf_offset_zeros, + ~g_Mweight, + g_min_diff2, + nr_coarse_orient, + nr_coarse_trans, + ((size_t)nr_coarse_orient)*((size_t)nr_coarse_trans)*((size_t)num_classes)); #else CpuKernels::weights_exponent_coarse( ~g_pdf_orientation, @@ -1016,12 +1623,17 @@ void kernel_exponentiate( { int blockDim = (int) ceilf( (double)array.getSize() / (double)BLOCK_SIZE ); #ifdef _CUDA_ENABLED -cuda_kernel_exponentiate - <<< blockDim,BLOCK_SIZE,0,array.getStream()>>> - (~array, add, array.getSize()); + cuda_kernel_exponentiate + <<< blockDim,BLOCK_SIZE,0,array.getStream()>>>(~array, add, array.getSize()); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_exponentiate), dim3(blockDim), dim3(BLOCK_SIZE), 0, array.getStream(), ~array, add, array.getSize()); +#elif _SYCL_ENABLED + if (array.getAccType() == accSYCL) + syclGpuKernels::sycl_kernel_exponentiate(array.getDevicePtr(), add, array.getSize(), array.getStream()); + else + syclKernels::exponentiate(~array, add, array.getSize()); #else - CpuKernels::exponentiate - (~array, add, array.getSize()); + CpuKernels::exponentiate (~array, add, array.getSize()); #endif } @@ -1037,10 +1649,28 @@ void kernel_exponentiate_weights_fine( int grid_size, unsigned long *d_job_idx, unsigned long *d_job_num, long int job_num, - cudaStream_t stream); + deviceStream_t stream); -}; // namespace AccUtilities +#ifdef _SYCL_ENABLED +template< typename T> +size_t findThresholdIdxInCumulativeSum(AccPtr &data, T threshold) +{ + if (data.getAccType() == accSYCL) + return syclGpuKernels::findThresholdIdxInCumulativeSum(data.getDevicePtr(), data.getSize(), threshold, data.getStream()); + else + { + const size_t count = data.getSize(); + const T* in = data.getHostPtr(); + size_t dist = 0; + for (size_t i = 0; i < count-1; i++) + if (in[i] <= threshold && in[i+1] > threshold) + dist = i+1; + return dist; + } +} +#endif -#endif //ACC_UTILITIES_H_ +}; // namespace AccUtilities +#endif //ACC_UTILITIES_H_ diff --git a/src/acc/utilities_impl.h b/src/acc/utilities_impl.h index 5fcf99aa9..2c103d9c0 100644 --- a/src/acc/utilities_impl.h +++ b/src/acc/utilities_impl.h @@ -9,10 +9,22 @@ #include "src/acc/cuda/cuda_kernels/wavg.cuh" #include "src/acc/cuda/cuda_kernels/diff2.cuh" #include "src/acc/cuda/cuda_fft.h" +using deviceStream_t = cudaStream_t; +#elif _HIP_ENABLED +#include "src/acc/hip/hip_kernels/helper.h" +#include "src/acc/hip/hip_kernels/wavg.h" +#include "src/acc/hip/hip_kernels/diff2.h" +#include "src/acc/hip/hip_fft.h" +using deviceStream_t = hipStream_t; +#elif _SYCL_ENABLED +#include "src/acc/sycl/sycl_kernels/helper.h" +#include "src/acc/sycl/sycl_kernels/wavg.h" +#include "src/acc/sycl/sycl_kernels/diff2.h" #else #include "src/acc/cpu/cpu_kernels/helper.h" #include "src/acc/cpu/cpu_kernels/wavg.h" #include "src/acc/cpu/cpu_kernels/diff2.h" +using deviceStream_t = cudaStream_t; #endif void dump_array(char *name, bool *ptr, size_t size) @@ -280,6 +292,65 @@ void makeNoiseImage(XFLOAT sigmaFudgeFactor, // transformer can be used to set up the actual particle image accMLO->transformer1.reals.cpOnDevice(~RandomImage); //cudaMLO->transformer1.reals.streamSync(); +#elif _HIP_ENABLED + // Set up states to seed and run randomization on the GPU + // AccDataTypes::Image RandomStates(RND_BLOCK_NUM*RND_BLOCK_SIZE,ptrFactory); + AccPtr RandomStates = RandomImage.make(RND_BLOCK_NUM*RND_BLOCK_SIZE); + RandomStates.deviceAlloc(); + + NoiseSpectra.cpToDevice(); + NoiseSpectra.streamSync(); + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); + + // Initialize randomization by particle ID, like on the CPU-side + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_initRND), RND_BLOCK_NUM, RND_BLOCK_SIZE, 0, 0, + seed, + ~RandomStates); + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); + + // Create noise image with the correct spectral profile + if(is3D) + { + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_RNDnormalDitributionComplexWithPowerModulation3D), dim3(RND_BLOCK_NUM), dim3(RND_BLOCK_SIZE), 0, 0, + ~accMLO->transformer1.fouriers, + ~RandomStates, + accMLO->transformer1.xFSize, + accMLO->transformer1.yFSize, + ~NoiseSpectra); + } + else + { + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_RNDnormalDitributionComplexWithPowerModulation2D), dim3(RND_BLOCK_NUM), dim3(RND_BLOCK_SIZE), 0, 0, + ~accMLO->transformer1.fouriers, + ~RandomStates, + accMLO->transformer1.xFSize, + ~NoiseSpectra); + } + LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); + + // Transform to real-space, to get something which look like + // the particle image without actual signal (a particle) + accMLO->transformer1.backward(); + + // Copy the randomized image to A separate device-array, so that the + // transformer can be used to set up the actual particle image + accMLO->transformer1.reals.cpOnDevice(~RandomImage); + //hipMLO->transformer1.reals.streamSync(); +#elif _SYCL_ENABLED + // Create noise image with the correct spectral profile + if(is3D) + syclKernels::RNDnormalDitributionComplexWithPowerModulation3D(accMLO->transformer1.fouriers(), accMLO->transformer1.xFSize, accMLO->transformer1.yFSize, ~NoiseSpectra); + else + syclKernels::RNDnormalDitributionComplexWithPowerModulation2D(accMLO->transformer1.fouriers(), accMLO->transformer1.xFSize, ~NoiseSpectra); + + // Transform to real-space, to get something which look like + // the particle image without actual signal (a particle) + accMLO->transformer1.backward(); + + // Copy the randomized image to A separate device-array, so that the + // transformer can be used to set up the actual particle image + for(size_t i=0; itransformer1.reals[i]; #else // Create noise image with the correct spectral profile @@ -324,6 +395,11 @@ static void TranslateAndNormCorrect(MultidimArray &img_in, #ifdef _CUDA_ENABLED int BSZ = ( (int) ceilf(( float)temp.getSize() /(float)BLOCK_SIZE)); CudaKernels::cuda_kernel_multi<<>>(temp(),normcorr,temp.getSize()); +#elif _HIP_ENABLED + int BSZ = ( (int) ceilf(( float)temp.getSize() /(float)BLOCK_SIZE)); + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_multi), dim3(BSZ), dim3(BLOCK_SIZE), 0, temp.getStream(), temp(), normcorr, temp.getSize()); +#elif _SYCL_ENABLED + syclKernels::sycl_kernel_multi(temp(),normcorr, temp.getSize()); #else CpuKernels::cpu_kernel_multi(temp(),normcorr, temp.getSize()); #endif @@ -339,6 +415,18 @@ static void TranslateAndNormCorrect(MultidimArray &img_in, else CudaKernels::cuda_kernel_translate2D<<>>(temp(),img_out(),img_in.zyxdim,img_in.xdim,img_in.ydim,xOff,yOff); //LAUNCH_PRIVATE_ERROR(cudaGetLastError(),accMLO->errorStatus); +#elif _HIP_ENABLED + int BSZ = ( (int) ceilf(( float)temp.getSize() /(float)BLOCK_SIZE)); + if (DATA3D) + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_translate3D), dim3(BSZ), dim3(BLOCK_SIZE), 0, temp.getStream(), temp(), img_out(), img_in.zyxdim, img_in.xdim, img_in.ydim, img_in.zdim, xOff, yOff, zOff); + else + hipLaunchKernelGGL(HIP_KERNEL_NAME(HipKernels::hip_kernel_translate2D), dim3(BSZ), dim3(BLOCK_SIZE), 0, temp.getStream(), temp(), img_out(), img_in.zyxdim, img_in.xdim, img_in.ydim, xOff, yOff); + //LAUNCH_PRIVATE_ERROR(hipGetLastError(),accMLO->errorStatus); +#elif _SYCL_ENABLED + if (DATA3D) + syclKernels::sycl_translate3D(temp(),img_out(),img_in.zyxdim,img_in.xdim,img_in.ydim,img_in.zdim,xOff,yOff,zOff); + else + syclKernels::sycl_translate2D(temp(),img_out(),img_in.zyxdim,img_in.xdim,img_in.ydim,xOff,yOff); #else if (DATA3D) CpuKernels::cpu_translate3D(temp(),img_out(),img_in.zyxdim,img_in.xdim,img_in.ydim,img_in.zdim,xOff,yOff,zOff); @@ -405,7 +493,7 @@ static void softMaskBackgroundValue( { int block_dim = 128; //TODO: set balanced (hardware-dep?) #ifdef _CUDA_ENABLED - cuda_kernel_softMaskBackgroundValue<<>>( + cuda_kernel_softMaskBackgroundValue<<>>( ~vol, vol.getxyz(), vol.getx(), @@ -419,6 +507,38 @@ static void softMaskBackgroundValue( cosine_width, ~g_sum, ~g_sum_bg); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_softMaskBackgroundValue), dim3(block_dim), dim3(SOFTMASK_BLOCK_SIZE), 0, vol.getStream(), + ~vol, + vol.getxyz(), + vol.getx(), + vol.gety(), + vol.getz(), + vol.getx()/2, + vol.gety()/2, + vol.getz()/2, + radius, + radius_p, + cosine_width, + ~g_sum, + ~g_sum_bg); +#elif _SYCL_ENABLED + syclKernels::softMaskBackgroundValue( + block_dim, + SOFTMASK_BLOCK_SIZE, + ~vol, + vol.getxyz(), + vol.getx(), + vol.gety(), + vol.getz(), + vol.getx()/2, + vol.gety()/2, + vol.getz()/2, + radius, + radius_p, + cosine_width, + ~g_sum, + ~g_sum_bg); #else CpuKernels::softMaskBackgroundValue( block_dim, @@ -465,6 +585,40 @@ static void cosineFilter( radius_p, cosine_width, sum_bg_total); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_cosineFilter), dim3(block_dim), dim3(SOFTMASK_BLOCK_SIZE), 0, vol.getStream(), + ~vol, + vol.getxyz(), + vol.getx(), + vol.gety(), + vol.getz(), + vol.getx()/2, + vol.gety()/2, + vol.getz()/2, + !do_Mnoise, + ~Noise, + radius, + radius_p, + cosine_width, + sum_bg_total); +#elif _SYCL_ENABLED + syclKernels::cosineFilter( + block_dim, + SOFTMASK_BLOCK_SIZE, + ~vol, + vol.getxyz(), + vol.getx(), + vol.gety(), + vol.getz(), + vol.getx()/2, + vol.gety()/2, + vol.getz()/2, + !do_Mnoise, + ~Noise, + radius, + radius_p, + cosine_width, + sum_bg_total); #else CpuKernels::cosineFilter( block_dim, @@ -493,6 +647,11 @@ void initOrientations(AccPtr &pdfs, AccPtr &pdf_orientation, Acc int gs = ceil(pdfs.getSize()/(float)(bs)); cuda_kernel_initOrientations<<>>(~pdfs, ~pdf_orientation, ~pdf_orientation_zeros, pdfs.getSize()); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + int bs = 512; + int gs = ceil(pdfs.getSize()/(float)(bs)); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_initOrientations), dim3(gs), dim3(bs), 0, pdfs.getStream(), ~pdfs, ~pdf_orientation, ~pdf_orientation_zeros, pdfs.getSize()); + LAUNCH_HANDLE_ERROR(hipGetLastError()); #else for(int iorientclass=0; iorientclass< pdfs.getSize(); iorientclass++) { @@ -512,7 +671,7 @@ void initOrientations(AccPtr &pdfs, AccPtr &pdf_orientation, Acc } void centerFFT_2D(int grid_size, int batch_size, int block_size, - cudaStream_t stream, + deviceStream_t stream, XFLOAT *img_in, size_t image_size, int xdim, @@ -529,8 +688,17 @@ void centerFFT_2D(int grid_size, int batch_size, int block_size, ydim, xshift, yshift); +#elif _HIP_ENABLED + dim3 blocks(grid_size, batch_size); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_centerFFT_2D), blocks, block_size, 0, stream, + img_in, + image_size, + xdim, + ydim, + xshift, + yshift); #else - CpuKernels::centerFFT_2D(batch_size, 0, image_size/2, + centerFFT_2D_CPU(batch_size, 0, image_size/2, img_in, image_size, xdim, @@ -557,8 +725,17 @@ void centerFFT_2D(int grid_size, int batch_size, int block_size, ydim, xshift, yshift); +#elif _HIP_ENABLED + dim3 blocks(grid_size, batch_size); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_centerFFT_2D), dim3(blocks), dim3(block_size), 0, 0, + img_in, + image_size, + xdim, + ydim, + xshift, + yshift); #else - CpuKernels::centerFFT_2D(batch_size, 0, image_size/2, + centerFFT_2D_CPU(batch_size, 0, image_size/2, img_in, image_size, xdim, @@ -569,7 +746,7 @@ void centerFFT_2D(int grid_size, int batch_size, int block_size, } void centerFFT_3D(int grid_size, int batch_size, int block_size, - cudaStream_t stream, + deviceStream_t stream, XFLOAT *img_in, size_t image_size, int xdim, @@ -590,8 +767,19 @@ void centerFFT_3D(int grid_size, int batch_size, int block_size, xshift, yshift, zshift); +#elif _HIP_ENABLED + dim3 blocks(grid_size, batch_size); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_centerFFT_3D), dim3(blocks), dim3(block_size), 0, stream, + img_in, + image_size, + xdim, + ydim, + zdim, + xshift, + yshift, + zshift); #else - CpuKernels::centerFFT_3D(batch_size, (size_t)0, (size_t)image_size/2, + centerFFT_3D_CPU(batch_size, (size_t)0, (size_t)image_size/2, img_in, image_size, xdim, @@ -616,12 +804,12 @@ void kernel_exponentiate_weights_fine( XFLOAT *g_pdf_orientation, unsigned long *d_job_idx, unsigned long *d_job_num, long int job_num, - cudaStream_t stream) + deviceStream_t stream) { long block_num = ceil((double)job_num / (double)SUMW_BLOCK_SIZE); #ifdef _CUDA_ENABLED -cuda_kernel_exponentiate_weights_fine<<>>( + cuda_kernel_exponentiate_weights_fine<<>>( g_pdf_orientation, g_pdf_orientation_zeros, g_pdf_offset, @@ -635,6 +823,54 @@ cuda_kernel_exponentiate_weights_fine<<>>( d_job_idx, d_job_num, job_num); +#elif _HIP_ENABLED + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_exponentiate_weights_fine), dim3(block_num), dim3(SUMW_BLOCK_SIZE), 0, stream, + g_pdf_orientation, + g_pdf_orientation_zeros, + g_pdf_offset, + g_pdf_offset_zeros, + g_weights, + min_diff2, + oversamples_orient, + oversamples_trans, + d_rot_id, + d_trans_idx, + d_job_idx, + d_job_num, + job_num); +#elif _SYCL_ENABLED + #ifdef USE_ONEDPL + syclGpuKernels::sycl_kernel_exponentiate_weights_fine( + g_pdf_orientation, + g_pdf_orientation_zeros, + g_pdf_offset, + g_pdf_offset_zeros, + g_weights, + min_diff2, + oversamples_orient, + oversamples_trans, + d_rot_id, + d_trans_idx, + d_job_idx, + d_job_num, + job_num, + stream); + #else + syclKernels::exponentiate_weights_fine( + g_pdf_orientation, + g_pdf_orientation_zeros, + g_pdf_offset, + g_pdf_offset_zeros, + g_weights, + min_diff2, + oversamples_orient, + oversamples_trans, + d_rot_id, + d_trans_idx, + d_job_idx, + d_job_num, + job_num); + #endif #else CpuKernels::exponentiate_weights_fine( g_pdf_orientation, @@ -658,11 +894,16 @@ cuda_kernel_exponentiate_weights_fine<<>>( void run_griddingCorrect(RFLOAT *vol, int interpolator, RFLOAT rrval, RFLOAT r_min_nn, size_t iX, size_t iY, size_t iZ) { -#ifdef CUDA +#ifdef _CUDA_ENABLED dim3 bs(32,4,2); dim3 gs(ceil(iX/(float)bs.x), ceil(iY/(float)bs.y), ceil(iZ/(float)bs.z)); cuda_kernel_griddingCorrect<<>>(vol, interpolator, rrval, r_min_nn, iX, iY, iZ); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + dim3 bs(32,4,2); + dim3 gs(ceil(iX/(float)bs.x), ceil(iY/(float)bs.y), ceil(iZ/(float)bs.z)); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_griddingCorrect), gs, bs, 0, 0, vol, interpolator, rrval, r_min_nn, iX, iY, iZ); + LAUNCH_HANDLE_ERROR(hipGetLastError()); #endif } @@ -670,15 +911,15 @@ void run_padTranslatedMap( RFLOAT *d_in, RFLOAT *d_out, size_t isX, size_t ieX, size_t isY, size_t ieY, size_t isZ, size_t ieZ, //Input dimensions size_t osX, size_t oeX, size_t osY, size_t oeY, size_t osZ, size_t oeZ, //Output dimensions - cudaStream_t stream) + deviceStream_t stream) { -#ifdef CUDA size_t iszX = ieX - isX + 1; size_t iszY = ieY - isY + 1; size_t iszZ = ieZ - isZ + 1; size_t oszX = oeX - osX + 1; size_t oszY = oeY - osY + 1; - size_t oszZ = oeZ - osZ + 1; + size_t oszZ = oeZ - osZ + 1; +#ifdef _CUDA_ENABLED if(iszX == oszX && iszY == oszY && iszZ == oszZ) { @@ -695,15 +936,33 @@ void run_padTranslatedMap( ); LAUNCH_HANDLE_ERROR(cudaGetLastError()); } +#elif _HIP_ENABLED + + if(iszX == oszX && iszY == oszY && iszZ == oszZ) + { + hipCpyDeviceToDevice(d_in, d_out, iszX*iszY*iszZ, stream); + } + else + { + dim3 block_dim(16,4,2); + dim3 grid_dim(ceil(oszX / (float) block_dim.x), ceil(oszY / (float) block_dim.y), ceil(oszZ / (float) block_dim.z)); + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_window_transform), grid_dim, block_dim, 0, stream, + d_in, d_out, + iszX, iszY, iszZ, //Input dimensions + isX-osX, isY-osY, isZ-osZ, oszX, oszY, oszZ //Output dimensions + ); + LAUNCH_HANDLE_ERROR(hipGetLastError()); + } #endif } -void run_CenterFFTbySign(Complex *img_in, int xSize, int ySize, int zSize, cudaStream_t stream) +void run_CenterFFTbySign(Complex *img_in, int xSize, int ySize, int zSize, deviceStream_t stream) { -#ifdef CUDA - dim3 bs(32,4,2); - dim3 gs(ceil(xSize/(float)bs.x), ceil(ySize/(float)bs.y), ceil(zSize/(float)bs.z)); - if(sizeof(RFLOAT) == sizeof(double)) +#ifdef _CUDA_ENABLED + dim3 bs(32,4,2); + dim3 gs(ceil(xSize/(float)bs.x), ceil(ySize/(float)bs.y), ceil(zSize/(float)bs.z)); + + if(sizeof(RFLOAT) == sizeof(double)) cuda_kernel_centerFFTbySign<<>>( (double2*)img_in, xSize, @@ -716,8 +975,24 @@ void run_CenterFFTbySign(Complex *img_in, int xSize, int ySize, int zSize, cudaS ySize, zSize); LAUNCH_HANDLE_ERROR(cudaGetLastError()); +#elif _HIP_ENABLED + dim3 bs(32,4,2); + dim3 gs(ceil(xSize/(float)bs.x), ceil(ySize/(float)bs.y), ceil(zSize/(float)bs.z)); + + if(sizeof(RFLOAT) == sizeof(double)) + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_centerFFTbySign), gs, bs, 0, stream, + (double2*)img_in, + xSize, + ySize, + zSize); + else + hipLaunchKernelGGL(HIP_KERNEL_NAME(hip_kernel_centerFFTbySign), gs, bs, 0, stream, + (float2*)img_in, + xSize, + ySize, + zSize); + LAUNCH_HANDLE_ERROR(hipGetLastError()); #endif } #endif //ACC_UTILITIES_H_ - diff --git a/src/align_tiltseries_runner.cpp b/src/align_tiltseries_runner.cpp new file mode 100644 index 000000000..1423d0270 --- /dev/null +++ b/src/align_tiltseries_runner.cpp @@ -0,0 +1,310 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * 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. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#include "src/align_tiltseries_runner.h" + +void AlignTiltseriesRunner::read(int argc, char **argv, int rank) +{ + parser.setCommandLine(argc, argv); + int gen_section = parser.addSection("General options"); + fn_in = parser.getOption("--i", "STAR file with all input tomograms, or a unix wildcard to all tomogram files, e.g. \"mics/*.mrc\""); + fn_out = parser.getOption("--o", "Directory, where all output files will be stored", "AlignTiltSeries/"); + continue_old = parser.checkOption("--only_do_unfinished", "Only estimate CTFs for those tomograms for which there is not yet a logfile with Final values."); + do_at_most = textToInteger(parser.getOption("--do_at_most", "Only process up to this number of (unprocessed) tomograms.", "-1")); + fn_imodwrapper_exe = parser.getOption("--wrapper_executable", "Alister Burt's wrapper script to call IMOD/AreTomo (default is set through $RELION_IMOD_WRAPPER_EXECUTABLE)", ""); + + int fid_section = parser.addSection("IMOD fiducial-based alignment options"); + do_imod_fiducials = parser.checkOption("--imod_fiducials", "Use IMOD's fiducial-based alignment method"); + fiducial_diam = textToFloat(parser.getOption("--fiducial_diameter", "Diameter of the fiducials (in nm)", "10")); + + int pat_section = parser.addSection("IMOD patch-tracking alignment options"); + do_imod_patchtrack = parser.checkOption("--imod_patchtrack", "OR: Use IMOD's patrick-tracking alignment method"); + patch_overlap = textToFloat(parser.getOption("--patch_overlap", "Overlap between the patches (in %)", "10.")); + patch_size = textToFloat(parser.getOption("--patch_size", "Patch size (in A)", "10.")); + + int aretomo_section = parser.addSection("AreTomo alignment options"); + do_aretomo = parser.checkOption("--aretomo", "OR: Use AreTomo's alignment method"); + aretomo_resolution = textToFloat(parser.getOption("--aretomo_resolution", "Resolution (in A) of the tilt series output by AreTomo. Has little bearing on processing.", "10")); + aretomo_thickness = textToFloat(parser.getOption("--aretomo_thickness", "Thickness (in A) for AreTomo alignment", "2000")); + do_aretomo_tiltcorrect = parser.checkOption("--aretomo_tiltcorrect", "Specify to correct the tilt angle offset in the tomogram (AreTomo -TiltCor option; default=false)"); + gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread, e.g 0:1:2:3", ""); + + patch_overlap = textToFloat(parser.getOption("--patch_overlap", "Overlap between the patches (in %)", "10")); + patch_size = textToInteger(parser.getOption("--patch_size", "Patch size (in unbinned pixels)", "10")); + + int exp_section = parser.addSection("Expert options"); + other_wrapper_args = parser.getOption("--other_wrapper_args", "Additional command-line arguments that will be passed onto the wrapper.", ""); + + // Initialise verb for non-parallel execution + verb = 1; + + // Check for errors in the command-line option + if (parser.checkForErrors()) + REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); +} + +void AlignTiltseriesRunner::usage() +{ + parser.writeUsage(std::cout); +} + +void AlignTiltseriesRunner::initialise(bool is_leader) +{ + // Get the Imod wrapper executable + if (fn_imodwrapper_exe == "") + { + char *penv; + penv = getenv("RELION_IMOD_WRAPPER_EXECUTABLE"); + if (penv != NULL) + fn_imodwrapper_exe = (std::string)penv; + } + + int i = 0; + if (do_imod_fiducials) i++; + if (do_imod_patchtrack) i++; + if (do_aretomo) i++; + if (i != 1) REPORT_ERROR("ERROR: you need to specify one of these options: --imod_fiducials or --imod_patchtrack or --aretomo"); + + // Make sure fn_out ends with a slash + if (fn_out[fn_out.length()-1] != '/') + fn_out += "/"; + + // Check if this is a TomographyExperiment starfile, if not raise an error + if (!tomogramSet.read(fn_in, 1)) + { + REPORT_ERROR("ERROR: the input file is not a valid tilt series star file"); + } + + idx_tomograms_all.clear(); + idx_tomograms.clear(); + bool warned = false; + for (long int itomo = 0; itomo < tomogramSet.size(); itomo++) + { + FileName fn_star; + tomogramSet.globalTable.getValue(EMDL_TOMO_TILT_SERIES_STARFILE, fn_star, itomo); + FileName fn_newstar = getOutputFileWithNewUniqueDate(fn_star, fn_out); + tomogramSet.globalTable.setValue(EMDL_TOMO_TILT_SERIES_STARFILE, fn_newstar, itomo); + + bool process_this = true; + bool ignore_this = false; + if (continue_old) + { + if (checkImodWrapperResults(itomo)) + { + process_this = false; // already done + } + } + + if (do_at_most >= 0 && idx_tomograms.size() >= do_at_most) + { + if (process_this) { + ignore_this = true; + process_this = false; + if (!warned) + { + warned = true; + std::cout << "NOTE: processing of some tomograms will be skipped as requested by --do_at_most" << std::endl; + } + } + // If this tomogram has already been processed, the result should be included in the output. + // So ignore_this remains false. + } + + if (process_this) + { + idx_tomograms.push_back(itomo); + } + + if (!ignore_this) + { + idx_tomograms_all.push_back(itomo); + } + } + + if (is_leader && do_at_most >= 0 ) + { + std::cout << tomogramSet.size() << " tomograms were given in the input tomogram set, but we process only "; + std::cout << do_at_most << " tomograms as specified in --do_at_most." << std::endl; + } + + if (do_aretomo) + { + if (gpu_ids.length() > 0) + untangleDeviceIDs(gpu_ids, allThreadIDs); + else if (verb>0) + std::cout << "--gpu_ids not specified, threads will automatically be mapped to devices."<< std::endl; + } + + if (verb > 0) + { + std::cout << " Using IMOD wrapper executable in: " << fn_imodwrapper_exe << std::endl; + std::cout << " to align tilt series for the following tomograms: " << std::endl; + if (continue_old) + std::cout << " (skipping all tomograms for which a logfile with Final values already exists " << std::endl; + for (unsigned int i = 0; i < idx_tomograms.size(); ++i) + std::cout << " * " << tomogramSet.getTomogramName(idx_tomograms[i]) << std::endl; + } +} + +void AlignTiltseriesRunner::run() +{ + + int barstep; + if (verb > 0) + { + std::cout << " Aligning tilt series ..." << std::endl; + init_progress_bar(idx_tomograms.size()); + barstep = XMIPP_MAX(1, idx_tomograms.size() / 60); + } + + std::vector alltomonames; + for (long int itomo = 0; itomo < idx_tomograms.size(); itomo++) + { + + // Abort through the pipeline_control system + if (pipeline_control_check_abort_job()) + exit(RELION_EXIT_ABORTED); + + executeImodWrapper(idx_tomograms[itomo]); + + if (verb > 0 && itomo % barstep == 0) + progress_bar(itomo); + } + + if (verb > 0) + progress_bar(idx_tomograms.size()); + + joinImodWrapperResults(); +} + + +void AlignTiltseriesRunner::executeImodWrapper(long idx_tomo, int rank) +{ + + RFLOAT angpix = tomogramSet.getTiltSeriesPixelSize(idx_tomo); + + std::string command = fn_imodwrapper_exe + " "; + // Make sure the methods are the first argument to the program! + if (do_imod_fiducials) + { + command += " IMOD:fiducials"; + command += " --nominal-fiducial-diameter-nanometers " + floatToString(fiducial_diam); + } + else if (do_imod_patchtrack) + { + command += " IMOD:patch-tracking"; + command += " --patch-size-angstroms " + integerToString(patch_size); + command += " --patch-overlap-percentage " + floatToString(patch_overlap); + } + else if (do_aretomo) + { + command += " AreTomo"; + command += " --output-resolution " + floatToString(aretomo_resolution); + command += " --alignment-thickness " + floatToString(aretomo_thickness); + } + command += " --tilt-series-star-file " + fn_in; + command += " --tomogram-name " + tomogramSet.getTomogramName(idx_tomo); + command += " --output-directory " + fn_out; + + if (do_aretomo_tiltcorrect) command += " --tilt-angle-offset-correction"; + + if (gpu_ids.length() > 0) + { + if (rank >= allThreadIDs.size()) + REPORT_ERROR("ERROR: not enough MPI nodes specified for the GPU IDs."); + + command += " --gpu-ids "; + for (int igpu = 0; igpu < allThreadIDs[rank].size(); igpu++) + { + command += allThreadIDs[rank][igpu]; + if (igpu < allThreadIDs[rank].size()-1) command += ":"; + } + } + + if (other_wrapper_args.length() > 0) + command += " " + other_wrapper_args; + + if (system(command.c_str())) + std::cerr << "WARNING: there was an error in executing: " << command << std::endl; + +} + +bool AlignTiltseriesRunner::checkImodWrapperResults(long idx_tomo) +{ + FileName fn_star; + tomogramSet.globalTable.getValue(EMDL_TOMO_TILT_SERIES_STARFILE, fn_star, idx_tomo); + + MetaDataTable MDtomo; + MDtomo.read(fn_star); + return (MDtomo.containsLabel(EMDL_TOMO_XSHIFT_ANGST) && + MDtomo.containsLabel(EMDL_TOMO_YSHIFT_ANGST) && + MDtomo.containsLabel(EMDL_TOMO_XTILT) && + MDtomo.containsLabel(EMDL_TOMO_YTILT) && + MDtomo.containsLabel(EMDL_TOMO_ZROT) ); + +} + +bool AlignTiltseriesRunner::checkEtomoDirectiveFile(long idx_tomo, FileName &filename) +{ + FileName fn_tomo = tomogramSet.getTomogramName(idx_tomo); + filename = fn_out + "external/" + fn_tomo + "/" + fn_tomo + ".edf"; + return exists(filename); + +} + +void AlignTiltseriesRunner::joinImodWrapperResults() +{ + // Check again the STAR file exists and has the right labels + // Also check for the presence of any eTomoDirective files + + MetaDataTable MDout; + MDout.setName("global"); + bool any_edf = false; + + for (long itomo = 0; itomo < tomogramSet.size(); itomo++) + { + if (checkImodWrapperResults(itomo)) + { + MDout.addObject(tomogramSet.globalTable.getObject(itomo)); + FileName fn_edf; + if (checkEtomoDirectiveFile(itomo, fn_edf)) + { + any_edf = true; + MDout.setValue(EMDL_TOMO_ETOMO_DIRECTIVE_FILE, fn_edf, itomo); + } + else + { + MDout.setValue(EMDL_TOMO_ETOMO_DIRECTIVE_FILE, std::string("undefined"), itomo); + } + } + else if (verb) + { + std::cerr << "WARNING: cannot find tilt series alignment parameters in " << tomogramSet.getTomogramName(itomo) << std::endl; + } + } + if (!any_edf) MDout.deactivateLabel(EMDL_TOMO_ETOMO_DIRECTIVE_FILE); + + MDout.write(fn_out + "aligned_tilt_series.star"); + + if (verb > 0) + { + std::cout << " Done! Written out: " << fn_out << "aligned_tilt_series.star" << std::endl; + } + +} diff --git a/src/align_tiltseries_runner.h b/src/align_tiltseries_runner.h new file mode 100644 index 000000000..88a1b743c --- /dev/null +++ b/src/align_tiltseries_runner.h @@ -0,0 +1,127 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * 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. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#ifndef RELION_ALIGN_TILTSERIES_RUNNER_H +#define RELION_ALIGN_TILTSERIES_RUNNER_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "src/jaz/tomography/tomogram_set.h" + +class AlignTiltseriesRunner +{ +public: + + // I/O Parser + IOParser parser; + + // Verbosity + int verb; + + // Output rootname + FileName fn_in, fn_out; + + // Filenames of all the tomograms to estimate the CTF from + std::vector idx_tomograms, idx_tomograms_all; + + // Information about tomography experiment + TomogramSet tomogramSet; + + // CTFFIND and Gctf executables and shell + FileName fn_imodwrapper_exe; + + // Use IMOD:fiducials + bool do_imod_fiducials; + + // Nominal fiducial diameter (nm) + RFLOAT fiducial_diam; + + // Use IMOD:patch-tracking + bool do_imod_patchtrack; + + // Unbinned patch size (pixels) + RFLOAT patch_size; + + // Patch overlap (percentage 0-100) + RFLOAT patch_overlap; + + // Use AreTomo + bool do_aretomo; + + // Resolution used in AreTomo alignment + RFLOAT aretomo_resolution; + + // Alignment thickness in AreTomo + RFLOAT aretomo_thickness; + + // Perform tilt angle correction in AreTomo + bool do_aretomo_tiltcorrect; + + // Which GPU devices to use? + int devCount; + std::string gpu_ids; + std::vector < std::vector < std::string > > allThreadIDs; + + // Additional gctf command line options + std::string other_wrapper_args; + + // Continue an old run: only estimate CTF if logfile WITH Final Values line does not yet exist, otherwise skip the tomogram + bool continue_old; + + // Process at most this number of unprocessed tomograms + long do_at_most; + +public: + // Read command line arguments + void read(int argc, char **argv, int rank = 0); + + // Print usage instructions + void usage(); + + // Initialise some stuff after reading + void initialise(bool is_leader = true); + + // Execute all CTFFIND jobs to get CTF parameters + void run(); + + // Check STAR file for tomogram exists and has the correct labels + bool checkImodWrapperResults(long idx_tomo); + + // Execute CTFFIND for a single tomogram + void executeImodWrapper(long idx_tomo, int rank = 0); + + // Find the etomo directives file (.edf) + bool checkEtomoDirectiveFile(long idx_tomo, FileName &filename); + + // Harvest all IMOD results into the single tomograms set starfile, and write it out + void joinImodWrapperResults(); + +}; + + +#endif //RELION_ALIGN_TILTSERIES_RUNNER_H diff --git a/src/align_tiltseries_runner_mpi.cpp b/src/align_tiltseries_runner_mpi.cpp new file mode 100644 index 000000000..d057c9db7 --- /dev/null +++ b/src/align_tiltseries_runner_mpi.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * 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. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#include "src/align_tiltseries_runner_mpi.h" + +void AlignTiltseriesRunnerMpi::read(int argc, char **argv) +{ + // Define a new MpiNode + node = new MpiNode(argc, argv); + + // First read in non-parallelisation-dependent variables + AlignTiltseriesRunner::read(argc, argv); + + // Don't put any output to screen for mpi followers + verb = (node->isLeader()) ? 1 : 0; + + // Possibly also read parallelisation-dependent variables here + + // Print out MPI info + printMpiNodesMachineNames(*node); +} + +void AlignTiltseriesRunnerMpi::run() +{ + + // Each node does part of the work + long int my_first_tomogram, my_last_tomogram, my_nr_tomograms; + divide_equally(idx_tomograms.size(), node->size, node->rank, my_first_tomogram, my_last_tomogram); + my_nr_tomograms = my_last_tomogram - my_first_tomogram + 1; + + int barstep; + if (verb > 0) + { + std::cout << " Aligning tilt series ..." << std::endl; + init_progress_bar(my_nr_tomograms); + barstep = XMIPP_MAX(1, my_nr_tomograms / 60); + } + + std::vector allmicnames; + for (long int itomo = my_first_tomogram; itomo <= my_last_tomogram; itomo++) + { + + // Abort through the pipeline_control system + if (pipeline_control_check_abort_job()) + MPI_Abort(MPI_COMM_WORLD, RELION_EXIT_ABORTED); + + executeImodWrapper(idx_tomograms[itomo], node->rank); + + if (verb > 0 && itomo % barstep == 0) progress_bar(itomo); + + } + + if (verb > 0) progress_bar(my_nr_tomograms); + + MPI_Barrier(MPI_COMM_WORLD); + + // Only the leader writes the joined result file + if (node->isLeader()) joinImodWrapperResults(); + +} diff --git a/src/align_tiltseries_runner_mpi.h b/src/align_tiltseries_runner_mpi.h new file mode 100644 index 000000000..9f9bcdf7b --- /dev/null +++ b/src/align_tiltseries_runner_mpi.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * 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. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ + +#ifndef ALIGN_TILTSERIES_RUNNER_MPI_H_ +#define ALIGN_TILTSERIES_RUNNER_MPI_H_ + +#include "src/mpi.h" +#include "src/align_tiltseries_runner.h" +#include "src/parallel.h" + +class AlignTiltseriesRunnerMpi: public AlignTiltseriesRunner +{ +public: + MpiNode *node; + + /** Destructor, calls MPI_Finalize */ + ~AlignTiltseriesRunnerMpi() + { + delete node; + } + + /** Read + * This could take care of mpi-parallelisation-dependent variables + */ + void read(int argc, char **argv); + + // Parallelized run function + void run(); + +}; + +#endif //ALIGN_TILTSERIES_RUNNER_MPI_H diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index 709d3c5bd..be7f69e56 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -34,7 +34,7 @@ if (ALTCPU) file(GLOB REL_SRC_H "${CMAKE_SOURCE_DIR}/src/*.h" "${CMAKE_SOURCE_DIR}/src/acc/*.h" "${CMAKE_SOURCE_DIR}/src/acc/cpu/*.h" "${CMAKE_SOURCE_DIR}/src/acc/cpu/cpu_kernels/*.h" ) else() file(GLOB REL_SRC "${CMAKE_SOURCE_DIR}/src/*.cpp" "${CMAKE_BINARY_DIR}/macros.cpp" "${CMAKE_SOURCE_DIR}/src/*.c" "${CMAKE_SOURCE_DIR}/src/acc/*.cpp" ) - file(GLOB REL_SRC_H "${CMAKE_SOURCE_DIR}/src/*.h" "${CMAKE_SOURCE_DIR}/src/acc/*.h" ) + file(GLOB REL_SRC_H "${CMAKE_SOURCE_DIR}/src/*.h" "${CMAKE_SOURCE_DIR}/src/acc/*.h" ) endif(ALTCPU) # Remove GUI files from relion_lib @@ -112,14 +112,15 @@ file(GLOB RELION_TARGETS "${CMAKE_SOURCE_DIR}/src/apps/*.cpp") file(GLOB RELION_JAZ_SPA_TARGETS "${CMAKE_SOURCE_DIR}/src/jaz/single_particle/apps/*.cpp") file(GLOB RELION_JAZ_TOMO_TARGETS "${CMAKE_SOURCE_DIR}/src/jaz/tomography/apps/*.cpp") +file(GLOB RELION_MPI_TARGETS RELATIVE "${CMAKE_SOURCE_DIR}/src/apps" "${CMAKE_SOURCE_DIR}/src/apps/*_mpi.cpp") +list(APPEND RELION_MPI_TARGETS "postprocess.cpp;flex_analyse.cpp;autopick.cpp") + if(RELION_TEST) file(GLOB RELION_JAZ_EXPERIMENTAL_TARGETS "${CMAKE_SOURCE_DIR}/src/jaz/scripts/*.cpp") endif(RELION_TEST) set(GUI_TARGETS maingui display manualpick) -set(TORCH_TARGETS class_ranker) - #--Remove apps using X11 if no GUI-- if(NOT GUI) foreach(TARGET ${GUI_TARGETS}) @@ -187,20 +188,20 @@ else() ${REL_JAZ_MESH_PROC_SRC} ${REL_JAZ_MESH_PROC_H} ${REL_JAZ_UTIL_SRC} ${REL_JAZ_UTIL_H} ${REL_JAZ_TOMO_SRC} ${REL_JAZ_TOMO_H} - ${REL_JAZ_TOMO_PROJ_SRC} ${REL_JAZ_TOMO_PROJ_H} + ${REL_JAZ_TOMO_PROJ_SRC} ${REL_JAZ_TOMO_PROJ_H} ${REL_JAZ_TOMO_MEMBRANE_SRC} ${REL_JAZ_TOMO_MEMBRANE_H} ${REL_JAZ_TOMO_FILAMENT_SRC} ${REL_JAZ_TOMO_FILAMENT_H} ${REL_JAZ_TOMO_MOTION_SRC} ${REL_JAZ_TOMO_MOTION_H} ${REL_JAZ_TOMO_MOTION_MODULAR_ALIGNMENT_SRC} ${REL_JAZ_TOMO_MOTION_MODULAR_ALIGNMENT_H} ${REL_JAZ_TOMO_MANIFOLD_SRC} ${REL_JAZ_TOMO_MANIFOLD_H} - ${REL_JAZ_TOMO_LATTICE_SRC} ${REL_JAZ_TOMO_LATTICE_H} + ${REL_JAZ_TOMO_LATTICE_SRC} ${REL_JAZ_TOMO_LATTICE_H} ${REL_JAZ_OPTIM_SRC} ${REL_JAZ_OPTIM_H} ${REL_JAZ_OPTICS_SRC} ${REL_JAZ_OPTICS_H} ${REL_JAZ_OPTICS_DUAL_CONTRAST_SRC} ${REL_JAZ_OPTICS_DUAL_CONTRAST_H} - ${REL_JAZ_SEGMENTATION_SRC} ${REL_JAZ_SEGMENTATION_H} - ${REL_JAZ_ATOMIC_SRC} ${REL_JAZ_ATOMIC_H} - ${REL_JAZ_PROGRAMS_SRC} ${REL_JAZ_PROGRAMS_H} - ${REL_JAZ_DYNAMO_SRC} ${REL_JAZ_DYNAMO_H} + ${REL_JAZ_SEGMENTATION_SRC} ${REL_JAZ_SEGMENTATION_H} + ${REL_JAZ_ATOMIC_SRC} ${REL_JAZ_ATOMIC_H} + ${REL_JAZ_PROGRAMS_SRC} ${REL_JAZ_PROGRAMS_H} + ${REL_JAZ_DYNAMO_SRC} ${REL_JAZ_DYNAMO_H} ${REL_JAZ_SPA_SRC} ${REL_JAZ_SPA_H} ${REL_JAZ_SPA_OPT_SRC} ${REL_JAZ_SPA_OPT_H} ${REL_JAZ_SPA_CTF_SRC} ${REL_JAZ_SPA_CTF_H} @@ -211,7 +212,7 @@ else() ${REL_JAZ_SPA_IO_SRC} ${REL_JAZ_SPA_IO_H} ${REL_JAZ_SPA_PROGRAMS_SRC} ${REL_JAZ_SPA_PROGRAMS_H} ${REL_D3x3_SRC} ${REL_D3x3_H} - ${REL_LBFGS_SRC} ${REL_LBFGS_H} + ${REL_LBFGS_SRC} ${REL_LBFGS_H} ${REL_SPHERICAL_HARMONICS_SRC} ${REL_SPHERICAL_HARMONICS_H}) if(GUI) @@ -290,37 +291,132 @@ if (CUDA_FOUND) target_link_libraries(relion_lib relion_jaz_gpu_util ${CUDA_CUFFT_LIBRARIES}) target_link_libraries(relion_lib relion_jaz_gpu_util ${CUDA_CUFFT_LIBRARIES} ${CUDA_curand_LIBRARY}) +elseif (HIP_FOUND) + file(GLOB REL_HIP_SRC "${CMAKE_SOURCE_DIR}/src/acc/hip/*.cpp" "${CMAKE_SOURCE_DIR}/src/acc/hip/hip_kernels/*.cpp" ) + hip_add_library(relion_gpu_util ${REL_HIP_SRC}) + + if (${CMAKE_BUILD_TYPE_LOWER} STREQUAL "profiling") + find_library(ROCM_TRACER_LIB NAMES roctx64 PATHS ${HIP_ROOT_DIR}/lib ${HIP_ROOT_DIR}/../lib REQUIRED) + list(APPEND EXTRA_LIBS "${ROCM_TRACER_LIB}") + target_link_libraries(relion_gpu_util ${ROCM_TRACER_LIB}) + message(STATUS "Adding extra library for AMD profiling: ${ROCM_TRACER_LIB}") + endif() + + # Presently we have a number of (bad) circular dependencies between the gpu util + # and relion libraries, which cause errors at least on OS X with clang. Tell the + # compiler to ignore them. + if(APPLE) + set(new_link_flags "-undefined suppress -flat_namespace") + get_target_property(existing_link_flags relion_gpu_util LINK_FLAGS) + if(existing_link_flags) + set(new_link_flags "${existing_link_flags} ${new_link_flags}") + endif() + set_target_properties(relion_gpu_util PROPERTIES LINK_FLAGS "${new_link_flags}") + endif() + + list(APPEND EXTRA_LIBS "${HIPFFT_LIBRARIES}") + if(BUILD_SHARED_LIBS) + install (TARGETS relion_gpu_util LIBRARY DESTINATION lib) + else() + target_link_libraries(relion_gpu_util relion_lib) + target_link_libraries(relion_gpu_util ${HIPFFT_LIBRARIES}) + endif() + + target_link_libraries(relion_lib relion_gpu_util ${HIPFFT_LIBRARIES}) + target_link_libraries(relion_lib relion_gpu_util ${HIPFFT_LIBRARIES} ${HIPRAND_LIBRARIES}) + target_link_libraries(relion_lib relion_gpu_util ${HIPCUB_LIBRARIES}) + + file(GLOB REL_JAZ_HIP_SRC "${CMAKE_SOURCE_DIR}/src/jaz/hip/*.cpp" "${CMAKE_SOURCE_DIR}/src/jaz/hip/kernels/*.cpp" ) + hip_add_library(relion_jaz_gpu_util ${REL_JAZ_HIP_SRC}) + + #list(APPEND EXTRA_LIBS "${HIPFFT_LIBRARIES}") + #if(BUILD_SHARED_LIBS) + # install (TARGETS relion_jaz_gpu_util LIBRARY DESTINATION lib) + #else() + # target_link_libraries(relion_jaz_gpu_util relion_lib) + # target_link_libraries(relion_jaz_gpu_util ${HIPFFT_LIBRARIES}) + #endif() + + target_link_libraries(relion_lib relion_jaz_gpu_util ${HIPFFT_LIBRARIES}) + target_link_libraries(relion_lib relion_jaz_gpu_util ${HIPFFT_LIBRARIES} ${HIPRAND_LIBRARIES}) + target_link_libraries(relion_lib relion_gpu_util ${HIPCUB_LIBRARIES}) + +elseif(SYCL) + + set(SYCL_TARGETS refine refine_mpi) + file(GLOB REL_SYCL_SRC "${CMAKE_SOURCE_DIR}/src/acc/sycl/*.cpp" "${CMAKE_SOURCE_DIR}/src/acc/sycl/sycl_kernels/*.cpp") + set(REL_SYCL_NO_OMP "${CMAKE_SOURCE_DIR}/src/acc/sycl/sycl_ml_optimiser.cpp") + add_library(relion_sycl_util ${REL_SYCL_SRC}) + + if(DEFINED SYCL_CUDA_COMPILE OR DEFINED SYCL_HIP_COMPILE) + if(SYCL_CUDA_COMPILE) + set(REL_SYCL_TARGETS nvptx64-nvidia-cuda) +# set(REL_SYCL_FRONTEND -Xsycl-target-frontend=nvptx64-nvidia-cuda " -fp-model=fast") + set(REL_SYCL_BACKEND -Xsycl-target-backend=nvptx64-nvidia-cuda --cuda-gpu-arch=sm_${SYCL_CUDA_TARGET}) +# set(REL_SYCL_BACKEND -Xsycl-target-backend=nvptx64-nvidia-cuda "--cuda-gpu-arch=sm_${SYCL_CUDA_TARGET} -options -cl-fast-relaxed-math") + set(REL_SYCL_COMPILE_TOOLCHAIN -Wno-unknown-cuda-version -DEIGEN_NO_CUDA) + endif(SYCL_CUDA_COMPILE) + if(SYCL_HIP_COMPILE) + if(SYCL_CUDA_COMPILE) + set(REL_SYCL_TARGETS ${REL_SYCL_TARGETS},amdgcn-amd-amdhsa) + else(SYCL_CUDA_COMPILE) + set(REL_SYCL_TARGETS amdgcn-amd-amdhsa) + endif(SYCL_CUDA_COMPILE) +# set(REL_SYCL_FRONTEND ${REL_SYCL_FRONTEND} -Xsycl-target-frontend=amdgcn-amd-amdhsa " -fp-model=fast") + set(REL_SYCL_BACKEND ${REL_SYCL_BACKEND} -Xsycl-target-backend=amdgcn-amd-amdhsa --offload-arch=${SYCL_HIP_TARGET}) +# set(REL_SYCL_BACKEND ${REL_SYCL_BACKEND} -Xsycl-target-backend=amdgcn-amd-amdhsa "--offload-arch=${SYCL_HIP_TARGET} -options -cl-fast-relaxed-math") + set(REL_SYCL_COMPILE_TOOLCHAIN ${REL_SYCL_COMPILE_TOOLCHAIN} -DEIGEN_NO_CUDA -DEIGEN_NO_HIP) + endif() + set(REL_SYCL_TARGETS ${REL_SYCL_TARGETS},spir64) +# set(REL_SYCL_FRONTEND ${REL_SYCL_FRONTEND} -Xsycl-target-frontend=spir64 "-fp-model=fast") + set(REL_SYCL_COMPILE_TOOLCHAIN ${REL_SYCL_COMPILE_TOOLCHAIN} -Wno-unused-command-line-argument -DSYCL_USE_NATIVE_FP_ATOMICS -fsycl -fsycl-unnamed-lambda -fsycl-targets=${REL_SYCL_TARGETS} ${REL_SYCL_FRONTEND} ${REL_SYCL_BACKEND}) + target_compile_options(relion_sycl_util PRIVATE ${REL_SYCL_COMPILE_TOOLCHAIN}) + + else(DEFINED SYCL_CUDA_COMPILE OR DEFINED SYCL_HIP_COMPILE) + target_compile_options(relion_sycl_util PRIVATE -Wno-unused-command-line-argument -DSYCL_USE_NATIVE_FP_ATOMICS -fsycl -fsycl-unnamed-lambda -fsycl-targets=spir64) +# target_compile_options(relion_sycl_util PRIVATE -Wno-unused-command-line-argument -DSYCL_USE_NATIVE_FP_ATOMICS -fsycl -fsycl-unnamed-lambda -fsycl-targets=spir64 -Xsycl-target-frontend=spir64 "-fp-model=fast") +# target_compile_options(relion_sycl_util PRIVATE -Wno-unused-command-line-argument -DSYCL_USE_NATIVE_FP_ATOMICS -fsycl -fsycl-unnamed-lambda -fsycl-targets=spir64 -Xsycl-target-backend=spir64 "-options -cl-fast-relaxed-math") + endif(DEFINED SYCL_CUDA_COMPILE OR DEFINED SYCL_HIP_COMPILE) + + foreach (_source ${REL_SYCL_SRC}) + list(FIND REL_SYCL_NO_OMP ${_source} IS_SYCL_NO_OMP) + if(NOT ${IS_SYCL_NO_OMP} LESS 0) + set_source_files_properties(${_source} PROPERTIES COMPILE_FLAGS -qno-openmp) + endif() + endforeach() + target_link_libraries(relion_sycl_util sycl OpenCL relion_lib) -endif(CUDA_FOUND) +endif() if(TIFF_FOUND) - #message("TIFF FOUND") include_directories(${TIFF_INCLUDE_DIRS}) target_link_libraries(relion_lib ${TIFF_LIBRARIES}) -else() - #message("TIFF NOT FOUND") endif() -find_package(ZLIB) - -find_package(PNG) if(PNG_FOUND) - add_definitions(-DHAVE_PNG) - message("PNG FOUND") include_directories(${PNG_INCLUDE_DIRS}) target_link_libraries(relion_lib ${PNG_LIBRARY}) else() - message("PNG NOT FOUND") + find_library(PNG_LIBRARY NAMES png PATHS /share/modules/libpng/1.6.37/lib/ /usr/local/lib) + if(PNG_LIBRARY) + message("Trying manual search for PNG") + add_definitions(-DHAVE_PNG) + message(STATUS "PNG_LIBRARY: ${PNG_LIBRARY}") + target_link_libraries(relion_lib ${PNG_LIBRARY}) + endif() endif() -find_package(JPEG) if(JPEG_FOUND) - add_definitions(-DHAVE_JPEG) - message("JPEG FOUND") include_directories(${JPEG_INCLUDE_DIRS}) target_link_libraries(relion_lib ${JPEG_LIBRARY}) else() - message("JPEG NOT FOUND") + find_library(JPEG_LIBRARY NAMES jpeg PATHS /share/modules/libjpeg/62.2.0/lib64/ /usr/local/lib) + if(JPEG_LIBRARY) + message("Trying manual search for JPEG") + add_definitions(-DHAVE_JPEG) + message(STATUS "JPEG_LIBRARY: ${JPEG_LIBRARY}") + target_link_libraries(relion_lib ${JPEG_LIBRARY}) + endif() endif() if(BUILD_OWN_TBB) @@ -339,29 +435,45 @@ foreach (_target ${RELION_TARGETS}) endif() set(LIB relion_lib) - + add_dependencies(${_target} relion_lib) if(NOT MKLFFT) target_link_libraries(${_target} ${LIB} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) - else() + else() target_link_libraries(${_target} ${LIB} ${FFTW_LIBRARIES} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) endif(NOT MKLFFT) - if(CUDA_FOUND) + if(CUDA_FOUND OR HIP_FOUND) target_link_libraries(${_target} relion_gpu_util) - endif(CUDA_FOUND) - + endif() + + if(SYCL) + target_link_libraries(${_target} relion_sycl_util sycl OpenCL relion_lib) + list(FIND SYCL_TARGETS ${_target} IS_SYCL_TARGET) + if(NOT ${IS_SYCL_TARGET} LESS 0) + if(DEFINED SYCL_CUDA_COMPILE OR DEFINED SYCL_HIP_COMPILE) + if(SYCL_CUDA_COMPILE) + set(REL_SYCL_TARGET_LINK -Wno-unknown-cuda-version) + endif(SYCL_CUDA_COMPILE) + set(REL_SYCL_TARGET_LINK ${REL_SYCL_TARGET_LINK} -fsycl -fsycl-device-code-split=per_kernel -fsycl-targets=${REL_SYCL_TARGETS},spir64 ${REL_SYCL_BACKEND}) + else(DEFINED SYCL_CUDA_COMPILE OR DEFINED SYCL_HIP_COMPILE) + set(REL_SYCL_TARGET_LINK -fsycl -fsycl-device-code-split=per_kernel -fsycl-targets=spir64) +# set(REL_SYCL_TARGET_LINK -fsycl -fsycl-device-code-split=per_kernel -fsycl-targets=spir64 -Xsycl-target-backend=spir64 "-options -cl-fast-relaxed-math") + endif(DEFINED SYCL_CUDA_COMPILE OR DEFINED SYCL_HIP_COMPILE) + string(REPLACE ";" " " REL_SYCL_TARGET_LINK_FLAGS "${REL_SYCL_TARGET_LINK}") + set_target_properties(${_target} PROPERTIES LINK_FLAGS "-Wno-unused-command-line-argument ${REL_SYCL_TARGET_LINK_FLAGS}") + endif() + endif(SYCL) + if (ALTCPU) target_link_libraries(${_target} ${TBB_LIBRARIES}) endif(ALTCPU) set_target_properties(${_target} PROPERTIES - XX_STANDARD 11 - XX_STANDARD_REQUIRED ON XX_EXTENSIONS OFF ) - + list(FIND GUI_TARGETS ${_target} IS_GUI_TARGET) if((NOT ${IS_GUI_TARGET} LESS 0) OR (${_target} STREQUAL "relion")) add_dependencies(${_target} relion_gui_lib) @@ -371,86 +483,107 @@ foreach (_target ${RELION_TARGETS}) if(TIFF_FOUND) target_link_libraries(${_target} ${TIFF_LIBRARIES}) endif() - + + list(FIND RELION_MPI_TARGETS ${_target}.cpp IS_MPI_TARGET) + if(NOT ${IS_MPI_TARGET} LESS 0) + target_link_libraries(${_target} ${MPI_LIBRARIES}) + endif() + install (TARGETS ${_target} RUNTIME DESTINATION bin) endforeach() -# Python script for running pytorch is required for class_ranker along with torch model -if(FETCH_TORCH_MODELS) - add_dependencies(class_ranker copy_scripts class_ranker_model_file) -endif() - foreach (_target ${RELION_JAZ_SPA_TARGETS}) - GET_FILENAME_COMPONENT(_target "relion_${_target}" NAME_WE) + GET_FILENAME_COMPONENT(_target "relion_${_target}" NAME_WE) add_executable(${_target} ${CMAKE_SOURCE_DIR}/src/jaz/single_particle/apps/${_target}.cpp) - set(LIB relion_lib) - add_dependencies(${_target} relion_lib) + set(LIB relion_lib) + add_dependencies(${_target} relion_lib) set_target_properties(${_target} PROPERTIES PREFIX "relion_") target_link_libraries(${_target} ${LIB} ${FFTW_LIBRARIES} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) - set_target_properties(${_target} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF ) - + set_target_properties(${_target} PROPERTIES CXX_EXTENSIONS OFF ) + if(TIFF_FOUND) target_link_libraries(${_target} ${TIFF_LIBRARIES}) endif() + if(SYCL) + target_link_libraries(${_target} sycl OpenCL relion_lib) + endif(SYCL) + if (ALTCPU) target_link_libraries(${_target} ${TBB_LIBRARIES}) endif(ALTCPU) - + install (TARGETS ${_target} RUNTIME DESTINATION bin) endforeach() # former Dynamite programs: foreach (_target ${RELION_JAZ_TOMO_TARGETS}) - GET_FILENAME_COMPONENT(_target "relion_tomo_${_target}" NAME_WE) + GET_FILENAME_COMPONENT(_target "relion_tomo_${_target}" NAME_WE) add_executable(${_target} ${CMAKE_SOURCE_DIR}/src/jaz/tomography/apps/${_target}.cpp) - set(LIB relion_lib) - add_dependencies(${_target} relion_lib) + set(LIB relion_lib) + add_dependencies(${_target} relion_lib) set_target_properties(${_target} PROPERTIES PREFIX "relion_tomo_") target_link_libraries(${_target} ${LIB} ${FFTW_LIBRARIES} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) - set_target_properties(${_target} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF ) - + set_target_properties(${_target} PROPERTIES CXX_EXTENSIONS OFF ) + if(TIFF_FOUND) target_link_libraries(${_target} ${TIFF_LIBRARIES}) endif() - + + if(SYCL) + target_link_libraries(${_target} sycl OpenCL relion_lib) + endif(SYCL) + install (TARGETS ${_target} RUNTIME DESTINATION bin) endforeach() foreach (_target ${RELION_JAZ_EXPERIMENTAL_TARGETS}) - GET_FILENAME_COMPONENT(_target "relion_exp_${_target}" NAME_WE) - add_executable(${_target} ${CMAKE_SOURCE_DIR}/src/jaz/scripts/${_target}.cpp) - set(LIB relion_lib) - add_dependencies(${_target} relion_lib) + GET_FILENAME_COMPONENT(_target "relion_exp_${_target}" NAME_WE) + add_executable(${_target} ${CMAKE_SOURCE_DIR}/src/jaz/scripts/${_target}.cpp) + set(LIB relion_lib) + add_dependencies(${_target} relion_lib) set_target_properties(${_target} PROPERTIES PREFIX "relion_exp_") target_link_libraries(${_target} ${LIB} ${FFTW_LIBRARIES} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) - set_target_properties(${_target} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF ) - + set_target_properties(${_target} PROPERTIES CXX_EXTENSIONS OFF ) + if(TIFF_FOUND) target_link_libraries(${_target} ${TIFF_LIBRARIES}) endif() + if(SYCL) + target_link_libraries(${_target} sycl OpenCL relion_lib) + endif(SYCL) + if (ALTCPU) target_link_libraries(${_target} ${TBB_LIBRARIES}) endif(ALTCPU) - + install (TARGETS ${_target} RUNTIME DESTINATION bin) endforeach() -FIND_PACKAGE( OpenMP REQUIRED) -if(OPENMP_FOUND) - #message("OPENMP FOUND") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") - target_link_libraries(relion_lib ${OpenMP_omp_LIBRARY}) -endif() +if(SYCL) # Intel DPC++ specific + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fiopenmp") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fiopenmp") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fiopenmp") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(CMAKE_C_FLAGS "-std=c99 ${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS "-fPIC -std=c++17 ${CMAKE_CXX_FLAGS}") +else() + FIND_PACKAGE( OpenMP REQUIRED) + if(OPENMP_FOUND) + #message("OPENMP FOUND") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") + target_link_libraries(relion_lib ${OpenMP_omp_LIBRARY}) + endif() + + set(CMAKE_C_FLAGS "-std=c99 ${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS "-fPIC -std=c++14 ${CMAKE_CXX_FLAGS}") +endif() # Set this flag to activate bounds checking in stl-vectors (incl. strings) -# It is useful to do this periodically, as it catches +# It is useful to do this periodically, as it catches # difficult-to-see and rare-to-manifest bugs # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_DEBUG") diff --git a/src/apps/autopick.cpp b/src/apps/autopick.cpp index 322bb8d35..65f30be31 100644 --- a/src/apps/autopick.cpp +++ b/src/apps/autopick.cpp @@ -20,6 +20,8 @@ #include #ifdef _CUDA_ENABLED #include +#elif _HIP_ENABLED +#include #endif int main(int argc, char *argv[]) @@ -32,7 +34,7 @@ int main(int argc, char *argv[]) prm.initialise(); -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED std::stringstream didSs; if (prm.do_gpu) { @@ -42,8 +44,8 @@ int main(int argc, char *argv[]) if (prm.do_gpu && !(prm.do_topaz_train || prm.do_topaz_extract)) { - prm.cudaPicker = (void*) new AutoPickerCuda((AutoPicker*)&prm, didSs.str().c_str()); - ((AutoPickerCuda*)prm.cudaPicker)->run(); + prm.gpuPicker = (void*) new AutoPickerAccGPU((AutoPicker*)&prm, didSs.str().c_str()); + ((AutoPickerAccGPU*)prm.gpuPicker)->run(); } else #endif diff --git a/src/apps/autopick_mpi.cpp b/src/apps/autopick_mpi.cpp index 257520331..63e7de74f 100644 --- a/src/apps/autopick_mpi.cpp +++ b/src/apps/autopick_mpi.cpp @@ -20,6 +20,8 @@ #include #ifdef _CUDA_ENABLED #include +#elif _HIP_ENABLED +#include #endif int main(int argc, char *argv[]) @@ -32,7 +34,7 @@ int main(int argc, char *argv[]) prm.initialise(prm.getRank()); -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED std::stringstream didSs; if (prm.do_gpu) { @@ -42,8 +44,8 @@ int main(int argc, char *argv[]) if (prm.do_gpu && !(prm.do_topaz_train || prm.do_topaz_extract)) { - prm.cudaPicker = (void*) new AutoPickerCuda((AutoPickerMpi*)&prm, didSs.str().c_str()); - ((AutoPickerCuda*)prm.cudaPicker)->run(); + prm.gpuPicker = (void*) new AutoPickerAccGPU((AutoPickerMpi*)&prm, didSs.str().c_str()); + ((AutoPickerAccGPU*)prm.gpuPicker)->run(); } else #endif diff --git a/src/apps/estimate_gain.cpp b/src/apps/estimate_gain.cpp index 7f6c049a6..4bdb72d5a 100644 --- a/src/apps/estimate_gain.cpp +++ b/src/apps/estimate_gain.cpp @@ -55,7 +55,7 @@ class estimate_gain max_frames = textToInteger(parser.getOption("--max_frames", "Target number of frames to average (rounded to movies; -1 means use all)", "-1")); randomise_order = parser.checkOption("--random", "Randomise the order of input movies before taking subset"); dont_invert = parser.checkOption("--dont_invert", "Don't take the inverse but simply writes the sum"); - eer_upsampling = textToInteger(parser.getOption("--eer_upsampling", "EER upsampling (1 = 4K or 2 = 8K)", "2")); + eer_upsampling = textToInteger(parser.getOption("--eer_upsampling", "EER upsampling (1 = physical or 2 = 2x super-resolution)", "2")); // --eer_upsampling 3 is only for debugging. Hidden. if (eer_upsampling != 1 && eer_upsampling != 2 && eer_upsampling != 3) REPORT_ERROR("eer_upsampling must be 1, 2 or 3"); diff --git a/src/apps/merge_particles.cpp b/src/apps/merge_particles.cpp index 17993da72..0ed0d54f0 100644 --- a/src/apps/merge_particles.cpp +++ b/src/apps/merge_particles.cpp @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) } } - ObservationModel::saveNew(particleOut, opticsOut, destFn); + ObservationModel::saveNew(particleOut, opticsOut, obsModels[0].generalMdt, destFn); return RELION_EXIT_SUCCESS; } diff --git a/src/apps/movie_reconstruct.cpp b/src/apps/movie_reconstruct.cpp index 591278761..3329b96c3 100644 --- a/src/apps/movie_reconstruct.cpp +++ b/src/apps/movie_reconstruct.cpp @@ -289,16 +289,6 @@ void MovieReconstructor::backproject(int rank, int size) eer_grouping = requested_eer_grouping; } - FileName fn_gain = mic.getGainFilename(); - if (fn_gain != prev_gain) - { - if (isEER) - EERRenderer::loadEERGain(fn_gain, Igain(), eer_upsampling); - else - Igain.read(fn_gain); - prev_gain = fn_gain; - } - // Read trajectories. Both particle ID and frame ID are 0-indexed in this array. std::vector> trajectories = MotionHelper::readTracksInPix(fn_traj, movie_angpix); @@ -322,6 +312,17 @@ void MovieReconstructor::backproject(int rank, int size) const int w0 = XSIZE(Iframe()); const int h0 = YSIZE(Iframe()); + // Read the gain reference + FileName fn_gain = mic.getGainFilename(); + if (fn_gain != prev_gain) + { + if (isEER) + renderer.loadEERGain(fn_gain, Igain()); + else + Igain.read(fn_gain); + prev_gain = fn_gain; + } + // Apply gain correction // Probably we can ignore defect correction, because we are not re-aligning. if (fn_gain == "") diff --git a/src/apps/particle_reposition.cpp b/src/apps/particle_reposition.cpp index db0f0d7b1..c7ef86b99 100644 --- a/src/apps/particle_reposition.cpp +++ b/src/apps/particle_reposition.cpp @@ -157,16 +157,15 @@ class particle_reposition_parameters bool found_one = false; for (long int part_id = 0; part_id < optimiser.mydata.numberOfParticles(); part_id++) { - long int ori_img_id = optimiser.mydata.particles[part_id].images[0].id; - int optics_group = optimiser.mydata.getOpticsGroup(part_id, 0); - RFLOAT my_pixel_size = optimiser.mydata.getImagePixelSize(part_id, 0); + int optics_group = optimiser.mydata.getOpticsGroup(part_id); + RFLOAT my_pixel_size = optimiser.mydata.getImagePixelSize(part_id); int my_image_size = optimiser.mydata.getOpticsImageSize(optics_group); if (do_subtract && fabs(my_pixel_size - mic_pixel_size) > 1e-6) REPORT_ERROR("ERROR: subtract code has only been validated with same pixel size for particles and micrographs... Sorry!"); FileName fn_mic2; - optimiser.mydata.MDimg.getValue(EMDL_MICROGRAPH_NAME, fn_mic2, ori_img_id); + optimiser.mydata.MDimg.getValue(EMDL_MICROGRAPH_NAME, fn_mic2, part_id); FileName fn_mic2_pre, fn_mic2_jobnr, fn_mic2_post; decomposePipelineFileName(fn_mic2, fn_mic2_pre, fn_mic2_jobnr, fn_mic2_post); @@ -197,23 +196,23 @@ class particle_reposition_parameters MDcoord.addObject(); - MDcoord.setObject(optimiser.mydata.MDimg.getObject(ori_img_id)); + MDcoord.setObject(optimiser.mydata.MDimg.getObject(part_id)); MDcoord.setValue(EMDL_MICROGRAPH_NAME,fn_mic_out); - optimiser.mydata.MDimg.getValue(EMDL_IMAGE_COORD_X, xcoord, ori_img_id); - optimiser.mydata.MDimg.getValue(EMDL_IMAGE_COORD_Y, ycoord, ori_img_id); + optimiser.mydata.MDimg.getValue(EMDL_IMAGE_COORD_X, xcoord, part_id); + optimiser.mydata.MDimg.getValue(EMDL_IMAGE_COORD_Y, ycoord, part_id); if (optimiser.mymodel.ref_dim == 3) { - optimiser.mydata.MDimg.getValue(EMDL_ORIENT_ROT, rot, ori_img_id); - optimiser.mydata.MDimg.getValue(EMDL_ORIENT_TILT, tilt, ori_img_id); + optimiser.mydata.MDimg.getValue(EMDL_ORIENT_ROT, rot, part_id); + optimiser.mydata.MDimg.getValue(EMDL_ORIENT_TILT, tilt, part_id); } - optimiser.mydata.MDimg.getValue(EMDL_ORIENT_PSI, psi, ori_img_id); - optimiser.mydata.MDimg.getValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, XX(offsets), ori_img_id); - optimiser.mydata.MDimg.getValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, YY(offsets), ori_img_id); + optimiser.mydata.MDimg.getValue(EMDL_ORIENT_PSI, psi, part_id); + optimiser.mydata.MDimg.getValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, XX(offsets), part_id); + optimiser.mydata.MDimg.getValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, YY(offsets), part_id); if (optimiser.mymodel.data_dim == 3) { - optimiser.mydata.MDimg.getValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, ZZ(offsets), ori_img_id); - optimiser.mydata.MDimg.getValue(EMDL_IMAGE_COORD_Z, zcoord, ori_img_id); + optimiser.mydata.MDimg.getValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, ZZ(offsets), part_id); + optimiser.mydata.MDimg.getValue(EMDL_IMAGE_COORD_Z, zcoord, part_id); } else { @@ -223,7 +222,7 @@ class particle_reposition_parameters // Offsets in pixels offsets /= my_pixel_size; - optimiser.mydata.MDimg.getValue(EMDL_PARTICLE_CLASS, iclass, ori_img_id); + optimiser.mydata.MDimg.getValue(EMDL_PARTICLE_CLASS, iclass, part_id); iclass--; Euler_angles2matrix(rot, tilt, psi, A); @@ -253,7 +252,7 @@ class particle_reposition_parameters Image Ictf; FileName fn_ctf; - optimiser.mydata.MDimg.getValue(EMDL_CTF_IMAGE, fn_ctf, ori_img_id); + optimiser.mydata.MDimg.getValue(EMDL_CTF_IMAGE, fn_ctf, part_id); Ictf.read(fn_ctf); // If there is a redundant half, get rid of it @@ -279,7 +278,7 @@ class particle_reposition_parameters } else { - ctf.readByGroup(optimiser.mydata.MDimg, &optimiser.mydata.obsModel, ori_img_id); + ctf.readByGroup(optimiser.mydata.MDimg, &optimiser.mydata.obsModel, part_id); ctf.getFftwImage(Fctf, my_image_size, my_image_size, my_pixel_size, optimiser.ctf_phase_flipped, false, optimiser.intact_ctf_first_peak, true); } @@ -305,7 +304,7 @@ class particle_reposition_parameters if (optimiser.do_scale_correction) { - int group_id = optimiser.mydata.getGroupId(part_id, 0); + int group_id = optimiser.mydata.getGroupId(part_id); RFLOAT myscale = optimiser.mymodel.scale_correction[group_id]; FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Fref) { @@ -354,8 +353,8 @@ class particle_reposition_parameters RFLOAT part_avg, part_stdev; if (optimiser.do_helical_refine) { - optimiser.mydata.MDimg.getValue(EMDL_ORIENT_TILT_PRIOR, tilt_deg, ori_img_id); - optimiser.mydata.MDimg.getValue(EMDL_ORIENT_PSI_PRIOR, psi_deg, ori_img_id); + optimiser.mydata.MDimg.getValue(EMDL_ORIENT_TILT_PRIOR, tilt_deg, part_id); + optimiser.mydata.MDimg.getValue(EMDL_ORIENT_PSI_PRIOR, psi_deg, part_id); } calculateBackgroundAvgStddev(Ipart, part_avg, norm_factor, norm_radius, optimiser.do_helical_refine, @@ -365,7 +364,7 @@ class particle_reposition_parameters if (optimiser.do_norm_correction) { RFLOAT mynorm; - optimiser.mydata.MDimg.getValue(EMDL_IMAGE_NORM_CORRECTION, mynorm, ori_img_id); + optimiser.mydata.MDimg.getValue(EMDL_IMAGE_NORM_CORRECTION, mynorm, part_id); // TODO: check whether this is the right way around!!! norm_factor *= mynorm/optimiser.mymodel.avg_norm_correction; } diff --git a/src/apps/run_align_tiltseries.cpp b/src/apps/run_align_tiltseries.cpp new file mode 100644 index 000000000..29b93755b --- /dev/null +++ b/src/apps/run_align_tiltseries.cpp @@ -0,0 +1,41 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * 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. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#include + + +int main(int argc, char *argv[]) +{ + AlignTiltseriesRunner prm; + + try + { + prm.read(argc, argv); + prm.initialise(); + prm.run(); + } + catch (RelionError XE) + { + //prm.usage(); + std::cerr << XE; + return RELION_EXIT_FAILURE; + } + + return RELION_EXIT_SUCCESS; +} diff --git a/src/apps/run_align_tiltseries_mpi.cpp b/src/apps/run_align_tiltseries_mpi.cpp new file mode 100644 index 000000000..88fbb43b6 --- /dev/null +++ b/src/apps/run_align_tiltseries_mpi.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * 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. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#include + + +int main(int argc, char *argv[]) +{ + AlignTiltseriesRunnerMpi prm; + + try + { + prm.read(argc, argv); + prm.initialise(prm.node->isLeader()); + MPI_Barrier(MPI_COMM_WORLD); + prm.run(); + } + catch (RelionError XE) + { + if (prm.verb > 0) + //prm.usage(); + std::cerr << XE; + MPI_Abort(MPI_COMM_WORLD, RELION_EXIT_FAILURE); + } + + MPI_Barrier(MPI_COMM_WORLD); + return RELION_EXIT_SUCCESS; +} diff --git a/src/apps/stack_create.cpp b/src/apps/stack_create.cpp index 36ff4586c..24a76fa54 100644 --- a/src/apps/stack_create.cpp +++ b/src/apps/stack_create.cpp @@ -36,7 +36,7 @@ class stack_create_parameters MetaDataTable MD; // I/O Parser IOParser parser; - bool do_split_per_micrograph, do_apply_trans, do_apply_trans_only, do_ignore_optics, do_one_by_one; + bool do_split_per_micrograph, do_apply_trans, do_apply_trans_only, do_ignore_optics, do_one_by_one, do_float16; ObservationModel obsModel; void usage() @@ -56,6 +56,7 @@ class stack_create_parameters do_apply_trans_only = parser.checkOption("--apply_rounded_offsets_only", "Apply the rounded translations only (so-recentering without interpolation; needs _rlnOriginX/Y in STAR file)"); do_ignore_optics = parser.checkOption("--ignore_optics", "Ignore optics groups. This allows you to read and write RELION 3.0 STAR files but does NOT allow you to convert 3.1 STAR files back to the 3.0 format."); do_one_by_one = parser.checkOption("--one_by_one", "Write particles one by one. This saves memory but can be slower."); + do_float16 = parser.checkOption("--float16", "Write images in 16bit float format (default is 32bit)."); if (do_apply_trans) std::cerr << "WARNING: --apply_transformation uses real space interpolation. It also invalidates CTF parameters (e.g. beam tilt & astigmatism). This can degrade the resolution. USE WITH CARE!!" << std::endl; @@ -232,10 +233,10 @@ class stack_create_parameters } else { - if (n == 0) - in.write(fn_img, -1, false, WRITE_OVERWRITE); + if (n == 0) + in.write(fn_img, -1, false, WRITE_OVERWRITE, do_float16 ? Float16: Float); else - in.write(fn_img, -1, true, WRITE_APPEND); + in.write(fn_img, -1, true, WRITE_APPEND, do_float16 ? Float16: Float); } n++; @@ -245,7 +246,7 @@ class stack_create_parameters progress_bar(ndim); if (!do_one_by_one) - out.write(fn_out); + out.write(fn_out, -1, true, WRITE_OVERWRITE, do_float16 ? Float16: Float); std::cout << "Written out: " << fn_out << std::endl; } diff --git a/src/apps/star_handler.cpp b/src/apps/star_handler.cpp index 3d47c8877..3f8ababe1 100644 --- a/src/apps/star_handler.cpp +++ b/src/apps/star_handler.cpp @@ -260,7 +260,10 @@ class star_handler_parameters else MDout = subsetMetaDataTable(MDin, EMDL::str2Label(select_str_label), select_exclude_str, true); - write_check_ignore_optics(MDout, fn_out, MDin.getName()); + // Remove optics groups that are no longer in use, and renumber in MDout + obsModel.removeUnusedOpticsGroups(MDout); + + write_check_ignore_optics(MDout, fn_out, MDin.getName()); std::cout << " Written: " << fn_out << std::endl; } @@ -341,7 +344,7 @@ class star_handler_parameters std::vector fns_in; std::vector words; tokenize(fn_in, words); - for (int iword = 0; iword < words.size(); iword++) + for (int iword = 0; iword < words.size(); iword++) { FileName fnt = words[iword]; const int n_files = fns_in.size(); @@ -353,307 +356,307 @@ class star_handler_parameters if (fns_in.size() == 0) REPORT_ERROR("No STAR files to combine."); - MetaDataTable MDin0, MDout; - std::vector MDsin, MDoptics; - std::vector obsModels; - ObservationModel myobsModel0; - // Read the first table into the global obsModel - if (do_ignore_optics) MDin0.read(fns_in[0], tablename_in); - else ObservationModel::loadSafely(fns_in[0], obsModel, MDin0, "discover", 1); - MDsin.push_back(MDin0); - // Read all the rest of the tables into local obsModels - for (int i = 1; i < fns_in.size(); i++) - { - ObservationModel myobsModel; - MetaDataTable MDin; // define again, as reading from previous one may linger here... - if (do_ignore_optics) MDin.read(fns_in[i], tablename_in); - else ObservationModel::loadSafely(fns_in[i], myobsModel, MDin, "discover", 1); - MDsin.push_back(MDin); - obsModels.push_back(myobsModel); - } - - // Combine optics groups with the same EMDL_IMAGE_OPTICS_GROUP_NAME, make new ones for those with a different name - if (!do_ignore_optics) - { - std::vector optics_group_uniq_names; - - // Initialise optics_group_uniq_names with the first table - FOR_ALL_OBJECTS_IN_METADATA_TABLE(obsModel.opticsMdt) - { - std::string myname; - obsModel.opticsMdt.getValue(EMDL_IMAGE_OPTICS_GROUP_NAME, myname); - optics_group_uniq_names.push_back(myname); - } - - // Now check uniqueness of the other tables - for (int MDs_id = 1; MDs_id < fns_in.size(); MDs_id++) - { - const int obs_id = MDs_id - 1; - - std::vector new_optics_groups; - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDsin[MDs_id]) - { - int tmp; - MDsin[MDs_id].getValue(EMDL_IMAGE_OPTICS_GROUP, tmp); - new_optics_groups.push_back(tmp); - } - - MetaDataTable unique_opticsMdt; - unique_opticsMdt.addMissingLabels(&obsModels[obs_id].opticsMdt); - - FOR_ALL_OBJECTS_IN_METADATA_TABLE(obsModels[obs_id].opticsMdt) - { - std::string myname; - int my_optics_group; - obsModels[obs_id].opticsMdt.getValue(EMDL_IMAGE_OPTICS_GROUP_NAME, myname); - obsModels[obs_id].opticsMdt.getValue(EMDL_IMAGE_OPTICS_GROUP, my_optics_group); - - // Check whether this name is unique - bool is_uniq = true; - int new_group; - for (new_group = 0; new_group < optics_group_uniq_names.size(); new_group++) - { - if (optics_group_uniq_names[new_group] == myname) - { - is_uniq = false; - break; - } - } - new_group ++; // start counting of groups at 1, not 0! - - if (is_uniq) - { - std::cout << " + Adding new optics_group with name: " << myname << std::endl; - - optics_group_uniq_names.push_back(myname); - // Add the line to the global obsModel - obsModels[obs_id].opticsMdt.setValue(EMDL_IMAGE_OPTICS_GROUP, new_group); - - unique_opticsMdt.addObject(); - unique_opticsMdt.setObject(obsModels[obs_id].opticsMdt.getObject()); - } - else - { - std::cout << " + Joining optics_groups with the same name: " << myname << std::endl; - std::cerr << " + WARNING: if these are different data sets, you might want to rename optics groups instead of joining them!" << std::endl; - std::cerr << " + WARNING: if so, manually edit the rlnOpticsGroupName column in the optics_groups table of your input STAR files." << std::endl; - } - - if (my_optics_group != new_group) - { - std::cout << " + Renumbering group " << myname << " from " << my_optics_group << " to " << new_group << std::endl; - } - - // Update the optics_group entry for all particles in the MDsin - for (long int current_object2 = MDsin[MDs_id].firstObject(); - current_object2 < MDsin[MDs_id].numberOfObjects() && current_object2 >= 0; - current_object2 = MDsin[MDs_id].nextObject()) - { - int old_optics_group; - MDsin[MDs_id].getValue(EMDL_IMAGE_OPTICS_GROUP, old_optics_group, current_object2); - if (old_optics_group == my_optics_group) - new_optics_groups[current_object2] = new_group; - } - } - - obsModels[obs_id].opticsMdt = unique_opticsMdt; - - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDsin[MDs_id]) - { - MDsin[MDs_id].setValue(EMDL_IMAGE_OPTICS_GROUP, new_optics_groups[current_object]); - - // Also rename the rlnGroupName to not have groups overlapping from different optics groups - std::string name; - if (MDsin[MDs_id].getValue(EMDL_MLMODEL_GROUP_NAME, name)) - { - name = "optics"+integerToString(new_optics_groups[current_object])+"_"+name; - MDsin[MDs_id].setValue(EMDL_MLMODEL_GROUP_NAME, name); - } - } - } - - // Make one vector for combination of the optics tables - MDoptics.push_back(obsModel.opticsMdt); - for (int i = 1; i < fns_in.size(); i++) - { - MDoptics.push_back(obsModels[i - 1].opticsMdt); - } - - // Check if anisotropic magnification and/or beam_tilt are present in some optics groups, but not in others. - // If so, initialise the others correctly - bool has_beamtilt = false, has_not_beamtilt = false; - bool has_anisomag = false, has_not_anisomag = false; - bool has_odd_zernike = false, has_not_odd_zernike = false; - bool has_even_zernike = false, has_not_even_zernike = false; - bool has_ctf_premultiplied = false, has_not_ctf_premultiplied = false; - for (int i = 0; i < fns_in.size(); i++) - { - if (MDoptics[i].containsLabel(EMDL_IMAGE_BEAMTILT_X) || - MDoptics[i].containsLabel(EMDL_IMAGE_BEAMTILT_Y)) - { - has_beamtilt = true; - } - else - { - has_not_beamtilt = true; - } - if (MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_00) && - MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_01) && - MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_10) && - MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_11)) - { - has_anisomag = true; - } - else - { - has_not_anisomag = true; - } - if (MDoptics[i].containsLabel(EMDL_IMAGE_ODD_ZERNIKE_COEFFS)) - { - has_odd_zernike = true; - } - else - { - has_not_odd_zernike = true; - } - if (MDoptics[i].containsLabel(EMDL_IMAGE_EVEN_ZERNIKE_COEFFS)) - { - has_even_zernike = true; - } - else - { - has_not_even_zernike = true; - } - if (MDoptics[i].containsLabel(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED)) - { - has_ctf_premultiplied = true; - } - else - { - has_not_ctf_premultiplied = true; - } - } + MetaDataTable MDin0, MDout; + std::vector MDsin, MDoptics; + std::vector obsModels; + ObservationModel myobsModel0; + // Read the first table into the global obsModel + if (do_ignore_optics) MDin0.read(fns_in[0], tablename_in); + else ObservationModel::loadSafely(fns_in[0], obsModel, MDin0, "discover", 1); + MDsin.push_back(MDin0); + // Read all the rest of the tables into local obsModels + for (int i = 1; i < fns_in.size(); i++) + { + ObservationModel myobsModel; + MetaDataTable MDin; // define again, as reading from previous one may linger here... + if (do_ignore_optics) MDin.read(fns_in[i], tablename_in); + else ObservationModel::loadSafely(fns_in[i], myobsModel, MDin, "discover", 1); + MDsin.push_back(MDin); + obsModels.push_back(myobsModel); + } + + // Combine optics groups with the same EMDL_IMAGE_OPTICS_GROUP_NAME, make new ones for those with a different name + if (!do_ignore_optics) + { + std::vector optics_group_uniq_names; + + // Initialise optics_group_uniq_names with the first table + FOR_ALL_OBJECTS_IN_METADATA_TABLE(obsModel.opticsMdt) + { + std::string myname; + obsModel.opticsMdt.getValue(EMDL_IMAGE_OPTICS_GROUP_NAME, myname); + optics_group_uniq_names.push_back(myname); + } + + // Now check uniqueness of the other tables + for (int MDs_id = 1; MDs_id < fns_in.size(); MDs_id++) + { + const int obs_id = MDs_id - 1; + + std::vector new_optics_groups; + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDsin[MDs_id]) + { + int tmp; + MDsin[MDs_id].getValue(EMDL_IMAGE_OPTICS_GROUP, tmp); + new_optics_groups.push_back(tmp); + } + + MetaDataTable unique_opticsMdt; + unique_opticsMdt.addMissingLabels(&obsModels[obs_id].opticsMdt); + + FOR_ALL_OBJECTS_IN_METADATA_TABLE(obsModels[obs_id].opticsMdt) + { + std::string myname; + int my_optics_group; + obsModels[obs_id].opticsMdt.getValue(EMDL_IMAGE_OPTICS_GROUP_NAME, myname); + obsModels[obs_id].opticsMdt.getValue(EMDL_IMAGE_OPTICS_GROUP, my_optics_group); + + // Check whether this name is unique + bool is_uniq = true; + int new_group; + for (new_group = 0; new_group < optics_group_uniq_names.size(); new_group++) + { + if (optics_group_uniq_names[new_group] == myname) + { + is_uniq = false; + break; + } + } + new_group ++; // start counting of groups at 1, not 0! + + if (is_uniq) + { + std::cout << " + Adding new optics_group with name: " << myname << std::endl; + + optics_group_uniq_names.push_back(myname); + // Add the line to the global obsModel + obsModels[obs_id].opticsMdt.setValue(EMDL_IMAGE_OPTICS_GROUP, new_group); + + unique_opticsMdt.addObject(); + unique_opticsMdt.setObject(obsModels[obs_id].opticsMdt.getObject()); + } + else + { + std::cout << " + Joining optics_groups with the same name: " << myname << std::endl; + std::cerr << " + WARNING: if these are different data sets, you might want to rename optics groups instead of joining them!" << std::endl; + std::cerr << " + WARNING: if so, manually edit the rlnOpticsGroupName column in the optics_groups table of your input STAR files." << std::endl; + } + + if (my_optics_group != new_group) + { + std::cout << " + Renumbering group " << myname << " from " << my_optics_group << " to " << new_group << std::endl; + } + + // Update the optics_group entry for all particles in the MDsin + for (long int current_object2 = MDsin[MDs_id].firstObject(); + current_object2 < MDsin[MDs_id].numberOfObjects() && current_object2 >= 0; + current_object2 = MDsin[MDs_id].nextObject()) + { + int old_optics_group; + MDsin[MDs_id].getValue(EMDL_IMAGE_OPTICS_GROUP, old_optics_group, current_object2); + if (old_optics_group == my_optics_group) + new_optics_groups[current_object2] = new_group; + } + } + + obsModels[obs_id].opticsMdt = unique_opticsMdt; + + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDsin[MDs_id]) + { + MDsin[MDs_id].setValue(EMDL_IMAGE_OPTICS_GROUP, new_optics_groups[current_object]); + + // Also rename the rlnGroupName to not have groups overlapping from different optics groups + std::string name; + if (MDsin[MDs_id].getValue(EMDL_MLMODEL_GROUP_NAME, name)) + { + name = "optics"+integerToString(new_optics_groups[current_object])+"_"+name; + MDsin[MDs_id].setValue(EMDL_MLMODEL_GROUP_NAME, name); + } + } + } + + // Make one vector for combination of the optics tables + MDoptics.push_back(obsModel.opticsMdt); + for (int i = 1; i < fns_in.size(); i++) + { + MDoptics.push_back(obsModels[i - 1].opticsMdt); + } + + // Check if anisotropic magnification and/or beam_tilt are present in some optics groups, but not in others. + // If so, initialise the others correctly + bool has_beamtilt = false, has_not_beamtilt = false; + bool has_anisomag = false, has_not_anisomag = false; + bool has_odd_zernike = false, has_not_odd_zernike = false; + bool has_even_zernike = false, has_not_even_zernike = false; + bool has_ctf_premultiplied = false, has_not_ctf_premultiplied = false; + for (int i = 0; i < fns_in.size(); i++) + { + if (MDoptics[i].containsLabel(EMDL_IMAGE_BEAMTILT_X) || + MDoptics[i].containsLabel(EMDL_IMAGE_BEAMTILT_Y)) + { + has_beamtilt = true; + } + else + { + has_not_beamtilt = true; + } + if (MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_00) && + MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_01) && + MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_10) && + MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_11)) + { + has_anisomag = true; + } + else + { + has_not_anisomag = true; + } + if (MDoptics[i].containsLabel(EMDL_IMAGE_ODD_ZERNIKE_COEFFS)) + { + has_odd_zernike = true; + } + else + { + has_not_odd_zernike = true; + } + if (MDoptics[i].containsLabel(EMDL_IMAGE_EVEN_ZERNIKE_COEFFS)) + { + has_even_zernike = true; + } + else + { + has_not_even_zernike = true; + } + if (MDoptics[i].containsLabel(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED)) + { + has_ctf_premultiplied = true; + } + else + { + has_not_ctf_premultiplied = true; + } + } #ifdef DEBUG - printf("has_beamtilt = %d, has_not_beamtilt = %d, has_anisomag = %d, has_not_anisomag = %d, has_odd_zernike = %d, has_not_odd_zernike = %d, has_even_zernike = %d, has_not_even_zernike = %d, has_ctf_premultiplied = %d, has_not_ctf_premultiplied = %d\n", has_beamtilt, has_not_beamtilt, has_anisomag, has_not_anisomag, has_odd_zernike, has_not_odd_zernike, has_even_zernike, has_not_even_zernike, has_ctf_premultiplied, has_not_ctf_premultiplied); + printf("has_beamtilt = %d, has_not_beamtilt = %d, has_anisomag = %d, has_not_anisomag = %d, has_odd_zernike = %d, has_not_odd_zernike = %d, has_even_zernike = %d, has_not_even_zernike = %d, has_ctf_premultiplied = %d, has_not_ctf_premultiplied = %d\n", has_beamtilt, has_not_beamtilt, has_anisomag, has_not_anisomag, has_odd_zernike, has_not_odd_zernike, has_even_zernike, has_not_even_zernike, has_ctf_premultiplied, has_not_ctf_premultiplied); #endif - for (int i = 0; i < fns_in.size(); i++) - { - if (has_beamtilt && has_not_beamtilt) - { - if (!MDoptics[i].containsLabel(EMDL_IMAGE_BEAMTILT_X)) - { - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) - { - MDoptics[i].setValue(EMDL_IMAGE_BEAMTILT_X, 0.); - } - } - if (!MDoptics[i].containsLabel(EMDL_IMAGE_BEAMTILT_Y)) - { - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) - { - MDoptics[i].setValue(EMDL_IMAGE_BEAMTILT_Y, 0.); - } - } - } - - if (has_anisomag && has_not_anisomag) - { - if (!(MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_00) && - MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_01) && - MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_10) && - MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_11)) ) - { - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) - { - MDoptics[i].setValue(EMDL_IMAGE_MAG_MATRIX_00, 1.); - MDoptics[i].setValue(EMDL_IMAGE_MAG_MATRIX_01, 0.); - MDoptics[i].setValue(EMDL_IMAGE_MAG_MATRIX_10, 0.); - MDoptics[i].setValue(EMDL_IMAGE_MAG_MATRIX_11, 1.); - } - } - } - - if (has_odd_zernike && has_not_odd_zernike) - { - std::vector six_zeros(6, 0); - if (!MDoptics[i].containsLabel(EMDL_IMAGE_ODD_ZERNIKE_COEFFS)) - { - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) - { - MDoptics[i].setValue(EMDL_IMAGE_ODD_ZERNIKE_COEFFS, six_zeros); - } - } - } - - if (has_even_zernike && has_not_even_zernike) - { - std::vector nine_zeros(9, 0); - if (!MDoptics[i].containsLabel(EMDL_IMAGE_EVEN_ZERNIKE_COEFFS)) - { - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) - { - MDoptics[i].setValue(EMDL_IMAGE_EVEN_ZERNIKE_COEFFS, nine_zeros); - } - } - } - - if (has_ctf_premultiplied && has_not_ctf_premultiplied) - { - if (!MDoptics[i].containsLabel(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED)) - { - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) - { - MDoptics[i].setValue(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED, false); - } - } - } - } - - // Now combine all optics tables into one - obsModel.opticsMdt = MetaDataTable::combineMetaDataTables(MDoptics); - } - - // Combine the particles tables - MDout = MetaDataTable::combineMetaDataTables(MDsin); - - //Deactivate the group_name column - MDout.deactivateLabel(EMDL_MLMODEL_GROUP_NO); - - if (fn_check != "") - { - EMDLabel label = EMDL::str2Label(fn_check); - if (!MDout.containsLabel(label)) - REPORT_ERROR("ERROR: the output file does not contain the label to check for duplicates. Is it present in all input files?"); - - /// Don't want to mess up original order, so make a MDsort with only that label... - FileName fn_this, fn_prev = ""; - MetaDataTable MDsort; - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDout) - { - MDout.getValue(label, fn_this); - MDsort.addObject(); - MDsort.setValue(label, fn_this); - } - // sort on the label - MDsort.newSort(label); - long int nr_duplicates = 0; - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDsort) - { - MDsort.getValue(label, fn_this); - if (fn_this == fn_prev) - { - nr_duplicates++; - std::cerr << " WARNING: duplicate entry: " << fn_this << std::endl; - } - fn_prev = fn_this; - } - - if (nr_duplicates > 0) - std::cerr << " WARNING: Total number of duplicate "<< fn_check << " entries: " << nr_duplicates << std::endl; - } - - write_check_ignore_optics(MDout, fn_out, MDin0.getName()); + for (int i = 0; i < fns_in.size(); i++) + { + if (has_beamtilt && has_not_beamtilt) + { + if (!MDoptics[i].containsLabel(EMDL_IMAGE_BEAMTILT_X)) + { + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) + { + MDoptics[i].setValue(EMDL_IMAGE_BEAMTILT_X, 0.); + } + } + if (!MDoptics[i].containsLabel(EMDL_IMAGE_BEAMTILT_Y)) + { + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) + { + MDoptics[i].setValue(EMDL_IMAGE_BEAMTILT_Y, 0.); + } + } + } + + if (has_anisomag && has_not_anisomag) + { + if (!(MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_00) && + MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_01) && + MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_10) && + MDoptics[i].containsLabel(EMDL_IMAGE_MAG_MATRIX_11)) ) + { + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) + { + MDoptics[i].setValue(EMDL_IMAGE_MAG_MATRIX_00, 1.); + MDoptics[i].setValue(EMDL_IMAGE_MAG_MATRIX_01, 0.); + MDoptics[i].setValue(EMDL_IMAGE_MAG_MATRIX_10, 0.); + MDoptics[i].setValue(EMDL_IMAGE_MAG_MATRIX_11, 1.); + } + } + } + + if (has_odd_zernike && has_not_odd_zernike) + { + std::vector six_zeros(6, 0); + if (!MDoptics[i].containsLabel(EMDL_IMAGE_ODD_ZERNIKE_COEFFS)) + { + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) + { + MDoptics[i].setValue(EMDL_IMAGE_ODD_ZERNIKE_COEFFS, six_zeros); + } + } + } + + if (has_even_zernike && has_not_even_zernike) + { + std::vector nine_zeros(9, 0); + if (!MDoptics[i].containsLabel(EMDL_IMAGE_EVEN_ZERNIKE_COEFFS)) + { + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) + { + MDoptics[i].setValue(EMDL_IMAGE_EVEN_ZERNIKE_COEFFS, nine_zeros); + } + } + } + + if (has_ctf_premultiplied && has_not_ctf_premultiplied) + { + if (!MDoptics[i].containsLabel(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED)) + { + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDoptics[i]) + { + MDoptics[i].setValue(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED, false); + } + } + } + } + + // Now combine all optics tables into one + obsModel.opticsMdt = MetaDataTable::combineMetaDataTables(MDoptics); + } + + // Combine the particles tables + MDout = MetaDataTable::combineMetaDataTables(MDsin); + + //Deactivate the group_name column + MDout.deactivateLabel(EMDL_MLMODEL_GROUP_NO); + + if (fn_check != "") + { + EMDLabel label = EMDL::str2Label(fn_check); + if (!MDout.containsLabel(label)) + REPORT_ERROR("ERROR: the output file does not contain the label to check for duplicates. Is it present in all input files?"); + + /// Don't want to mess up original order, so make a MDsort with only that label... + FileName fn_this, fn_prev = ""; + MetaDataTable MDsort; + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDout) + { + MDout.getValue(label, fn_this); + MDsort.addObject(); + MDsort.setValue(label, fn_this); + } + // sort on the label + MDsort.newSort(label); + long int nr_duplicates = 0; + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDsort) + { + MDsort.getValue(label, fn_this); + if (fn_this == fn_prev) + { + nr_duplicates++; + std::cerr << " WARNING: duplicate entry: " << fn_this << std::endl; + } + fn_prev = fn_this; + } + + if (nr_duplicates > 0) + std::cerr << " WARNING: Total number of duplicate "<< fn_check << " entries: " << nr_duplicates << std::endl; + } + + write_check_ignore_optics(MDout, fn_out, MDin0.getName()); std::cout << " Written: " << fn_out << std::endl; } diff --git a/src/apps/suggest_tvalue.cpp b/src/apps/suggest_tvalue.cpp new file mode 100644 index 000000000..426026a03 --- /dev/null +++ b/src/apps/suggest_tvalue.cpp @@ -0,0 +1,174 @@ +/*************************************************************************** +* +* Author: "Sjors H.W. Scheres" +* MRC Laboratory of Molecular Biology + * + * 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. +* +* This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. +***************************************************************************/ + +#include +#include "src/time.h" +#include +#include +#include +#include + +class SuggestTvalue { +public: + // I/O Parser + IOParser parser; + + FileName fn_map, fn_mask; + RFLOAT standard_t; + int n_try; +public: + SuggestTvalue() { } + + // Read command line arguments + void read(int argc, char **argv); + + // Execute + void run(); + + RFLOAT getSumAmpl2(MultidimArray map); + +}; + +void SuggestTvalue::read(int argc, char **argv) +{ + parser.setCommandLine(argc, argv); + + int general_section = parser.addSection("Options"); + fn_map = parser.getOption("--map", "Consensus map"); + fn_mask = parser.getOption("--mask", "Mask used for focussed classification/refinement"); + standard_t = textToFloat(parser.getOption("--T", "Standard T-value", "4")); + n_try = textToInteger(parser.getOption("--try", "Number of times to position mask randomly to find a high-power density area", "10")); + + // Check for errors in the command-line option + if (parser.checkForErrors()) + REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); +} +RFLOAT SuggestTvalue::getSumAmpl2(MultidimArray map) +{ + + FourierTransformer transformer; + MultidimArray FT; + transformer.FourierTransform(map, FT, false); + + RFLOAT result = 0.; + FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM(FT) + { + result += norm(DIRECT_A3D_ELEM(FT, k, i, j)); + } + + return result*NZYXSIZE(map); + +} +void SuggestTvalue::run() +{ + + randomize_random_generator(); + + Image Imap, Imask; + Imap.read(fn_map); + Imask.read(fn_mask); + Imap().setXmippOrigin(); + Imask().setXmippOrigin(); + + if (!Imap().sameShape(Imask())) + { + std::cerr << " Size of map: "; Imap().printShape(std::cerr); std::cerr << std::endl; + std::cerr << " Size of mask: "; Imask().printShape(std::cerr); std::cerr << std::endl; + REPORT_ERROR("SuggestTvalue ERROR: The map and mask are not of the same size!"); + } + + // Check values are between 0 and 1 + RFLOAT avg, stddev, minval, maxval; + Imask().computeStats(avg, stddev, minval, maxval); + + if (minval < -1e-6 || maxval - 1. > 1.e-6) + { + std::cerr << " minval= " << minval << " maxval= " << maxval << std::endl; + REPORT_ERROR("SuggestTvalue ERROR: mask values not in range [0,1]!"); + } + std::cout << " This program provides suggestions for regularization parameter T in focussed classification or refinement " << std::endl; + std::cout << " The mask occupies " << avg*100. << "% of the box" << std::endl; + std::cout << " Standard T-value = " << standard_t << std::endl; + + RFLOAT sum_ori = getSumAmpl2(Imap()); + RFLOAT sum_msk = getSumAmpl2(Imap() * Imask()); + + std::cout << " Randomly shifting mask around the map to find a region with higher power. " << std::endl; + init_progress_bar(n_try+1); + // Place mask in the center of the map + selfTranslateCenterOfMassToCenter(Imask()); + RFLOAT sum_max = getSumAmpl2(Imap() * Imask()); + + // Translate mask n_try times randomly within 1/3 of the box to see if there are stronger powers than at the center + MultidimArray Mshift; + Matrix1D shift(3); + RFLOAT thirdbox = XSIZE(Imap())/3.; + for (int i = 0; i < n_try; i++) + { + XX(shift) = ROUND(rnd_unif(-thirdbox, thirdbox)); + YY(shift) = ROUND(rnd_unif(-thirdbox, thirdbox)); + ZZ(shift) = ROUND(rnd_unif(-thirdbox, thirdbox)); + translate(Imask(), Mshift, shift); + RFLOAT sum_shift = getSumAmpl2(Imap() * Mshift); + if (sum_shift > sum_max) sum_max = sum_shift; + //std::cout << " random shift = " << shift << " sum_shift " << sum_shift << std::endl; + + progress_bar(i+1); + } + progress_bar(n_try+1); + + std::cout << " " << std::endl; + std::cout << " Power of input map = " << sum_ori << std::endl; + std::cout << " Power of masked map = " << sum_msk << std::endl; + std::cout << " Max power of randomly shifted masked map = " << sum_max << std::endl; + + RFLOAT t_min = XMIPP_MIN(standard_t * sum_ori/sum_max, standard_t * sum_ori/sum_msk); + RFLOAT t_max = XMIPP_MAX(standard_t * sum_ori/sum_max, standard_t * sum_ori/sum_msk); + + std::cout << " " << std::endl; + std::cout << " Suggested T-value based on power in masked region: " << standard_t * sum_ori/sum_msk << std::endl; + std::cout << " Suggested T-value based on power in other regions: " << standard_t * sum_ori/sum_max << std::endl; + std::cout << " " << std::endl; + std::cout << " You may want to run focussed classifications or refinements with T-values around these values... " << std::endl; + std::cout << " But, always keep an eye on the noise in the resulting maps: " << std::endl; + std::cout << " Too much high-res noise means your T-value is too high." << std::endl; + std::cout << " Too low-resolution maps mean your T-value is too low." << std::endl; + + +} + +int main(int argc, char *argv[]) +{ + + SuggestTvalue app; + + try { + app.read(argc, argv); + app.run(); + + } + catch (RelionError XE) { + std::cerr << XE; + return RELION_EXIT_FAILURE; + } + return RELION_EXIT_SUCCESS; + +} + diff --git a/src/autopicker.cpp b/src/autopicker.cpp index 05d524a6c..f88deb40a 100644 --- a/src/autopicker.cpp +++ b/src/autopicker.cpp @@ -109,10 +109,10 @@ void AutoPicker::read(int argc, char **argv) do_only_unfinished = parser.checkOption("--only_do_unfinished", "Only autopick those micrographs for which the coordinate file does not yet exist"); do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); -#ifndef _CUDA_ENABLED +#if !defined _CUDA_ENABLED && !defined _HIP_ENABLED if(do_gpu) { - std::cerr << "+ WARNING : Relion was compiled without CUDA of at least version 7.0 - you do NOT have support for GPUs" << std::endl; + std::cerr << "+ WARNING : Relion was compiled without CUDA >= 7.0 or HIP with ROCm >= 4.0 - you do NOT have support for GPUs" << std::endl; do_gpu = false; } #endif @@ -157,10 +157,10 @@ if(do_gpu) topaz_downscale = textToInteger(parser.getOption("--topaz_downscale", "Downscale factor for topaz", "-1")); topaz_model = parser.getOption("--topaz_model", "Saved model model from topaz train for topaz extract. Leave this empty to use the default (general) model.", ""); topaz_radius = textToInteger(parser.getOption("--topaz_radius", "Particle radius (in pix) for topaz extract (default is from particle diameter)", "-1")); - fn_topaz_exe = parser.getOption("--topaz_exe", "Name of topaz executable", "topaz"); topaz_additional_args = parser.getOption("--topaz_args", "Additional arguments to be passed to topaz", ""); topaz_workers = textToInteger(parser.getOption("--topaz_workers", "Number of topaz workers for parallelized training", "1")); do_topaz_plot = parser.checkOption("--topaz_plot", "Plot intermediate information for helical picking in topaz (developmental)"); + fn_topaz_exe = parser.getOption("--fn_topaz_exe", "Topaz executable (default is using relion_python_topaz from conda install)", "relion_python_topaz"); int helix_section = parser.addSection("Helix options"); autopick_helical_segments = parser.checkOption("--helix", "Are the references 2D helical segments? If so, in-plane rotation angles (psi) are estimated for the references."); @@ -971,11 +971,11 @@ void AutoPicker::initialise(int rank) #endif } -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED void AutoPicker::deviceInitialise() { int devCount; - cudaGetDeviceCount(&devCount); + accGPUGetDeviceCount(&devCount); std::vector < std::vector < std::string > > allThreadIDs; untangleDeviceIDs(gpu_ids, allThreadIDs); diff --git a/src/autopicker.h b/src/autopicker.h index f5c80f30f..f2a237f5c 100644 --- a/src/autopicker.h +++ b/src/autopicker.h @@ -27,6 +27,12 @@ #include "src/acc/cuda/cuda_settings.h" #include "src/acc/cuda/cuda_fft.h" #include "src/acc/cuda/cuda_benchmark_utils.h" +#elif _HIP_ENABLED +#include "src/acc/hip/hip_mem_utils.h" +#include "src/acc/acc_projector.h" +#include "src/acc/hip/hip_settings.h" +#include "src/acc/hip/hip_fft.h" +#include "src/acc/hip/hip_benchmark_utils.h" #endif //#define OUTPUT_MEAN_MAP_ONLY 1 //#define OUTPUT_STDDEV_MAP_ONLY 2 @@ -84,7 +90,7 @@ class AutoPicker public: // For GPU-acceleration - void* cudaPicker; + void* gpuPicker; // Available memory (in Gigabyte) RFLOAT available_memory; RFLOAT available_gpu_memory; @@ -191,9 +197,6 @@ class AutoPicker // Number of topaz workers for training int topaz_workers; - // Topaz command executable - FileName fn_topaz_exe; - // sh executable FileName fn_shell; @@ -203,6 +206,9 @@ class AutoPicker // Topaz particle radius for use in extract int topaz_radius; + // Topaz executable + FileName fn_topaz_exe; + // GPU Device ID int device_id = -1; diff --git a/src/autopicker_mpi.cpp b/src/autopicker_mpi.cpp index bfa744fee..20d009b27 100644 --- a/src/autopicker_mpi.cpp +++ b/src/autopicker_mpi.cpp @@ -44,11 +44,11 @@ void AutoPickerMpi::read(int argc, char **argv) printMpiNodesMachineNames(*node); } -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED void AutoPickerMpi::deviceInitialise() { int devCount; - cudaGetDeviceCount(&devCount); + accGPUGetDeviceCount(&devCount); std::vector < std::vector < std::string > > allThreadIDs; untangleDeviceIDs(gpu_ids, allThreadIDs); diff --git a/src/backprojector.cpp b/src/backprojector.cpp index 511fbb80d..e76ff72ad 100644 --- a/src/backprojector.cpp +++ b/src/backprojector.cpp @@ -1078,7 +1078,7 @@ void BackProjector::updateSSNRarrays(RFLOAT tau2_fudge, // Average (inverse of) sigma2 in reconstruction FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY1D(sigma2) { - if (DIRECT_A1D_ELEM(sigma2, i) > 1e-10) + if (DIRECT_A1D_ELEM(sigma2, i) > 1e-20) DIRECT_A1D_ELEM(sigma2, i) = DIRECT_A1D_ELEM(counter, i) / DIRECT_A1D_ELEM(sigma2, i); else if (DIRECT_A1D_ELEM(sigma2, i) == 0) DIRECT_A1D_ELEM(sigma2, i) = 0.; @@ -1207,11 +1207,13 @@ void BackProjector::externalReconstruct(MultidimArray &vol_out, FileName &fn_out, MultidimArray &fsc_halves_io, MultidimArray &tau2_io, - MultidimArray &sigma2_ref, - MultidimArray &data_vs_prior, - RFLOAT pixel_size, - RFLOAT particle_diameter, - bool is_whole_instead_of_half, + MultidimArray &sigma2_ref, + MultidimArray &data_vs_prior, + RFLOAT pixel_size, + RFLOAT particle_diameter, + bool is_whole_instead_of_half, + bool do_blush, + std::string blush_args, RFLOAT tau2_fudge, int verb) { @@ -1278,21 +1280,50 @@ void BackProjector::externalReconstruct(MultidimArray &vol_out, MDtau.write(fh); fh.close(); - - // Make the system call: program name plus the STAR file for the external reconstruction program as its first argument - char *my_exec = getenv ("RELION_EXTERNAL_RECONSTRUCT_EXECUTABLE"); - char default_exec[]=DEFAULT_EXTERNAL_RECONSTRUCT; - if (my_exec == NULL) + if (do_blush) { - my_exec = default_exec; - } - std::string command = std::string(my_exec) + " " + fn_star; + //run python wrapper command + std::string cmd = "relion_python_blush " + fn_star; + + cmd += " " + blush_args; + + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) + throw std::runtime_error("Failed to dispatch command: " + cmd); + + char buffer[128]; + std::string result; + + // read till end of process + while (!feof(pipe)) + if (fgets(buffer, 128, pipe) != nullptr) + result += buffer; - if (verb > 0) std::cout << std::endl << " + Making system call for external reconstruction: " << command << std::endl; + pclose(pipe); - int res = system(command.c_str()); - if (res) REPORT_ERROR(" ERROR: there was something wrong with system call: " + command); - else if (verb > 0) std::cout << " + External reconstruction finished successfully, reading result back in ... " << std::endl; + if (trim2(result) != "success") + { + std::cerr << std::endl << "Something went wrong in the external Python call..." << std::endl; + std::cerr << "Command: " << cmd << std::endl; + std::cerr << result << std::endl; + exit(1); + } + } + else + { + // Make the system call: program name plus the STAR file for the external reconstruction program as its first argument + char *my_exec = getenv("RELION_EXTERNAL_RECONSTRUCT_EXECUTABLE"); + char default_exec[] = DEFAULT_EXTERNAL_RECONSTRUCT; + if (my_exec == NULL) + my_exec = default_exec; + std::string command = std::string(my_exec) + " " + fn_star; + if (verb > 0) + std::cout << std::endl << " + Making system call for external reconstruction: " << command << std::endl; + int res = system(command.c_str()); + if (res) REPORT_ERROR(" ERROR: there was something wrong with system call: " + command); + else if (verb > 0) + std::cout << " + External reconstruction finished successfully, reading result back in ... " << std::endl; + } // Read the resulting map back into memory Iweight.read(fn_recons); @@ -1395,6 +1426,15 @@ void BackProjector::reconstruct(MultidimArray &vol_out, ttt()=weight; ttt.write("reconstruct_initial_weight.spi"); std::cerr << " pad_size= " << pad_size << " padding_factor= " << padding_factor << " max_r2= " << max_r2 << std::endl; + FourierTransformer transformer2; + ttt().resize(vol_out); + transformer2.setReal(ttt()); // Fake set real. 1. Allocate space for Fconv 2. calculate plans. + MultidimArray& Fconv2 = transformer2.getFourierReference(); + Fconv2.initZeros(); // to remove any stuff from the input volume + Projector::decenter(data, Fconv2, max_r2); + windowToOridimRealSpace(transformer2, ttt(), printTimes); + ttt.write("reconstruct_initial_data.spi"); + std::cerr << "DEBUG_RECONSTRUCT: Written out reconstruct_initial_weight.spi and reconstruct_initial_data.spi" << std::endl; #endif // Set Fconv to the right size diff --git a/src/backprojector.h b/src/backprojector.h index b85a6fedc..7651d5530 100644 --- a/src/backprojector.h +++ b/src/backprojector.h @@ -30,6 +30,7 @@ #define DEFAULT_EXTERNAL_RECONSTRUCT "relion_external_reconstruct" +#include "src/strings.h" #include "src/projector.h" #include "src/mask.h" #include "src/tabfuncs.h" @@ -297,11 +298,13 @@ class BackProjector: public Projector FileName &fn_out, MultidimArray &fsc_halves_io, MultidimArray &tau2_io, - MultidimArray &sigma2_ref, - MultidimArray &data_vs_prior, - RFLOAT pixel_size=1, - RFLOAT particle_diameter=0, - bool is_whole_instead_of_half = false, + MultidimArray &sigma2_ref, + MultidimArray &data_vs_prior, + RFLOAT pixel_size=1, + RFLOAT particle_diameter=0, + bool is_whole_instead_of_half = false, + bool do_blush = false, + std::string gpu_id = "", RFLOAT tau2_fudge = 1., int verb = 0); diff --git a/src/class_ranker.cpp b/src/class_ranker.cpp index 40852c615..3f2ce1c9a 100644 --- a/src/class_ranker.cpp +++ b/src/class_ranker.cpp @@ -21,6 +21,7 @@ #include "src/npy.hpp" #include "src/class_ranker.h" + const static int IMGSIZE = 64; const static int NR_FEAT = 24; @@ -399,8 +400,6 @@ void ClassRanker::read(int argc, char **argv, int rank) fn_sel_parts = parser.getOption("--fn_sel_parts", "Filename for output star file with selected particles", "particles.star"); fn_sel_classavgs = parser.getOption("--fn_sel_classavgs", "Filename for output star file with selected class averages", "class_averages.star"); fn_root = parser.getOption("--fn_root", "rootname for output model.star and optimiser.star files", "rank"); - fn_pytorch_model = parser.getOption("--fn_pytorch_model", "Filename for the serialized Torch model.", ""); // Default should be compile-time defined - python_interpreter = parser.getOption("--python", "Command or path to python interpreter with pytorch.", ""); int part_section = parser.addSection("Network training options (only used in development!)"); do_ranking = !parser.checkOption("--train", "Only write output files for training purposes (don't rank classes)"); @@ -458,22 +457,6 @@ void ClassRanker::initialise() if (fn_out[fn_out.length()-1] != '/') fn_out += "/"; mktree(fn_out); - - // Get the python executable - if (python_interpreter == "") - { - char *penv; - penv = getenv("RELION_PYTHON_EXECUTABLE"); - if (penv != NULL) { - python_interpreter = (std::string) penv; - std::cout << " + Using python from RELION_PYTHON_EXECUTABLE environment variable: " << python_interpreter << std::endl; - } - else - { - REPORT_ERROR("ERROR: you need to specify the python executable through --python, or the RELION_PYTHON_EXECUTABLE environment variable"); - } - } - if (do_skip_angular_errors) { if (fn_cf == "") REPORT_ERROR("ERROR: you need to provide a class feature input file if you wish to skip some calculations!"); @@ -549,7 +532,7 @@ void ClassRanker::initialise() if (!only_do_subimages && (intact_ctf_first_peak || do_ranking || (!do_skip_angular_errors && !haveAllAccuracies)) ) { // Read in particles (otherwise wait until haveAllAccuracies or performRanking, as Liyi sometimes doesn't need mydata) - mydata.read(fn_data, true, true); // true true means: ignore particle_name and group name! + mydata.read(fn_data, "", "", true, true); // true true means: ignore particle_name and group name! total_nr_particles = mydata.numberOfParticles(0); } @@ -633,20 +616,6 @@ void ClassRanker::initialise() { std::cout << "WARNING: Should not provide radius ratio and radius at the same time. Ignoring the radius ratio..." << std::endl; } - - if (fn_pytorch_model == "") { - fn_pytorch_model = get_default_pytorch_model_path(); - if (fn_pytorch_model != "") - std::cout << "Using default pytorch model: " << fn_pytorch_model << std::endl; - } - - if (fn_pytorch_script == "") { - fn_pytorch_script = get_python_script_path(); - if (fn_pytorch_script != "") - std::cout << "Using python script: " << fn_pytorch_script << std::endl; - else - REPORT_ERROR("Python script file is missing."); - } } @@ -1064,8 +1033,8 @@ void ClassRanker::calculateExpectedAngularErrors(int iclass, classFeatures &cf) { // SHWS 6Feb2020: just work with noise spectrum from group 0 to save time! int group_id = 0; // mydata.getGroupId(part_id, 0); - RFLOAT my_pixel_size = mydata.getImagePixelSize(part_id, 0); - const int optics_group = mydata.getOpticsGroup(part_id, 0); + RFLOAT my_pixel_size = mydata.getImagePixelSize(part_id); + const int optics_group = mydata.getOpticsGroup(part_id); int my_image_size = (mydata.obsModel.hasBoxSizes) ? mydata.getOpticsImageSize(optics_group) : mymodel.ori_size; bool ctf_premultiplied = mydata.obsModel.getCtfPremultiplied(optics_group); @@ -1903,7 +1872,7 @@ void ClassRanker::readFeatures() } -void ClassRanker::deployTorchModel(FileName &model_path, std::vector &features, std::vector &subimages, std::vector &scores) +void ClassRanker::deployTorchModel(std::vector &features, std::vector &subimages, std::vector &scores) { const long unsigned count = features.size() / NR_FEAT; const long unsigned featues_shape [] = {count, NR_FEAT}; @@ -1912,21 +1881,18 @@ void ClassRanker::deployTorchModel(FileName &model_path, std::vector &fea const long unsigned image_shape [] = {count, IMGSIZE, IMGSIZE}; npy::SaveArrayAsNumpy(fn_out + "images.npy", false, 3, image_shape, subimages); - char buffer[128]; - std::string result = ""; - - std::string command = python_interpreter + " " + fn_pytorch_script + " " + fn_pytorch_model + " " + fn_out; - - // Open pipe to file - FILE* pipe = popen(command.c_str(), "r"); + //run python wrapper command + std::string cmd = "relion_python_classranker " + fn_out; + FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) - { - REPORT_ERROR("Failed to run external python script with the following command:\n " + command); - } + throw std::runtime_error("Failed to dispatch command: " + cmd); + + char buffer[128]; + std::string result; - // read till end of process: + // read till end of process while (!feof(pipe)) - if (fgets(buffer, 128, pipe) != NULL) + if (fgets(buffer, 128, pipe) != nullptr) result += buffer; pclose(pipe); @@ -1944,14 +1910,17 @@ void ClassRanker::deployTorchModel(FileName &model_path, std::vector &fea scores.push_back(std::stof(token, &sz)); s.erase(0, pos + delimiter.length()); } - if (scores.size() != count){ + if (scores.size() != count) + { + std::cerr << std::endl << "Something went wrong in the external Python call..." << std::endl; + std::cerr << "Command: " << cmd << std::endl; std::cerr << result << std::endl; - REPORT_ERROR("Failed to run external python script with the following command:\n " + command); + exit(1); } } catch (const std::invalid_argument& ia) { std::cerr << result << std::endl; - REPORT_ERROR("Failed to run external python script with the following command:\n " + command); + exit(1); } } @@ -1960,7 +1929,7 @@ void ClassRanker::performRanking() if (mydata.numberOfParticles() == 0) { // Read in particles if we haven't done this already - mydata.read(fn_data, true, true); // true true means: ignore particle_name and group name! + mydata.read(fn_data, "", "", true, true); // true true means: ignore particle_name and group name! if (debug>0) std::cerr << "Done with reading data.star ..." << std::endl; } @@ -2022,7 +1991,7 @@ void ClassRanker::performRanking() } } - deployTorchModel(fn_pytorch_model, feature_vector, image_vector, scores); + deployTorchModel(feature_vector, image_vector, scores); RFLOAT my_min = select_min_score; RFLOAT my_max = select_max_score; @@ -2185,6 +2154,7 @@ void ClassRanker::performRanking() // Maintain the original image ordering and obsModel in mydata too MDselected_particles.sort(EMDL_SORTED_IDX); mydata.MDimg = MDselected_particles; + mydata.MDimg.setName("particles"); mydata.write(fn_out+fn_sel_parts); // Also write out class_averages.star with the selected classes @@ -2322,58 +2292,3 @@ void ClassRanker::writeFeatures() } } - - -std::string ClassRanker::get_default_pytorch_model_path() -{ - - std::vector buff(512); - ssize_t len; - - //Read path string into buffer - do { - buff.resize(buff.size() + 128); - len = ::readlink("/proc/self/exe", &(buff[0]), buff.size()); - } while (buff.size() == len); - - // Convert to string and return - if (len > 0) { - buff[len] = '\0'; //Mark end of string - std::string path = std::string(&(buff[0])); - std::size_t found = path.find_last_of("/\\"); - path = path.substr(0,found) + "/relion_class_ranker_default_model.pt"; - if (FILE *file = fopen(path.c_str(), "r")) { //Check if file can be opened - fclose(file); - return path; - } - } - - return ""; -} - - std::string ClassRanker::get_python_script_path() - { - - std::vector buff(512); - ssize_t len; - - //Read path string into buffer - do { - buff.resize(buff.size() + 128); - len = ::readlink("/proc/self/exe", &(buff[0]), buff.size()); - } while (buff.size() == len); - - // Convert to string and return - if (len > 0) { - buff[len] = '\0'; //Mark end of string - std::string path = std::string(&(buff[0])); - std::size_t found = path.find_last_of("/\\"); - path = path.substr(0,found) + "/relion_class_ranker.py"; - if (FILE *file = fopen(path.c_str(), "r")) { //Check if file can be opened - fclose(file); - return path; - } - } - - return ""; - } diff --git a/src/class_ranker.h b/src/class_ranker.h index 1bcf43e9e..41d46f5a4 100644 --- a/src/class_ranker.h +++ b/src/class_ranker.h @@ -386,10 +386,6 @@ class ClassRanker MetaDataTable MD_optimiser, MD_select; std::vector features_all_classes, preread_features_all_classes; - FileName fn_pytorch_model; - FileName fn_pytorch_script; - FileName python_interpreter; - public: ClassRanker(){} @@ -415,16 +411,6 @@ class ClassRanker // Execute the program void run(); - /* Get path to the default pytorch model [LINUX ONLY] - * Check if file exists, return empty string otherwise - */ - static std::string get_default_pytorch_model_path(); - - /* Get path to the python script for executing pytorch model [LINUX ONLY] - * Check if file exists, return empty string otherwise - */ - static std::string get_python_script_path(); - private: int getClassIndex(FileName &name); @@ -465,7 +451,7 @@ class ClassRanker void writeFeatures(); - void deployTorchModel(FileName &model_path, std::vector &features, std::vector &subimages, std::vector &score); + void deployTorchModel(std::vector &features, std::vector &subimages, std::vector &score); void performRanking(); }; diff --git a/src/ctf.cpp b/src/ctf.cpp index f95050bd0..0ed9bd545 100644 --- a/src/ctf.cpp +++ b/src/ctf.cpp @@ -132,7 +132,7 @@ void CTF::read(const MetaDataTable &MD1, const MetaDataTable &MD2, long int obje } void CTF::setValues(RFLOAT _defU, RFLOAT _defV, RFLOAT _defAng, RFLOAT _voltage, - RFLOAT _Cs, RFLOAT _Q0, RFLOAT _Bfac, RFLOAT _scale, RFLOAT _phase_shift) + RFLOAT _Cs, RFLOAT _Q0, RFLOAT _Bfac, RFLOAT _scale, RFLOAT _phase_shift, RFLOAT _dose) { kV = _voltage; DeltafU = _defU; @@ -143,13 +143,14 @@ void CTF::setValues(RFLOAT _defU, RFLOAT _defV, RFLOAT _defAng, RFLOAT _voltage, scale = _scale; Q0 = _Q0; phase_shift = _phase_shift; + dose = _dose; initialise(); } void CTF::setValuesByGroup(ObservationModel *obs, int _opticsGroup, RFLOAT _defU, RFLOAT _defV, RFLOAT _defAng, - RFLOAT _Bfac, RFLOAT _scale, RFLOAT _phase_shift) + RFLOAT _Bfac, RFLOAT _scale, RFLOAT _phase_shift, RFLOAT _dose) { opticsGroup = _opticsGroup; @@ -160,10 +161,14 @@ void CTF::setValuesByGroup(ObservationModel *obs, int _opticsGroup, Bfac = _Bfac; scale = _scale; phase_shift = _phase_shift; - - obs->opticsMdt.getValue(EMDL_CTF_VOLTAGE, kV, opticsGroup); - obs->opticsMdt.getValue(EMDL_CTF_CS, Cs, opticsGroup); - obs->opticsMdt.getValue(EMDL_CTF_Q0, Q0, opticsGroup); + dose = _dose; + + if (!obs->opticsMdt.getValue(EMDL_CTF_VOLTAGE, kV, opticsGroup)) + REPORT_ERROR("ERROR: no rlnVoltage label was found in the CTF parameters"); + if (!obs->opticsMdt.getValue(EMDL_CTF_CS, Cs, opticsGroup)) + REPORT_ERROR("ERROR: no rlnSphericalAberration label was found in the CTF parameters"); + if (!obs->opticsMdt.getValue(EMDL_CTF_Q0, Q0, opticsGroup)) + REPORT_ERROR("ERROR: no rlnAmplitudeContrast label was found in the CTF parameters"); initialise(); diff --git a/src/ctf.h b/src/ctf.h index a79089369..3475017d1 100644 --- a/src/ctf.h +++ b/src/ctf.h @@ -132,13 +132,16 @@ class CTF // Overall scale-factor of CTF RFLOAT scale; + // Niko Grigorieff's exponential decay + RFLOAT dose; + // Phase-shift from a phase-plate (in rad) RFLOAT phase_shift; /** Empty constructor. */ CTF() : kV(200), DeltafU(0), DeltafV(0), azimuthal_angle(0), phase_shift(0), - Cs(0), Bfac(0), Q0(0), scale(1), obsModel(0), opticsGroup(0) + Cs(0), Bfac(0), dose(-1.), Q0(0), scale(1), obsModel(0), opticsGroup(0) {} // Read CTF parameters from particle table partMdt and optics table opticsMdt. @@ -156,12 +159,12 @@ class CTF /** Just set all values explicitly */ void setValues(RFLOAT _defU, RFLOAT _defV, RFLOAT _defAng, - RFLOAT _voltage, RFLOAT _Cs, RFLOAT _Q0, RFLOAT _Bfac, RFLOAT _scale = 1., RFLOAT _phase_shift = 0.); + RFLOAT _voltage, RFLOAT _Cs, RFLOAT _Q0, RFLOAT _Bfac, RFLOAT _scale = 1., RFLOAT _phase_shift = 0., RFLOAT _dose = -1.0 ); /** Set all values explicitly in 3.1 */ void setValuesByGroup(ObservationModel* obs, int opticsGroup, RFLOAT _defU, RFLOAT _defV, RFLOAT _defAng, - RFLOAT _Bfac = 0.0, RFLOAT _scale = 1.0, RFLOAT _phase_shift = 0.0); + RFLOAT _Bfac = 0.0, RFLOAT _scale = 1.0, RFLOAT _phase_shift = 0.0, RFLOAT _dose = -1.0 ); /** Read from a single MetaDataTable */ @@ -215,8 +218,20 @@ class CTF if (do_damping) { - RFLOAT E = exp(K4 * u2); // B-factor decay (K4 = -Bfac/4); - retval *= E; + RFLOAT E; + if (dose >= 0.) + { + // Niko Grigorieff's formulae + //RFLOAT d0 = 0.245 * pow(sqrt(u2), -1.665) + 2.81; + // sqrt = ^0.5; -1.665*0.5 = -0.8325 + RFLOAT d0 = 0.245 * pow(u2, -0.8325) + 2.81; + E = exp(-0.5 * dose / d0); + } + else + { + E = exp(K4 * u2); // B-factor decay (K4 = -Bfac/4); + } + retval *= E; } if (do_abs) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index a4d7c98a0..fe72eb55f 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -22,6 +22,8 @@ #ifdef _CUDA_ENABLED #include "src/acc/cuda/cuda_mem_utils.h" +#elif _HIP_ENABLED +#include "src/acc/hip/hip_mem_utils.h" #endif void CtffindRunner::read(int argc, char **argv, int rank) @@ -140,10 +142,25 @@ void CtffindRunner::initialise(bool is_leader) fn_out += "/"; // Set up which micrographs to estimate CTFs from - if (fn_in.isStarFile()) + is_tomo = false; + if (fn_in.isStarFile()) { MetaDataTable MDin; - ObservationModel::loadSafely(fn_in, obsModel, MDin, "micrographs", verb); + + // Check if this is a TomographyExperiment starfile, and if so, unpack into one large metadatatable + if (tomogramSet.read(fn_in, 1)) + { + is_tomo = true; + tomogramSet.generateSingleMetaDataTable(MDin, obsModel); + } + else + { + ObservationModel::loadSafely(fn_in, obsModel, MDin, "micrographs", verb); + } + if (MDin.numberOfObjects() == 0) + { + REPORT_ERROR("ERROR: no input micrographs to work on."); + } if (MDin.numberOfObjects() > 0 && !MDin.containsLabel(EMDL_MICROGRAPH_NAME)) REPORT_ERROR("ERROR: There is no rlnMicrographName label in the input micrograph STAR file."); @@ -173,6 +190,14 @@ void CtffindRunner::initialise(bool is_leader) int optics_group; MDin.getValue(EMDL_IMAGE_OPTICS_GROUP, optics_group); optics_group_micrographs_all.push_back(optics_group); + + if (is_tomo) + { + RFLOAT exposure; + MDin.getValue(EMDL_MICROGRAPH_PRE_EXPOSURE, exposure); + pre_exposure_micrographs.push_back(exposure); + } + } } else @@ -218,7 +243,8 @@ void CtffindRunner::initialise(bool is_leader) obsModel.opticsMdt.setValue(EMDL_CTF_Q0, AmplitudeConstrast); } } - if (!obsModel.opticsMdt.containsLabel(EMDL_MICROGRAPH_PIXEL_SIZE)) + EMDLabel mylabel = (is_tomo) ? EMDL_TOMO_TILT_SERIES_PIXEL_SIZE : EMDL_MICROGRAPH_PIXEL_SIZE; + if (!obsModel.opticsMdt.containsLabel(mylabel)) { if (angpix < 0.) { @@ -226,7 +252,7 @@ void CtffindRunner::initialise(bool is_leader) } FOR_ALL_OBJECTS_IN_METADATA_TABLE(obsModel.opticsMdt) { - obsModel.opticsMdt.setValue(EMDL_MICROGRAPH_PIXEL_SIZE, angpix); + obsModel.opticsMdt.setValue(mylabel, angpix); } } @@ -328,13 +354,13 @@ void CtffindRunner::initialise(bool is_leader) untangleDeviceIDs(gpu_ids, allThreadIDs); if (allThreadIDs[0].size()==0 || (!std::isdigit(*gpu_ids.begin())) ) { -#ifdef _CUDA_ENABLED +#if defined _CUDA_ENABLED || defined _HIP_ENABLED if (verb>0) std::cout << "gpu-ids were not specified, so threads will automatically be mapped to devices (incrementally)."<< std::endl; - HANDLE_ERROR(cudaGetDeviceCount(&devCount)); + HANDLE_ERROR(accGPUGetDeviceCount(&devCount)); #else if (verb>0) - REPORT_ERROR("gpu-ids were not specified, but we could not figure out which GPU to use because RELION was not compiled with CUDA support."); + REPORT_ERROR("gpu-ids were not specified, but we could not figure out which GPU to use because RELION was not compiled with CUDA or HIP support."); #endif } @@ -395,7 +421,8 @@ void CtffindRunner::run() obsModel.opticsMdt.getValue(EMDL_CTF_CS, Cs, optics_group_micrographs[imic]-1); obsModel.opticsMdt.getValue(EMDL_CTF_VOLTAGE, Voltage, optics_group_micrographs[imic]-1); obsModel.opticsMdt.getValue(EMDL_CTF_Q0, AmplitudeConstrast, optics_group_micrographs[imic]-1); - obsModel.opticsMdt.getValue(EMDL_MICROGRAPH_PIXEL_SIZE, angpix, optics_group_micrographs[imic]-1); + EMDLabel mylabel = (is_tomo) ? EMDL_TOMO_TILT_SERIES_PIXEL_SIZE : EMDL_MICROGRAPH_PIXEL_SIZE; + obsModel.opticsMdt.getValue(mylabel, angpix, optics_group_micrographs[imic]-1); if (do_use_gctf) { @@ -469,17 +496,33 @@ void CtffindRunner::joinCtffindResults() MDctf.setValue(EMDL_CTF_PHASESHIFT, phaseshift); if (fabs(valscore + 999.) > 0.) MDctf.setValue(EMDL_CTF_VALIDATIONSCORE, valscore); + + if (is_tomo) + { + // Store pre-exposure to sort images on, just in case this program messed up the order... + MDctf.setValue(EMDL_MICROGRAPH_PRE_EXPOSURE, pre_exposure_micrographs[imic]); + } + } if (verb > 0 && imic % 60 == 0) progress_bar(imic); } - obsModel.save(MDctf, fn_out+"micrographs_ctf.star", "micrographs"); + if (is_tomo) + { + tomogramSet.convertBackFromSingleMetaDataTable(MDctf); + tomogramSet.write(fn_out+"tilt_series_ctf.star"); + } + else + { + obsModel.save(MDctf, fn_out + "micrographs_ctf.star", "micrographs"); + } if (verb > 0) { progress_bar(fn_micrographs_all.size()); - std::cout << " Done! Written out: " << fn_out << "micrographs_ctf.star" << std::endl; + if (is_tomo) std::cout << " Done! Written out: " << fn_out << "tilt_series_ctf.star" << std::endl; + else std::cout << " Done! Written out: " << fn_out << "micrographs_ctf.star" << std::endl; } if (verb > 0) diff --git a/src/ctffind_runner.h b/src/ctffind_runner.h index 0ec1b6441..577c5ad20 100644 --- a/src/ctffind_runner.h +++ b/src/ctffind_runner.h @@ -31,6 +31,7 @@ #include #include #include +#include "src/jaz/tomography/tomogram_set.h" class CtffindRunner { @@ -57,6 +58,15 @@ class CtffindRunner // Information about the optics groups ObservationModel obsModel; + // Tilt movie index for each micrograph in a tilt serie (needed for converting back to tomographyExperiment) + std::vector pre_exposure_micrographs; + + // Is this a tomography experiment? + bool is_tomo; + + // Information about tomography experiment + TomogramSet tomogramSet; + // Dimension of squared area of the micrograph to use for CTF estimation int ctf_win; diff --git a/src/ctffind_runner_mpi.cpp b/src/ctffind_runner_mpi.cpp index a16ef5d2b..fa493c071 100644 --- a/src/ctffind_runner_mpi.cpp +++ b/src/ctffind_runner_mpi.cpp @@ -68,7 +68,8 @@ void CtffindRunnerMpi::run() obsModel.opticsMdt.getValue(EMDL_CTF_CS, Cs, optics_group_micrographs[imic]-1); obsModel.opticsMdt.getValue(EMDL_CTF_VOLTAGE, Voltage, optics_group_micrographs[imic]-1); obsModel.opticsMdt.getValue(EMDL_CTF_Q0, AmplitudeConstrast, optics_group_micrographs[imic]-1); - obsModel.opticsMdt.getValue(EMDL_MICROGRAPH_PIXEL_SIZE, angpix, optics_group_micrographs[imic]-1); + EMDLabel mylabel = (is_tomo) ? EMDL_TOMO_TILT_SERIES_PIXEL_SIZE : EMDL_MICROGRAPH_PIXEL_SIZE; + obsModel.opticsMdt.getValue(mylabel, angpix, optics_group_micrographs[imic]-1); if (do_use_gctf) { diff --git a/src/displayer.cpp b/src/displayer.cpp index 2d006774c..fa7fc046e 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -2568,7 +2568,7 @@ void Displayer::topazDenoiseMap(FileName fn_in, FileName fn_odir, Image fh << "#!" << fn_shell << std::endl; // Call Topaz to train the network - fh << fn_topaz_exe << " denoise "; + fh << "relion_python_topaz denoise "; fh << fn_in; fh << " --output " << fn_odir; fh << " --device 0"; // pyTorch threads @@ -2650,7 +2650,6 @@ void Displayer::read(int argc, char **argv) particle_radius = textToFloat(parser.getOption("--particle_radius", "Particle radius in pixels", "100")); particle_radius *= coord_scale; do_topaz_denoise = parser.checkOption("--topaz_denoise", "Use Topaz denoising before picking (on GPU 0)"); - fn_topaz_exe = parser.getOption("--topaz_exe", "Name of topaz executable", "topaz"); fn_shell = parser.getOption("--bash_exe", "Name of bash shell executable", "/bin/bash"); lowpass = textToFloat(parser.getOption("--lowpass", "Lowpass filter (in A) to filter micrograph before displaying", "0")); highpass = textToFloat(parser.getOption("--highpass", "Highpass filter (in A) to filter micrograph before displaying", "0")); @@ -2891,7 +2890,6 @@ int Displayer::runGui() img.read(fn_in, false); win.is_multi = (ZSIZE(img()) * NSIZE(img()) > 1); } - return win.fill(fn_in); } @@ -2968,7 +2966,7 @@ void Displayer::run() REPORT_ERROR("Cannot find metadata label in input STAR file"); // Store class number in metadata table - if (do_class) + if (do_class && !MDin.containsLabel(EMDL_PARTICLE_CLASS)) { int iclass = 0; FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDin) diff --git a/src/displayer.h b/src/displayer.h index afcfa8023..cf9ed5437 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -49,7 +49,7 @@ #define GUI_BACKGROUND_COLOR (fl_rgb_color(240,240,240)) #define GUI_INPUT_COLOR (fl_rgb_color(255,255,230)) -#define GUI_RUNBUTTON_COLOR (fl_rgb_color(160, 30, 60)) +#define GUI_RUNBUTTON_COLOR (fl_rgb_color(100, 60, 255)) @@ -648,9 +648,6 @@ class Displayer // Only show a limited number of images long int max_nr_images; - // Topaz executable (for denoising of micrographs in picking mode) - FileName fn_topaz_exe; - // Shell for calling Topaz FileName fn_shell; diff --git a/src/error.h b/src/error.h index 237e9489b..72bac2224 100644 --- a/src/error.h +++ b/src/error.h @@ -139,12 +139,12 @@ This is a developer error message which you cannot fix \n\ through changing the run config. Either your data is broken or\n\ an unforseen combination of options was encountered. Please report\n\ this error, the command used and a brief description to\n\ -the relion developers at \n\n github.com/3dem/relion/issues \n\n" +the relion developers at \n\n github.com/3dem/relion/issues \n" #define ADVERR "\n\ This error is normally only displayed when using advanced \n\ features or build-utilities for code development or benchmarking.\n\ -You can ask the relion developers for assistance at \n\n github.com/3dem/relion/issues" +You can ask the relion developers for assistance at github.com/3dem/relion/issues" #define ERR_GPUID ("\ There was an issue with the GPU-ids. Either \n \t\ @@ -159,16 +159,18 @@ If this occured at the start of a run, you might have GPUs which\n\ are incompatible with either the data or your installation of relion.\n\ If you \n\n\ \t-> INSTALLED RELION YOURSELF: if you e.g. specified -DCUDA_ARCH=50\n\ -\t and are trying ot run on a compute 3.5 GPU (-DCUDA_ARCH=3.5), \n\ -\t this may happen.\n\n\ -\t-> HAVE MULTIPLE GPUS OF DIFFERNT VERSIONS: relion needs GPUS with\n\ -\t at least compute 3.5. You may be trying to use a GPU older than\n\ -\t this. If you have multiple generations, try specifying --gpu \n\ +\t and are trying ot run on a compute 3.5 GPU (-DCUDA_ARCH=3.5), or \n\ +\t a similar mismatch on AMD or Intel GPUs, this may happen.\n\n\ +\t-> HAVE MULTIPLE GPUS OF DIFFERNT VERSIONS: relion needs at least NVIDIA \n\ +\t GPUs with compute 5.0 or AMD MI GPUs with archtiecture gfx906. \n\ +\t You may be trying to use a GPU architectures older than these. \n\ +\t If you have multiple generations, try specifying --gpu \n\ \t with X=0. Then try X=1 in a new run, and so on. The numbering of\n\ \t GPUs may not be obvious from the driver or intuition. For a list\n\ -\t of GPU compute generations, see \n\n\ -\t en.wikipedia.org/wiki/CUDA#Version_features_and_specifications\n\n\ -\t-> ARE USING DOUBLE-PRECISION GPU CODE: relion was been written so\n\ +\t of NVIDIA and AMD GPU compute generations and architectures, see \n\n\ +\t en.wikipedia.org/wiki/CUDA#Version_features_and_specifications and \n\ +\t docs.amd.com/bundle/Hardware_and_Software_Reference_Guide/page/Hardware_and_Software_Support.html\n\n\ +\t-> ARE USING DOUBLE-PRECISION GPU CODE: relion has been written so\n\ \t as to not require this, and may thus have unforeseen requirements\n\ \t when run in this mode. If you think it is nonetheless necessary,\n\ \t please consult the developers with this error.\n\n\ @@ -179,10 +181,10 @@ If this occurred at the middle or end of a run, it might be that\n\n\ \t configurations may occur. See also above point regarding \n\ \t double precision.\n\ If none of the above applies, please report the error to the relion\n\ -developers at github.com/3dem/relion/issues\n\n") +developers at github.com/3dem/relion/issues.") -#define ERRCUDACAOOM ("\n\ +#define ERRGPUCAOOM ("\n\ You ran out of memory on the GPU(s).\n\n\ Each MPI-rank running on a GPU increases the use of GPU-memory. Relion\n\ tries to distribute load over multiple GPUs to increase performance,\n\ @@ -209,9 +211,9 @@ but doing this in a general and memory-efficient way is difficult.\n\n\ adding the --maxsig

, flag, where P is an integer limit, but you \n\ should probably also consult expertise or re-evaluate your data and/or \n\ input reference. Seeing large such values means relion is finding nothing\n\ - to align.\n\n\ + to align.\n\ If none of the above applies, please report the error to the relion\n\ -developers at github.com/3dem/relion/issues\n\n") +developers at github.com/3dem/relion/issues.\n") #define ERR_CANZ ("There is an allocation on the GPU left between iterations." DEVERR) #define ERR_CAMUX ("A mutex could not be created for a GPU memory allocation." DEVERR) @@ -224,11 +226,17 @@ developers at github.com/3dem/relion/issues\n\n") #define ERRGTIC ("You are trying to benchmark a (GPU) section, but started timing it twice." ADVERR) #define ERRGTOC ("You are trying to benchmark a (GPU) section, but this section has not begun." ADVERR) #define ERRTPC ("You are trying to benchmark a (GPU) section, but there is nothing to print." ADVERR) - -#define ERRCUFFTDIM ("You are changing the dimension of a CUFFT-transform (plan)" DEVERR) -#define ERRCUFFTDIR ("You are setting the direction of a CUFFT-transform to something other than forward/inverse" DEVERR) -#define ERRCUFFTDIRF ("You are trying to run a forward CUFFT-transform for an inverse transform" DEVERR) -#define ERRCUFFTDIRR ("You are trying to run an inverse CUFFT-transform for a forward transform" DEVERR) +#ifdef _CUDA_ENABLED + #define ERRCUFFTDIM ("You are changing the dimension of a CUFFT-transform (plan)" DEVERR) + #define ERRCUFFTDIR ("You are setting the direction of a CUFFT-transform to something other than forward/inverse" DEVERR) + #define ERRCUFFTDIRF ("You are trying to run a forward CUFFT-transform for an inverse transform" DEVERR) + #define ERRCUFFTDIRR ("You are trying to run an inverse CUFFT-transform for a forward transform" DEVERR) +#elif _HIP_ENABLED + #define ERRHIPFFTDIM ("You are changing the dimension of a HIPFFT-transform (plan)" DEVERR) + #define ERRHIPFFTDIR ("You are setting the direction of a HIPFFT-transform to something other than forward/inverse" DEVERR) + #define ERRHIPFFTDIRF ("You are trying to run a forward HIPFFT-transform for an inverse transform" DEVERR) + #define ERRHIPFFTDIRR ("You are trying to run an inverse HIPFFT-transform for a forward transform" DEVERR) +#endif #define ERRFFTMEMLIM ("\n\ When trying to plan one or more Fourier transforms, it was found that the available\n\ GPU memory was insufficient. Relion attempts to reduce the memory by segmenting\n\ @@ -261,20 +269,18 @@ A particle image was compared to the reference and resulted in all-zero\n\ weights (for all orientations). This should not happen, unless your data\n\ has very special characteristics. This has historically happened for some \n\ lower-precision calculations, but multiple fallbacks have since been \n\ -implemented. Please report this error to the relion developers at \n\n\ - github.com/3dem/relion/issues \n ") +implemented. Please report this error to the relion developers at \n\ +github.com/3dem/relion/issues \n ") #define ERRNOSIGNIFS ("The number of contributing orientations for an image\n\ was found to be zero. This should not happen, unless your data\n\ has very special characteristics. Please report this error to \n\ -the relion developers at \n\n\ - github.com/3dem/relion/issues ") +the relion developers at github.com/3dem/relion/issues ") #define ERRSUMWEIGHTZERO ("The sum of weights for all orientations was\n\ found to be zero for an image. This should not happen, unless your data\n\ has very special characteristics. Please report this error to \n\ -the relion developers at \n\n\ - github.com/3dem/relion/issues ") +the relion developers at github.com/3dem/relion/issues ") #define ERRNUMFAILSAFE ("Relion had to use extra-precision fallbacks too many times.\n\n\ In some cases relion find it difficult to reconcile the data with the\n\ diff --git a/src/exp_model.cpp b/src/exp_model.cpp index e0d45fd7b..83a211b8a 100644 --- a/src/exp_model.cpp +++ b/src/exp_model.cpp @@ -19,6 +19,7 @@ ***************************************************************************/ #include "src/exp_model.h" #include +using namespace gravis; long int Experiment::numberOfParticles(int random_subset) { @@ -66,14 +67,14 @@ int Experiment::getOpticsImageSize(int optics_group) return obsModel.getBoxSize(optics_group); } -long int Experiment::getGroupId(long int part_id, int img_id) +long int Experiment::getGroupId(long int part_id) { - return (particles[part_id].images[img_id]).group_id; + return particles[part_id].group_id; } -int Experiment::getOpticsGroup(long part_id, int img_id) +int Experiment::getOpticsGroup(long part_id) { - return particles[part_id].images[img_id].optics_group; + return particles[part_id].optics_group; } int Experiment::getRandomSubset(long int part_id) @@ -81,17 +82,28 @@ int Experiment::getRandomSubset(long int part_id) return particles[part_id].random_subset; } -int Experiment::getOriginalImageId(long part_id, int img_id) +RFLOAT Experiment::getImagePixelSize(long int part_id) { - return particles[part_id].images[img_id].id; + int optics_group = particles[part_id].optics_group; + return obsModel.getPixelSize(optics_group); } -RFLOAT Experiment::getImagePixelSize(long int part_id, int img_id) + +Matrix2D Experiment::getRotationMatrix(long int part_id, int img_id) { - int optics_group = particles[part_id].images[img_id].optics_group; - return obsModel.getPixelSize(optics_group); + return particles[part_id].images[img_id].Aproj; +} + +void Experiment::getTranslationInTiltSeries(long int part_id, int img_id, + RFLOAT shift3d_x, RFLOAT shift3d_y, RFLOAT shift3d_z, + RFLOAT &shift2d_x, RFLOAT &shift2d_y, RFLOAT &shift2d_z) +{ + Matrix2D Aproj = particles[part_id].images[img_id].Aproj; + shift2d_x = Aproj(0,0) * shift3d_x + Aproj(0,1) * shift3d_y + Aproj(0,2) * shift3d_z; + shift2d_y = Aproj(1,0) * shift3d_x + Aproj(1,1) * shift3d_y + Aproj(1,2) * shift3d_z; + shift2d_z = 0.; } -void Experiment::getNumberOfImagesPerGroup(std::vector &nr_particles_per_group, int random_subset) +void Experiment::getNumberOfParticlesPerGroup(std::vector &nr_particles_per_group, int random_subset) { nr_particles_per_group.resize(groups.size()); for (long int i = 0; i < nr_particles_per_group.size(); i++) @@ -101,14 +113,13 @@ void Experiment::getNumberOfImagesPerGroup(std::vector &nr_particles_p { if (random_subset == 0 || particles[part_id].random_subset == random_subset) { - for (int img_id = 0; img_id < particles[part_id].images.size(); img_id++) - nr_particles_per_group[particles[part_id].images[img_id].group_id] += 1; + nr_particles_per_group[particles[part_id].group_id] += 1; } } } -void Experiment::getNumberOfImagesPerOpticsGroup(std::vector &nr_particles_per_optics_group, int random_subset) +void Experiment::getNumberOfParticlesPerOpticsGroup(std::vector &nr_particles_per_optics_group, int random_subset) { nr_particles_per_optics_group.resize(obsModel.numberOfOpticsGroups()); for (long int i = 0; i < nr_particles_per_optics_group.size(); i++) @@ -118,83 +129,96 @@ void Experiment::getNumberOfImagesPerOpticsGroup(std::vector &nr_parti { if (random_subset == 0 || particles[part_id].random_subset == random_subset) { - for (int img_id = 0; img_id < particles[part_id].images.size(); img_id++) - nr_particles_per_optics_group[particles[part_id].images[img_id].optics_group] += 1; + nr_particles_per_optics_group[particles[part_id].optics_group] += 1; } } } -MetaDataTable Experiment::getMetaDataImage(long int part_id, int img_id) +MetaDataTable Experiment::getMetaDataParticle(long int part_id) { MetaDataTable result; - result.addObject(MDimg.getObject(getOriginalImageId(part_id, img_id))); + result.addObject(MDimg.getObject(part_id)); return result; } -FileName Experiment::getMicrographName(long int ori_image_id) +FileName Experiment::getMicrographName(long int part_id) { FileName micname=""; - if (is_3D) + if (is_3D || is_tomo) { - MDimg.getValue(EMDL_TOMO_NAME, micname, ori_image_id); + MDimg.getValue(EMDL_TOMO_NAME, micname, part_id); } else { - MDimg.getValue(EMDL_MICROGRAPH_NAME, micname, ori_image_id); + MDimg.getValue(EMDL_MICROGRAPH_NAME, micname, part_id); } - // SHWS 16112020: in relion-3.2 it is time to let go of the old polishing.... - // TODO: this is a temporary check: remove from distribution code!!! - if (micname.contains("@")) REPORT_ERROR("ERROR: micrographnames cannot have @ signs in them"); return micname; } -FileName Experiment::getMicrographName(long int part_id, int img_id) +void Experiment::addParticle(std::string img_name, int optics_group, long int group_id, int random_subset, int tomogram_id) { - return getMicrographName(getOriginalImageId(part_id, img_id)); -} -long int Experiment::addParticle(std::string part_name, int random_subset) -{ + if (optics_group >= obsModel.numberOfOpticsGroups()) + REPORT_ERROR("Experiment::addImageToParticle: optics_group out of range"); + + if (group_id >= groups.size()) + REPORT_ERROR("Experiment::addImageToParticle: group_id out of range"); - ExpParticle particle; - particle.name = part_name; + ExpParticle particle; + particle.id = particles.size(); + particle.name = img_name; + particle.tomogram_id = tomogram_id; particle.random_subset = random_subset; + particle.optics_group = optics_group; + particle.group_id = group_id; + + nr_particles_per_optics_group[optics_group]++; + particle.optics_group_id = nr_particles_per_optics_group[optics_group] - 1; // Push back this particle in the particles vector and its sorted index in sorted_idx sorted_idx.push_back(particles.size()); particles.push_back(particle); - // Return the current part_id in the particles vector - return particles.size() - 1; + return; } -int Experiment::addImageToParticle(long int part_id, std::string img_name, long int ori_img_id, long int group_id, - int optics_group, bool unique) +void Experiment::addImageToParticle(long int part_id, d4Matrix *Aproj, CTF *ctf, float dose) { - if (group_id >= groups.size()) - REPORT_ERROR("Experiment::addImageToParticle: group_id out of range"); - if (optics_group >= obsModel.numberOfOpticsGroups()) - REPORT_ERROR("Experiment::addImageToParticle: optics_group out of range"); + Matrix2D A(3,3); + if (Aproj == NULL) + { + A.initIdentity(); + } + else + { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + A(i, j) = (*Aproj)(i, j); + } ExpImage img; - img.name = img_name; - img.id = ori_img_id; - img.particle_id = part_id; - img.group_id = group_id; - img.optics_group = optics_group; - if (unique) - nr_images_per_optics_group[optics_group]++; - img.optics_group_id = nr_images_per_optics_group[optics_group] - 1; + if (ctf == NULL) + { + img.defU = img.defV = img.defAngle = 0.; + } + else + { + img.defU = ctf->DeltafU; + img.defV = ctf->DeltafV; + img.defAngle = ctf->azimuthal_angle; + } - if (img.optics_group_id < 0) - REPORT_ERROR("Logic error in Experiment::addImageToParticle."); + img.particle_id = part_id; + img.Aproj = A; + img.is_empty = false; + img.dose = dose; // Push back this particle in the particles vector particles[part_id].images.push_back(img); - return particles[part_id].images.size() - 1; + return; } long int Experiment::addGroup(std::string group_name, int _optics_group) @@ -261,11 +285,10 @@ void Experiment::divideParticlesInRandomHalves(int seed, bool do_helical_refine) for (long int part_id = 0; part_id < particles.size(); part_id++) { // Get name of micrograph of the first image in this particle - mic_name = getMicrographName(part_id, 0); + mic_name = getMicrographName(part_id); if (divide_according_to_helical_tube_id) { - long int ori_img_id = getOriginalImageId(part_id, 0); - MDimg.getValue(EMDL_PARTICLE_HELICAL_TUBE_ID, helical_tube_id, ori_img_id); + MDimg.getValue(EMDL_PARTICLE_HELICAL_TUBE_ID, helical_tube_id, part_id); if (helical_tube_id < 1) REPORT_ERROR("ERROR Experiment::divideParticlesInRandomHalves: Helical tube ID should be positive integer!"); mic_name += std::string("_TUBEID_"); @@ -314,11 +337,10 @@ void Experiment::divideParticlesInRandomHalves(int seed, bool do_helical_refine) for (long int part_id = 0; part_id < particles.size(); part_id++) { // Get name of micrograph of the first image in this particle - mic_name = getMicrographName(part_id, 0); + mic_name = getMicrographName(part_id); if (divide_according_to_helical_tube_id) { - long int ori_img_id = getOriginalImageId(part_id, 0); - MDimg.getValue(EMDL_PARTICLE_HELICAL_TUBE_ID, helical_tube_id, ori_img_id); + MDimg.getValue(EMDL_PARTICLE_HELICAL_TUBE_ID, helical_tube_id, part_id); if (helical_tube_id < 1) REPORT_ERROR("ERROR Experiment::divideParticlesInRandomHalves: Helical tube ID should be positive integer!"); mic_name += std::string("_TUBEID_"); @@ -348,11 +370,7 @@ void Experiment::divideParticlesInRandomHalves(int seed, bool do_helical_refine) else REPORT_ERROR("ERROR Experiment::divideParticlesInRandomHalves: invalid number for random subset (i.e. not 1 or 2): " + integerToString(random_subset)); - for (int img_id = 0; img_id < numberOfImagesInParticle(part_id); img_id++) - { - long int ori_img_id = getOriginalImageId(part_id, img_id); - MDimg.setValue(EMDL_PARTICLE_RANDOM_SUBSET, random_subset, ori_img_id); - } + MDimg.setValue(EMDL_PARTICLE_RANDOM_SUBSET, random_subset, part_id); } } @@ -458,10 +476,13 @@ void Experiment::initialiseBodies(int _nr_bodies) } } -bool Experiment::getImageNameOnScratch(long int part_id, int img_id, FileName &fn_img, bool is_ctf_image) +bool Experiment::getImageNameOnScratch(long int part_id, FileName &fn_img, bool is_ctf_image) { - int optics_group = getOpticsGroup(part_id, img_id); - long int my_id = particles[part_id].images[img_id].optics_group_id; + int optics_group = getOpticsGroup(part_id); + //TODO: move optics_group_id to particle, not image!!!! + long int my_id = particles[part_id].optics_group_id; + + //REPORT_ERROR("DEBUG: STILL NEED TO ACCOUNT FOR MULTIPLE IMAGES PER PARTICLE HEREE!!!!!! UNFINISHED CODE...."); #ifdef DEBUG_SCRATCH std::cerr << "part_id = " << part_id << " img_id = " << img_id << " my_id = " << my_id << " nr_parts_on_scratch[" << optics_group << "] = " << nr_parts_on_scratch[optics_group] << std::endl; @@ -476,6 +497,10 @@ bool Experiment::getImageNameOnScratch(long int part_id, int img_id, FileName &f else fn_img = fn_scratch + "opticsgroup" + integerToString(optics_group+1) + "_particle" + integerToString(my_id+1)+".mrc"; } + else if (is_tomo) + { + fn_img = fn_scratch + "opticsgroup" + integerToString(optics_group+1) + "_particle" + integerToString(my_id+1)+".mrcs"; + } else { // Write different optics groups into different stacks, as sizes might be different @@ -607,9 +632,10 @@ void Experiment::deleteDataOnScratch() void Experiment::copyParticlesToScratch(int verb, bool do_copy, bool also_do_ctf_image, RFLOAT keep_free_scratch_Gb) { - // This function relies on prepareScratchDirectory() being called before! - long int nr_part = MDimg.numberOfObjects(); + // This function relies on prepareScratchDirectory() being called before! + + long int nr_part = particles.size(); int barstep; if (verb > 0 && do_copy) { @@ -634,36 +660,31 @@ void Experiment::copyParticlesToScratch(int verb, bool do_copy, bool also_do_ctf FileName prev_img_name = "/Unlikely$filename$?*!"; int prev_optics_group = -999; - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDimg) + for (long int part_id = 0; part_id < particles.size(); part_id++) { // TODO: think about MPI_Abort here.... - if (current_object % check_abort_frequency == 0 && pipeline_control_check_abort_job()) + if (part_id % check_abort_frequency == 0 && pipeline_control_check_abort_job()) exit(RELION_EXIT_ABORTED); - long int imgno; - FileName fn_img, fn_ctf, fn_stack, fn_new; - Image img; - MDimg.getValue(EMDL_IMAGE_NAME, fn_img); + long int imgno; + FileName fn_ctf, fn_stack, fn_new; + Image img; + FileName fn_img = particles[part_id].name; + int optics_group = particles[part_id].optics_group; - int optics_group = 0; - if (MDimg.getValue(EMDL_IMAGE_OPTICS_GROUP, optics_group)) - { - optics_group--; - } - - // Get the size of the first particle + // Get the size of the first particle if (nr_parts_on_scratch[optics_group] == 0) { Image tmp; tmp.read(fn_img, false); // false means: only read the header! - one_part_space = ZYXSIZE(tmp())*sizeof(float); // MRC images are stored in floats! + one_part_space = NZYXSIZE(tmp())*sizeof(float); // MRC images are stored in floats! bool myis3D = (ZSIZE(tmp()) > 1); if (myis3D != is_3D) REPORT_ERROR("BUG: inconsistent is_3D values!"); - // add MRC header size for subtomograms, which are stored as 1 MRC file each - if (is_3D) + // add MRC header size for subtomograms (in 3D or as 2D stack), which are stored as 1 MRC file each + if (is_3D || is_tomo) one_part_space += 1024; + if (is_3D) { - one_part_space += 1024; also_do_ctf_image = MDimg.containsLabel(EMDL_CTF_IMAGE); if (also_do_ctf_image) one_part_space *= 2; @@ -673,79 +694,87 @@ void Experiment::copyParticlesToScratch(int verb, bool do_copy, bool also_do_ctf #endif } - bool is_duplicate = (prev_img_name == fn_img && prev_optics_group == optics_group); - // Read in the particle image, and write out on scratch - if (do_copy && !is_duplicate) - { + bool is_duplicate = (prev_img_name == fn_img && prev_optics_group == optics_group); + // Read in the particle image, and write out on scratch + if (do_copy && !is_duplicate) + { #ifdef DEBUG_SCRATCH - std::cerr << "used_space = " << used_space << std::endl; + std::cerr << "used_space = " << used_space << std::endl; #endif - // Now we have the particle in memory - // See how much space it occupies - used_space += one_part_space; - // If there is no more space, exit the loop over all objects to stop copying files and change filenames in MDimg - if (used_space > max_space) - { - char nodename[64] = "undefined"; - gethostname(nodename,sizeof(nodename)); - std::string myhost(nodename); - std::cerr << " Warning: scratch space full on " << myhost << ". Remaining " << nr_part - total_nr_parts_on_scratch << " particles will be read from where they were."<< std::endl; - break; - } - - if (is_3D) - { - // For subtomograms, write individual .mrc files,possibly also CTF images - img.read(fn_img); - fn_new = fn_scratch + "opticsgroup" + integerToString(optics_group+1) + "_particle" + integerToString(nr_parts_on_scratch[optics_group]+1)+".mrc"; - img.write(fn_new); - if (also_do_ctf_image) - { - FileName fn_ctf; - MDimg.getValue(EMDL_CTF_IMAGE, fn_ctf); - img.read(fn_ctf); - fn_new = fn_scratch + "opticsgroup" + integerToString(optics_group+1) + "_particle_ctf" + integerToString(nr_parts_on_scratch[optics_group]+1)+".mrc"; - img.write(fn_new); - } - } - else - { - // Only open/close new stacks, so check if this is a new stack - fn_img.decompose(imgno, fn_stack); - if (fn_stack != fn_open_stack) - { - // Manual closing isn't necessary: if still open, then openFile will first close the filehandler - // Also closing the last one isn't necessary, as destructor will do this. - //if (fn_open_stack != "") - // hFile.closeFile(); - hFile.openFile(fn_stack, WRITE_READONLY); - fn_open_stack = fn_stack; - } - img.readFromOpenFile(fn_img, hFile, -1, false); - - fn_new.compose(nr_parts_on_scratch[optics_group]+1, fn_scratch + "opticsgroup" + integerToString(optics_group+1) + "_particles.mrcs"); - if (nr_parts_on_scratch[optics_group] == 0) - img.write(fn_new, -1, false, WRITE_OVERWRITE); - else - img.write(fn_new, -1, true, WRITE_APPEND); + // Now we have the particle in memory + // See how much space it occupies + used_space += one_part_space; + // If there is no more space, exit the loop over all objects to stop copying files and change filenames in MDimg + if (used_space > max_space) + { + char nodename[64] = "undefined"; + gethostname(nodename,sizeof(nodename)); + std::string myhost(nodename); + std::cerr << " Warning: scratch space full on " << myhost << ". Remaining " << nr_part - total_nr_parts_on_scratch << " particles will be read from where they were."<< std::endl; + break; + } + + if (is_3D) + { + // For subtomograms, write individual .mrc files,possibly also CTF images + img.read(fn_img); + fn_new = fn_scratch + "opticsgroup" + integerToString(optics_group+1) + "_particle" + integerToString(nr_parts_on_scratch[optics_group]+1)+".mrc"; + img.write(fn_new); + if (also_do_ctf_image) + { + FileName fn_ctf; + MDimg.getValue(EMDL_CTF_IMAGE, fn_ctf, part_id); + img.read(fn_ctf); + fn_new = fn_scratch + "opticsgroup" + integerToString(optics_group+1) + "_particle_ctf" + integerToString(nr_parts_on_scratch[optics_group]+1)+".mrc"; + img.write(fn_new); + } + } + else if (is_tomo) + { + // For subtomograms as 2D stacks, write individual .mrcs files + img.read(fn_img); + fn_new = fn_scratch + "opticsgroup" + integerToString(optics_group+1) + "_particle" + integerToString(nr_parts_on_scratch[optics_group]+1)+".mrcs"; + img.write(fn_new); + } + else + { + // Only open/close new stacks, so check if this is a new stack + fn_img.decompose(imgno, fn_stack); + if (fn_stack != fn_open_stack) + { + // Manual closing isn't necessary: if still open, then openFile will first close the filehandler + // Also closing the last one isn't necessary, as destructor will do this. + //if (fn_open_stack != "") + // hFile.closeFile(); + hFile.openFile(fn_stack, WRITE_READONLY); + fn_open_stack = fn_stack; + } + img.readFromOpenFile(fn_img, hFile, -1, false); + + fn_new.compose(nr_parts_on_scratch[optics_group]+1, fn_scratch + "opticsgroup" + integerToString(optics_group+1) + "_particles.mrcs"); + if (nr_parts_on_scratch[optics_group] == 0) + img.write(fn_new, -1, false, WRITE_OVERWRITE); + else + img.write(fn_new, -1, true, WRITE_APPEND); #ifdef DEBUG_SCRATCH - std::cerr << "Cached " << fn_img << " to " << fn_new << std::endl; + std::cerr << "Cached " << fn_img << " to " << fn_new << std::endl; #endif - } - } + } + } - // Update the counter and progress bar - if (!is_duplicate) - nr_parts_on_scratch[optics_group]++; - total_nr_parts_on_scratch++; + // Update the counter and progress bar + if (!is_duplicate) + nr_parts_on_scratch[optics_group]++; + total_nr_parts_on_scratch++; - prev_img_name = fn_img; - prev_optics_group = optics_group; + prev_img_name = fn_img; + prev_optics_group = optics_group; - if (verb > 0 && total_nr_parts_on_scratch % barstep == 0) - progress_bar(total_nr_parts_on_scratch); - } + if (verb > 0 && total_nr_parts_on_scratch % barstep == 0) + progress_bar(total_nr_parts_on_scratch); + + } // end loop part_id if (verb) { @@ -765,8 +794,9 @@ void Experiment::copyParticlesToScratch(int verb, bool do_copy, bool also_do_ctf } // Read from file -void Experiment::read(FileName fn_exp, bool do_ignore_particle_name, bool do_ignore_group_name, bool do_preread_images, - bool need_tiltpsipriors_for_helical_refine, int verb) +bool Experiment::read(FileName fn_exp, FileName fn_tomo, FileName fn_motion, + bool do_ignore_particle_name, bool do_ignore_group_name, bool do_preread_images, + bool need_tiltpsipriors_for_helical_refine, bool set_offset_priors_to_offsets, int verb) { //#define DEBUG_READ @@ -785,7 +815,11 @@ void Experiment::read(FileName fn_exp, bool do_ignore_particle_name, bool do_ign timer.tic(tread); #endif - // Only open stacks once and then read multiple images + bool remove_priors_again = false; + + is_tomo = false; + + // Only open stacks once and then read multiple images fImageHandler hFile; long int dump; FileName fn_stack, fn_open_stack=""; @@ -796,14 +830,35 @@ void Experiment::read(FileName fn_exp, bool do_ignore_particle_name, bool do_ign if (!fn_exp.isStarFile()) { - REPORT_ERROR("ERROR: relion-3.2 no longer accepts image stacks as input to refinement. Use a STAR file instead..."); + REPORT_ERROR("ERROR: Input particles should be provides in a STAR file."); } else { - // MDimg and MDopt have to be read at the same time, so that the optics groups can be + + // MDimg and MDopt have to be read at the same time, so that the optics groups can be // renamed in case they are non-contiguous or not sorted ObservationModel::loadSafely(fn_exp, obsModel, MDimg, "particles", verb); - nr_images_per_optics_group.resize(obsModel.numberOfOpticsGroups(), 0); + is_tomo = obsModel.isTomoStack2D; + + // The below is useful for filenames on scratch disk (related to avoiding copying duplicate particles when doing symmetry expansion) + nr_particles_per_optics_group.resize(obsModel.numberOfOpticsGroups(), 0); + + std::vector> particles_idx; + if (is_tomo) + { + + if (fn_tomo == "") REPORT_ERROR("ERROR: you need to provide --tomograms when refining 2D stacks of tilt series images"); + + // For now read in particle table twice: once into MDimg and once into particleSet... + // TODO: work with pointer to avoid duplication? + // TODO: ObservationModel::loadSafely(fn_exp, obsModel, MDimg, "particles", verb, true, true); + tomogramSet.read(fn_tomo, false); + particleSet.read(fn_exp, fn_motion, false); + + // For checking tomogram sanity below + particles_idx = particleSet.splitByTomogram(tomogramSet, false); + + } // Set is_3D from MDopt int mydim=2; @@ -832,30 +887,19 @@ void Experiment::read(FileName fn_exp, bool do_ignore_particle_name, bool do_ign // allocate 1 block of memory particles.reserve(MDimg.numberOfObjects()); + Tomogram tomogram; + FileName prev_tomo_name = ""; + // Now Loop over all objects in the metadata file and fill the logical tree of the experiment - long int first_part_id = -1; - FileName prev_mic_name = "/Unlikely$filename$?*!"; - FileName prev_img_name = "/Unlikely$filename$?*!"; - int prev_optics_group = -999; - //FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDimg) - for (long int ori_img_id = 0; ori_img_id < MDimg.numberOfObjects(); ori_img_id++) + for (long int part_id = 0; part_id < MDimg.numberOfObjects(); part_id++) { // Get the optics group of this particle - int optics_group = obsModel.getOpticsGroup(MDimg, ori_img_id); + int optics_group = obsModel.getOpticsGroup(MDimg, part_id); #ifdef DEBUG_READ timer.tic(tgroup); #endif - FileName mic_name = getMicrographName(ori_img_id); - // Find last_part_id, which is the first part_id of this micrograph (for efficient searching of ori_particles below...) - if (mic_name != prev_mic_name) - { - prev_mic_name = mic_name; - first_part_id = particles.size(); - - } - // For example in particle_polishing the groups are not needed... if (!do_ignore_group_name) { @@ -863,11 +907,12 @@ void Experiment::read(FileName fn_exp, bool do_ignore_particle_name, bool do_ign // Check whether there is a group label, if not use a group for each micrograph if (MDimg.containsLabel(EMDL_MLMODEL_GROUP_NAME)) { - MDimg.getValue(EMDL_MLMODEL_GROUP_NAME, group_name, ori_img_id); + MDimg.getValue(EMDL_MLMODEL_GROUP_NAME, group_name, part_id); } else { - FileName fn_pre, fn_jobnr; + FileName mic_name = getMicrographName(part_id); + FileName fn_pre, fn_jobnr; decomposePipelineFileName(mic_name, fn_pre, fn_jobnr, group_name); } @@ -889,85 +934,105 @@ void Experiment::read(FileName fn_exp, bool do_ignore_particle_name, bool do_ign else { // All images belong to the same micrograph and group - group_id = addGroup("group", 0); + if (part_id == 0) group_id = addGroup("group", 0); + else group_id = 0; } + // The group number is only set upon reading: it is not read from the STAR file itself, + // there the only thing that matters is the order of the micrograph_names + // Write igroup+1, to start numbering at one instead of at zero + MDimg.setValue(EMDL_MLMODEL_GROUP_NO, group_id + 1, part_id); + #ifdef DEBUG_READ timer.toc(tgroup); #endif // If there is an EMDL_PARTICLE_RANDOM_SUBSET entry in the input STAR-file, then set the random_subset, otherwise use default (0) int my_random_subset; - if (!MDimg.getValue(EMDL_PARTICLE_RANDOM_SUBSET, my_random_subset, ori_img_id)) + if (!MDimg.getValue(EMDL_PARTICLE_RANDOM_SUBSET, my_random_subset, part_id)) { my_random_subset = 0; } - // Add this image to an existing particle, or create a new particle - std::string part_name; - long int part_id = -1; - - if (MDimg.containsLabel(EMDL_PARTICLE_NAME)) - MDimg.getValue(EMDL_PARTICLE_NAME, part_name, ori_img_id); - else - MDimg.getValue(EMDL_IMAGE_NAME, part_name, ori_img_id); - - if (MDimg.containsLabel(EMDL_PARTICLE_NAME) && !do_ignore_particle_name) - { - // Only search ori_particles for the last (original) micrograph - for (long int i = first_part_id; i < particles.size(); i++) - { - if (particles[i].name == part_name) - { - part_id = i; - break; - } - } - } - - // If no particles with this name was found, - // or if no EMDL_PARTICLE_NAME in the input file, or if do_ignore_original_particle_name - // then add a new particle - if (part_id < 0) - { - part_id = addParticle(part_name, my_random_subset); - } - - // Create a new image in this particle - FileName img_name; - MDimg.getValue(EMDL_IMAGE_NAME, img_name, ori_img_id); - - bool do_cache = (prev_img_name != img_name || prev_optics_group != optics_group); -#ifdef DEBUG_SCRATCH - std::cerr << "prev_img_name = " << prev_img_name << " img_name = " << img_name << " prev_optics_group = " << prev_optics_group << " optics_group = " << optics_group << " do_cache = " << do_cache << std::endl; -#endif - prev_img_name = img_name; - prev_optics_group = optics_group; - - int img_id = addImageToParticle(part_id, img_name, ori_img_id, group_id, optics_group, do_cache); - - // The group number is only set upon reading: it is not read from the STAR file itself, - // there the only thing that matters is the order of the micrograph_names - // Write igroup+1, to start numbering at one instead of at zero - MDimg.setValue(EMDL_MLMODEL_GROUP_NO, group_id + 1, ori_img_id); + FileName img_name; + MDimg.getValue(EMDL_IMAGE_NAME, img_name, part_id); + + // Now get all the images for this particle (only one for SPA, multiple for STA) + if (is_tomo) + { + + // Find the tomogram this particle belongs to + std::string tomo_name = MDimg.getString(EMDL_TOMO_NAME, part_id); + int tomo_id = tomogramSet.getTomogramIndex(tomo_name); + + if (tomo_name != prev_tomo_name) + { + tomogram = tomogramSet.loadTomogram(tomo_id, false, false, false, particleSet.max_dose); + prev_tomo_name = tomo_name; + + // Tomogram sanity checks + tomogram.validateParticleOptics(particles_idx[tomo_id], particleSet); + particleSet.checkTrajectoryLengths(particles_idx[tomo_id], tomogram.frameCount, "exp_model.read"); + } + + // Add this particle to the Experiment, with its tomogram + addParticle(img_name, optics_group, group_id, my_random_subset, tomo_id); + + // Pre-orientation of this particle in the tomogram + ParticleIndex id(part_id); + d3Matrix A = particleSet.getSubtomogramMatrix(id); + const d3Vector pos = particleSet.getPosition(id); + + // Add all images for this particle + const int fc = tomogram.frameCount; + for (int f = 0; f < fc; f++) + { + + int fp = tomogram.selectedFrameIndex[f]; + if (fp >= 0) + { + d4Matrix P = tomogram.projectionMatrices[f] * d4Matrix(A); + + CTF ctf = tomogram.getCtf(f, pos); + + float dose = tomogram.getCumulativeDose(f); + addImageToParticle(part_id, &P, &ctf, dose); + } + } + } + else + { + // Add this particle to the Experiment + addParticle(img_name, optics_group, group_id, my_random_subset); + + // Create a new image in this particle + addImageToParticle(part_id); + } #ifdef DEBUG_READ - timer.tic(tori); + timer.tic(tori); #endif - - if (do_preread_images) - { - Image img; - img_name.decompose(dump, fn_stack); - if (fn_stack != fn_open_stack) - { - hFile.openFile(fn_stack, WRITE_READONLY); - fn_open_stack = fn_stack; - } - img.readFromOpenFile(img_name, hFile, -1, false); - img().setXmippOrigin(); - particles[part_id].images[img_id].img = img(); - } + if (do_preread_images) + { + Image img; + if (is_tomo || is_3D) + { + img.read(img_name); + particles[part_id].img = img(); + } + else + { + img_name.decompose(dump, fn_stack); + if (fn_stack != fn_open_stack) + { + hFile.openFile(fn_stack, WRITE_READONLY); + fn_open_stack = fn_stack; + } + img.readFromOpenFile(img_name, hFile, -1, false); + img().setXmippOrigin(); + particles[part_id].img = img(); + } + } #ifdef DEBUG_READ timer.toc(tori); @@ -976,7 +1041,7 @@ void Experiment::read(FileName fn_exp, bool do_ignore_particle_name, bool do_ign #ifdef DEBUG_READ nr_read++; #endif - } // end loop over all objects in MDimg (ori_part_id) + } // end loop over all objects in MDimg (part_id) #ifdef DEBUG_READ timer.toc(tfill); @@ -1069,8 +1134,35 @@ void Experiment::read(FileName fn_exp, bool do_ignore_particle_name, bool do_ign MDimg.setValue(EMDL_ORIENT_PSI, psi); } } + + if (set_offset_priors_to_offsets) + { + RFLOAT off; + if (!MDimg.containsLabel(EMDL_ORIENT_ORIGIN_X_PRIOR_ANGSTROM)) + { + MDimg.getValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, off); + MDimg.setValue(EMDL_ORIENT_ORIGIN_X_PRIOR_ANGSTROM, off); + remove_priors_again = true; + } + if (!MDimg.containsLabel(EMDL_ORIENT_ORIGIN_Y_PRIOR_ANGSTROM)) + { + MDimg.getValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, off); + MDimg.setValue(EMDL_ORIENT_ORIGIN_Y_PRIOR_ANGSTROM, off); + } + if (have_zcoord && !MDimg.containsLabel(EMDL_ORIENT_ORIGIN_Z_PRIOR_ANGSTROM)) + { + MDimg.getValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, off); + MDimg.setValue(EMDL_ORIENT_ORIGIN_Z_PRIOR_ANGSTROM, off); + } + } } + // Make sure the partTable in particleSet is kept the same as MDimg in Experiment, for reading from it in ml_optimiser.cpp + // TODO! Make use of pointers to avoid duplication of entire MDimg here... + if (is_tomo) particleSet.partTable = MDimg; + + // Keep track whether priors that were added above should be removed again later... + return remove_priors_again; #ifdef DEBUG_READ timer.toc(tdef); @@ -1084,27 +1176,30 @@ void Experiment::read(FileName fn_exp, bool do_ignore_particle_name, bool do_ign } // Write to file -void Experiment::write(FileName fn_out) +void Experiment::write(FileName fn_out, bool remove_offset_priors) { - std::ofstream fh; - fh.open((fn_out).c_str(), std::ios::out); - if (!fh) - REPORT_ERROR( (std::string)"Experiment::write: Cannot write file: " + fn_out); - - obsModel.opticsMdt.setName("optics"); - obsModel.opticsMdt.write(fh); - - // Always write MDimg - MDimg.setName("particles"); - MDimg.write(fh); - - if (nr_bodies > 1) - { - for (int ibody = 0; ibody < nr_bodies; ibody++) - { - MDbodies[ibody].write(fh); - } - } + std::ofstream ofs(fn_out); + + if (is_tomo) obsModel.generalMdt.write(ofs); + + obsModel.opticsMdt.write(ofs); + if (remove_offset_priors) + { + if (MDimg.containsLabel(EMDL_ORIENT_ORIGIN_X_PRIOR_ANGSTROM)) + MDimg.deactivateLabel(EMDL_ORIENT_ORIGIN_X_PRIOR_ANGSTROM); + if (MDimg.containsLabel(EMDL_ORIENT_ORIGIN_Y_PRIOR_ANGSTROM)) + MDimg.deactivateLabel(EMDL_ORIENT_ORIGIN_Y_PRIOR_ANGSTROM); + if (MDimg.containsLabel(EMDL_ORIENT_ORIGIN_Z_PRIOR_ANGSTROM)) + MDimg.deactivateLabel(EMDL_ORIENT_ORIGIN_Z_PRIOR_ANGSTROM); + } + MDimg.write(ofs); + + if (nr_bodies > 1) + { + for (int ibody = 0; ibody < nr_bodies; ibody++) + { + MDbodies[ibody].write(ofs); + } + } - fh.close(); } diff --git a/src/exp_model.h b/src/exp_model.h index 7d9f54110..a3b586e77 100644 --- a/src/exp_model.h +++ b/src/exp_model.h @@ -27,38 +27,35 @@ #include "src/time.h" #include "src/ctf.h" #include +#include +#include +#include +#include /// Reserve large vectors with some reasonable estimate // Larger numbers will still be OK, but memory management might suffer #define MAX_NR_GROUPS 2000 +using namespace gravis; ////////////// Hierarchical metadata model class ExpImage { public: - // Position of the image in the original input STAR file - long int id; // To which particle does this image belong long int particle_id; - // This is the Nth image in this optics_group, for writing to scratch disk: filenames - long int optics_group_id; - - // Name of this image (by this name it will be recognised upon reading) - std::string name; + // Projection matrix for tilt series stacks + Matrix2D Aproj; - // ID of the group that this image comes from - long int group_id; + // Flag to monitor whether this images is all-zero + bool is_empty; - // The optics group for this image - int optics_group; + // CTF information for defocus adjustment of tilt seriers + float defU, defV, defAngle, dose; - // Pre-read array of the image in RAM - MultidimArray img; - - // Empty Constructor + // Empty Constructor ExpImage() {} // Destructor needed for work with vectors @@ -67,26 +64,26 @@ class ExpImage // Copy constructor needed for work with vectors ExpImage(ExpImage const& copy) { - id = copy.id; particle_id = copy.particle_id; - optics_group_id = copy.optics_group_id; - name = copy.name; - group_id = copy.group_id; - optics_group = copy.optics_group; - img = copy.img; - + Aproj = copy.Aproj; + is_empty =copy.is_empty; + defU = copy.defU; + defV = copy.defV; + defAngle = copy.defAngle; + dose = copy.dose; } // Define assignment operator in terms of the copy constructor ExpImage& operator=(ExpImage const& copy) { - id = copy.id; + particle_id = copy.particle_id; - optics_group_id = copy.optics_group_id; - name = copy.name; - group_id = copy.group_id; - optics_group = copy.optics_group; - img = copy.img; + Aproj = copy.Aproj; + is_empty =copy.is_empty; + defU = copy.defU; + defV = copy.defV; + defAngle = copy.defAngle; + dose = copy.dose; return *this; } }; @@ -95,10 +92,28 @@ class ExpParticle { public: - // Name of this particle (by this name all the images inside it will be grouped) - std::string name; + // Position of the image in the original input STAR file + long int id; + + // Name of this particle (by this name it will be recognised upon reading) + FileName name; + + // Pre-read array of the image in RAM + MultidimArray img; + + // Which tomogram does this particle belong to + int tomogram_id; + + // ID of the group that this particle comes from + long int group_id; + + // The optics group for this particle + int optics_group; + + // This is the Nth particle in its optics_group, for writing to scratch disk: filenames + long int optics_group_id; - // Random subset this particle belongs to + // Random subset this particle belongs to int random_subset; // Vector of all the images for this particle @@ -113,17 +128,29 @@ class ExpParticle // Copy constructor needed for work with vectors ExpParticle(ExpParticle const& copy) { - name = copy.name; - random_subset = copy.random_subset; + id = copy.id; + name = copy.name; + img = copy.img; + tomogram_id = copy.tomogram_id; + group_id = copy.group_id; + random_subset = copy.random_subset; + optics_group = copy.optics_group; + optics_group_id = copy.optics_group_id; images = copy.images; } // Define assignment operator in terms of the copy constructor ExpParticle& operator=(ExpParticle const& copy) { - name = copy.name; - random_subset = copy.random_subset; - images = copy.images; + id = copy.id; + name = copy.name; + img = copy.img; + tomogram_id = copy.tomogram_id; + group_id = copy.group_id; + random_subset = copy.random_subset; + optics_group = copy.optics_group; + optics_group_id = copy.optics_group_id; + images = copy.images; return *this; } @@ -185,12 +212,19 @@ class Experiment long int nr_particles_subset1, nr_particles_subset2; // Number of images per optics group - std::vector nr_images_per_optics_group; + std::vector nr_particles_per_optics_group; - // One large MetaDataTable for all images + // One large MetaDataTable for all particles MetaDataTable MDimg; - // Number of bodies in multi-body refinement + // TODO: Pointer to the relevant MetaDataTable for all particles (MDimg for SPA, particleSet.partTable for STA) + //MetaDataTable *ptrMDimg; + + // For subtomogram averaging in RELION-4.1.... + ParticleSet particleSet; + TomogramSet tomogramSet; + + // Number of bodies in multi-body refinement int nr_bodies; // Vector with MetaDataTables for orientations of different bodies in the multi-body refinement @@ -209,7 +243,7 @@ class Experiment RFLOAT free_space_Gb; // Is this sub-tomograms? - bool is_3D; + bool is_tomo, is_3D; // Empty Constructor Experiment() @@ -227,6 +261,7 @@ class Experiment groups.clear(); groups.reserve(MAX_NR_GROUPS); particles.clear(); // reserve upon reading + particleSet.clearParticles(); sorted_idx.clear(); nr_particles_subset1 = nr_particles_subset2 = 0; nr_bodies = 1; @@ -234,6 +269,7 @@ class Experiment nr_parts_on_scratch.clear(); free_space_Gb = 10; is_3D = false; + is_tomo = false; MDimg.clear(); MDimg.setIsList(false); MDbodies.clear(); @@ -264,37 +300,40 @@ class Experiment // Get the random_subset for this particle int getRandomSubset(long int part_id); - // Get the group_id for the N'th image for this particle - long int getGroupId(long int part_id, int img_id); + // Get the group_id for this particle + long int getGroupId(long int part_id); - // Get the optics group to which the N'th image for this particle belongs - int getOpticsGroup(long int part_id, int img_id); + // Get the optics group to which this particle belongs + int getOpticsGroup(long int part_id); - // Get the original position in the input STAR file for the N'th image for this particle - int getOriginalImageId(long int part_id, int img_id); + // Get the pixel size for (all) the images of this particle + RFLOAT getImagePixelSize(long int part_id); - // Get the pixel size for the N-th image of this particle - RFLOAT getImagePixelSize(long int part_id, int img_id); + // Get the rotation matrix for Nth image of the subtomo particle + Matrix2D getRotationMatrix(long int part_id, int img_id); - // Get the vector of number of images per group_id - void getNumberOfImagesPerGroup(std::vector &nr_particles_per_group, int random_subset = 0); + // Get the shift in the 2D tilt series image for a subtomo particle from a 3D shift + void getTranslationInTiltSeries(long int part_id, int img_id, + RFLOAT shift3d_x, RFLOAT shift3d_y, RFLOAT shift3d_z, + RFLOAT &shift2d_x, RFLOAT &shift2d_y, RFLOAT &shift2d_z); + + // Get the vector of number of images per group_id + void getNumberOfParticlesPerGroup(std::vector &nr_particles_per_group, int random_subset = 0); // Get the vector of number of images per group_id - void getNumberOfImagesPerOpticsGroup(std::vector &nr_particles_per_group, int random_subset = 0); + void getNumberOfParticlesPerOpticsGroup(std::vector &nr_particles_per_group, int random_subset = 0); // Get the metadata-row for this image in a separate MetaDataTable - MetaDataTable getMetaDataImage(long int part_id, int img_id); + MetaDataTable getMetaDataParticle(long int part_id); // Which micrograph (or tomogram) doe this particle image comes from? - FileName getMicrographName(long int ori_img_id); - FileName getMicrographName(long int part_id, int img_id); + FileName getMicrographName(long int part_id); // Add a particle - long int addParticle(std::string part_name, int random_subset = 0); + void addParticle(std::string img_name, int optics_group, long int group_id, int random_subset = 0, int tomogram_id = 0); // Add an image to the given particle - int addImageToParticle(long int part_id, std::string img_name, long int ori_img_id, long int group_id, - int optics_group, bool unique); + void addImageToParticle(long int part_id, d4Matrix *Aproj = NULL, CTF *ctf = NULL, float dose = 0.); // Add a group long int addGroup(std::string mic_name, int optics_group); @@ -313,7 +352,7 @@ class Experiment void initialiseBodies(int _nr_bodies); // Get the image name for a given part_id - bool getImageNameOnScratch(long int part_id, int img_id, FileName &fn_img, bool is_ctf_image = false); + bool getImageNameOnScratch(long int part_id, FileName &fn_img, bool is_ctf_image = false); // For parallel executions, lock the scratch directory with a unique code, so we won't copy the same data many times to the same position // This determines the lockname and removes the lock if it exists @@ -335,14 +374,15 @@ class Experiment void copyParticlesToScratch(int verb, bool do_copy = true, bool also_do_ctf_image = false, RFLOAT free_scratch_Gb = 10); // Read from file - void read( - FileName fn_in, + bool read( + FileName fn_in, FileName fn_tomo, FileName fn_motion, bool do_ignore_particle_name = false, bool do_ignore_group_name = false, bool do_preread_images = false, - bool need_tiltpsipriors_for_helical_refine = false, int verb = 0); + bool need_tiltpsipriors_for_helical_refine = false, + bool set_offset_priors_to_offsets = false, int verb = 0); // Write - void write(FileName fn_root); + void write(FileName fn_root, bool remove_offset_priors = false); private: @@ -351,7 +391,7 @@ class Experiment { const std::vector& particles; compareOpticsGroupsParticles(const std::vector& particles) : particles(particles) { } - bool operator()(const long int i, const long int j) { return particles[i].images[0].optics_group < particles[j].images[0].optics_group;} + bool operator()(const long int i, const long int j) { return particles[i].optics_group < particles[j].optics_group;} }; struct compareRandomSubsetParticles diff --git a/src/fftw.h b/src/fftw.h index 5d317cf94..4a0f6b1b0 100644 --- a/src/fftw.h +++ b/src/fftw.h @@ -60,11 +60,12 @@ #endif #ifdef FAST_CENTERFFT // defined if ALTCPU=on *AND* Intel Compiler used -#include "src/acc/cpu/cuda_stubs.h" #include "src/acc/settings.h" -#include "src/acc/cpu/cpu_settings.h" -#include "src/acc/cpu/cpu_kernels/helper.h" -#include +#ifdef _SYCL_ENABLED + #include "src/acc/sycl/sycl_settings.h" +#else + #include "src/acc/cpu/cpu_settings.h" +#endif #endif /** @defgroup FourierW FFTW Fourier transforms @@ -401,6 +402,104 @@ void CenterFFTbySign(MultidimArray &v) } } +template +#ifndef __INTEL_COMPILER +__attribute__((always_inline)) +inline +#endif +void centerFFT_2D_CPU( + int batch_size, + size_t pixel_start, + size_t pixel_end, + T *img_in, + size_t image_size, + int xdim, + int ydim, + int xshift, + int yshift) +{ +#ifdef DEBUG_CUDA + if (image_size > (size_t)std::numeric_limits::max()) + ACC_PTR_DEBUG_INFO("centerFFT_2D: image_size > std::numeric_limits::max()"); + if (image_size*(size_t)batch_size > (size_t)std::numeric_limits::max()) + ACC_PTR_DEBUG_INFO("centerFFT_2D: image_size*batch_size > std::numeric_limits::max()"); + if (pixel_end > image_size) + ACC_PTR_DEBUG_INFO("centerFFT_2D: pixel_end > image_size"); +#endif + size_t pix_start = pixel_start; + size_t pix_end = pixel_end; + for(int batch=0; batch +#ifndef __INTEL_COMPILER +__attribute__((always_inline)) +inline +#endif +void centerFFT_3D_CPU( + int batch_size, + size_t pixel_start, + size_t pixel_end, + T *img_in, + size_t image_size, + int xdim, + int ydim, + int zdim, + int xshift, + int yshift, + int zshift) +{ +#ifdef DEBUG_CUDA + if (image_size > (size_t)std::numeric_limits::max()) + ACC_PTR_DEBUG_INFO("centerFFT_3D: image_size > std::numeric_limits::max()"); + if (image_size*(size_t)batch_size > (size_t)std::numeric_limits::max()) + ACC_PTR_DEBUG_INFO("centerFFT_3D: image_size*batch_size > std::numeric_limits::max()"); + if (pixel_end > image_size) + ACC_PTR_DEBUG_INFO("centerFFT_3D: pixel_end > image_size"); +#endif + size_t pix_start = pixel_start; + size_t pix_end = pixel_end; + int xydim = xdim*ydim; + for(int batch=0; batch& v, bool forward) size_t isize2 = image_size/2; int blocks = ceilf((float)(image_size/(float)(2*CFTT_BLOCK_SIZE))); -// for(int i=0; i isize2) pixel_end = isize2; - CpuKernels::centerFFT_2D(batchSize, pixel_start, pixel_end, MULTIDIM_ARRAY(v), + centerFFT_2D_CPU(batchSize, pixel_start, pixel_end, MULTIDIM_ARRAY(v), (size_t)xSize*ySize, xSize, ySize, xshift, yshift); } - ); } else if ( v.getDim() == 3 ) { @@ -683,17 +781,16 @@ void CenterFFT(MultidimArray< T >& v, bool forward) size_t image_size = xSize*ySize*zSize; size_t isize2 = image_size/2; int block =ceilf((float)(image_size/(float)(2*CFTT_BLOCK_SIZE))); -// for(int i=0; i isize2) pixel_end = isize2; - CpuKernels::centerFFT_3D(batchSize, pixel_start, pixel_end, MULTIDIM_ARRAY(v), + centerFFT_3D_CPU(batchSize, pixel_start, pixel_end, MULTIDIM_ARRAY(v), (size_t)xSize*ySize*zSize, xSize, ySize, zSize, xshift, yshift, zshift); } - ); } else { @@ -709,17 +806,16 @@ void CenterFFT(MultidimArray< T >& v, bool forward) size_t image_size = xSize*ySize; size_t isize2 = image_size/2; int blocks = ceilf((float)(image_size/(float)(2*CFTT_BLOCK_SIZE))); -// for(int i=0; i isize2) pixel_end = isize2; - CpuKernels::centerFFT_2D(batchSize, pixel_start, pixel_end, MULTIDIM_ARRAY(v), + centerFFT_2D_CPU(batchSize, pixel_start, pixel_end, MULTIDIM_ARRAY(v), (size_t)xSize*ySize, xSize, ySize, xshift, yshift); } - ); } } else diff --git a/src/flex_analyser.cpp b/src/flex_analyser.cpp index 94c41cb96..96e27abe6 100644 --- a/src/flex_analyser.cpp +++ b/src/flex_analyser.cpp @@ -64,7 +64,7 @@ void FlexAnalyser::initialise() if (fn_data == "") REPORT_ERROR("ERROR: please provide the --data argument!"); else - data.read(fn_data); + data.read(fn_data, "", ""); if (verb > 0) std::cout << " Reading in model.star file ..." << std::endl; @@ -350,7 +350,7 @@ void FlexAnalyser::make3DModelOneParticle(long int part_id, long int imgno, std: data.MDimg.getValue(EMDL_ORIENT_PSI, psi, part_id); Euler_angles2matrix(rot, tilt, psi, Aori, false); - RFLOAT my_pixel_size = data.getImagePixelSize(part_id, 0); + RFLOAT my_pixel_size = data.getImagePixelSize(part_id); Image img; MultidimArray sumw; diff --git a/src/gui_background.xpm b/src/gui_background.xpm index bb288dcb3..a13604579 100644 --- a/src/gui_background.xpm +++ b/src/gui_background.xpm @@ -1,591 +1,594 @@ /* XPM */ static const char *gui_background[] = { -"598 332 256 2", -" c None", -". c #000100", -"+ c #070A06", -"@ c #10120E", -"# c #111620", -"$ c #1A1B19", -"% c #092856", -"& c #272926", -"* c #00327B", -"= c #5C2632", -"- c #532932", -"; c #03398C", -"> c #343633", -", c #003E9C", -"' c #78273A", -") c #702A3A", -"! c #044393", -"~ c #752C35", -"{ c #82293E", -"] c #6B323E", -"^ c #62353F", -"/ c #713434", -"( c #802F40", -"_ c #9A2945", -": c #7A3343", -"< c #922C42", -"[ c #424441", -"} c #962C3F", -"| c #902F47", -"1 c #8B3246", -"2 c #194C9A", -"3 c #5A4527", -"4 c #96323C", -"5 c #86383A", -"6 c #893749", -"7 c #394C69", -"8 c #A13246", -"9 c #833C4C", -"0 c #9F344D", -"a c #1E539B", -"b c #7E4042", -"c c #7D4233", -"d c #7B414E", -"e c #9A3950", -"f c #73464F", -"g c #963C50", -"h c #AB3947", -"i c #525351", -"j c #924054", -"k c #674E55", -"l c #A13F42", -"m c #6E5223", -"n c #8D4455", -"o c #964347", -"p c #A93E57", -"q c #98473D", -"r c #A24459", -"s c #9C485D", -"t c #8E4E50", -"u c #964D5D", -"v c #8C505E", -"w c #5E5E5D", -"x c #3764A6", -"y c #5A6168", -"z c #B14A4C", -"A c #7E5A45", -"B c #835761", -"C c #BF484B", -"D c #AB4C62", -"E c #B64A63", -"F c #785D64", -"G c #716167", -"H c #A65456", -"I c #985A47", -"J c #666665", -"K c #9F5666", -"L c #A95468", -"M c #995E36", -"N c #846737", -"O c #9D612C", -"P c #B6556B", -"Q c #8B682F", -"R c #7C6A4D", -"S c #9B5F6E", -"T c #A05F61", -"U c #4B72AD", -"V c #DA5152", -"W c #916B2A", -"X c #95636F", -"Y c #A85E6F", -"Z c #8F6670", -"` c #7D6C71", -" . c #717170", -".. c #786F74", -"+. c #866B73", -"@. c #CB5A58", -"#. c #BE5E75", -"$. c #AF6A6A", -"%. c #9D7068", -"&. c #B26979", -"*. c #B96779", -"=. c #AF703E", -"-. c #F25A5B", -";. c #AB6D7C", -">. c #7D7C7C", -",. c #5A81B7", -"'. c #A27934", -"). c #B17158", -"!. c #9E7A3E", -"~. c #A1747F", -"{. c #A6737F", -"]. c #9A7781", -"^. c #AB7B2E", -"/. c #8D7E6B", -"(. c #937E5C", -"_. c #9B7D4F", -":. c #907C84", -"<. c #A9795F", -"[. c #8C8276", -"}. c #BD7485", -"|. c #858585", -"1. c #B77888", -"2. c #FF6767", -"3. c #6F8BAD", -"4. c #BD824E", -"5. c #C18438", -"6. c #B6828F", -"7. c #B98A3A", -"8. c #B2867A", -"9. c #B28B4A", -"0. c #B58B44", -"a. c #B28590", -"b. c #BE8B32", -"c. c #AE8D54", -"d. c #AB8D5F", -"e. c #C48B2C", -"f. c #9D8F7B", -"g. c #7893C1", -"h. c #A48F6E", -"i. c #90928E", -"j. c #AD8992", -"k. c #A38C93", -"l. c #949195", -"m. c #C48494", -"n. c #CC9334", -"o. c #CE9245", -"p. c #CB983F", -"q. c #CA9846", -"r. c #FD8383", -"s. c #A09DA2", -"t. c #C7929E", -"u. c #BD9694", -"v. c #9E9F9B", -"w. c #C3949F", -"x. c #C49B59", -"y. c #D69A32", -"z. c #C79C4B", -"A. c #C69C54", -"B. c #BF9D63", -"C. c #89A3CA", -"D. c #BC98A1", -"E. c #D49C3D", -"F. c #BD9E6F", -"G. c #B69F7D", -"H. c #B49DA3", -"I. c #B0A18C", -"J. c #A6A7A3", -"K. c #A9A6AA", -"L. c #E7A232", -"M. c #E0A445", -"N. c #E3A53D", -"O. c #DBA754", -"P. c #DDA84E", -"Q. c #DFAA49", -"R. c #ADAFAC", -"S. c #D8AC4F", -"T. c #B0AEB3", -"U. c #E2AB41", -"V. c #D3AC68", -"W. c #E5A951", -"X. c #D8AC5A", -"Y. c #D7AC63", -"Z. c #D0AD72", -"`. c #CDA8B0", -" + c #9BB4D4", -".+ c #C7ACA5", -"++ c #CBAF7F", -"@+ c #C5B08D", -"#+ c #BEB1A0", -"$+ c #C5AEB3", -"%+ c #FBA0A0", -"&+ c #B7B5BC", -"*+ c #B6B7B4", -"=+ c #EBB458", -"-+ c #EEB846", -";+ c #E7BA60", -">+ c #EBBB50", -",+ c #BDBDC7", -"'+ c #E6BA73", -")+ c #E9BC57", -"!+ c #FAAEB0", -"~+ c #CEBABD", -"{+ c #D9BC8B", -"]+ c #BEBFBE", -"^+ c #C4BEB7", -"/+ c #E6BC6B", -"(+ c #D7B9BF", -"_+ c #E3BF77", -":+ c #E2BF7F", -"<+ c #E0BF86", -"[+ c #D6C09A", -"}+ c #B2C5DD", -"|+ c #D4C1A4", -"1+ c #CDC1C3", -"2+ c #D2C4B1", -"3+ c #C8C8C7", -"4+ c #D8C4C7", -"5+ c #C8C8D2", -"6+ c #EEC86C", -"7+ c #E4C795", -"8+ c #E0C4C9", -"9+ c #F0CE80", -"0+ c #FEC3C4", -"a+ c #D0D1D0", -"b+ c #D0D0DA", -"c+ c #C3D3E6", -"d+ c #E1CDCF", -"e+ c #E7D1A9", -"f+ c #EDD29E", -"g+ c #E3D3BB", -"h+ c #DFD2D3", -"i+ c #E6D4B5", -"j+ c #E0D5C4", -"k+ c #D8D9D6", -"l+ c #D7D9E3", -"m+ c #EDD9DB", -"n+ c #F4DDB2", -"o+ c #F1E0C4", -"p+ c #E2E2DF", -"q+ c #E9E1D5", -"r+ c #E0E2EC", -"s+ c #EFE2CF", -"t+ c #EFE2E2", -"u+ c #E8EAF3", -"v+ c #E9EBE8", -"w+ c #F1EADF", -"x+ c #F0EBE9", -"y+ c #F1F2F9", -"z+ c #F7F2E9", -"A+ c #F2F4F1", -"B+ c #F9F3F2", -"C+ c #FFF7F0", -"D+ c #FFF9F8", -"E+ c #F9FBF8", -"F+ c #FCFAFE", -"G+ c #FEFFFC", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+E+E+E+A+D+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D+D+D+B+B+B+D+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+E+E+D+C+C+s+q+o+o+f+f+:+_+/+/+z.0.B.7+E+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+D+B+B+x+d+d+(+(+(+w.t.1.1.&.*.*.r n X 4+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+g+[+:+:+:+/+;+=+=+z.!.0.Q.Q.M.p.p.p.7.!.9.Q.M.'+e+z+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+E+E+E+D+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+B &.*.r r r r r r g e r e g j Y &.9 = ) j x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+n+0.^.0.'.^.p.p.7.p.p.b.y.N.N.N.N.-+-+-+p.p.A+h._.p.N.N.U.Y.e+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+z+/+Q 9.V.V.A.z.q.q.P.Q q.Q.Q.=+=+=+Q.Q.q._.w+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+~+d ] - : t.&.}.}.}.*.L e ( e r r r p r p 0 : ( : s `.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+C+<+;+y.>+>+U.U.>+U.Q.Q.Q.Q.Q.Q.Q.Q.S.P.S.P.p.N.X.B.!.j+x.Q.E.P.Q.P.j+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+o+_+B.Q c.o+o+o+o+i+e+[+'.x.{+++x.Y.A.q.q.7.'.=+<+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k.] ] Z ] ) g g j K Y S n : ) S {.a.D.D.D.B B :.) a.B 6 B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+Z.z.7.y.V.X.d.!.@+2+q+q+|+++j+j+#+@+k+k+A+j+p.Z.N.E.N.e+q+@+z.V.F.0.0.:+s+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+<+6+X.B.'.Q.)+)+)+)+)+)+)+'.P.=+=+S.S.S.S.S.0.'.N.N.=+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+] d ~+x+1 = {.B+B+B+B+B+k.] ] w.B+B+D+B+B+f X D.) r ~.^ t.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+z.7.'.Q y.;+'+q+_.@+D+G+G+j+i+G+G+v+@+E+G+G+q+p.[+E.V._.M.Q.'+e+p.P.c.!.q.P.7+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+6+;+q.7.!.'.'.@+|+g+g+g+g+g+'.c.j+q+j+j+j+k+k+#+!.++z.Q.=+f+C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+f ].x+D+k+D ] u t+d+d+8+(+~.: 9 s }.}.*.*.D 0 p p 1 < | d g `.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+2+!.Q.!.Q x.N.s+G+D+G.c.z+G+G+g+o+G+G+E+_.s+o+:+Y.E.P.B.p.p.x.O.P.q.p.M.p.7.G.I.Q '+C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[+O.z.9.G.j+E+j+'.'.:+:+:+7+7+e+e+^.0.n+n+7+7+f+f+f+Z.'.E+G+G+v+N 0.Q.C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+D 9 D+G+x+S ) ] ' 1 1 1 g g : ] : ' g r u Y ;.] j &.~.B = ( 1.v r (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g+!.W 0.Q _.[+M.s+G+G+2+!.j+G+G+|+o+w+o+n+!.X.;+Y.x.E.[+A+@+N.|+j+@+A.p.7.b.N.X.|+N z.O.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+7.0.F.v+E+G+G+++^.y.N.N.N.U.N.N.N.^.N.N.N.U.N.N.N.N.E.^.:+w+E+A+(.d.F.'+o+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+;.P B B+D+H.: ) : ) s u Y ;.{.f v H.) {.h+B+B+B+^ ].v 9 ] f H.0 1 ( r D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+!.Q 0.z.N 2+E+M.q+G+G+q+_.|+z+o+Z.X.;+;+;+_.F.j+E+@+E.s+G+z+z.@+E+E+B+++7.p.7.M.P.7.2+A.=+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+i+c.G.j+E+G+G+E+s+z.^.'.Z.:+V.Y.p.E.E.^.y.U.U.U.-+-+U.U.p.^.U.=+:+z+(.d.A+z.z.|+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D w.k.~+j.6 ( D.~+] m+B+B+B+B++.Z B+] ;.D+G+G+G+f ~+x+].' ].B+| | | | t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+2+N N A.F._.F.z+E.C+G+G+A+_.B.;+;+q.Z.[+g+q+_.F.s+o+Z.E.s+D+E+|+x.E+G+G+A+0.x.0.^.E.7.:+|+B.e+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+e+6+!.j+G+G+D+z+{+O.7.^.U.U.U.Q.M.p.z.Y.^.^.c.F.[+|+g+j+j+#+'.F.0.E.Q.q.0.s+q+_.7.z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+P t+h+d ) p v ~+F+Z B+G+G+G+B+F Z F+Z n B+G+G+G+f h+G+B+{ s d+Z | ) ( Y x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+h.Q g+j+U.E+A+h.^.N.U.U.U.E.E.p.z.Y.q.x.Z.Z.++F.!.F.g+B.p.++E.U.U.N.<+z+E+j+q.[+E+h.E.x.7.0.z.p.Q.:+C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+C+6+<+z+h.@+z+i+P.)+x.@+2+^.N B+E+E+D+G+G+G+'.Q G.E+G+G+G+G+G+h+W g+G+E+F.n.y.0.E.p.7.p.=+o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+;.h+Z ] : e m.~+d+G+~+`.d+d+d+(+v S t.;.| Y }.*.*.| 0 p p p < | t+1+9 L - : w.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+q.9.s+[+N.j+++7.^.O.|+V.B.2+2+!.A+B+Z.A+E+E+E+q+_.|+E+Z.X.z+{+z.7.n.N.P.:+g+p.i+G+#+7.X.9.P.x.z.E.q.Z.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+:+{+E+A+d.9.q.b.)+e+E+D+2+^._.:+:+:+<+f+f+e+^.Q d.o+s+s+s+z+z+j+W 2+G+G+D+|+^.N.g+_.^.N.y.Q.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+] d ] B 1 p d+1+d+B+j.p p p p p 0 0 < < p p < | | ( S X X r g | L 8+$+6 - ) g D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g+U.x.j+'+E.7.0.F.2+p._+/+C+G+x+_.D+E+x.E+G+G+G+A+h.G.w+A.P./+;+_+{+c.n.y.E.p.p.7+E+k+!.M.N d.O.|+@+E.p.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+_+6+E+z+[+!.7.7.)+g+E+G+G+2+^.X.Y.A.A.z.X.O.P.^.Q 0.Q.U.Q.Q.Q.N.N.U./+n+o+D+A+R W Q.!.'.F.q.N.Q.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+].= f ~+{.1 L B+~+D.B ) #.a.a.~.{.] n s 6 1 D D D s 9 Y 8++.g t.$+( | L : ) = ( t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+:+P.Z.c.U._.g+G+C+7+q.c.++G+G+D+G.A+A+z.E+D+s+e+6+X.A.[+7.V.E+G+G+G+++X.z.P.@+7.'.N.Q.!.7.h._.v+++q.E.'.|+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+g+;+[+++0.d.@+W 6+Z.z+G+D+z+7+z.n.Q.N.N.N.N.y.E.q.N.b.7.[+j+j+q+D+D+A+p.x.h.'.n.y.N.U.b.W ^.o+D+A+(.E._+C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$+6 = 4+a.D r ^ X : ) u u P {.D.$+~+n t.B+x+K *.G+G+G+s $+d ^ n }.D+D+$+9 j | 0 { g `.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+'+'+B.7.Q.j+@+C+:+6+V.#+9.E+G+G+2+j+z+b.f+_+6+;+[+@+O.A+7.Z.E+G+G+G+[+O.|+E.F.7.w+9.E.p.7.9._.j+B+G.E.W B.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+o+6+0.7.0.j+w+Y.0.0.q+G+D+j+A.p.b.N.p.x.[+|+g+j+w+A+y.p.P.z+G+G+G+G+G+A+z._+B+j+q.p.9.'.E.N.U.N.7+j+R G.p.'+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+;.e ] a.L u u ) ) ) $+k+x+#.x+D+D+x+n &.`.%.6 P `.8+8+n d B Z 1 *.D+G+D+k.j ( | 1 : r 8+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+P.:+c.n.'+A+B.9+6+[+Z.B+h.g+E+G+q+++:+n.Y.++i+q+E+j+O.q+b.++E+G+G+G+g+X.s+B.0.A.E+v+|+9.n.7.N V.E+g+P.N 0.j+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+g+z.p.9.@+q+z+_+)+0.Q 2+z+Z.p.b.7.@+N.{+e+G+G+G+G+G+G+E.p.P.s+G+G+G+G+G+E+X.'+E+D+2+x.0.N j+B.>+N.N.z.Q A+G.p.C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+s 6 ] j D d ) ) : g `.(+`.E m.m.m.;.b 5 l l ( E C z z n j.x+B+: #.B+G+G+B+].| t.n | 1 L t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+Q.c.7.N.7+E+0./+o+q+7+G+A+c.j+z+e+z.x.n.i+i+n+e+f+'+U.=+N.;+:+s+C+D+j+X.s+w+y.B.t+]+1+#+_.y.7.q.7+i+P.@+7.G.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+!.7.9.z+E+G+V.S.;+0.!.0.q.^.d.2+v+j+N.{+7+G+G+G+G+G+G+p.p.P.s+G+G+G+G+G+E+Y.'+E+G+q+F.9.!.E+[+)+b.p.-+y.o+C+|+'+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$+r X ] ( 9 : ].s &.S D L L E u &.Y 5 5 l C C 5 E V V C z (+D+G+B E m.G+G+G+B+0 w.S f 1 | }.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[+'.N b.Q.w+9+0.|+j+B.N.U.N.E.n.b.E.p.x.-+@+i+g+j+q+z+Z.g+U.7+q+@+B.z.U.-+;+9+b.E.I.]+]+]+^+M.d.(.p.7.E.:+p.0.|+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+'.B.D+G+z+Z.S.b.b.n.Q 9.Z.E+G+G+G+q+E.<+:+E+G+G+G+G+G+X.P.P.o+G+G+G+G+G+E+Z.P.[+x.G.w+E+A+/+[+)+B.U.Q j+@+0.E.9.W Z.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+u }.3+] 6 j.1+0 1.D+h+8+G+G+E j.f / l C C V V z p -.-.-.V V C m.D.E S m.D+G+1+Y d : B+x+:.: m+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h.Q h.n.X.9+6+!.0.7.^.Z.2+j+X.Y.@+Z.;+X.U.9+f+f+n+o+o+Z.g+U.7+E+E+E+A+|+0.0.q.L.N.O.a+a+a+a+A.z.v.d.q.E.p.N.U.F.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+'.!.A+z+Z.b.7.c.@+z.z.=+=+f+o+z+E+A+p.'+_+D+G+G+G+G+G+Y.E.O.e+D+E+z+o+n+f+A.q.9.G.E+G+G+D+O.Y.>+U.7.7.e+D+x+#+n.y.n.f+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+r `.$+= D 8+B+0 (+D+h+(+G+G+D b / 5 z C C V -.C p -.-.-.-.-.V C $+E (+s m.B+k.6.1+d m+G+F+H.w.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+0.B.g+p.P.;+++7.'.G.2+f+G+o+:+z+E+E+=+@+U.<+:+_+_+/+6+;+V.U.;+f+n+n+o+s+B. .[.N.n.N.++k+k+k+G.N.#+*+@+p.p.N.N.9.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+'.!.g+A.b.n.j+A+B+A.A./+@+O.=+;+_+s+p.'+'+E+G+G+E+E+E+z.Q.P.'+_+/+=+=+P.X.0.;+g+E+G+G+G+G+F.p.>+U.N !.=+G+G+E+2+p.y.q.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+r ].] = D `.x+{ m.D+k+`.G+G+r 6 5 o C C V -.-.C p -.-.-.-.-.-.V a.D (+].s m.B D.E+j.1.B+G+~+`.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+N.e+[+b.7.'.@+2+9.q+q+/+9+<+G+G+G+G+=+g+)+G+G+G+G+G+G+|+q+Q.:+A+A+q+q+#+ .i.J.N.n.P.#+9.p.V.p+A.O.J._.q.C+p.E.Q z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g+p.^.'.++|+N.D+o+6+p.[+6+C+G+G+E+A+|+p.N.=+=+P.O.V.Z.++A.U.X.f+o+o+o+n+n+7+Y.X.#+3+v+E+G+G+E+I.S.(.N v+|+f+G+G+A+z./+'.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+d+] Z ~.r 6 m.K ~+1 m.B+m.D+B+1 E 0 0 0 0 0 _ 0 < p < 1 e 8 8 h 8 4 e r 0 s ) < ~+G+~+n r (+].8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+#+N.g+G.b.d.q+E+j+9.g+z+P.{+A+G+G+G+G+;+i+)+E+G+G+G+G+G+g+o+Q.V.E+G+G+G+i.>.v.R.N.n.=+E+v+@+0.{+F.N.*+c.Y.E+0.E._.2+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g+N.'.'.E+j+N.w+/+/+V.[+6+q+G+G+G+G+A+Y.Q.;+<+Z.Z.'+_+_+U.-+N.U.U.-+-+U.U.y.y.N.E.B.v.*+p+E+G+g+U.N h.E+D+=+o+G+E+Y.E.U.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+H.].x+Y r n D P B+X r m+m.~+v { p 5 4 l z C C V V { %+!+!+r.2.-.-.C 1 }.) ' < 0 D.D+1+t.v s f d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+_.Q.q+_.^.0.g+E+v+c.++<+Y.A+G+G+G+G+G+=+n+S.E+G+G+G+G+G+j+o+Q._+E+G+G+G+ .|.v.R.N.y.;+G+G+E+q+7.p.E.*+c.V.E+!.Q.#+++E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g+N.p.!.E+q+p._+6+j+Z.i+6+s+G+G+E+z+Z.E.-+N.N.U.U.p.p.X.q.-+7.z.[+@+i+/./.[.(.c.9.n.P.Z.G.^+D+V.-+N 2+G+G+0.;+z+E+V.U.U.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+1.4+r 1 K D }.G+1+1 m.: ) ) ' p o z C V V -.-.-.} %+0+0+r.2.-.-.V { D v k.u 0 | m.$+t.1+9 ] d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+#+N X.h.'.y.E.9.s+E+F.z.6+9+E+G+G+G+G+G+X.7+S.E+G+G+G+G+G+q+i+Q._+E+E+z+n+J |.v.R.N.y.;+G+G+G+G+2+c.p.I.9.V.E+d.M.d.7+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+N.p.W q+A+z.6+o+B+F.i+6+s+z+g+Z.q.7.7.=+X.[+j+j+A+A+A+[+-+z.9+G+G+v+|.i.J.I.[+3+^+9.n.N.P.[+U.-+!.w+G+G+2+p.<+v+x.-+/+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+r a.r : S E K t+B+X : : ].` ) p l z C V V -.-.-.8 %+!+0+r.2.-.-.V 4 P x+(+p S : { e m.B+$+= t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+h.Q W N |+N.A.n.!.{+6+X.w+9+D+G+G+G+G+G+Y.:+S.D+G+E+E+z+s+o+Y.U.=+/+;+{+j+J |.v.R.N.y.X.v+A+A+A+p+k+V._.b.A.w+N p.p.w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+S.N.!.!._.i+z.q+G+G+[+x.e.e.B.|+w+E+E+B.U.6+o+G+G+G+G+G+i+-+A.9+E+R. .v.*+]+#+{+p+v+v+p+k+a+U.n.y.E.'+E+G+G+z+x.N ^.)+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+: p t.d : #.t+] ) ) ) P x+` ) E o l C V V -.-.-.4 2.r.r.2.2.-.-.V 4 P ~+p t.h+t.h+d p 1 9 ) B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+d.N #+g+N.|+N.x.z.z.=+s+9+E+G+G+G+E+E+_+'+S.f+<+'+;+N.N.N.y.U.>+>+>+)+6+c.h.h.I.N.y.X.p+p+p+p+k+a+X.J.z.P.q.!.7.N.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+o+A.U.'.!.N z.z.o+q+[+q.b.0.++E+G+G+G+E+F.N.6+o+G+G+G+G+G+g+-+A.9+v+J >.J.*+3+#+<+v+E+E+A+p+k+-+h.9.e.y.7+C+G+G+2+Q U.f+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+] 0 8+d 6 P a.] ].~.n 0 t.f ] E 5 l C C V -.-.-.h -.2.2.-.-.-.-.V 5 E K D t+B+m.D+1+P Z : | D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+#+N j+|+y.E+x.M.P.P.=+7+9+w+s+n+7+'+/+N.N.-+y.n.q.Y.B.A.X.z.S._+j+@+Z.Y.S.U.>+>+-+N.X.k+k+k+k+k+3+p.J.X.z.!.q.p.N.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+;+2+E.x.!.d.0.q.z.b.7.0.@+/+f+G+G+G+G+G+++N.6+n+G+G+G+G+G+g+-+V.9+*+J |.R.]+3+^+:+A+G+G+G+A+k+X.J.c.++!.e.P.C+E+@+b.)+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k 1 w.X 6 ) ) 1+D+D+1+{ 0 ^ = p o l C C V V -.-.h V -.-.-.-.-.-.C 4 E p t.D+B+m.D+D+E &.B ' D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+b.!.0.b.E+E+B.V.[+7.b.S.'.7.Z.[+z+A+@+O.X.z+s+f+9+{+j+A+9+/+9+E+G+G+G+a+w .|.N.L.7.q.@+3+3+]+I.N.|.A.9+E+B.'.p.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+{+6+e+A+p.A.W b.7.c.E.++Z.z.P.;+_+w+E+G+G+G+@+N.Y.f+z+D+E+D+G+j+U.Y.6+J .|.R.]+3+a+9+E+G+G+G+v+z.^+*+d.7+j+q.<+c.e.y.)+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+] | ] = D (+G+G+G+G+L ) < - p 5 5 z C C V V V C V -.-.-.-.V V z t p m.D+G+B+}.D+G+E v - < B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+N.c.!.b.E+G+j+b.7.^.F.;+q+|+X.O./+f+[+=+z.9+6+_+e+E+G+E+/+X.9+E+G+G+G+E+|.w .N.L.F.!.b.x.#+R.I.N. .A.9+E+Y.7.U.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+;+:+E+A+7.b.b.G.j+A+N.w+E+|+V.X.;+;+/+f+n+C+|+N.;+)+=+/+:+f+n+|+U.X.6+i .|.R.]+3+a+9+A+G+G+E+g+-+3+*+h.7+E+++=+h.7.^.7+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+f ) = H.D `.G+G+G+G+*.a.( - r 6 5 l z C V V V C C -.-.V V V C l ~.p t+G+G+D+}.D+B+p 9 ] 0 B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x.Q.z.n.B+G+v+n.7.P.s+)+z+G+w+2+++Y.O.=+p.;+Z.g+s+E+E+E+=+X.9+D+G+G+G+G+v+w w N.L.h.v.(.^.p.v.h.N.i.V.9+E+z.7.Q.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+;+z+G+A+q.b.7.E+G+G+N.o+G+2+n+:+:+[+Z.Y.X.;+X.N.6+_+B.0.Q.>+>+P.E.U.>+i J |.J.*+3+3+6+p+A+A+x+A.Q.3+*+h.:+E+2+P.'.p.7.w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+:.= { 1+r t.G+G+G+G+Y `.j.) e 6 / q l z C C V C C V V C C h 8 0 0 E B+D+G+F+*.1+Z p a.X 0 B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+E.U.p.++A+#+^.N.Q.s+>+z+G+G+D+E+z+++=+E.;+6+9+9+9+9+9+;+p.6+n+o+s+w+E+A+G.w y.L.h.i.i.[._.0.!.N.v+:+9+E+q.7.'+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+Z.E+G+A+X.U.O.D+G+G+E.<+D+j+n+Z.9+G+E+A+q+++x.L.>+>+U.U.N.E.E.p.U.U.U.!.c.c.Z.Z.<+<+6+j+t+p+2+U.Z.]+R.f._+E+A+c.p.Q U.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+- | t+s m.G+G+G+G+Y `.x+< | g ~ 4 4 h h h 8 8 p _ 0 0 0 p 0 _ _ 0 Y `.t+B+n d d D `.u | B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+p.n._.N m <+|+b.M.>+s+G+G+G+G+G+o+=+E.w+G+G+G+G+E+E+P.z.S.j+j+o+|+{+{+V.X.N.N.w w J w w R 7.E.w+Z.)+0.Q U.i+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E.F.E+:+E.;+:+E+G+G+++U.z+j+7+{+9+z+g+Z.q.^.^.n.B.V.g+s+s+j+j+j+z.q.z.f.i J [.[.f.I.y.9.b.O.U.Y.]+J.l.f.X.F.9.7.W U.7+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+:.- Y g }.G+G+G+G+;.&.f ) { < < ( ( ( 5 5 5 l } z z l q 5 b C C ' -.C { { < w.t+j K | { t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+s+q.n.F.m Q 7+o+q.n.)+o+G+G+G+G+G+o+=+E.s+G+G+G+G+G+G+O.V.Q.C+G+G+G+E+B+@+X.N.Q.k+J.]+R.[.V.U.9.x.0.)+@+!.Q.w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+N.!.g+)+p.;+Z.E+G+G+|+N.o+k+<+++X.q.b.7.B.{+X.e.++9+D+G+G+G+G+E+x.P.X.p+w w |.i.J.R.y.#+#+E.N.p.@+v.>.F 7.!.7.Q 7.)+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+- < { P t+D+G+G+X ) ) z 4 ( - / / / 5 5 o o 4 l l 5 5 T k+5 l ' -.2.-.z ' | r 1 r 0 ' d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i+Q.'.Q N h.V.7+=+@+>+7+z+D+G+G+G+g+=+p.s+G+G+G+G+G+G+O.V.>+z+G+G+G+G+D+++Y.N.;+A+a+A+p+I.p.U.F.'.b.S.z+p./+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+M.W c._+A.;+++C+G+G+j+E.7+z+q.7.e.9.@+w+E+6+_+b.++6+E+G+G+G+G+E+B.P.X.E+v.i >.|.i.v.y.I.#+)+d.'.n.h.B / =.<.0.N U.7+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+k < { E L w.t+m+) ) l C { s $+- / / / 5 5 5 4 5 5 b v m.B+Z / ' V -.2.-.l ;.v < 0 0 d 4+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+{+P.w+^.Y.g+[+++Q.O.^.^.7.y.N.N./+Z.Q.Q.i+E+z+s+<+/+M.y.y.-+S.S.)+>+>+>+>+>+>+X.w+3+A+p+I.U.B.j+E+|+N.7.N.i+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q.Z.c.Q.p.U.Z.s+E+E+Z.b.n.e.Y.|+6+G+G+E+e+i+B+A.[+6+w+G+G+G+G+D+F.y.Y.D+G+v+i w .>.y.>+S.i.i.|.>.(.0.n.p.Q >+=.7+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+' ) E ~+d ) ~ @.V @.L : e m.}.*.g ] ] ] ] 1 n D P D s S h+S ' b o C V 2.-.@.P | ) : $+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+j+@+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+:+/+2+0.p.M.o+[+=+|+!.7.P.A.B.B.z.N.L.N.X.X.Q.N.n.z.B.z.F.N.o+j+j+g+p.U.-+U.^.^.B.3+A+p+I.=+w+D+G+g+N.'.p.w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+V.'+@+F.0.U.Q.'+++A.7.F.F.q.N.Y.;+E+G+s+6+E+E+x.|+6+s+G+G+G+G+G+@+y.z.D+E+z+R _.0.z.N.p./.>. . .>.4+&.c y.y.z.^.8.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+( ) D ) ) 5 @.V @.H K : e ;.;.1.6.K ~.D.D.s $+8+4+j.r d+B+X ) x+k.5 z -.V h 1 ) = ' w.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+0.P.=+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+'+X.h.7.|+q.e+[+=+q+d.7.|+A.O.=+'+B.^.b.y.N.L.y.A.e+i+X.[+N.o+7+/+>+Q.Y.0.x._.W ^.^+A+p+I.V.D+G+G+g+N.W Y.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+++=+j+7+F.U.F.b.b.^.X.9+f+e+e.n.N._+o+_+;+G+E+B.i+6+s+G+G+G+G+E+@+y.q._+;+U.U.N.n.7.e._.w w .*+4+E P d y.^.n.U.g 1.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+- - ) ) z @.V C o `.x+v Y B+D+D+B+&.B+G+B+Y B+G+G+1+s 8+B+X ) D+D+1+f h E P ^ f ^ ' }.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R 7.p.>+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+++P.m 7.s+|+N.Y.=+z+G.7.q+B+j+@+Z.q.^.W '.z.z.S.n.N.N.N.P.L.)+-+p.A.q+z+Z.V._.^.Q 7.j+p+G.).w.x+G+j+N.7.e+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|+U.g+/+{+n.b.c.2+j+Z.Y.X.=+n.z.q.n.N.N.O.C+B+F.g+6+o+E+s+o+f+/+U.N.-+E.5.=.q c t !.b._.>.*+H.&.E S a.v N.=.z.p.B g 1.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k - = < z @.@.o g `.D+X Y B+G+G+D+L B+G+B+Y B+G+G+h+u `.D+~.) D+G+B+$+E e 8.- X k.] D B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+m Q E.)+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+(.N 7.[+i+g+F.y.'+j+^.[+G+E+g+!.!.O.Y.'.w+D+E+A+A+q+'+j+W c.o+|+|+[+++Y.Z..+W I I.W ^.E.g ( 1 u._.b.E.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+A.E.p.z.N.[+g+G+G+G+G+G+G+y.@+q.[+z.Q.z.7.e.e.-+U.y.y.n.E.p.Q.q.e.=.L 8.@+j+j+:.: W 4.u < < | { 5 c c y.P.q < `.x+~.1.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+K.] = z o { < t+X r &.d 1 B+G+G+G+s 8+G+B+Y B+G+G+B+S }.F+~.' (+*.E 1 9 9 +.= ].a+p+k+h+v+G+G+G+G+G+G+G+G+G+G+G+G+G+^+(.m '.Q.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+N W ^.x.[+q+[+7.E.g+'.B.z+F.'.Q [+P.Z.^.j+G+G+G+G+E+'+A+_.9.G+G+G+G+B+Z.<.6 Q M 1+G.W b.1 0 | I Q p.P.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+@+N.7.n.N.++o+G+G+G+G+G+G+E.[+q.E+j+O.:+2+@+c.7.7.P.O.V.Z.++g+#+b.M q+D+D+D+x+(+: O 5.< ~.j.K D '.P.^.S.I : < | ;.(+e 1.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+l.B = o H n : r n s v ) | t.D+G+G+n (+G+B+Y D+G+G+D+~.&.B+~.' E r 6 9 j.n t.= F R.3+v+p+k+A+G+G+G+G+G+G+G+G+G+G+G+A+R h.m '.Q.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+F.M.^.7.7.w+++P.i+M.'.x.9.'.h.v+[+O.++^.|+G+G+G+G+E+_+A+h.^.G+G+G+x+D.n 1 P M 9./+>+z.W b 1 | N '.>+F.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+E.0.7.y.++o+G+G+G+G+G+G+E.@+q.G+E+G.P.E+E+2+F.z.{+q+D+E+E+G+~+^.O s+B+m+;.g 1 ( W 5.e t+3+%.M p.S.)+8.x+Z < : 6 g 9 e w.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+v.*+s.] f ].n g ' < e t+B < j 1.8+B+n t.G+B+L B+B+8+(+S e *.e < 1 n ~.~+B+v 1.] f .v.a+p+v+k+v+G+G+G+G+G+G+G+G+E+E+^+m S.W ^.=+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+^.b.7.Q.P.z.n+j+_.^.Q.;+'+7+Z.O.g+'.c.E+G+G+G+E+_+E+2+'.B+4+S : P H =.>+U.^.u.) v f.'.|+V.Q.O.%.n K 4+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+++|+N e.q.e+D+E+G+G+G+G+b.B.q.G+G+D+j+'+C+k+{+A.e+G+G+G+G+4+r O 5.6 S 8._+;+4.H m e.~ k+k+^+[+)+'.R S B+1+| ].g &.Z ( | g G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+v.J.*+]+R.l.= e &.t+H.S 9 | ' < r 0 0 0 0 0 0 < { 1 1 r D D r n d - B+G+G+G+x+S E 6 (+G+v+ .v.*+p+v+k+k+G+G+E+i.>.i.i.J.J.J.[.f._.>+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G.q.p.7.0.9.O./+N W Z.Y.p.E.N.N.Q.^.0.n+s+E+E+E+:+D+x+W X 9 ] =.o.>+>+F.h.W ( { | e Q 0.)+Q.q 4+d+n n (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+e+2+b.y.=+/+9+o+s+E+E+b.Z.z.G+G+G+E+P.f+w+Z.0.e+G+G+G+G+1.6 O 5.@+<+6+4.H P *.M e./ 1+k+++)+;+G.1+n B+x+1 x+$+u 8+H.1 1 D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+i.J.*+]+R.R.x+- ] ( r B+t+d H.] < e K K ;.v 1 n j.d D.D.D.j.~.j x+].= D+G+G+D+L E r ) d+G+G+v.w |.3+k+v+p+k+v+|.|.i.v.R.]+]+]+*+J.(.U.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|+q.7.N B.j+Z.m W '+e+s+j+B.q.B.^.^.N.N.=+/+_+Y.s+q+_.c M O.U.z.M <.u ( Q B { ~.B c W 7.F.6 r d+^ = 9 D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+'+C+A.n.Z.Z.Y.)+)+;+9+7.Y.q.E+D+G+G+++=+o+V.0.{+E+G+G+G+9 = M 5.)+O.=.).4.4.O.M e.A u._+)+Q.B.4+w.1 t.m+u B+D+1+D a.( : d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+G+G+G+G+G+E+a+R.]+3+*+R.A+G+- ] H.X &.(+B B+F ^ 1+B+B+D+H.g r 1.Z D+G+G+E+x+n B++.= G+D+d+&.*.~.r : d+D+x+v d +.v.*+a+p+a+ . .i.J.R.]+3+a+a+a+3+h.U.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+j+N.E.p.'+2+m Q A.O.=+/+V./+j+q.W d.F.0.b.n.N.N.N.'.U.U.q.I a.1 1 c _.W ].1 H.#+4.Q '.s u 1 r ^ ] : t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+Q.w+x.N.7+w+C+j+|+++V.7.y.Q.9+f+o+s+z+x.O.Y.0.<+E+E+E+B+] c W e.E.E.M.E.q.V.Z.A n.N q.>+E.)+&.#.D | j L 6 B+G+E+X g ( 9 t.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+3+R.*+a+p+E+G+E+3+i.]+3+*+J.A+G+G++.] D.x+n e n x+k B B+G+G+G+1+j v j ].D+G+G+G+B+S B+B = x+t.P #.x+1+g ) $+j.n P m.x+|.|.J.3+>.w >.J.R.]+3+k+k+k+k+k+#+U.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+j+Z.N.W N #+j+G+G+E+c.B.[+@+!.^.O.=+Q.o.H 8.@+<.I ( ' n %.b T /+s+#+c { 4 o.z.I r 8+h+j.: 1 ) 6 g 1.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F.E.^.3 0.Z.z.N.Q.'+f+B.O.0.A+w+j+[+p.e.e.e.n.n.n.E.Q.n.5.Y.o.n.z.Y.Y.z.b.b.n.n.n.y.p.)+!.b K g | _ 0 < < e 1.t+G+B+u | e 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+i.|.i.*+*+]+3+3+]+*+v.*+]+*+J.k+E+G+G+G+F+:.] 1.B+B+H.1 - B (+(+`.w.w.6 Y ~.( Y t.t.t.t.K &.: = *.8+D+D+t+H.] ) X j #.B+G+G+G+E+a+>.w >.i.R.]+3+a+p+A+E+E+v+p+/+3+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|+Q Q F.@+G+G+G+9.|+G+E+h.'.2+|+++c M W.I ( M t ].6 r g H j+x+$.c ( S.=+j.n a.r m.H.^ ( { 1 ~.g (+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+G.W 3 M.:+q+|+B.q.y.b.y.n.o+g+++q.7.B.|+b.q.z.A.6+Y.@+$.2+M b.Y._+/+U.U.N.N.n.^.W =+q { < ' { K < ] : 6 < 0 D D+B+S 1 ( 1.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+ .|.v.J.]+3+a+k+k+a+3+*+]+]+v.k+E+G+G+G+G+G+x+Z g Y w.$+] - 6 e e e e e | | 1 1 | | g g g 1 1 ( ) x+D+x+$+u 9 - : L #.(+G+G+G+G+G+G+R.J >.i.*+]+3+k+v+E+G+G+E+v+f+*+v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+h.p.n.F.E+G+G+9.j+G+G+2+'.j+D+4+] b o g : ^.P.4.).I o g w.}.D c q x.%.S n 4+D.j ] B ( | ( K n r 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+2+3 b.N.Q.e+w+w+g+9.b.y.N.e.b.b.<+e+o+b.p.S.)+X.Y./+N.U.U.e.y.n.y.b.b.b.^.O 3 Q q < v ].D.;.s < f 1+t+g 6 < ;.8+v K ) r t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+>.i.J.*+3+a+k+p+p+p+k+3+]+R.p+G+G+G+G+G+G+G+G+B+H.e e g ] - ~.D.4+4+4+4+Z 9 :.$+~+f `.x+B+$+`.^ ] ~+a.u j ].4+~+6 #.8+D+G+G+G+G+G+E+|.J |.i.*+]+a+k+v+G+G+G+G+A+7+*+k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+w+^.E.N.N.Q./+'+n+i+W F.$+: ( 1 r 4+D+'.j+$+~+d.4.=.o.q { ' j u | _ < ;.].f H.~.: $+| 1+X 6 1 x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+c.p.++F.z.M.X.3 W A+B+2+F.e.p.x.W ^.x.i+@+{+Y.n.n.^.3 A.Y./+U.U.U.p.^.q 3 5 < V V G+G+E+< w.G+D+r x+G+1+B ( | d { }.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+J i.J.*+]+k+p+v+E+G+E+v+k+]+*+G+G+G+G+G+G+G+G+G+G+G+x+D.n - ^ $+$+j.{.1.&.v ^ - D #.#.P P *.K 9 ] ) $+x+D+D+}.P #.x+D+G+G+G+G+G+G+G+A+[ J |.i.*+]+3+k+p+A+G+G+E+v+B.^+]+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+R m '.^.7.b.N.N.^.7.0.=.M z ).f+f+'.++8.++X.>+U.5 5 %.' S r+l.X 1 < < K (+t+( Y v 8+B+$+9 6.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+@+E.P.X.z.b.m Q ;+'+f+e+++7.E.3 W q.Y.E.n.b.b.p.=+m U.E.5.M M '.p.4.0 / 5 < C V G+G+E+< {.D+G+0 8+G+G+x+Z < | | r G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.J i.J.*+3+k+p+v+G+G+G+E+p+a+*+G+G+G+G+G+G+G+G+G+G+G+G+E+1+- ] }.m.(+d+x+B+x+k - ~+~+(+D.~.S g 9 ] ] G+D+4+}.#.w.t+G+G+G+G+G+G+G+G+G+A+i J >.i.R.*+3+a+p+v+A+A+v+p+d.^+*+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+I.R q.'.7.'.W W W 7.n.N.N.U.)+)+W 7.P.>+o.M o 4.[+v+' a.u+T.1.d < n 1 0 e : j 6 s x+D+t+e 8+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+@+A.Q.U.>+Q m p.p.E.N.Q.X.7.3 W e.n.E.U.U.E.E.b.m c 5 q P.=+S._+u.0 g o { h -.D+G+G+< 1.D+G+< `.G+G+G+j.< ( ) | D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.J v.J.*+3+k+p+A+G+G+G+G+p+k+]+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k.Z K g r r r &.1.B - m+B+B+D+D+B+h+- - v 4+1.P #.t+D+G+G+G+G+G+G+G+G+G+G+A+J w >.|.J.*+]+3+k+p+p+p+p+k+h.#+3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+^+++Z.V.q.P.q.q.3 c I W 7.e.n.^.7.p.c c c p q+E+E+{ a.u+T.u g ) 6.h+H.S { 1 ( | t.D+3+( *.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+v+g+++N.7.3 z.X.z.z.b.p.n.m Q E.U.E.b.b.o M c / =.O.S.++g+w+E+4+0 g o 4 h -.D+G+D+< K t+G+1 m.D+G+G+j.| n ^ ' x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+ .J v.J.*+3+k+p+v+G+G+G+G+p+k+]+E+G+G+G+G+G+G+G+G+G+G+G+G+G+E+D+B+x+~+D.~.K j g ] e D *.1.w.(+(+f - d P #.w.t+G+G+G+G+G+G+G+G+G+G+G+G+E+|.w .|.J.R.*+3+a+k+k+k+k+a+G.G.{+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+u H.6 p t %.%.5 : b ~ 3 I p n B+G+E+{ `.y+&+D+~+n u e 8+1+`.u+$+| < K B t+S r G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+E+q+@+Z.z.Q.P.'+7+f.3 _.[+_+=+>+q ) t n ;.D+G+G+G+G+G+d+| e %.4 _ V m.#.#._ 1 6 r S e B+G+G+~+| ~+^ : j x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+J J i.J.*+]+a+k+p+A+A+A+v+k+a+*+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+D+D+B+h+d+~+j.].D.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+i w .i.v.J.R.]+3+3+3+]+*+I.U.M.s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+r ) 1 ;.: { ( ( L w.u H.p &.D.m+G+F+{ `.u+&+8+}.s $+6 s D.(+G+B+d ] | { B+4+g D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+E+z+j+++Y.p.p.3 '.)+X.x.I < r (+K &.D+G+G+G+G+G+h+| e u.o < C r 1 { _ _ 0 _ | < (+F+G+~+| f Z ].< D.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+>.J i.v.R.*+a+k+k+p+p+p+p+k+3+*+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+F+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+[ i w |.i.v.J.R.*+*+*+*+J.[.U.N.f+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+6.r d { n ) : j #.4+x+9 Y D d+~+4+x+$+0 D }.{.#.L j L S < 1 d+G+B+;.~+: < &.~+~.8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+q+@+(.G.e+A+$+6 { Y t+;.Y B+G+G+G+G+G+B+1 | u.o { } < ( S < 1 ~.S 0 p 0 t.8+~+| f h+x+< n x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+i.w |.i.J.*+3+a+k+k+k+k+k+3+]+J.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+G.3 [ i .>.i.i.J.J.J.J.v.i.[.U.N.;+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+r `.: | : P s h+F+G+h+( D t+(+S ' ] F _ j.K.` f n | 0 _ _ | `.D+E+n ~+^ ] K X ( ( $+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+f = n g d : s t+G+G+G+G+D+`.{ < : ~ l h < L `.| | ;.E (+B+1+g 6 : < L w.t+Z < r t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|. .|.i.v.*+*+]+3+3+3+3+R.v.]+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+i+6+9+D+A+R i i w J >.(.E._.^.^.d.n.b.N.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+6.r H.{ | B j n d+G+G+1+( v n { ) G >.l.< ].s.s.l.l.+.$+{.Y e 0 &.(+n B ^ $+e (+$+) d x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+- ] S L h+v g `.x+D+x+(+{.{ { { m+T C -.4 { < | 0 E &.D+G+G+j.n ] { n | g j.{ 0 t.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D+^+J .|.i.J.R.*+*+*+*+*+v.i.A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i+6+9+z+G+E+Z.N i i i w N b.^.z.[+A+b.^.n.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+L n ] 1 1 j.9 9 (+G+D+$+{ { d F G l.s.K.< {.&+&+&+T.s.K.t+(+D { { < 1 ] ].B+( r t+) : 6.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k.- X {.e B+D.g r L a.n { { j.n < u.t C C z l 4 < 0 &.x+G+G+G+$+d : { x+~+X < < < r G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+e+:+X.9.q.c._.i.v.J.J.J.J.v.i.k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+6+9+z+G+G+E+B.'.++^+(.^.7.^.y.Q.f+z+y.>+^.s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+e = ^ | | Y Z 9 1.h+S ( < ~.l.>.l.K.T.&+< ;.1+1+,+,+&+s.Z P p ].u ( < < Y (+Z | ;.) = e 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+n j 4+j.e t+D.g n { { ' 6 u x+S < o l z C C C C < } z $.D+G+B+f ].$+( B+G+B+a.6 0 | D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+q+e+Z.O.z.z.++(.J _.|.i.i.i.i.i.|.k+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+i+9+z+G+G+G+E+F.W 0.0.7.F.j+w+c.b.N.P.U.B.N f+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+f ] H.(+{ | r D.: { : n S < d < n k.,+3+5+j Y b+l+l+b+H.E v s.+.(+h+~.: 1.h+j.| < < f - ].g D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+t+r d+G+~+1 d ) ( a.(+*.d+B+h+] ~ 8 C C V V V -.-.4 8 V V z X f B+G+B+S L t+D+P *.$+= t.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+[+Z.X.O.0.O.++g+E+E+G+G+G+#+!.f.>.J J R x.z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g+X.9+G+E+z+g+q.p.7.!.M.C+D+G+G+G+A+z.9+A+/+E+p+7.s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+: f B+w.( | j ( ) v t+a.u < :.B 1 | ,+b+b+u L l+r+l+D.P j.,+&+:.K n v S D 8+D+~+X 6 : : $+{.8+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+:.*.B+G+].' ) B e &.*.h+D+G+B+b ~ < C V V V -.-.-.4 } -.V C z u.G+G+D+X j 1.m+P 4++.- j t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+i+_+P.z.Z.@+(.O.z+G+G+G+G+G+G+#+9.o+A+x+_.(.O.n+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+_.z.w+i+Z.z.q.F.2+G.d.N.C+G+G+G+G+B+V.9+B+/+E+D+W [+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+e ] ~+&.9 { ' $+X L B+D+1+( l.s.k.v K $+l+u P r+u+;.E L b+5+,+k.9 j.t+;.j L t+G+B+;.{ { a.h+L x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+^ `.D+x+] d | n e P }.D+G+D+H.5 5 _ V V V -.-.-.-.4 } -.-.V C $.G+G+D+Z X r s (+~+^ k.) 6.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[+6+x.O.Y.7+s+h.q.s+G+G+G+G+G+G+2+9.n+x+h.I.A+F./+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+Q ^.Z.q.0.++|+E+D+|+c.N.C+G+G+G+G+E+Z.9+A+/+E+x+!.{+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+z+{+E.;+7+o+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+(+`.~+] ) { *.B+G+(+r d+6.G : s.T.,+1+H.9 < u 0 `.P `.y+u+l+b+5+,+u K.E+6.*.D.r 1.E u ' ) ] ( ( r 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+6.] 1 D.] ] D+G+B+H.0 L d+G+E+k./ l 4 < V V -.-.2.2.r.z _ V -.-.V C x+G+G+j.u E 1 :.^ 1+G+].( X G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+{+7+B+G+E+E+q+|+x.0.X.9+f+o+w+E+D+j+A.q._.j+G+G+x+x.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+W S.n+G+G+G+G+G+G+|+B.N.o+G+G+G+G+E+F.6+z+q.(.N 6+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i+6+S.!.7.n.N.:+o+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+1.x+k+= ' | P F+G+~+s `.K J d s.&+,+5+l+5+~.< 0 E L D+F+y+r+l+b+,+n l.y+a.#.x+~.E 1.1+9 B g ~.{ | &.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a.P = 0 ) = $+G+G+D+`.{ { 0 (+x+F / l l < V -.-.-.2.r.%+*.8 V 2.-.V V (+G+G+$+E 1.H.^ :.D+D+P 1 : G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+9.E+G+G+G+G+G+G+F.0.V.++X.;+;+:+e+[+z.P.[+z+G+G+E+@+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+W p.f+G+G+G+G+G+G+2+c.N.o+G+G+G+G+E+@+6+z+p.N G.f+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+6+X.9.W _.@+d.b.N.Q.e+w+E+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+D x+B ] : { r B+G+h+s Y F ..d s.&+,+5+l+r+r+9 0 ;.x+G+F+F+r+l+b+,+j l.a+D.#.t+{.e d+k+;.j.: e ].: 1 D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+#.S ) ) f 9 K D+D+m+L | K a.e L : 5 l l < V -.-.-.2.r.!+m.< C 2.-.V V t.D+B+a.&.x+k+d 4+D+x+P Z { D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+9.E+G+G+G+G+G+G+@+0.:+A+q+[+Z.P.P.P.q.Q.P./+w+D+G+q+f+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+N z.9+s+z+D+E+D+G+2+Z.M.o+G+G+G+G+G+@+;+G.p.2+Z.w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+e+6+++{+7.d.q+o+9+Q W ^.N.M.Z.e+E+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+(+r B ] $+S { p t+G+x+s D >...B k.&+,+5+l+r+u+`.< `.B+G+F+F+r+l+b+5+| l.&+D.#.;.E u ;.k.&.$+9 n 1.H.( t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+8+1.$+] ] x+1+v 6.t+1.#.| L B+j.9 < < l l < V -.-.-.2.r.%+6 < 8 8 C V V $.t+1.E D.x+F n }.B+6.(+k.( 4+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+9.D+G+G+G+G+G+G+@+0.Y.[+X.O.O.Z.++z.z.P.|+B.P.:+o+z+=+C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[.'.U.=+6+9+9+f+n+[+Z.N.f+s+s+s+o+o+@+0.N /+q+p.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+z+6+Q.b.q.n.7.N.Q.q.Y._+z.X.X.)+>+-+>+>+)+)+Z.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+t+r ) ~+B+&.L 9 p `.x+Y D m.w ..+.].&+,+5+$+S 6 d < n n < r }.4+b+b+5+< :.K.w.E }.x+4+d ^ L t+d j.].p g d x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+d+#.r ] f g | t+D+G+D.s g Y < D t+D+:./ 5 ( < < C C h 4 { 4 H @.< C -.C 4 < 0 p 6.1+] ^ H.x+X e L G+~+( ] 4+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+++0.w+s+e+_+O.P.O.B.9.q.g+E+G+G+E+{+;+9.P.o+G+E+A+|+B.Q.'+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+j+G.A.E.X.{+|+m E.7+o+g+g+g+g+2+R c.O.z.6+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+o+z.E.n.b.^._.'.N.:+j+E+E+Z.X.w+j+X.X.@+!.7.N.b.[+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D.) ] D+(+D `.j.< g D.P L m.w ..+.].T.T.j.( ' B b+{ ;.y+j.S 1 0 L 6.D.< :.s.E p 8+4+n v B e x+B {.;.D 1 ] H.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+L r ) ].t+L g r d+D+L *.D.S | r d+B+:./ / l l ( < < { { V 2.2.2.{ h -.V h p ( ) 1 K : :.B+B+;.E &.G+~+6 g 4+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+Z.0.X.P.O.Z.[+g+q+g+A.q.s+G+G+G+D+Z._+c.q.n+G+G+G+D+q+!.P.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+v+2+z.M.Q.N E.n+D+G+G+G+G+E+J.N n.U.f+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|+E.n.0.z.B.g+Q A.N.E+G+G+G+[+O.w+E+/+Y.E+2+_.N.7.b.Z.j+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+Z ] $+x+&.L x+4+| < r ;.&.}.w ..` X Z : ' ].&+l+l+6 &.u+u+r+b+X n | < _ B Z s D u n j.t+x+< `.Z S E t.~+= X B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+n ) d D+B+s e n r `.#.d+D+x+< g L {.o / / l 4 ' ) 4 < < h V V -.{ h -.p E 8 T $+] ' v x+G+`.E u r D+1+: *.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+d.N Z.g+q+E+G+G+G+j+A.z.o+G+G+G+q+X.f+Z.7.f+G+G+E+2+_.G.A.:+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+2+F.E.y.=+<+s+z+E+G+E+v+3 y.)+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+h.z.E.7.R 3 R A.p.q.N.D+E+G+G+j+O.i+A+p.V.v.i.d.N.f.(._.W A.o+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$+e $+L &.x+G+h+e g B 6 < u B ) { : :.K.&+5+b+b+b+S g l+r+l+l+b+5+,+&+< p j < | w.x+G+G+G+e n 9 ( `.D+G+;.6 9 ~+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+].: $+B+G+G+B+&.D B+a.6 `.D+G+D+| o z V V l ) ) 5 ~ C V V V V C 4 < { _ p V C C b d v Z ' ( {.P 4+G+1+t.1+: x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+P.0.{+z+E+E+D+G+G+w+Y.0.<+G+G+G+@+;+z+++7.'+B+_._.#+A+G+j+q.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+A+q+|+++V.E.M.N.y.N.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+#+W S.Q.P.<+c.N @+@+z.N.;+<+i+z+w+q.[+j+E.9.v.R.x.E.R.J.l.(.'.7.++C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+g 1.D (+D+G+B+g e 5+:.: < { ) f n s.K.T.1+5+b+b+X | 1+l+l+b+5+1+&+{.p K h+].9 | s t+D+G+e `.~.0 t.8+t+P {.f 9 D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D.: d D+G+G+G+B+&.E (+#.1.e 1.B+4+| z C h ( ' 1 5 o 5 z C V V V -.-.C ( } V V C z v H.x+x+{ ] ' D.D+G+D+r f r D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+=+c.O.6+9+f+f+n+o+z+A.A._+G+G+G+Z.9+w+F.7.;+@+d.j+E+G+G+A+d.C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+j+g+|+@+{+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|+W 9.;+P.N.;+{+q.{+E+7+N.Z.X.P.=+e+X.Z./.E.x.*+1+A.X.3+]+R.J.0.'.E.B.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+K g }.D+G+G+4+| s B : { : | < d j l.s.T.,+,+5+b+].< $+b+b+5+,+D.D E ( Y F+r+1+].v g ;.d+| w.;.p s r j K d+1+d (+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+G+G+G+x+L 6 9 4+D+G+G+D+;.E #.1.x+Z e u o { 4 4 ' 1 C 2.5 5 ~ l C C V V V V V 5 { V C z q x+F+G+D+{ 1 r {.B+G+G+d 1 L G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|+=+[+A.[+{+_+/+;+6+6+;+0.X.e+o+e+P.P.P.Q.p.Q.Q.7+s+E+G+G+D+G.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|+W !.x._+P.N.j+B+V.Z.E+Z.N.E+z+2+++P.Q.0.[.N.F.3+3+O.Y.h+a+3+*+f.!.N.^.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+j.< 8+G+G+G+Y g 1 ( v j.,+$+v | | +.l.s.&+,+,+5+k.< H.5+5+1+K E g Z v Y B+F+y+r+b+B 6 | < Y p e h+1.E g r Y K n `.t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D+B+B+d+8+(+w.1.1.1.&.*.*.*.*.;.S *.1.1.t.`.(+(+D.D (+$+j a.t+G+D+{.E *.t+D+u.~ < 8 { ( ' @.2.2.2.5 5 5 l C C C V V V V 5 { C z l t G+G+G+B+{ E s : D.D+G+9 E 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+@+=+g+V.D+E+A+w+j+|+x.q.q.Q.Q.Q.P.p.X.X.X.Q P.@+O.Q.'+n+D+E+h.'+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|+!.2+e+6+P.9+O.E+G+[+O.E+V.U.z+C+C+z+D+#+[.I.N.++h+2+N.{+v+p+k+a+R.f.N.@+!.s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+j x+E+b+Z ) : g r+r+u+u+u+u+r+{ ^ : | g S k.T.k.| ~.j.D E :.l.l.l.~.| h+F+G+G+F+u+l+5+g < ~.j P *.x+G+D+B+h+6 ( : g r L }.(+d+B+B+x+d+8+m.}.*.D : : r &.;.{.$+~+h+H.Z B+B+B+B+B+D+B++.: B+B+B+B+h+d+~+v S B+G+D+1+B ;.$+n E ~+{.6 < 1 1 l < < 0 C V -.-.V / / 5 o l l C C C C 5 { l 5 %.B+D+t+;.P ( ~+{.(+B 9 Z P t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+Z.9+j+p.P.P.O.Y.Z.++j+F.z.P.G+G+E+'+B+E+j+B.P.s+G+E+x+2+z.P.P.Q.f+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+2+!.N._+6+:+_+X.Z.z+G+g+p.Z.E.>+)+)+)+)+9+;+X.X.-+/+_+V.N.[+E+v+p+k+*+I.N.i+Q d.A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+S (+H.d ) s.X e u+u+y+u+r+r+l+| k ....9 | < S ].{ n E s ].l.l.>.K.].| (+F+G+G+G+F+u+w.g = :.X D 4+B+(+w.1.Y 6 1 v ~+`.{.K r r s Y D D D s Y ;.D.f ] ~+B+D+E+G+G+D+k.B B+F+G+G+G+G+D+Z : G+G+G+G+G+G+G+S S B+G+G+G+x+n g s D 1 1 { h C -.-.4 < z < 0 8 h C o / ( 5 5 o l l l l 5 { 5 ].x+F+w.P P t.( 4+k+&.~+] ' `.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+A.:+2+q.++g+w+E+E+E+G+[+z.P.G+G+A+f+G+G+q+0.P.e+G+G+G+G+v+g+B.N.=+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+!.m E.P.|+E+:+;+[+[+e+Y.n.7.p.Q.{+F.Z.Z.x.0.A.z.N.S.X.S.N.q.7+v+p+k+]+G.N.g+!.!.|+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+`.] ) ] k.l+j.0 y+y+u+u+l+b+5+| k J ..>...B | < { 0 B l.l.|.>...s.k.| D.r+y+F+G+t+1.#.+.- +.s ~.&.s r j Y ;.6 r d+G+G+D+B+4+$+Z v a.`.~+x+B+D+x+^ ] ~+B+t+d+(+w.{.d s m.t.(+8+8+d+m+Z ) D+D+G+G+G+G+G+S n B+G+D+x+4+n 1 | e 4 z V -.-.2.2.1 8 -.V l 4 < 0 < ] ~ / / 5 5 5 o 5 ~ ' ] B+D+8+E &.4+D+( (+D+s {.^ : B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+O.B.N p.o+G+G+G+G+G+G+|+O.P.G+G+j+n+G+G+A+z.M._+G+G+G+G+G+E+2+;+_+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G.M.P.B.Q.7+E+{+)+d.z.g+w+V.C+/+/+G+E+i+_+h.>.v.@+N.|+p+Y.P.z+2+b.q.@+^+G.y.f.Q p.:+G+G+G+G+G+G+G+G+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+T.s.K.| K 5+r+u+$+0 r+r+l+b+&+T.s.{ S ,+w w J ..>.>.d { >.....G 5+a+|...{ X 1+b+b+D.P (+D+f = - $+b+1+T.v+E+G+G+g P d+G+G+G+G+D+D+].v d+(+w.&.L e ( ' 0 0 p p r &.{.D.d ) { < j 6.`.(+D.d ( Y D D P P P #.L 1 g g K {.].o C z E -.-.-.2.2.2.2.4 < V C z o t B+x+$+{ | < < { ~ ] H.H.) s #.E 6.D+G+G+G+: D.x+d D D ~+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+N d.w+q.o+G+G+G+G+G+G+j+A.p.G+E+++E+G+G+E+O.p.Q.G+G+G+E+d.X.6+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h.p.Y.Z.V.P.z+7+z.Q O.E+E+Z.C+;+X.s+:+6+Y.J.>.v.@+N.|+p+X.'+E+D+I.9.b.@+F.E._.p.p.:+G+G+G+G+G+G+G+h+a.`.8+B+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+1+K.&+1+= | s (+y+~+e l+b+5+,+s.l.>.n ;.B+v+J i w w J ^ { w w G D d+D+E+>.( v K.T.S #.~+u+y+- d (+F+r+l+b+T.3+y+G+g P 8+D+B+t+8+w.;.9 e e e g 6 1 e 1 ( 1 g D.j.D.t.1.*.= ] 1 1 1 0 r 1.t+v : x+B+h+4+`.a.;.j 1 {.$+t+u.H C V C E -.2.2.2.2.-.-.l < C l t u.x+G+G+B+e 1.~+D.j | < L &.) | &.4+D+G+G+G+G+: X Z r r j.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+F.P.{+q.n+G+G+G+G+G+G+j+0.q.G+D+V.E+G+G+E+z.p.P.G+E+j+G.6+9+s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+d.p.'+Y.|+N.e+@+W N M.z+G+{+w+X./+6+/+[+z+*+ .i.G.N.2+k+N.Z.A+A+p+3+G.p.b.b._.Q.E.:+G+G+G+G+G+G+D+] 6 E D *.;.t.8+t+y+D+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+k+K.s.,+b+l+^ ] 6 e r+$+| 5+,+&+K.>.l.p+K Y B+G+~+K l.>.>.F { J.H.Y r L 8+D+t+n 9 :.Y E S b+~+}.u ~+D+G+F+y+r+5+,+T.5+g r ;.*.s e e g j : : a.a.Y p p r ] ) d d D D D s Y S ) B B {.n 6 ( 1 r 6 ) B+G+G+G+G+E+D+S 1 8+E+T z C V -.V E 2.2.2.2.-.-.V l < l T 1+B+G+G+G+D+e m.D+D+B+h+$+K 1 ' | d+D+G+G+G+G+D+) : ] r Z x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+B.Q.q.7+D+G+G+G+G+G+q+9.q.G+q+X.D+G+G+E+z.p.q.w+G.!.O.<+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+Z.M.E.q.q+z.X.d.'.2+N.o+G+i+g+E.Q.{+z+G+G+a+ .i.d.N.#+a+N.[+p+p+k+k+3+h.7.p.#+Q.E.:+G+G+G+G+G+D+a.) v : ( s D p 0 e s S 1.w.(+8+d+d+x+B+B+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+T.s.T.,+b+l+r+:.] H.9 D.5+1 &+K.s.>.~.w.`.K g (+m+D.#.(+x+x+$+{ (+*.L r n D w.Y ( 1 D D Z s.1.P P b+y+F+G+G+G+F+r+b+5+&+e e g S {.j.$+h+x+] g r p e j u L ) ) 9 : a.`.~+x+B+1+) +.Z B+x+4+H.~.: ) | w.x+D+G+G+G+G+S 1 H.K.z V V -.-.V D 2.2.2.-.-.V V l < 5 D *.m.`.m+B+B+1 m.D+G+G+G+G+x+;.: 6 &.x+G+D+B+4+~.) : r d k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+9.7.'+f+e+o+o+o+s+w+9.q.E+g+;+G+G+G+G+O.p.0.c.0.;+6+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+p.Q.0.q.P.7.W -+i+E.Z.s+9+O.N.Z.G+G+G+G+E+i. .d.N.I.]+N.#+a+a+3+]+*+v.z.n.W Q.E.:+G+G+G+G+(+P ( ].(+#.] v ;.1.S Y ) ] ^ - ) : : : ( ) ) | 0 e L 1.m+B+D+G+G+G+G+G+G+G+G+G+G+G+k+s.s.,+b+l+r+u+y+y+u+r+l+b+B | | >.s.$+9 x+h+h+a.| D.8+~.: ( | | 6 ' | < < g s D.h+D.s < h+d s P +.l.K.1+b+l+r+F+G+F+G+F+F+u+e j.T.A+G+G+G+1.D ' u n ;.D.h+x+x+d n 1.( g e 0 | j u 0 g e p E P *.}.`.: : < < e L x+D+y+d u T.1+4+2.-.2.2.2.r -.-.V C C l o $+1 : 8+B+B+k+`.a.;.{ j L }.`.8+t+#.1.B g $+s ;.n v H.t+u 1 P D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+9.P.Q.Q.U.Q.Q.Q.P.0.q.P.S.P.P.=+=+=+N 7.P.6+7+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+A.N.B.w+|+Q 7.-+X.p.Q.6+;+A.N.<+G+G+G+G+G+k+ .!.N.J.*+p.^+3+3+*+*+J.i.q.n.(.7.E.:+G+G+G+G+L g ) v #.&.~+B t+B+x+H.) ].8+Z - 6.;.&.Y = ) 6 < < < | r L m.m+B+D+G+G+G+G+G+G+E+p+s.T.,+b+r+r+u+y+u+u+r+l+b+1+{.: { K `.a.9 F+G+G+(+| `.8+j < { ~.~+t+v h+$+{.< 0 r &.E s ^ ;.E L D.G ..l.T.,+5+b+r+u+F+G+G+G+F+e D.b+T.b+y+d+P S ' 9 v t+d+6.&.L 0 0 | { ;.j.$+h+B+4+0 1+^ j.j.;.Y s p E p | < < 1 &.~+s.d u 5+b+r+!+2.2.2.-.g V V V z o %.1+d+6 ( s m.t+D+G+D+B+{ 9 a.1.L P P g n ' | {.u L ~+x+t+1.6 ] a.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+q+j+|+[+++++V.Y.z.q.Q.Q.Q.Q.Q.Q.Q.0.q.;+o+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+Z.N.@+E+G.Q 0.U.z.n.N.Q._+7.N.:+s+s+s+q+C+C+R.9.y.v.J.z.*+*+*+R.J.v. .p.p.j+'.p.B.A+G+G+x+D ) | p p *.}.g *.}.*.j | e p p g 0 0 p 0 ' v j.$+u < < 9 9 6 e K Z j.x+D+G+G+y+,+s.,+5+b+r+u+u+y+u+r+r+b+5+,+T.9 | { { < 0 < t.(+t+a.< | 1 : v 1+D+G+D+X B+G+h+d 1 | < {.6.n : ( a.~.] ` ..l.s.T.,+b+l+r+F+G+G+G+e `.r+5+&+j.#.D.S ( 9 1 g e 1 n S s *.d+Z B+E+F+B+m+D.< ;.d ;.x+B+B+B+~+r D 1 e 0 p g Z k.v K b+r+u+u+%+2.-.-.h V C z 5 S `.8+`.( 6 j.s L (+G+G+G+1 n B+B+D.9 { { ( ) ' < e ;.B+8+*.E n +.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+D+E+B+q+j+j+g+[+++Z.Z.z.q.<+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+M.w+(.N k+N.U._+g+y.q.e.^.U.7.q.p.E.E.7.0.B.y.>+-+>+b.n.0.h.|. .J 2+N.b.i.0.'.F.v+3+~.E ) ) p E s `.m.u j h+4+0 t.B+B+B+x+S x+B+4+{.$+a.1 0 0 e { < 0 ] ^ Z e | g v s.T.,+b+r+r+u+y+u+u+r+l+b+1+K.l.|.l.~+d : ^ ) | 0 | | { ' ' < < < 0 0 L *.&.n D E 0 ) 1 p p e e r ^ +.D.h+G+G+G+a+|...|.K.&+1+l+r+u+F+e (+G+;.r 1 { : d p 1+d $+G+G+D+D+;.#.m.K D D Y 6.`.H.| y+S ;.X s D 1.t+Y }.X $+r S &+H.j.g p E E P *.*.h C C e 5 / 6 g r r g e r 0 e e g 6 D w.d+( 1 1 n $+t+B+D+$+d ' m.~+9 e e E s x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+O.@+N #+E+E.U._+2+b.n.9.B.U.V.s+|+++V.Q.Q.P.-+U.!.R J N ^.n._.R ^+s+N.7.q+W E.Z.D+(+D ' : ) E ;.d+}.#.$+j 4+d+p w.G+G+G+E+u B+G+d+w.G+E+x+~+d 9 e 0 0 | S h+~.1 B l.,+5+b+l+u+u+y+u+r+r+l+5+&+T.|.>.]+F+D+h+Z g - - ( 1 1 | | ' - - ' ' { { < < < ' ' ) e e e r L ;.D.x+B+F+G+G+G+G+G+E+3+..|.s.T.5+b+l+r+e (+4+1 1 9 ~.1+k.p k+f j.D+d+`.1.g p Y S D.h+B+D+G+B+g B+;.}.B+h+D.u L g *.+.v r H.b+l+r+X e $+`.{.S K r p p E 0 g u D.d+8+&.P ;.D.S 6 | 0 0 1 1 ( ( {.`.d+t+B+D+B+H.' 1.P E g P j ].G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+[+Q (.A+G+Z.U./+h.Q b.N.++Q._+D+E+s+7+-+-+P.7.^.|+a+|.>.[.h.n.n.V.g+N.p.g+W n.B.~+j ) ] K.B p 8+*.*.(+h+n `.d+p w.G+G+G+G+n h+G+d+w.G+G+G+F+Z v h+$+{.< 0 r a.6 g j.b+l+r+r+y+y+u+r+l+b+5+&+s.l.K.v+E+G+G+G+x+{.~.+.S Y j j 6 6 6 - ) { ( ' ) ' { - = ) r Y ;.`.x+D+G+G+G+G+G+G+G+G+G+G+G+v+..>.l.T.,+5+b+1 v : | j 4+y+r+$+e K.F v 1.P D Y u E t+4+8+G+G+G+G+B+1 B+{.&.G+G+D+x+X 1 D :.p 1.b+r+u+u+~.p r+r+b+5+,+{.5 b d u Y g p E E #.h+D+G+B+t+$+Z ( ( ) ) | e 0 p e e *.}.K ' E ( ) P P m.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+W B.i+z+g+E./+m (.q.b.E.Q./+f+/+>+>+Z.g+w+q.^.F.E+E+E+E+A+7.7.b.q.N.b.7.U.E.B.9 ) ] $+v+$+p *.Y 4+D+B+u 6.d+0 w.G+G+G+G+n (+G+d+w.G+G+G+G+H.9 G+G+B+1 0 : { d ( e l+r+u+u+u+u+r+b+b+1+&+l.>.l.E+G+G+G+G+G+G+D+D+D+B+B+B+B+h+d+~+~+k.D.D.D.D.D.D.k.` ` h+B+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+K...l.K.T.,+) ) Z j.j r w.F+1+e 3+T.k.S (+m+B+a.P t+h+(+y+p+k+p+x+1 B+~.Y G+G+G+G+T.v L v D ~+r+u+y+y+j.p $+l+5+&+K.>.u.x+a.w.t+j.D g a.t+G+G+G+G+G+G+D+$+~.n - = ( | 1 { ( 6 K ( ) ^ = 9 #.6.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+V.E.[+Z.M.7.y.(.A+s+Q.2+A.n.j+A+E+G+G+G+E+:+G.^.q+G+G+G+q+M.z.B.7.b.^.|+U.E.=.B S &.v.k+1+p r+3+b+A+D+a.u d+0 w.G+G+G+E++.:.s.:.].s.T.D.1.L 9 m.d+t+p ~.&+5+H.X 1 e Y l+r+l+b+5+&+s.l.>.v+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+5+F ` k ( ~.5+l+r+r+D.e m.g y+u+l+b+T.5+A+D.E :.|.l.s.s.K.K.s.n l.:.S E+k+s.l.&+v E D h+u+y+u+u+r+H.p D.&+l...X G+G+B+a.E 1 : x+F+G+G+G+G+G+G+G+G+G+G+G+G+F+B+h+$+d : : 1 0 e - ) s #.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+Q.7+A+[+9.^.9.i+w+V.z+|+^.G+G+G+G+G+G+E+<+|+^.|+G+G+G+s+7.b.Q.Q.b.^.!.U.n.=.S P 1.h+J.H.p F+y+l+5+E+$+n d+0 w.G+E+v+T.l.l.s.:.].s.s.|.].X v u K :.r &+b+l+r+r+~+1 | n $+5+,+&+l.>.l.p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+;.: = ) v T.5+b+l+r+X | < B+F+y+r+5+,+T.j.E +.l.K.T.T.k.].u | s X ].T.l.K.&+b+~.E 6.u+y+y+r+r+l+k.p ].l.T.H.j 8+t+;.P ] d 1+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+t+H.X : : : - D *.8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+Z.Q.s+D+|+Q n.M.P.q.o+j+'.E+G+G+G+G+G+A+{+q+'.F.E+z+|+B.U.>+X.j+B.^.Q E.E.=.{ = $+t+Z ..p u+F+F+u+1+$+n d+0 w.G+*+|.l.s.K.T.k.j.&+&+K.s.l.+.s.l.k.Y b+l+r+u+y+u+$+B 1 g k.K.s.F H.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+D.= f d d l.&+,+5+b+r+$+j 1.t+D+F+r+l+5+F E ].s.H.].S | | ( 1 j | 0 g K j.H.l+j.E l+y+u+r+l+b+5+k.e Z |.E+D+~+r P D : :.x+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+j.u g - Y d+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+{+M.E+h.W |+z.N.n.Q.A.'.O.'+f+o+D+D+q+i+z+d.^.Q.>+;+A.U.o+G+G+2+7.|+W 7.=.< n ^ ^ D+B+D s.s.,+h+d+(+$+S 0 e v S S K Y Y L s u D.1+1+,+&+T.1+&+r *.r+y+y+u+r+l+5+j.p ].| 1 e X n e 6.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+$+g X 1 u ..|.s.T.5+5+S H.n e ;.D+y+~+9 0 | ( ].H.,+l+l+b+S b+b+5+T.&+$+j.j 0 p 6.~+b+5+&+K.l.l.e e 6.D+4+}.e P X k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+O.w+N W A+z+B.y.M.i+2+9.Y.P.Q.'+f+[+Z.X.p.7.X.[+w+[+U.s+G+G+q+7.[+0.'.M ( e s 9 G+x+r L e 0 p 0 0 p 0 p 0 0 < | | g L K j < < e j.,+,+,+b+a.0 *.u+u+u+r+l+b+T.s u ..L S j g | 1 e m+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+$+( 1 D k.....|.T.&+v b+b+~.| ;.u 1 : p X &+b+b+l+r+r+r+S b+l+b+,+,+r+u+~+0 p 1 | K j.l.l.:.`.g 0 1.`.#.D #.6.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+[+F.N _.j+E+F.0.N.P.[+B.A+q+i+E.U.U.-+Q.X.7.B+D+G+[+Q.z+G+G+A+x.Z.E.E.c S < 1 g x+Z e ;.;.;.X Z ].j.j.p v :.k.s.&+5+b+b+D.j.j.v | e K a.&+s L D r+r+r+b+5+,+].p F T.e ;.s ~+{.n e s (+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+: 1 r x+v+T...l.s.Z $+$+~.( < ( B :.r {.5+b+l+r+r+u+r+S l+r+l+5+,+b+y+6.0 p H.B ( | v $+t+r 1 0 K E e D `.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+W N 2+@+A+x.Z.F.b.N.0.i+f+/+U.Q.P.[+q+A+d.E+G+G+{+X.E+G+G+E+F.7.Q.7.6 6 1 1 6 Z = e B+D+D+B+].s.1+&+p S s.&+1+5+l+l+r+H.`.r+r+H.X 6 < j 0 a.D b+l+b+1+&+K.j Y x+E+e S r G+D+x+H.1 r m+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k.( 0 D+G+E+t+J >.B n ( ( v v | X l.r 6.5+l+r+r+u+F+y+X r+r+r+b+1+,+r+g 1 p T.s.|.F 1 e Y 9 | 0 r s D L D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+@+z.N.'.@+q.j+z+X.'+'.c.q+E+G+E+Z.D+G+G+j+|+D+E+<+:+E+E+[+Z.7.7.Y.~+] 1 $+g ' +.t+*.B+D+`.#.x+G+x+..p v K.1+5+b+r+r+u+$+(+G+F+u+r+r+b+0 n d < X T.T.|...].s j &.x+e {.r x+H.d 6 1.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+Z }.x+F+D.j.] ) F |.s.T.>.>.f K g }.b+r+r+u+F+G+G+D.4+y+r+l+b+,+T.r :.r +.;.B+G+G+E+x+v 0 ( g 1.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+z+[+7.b.7.j+E+{+Q.d.!.w+G+G+D+Z.D+G+G+x+F.E+D+Z.{+s+++7.9.d.^.o+~+^ 1 m+a.= k+B+Y B+(+#.1.G+G+B+..r v T.3+b+l+r+u+F+(+(+G+F+y+u+r+l+0 k.T.p { 6 Z s.S 6 (+$+s &.e S r S : : E x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+t+| 1.m+K ) f j.]...>.l.>.l.l.| 0 }.b+r+u+y+G+G+G+j.d+F+u+l+b+1+k.X :.g ~.D B+G+G+G+G+K e - 9 B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+[+q.N.P.g+g+Q.#+'.j+G+G+E+'+D+G+G+E+0.z+A+B.7.q.7.z.;+>+P.E+~+^ | 8+k.{ d+D+;.`.#.m.x+G+G+A+>.r K T.5+b+l+r+y+F+(+(+G+G+F+u+r+b+s &+T.p B d { `.X e B+D+$+s 1 S 9 d X P 1.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+: | u ) Z x+d+w.x+:.G >.l.s.B | L 5+r+u+y+G+G+G+D.8+F+u+l+b+,+S :.K.g D.D x+G+D+B+~+E ) ) S G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+w+p.N.y.V.W !.B+G+w+P.D+E+z+g+^.'.7.Q.=+X.<+w+E+G+G+G+x+s ( g f = D.B+;.&.x+G+G+G+G+a+>.e K T.5+b+l+u+y+F+(+(+G+G+F+u+r+;.$+1+&+p S K L ].1 | x+D+G+D+1 1 : P 1.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+n ) 1 D+G+B+1.D+D.k >.l.s.T.S 1 < l+r+u+(+S ( { < < r D.1+~.0 K.B+Y (+L 8+$+9 d 1 = ) L x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+[+P.N.^.'.x.s+j+P.i+<+O.U.^.p.P.V.j+B+E+G+G+G+G+G+D+6.) | = B Y (+s 4+G+G+G+G+G+3+>.g K T.5+b+l+r+u+F+(+(+G+G+F+u+b+g 5+,+&+p w.m+;.K 0 ) u 4+D+E+: | : 1.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D.) ' D.x+y+}.D+D.k >.l.s.T.v p 9 $+~+j.( : ~.j.a.j.v < e e e :.`.K $+s v 9 H.a.E - 9 m.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+w+P.n.W ^.Q.=+U.U.U.P.Y.[+g+w+E+G+G+G+G+G+G+G+G+G+4+( ) = B < P g d+G+G+G+G+G+3+>.g Y T.1+5+5+`.D.6.K t.x+F+u+r+D.0 5+,+&+0 t.G+x+g | j 9 n D.~+( | E x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+X = e u D.Y D+(+k ..|.l.T.v E k.( ' ' j.r+u+~+a.r+l+Z | 0 < v g 1 n j X ~+4+D *.6 r 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+++!.b.b.7.7.@+j+z+E+G+G+G+G+G+G+G+G+G+G+G+G+G+B+1 ) = : < j {.d+G+G+G+D+D+5+G s s X K s < < < | < < g w.r+r+S e 3+,+T.e m.E+G+p j n m+Z ( : g p &.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+^ < 6 j L x+(+f ..|.l.K.Z e 9 { { j r+u+u+1+1.r+l+D P ].+.< < < v r 8+D+&.P `.6 *.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j = ) g ) < j s L | < < | | f ) ' :.T.,+5+b+l+l+b+S b+$+( < < ~.,+T.s.s }.D+t+;.B+;.L Z f ].1 `.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+j 6 g 0 v j s w ..+.) ) | k.5+5+T.1 0 Y $+K Y E &+&+K.s.~+t+] { | 6.4+*.h+D ] m+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+;.= = < : { ' { u {.D.~+}.*.J f { :.&+,+,+b+b+l+b+n b+l+K r ' { k.K.s.n *.B+w.d+D+1.9 ] k.a.D x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+| S g 0 4+S r k ] ) F +.| j.1+5+5+T.Z 1 < < E S &+T.s.l.E+B+9 ].) { { 4+w.p ~.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+~+] = { g ( = ' K 4+B+}.*.8+J k { +.T.&+,+5+b+b+b+{ ,+D.P ~.K.Z { v :.9 *.t+r B+h+S 9 Y t+v w.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+) r a.0 d+$+6 ) ] k l.:.| ].,+,+,+5+5+K.Z < j.&+T.K.l.>.G+B+9 j.j ( e t+D 1 ~+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+j.( ] 1 ) = { < | < < < 0 < < < < < < 0 0 e D L < r S &+,+&+T.l.` ] { | L e v D.;.s m.].e D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k.] e ) ' ) ) Y ..w >.>.( v K.T.&+&+&+&+&+| k.K.l.|.>.v+G+B+9 1 E &.f g s g D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+g ( | = f ' (+h+K v j g j d ) ) B B B v v v n { S &+&+T.K.s.|.....) { < n x+B+;.r w.9 D G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+t+f 1 - = { < g S w J ..: u s.s.s.K.T.T.K.< :.s.>.>.a+E+G+B+s 0 1 S ^ p {.r G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+Y 1 < = +.) j.B+;.B+m+D.K r : ' >.l.l.s.K.K.K.) v K.K.s.s.l...G n : | { 6 B+B+;.r X e 1.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+~.6 = - B ( < e ~.w J : 9 l.l.l.s.s.s.s.| +.|...5+E+G+G+t+6 ( : d ^ K {.D G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+D.e ) = v v S B+1.D+G+G+t+s v ' ..>.|.l.s.s.s.d 9 s.s.l.l.>.w v j E | ~.X {.t+&.e : p d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+~+g = = Z j n < e >.w ] 6 >.|.|.l.l.l.l.| B ..{.B+G+G+t+&.E ) : S :.j.L }.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+;.+.) < j.: ~+6.B+G+G+h+D D.: K.J w ....>.>.F { F ..J J K.(+P P x+v S D+1+d ( : p D.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+H.) = d 9 D D s < e n | ..>.>.|.|.|.|.{ 6.B+}.D+D+(+E : g ) ( | r : D t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$+= = ( n ) j.a.y+G+G+h+D (+X B+v+|.J J ....G { k J l.r+x+E e ;.D+X u B+F+t+] { g h+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+: ] 6 9 (+j.;.g { { < s.1+b+y+F+r+l+( ;.B+*.t+D.P Z H.g ) ' E a.: }.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+) f u < ' S w.B+G+G+h+D 8+a.h+G+T.T.1+b+5+1+( X s.l+t+*.;.Y }.F+j.6 t+G+B+n 1 K B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+1 = 1 e D+E+D+S s Z { g {.5+F+F+u+1+' s `.n u E *.B+h+r ] e *.t+: D.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+= B `.(+j.( | 0 0 0 D e Y {.~.h+K.T.5+l+u+y+v 6 X E L 4+D+;.&.G+1+6 a.9 g E ^ 4+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+X - ) 9 e }.t+x+K j ) K.k.S | : ' d d 1 Z 0 j m+D+G+h+r r ( 1.$+S B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B 9 a.`.d+) ) 6 K S u | | < < 0 | K ;.6.`.4+~.{ E 1.x+F+D+&.m.D+~+( : 6 E ( ^ x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$+- ) S j.g L D+D.g ) K.3+b+) ) { e : | D ( ) X B+G+h+j 1 ( 9 ] `.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+n e 1.x+d ' u s s a.r `.~+' ~.v n 1 | 0 0 | < Z l+G+G+D+;.1.~.n : u E S : ].D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+f ) S B+~+n (+t+u ) K.1+k.B h+$+S { < D P f ) ~.G+~+r B ) ] : x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+k.( Y B+].' 8+h+D.r D #.*.( &.].T.1+$+$+j.S { l.D.(+8+8+Y n v j.X { 1.4+] $+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+k.] : w.m+h+e 1.a.] K.k.] r+F+u+b+B { (+#.(+Z ) t+*.E B ) ) e D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+t+( e a.) (+G+G+8+D d+F+: {.K.&+b+r+u+y+H.9 +.~+h+h+d+Y 1.m+}.P ) B+D.p B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+j.e d+D.S g 6 | { = ^ ,+F+F+u+l+:.' D.P 8+G+x+' 9 ] +.) e 1.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+] { 0 ) (+G+G+`.D d+G+v v l.&+b+l+b+u ( ' | K (+x+B+Y 1.#.#.`.F d+D 6 D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+;.8+D+B+j.K 6 ) = k.b+F+F+u+l+s.) 6.P 8+D+(+1 ) 6 1+) g (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+~.e ( ' `.B+D+D.r h+G+].: :.T.H.X 9 v j.j.f 9 1 u t+n s L 4+D+~+d r ] E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+r d+D+D+x+$+- 1 g 6.F+F+u+l+K.: ;.P (+(+P ] = : x+] v B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D.e ) v &.j 0 0 L 1.D.' ( d ].5+u+y+r+1+f F+D+h+1 | S G+G+G+x+] - ^ G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+~+g }.F+H.- ^ S 1+H.e Y `.b+k.: n g p r Z D+D.: 6 D x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+S | X B+B+{.6 S j ( ) ' S j.~+r+u+r+b+9 x+8+}.Y ) ) D+D+B+6.: ] u G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D.e 1+^ B ..u ,+l+D.n e Y k.d { e P D.B+G+j.g : D.F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+g : (+x+{.s m+Z B d ( | e 0 r 1.D.H.( 6.P P t+s ) ~+4+X D ~+Y }.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+~.^ ^ h+>.n ,+l+F+r+j.j K Z { E 6.D+G+D+{.r s x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+~.) 6 K ;.j B ] x+j.( s.H.j.;.g e 0 { r 1.t+B+s {.) 9 e D t+s t.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+] X t+>.6 1+l+F+F+u+1+1 { { D m.D+G+D+1.e w.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+) v t+) ] S }.Y K : &+b+r+u+y+r+b+( 6 #.#.#.n 9 ].) S B+D.r m+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+S e d 1 H.l+F+F+u+l+&+1+1 }.t.D+G+B+S p D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+) : B ) &.B+B+B+x+v &+b+r+u+y+r+b+9 n D.D.D.D j.P j ] 4+;.L B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+S e 6 D.l+F+F+u+l+&+k+6 ;.m.D+G+x+u D G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+6 ) ) s &.y+D+G+D+~.&+b+r+u+y+r+b+v u B+G+y+P ;.D 4+f v Y ;.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+v ( j.l+F+F+u+l+&+x+: 1.}.B+G+D.p a.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+9 1 1.;.j u e 1.(++.j.b+r+u+y+r+b++.1 4+G+t+r E D+G+D+:.g 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.: e `.F+F+u+l+T.~+9 g g t.B+p e (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+d ( &.1.&.x+~+s e 1 X ,+r+u+y+r+b+:.( `.G+1.E 6.G+G+G+k+d t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.s.v e y+F+u+b+B ) ( { 1 L $+e r 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B ( *.&.&.D+G+x+$+d | s ;.(+u+r+b+l.: {.B+E 1 1.G+G+x+Z Y B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.,+S 6.y+1+Z ) B n X ( 1 9 S r d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G++.: L &.&.D+G+G+F+l.j S g e 1.`.1+s.v K ;.&.9 D G+x+].] m.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.5+1+e v 9 ) +.j ( `.j ) { j.r t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+(+1 0 n &.B+D+G+F+l.e &+r+u+h+D.S 0 { 1 s B+1.g ].] :.D.d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.5+l+~.e $+l+k.D n n d Z u ( L B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+(+g 0 9 L }.m.t.`.l.e $+r+y+u+r+5++.v | `.D+}.s d H.B+1.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.5+l+;.e $+l+k.*.a.H.n `.;.: *.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+6 p S D ;.}.L P +.e D.r+y+u+r+b+:.~.0 w.(+&.9 H.D+B+L D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.5+l+Y 1 H.l+k.`.j.(+s w.~.) 1.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+K e {.L B+G+G+E+l.e {.$+a.g 1 6 B j.e ;.1 1 9 }.D+$+r G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.5+l+e w.u+l+&+F+(+8+S 1.$+' s t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+;.| w.D B+G+B+x+:.1 | g 6 ~.H.T.s.~+e (+$+v 1 j 4+{.r G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.5+l+e (+u+l+&+E+(+(+;.1.$+v e `.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+{.e D.D ~+(+&.g 1 1 K H.h+u+r+b+K.$+e (+G+$+j 6 g g &.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.5+l+p 8+u+l+T.1+~.1.;.&.4+w.9 *.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+j.| S 1 1 g n 9 F 1 a.r+y+u+r+b+T.(+e (+G+~+s n 9 1 &.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.5+l+p h+r+~+d ( ( | | g j.w.].e G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D.( D.D w.1.*.4+>.g D.r+y+u+r+b+T.(+e d+G+t+s D.6 ] e G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.&+{.{ ( 9 ].T.D+d+(+H.s ~.| ~.] (+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+K ( ~+D #.*.x+A+>.g $+r+F+u+r+5+T.`.0 d+G+B+u 4+: = 1 G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.T.+.= K ~+l+l+&+E+d+w.1.D `.< ' ) r d+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+`.e 1 a.D S t+G+A+>.1 $+r+F+u+r+5+T.j.0 d+G+B+;.t+f ^ u D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.l.- = `.F+u+l+T.E+d+r #.n D | = ) e s t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D v 6 ] : d h+G+A+>.1 5+l+h+`.6.S g < 0 L 8+B+a.x+Z g n (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.- ' { t+F+u+l+k.v X ;.t+n S < D.B+: ( g m.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+L X n : ] d $+x+4++.| K s e e | j v S 0 u e s u 4+f d e 1.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+>.= ' | y+F+u+l+s.v 9 1.B+v X < 1.D+$+1 1 g E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+(+D.S u g : ( j e e | | K S a.$+&+1+l.S 0 1.1 1 | s : Z 6 L y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+B F ( < F+F+u+l+k.#.S g (+X d < e w.x+K | ( ~+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+&.$+9 | e | 1 ( {.].F K h+r+y+u+r+5++.6 < L 1.a.a.( 1 | | e 4+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+(+D :.' 0 F+F+u+l+k.a.x+1 n 9 ^ | 6 r D+H.e ) n d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+;.( d | ;.G+1+] d K S ..S b+r+F+u+l+5+k.u | 1.m.1.*.d 9 L (+j j h+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+;.E k l.' D y+t+$+~.1 | 1 0 < < | { &.x+D (+D.1 ] : 1.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+] ] S | `.D+4+d 9 *.u ..S b+r+F+u+l+5+Z 1 e s }.&.&.~.1+u D 1.| Z G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+`.E 9 G :.{ s Y 1 1 1 X $+h+L ~+~.1 < e w.u D (+) v ^ g D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+;.9 $+r | (+h+*.L v {.]...~.l+r+F+u+l+5+K.S L x+D+D+B+1.B+h+&.;.< : G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D g H.>.f { | u {.H.&+T.E+B+*.B+$+n 1 | 1 S 6 s ) ) ^ B `.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+;.e = u {.1 4+#.8+D+D.m+k+` j.l+r+F+u+l+5+&+K Y B+G+G+D+s B+G+G+S | ' H.F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+~.{.B ) ) +.1 < y+F+u+l+T.E+D+p #.&.n 0 `.D+4+X 1 | < j.D+K r 4+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p : = D ;.1 1.8+D+G+~+d+a+F j.b+r+y+(+$+D.S e r `.B+D+G+s m+G+G+(+| | s x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D.r Z ) ) ^ k.r 6 8+F+u+l+T.E+t+0 6.m+s _ s d+G+B+~+g | K t+h+n s t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D B ^ 1.;.| P B+B+B+$+w.D.d u K L e 0 0 0 | 0 0 0 L 1.`.s 4+F+G+D.| 0 e w.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$+j 6 ) ].~+J s.D ;.*.y+u+l+T.4+*.p B+B+u e u L D+G+B+r ) | g t+~+s &.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+1.v ] x+u | 1 Y D e 0 0 0 < g K Y ;.j.H.H.K.Y L (+S g e 0 Y w.8+j.< < 6 s t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+^ ) ) 1+B+A+|.K.s &.< w.u+l+k.*.&.p B+B+S *.d+u d+D+B+j ) 0 ( j a.D.g B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+r D ( ) ) | &.x+B+D+D+D+X l.+.H.l+r+F+u+l+1+s.u *.B+G+G+G+K 6.h+$+g p | $+: e (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B++.1 g h+T.>.>.l.s.K &.k.| S T.K.s.l.p d+D+X `.G+D+u L ].6 ( *.~.6.~+X 6 g (+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+;.e 6 ' ) j L }.D+G+G+G+v+>.|.+.k.&+,+1+,+&+K.l.n L x+G+G+G+&.1.D+G+;.p n ) ] | s G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+H.- g 0 $+..|.l.s.K.u *.&+v | k.&+T.s.p 8+B+~.8+G+G+h+u ^ ( 1 1.x+r 6.B+1+6 s m+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+g ( : f d &.L }.D+G+E+a+>.l.s.].k.&+,+,+,+,+&+K.u s s.`.`.w.u Y x+G+;.p g ) ) ( e x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+^ ^ r { n |.l.s.K.&+u *.5+T.1 K k.~.v p e Y j t+G+F+j.d ] ( | ;.D+~.r (+D+j.e d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+6 ( ^ ^ v a.v 1 &.B+p+>.|.l.T.&+j.H.b+b+b+b+b+b+,+u L K.|.h+B+D.j j L {.p 0 D+a.) ' g (+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k.^ H.D+D k | s.K.&+,+5+S D H.v ) ' ~.H.5+p ].:.< ' | 1 ~+B+B+;._ n B+G+y+{.r B r B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+1 | D.d X B+x+1 g {.l.>.l.s.&+1+D.$+l+l+l+l+l+l+5+K L T.l.K.E+(+r D.S 9 p r G+$+j B ( s t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+{.6 4+G+P F 9 s.&+,+1+5+S e ' : j.D.1.b+b+p j.].0 d B n s w.t+{._ j `.G+G+x+{.: L B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y+u+u+r+r+r+r+l+l+r+r+r+r+u+u+y+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+|+o+q+E+E+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+1 L (+j u B+G+g s u ..l.s.T.1+5+D.$+l+r+r+r+r+r+b+K L &+s.l.b+4+s X v 6 p &.G+~+s $+1 ( ;.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+;.e Z B+P +.F j.&+1+5+].: ' j.l+u+r+e $+l+p a.S 0 H.B+x+K 1 g n < j r D+G+G+x+: *.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y+r+c+}+ +C.C.g.,.,.U U U U U U x x U U U U U U ,.,.g.g.C.}+c+u+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+q+[+'.'.Q.>+;+9+_+e+o+s+E+E+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+| D L D.s m+G+g ;.h+>.l.K.&+5+b+D.$+r+u+u+r+r+l+5+s L &+K.l.:.L n 9 ].a.E K D+h+s D.K : 6 D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+{.| ] ~+P :.l.K &+&+k.) : P u+F+F+F+1 Y l+p {.j u 8+G+G+B+~+S 1 | g u 6.B+G+G+6 &.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+u+c+ +g.,.x a a 2 2 2 ! ! ; ! a a x U U ,.,.g.g.g.g.g.,.,.U x a 2 ! ! 2 a a x U C.c+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+e+Z.b.7.7.p.e.y.p.=+S.c.!.B.B.0.^.^.'.U.U.Q./+[+o+q+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+X | e `.h+s `.G+e K +.X S K K r e 0 0 0 0 | s Y Y {.s r n < | j `.u 1.D+;.0 D.L (+S u (+D+$+d t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+{.s k.^ E ].s.' ) d :.r+`.P F+G+G+G+u+n g e 1 0 k.,+G+G+G+G+G+(+u : { 1 e 1.t+1 1.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+F+r+}+C.,.2 ; ; ; ; ; ; 2 U ,.g. +}+r+y+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+u+c+ +g.U 2 ; ; ; x C.r+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+E+q+[+7.b.^.'.7.p.)+b.9.p.p.[+z+F.z+E+A+c.!.|+|+c.7.q.N.E.E.;+e+o+w+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+n | g B+x+n 6.4+e _ 0 | | 1 s S S S w.~+d+h+B+B+F+r+K *.5+k.B 9 < < r 6.g e 8+;.r n 1 (+G+D+: a.F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+{.r 1+k D Z B ) 1 $+b+r+$+E G+G+G+G+F+$+< e < r T.T.E+G+G+G+G+(+a.s 6.9 | | v | t.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y+r+l+}+g.U a ! ; 2 a a x ,.C. +c+r+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+l+ +,.x a ! x C.c+u+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+[+V.p.b.'.W Q Q S.)+B.0.z+w+P.N.Y.B.j+G+G+'.@+E+G+g+p.|+[+P.N.E.p.'.'.[+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+1+6 | e B+B+v : : e : k :.s.T.&+b+l+`.8+G+G+G+G+G+G+r+Y *.5+,+T.l.H.u 1 | _ e (+x+{.j 1 d+G+F+6 1.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+{.r x+k 6 ) ) ].| a.b+r+$+E G+G+G+G+F+y+S | _ Y &+K.E+G+G+G+G+$+`.u t.x+D.: 1 | `.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+l+}+ +,.U 2 ; ! a U U g. +}+r+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+c+C.U a 2 ,. +r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+G.P.)+)+z.S.Y.^.j+g+/+z.q.j+j+|+G+G+G+E+q+Z.N.Z.|+h.E+G+G+E+A.Z.G+G+A+[+^.#+w+7.q.q.Z.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+].| { 1 9 d B n t+e 9 ..s.T.&+1+b+l+`.(+G+G+G+G+G+G+r+&.*.5+,+T.s.k+D.D h+g E 6 S t+~+9 6.t+F K *.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+{.e H.] r X s.&+k.| a.r+$+D G+F+G+G+F+u+r+< p H.&+l.B+G+G+G+B+&.`.;.Y 9 v j.v 0 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+r+}+C.,.x a ! ! a ,.C. +c+l+u+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+u+}+g.a 2 U C.u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+G.0.)+7.!.g+w+2+^.7+6+;+q+q.[+++s+z+E+E+E+E+A+7.y.y.2+E+G+G+E+Z.A.G+G+G+z+^.{+B+X.g+9.7.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+B e ' { v x+$+s (+0 9 ..s.K.&+,+b+l+`.$+F+F+G+G+F+F+r+;.D 5+,+T.s.k+(+D D.;.E w.: n ~.( r v ^ {.L B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+~.) ] ^ s S s.&+T.n s l+1+D F+F+G+F+u+r+H.< 0 n ].+.D.D+G+G+B+g K u 9 H.x+h+n p m+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+l+ +U a 2 ! ; a ,.C.c+r+u+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+l+g.! ! x }+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+2+W 7.A.++q.G+E+F.^.)+Y.s+e+7.p.b.)+)+;+9+9+_+9+p.7.n.O.e+s+z+E+[+q.G+G+D+2+'.Q.q+p.B.0.!._+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+t+j 0 ) 0 4+G+4+s u 0 : J l.K.&+,+b+l+D.`.y+F+G+G+F+y+r+S r 5+,+T.s.&+~+r j $+D w.1+X ( ( X ] Z w.r B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+~.) f >.s K l.&+&+].| l+b+D u+u+y+u+j.d ' : { ( | 9 r 8+D+t+j.< 6 n Y D+G+$+9 r B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+l+g.x ; ; ; ; x C.c+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+c+2 ; ! g.y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+!.^.!.z+q+q.G+j+^.Q.b.z.p.E.e.b.b.Z.Z.:+Y._+_+_+S.S.n.>+N.n.U._+V.p.z+E+A+d.@+N.Y.7.9.@+w+Q.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+g e ( < S D+t+n u e 9 J l.K.T.,+5+b+D.`.u+u+y+y+u+u+l+S r 1+&+K.l.T.l.X | 1+D t.G+x+H.9 d 6 1.D.r h+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+A+B | : K.j Y l.T.&+T.9 b+b+s r+r+h+j.' d ].B { s.+.{ < g ~.u 9 0 ;.{.&.G+D+6.] *.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+r+c+g.x ! ! a x ,. +r+G+G+G+G+G+G+v+*+v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+,.! ; U r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+7.^.!.g+j+[+z.S.p.q.@+q+A+E+G+q+7.b.G+G+G+G+G+E+E+x.7.e.E+z+++N.g+j+0.b.n.b.^.7.F.E.>+o+G+G+{+M.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+6.1 = 0 < D.Z l.s.&+e S l.|.l.K.T.1+5+D.D.l+r+r+r+r+l+b+S p &+K.s.b+r+b+S e K.D 1.x+D.v | H.9 6 $+r (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+s.l.K.X e k.d Y K >.s.K.&+K.K 1+u ) ) B H.b+b+5+:.{ :.l.k.v P S 1 < < }.{.&.G+h+9 6 1.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+A+A+v+v+p+p+k+k+a+]+*+R.p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+k+3+*+R.R.R.*+]+k+v+E+G+G+G+F+r+}+C.U ! ! a U g.}+u+G+G+G+G+G+G+G+G+G+k+[ |.A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+v+A+A+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+,.! ; x c+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+:+b.^.^.b.p.e.E.P.e.@+E+G+G+G+G+A+0.7.G+G+E+w+n+<+/+P.b.b.:+e+g+[+Q.Z.q.z.B.^.c.7.n.n.Q.e+G+G+w+P.V.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+&.: = 0 < ~.s.&+,+5+e ;.b+>.l.s.K.,+1+D.D.l+l+b+b+b+b+H.u p T.s.T.E+F+h+g Y T.s &.B d B 0 B+$+: n g (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+v+K.s.T.1+X e b+r+Y L >.l.s.T.T.{ d ' Z T.5+b+b+5+1+:.{ +.l.r+1+D.D.D r 6 e n &.D+$+] 1 w.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+v. .>.>.|.i.i.v.J.*+]+a+p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+i.|. . .J w i i i [ [ [ > > & $ $ v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|. . . . .|.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+ . . . .|.p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+R.>.i [ > $ $ @ @ $ & [ i .R.a+}+g.U a ; a ,. +c+u+F+G+G+G+G+G+G+G+G+G+G+G+k+[ $ i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+|.i i w .3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+r+U ; ; x c+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+o+)+7.e.n.7.0.Q.>+0.e.[+s+z+C+E+D+C+7.0.o+e+:+)+)+X.:+[+b.0.O.Q.Q.X.e.y.p.q+|+^.|+q+[+A.b.Z.z+E+E+{+M.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+u ) 6 < < S ,+5+b+l+e ;.r+..|.l.s.k.k.{.S 6.;.Y L L e 0 _ 0 g Z ~+B+G+$+0 D.5+Y &.:.$+$+0 D+h+j 9 ( `.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+]+l.s.,+5+b+S g r+y+;.L ..l.l.l.+.' ' ) H.1+5+5+5+,+,+l.' B K.F+y+~+E *.8+t+{ { g d+].] e (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+i + + + + @ @ @ @ $ $ & & i .J.v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.@ + + + . . . . . . . . . . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+@ + + + + > G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i.+ + + + $ 3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+|.> $ + . . . . . . . . . . . + $ & % ! 2 ,. +l+u+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+> . @ i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+[ . . . + v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+}+a ; ; ,.u+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+e+Q.'.!.w+o+/+Z.w+A+e.A.g+i+7+++:+)+X.e.p.S.6+6+h.[.|.i.h.e.f.|.>.f.q+Y.M.b.O.^.G.E+G+G+G+N.A.B.7.++s+{+s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+~+] 1 u 0 j K r+b+$+S < < 1 u 9 d d Z ].Z X s.&+&+&+,+&+&+:.< +.k.a.K < < g (+y+*.*.b+,+Z 0 x+D+{.+.( ;.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+a+s.K.&+1+b+r+r+u+;.s r+u+S D l.] ) ] F K.K.+.v n | 0 ~.k.s.|.) S T.Y E *.F+y+b+1+{ X j : { | | m+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+J . . . . . . . . . . . . . . . @ w k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.. . . . . . . . . . . . . . . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+& . . . . [ G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.. . . . $ a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.+ . . . . . . . . . . . . . . . . . . . # s.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+> . . + v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+[ . . . @ R.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+,.; ; ; C.G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+;+'.W F.7+6+;+E+G+E+e.F.E+G+E+i+_+;+q.b.E.s+g+@+|.v.J.R.d.b.I.J.i.|.i.z+[+O.n.n.g+G+G+G+G+X.P.w+j+7.q.x.Z.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+:.- 1 S e u L ~.9 ( : n u 5+,+>.J >.l.s.l.S T.&+&+&+&+&+K.:.| B 1+5+$+E K 9 1 j.K }.r+b+S g s.3+D.^ : Y B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+s.l.s.1+5+b+l+u+u+y+;.L l+l+X 6 ) ] k ..|.l.s.:.].s.k.B < < n ..d u w.E B &+y+F+F+r+{ : 9 9 e ( g B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+J . . . . + @ @ + + . . . . . . . . . .k+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.. . . . . + @ @ @ @ @ @ @ @ $ $ $ >.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+& . . . . i G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.. . . . $ k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+*+> . . . . . . + @ $ & & & # . . . . . . . . . @ .p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+& . . . $ J.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+[ . . . $ *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+}+; ; ; x l+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+p.7.!.X.)+V.q+G+G+G+e.|+E+z+9+)+Q.q.U.>+)+9+9+x.F.F.@+@+0.7.#+^+J.v.|.v+E+g+e.y.{+E+G+G+G+B.N.i+G+j+F.z.q.s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+k+K.:.f | {.| v { ' v H.b+S 1 1+&+s.w J |.l.l.u K.K.K.K.K.K.s.:.{ d K.{.P a.h+l+D.1 | L y+r+Y L ,+T.l.^ d g x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+T.s.T.,+b+l+r+u+y+y+u+;.L $+j.( ) ) >.w J ..|.l...Z s.s.l.+.f ( e : | E n 5+s.1+r+F+F+1 j.~.D e E 6 D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+ .. . . . [ v.i.|.>.w [ & + . . . . . . > ]+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.. . . . $ |.i.i.i.v.v.v.J.J.J.R.R.k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+& . . . . w G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.. . . . $ k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+i.& . . . . . $ [ >.v.3+p+}+,.a * 7 y > @ . . . . . + i p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+& . . . . & *+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+[ . . . $ *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+! ; ; ; +F+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+o+U.A.7.;+[+E+G+G+G+E+b.|+[+q.y.n.y.p.U.U.P./+z.S.>+>+>+>+)+>+>+)+;+/+V.V.:+<+n.y.p.g+G+G+G+Z.E.Y.E+D+A+@+p.Z.E+G+G+G+G+D+E+z+q+o+w+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+T.s.K.,+H.e D.1 ) { n $+l+l+~.| H.s.|.l.|...>.|.j l.s.s.s.s.l.|...: 9 L E K 1+b+l+r+$+v | ;.(+L Y b+5+&+:.].e d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+]+l.s.&+5+b+r+r+u+y+y+u+r+S 1 ( ( ] ' ( y+T.w w ..>...B |.|.>...G B 6 ' | 6.n ~+j.:.T.5+r+6 $+h+e E ) f G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+ .. . . + J G+G+G+G+A+k+]+i.i + . . . . . > a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.. . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . . . J G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.. . . . $ p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+ .@ . . . . @ J *+p+G+u+ +,.x , x C.c+u+a+i.> + . . . . . [ v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+$ . . . . . & ]+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+[ . . . $ *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x ; ; ; U y+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+|+W x.7.b.z+o+e+Z.n.y.y.n.9.[+j+E+E+G+E+X.=+Z. .i.R.3+k+k+p+;+X.p+k+3+3+*+i.[.g+y.-+P.q.z.[+z+Z.Q.y.Z.!.!.@+F.^.[+E+o+f+_+V.c.d.(.F.g+[+'+'+'+f+s+z+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+&+l.s.,+5+b+r+D.0 ].= 1+r+1+~.j < n < Z 4+d+d+(+{.d f < 9 n j g | < < 0 < 0 t+r+s.l.s.T.,+b+H.1 ~+j 1 1 D.t+r+b+H.e Z v+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+4+w.}.L = = e D &.w.8+B+D+G+G+G+G+G+E+k+s.s.&+1+5+l+r+u+y+y+u+u+l+b+5+T.:.f : { ' < *.w.d+~.j.3+*+d k.v 1+B+B+h+4+j.' ;.n $+t+G+E+v+K.: j #.$+- f 1+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+ .. . . + J G+G+G+G+G+G+G+E+p+v.& . . . . . .A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.. . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . . . J G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.. . . . $ p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w + . . . . > *+v+G+y+C.x 2 ; U +r+F+G+G+G+E+k+J + . . . . . i G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+@ . . . . . . $ 3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+[ . . . & ]+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+U ; ; ; ! u+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+_.p.A.b.b.E.y.y.p.B.[+{+U.[+D+G+G+G+G+E+_+Q.[+|.v.*+a+k+p+v+X.;+v+p+k+3+]+i.>.[+>+S.w+w+0.e.q.Z.Q.7.y.d.j+E+9.^.X.:+V.4.).] - : g ).8.|+i+[+{+<+Z.d.7+s+z+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+l.s.T.,+b+l+r+u+a.1 = ( $+l+5+&+K.+.| < < 0 0 0 p 0 0 < < 1 L Y ;.;.;.1 6.j.( G+G+E+l.>.l.K.1+K.( P P `.D.1 n w.r+b+g S K.,+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+8+w.}.r r e 1 | 1 e e e e p Y 1.(+B+D+G+E+T.l.s.,+5+b+l+u+u+y+u+u+r+r+b+1+&+l.s.k+d+n = = 1 e e s g (+B+n $+s d+G+G+G+G+1+: $+n B+G+G+G+D+x+1 e X L d 1+k+k+A+G+G+G+G+G+G+G+G+E+E+E+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+ .. . . + J G+G+G+G+G+G+G+G+G+G+R.$ . . . . + p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.+ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . . . .G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.. . . . & v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+ .+ . . . . i G+G+y+C.! ; ! U c+F+G+G+G+G+G+G+G+G+G+ .. . . . . + i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.+ . . . . . . . + 3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+[ . . . & 3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+U ; ; ; ; u+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|+7.-+^.y.E.++|+j+z+E+z+'+U.Z.E+G+G+G+G+E+Z.U.|+i.J.*+k+p+v+E+_+Q.A+p+k+a+1+G.X.>+e.p.A+G+w+F.^.n.b.^.y.w+C+o+^.7.N.).r g : ) = ] : e e r <.Z._+_+d.Z.'+/+_+7+i+A+E+G+G+G+G+G+G+G+G+G+E+a+s.T.,+5+b+r+r+u+u+Z = ] ( j.5+T.s.l...~.: ( w.D.`.(+~+K g ) {.B+B+D+D+E+D 8+d+) F+G+G+E+a+..>.K.K.( L $+u+y+~+S 6 a.~+K Y ,+T.s.y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+4+D.Z r g e | 1 p P | < 6 r r p p p p e v {.~+&+s.T.,+b+l+r+r+y+y+y+r+r+l+b+&+K.l.a+F+G+G+t+k.^ 1 ~.a.K 0 e 1.( `.s d+G+G+G+G+d+) j.n B+G+D+t+w.P j = u g ].B+E+k+3+p+G+G+G+G+G+E+v+J.v.v.J.k+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+ .. . . + J G+G+G+G+G+G+G+G+G+G+G+ .+ . . . . R.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.+ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . . . .G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . . . & v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.$ . . . + w y+u+ +a ! a ,.c+F+G+G+G+G+G+G+G+G+G+G+G+G+ .+ . . . . > k+G+G+G+G+G+G+G+G+G+G+G+G+G+J.. . . + + . . . . & 3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+[ . . . & 3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+2 ; ; ; ; u+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+^.E.W d.U.y.Z.g+z+E+s+6+Z.U.'+E+G+G+G+G+G+{+-+#+i.J.]+k+p+v+G+{+U.j+t+|+/+>+d.[.i.M.y.g+G+g+F.0.A.U.7.b.5.M c M U.E.5.( { 1 1 g | 0 1 6 : 6 9 : = e H ).q+q+o+e+V.V.Z.e+C+D+G+G+E+a+K.K.,+3+l+r+r+u+y+u+1.B = v e : 9 l.:.j r (+4+].' r r L 1.8+n 6.Z ' s w.B+B+B+r d+x+B x+B+B+B+x+&.s E n ] K.&+5+b+r+u+F+1+v 1 j u+r+l+&+K.T.k+G+G+G+G+G+G+G+G+G+G+G+B+h+$+S j g 1 1 : u : a.t+#.}.m+x+a.j.e 4+G+G+G+D+B+D.B n g e K j.b+u+u+y+y+u+r+l+5+,+&+s.T.A+E+G+G+G+G+G+G+G+4+~.9 g r s ( ) | < e w.m+B+G+B+v S n m+t.E &.a.}.r - - k+J.v.]+A+E+A+3+a+A+E+ .|.i.J.R.*+*+R.J.v.A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+>.. . . + J G+G+G+G+G+G+G+G+G+G+G+*+@ . . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*++ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . >.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . . . & v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+[ . . . + w c+ +x ! x g.c+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+i . . . . + |.E+G+G+G+G+G+G+G+G+G+G+G+G+v.. . . [ w + . . . . [ 3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+[ . . . > a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+l+! ; ; ; ; u+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B.p.W Q w+z.N.y.p.z.{+_+;+w+U.X.D+G+G+G+G+G+o+-+@+i.J.*+k+p+v+E+{+U.g+/+)+S.F.v.>.v.P.y.++[+0.9.@+<.p.U.Q.O 5.b.I Q.b.N.| 0 r p E E E p p e 1 : ] ] ] 6 e &.D.d+|+@+j+d.'+_+o+D+k+K.l.K.5+b+l+r+u+u+y+u+b+r = :.&+k.( ( B a.e r (+G+h+) 4+`.6.Y r r D s ' g P L L L D L L 9 s L P L s r E n S Z ` s.T.,+b+l+r+`.#.D ) F+F+u+b+5+&+K.p+F+G+G+G+D+D+x+d+(+j.X 9 1 1 g n D.$+: D.v v L w.x+G+d+D.x+S `.D+G+G+E+A+].s.T.K.j.j p ;.y+y+u+u+l+b+b+&+K.l.s.E+G+G+G+G+G+G+G+G+G+G+D+h+D.6 1 e = - ( n | 0 L 8+t+X v g P E 6.*.E D ] ^ +.G+E+k+J.3+p+A+A+p+3+*+|.i.J.*+]+3+3+3+]+*+*+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+>.. . . + J G+G+G+G+G+G+G+G+G+G+G+a+@ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*++ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . >.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . . . & A+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.+ . . . & 3.U ; a g.c+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+& . . . . [ k+G+G+G+G+G+G+G+G+G+G+G+E+v.. . . [ p+i + . . . . [ k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+> . . . > a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+C.; ; ; ; ; u+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+'.W W z.E+Z.n.p.N.N.n.Q.Z.w+U.q.z+G+G+G+G+G+i+-+|+|.J.*+a+k+p+v+e+-+S.S.++^+*+i.>.*+P.e.b.0.I.a.*.H E.>+q 1 5 O 5.p.^.n.d 0 D._..+u.K r e g p 0 0 0 ( : : s e e H 4+#+d.F.{+'+{+l.s.T.,+b+l+r+u+y+y+u+r+D.6 F T.K.s.F ( | | | g 8+G+B+v B+G+D+B+D.s ;.{.) n 6.r K 6.r {.6.X v {.;.p E r Y x+B+~++.>.l.s.,+5+5+#.&.4+^ F+G+F+r+r+b+3+s.T.d+(+D.{.;.j e e 1 1 1 6 $+1+B+F+d+6 `.K P L D+G+G+d+D.G+h+L x+G+G+]+l.Z &+5+b+l+j.r `.y+u+r+r+b+1+&+l.s.a+F+G+G+G+G+G+G+G+G+G+G+G+G+G+D+$+S 9 ) - | e j n 6 0 e : 6 E Y ;.E r 1 6 ] H.B+G+G+G+E+i.R.a+E+E+v+v.i.J.*+3+a+a+k+a+a+3+J.*+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+>.. . . + J G+G+G+G+G+G+G+G+G+G+G+3+@ . . . . R.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*++ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . > A+G+G+G+G+G+G+G+G+G+G+G+G+G+p+& . . . . % ! a g.r+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+ .. . . . + *+G+G+G+G+G+G+G+G+G+G+G+E+i.. . . [ G+k+[ . . . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+> . . . > k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+U ; ; ; ; ; u+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+W Q G.)+G+{+N.z+g+Z.y.y.n.q.-+M.s+E+G+G+G+G+g+-+[+|.v.R.3+a+k+k+;+-+S.3+3+3+*+i. .J.q.e.q.j+f 9 g 4.>+q 1 1 5 M W 7.5.b.8.0 8.<.$.L P $.8.b ( 0 6 1 1 e 0 0 e e 0 6 b H H Y H u u K K v X a.`.4+h+r+r+b+Z ] s.s.l.s.t+9 S n < 0 L 4+B+].h+G+G+G+4+s (+B+X v B+;.D (+s B+B+t+X D.P *.`.&.1.G+G+x+{.:.F >.K.k.L 6.b+r+F D+G+G+F+y+r+l+S j r e e e e g u ;.: S j.9 G+G+G+G+~+6 D.E j m+G+G+G+d+(+G+B+| m.B+a+l.K.~.1+l+r+r+;.D u+r+r+l+b+,+T.s.]+A+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+h+n - = 1 | 0 0 { { ' ' 6 L E 1 r P #.h+D+G+G+G+G+G+A+*+v.a+v+p+|.J.*+]+a+k+k+p+p+k+a+*+J.p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+>.. . . + J G+G+G+G+G+G+G+G+G+G+G+R.@ . . . + k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]++ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . > E+G+G+G+G+G+G+G+G+G+G+G+G+G+|.. . . . . ! ,.u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.$ . . . . |.E+G+G+G+G+G+G+G+G+G+G+E+|.. . . [ G+G+p+$ . . . . . & A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+> . . . [ k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+}+a ; ; ; ; ! u+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+G.7.Q.[+)+G+s+N.G+G+w+;+z+j+Z.N.N.U.Q.)+6+6+;+X.U.z. .|.v.G.X.>+S.2+p.x.3+*+R.v. .i.3+J.c.y.o t I =.4.1 o H I k.f.d.A.z.z.b 0 H 4+x+B+w+w+s+8.( e d.[+++<.t ] j j 6 0 : d ].j.D.D.`.`.6.p w.6.K E E r S T.+.9 1 K w.d+D+j.: {.e r 4+D.K 1 1 0 0 0 0 0 p p | ( 0 0 0 0 < e e e ( j.x+G+B+u 1.G+G+D+n B+~+j #.S :.K.&+1+b+r+y+F+F+G+F+F+u+~+r ,+K.l.&+G+G+G+6 D.x+v F+G+G+x+{.e }.1+n (+B+m+4+a.Y L D B 9 g H.5+b+a.r+u+y+y+s ;.l+b+1+&+s.F 9 g P t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+B+$+D.;.g g 1 = : E #.(+B+G+G+G+G+G+G+G+G+G+G+G+G+a+|. .i.R.]+3+p+p+A+E+E+v+p+a+]+J.p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+>.. . . + J G+G+G+G+G+G+G+G+G+G+E+ .+ . . . [ v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . > E+G+G+G+G+G+G+G+G+G+G+G+G+A+$ . . . . 7 }+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+[ . . . . [ v+G+G+G+G+G+G+G+G+G+G+E+|.. . . [ G+G+G+k+& . . . . . i p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+> . . . [ p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+,.; ; ; ; ; x y+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+N c.E.q.=+E+z+E.E+E+f+=+G+G+E+y.e.y.N.z.{+i+{+<+U.E._.[.d.>+z.G.^+]+A.q.*+R.v.i. .*+3+3+I.y.I 7+X.)+u 1 o B =.B.^+'.!.7.>+: 0 _.A+G+G+G+G+D+w.( 1 G.E+@+F.I.] n r r 0 Z l.,+5+b+l+r+u+$+r w.*.D j B k.T.l.|.T.s 0 e g D.D.: {.e r x+F+B+h+' a.D.D.S j (+(+$+) S d+d+$+j h+B+B+) $+G+G+B+s 6.G+G+G+6 4+(+P Y h+*+:.s.T.&+b+l+r+F+G+G+G+F+1+r h+5+,+K.b+E+G+j $+B+Z B+F+v+1.s 1 K.1+d v u 6 | { 0 0 e Z Z | K l+r+Z u+y+y+u+e a.b+&+K.l.+.u &.j 6.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+h+$+D.{.B D.t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+ . .i.*+3+a+p+v+E+G+G+E+v+a+3+J.3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+G+G+G+G+G+G+a+> . . . + v.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . > E+G+G+G+G+G+G+G+G+G+G+G+G+T.. . . . + p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J + . . . & v+G+G+G+G+G+G+G+G+G+G+E+>.. . . [ G+G+G+G+a+> . . . . + w p+G+G+G+G+G+G+G+G+G+G+G+G+G+3+> . . . i v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+}+a ; ; ; ; ; g.F+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+#+Q Z.[+N.E.E+D+X.E+w+;+Z.G+G+G+E.n.Z.[+y.N.P.g+[+U.=+p.S.>+h.v.R.R.*+c.n.J.v.i.>.|.J.*+a+#+n.d.A.)+4.] : | +.).M.8.^.W >+4.9 0 !.|+w+E+E+G+F+;.( g 2+q+G.j+x+d v $+Z e k.,+b+l+r+r+u+y+8+p D Y j.k.T.K.l.&+v+E+t+a.s 0 g g ) S e r B+G+G+D+: 4+G+G+{.Y B+G+B+Z d G+G+B+r D.D+G+) a.D+G+B+s w.G+G+G+6 K #.Y r L 6.#.X l.s.,+5+b+r+y+F+G+G+t+D u+r+b+5+K.T.r+6 (+E+].1+s...P ' ( Z 9 1 | | j K d 9 n g p p 0 0 $+r+X u+u+r+r+r D.&+l.>.F r x+D.P B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+B+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+w >.i.*+3+a+p+A+G+G+G+G+v+k+3+R.*+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+G+G+G+G+G+v+ .+ . . . [ p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > G+G+G+G+G+G+G+G+G+G+G+G+c+7 . . . . i G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i.+ . . . + k+G+G+G+G+G+G+G+G+G+G+E+ .. . . [ G+G+G+G+G+3+> . . . . + J v+G+G+G+G+G+G+G+G+G+G+G+G+3+> . . . i v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+u+U ; ; ; ; ; ! c+F+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+N _.p.w+z+q.q+E+Z.|+=+Z.D+G+G+G+V.n.A.e+q.n.b.x.n.y.-+P.Z.{+0.p.U.U.U.y.N._.R /.k+E+E+&+l.J.x.p.V.^+K.R.^ < 6 u 8.q.E.z.q+1+j < t [./.[.>.>.` j 6 1 t s &.D.f [.l.K.&+e b+r+u+u+y+y+l+1.P D.1+&+K.|.l.a+E+G+G+G+G+G+G+D+w.r ( ] = : e r s *.) v 8+m+S 1.B+G+G+1+: G+G+B+u r w.D+].: t+G+x+s w.`.*.D u ] $+t+P P ;.g r n ^ >.l.K.1+5+l+r+F+F+6.d+G+F+y+l+b+5+6 H.s.k 9 1 e n 9 j k.T.K.K.l.l.K.B H.,+5+l+r+r+S j.S 1 S $+5+,+e :.l.x+E+x+s #.}.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+w >.i.*+3+a+p+v+E+G+G+E+v+a+3+R.*+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+G+G+G+G+E+v.@ . . . & *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > G+G+G+G+G+G+G+G+G+G+F+ +x # . . . . J.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.@ . . . . *+G+G+G+G+G+G+G+G+G+G+A+J . . . i G+G+G+G+G+E+]+> . . . . + w G+G+G+G+G+G+G+G+G+G+G+G+3+& . . . w A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g.; ; ; ; ; ; U u+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+z.n.E.++q+V.Y.z+[+z.;+q+G+G+E+z+A.y.e.e.B.|+q+/+6+0.n.X.<+E+3+i.J R N '.y.N.N.N.P.<+e+g+f /.0.y.^+a+3+J...| ( H.#+0.=+g+B+v+K 1 ` l.l.l.s.s.l.j j 6 <.F.K d F K.&+1+5+e l+u+y+y+u+u+{.P ~.1+K.s.|.B.[+D+G+G+G+G+G+G+G+G+D+t+a.e e | e e e e : ) r D d K m.t.w.D.) 8+d+d+;.;.D d+$+: $+D+d+g L E L 1.$+^ ^ Y r s s r D s f a+l.>.K.&+1+b+r+u+1.t.D+G+G+y+r+l+( 9 ( 1 1 E ~.T.v u ,+,+,+&+K.s.s.B ,+b+l+u+u+u+a.4+l+X 6 1 v ].L 3+E+G+G+B+s }.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+J .i.R.]+3+k+p+v+A+A+v+p+a+]+J.*+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+G+G+G+G+*+. . . . + i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . [ E+G+G+G+G+G+G+G+G+G+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > G+G+G+G+G+G+G+G+G+y+g.; * . . . . . a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . . v.G+G+G+G+G+G+G+G+G+G+A+w . . . i G+G+G+G+G+G+G+3+$ . . . . . w G+G+G+G+G+G+G+G+G+G+G+3+& . . . w A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+}+; ; ; ; ; ; ! +G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[+N.y.7.q.z.N.{+i+Q.{+E+E+[+Z.q.e.e.^.|+A+s+9+_+o+e+y./+:+E+G+E+v+a+J.*+p.p.Z.{+Y.N.N.N.5.Q.>+y.*+3+3+3+*+j | 8.!.U.++|+G.<.1 p K S ~.].k.s.s.| j n >.F./.|.s.1+5+b+l+g r+y+u+u+r+l+P S &+T.|.>.J.[+'+[+E+G+G+G+G+G+G+G+G+G+D+4+D.;.s 0 0 e 1 ) 9 K ] S *.P D D : #.#.K s L D K v ) v ;.P E e g s g r j - : g D D 1.D.4+x+G+E+a+>.l.K.&+b+b+1.s x+G+G+B+1+].1 : B +.E S &+,+v u 5+5+5+1+&+T.K.B b+r+r+y+y+y+`.$+b+X H.Z : 1 &.B+G+G+G+B+Y h+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+|.J |.R.*+3+k+k+p+v+v+p+k+3+*+v.a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+G+G+v+i.. . . . @ i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . $ |.|. .J w i [ > > $ w v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > G+G+G+G+G+G+G+F+l+U ! ,.s.+ . . . @ v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+@ . . . . i.G+G+G+G+G+G+G+G+G+G+A+i . . . i G+G+G+G+G+G+G+G+*+$ . . . . . .A+G+G+G+G+G+G+G+G+G+]+& . . . J E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+c+! ; ; ; ; ; ; U r+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+V.n.x.F.'.e.N.++b.[+++A.n.7.B.|+y.q.q+f+6+/+z+G+j+y._+_+E+G+G+G+G+G+E+Y.P.<+E+E+q+i+x.N.y.y.N.d.I.*+a+3+K < M 7.>+H 1 < | | e L r p E E r D p | n +...s.T.,+b+l+r+r+s u+u+r+r+b+D.K K.s.|.k d.w+D+[+/+7+G+G+G+G+G+G+G+G+G+G+G+F+D+x+`.;.Y e ) ( | e L L L g | ( e 0 e 0 p 0 0 | ) { p ( : 6 e r E e g j d ~.`.4+D+G+G+G+G+G+G+B G |.s.,+1+S e (+F+B+~.: : 9 :.l.].Y &+1+5+u S b+b+b+b+1+&+T.B l+u+u+y+u+u+~+j.1+B l.l.B 9 1 ;.h+G+G+t+r F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.J >.J.R.]+a+k+k+k+k+k+k+]+R.v.A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+E+a+J + . . . & J.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . . . . . . . . . . . . i p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > G+G+G+G+G+G+u+ +2 a g.u+v.+ . . . > A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+@ . . . . i.G+G+G+G+G+G+G+G+G+G+A+[ . . . i G+G+G+G+G+G+G+G+G+*+& . . . . @ >.v+G+G+G+G+G+G+G+G+]+& . . + .E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+c+a ; ; ; ; ; ; 2 +G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+p.b.W I.D+i+^.^.7.F.q+G+G+G+E+X.n.6+s+E+G+G+G+E+z._+_+E+G+G+G+G+G+G+[+q.'+z+o+)+;+{+( I I 5.G.=.5.0.M o < =+Y.H.,+l.>.l.1 K ,+5+b+b+b+b+H.0 e S ].X L e p e L ;.6.L b+b+5+T.D D [./.r 6 ^ O.;+e+w+A+|+7+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+D+D+D+D+B+B+B+h+h+~+$+D.1.;.&.Y g e 9 ] D }.D.h+B+B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+;.s h+|.l.v B ( : d 1+F+B+d l.s D 5+b+b+b+( S r+r+r+l+b+5+,+d u+y+u+r+l+b+,+u l.f B+B+d g t.h+Z K 4+D.r G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+*+J |.v.J.*+]+3+3+3+3+*+v.i.A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+v+|.> + . . @ w ]+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . . . . . . . . . . . . [ p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > G+G+G+G+G+r+g.! U }+F+G+>.+ . . . [ A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . . R.G+G+G+G+G+G+G+G+G+G+A+> . . . i G+G+G+G+G+G+G+G+G+G+R.& . . . . @ |.A+G+G+G+G+G+G+G+]+& . . + >.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+c+U ; ; ; ; ; ; ; ,.y+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B.y.d.v+D+2+W n.0.q.z.s+E+G+E+V.e.[+G+G+G+G+G+E+Z.V.X.E+G+G+G+G+G+G+g+7.q._+)+7+w+B+e o q =.I 6 5 '.e.5.=.p.#+b+T.l.l.s.| S 5+b+b+l+l+l+D.0 e ~.,+T.l+5+(+{.Y s e 0 g Y j.Y D F x.).r t t c.B.Y.Y.7+g+'+[+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+D+B+B+h+d+4+1+t+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+g 6.v+s.f ] 1 B &+r+u+u+d u P j.5+b+l+b+( {.u+u+r+l+H.j.S 1 a.$+b+b+5+,+K.g ,+X B+B+d 1 w.G+B+d 9 u D G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+i.>.|.i.R.*+*+*+*+*+J.i.a+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+k+@ . . . . & 3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . + @ @ @ + + . . . . . [ k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > G+G+G+G+r+U ! ,.r+G+G+G+J + . . . [ A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.@ . . . + a+G+G+G+G+G+G+G+G+G+G+A+& . . . w G+G+G+G+G+G+G+G+G+G+E+R.& . . . . @ |.G+G+G+G+G+G+G+]+& . . + >.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+l+x ; ; ; ; ; ; ; 2 r+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+P.X.z+x+_.N N.P.7.^.q.Z.g+s+q.y.g+G+G+G+G+G+G+++A.=+A+G+G+G+G+G+G+q+n.>+)+<+E+G+B+e o 5 M 5 9 c '.p.^.E.d.,+5+b+1+T.K.| S b+l+l+r+r+r+j.0 0 ~.1+,+u+u+y+u+r+l+$+g S u 0 p t t x.H 6 d &.|+[+<+|+Z.Y.B.(.j+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+a.r D+F+].] e ].&+b+b+&+1 E j.,+b+l+l+&+{ a.d+a.S u | | | | < < g ~.k.l.:.0 d+{.B+B+S 6 `.G+G+t+B ( ;.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+J .|.v.v.J.J.J.v.i.a+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+E+v.. . . . . J A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > p+E+A+A+p+k+a+3+]+*+3+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > G+G+G+l+2 ! C.y+G+G+G+G+J . . . . [ A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.+ . . . $ p+G+G+G+G+G+G+G+G+G+G+A+$ . . . w G+G+G+G+G+G+G+G+G+G+G+G+J.@ . . . . . i.G+G+G+G+G+G+*+& . . + |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+u+a ; ; ; ; ; ; ; ; c+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+Z.U.Q (.A+p.e.A.[+P.N.N.n.b.y.z.:+f+e+o+s+q+j+7.)+s+G+G+G+E+_+)+)+e.Q.[+C+G+G+g+) ] 1 o ^.c.2+>+E.>+Q u S :.&+1+5+,+0 a.l+r+u+u+y+u+;.e 0 D.b+5+5+u+r+l+b+1+&+u |.l.e 9 ] ] o g ].(+8+G+E+'+E+G+G+'+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+`.].f d j.1 v |.s.l.G K s.K.H.S j 1 { 0 u j.d+B+y+r+l+b+S T.K.s.:.u j 1 0 Y v B+D+1.n (+F+1+d ].g t.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+v+J. . . . .v.3+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+v+J + . . . + v.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > G+F+}+! a +F+G+G+G+G+G+ .+ . . . & A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+>.+ . . . > v+G+G+G+G+G+G+G+G+G+G+v+$ . . . w G+G+G+G+G+G+G+G+G+G+G+G+G+v.@ . . . . + i.A+G+G+G+G+*+& . . + |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+}+2 ; ; ; ; ; ; ; ! C.F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+z.N k+G+A.b.N.i+z+g+{+N.L.n.y.S.;+;+6+6+6+;+n.=+7+o+w+i+_+)+Z.s+n.P.Q.X.o+z+<+] c o o '.h.q+U.>+'.c u H.l.|.K.&+,+0 D.r+u+y+F+F+F+Y L 0 D.b+5+&+b+l+5+&+T.s.S a+A+p u.A ] r {.x+D.m+G+G+/+z+G+G+'+q+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+] ^ j.w.1 1.x+..J f 9 n 1 | 6 S ~.$+p t.E+G+G+F+r+r+b+S K.s.|.B+1.m.$+E 9 { 4+B+;.s `.k.d 1+d+r (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+k+k+p+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+G+a+> . . . . $ *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . > y+ +2 a }+G+G+G+G+G+G+G+i.+ . . . + p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+i . . . . J A+G+G+G+G+G+G+G+G+G+G+v+@ . . . J G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.$ . . . . $ i.A+G+G+G+*+& . . + i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+ +2 ; ; ; ; ; ; ; ! ,.u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+^.B.g+Z.e.p.X.G+G+E+2+7.e.y.N.P.Z.i+e+e+{+n.q.P.Q.Q.>+U.{+o+j+y.E.x.Z.Q.P.A.b 0.o o 5.t 4.>+W m 5 u s.>.l.K.&+1+0 D.r+y+F+F+G+B+Y Y 0 $+l+b+,+&+1+T.s.l...&.j+[+e Z.d.^ n t+D+1.B+G+G+/+s+G+G+Y.z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+: 9 u w.1 &.D+f i ) { 9 X j.b+l+r+y+r 1.D+G+G+F+r+r+b+S K.s.l.D+m.m.`.&.m+X j a.Y r v d ].G+~+r (+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+G+G+*+@ . . . . & k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . [ C.a x c+G+G+G+G+G+G+G+G+J.+ . . . . *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+> . . . + v.G+G+G+G+G+G+G+G+G+G+G+p++ . . . J G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.$ . . . . $ v.A+G+G+*+$ . . @ i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+g.2 ; ; ; ; ; ; ; ! U r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+N.b.b.9.n.B.U.G+G+G+w+!.^.n.Z.q.N.M.:+e+w+n.0.{+>+S.Z.V.=+=+=+n.p.Y.<+q.;+z.( T o H b.%.7.7.W A e s <. .s.T.,+5+0 D.r+F+F+G+G+B+L &.0 $+l+b+,+T.K.l...B %.I X./+H 8.v d d x+B+L D+G+G+/+o+G+D+V.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g K n u 6 *.B+f - ) n H.&+3+l+r+r+y+r }.D+G+F+F+r+r+b+{.K.s.l.D+6.}.L L x+D.d ( ( 6 d ~+B+G+$+r d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . + J G+G+G+G+G+G+E+i.. . . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . # 2 x c+G+G+G+G+G+G+G+G+G+3++ . . . . .G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.@ . . . & 3+G+G+G+G+G+G+G+G+G+G+G+k+. . . . .G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+v.$ . . . . $ J.G+G+*+$ . . @ i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+r+U ! ; ; ; ; ; ; ; ; x r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+V.N.|+p.7.b.++D+E+!.G.j+n.'+s+E+E+w+i+Z.y.X.[+G+G+G+G+G+G+g+e.n.F.i+X._.2+( c z &.y.S.7.U.=.n { t d f n 1 | 1 _ s L r p E E p p r 0 a.$+1+&+T.s./.X.F.I K x.).v H.q+8.f ] I r <+i+q+:+<+E+E+<+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+6.L 8+S 1 e : = i ..9 n 0 u b+l+r+r+Y *.F+F+F+u+r+l+b+].k.s.s.E+a.0 *.K 1.4+a.B : ( 9 D.B+D+{.D B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . w G+G+G+G+G+G+G+G+i . . . . . w G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . # a c+G+G+G+G+G+G+G+G+G+G+A+$ . . . . @ G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+J . . . . w v+G+G+G+G+G+G+G+G+G+G+G+3+. . . . .G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i.+ . . . . + *+G+*+$ . . @ v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+}+! ; ; ; ; ; ; ; ; ; 2 u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+z.g+A.7.F.p.F.g+_.x+A+q.P.P.i+G+G+D+E+e.x.s+E+E+G+G+G+G+A+n.n.Z.6+Z.B.h.( M o $.U.^.m Q H b | { < | v Z :.K.0 D.l+h+d+$+;.E s Y 0 0 0 e S ~.]./.A.[+x.g v 6 h+D+D+1.u.f ] s :+/+/+'+_+z+E+7+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+`.r D.K 1 { = d w ..u s.].1 S 1+l+r+K *.y+y+u+r+l+b+b+j.k.l.K.B+;.p 8+D.L ~+: f : E : ( S B+;.*.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . w G+G+G+G+G+G+G+G+v+> . . . . @ i.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . # +G+G+G+G+G+G+G+G+G+G+G+G+J . . . . . J.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+$ . . . @ v.G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . .G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|.+ . . . . + *+*+$ . . @ v.G+G+G+G+G+G+G+G+G+G+G+G+G+F+u+g.! ; ; ; ; ; ; ; ; ! x u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+p.B.p.q+P.p.'.x.e+s+V.g+Y.Q.z+z+g+++e.N.U.;+_+7+e+z+D+E+p.y.;+:+7+Y.(.1 =.4.)+^.'.9.b ( { < < < : l.K.&+1+0 a.r+u+8+*.E D.t+u+< a.j.S g 0 0 g H $.).9 n 1 {+E+B+Y 4+~+] ~.E+w+g+'+X.Z.e+[+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+4+r a.n : = B s G ..n s.T.K.| e j.l+S #.r+r+r+r+l+b+5+j.:.l.]+4+p r x+h+s f u ].E g s j.9 `.K &.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . w E+G+G+G+G+G+G+G+G+]+& . . . . & R.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . & E+G+G+G+G+G+G+G+G+G+G+G+G+3++ . . . . & p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+i . . . . [ p+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . .G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|.@ . . . . $ >.& . . $ J.G+G+G+G+G+G+G+G+G+G+G+G+F+}+U ; ; ; ; ; ; ; ; ; ! U r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[+n.n.++N m 7.Z.|+[+x.n.b.b.b.^.B.@+d.b.|+E+A+q+g+Z.'+X.N.N.N.Q.X.O.z.>+7.q =.O.I t j.r <.S ;.T /.9 < < u | u ;.P 6.l+u+u+u+r+| ~.b+3+T.s.l.9.<.~.<.{ 1 | ++;+.+p k.] r `.G+G+G+q+;+i+2+g+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+S 6 ] ^ j.g r $+J | :.K.T.,+&+].< j p b+l+l+b+5+,+,+:.Z s.B+p Y #.D.d v g < P S #.r d+G+f 6 `.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F.q+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . i E+G+G+G+G+G+G+G+G+A+i.@ . . . . > a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . . & v+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . + >.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|.+ . . . + J.G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . . . >.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i.@ . . . . + + . . $ R.G+G+G+G+G+G+G+G+G+G+G+l+g.a ; ; ; ; ; ; ; ; ; ! ,.r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+F.b.^.(.2+g+N.P.++B.b.7.B.q.Q.M.q.z.7.B.z+E+E+G+E+E+E+7.b.b.Y.y.N.U.I Q 0.6+<.Y.g+d+D u.6.I A.(.|.:.v 1 < p D 1+r+r+r+r+r+r+1 S 5+,+K.s.|.z.[+j+'+1 E n .+<+).0 ^ d r d+G+G+G+z+X.e+s+s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+a.e = v y+$+g t+|.| :.s.K.&+,+1+v < p a.b+b+5+&+k.Z 9 j X {.p Y u d H.~+r 0 w.D.#.r 8+D+r }.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+7.O.s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . i E+G+G+G+G+G+G+G+G+G+v+w + . . . . > G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+@ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+. . . . & v+G+G+G+G+G+G+G+G+G+G+G+G+G+3++ . . . . $ *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.+ . . . . i A+G+G+G+G+G+G+G+G+G+G+G+G+G+i.. . . . >.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.@ . . . . . . . $ *+G+G+G+G+G+G+G+G+G+y+ +x ; ; ; ; ; ; ; ; ; ; ; g.u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+z+Z.N.E.V.e+0.b.b.b.9.w+E+z+j+B.!.X.7.^.=+'+<+e+w+E+E+q.'.b.)+E.^.W I S.=+M Z._+A+~+D u.<.4.P.9.|.l.s.K.j | H.l+l+l+r+l+l+l+u K ,+,+s.l.>.O.Y.'+V.p 1.m+d .+H 1 Z D.r m+G+G+G+E+:+7+g+q+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$+: : &.D+B+0 6.3+| +.l.s.T.&+,+,+].< < j.k.X { | { ' < < < g L K j.F+x+u e d+`.#.r `.$+P d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+Q.N.O.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . i E+G+G+G+G+G+G+G+G+G+G+v+$ . . . . . i G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]++ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+T.. . . . & v+G+G+G+G+G+G+G+G+G+G+G+G+G+A+ .. . . . . & a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.@ . . . . $ E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|.. . . . >.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+ .+ . . . . . . $ ]+G+G+G+G+G+G+G+G+u+U ; ; ; ; ; ; ; ; ; ; ; ; C.F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+j+V.P.N.b.b.b.b.7.++q+B+#+N N Z.@+b.{+'+P.Q.Q.Y.Z.U.U.n.7.!.m _.6+P.I 1 t 8.x+G.r ).=.4.z.++..l.s.K.X | &+b+b+b+l+b+b+b+S s &+T.l.|. .6+/+x.H 1 ++e+b ] j 6 1+6.L B+G+G+G+E+<+:+[+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+^ 1 &.D+F+1 e `.1 B |.l.K.T.&+,+k.| ) < < { B +.:...1.a.0 9 a.#.8+G+x+K 6 4+8+#.j S : `.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+q+q.X.[+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . i A+G+G+G+G+G+G+G+G+G+G+G+3+@ . . . . + >.A+G+G+G+G+G+G+G+G+G+G+G+G+G+*++ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+a . . . . $ p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+> . . . . . & a+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+i.@ . . . . @ 3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+ .. . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+J + . . . . . & 3+G+G+G+G+G+F+u+C.2 ; ; ; ; ; ; ; ; ; ; ; x }+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+z+j+@+'.^.b.N.N.N./+q.E.^.i+:+;+>+X.<+[+|+E+A+(.m 0.p.A 1 | < {+<.t 9 j D.x+6 ++=+(...|.l.:.| j.,+,+5+3+1+1+,+].| k.s.>.l.B+H P &.v ) X u.9 9 6 ) Z.o I <+w+E+D+G+g+;+x.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+x+1 : j.x+&.m+j { 9 J ..|.l.l.d ) ' d s.:.d | B :.t+g p 1.t+Z ' u 4+P K { D.(+#.9 g *.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . i A+G+G+G+G+G+G+G+G+G+G+G+G+i.@ . . . . $ v.E+G+G+G+G+G+G+G+G+G+G+G+G+*++ . . . > v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+& . . . . v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+U % . . . . $ p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.& . . . . . $ |.a+A+G+G+G+G+G+G+G+G+G+G+G+v+*+i + . . . . $ J.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J . . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+ .+ . . . . & 3+G+G+G+F+r+ +x ; ; ; ; ; ; ; ; ; ; ; ! ,.l+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+2+G.b.e.n.n.N.N.b.-+U.Y.7+B+D+@+q+s+7+'+m W N / ) ( < !.c.@+{.j u {.o _+_+O./...|.:.| Z &+&+,+,+,+&+&+].| ].l.l.m+m.D (+d+u v ) d v E I ] 1+r $.Z.X.:+e+E+s+;+X.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+n e 6 v &.B+4+{ K T.G ..G d ) B : v l.l.|.: < g `.0 E t+G+h+n ) n D m+' {.~+n e P 8+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . [ A+G+G+G+G+G+G+G+G+G+G+G+G+E+>.+ . . . . & ]+G+G+G+G+G+G+G+G+G+G+G+G+R.+ . . . & J.R.*+*+*+*+*+*+R.R.J.J.v.p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+& . . . . >.]+*+*+*+*+R.R.R.J.J.J.v.]+A+G+G+G+G+G+G+G+G+G+}+, % . . . . $ k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+J.$ . . . . . + > >.*+v+G+G+G+G+G+G+A+3+v.i $ . . . . . $ v.A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w . . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+>.+ . . . & 3+G+G+u+ +U ! ; ; ; ; ; ; ; ; ; ; ; 2 C.r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+2+++z.E.q.V.9.W #+A+G+G+C+9./+>+>+p.'.6+z.- : o : e I F.|+s K u < $.).x.O./. .:.1 v K.T.T.T.T.K.K.:.| B ..`.P P _+s+w.n ] ) ].P $.B.] B r (+D+|+Z.Y.:+{+X.X.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+h+j | : S x+D+( g j.s ] ) ] ..>.d 9 >.>...;.D.6 < p t.D+G+h+L 6.{ n ~+( S H.] E w.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+|.. . . . [ A+G+G+G+G+G+G+G+G+G+G+G+G+G+p+i . . . . . > a+G+G+G+G+G+G+G+G+G+G+G+R.. . . . + $ $ $ $ $ $ $ $ $ $ $ @ @ *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$ . . . . @ & $ $ $ $ $ $ $ $ $ @ @ w v+G+G+G+G+G+G+G+G+F+U , % . . . . $ k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+]+& . . . . . . + $ > J >.i.i.|. .[ & @ . . . . . . & *+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i . . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+ .+ . . & k+y+ +x 2 ; ; ; ; ; ; ; ; ; ; ; ; U c+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+2+F.7.b.m _.X.Q.p.0.E+k+N Q e+Y.B.b | o 8.I .+{.0 | H B.< <.'+G.<.;+4.G 9 j s.T.&+&+T.s.l...6 g D ;.`.a.9+I D j B+Z ) G.z+j.g d d t+G+G+G+G+A+<+/+++G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+~+s S e j j.' ' ) g r (+s.T.].1 y+E+F+L B+G+D+{ t+G+G+B+L e t+h+X ' { ] g m+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+>.. . . . [ A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+& . . . . . |.G+G+G+G+G+G+G+G+G+G+G+J.. . . . . . . . . . . . . . . . . . R.G+G+G+G+G+G+G+G+G+G+G+G+G+E+@ . . . . . . . . . . . . . . . . . w v+G+G+G+G+G+G+G+G+}+a , % . . . . $ k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+i . . . . . . . . . + + + + + . . . . . . . . J A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ . . . . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+w + . & +x ! ; ; ; ; ; ; ; ; ; ; ; ; ! g.u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+j+^.W m '.j+c.B.w+(.N c.B+:+B.A ] e K b X.g+a.g 0 6 < )._+:+9+;+'+d.b < D.(+t+B+r+b+,+f g p g u u S /+).Y u d+&.( B+D+~.r D w.D+G+G+G+G+e+9+e+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+(+K K e = ) : S j 0 s s.5+$+1 E+G+D+#.B+G+G+{ 4+G+G+B+j E D+B+H.' ( ) P D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+>.. . . . [ v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i.@ . . @ >.A+G+G+G+G+G+G+G+G+G+G+G+v.. . . . . . . . . . . . . . . . . . v.G+G+G+G+G+G+G+G+G+G+G+G+G+v++ . . . . . . . . . . . . . . . . . w v+G+G+G+G+G+G+G+F+g.! , % . . . . $ a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+J.> @ . . . . . . . . . . . . . . . + $ [ *+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+& . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y . # ; , ; ; ; ; ; ; ; ; ; ; ; ! x }+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+F.!.m h.'.9.z.N h.z+E+:+<+A ^ b e : 4./+B+;.6 6 | t B.B+z+A.<.( { < p p r L 1.a.j.E e ( n j.4+x+/+$.w.s #.D : D+D+{.e Y x+G+G+G+G+G+9+7+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+g K n = ) ( S 4+j j g 6.D.1 F+G+G+#.B+G+G+( $+G+D+`.E ;.1+].d j ( 6 1.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+i.[ [ [ [ .v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+J . & v.A+G+G+G+G+G+G+G+G+G+G+G+G+R.[ > > > & & $ $ $ $ @ @ @ + + + + + |.G+G+G+G+G+G+G+G+G+G+G+G+G+p+[ > > > & & & $ $ $ @ @ @ @ + + + + w v+G+G+G+G+G+G+G+r+U ; , 7 [ [ [ [ i k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+J.w > $ + . . . . . . . + @ & [ .*+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+[ > & & $ i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+l+ +x # # * ; ; ; ; ; ; ; ; ; ; ; a g.r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+j+h.m z.>+<+2+E+G+E+<+:+A ^ f j.' Z <+i+&.H 8.( < < j.S ( 1 ~.Z | H.D.{.&.0 0 p 6 ~.' v F+G+G+B.*.$+j H .+X B+B+;.g 8+G+G+G+G+G+q+:+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$+g n = : 1 g &.D t.9 | | { h+x+B+#.x+D+D+( a.B+h+D r 1.d d v v 1 K x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+p+p+p+p+p+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+ .*+E+G+G+G+G+G+G+G+G+G+G+G+G+G+A+k+k+a+3+3+]+*+*+R.R.J.J.v.v.v.i.i.|.]+G+G+G+G+G+G+G+G+G+G+G+G+G+E+k+k+k+a+3+]+*+*+R.R.J.J.J.v.v.v.i.i.R.A+G+G+G+G+G+G+G+c+a ; ; +p+p+p+p+p+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+k+R.i.>. .w i i J >.|.J.]+p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+a+3+*+R.k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+c+g.x ! ; , % * ; ; ; ; ; ; ; ; ; 2 ,.c+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+E+G+G+G+G+G+E+7+x.X.9.o e : ( e 4.I H I G.: ] t 5 < 0 ).%.( $+m.P E D.5+&+.+a.: ( D.t+B+D D D g E+G+h+X v r r o+G+G+G+A+7+9+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+].1 ) ( D.u d $+s.5+r+X g 0 0 9 1.m.m.9 j }.E Y v X B+1.D P (+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+G+G+G+G+G+G+G+F+ +; ; ; +G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+A+v+p+v+v+A+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+ +U a ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; x +u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+[+_.A.O.4.o Z d : e 6 g H [+d = !._._.).e < { E D 1.(+r+b+,+Z.;+4.( 6 n {.P H ).s B+h+].: r P u.e+G+G+G+e+6+i+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+e { ( S : d `.s.5+y+].4+D.s e t.m+B+X 6 $+&.t+d a.`.P 1 D.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+C.; ; ; g.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+C.x ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 C.F+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|+(.V.O.O.4.s t ) 6 e e g d.= ) @+v+e+O.d+X 9 ;.4+D+G+u+b+,+|+[+).' h.9 ( o o.).s {.n 9 : r 1.w+e+G+G+E+9+{+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+$+6 ( : ^ ] r :.5+y+H.4+D+t+( e e ;.].{ s h+k+d `.L g E B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+g.; ; ; x F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+u+}+,.2 ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; a g.u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+V.:+<+x.Y.(+g : F.d.^ ] = ( ) D D D H w.l.l.X u+F+G+r+b+&+A+(+H b r u r 9 : ( : `.P D s <.=+=+'+i+w+_+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+h+a.0 | | g K 8+h+6 r K j.t+B+h+6 { K G+~+9 a.r D d+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+g.; ; ; a c+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+r+c+C.U ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; a ,.c+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+s+Y.<+[+X.z+(+9 Z ^ A _.] 1 ( t ).).H s +.s.9 u+G+G+r+b+&+2+$.D E 9 K j T I ] d j j r (+x+q+i+O.O.X./+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+$+;.e 0 | g Y ( g : j s ;.D.f ) : D+H.9 : P (+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+g.; ; ; ! g.F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+c+C.U a ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; a g.c+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+X.<+g+x.:+s+D.: b _._.<.I 6 | e $.4.++[.K.( ~+G+G+r+b+K.D E e 6 j *.`.g+I d ;.: 6 L D+G+G+G+O.V./+o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+h+n e 0 | ( : - p r r 6 = ) ) H.+.] 6 `.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+C.; ; ; ; a }+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+c+C.U a ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 g.c+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+V.V.j+x.Y./+e+g g %.f Z.'+.+: 1 e e D...K.: j.F+G+b+j.L L 9 n L : h+F+d+d j L P K 4+G+G+G+G+X._+o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+K.k.;.g ( ) ) g S d = 9 : ' ) : ( E B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+}+! ; ; ; ; U r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+r+C.x ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 C.u+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+{+X.x.s+j+_.Y.[+u.d g n &.: I 4.H : 1 e 9 : j.L Y j.K.B v `.x+9 ~.B+f 9 D #.D+G+G+G+w+<+9+z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.3+v+v+x+d+a.( = ) 1 s v ' j.( 9 x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+l+x ; ; ; ; ; U u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y+u+c+C.x ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ! x C.y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+V.Y.[+(.(.{+;+:+|+g e g : 9 H I ] +.+.( ( D L b+5+T.d ].D+G+X v X 9 P Y 4+G+G+G+i+9+9+i+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.]+v+v+E+G+D+~+~.g < 1 ( ) D D 4+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+g.! ; ; ; ; ; x }+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+r+c+ +,.x ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; a U +u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g+/+c._.k+E+g+Y.;+F.;.e d : 9 ] g ` K.H.: v x+r+b+T.d ~+D+x+v ( ] #.I Z.D+G+G+z+9+<+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.]+v+v+E+G+G+G+D+h+a.0 < ) D $+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+c+a ; ; ; ; ; ; a g.l+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+r+c+ +g.U a ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ! x g.}+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+c.|+E+G+G+G+D+A.=+<+D.d 1 ;.9.>.T.r+j.6 4+r+b+T.9 j r P r g S D+g+X.s+7+9+:+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.]+v+v+E+G+G+G+G+G+G+E+B+~+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+g.; ; ; ; ; ; ; ! x C.u+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+u+}+C.,.x a 2 ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ! ,. +l+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+B.Y.i+G+G+G+G+B.B.X.{+h+;.e ).>.T.r+~+9 D.r+5+T.s P D g #.D.x+G+q+X.{+9+e+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.]+v+v+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+a ; ; ; ; ; ; ; ; ; ! ,.l+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+r+ +g.x ! ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; a C.r+F+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+j+x.;+o+E+G+G+@+B.w+X./+[+~+g >.T.r+u+9 X r+5+T.D g D P t+B+w+i+{+X.6+s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.]+v+v+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+u+a ; ; ; ; ; ; ; ; ; ; ; x C.c+u+y+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y+y+u+u+c+ +g.U 2 ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ! 2 ,.}+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+2+d.;+:+o+E+g+B.q+q+<+;+Y.8.>.&+r+y+D.6.r+5+K.D P H F._+/+;+;+;+X.Z.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J.]+v+v+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+l+x ; ; ; ; ; ; ; ; ; ; ; ; ; x ,.C.}+c+l+r+u+y+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y+y+u+r+l+c+c+}+C.g.U x a ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 x ,. +r+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+x.X.X.X.<+B.B.G+G+G+A+B.|.T.r+F+G+G+r+5+K.x./+/+6+6+6+6+6+6+/+A.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+*+v+v+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+l+,.! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ! a x U ,.,.C.C. + + +}+}+}+}+}+}+}+}+}+ + + +C.C.,.,.U U x x 2 ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 U g. +l+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+I.d.x.X.X.z.x.E+D+G+G+1+|.T.r+F+G+F+r+5+K.j+o+o+o+o+o+o+o+o+o+s+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+*+v+v+p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+u+C.! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ! ! 2 2 2 a a a a a a a a 2 2 2 2 ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 U C.}+r+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+I.d.c.Y.A.x.:+i+C+G+I.|.&+r+F+G+F+r+5+K.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+*+v+v+p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+l+U ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 ,. +u+y+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+E+|+:+/+d._.X.O.R l.&+r+F+G+G+l+5+K.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+]+*+p+v+p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+}+U 2 ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ! 2 a U C.c+y+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+q+:+V.c.9.(.l.&+r+F+B+8+l+1+s.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+*+p+v+k+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+}+,.x ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 a x ,.C.}+u+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+q+[+h.#+l.&+r+F+8+*.l+1.:.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+3+|.J.3+a+|.v.a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+y+c+ +,.x ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ! a U ,.g. +c+r+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+3+l.&+r+F+t.r b+s :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+i.>.i.v.J.J.J.v.i.p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y+l+}+C.U a ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 x ,.g. +c+c+r+y+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+l.,+r+F+1.L `.e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+|.v.J.]+3+3+3+3+3+]+v.i.v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y+y+l+ +g.U a ! ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 2 a U g.C.}+c+u+y+F+F+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+l.,+r+u+Y r {.e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+i.i.J.*+3+a+k+k+k+a+3+*+v.J.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F+y+r+c+}+C.g.,.,.,.U U U U U U U ,.,.,.g.g.C.}+c+l+u+F+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+l.,+u+d+g 0 0 e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+ .J.*+]+a+k+p+p+p+k+k+]+R.v.k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+&+l.,+u+F+Y 1.j.e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+ .|.*+]+a+p+v+A+E+E+v+p+a+3+R.v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+&+l.,+m+t+g ;.j.e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+3+ .i.*+3+a+p+A+G+G+G+E+v+k+3+*+v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+&+s.,+w.m.e ;.j.e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+ .i.*+3+a+p+A+G+G+G+G+v+k+3+*+v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+&+s.,+;.L e t.j.e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J. .i.*+3+a+p+A+G+G+G+E+v+k+3+*+v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B./+Y.Y.Y._+_+'+_+++G.G.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+s.s.,+L g 0 D.D.e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+ .|.R.]+3+p+p+v+A+A+v+p+a+]+R.i.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+X.A.Y.Y.Y.;+;+Y.c.(.(.d.A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+s.s.1+g n 0 `.D.e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+>.>.J.*+3+k+p+p+v+p+p+k+3+*+v.J.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+/+/+X.Z.[+[+d._.(.#+q+Z.[+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+s.s.1+e u p (+D.e :.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+J. .v.R.]+a+k+k+k+k+k+a+]+R.i.a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+D+i+Y.[+A.c.B.++/+i+7+<+'+w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+s.s.,+1 ) ) r E 0 n D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+3+|.i.J.*+]+3+3+3+]+*+v.i.k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+s+'+[+z.++|+F.x.B.[+s+'+7+B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+s.l.;.6 ) v 4+B ) | w.t+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+ .|.i.J.R.*+*+*+R.J.i.a+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+'+B.{+X.q+A+F.x.B.q+g+/+e+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+:.s P H.) j.F+:.^ +.e s D.x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+>.>.i.v.v.J.v.i.i.a+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+3+3+a+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v+3+3+v+G+G+G+A+3+3+p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+3+3+p+G+G+p+3+3+k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+3+3+3+G+G+G+G+G+G+G+G+G+G+G+p+3+3+p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+3+3+k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+3+3+3+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+Z.9.q+/+{+E+B+[+X.++j+{+'+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+P D D.].) h+F+5+K.l.(+u 0 Y D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+k+i.>.|.|.|.|.|.R.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+G+G+G+G+G+G+G+a+3+3+v+G+G+G+G+p+3+3+k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+J . . 3+G+G+G+>.. . R.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . |.G+G+w . . & G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . G+G+G+k+3+3+v+G+G+G+G+w . . w G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+>.. . > G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+. . . *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+G+D+F.c.G+q+X.{+G+E+e+++|+F.x.[+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+q+h.s+E+G+G+G+G+G+G+G+G+G+G+G+D+(+#.#.l.K.3+) H.D+F+b+&+|.1.(+m+h+( Y t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+E+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+G+G+G+G+G+G+G+@ . . J.G+G+G+G+ .. . [ G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v++ . > G+G+G+E+@ . $ E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . |.G+G+w . . & G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . G+G+G+& . . i.G+G+G+G+w . . w G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+>.. . > G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+a+. . . *+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+7+e+[+A.x.D+E+{+/+G+G+z+Z.A+A+|+x.g+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+G+G+G+G+i+^.W y.Y.g+G+G+G+G+G+G+G+G+G+G+1.P *.4+s.K.5+) k+G+F+b+&+|.s e e S ) | j D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+G+G+G+G+G+G+G+@ . . J.G+G+G+G+ .. . [ G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+i.. . v.G+G+G+v.. . |.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+> . . |.G+G+k+*+*+3+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+. . . G+G+G+& . . i.G+G+G+G+w . . w G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+>.. . > G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+*+*+*+v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+_+/+;+O.X.e+s+j+X.E+G+E+/+q+E+g+x.:+C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+z+7+X.i+D+G+E+<+U.y.y.e.e.|+z+G+G+G+G+G+G+D+4+s 1.x+F+l.:.].( 6.`.(+T.K.|.x+(+{.9 { ) | (+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+p+i.>.J.A+G+G+v+*+@ . . >.*+*+p+]+i . . > *+*+3+G+p+*+*+]+G+k+i.>.J.E+G+G+G+G+G+p+J.i.|.v.]+E+G+G+G+]+*+*+p+G+G+G+G+G+G+G+G+& . @ A+G+G+G+> . + v+G+G+G+v+*+*+*+G+G+p+]+A+G+G+G+k+i.>.>.i.p+G+G+G+G+> . . |.G+G+k+*+*+a+G+G+G+G+E+*+|.>.>.R.A+G+G+G+G+k+*+*+a+G+a+|.|.*+G+G+G+G+G+G+G+G+G+G+G+3+*+*+k+G+E+3+a+G+G+G+E+R.|.>.|.*+E+G+G+G+E+k+J.i.>.|.v.]+E+G+G+G+G+G+G+3+|.|.*+R.. . . G+A+*+$ . . J *+*+k+G+w . . w G+3+|.|.*+G+G+G+G+G+G+E+*+|.>.>.R.E+G+G+G+G+G+E+R.>.i.3+>.. . > G+G+G+G+G+*+|.>.>.R.A+G+G+G+G+G+G+G+3+i.>.i.R.A+G+G+G+G+3+v.|.|.J.k+G+G+G+G+G+G+G+G+G+E+*+*+*+A+G+G+G+G+p+i.>.>.i.k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+<+<+Y.B.Y.[+{+V.z.B.|+z+Y.X.d.I.D+7+'+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+w+7+>+S.[+^.0.N.L.[+z+D+F.^.@+g+^.e.L.{+z+E+D+L P ) ) 9 1 1 ' < { < S (+(+{.K j 0 0 0 { e K 9 | | {.h+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + i.+ . . . $ k+G+*+. . . . . . . i.$ . . . . . . > G+v.. . @ .. . . . > v+G+G+A+w . . . . . . @ G+G+G++ . . i.G+G+G+G+G+G+G+*+. . .G+G+G+3+. . w G+G+G+G+]+. . . a+[ . . k+G+A+i . . . . . + |.G+G+G+> . . |.G+G+ .. . [ G+G+G+a+& . . . . . $ *+G+G+G+ .. . > J . . . . [ E+G+G+G+G+G+G+G+G+G+[ . . >.|.@ . i G+G+*+$ . . . . . & k+G+G+a+. . . . . . . & a+G+G+G+A+[ . . . . + . . . G+a+. . . . . . . >.G+w . . [ i . . . . i E+G+G+G+3+& . . . . . $ a+G+G+G+k+& . . . . + . . > G+G+G+a+& . . . . . @ R.G+G+G+G+]+& . . . . . + 3+G+k+> . . . . . . i G+G+G+G+G+G+G+G+v+. . . a+G+G+E+J . . . . . . J A+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+7+7+O.Z.'+'+/+/+Y.x.x.x.B.X.Z.A+G+s+/+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+w+<+)+>+[+z+G+^.c.q+{+L.p.[+j+^.d.E+m _.{+L.E.{+u.e ) ' { ' X D.E r ' K < ;.t+5+K.+.p e { ' < < < < < < 9 t+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . . . . . . . . i G+*+. . . . . . . i.$ . . . . . . > G+v.. . . . + . . . . J G+G+i . . + [ i & . . G+G+G++ . . i.G+G+G+G+G+G+G+i . . p+G+G+G+w . . a+G+G+G+G+]+. . . + . . . k+G+[ . . > >.w + . . R.G+G+> . . |.G+G+ .. . [ G+G+p+@ . . @ [ @ . . + 3+G+G+ .. . . . . . . . . v.G+G+G+G+G+G+G+G+G+[ . . + . . . i G+*++ . + J >.& . . > G+G+a+. + & [ & . . . & G+G+G+i . . . + $ . . . . G+a+. . . . . . . >.G+w . . . . . . . . . R.G+G+a++ . + w >.> . . $ A+G+A+$ . . . + @ . . . > G+G+p+$ . . @ [ $ . . + ]+G+G+3++ . . . $ $ . . *+A+$ . . @ [ i $ . [ G+G+G+G+G+G+G+G+v+. . . a+G+G+J . . . > > . . . w G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+B+C+O.[+{+++Z.Y.Y.Y.V.x.9.9.X.q+G+x+h.z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+D+q+)+>+/+[+D+G+G+c.^.w+E+{+y.L.[+!.'.j+m 2+E+{+n.L.e.{ ) : H.) S P v :.' t+{.< j 5+T.+.`.D.9 ' r r e ) ) < < 9 j.t+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F.s+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . . |.a+3+& . . & G+G+G+@ . . J.G+G+G+G+ .. . [ G+G+G+G+v.. . + ]+E+v+[ . . + A+A+. . . v.G+G+G+3+i G+G+G++ . . i.G+G+G+G+G+G+k+. . i G+G+G+v++ . > G+G+G+G+G+]+. . . + > [ & k+J.. . > E+G+G+>.. . [ G+G+> . . |.G+G+ .. . [ G+G+J . . @ p+G+A+> . . [ G+G+ .. . $ v.k+R.+ . . J G+G+G+G+G+G+G+G+G+[ . . . $ > [ .G+& . . R.G+G+v++ . . ]+G+v+R.A+G+G+G+v.. . . p+G+3+. . . J A+G+R.. . . G+G+G+& . . i.G+G+G+G+w . . $ J.k+v.+ . . >.G+G+[ . . i.G+G+E+$ . . v.G+|.. . . v.G+G+ .. . > G+G+ .. . @ p+G+A+> . . > G+G+> . . $ a+G+G+3+> *+*+. . . k+G+G+G+R. .G+G+G+G+G+G+G+G+v+. . . a+G+k+. . . |.G+G+i.. . . a+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+:+'+z.'+'+:+<+7+[+{+q+q+F.x.x.++A+I._.i+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+s+V.>+X.i+E+G+G+G+G+@+^.@+G+E+w+{+L.^.^.N I.E+D+a.~ M y.%.) j.(+g E p >.+.' F+D+`.e j.K.+.4+B+v S x+d+(+~.: g 1 ' ( 9 d+B+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+!.P.7+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+i.. . + G+G+G+@ . . J.G+G+G+G+ .. . [ G+G+G+G+v.. . @ G+G+G+3+. . . k+E++ . . @ w i.3+E+G+G+G+G+R.J.J.k+G+G+G+G+G+G+ .. . *+G+G+G+|.. . J.G+G+G+G+G+]+. . . A+G+G+G+G+i . . [ |.|.|.w . . @ G+G+> . . |.G+G+ .. . [ G+G+$ . . .G+G+G+v.. . + A+G+ .. . [ G+G+G+i . . [ G+G+G+G+G+G+G+G+G+[ . . .G+G+G+G+k+. . . |.|.|.|.$ . . i.G+G+G+p+v.>.w [ . . . k+G+>.. . @ A+G+G+*+. . . G+G+G+& . . i.G+G+G+G+w . . w G+G+G+> . . w G+A+. . . .|.|.|.& . . .G+[ . . [ G+G+G+>.. . > G+G+& . . .G+G+G+v.. . . v+p+. . . v.G+G+G+G+E+E+3+. . . $ .v.k+G+G+G+G+G+G+G+G+G+G+v+. . . a+G+i.. . + E+G+G+E+@ . . |.G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+{+E+w+i+G+G+E+e+G+G+s+'+V.X.A.c.@+/+o+G+G+G+G+G+G+G+G+G+G+G+G+D+s+_+>+E.g+E+G+G+G+G+G+G+G+E+c.^.q+G+G+G+E+#+Q W Y.8.P `.j.] O L.O H r D d d >.) v F+G+F+F+S < ( w.H.' ~+G+G+G+B+u {.B+< S $+= ) = - x+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+V.s+g+=+7+z+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+J.. . + G+G+G+@ . . J.G+G+G+G+ .. . [ G+G+G+G+v.. . @ G+G+G+p+. . . *+G+J . . . . . . $ J.G+G+G+G+G+G+G+G+G+G+G+G+A+@ . & G+G+G+E+$ . $ E+G+G+G+G+G+]+. . . E+G+G+G+G+> . . . . . . . . . . G+G+> . . |.G+G+ .. . [ G+G+. . . i.G+G+G+]+. . . k+G+ .. . [ G+G+G+J . . [ G+G+G+G+G+G+G+G+G+[ . . >.G+G+G+G+*+. . . . . . . . . . |.G+v+i . . . . . . . . k+G+w . . [ G+G+G+*+. . . G+G+G+& . . i.G+G+G+G+w . . w G+G+G+i . . w G+a+. . . . . . . . . . w G+$ . . >.G+G+G+>.. . > G+G++ . . i.G+G+G+3+. . . a+]+. . . k+G+G+G+G+G+G+E+& . . . . . . > a+G+G+G+G+G+G+G+G+v+. . . a+G+ .. . & G+G+G+G+> . . J G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+q+i+G+E+[+E+G+E+e+G+G+w+'+X./+;+B.B.x.{+E+G+G+G+G+G+G+G+G+G+G+D+_+>+>+y.e.|+A+G+G+G+G+G+G+G+G+|+^.F.D+G+G+2+Q W ^.y.y.).E+D+~+Q ^.L.=.}.d+$+] G ' ].y+D+F+F+5+Z | ;.B : x+G+G+G+B+;.K B+;.e d +.j { ) 1.B+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+@+X.e+w+++P.X.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+R.. . + G+G+G+@ . . J.G+G+G+G+ .. . [ G+G+G+G+v.. . @ G+G+G+k+. . . ]+G+E+|.$ . . . . . . k+G+G+G+G+G+G+G+G+G+G+G+v.. . i.G+G+G+R.. . >.G+G+G+G+G+G+]+. . . E+G+G+G+G+> . . > |.|.|.|.|.|.|.G+G+> . . |.G+G+ .. . [ G+G++ . . i.G+G+G+*+. . . k+G+ .. . [ G+G+G+J . . [ G+G+G+G+G+G+G+G+G+[ . . >.G+G+G+G+*+. . . |.|.|.|.|.|.|.3+G+i . . @ >.J.R.. . . k+G+w . . [ G+G+G+*+. . . G+G+G+& . . i.G+G+G+G+w . . w G+G+G+i . . w G+k+. . . .|.|.|.|.|.|.*+G+$ . . >.G+G+G+>.. . > G+G++ . . |.G+G+G+]+. . . k+3+. . . a+G+G+G+G+G+G+G+v+J @ . . . . . $ E+G+G+G+G+G+G+G+v+. . . a+G+ .. . $ G+G+G+G+& . . J G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+<+Z.|+E+D+{+E+G+E+e+G+G+E+:+X._+X.|+j+x.B.q+G+G+G+G+G+G+G+G+E+s+V.>+X.Y.p.n.n.q.i+q+B+E+G+G+G+q+X.E.L.g+E+E+Q Q h.e.c e.L.g+D+x+o.O ^.L.t+m+$+9 ( < s ;.6.D.`.H.s.+.< ( S B+G+G+G+D+a.j x+x+v ' B+D.0 ( 6 j.D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+d.P.Q.=+_+V.q.P.C+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+R.. . + G+G+G+@ . . v.G+G+G+G+ .. . [ G+G+G+G+v.. . @ G+G+G+i.. . . v+A+v+G+G+p+R.J . . . i.G+G+J J J *+G+G+G+G+G+> . + A+G+G+G+[ . + p+G+G+G+G+G+G+]+. . . E+G+G+G+G+w . . > E+G+G+G+G+G+p+G+G+> . . |.G+G+ .. . [ G+G+& . . w G+G+G+i.. . + E+G+ .. . [ G+G+G+J . . [ G+G+k+J J J v+G+G+[ . . >.G+G+G+G+p+. . . R.G+G+G+G+G+A+v+E++ . . J.G+G+p+. . . k+G+>.. . @ A+G+G+*+. . . G+G+G+& . . |.G+G+G+G+w . . w G+G+G+i . . w G+A++ . . i.G+G+G+G+G+A+v+G+[ . . [ G+G+G+>.. . > G+G+> . . w G+G+G+i.. . + A+v+. . . |.G+G+G+G+p+v+v+A+G+E+a+v.[ . . . k+G+J.J J |.G+G+v+. . . a+G+v.. . + v+G+G+A++ . . i.G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+g+V.[+Z.z.A.++s+z+++E+G+G+[+X.<+Y.:+'+B./+q+G+G+G+G+G+E+w+<+>+'+o+G+G+G+G+e.A.C+[+x.p.y.n.Y.Z.)+i+|+^.Y.n.W #+d+D O #+Z ] 5.L.e.q e.L.e.^.I `.k.) ( ..:.` +.+.v 9 9 < _ _ < L ;.a.h+~+g $+h+: ' t+E+$+e d d n h+E+z+s+7+:+B.B.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2+N X.[+e+Z.A.q.O.P.[+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+R.. . + G+G+G+& . . [ p+k+k+G+i.. . + ]+v+3+G+v.. . @ ]+a+i.@ . . i G+p+@ >.a+G+G+k++ . . J.G+G++ . . i.G+G+G+G+3+. . J G+G+G+k+. . i G+G+G+G+G+G+G+]+. . . E+G+G+G+G+]+. . . i 3+v+p+R.[ > G+G+> . . |.G+G+ .. . [ G+G+|.. . + R.E+3+$ . . w G+G+ .. . [ G+G+G+J . . [ G+G+*+. . . k+G+G+[ . . >.G+G+G+G+G+[ . . @ i.k+v+a+|.@ R.E++ . . .G+A+|.. . . k+G+]+. . . i *+J.[ . . . G+G+G+[ . . > k+k+k+G+w . . w G+G+G+i . . w G+G+w . . + |.k+v+k+i.$ i.G+|.. . . .*+v.& . . > G+G+i.. . + R.E+3+$ . . i G+G+i . . + i.k+k+i.@ *+v.& i.p+G+E+J.. . . v+G+J . . > G+G+v+. . . a+G+v++ . . i v+v+w . . + p+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+X.z.E+D+q+c.A.X.A.F.k+E+G+g+;+<+:+q+'+c./+w+G+G+G+G+w+<+)+>+g+E+G+G+G+G+G+z.q.A+D+E+w+j+A.e.y.9.q+w+'.j+Q W B.H r c @+].) { M e.y.e.9.N.^.^.f...( 9 l.s.s.s.s.l.|.>.f _ e D.K e < < | < K j.' Z 6.D+B+S v ^ 1 T _+;+;+;+'+9.0.E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+x.0.P.P.S.)+;+_+w+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+R.. . + G+G+G+J . . . . . i.G+3+. . . . . > G+v.. . . . . . . . @ k+G+p+. . . + + . . . > A+G+G++ . . i.G+G+G+G+w . . k+G+G+G+ .. . ]+G+G+G+G+G+G+G+]+. . . E+G+G+G+G+G+>.. . . . . . . . > G+G+> . . |.G+G+ .. . [ G+G+A+> . . . . . . . $ p+G+G+ .. . [ G+G+G+J . . [ G+G+*+. . . k+G+G+[ . . >.G+G+G+G+G+k+$ . . . . . . . . R.G+i . . . . . . . . . k+G+G+[ . . . . . . . . . G+G+G+>.. . . . . >.G+w . . w G+G+G+i . . w G+G+v+& . . . . . . . . i.G+v+@ . . . . . . . . > G+G+A+> . . . . . . . $ p+G+G+p+$ . . . . . . . *+v.. . . + + . . . J G+G+J . . > G+G+v+. . . a+G+G+v.. . . . . . . . i.G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+Z.z.G+A+]+i.i.I.F.9.(.f.E+s+/+<+<+E+s+x.Y.E+G+G+G+D+=+>+X.i+D+G+G+G+G+G+G+Z.n.q+G+G+G+G+V.)+Y.^.^.++c.N m h.L.5.b / M { { 1 n #+A.^.c.g+L.e.^./.{ v s.T.T.T.T.K.s.l.B 8 Y B+B+x+d+a.Y 1 < < < u 1 2+i+F.' ^ t e :+7+i+z+q+9.0.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+g+0.O.)+_+e+w+E+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+R.. . + G+G+G+R.. . + G+G+G+v+i . . . . v.G+G+i.@ . . . > G+v.. . + [ . . . [ k+G+G+A+w @ . . . . @ J v+G+G+G++ . . i.G+G+G+v++ . [ G+G+G+A+@ . & G+G+G+G+G+G+G+G+]+. . . E+G+G+G+G+G+G+R.> . . . . . & i.G+G+> . . |.G+G+ .. . [ G+G+G+A+J + . . . . i p+G+G+G+ .. . [ G+G+G+J . . [ G+G+*+. . . k+G+G+[ . . >.G+G+G+G+G+G+v+J @ . . . . @ i k+G+v+[ . . . > R.. . . k+G+G+v+[ . . . i v.. . . G+G+G+A+J + . . . |.G+w . . w G+G+G+i . . w G+G+G+A+>.$ . . . . + i 3+G+G+3+& . . + . .. . > G+G+G+A+J + . . . . i p+G+G+G+G+v+J + . . . . [ k+a+[ + . . . . $ i.G+G+G+J . . > G+G+v+. . . a+G+G+G+R.& . . . . & J.G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+F.0.t+J.i.J.R.*+*+x.c.c.v.q+X.{+{+E+j+x._+E+G+G+G+C+-+q.C+G+G+G+G+G+G+G+G+[+y.g+G+G+G+q+)+Z.z+2+c.^.^.m N ~+M y.e.q ^.{.1 v x+D+t.^.n.|+^.L.n.^.< X &+,+,+,+,+&+T.K.B 8 d 1+G+G+G+D+B+{.6 u 0 { < q 4.o.< b 2+s x+E+E+q+|+9.!.Y.z+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+z._+e+E+D+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+k+k+v+G+G+G+G+G+p+k+p+G+G+v.. . @ G+A+k+v+G+G+G+G+G+G+E+p+3+3+p+E+G+G+G+G+G+G+G+G+G+G+G+G+|.. . R.G+G+G+i.. . v.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+a+]+k+v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+A+3+*+3+A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+3+3+p+E+G+G+G+G+G+p+3+p+G+G+G+G+G+G+G+G+G+G+k+]+p+G+G+G+G+G+G+G+G+G+G+E+k+k+v+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+p+3+3+k+E+G+G+G+G+G+G+a+]+v+G+G+G+G+G+G+G+G+G+G+A+3+*+]+v+G+G+G+G+G+G+G+G+E+k+]+a+A+G+G+G+G+E+p+3+3+p+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+k+*+*+k+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+9.9.f.R.]+3+a+a+a+Z.3+]+G.B.9.c.[+h.h.X.7+E+G+z+'+>+V.y.q+G+G+G+G+G+G+G+G+w+n.x.D+e+)+)+E+G+G+G+G+E+v+^./ ) B 1+$.=.e.^.^./ Z B+;.<.e.y.m R h.Q c k.5+b+b+b+b+5+1+,+X _ :.|.r r r r r e e 1 p | < 6 ) ' ' < =.q.o Z.<+[+!.B.Z.P.O.o+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.. . @ G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+E+$ . $ E+G+G+G+& . @ A+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+c.h.F.]+3+k+k+k+k+Z.a+3+R.v.h.x.d.h.q+X.[+G+G+Z.>+Z.++L.g+G+G+G+G+G+G+G+G+E+z.z.{+)+S.g+G+G+G+G+G+G+D+e.M B B+B+L e e.n.e.e.] ~+Y H e.e.m v.T.i.m /.b+b+l+l+b+b+5+5+K < l.l.B Y &.;.I I g ).p 5 <.8.8.8.' ' : k+e 1.B+j+9.g+v+!.0.;+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+v.. . @ G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+@ @ i.G+G+G+3+@ @ >.G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+|+R.<+a+p+v+v+v+v+7+p+p+a+3+*+@+@+|+q+<+o+G+G+9+f+A+g+/+g+G+G+G+G+G+G+G+G+D+{+Y.9+f+z+D+G+G+G+G+G+G+E+/+/+g+y+`.}.1.B.X.Y.B.%.~.&..+V.B.I.1+b+b+(.d.|+r+u+u+r+r+r+r+1.&.,+&+s.t+D+f+++F.;.8.}.&.s+s+s+|+8.S ;.j.u.}..+|+++i+i+G.h.Z.w+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+*+[ [ i G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+", -"G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+"}; +/* columns rows colors chars-per-pixel */ +"598 332 256 2 ", +" c #010101", +". c #09090A", +"X c #100F0F", +"o c #0D0814", +"O c #120C1A", +"+ c #171618", +"@ c #29121C", +"# c #1E201F", +"$ c #170D26", +"% c #1C1427", +"& c #1E1132", +"* c #251B28", +"= c #371927", +"- c #38152C", +"; c #251738", +": c #282528", +"> c #35252C", +", c #2C2436", +"< c #362A36", +"1 c #393639", +"2 c #50172E", +"3 c #5B1530", +"4 c #701236", +"5 c #472837", +"6 c #4D2D3C", +"7 c #2B1946", +"8 c #331D53", +"9 c #362748", +"0 c #3C3445", +"q c #3B2557", +"w c #1F2761", +"e c #3D2363", +"r c #463947", +"t c #583747", +"y c #4D2E50", +"u c #463757", +"i c #752147", +"p c #772949", +"a c #6E344E", +"s c #442969", +"d c #4B3567", +"f c #50376F", +"g c #4A2A77", +"h c #543879", +"j c #3E4242", +"k c #484649", +"l c #504551", +"z c #564956", +"x c #585659", +"c c #674757", +"v c #6E4C5C", +"b c #574768", +"n c #5D5764", +"m c #5B4477", +"M c #675966", +"N c #71526D", +"B c #685777", +"V c #5F6263", +"C c #686669", +"Z c #706770", +"A c #766976", +"S c #787679", +"D c #910538", +"F c #A8013D", +"G c #921746", +"H c #B80345", +"J c #AD134E", +"K c #8E2E55", +"L c #AD275A", +"P c #B71F60", +"I c #953A63", +"U c #B13568", +"Y c #C70249", +"T c #D30B55", +"R c #E80356", +"E c #F30259", +"W c #FD005D", +"Q c #E4125E", +"! c #C4225E", +"~ c #D51963", +"^ c #FF0164", +"/ c #FF006B", +"( c #FC0A68", +") c #E91065", +"_ c #FA156B", +"` c #FF0B73", +"' c #FF1877", +"] c #CF306F", +"[ c #E92D76", +"{ c #FC257A", +"} c #974769", +"| c #924F6B", +" . c #8E536E", +".. c #AB4A72", +"X. c #B84877", +"o. c #A75679", +"O. c #886878", +"+. c #906A7C", +"@. c #C7477A", +"#. c #D5487C", +"$. c #E1467E", +"%. c #80817F", +"&. c #03398C", +"*. c #083D8D", +"=. c #073E91", +"-. c #512D87", +";. c #593787", +":. c #5C3595", +">. c #623C98", +",. c #5F34A0", +"<. c #663BA6", +"1. c #6E3DB7", +"2. c #144996", +"3. c #2E5DA1", +"4. c #654888", +"5. c #6F558B", +"6. c #694797", +"7. c #765898", +"8. c #59609E", +"9. c #7A668E", +"0. c #7E7A83", +"q. c #734BA9", +"w. c #7249AC", +"e. c #7956A7", +"r. c #7648B8", +"t. c #733FC1", +"y. c #7B47C7", +"u. c #7E47D0", +"i. c #FB2082", +"p. c #FF2885", +"a. c #EC3A85", +"s. c #FD3789", +"d. c #FF3A95", +"f. c #B55884", +"g. c #877886", +"h. c #977788", +"j. c #8C7092", +"k. c #AF6F8E", +"l. c #835AB5", +"z. c #8768A7", +"x. c #8E70AF", +"c. c #9877B8", +"v. c #A26FA2", +"b. c #C75788", +"n. c #D5518A", +"m. c #E84987", +"M. c #F8488B", +"N. c #EC528D", +"B. c #FE4798", +"V. c #E85897", +"C. c #FB559A", +"Z. c #CD6A97", +"A. c #E8649C", +"S. c #FF4BA3", +"D. c #E95CA4", +"F. c #FE58A8", +"G. c #FF5CB2", +"H. c #D477A6", +"J. c #FA66AA", +"K. c #E971AA", +"L. c #FF68B7", +"P. c #FC76B9", +"I. c #8757C8", +"U. c #864FD5", +"Y. c #8A55D7", +"T. c #976BCD", +"R. c #A47AD3", +"E. c #9153ED", +"W. c #9C65E7", +"Q. c #AB74F0", +"!. c #DD79C0", +"~. c #FE6EC1", +"^. c #FF79C6", +"/. c #6788B9", +"(. c #7A97C2", +"). c #878689", +"_. c #94848D", +"`. c #8D8B93", +"'. c #978896", +"]. c #979699", +"[. c #A78798", +"{. c #AC8D9D", +"}. c #9987AB", +"|. c #9D9CA2", +" X c #A799A6", +".X c #AF92AF", +"XX c #9EA2A7", +"oX c #A6A5AA", +"OX c #B3A4AE", +"+X c #ADACB2", +"@X c #B6A9B6", +"#X c #B3B2B6", +"$X c #B6B5BA", +"%X c #BABABD", +"&X c #C3849F", +"*X c #D08FB1", +"=X c #EC8ABA", +"-X c #CAA9BB", +";X c #B18FCF", +":X c #90A7CB", +">X c #B6AACD", +",X c #BCB4C6", +"XyX>X3XQ.d.d.d.d.d.S.G.G.~.~.^.7XtX7XtX7X7X^.^.~.~.G.~.D.].DXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X=XwXqXwXwXxXmXbXbXzXxX0X,X;X}.}.A U { s.s.s.s.s.M.M.C.C.$.*X*X=X6XwXwXwXwXmXwXVXVXVXSXKXUXUXKXwXwX=XA.&X$.o.vXGXPXGXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX .R E _ ' { d.B.C.G.J.L.L.J.L.G.G.F.L.=X*X-X@X>X.X;X3X;X;X;XS.i.d.d.S.S.S.S.G.G.~.^.7XtX7X8X8X7X4X~.G.G.~.G.D.m JXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX_.wX6X6XwXwXmXmXbXbXvXcXjX,X>XXXz.K $.$.s.{ s.M.M.C.C.C.C.A.$.=X=X6X6X6X6X6XwXwX6XqXqXaXVXKXKXKXmX6X*Xk.o.k.o.C PXGXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXhX X X{.O.m.C.F.J.L.P.=XP.^.^.^.L.P.4X5X-X@X.X}.x.;X;X;X;X.XS.d.d.d.d.S.S.G.G.~.~.^.tXtXtX8X7X7X^.~.~.G.G.G.D.}.JXJXJXJXJXJXJXGXoXnXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXJXJXJXJXPXGXGX{.6X6X5XwXwXxXmXnXbXbXMX0X0X0X,X>X9.} $.[ M.M.s.B.C.C.C.C.A.A.=X6X=X6X6X-X=XwX5X6X6XwXwXwXVXVXKXVXwX*X%.kXkXvXGXJXGXGXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX$Xo.A.J.P.=X6X4X=X4X6X4X4X6X5X-X.Xc.j.7.9.z.c.c..X3XF.d.d.d.S.S.S.G.~.~.^.^.tXpXtXtX8X7X^.~.G.G.G.G.f.NXJXJXJXJXJXJXGX .P L +.OXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHX@Xh.| gXJXJXJXJXGXGXPXoX=X6X=X*X5XxXmXnXbXNXNXNXMXMXyX0X>X9...n.m.{ M.B.C.C.A.C.C.$.A.=X=X=X=X*X*X*X*X=X=X5X6X6X6XwXaXVXwX{.nXKXJXJXJXJXPXGXPXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX[.H.=X=X6X6X6X6X6X8X8X6X5X.X Xx.9.4.5.7.7.7.j.>X5XF.d.S.S.S.G.G.G.~.~.^.tXtXaXpXqX8X8X^.~.G.G.G.G.h.JXJXJXJXJXJXFX .R ^ / / ) } X>X>X.Xv.{ s.s.M.C.C.A.A.C.C.$.#.&X#.&X+.k.h.o.&Xo.&X=X=X=X=X=X=XwXoXGXKXJXJXJXJXJXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXHX'.K.=X=X=X6XqXqX6X=X6X=X*X.Xj.m b f h h m 4.m 4..XwXP.G.S.S.S.G.G.G.~.7X8X0XyX0XyX0XwX5X4XK.Z.k.f.&XGXJXJXJXJXJXJXJX+XG H H T ( ( / / / / R .GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXO.p ] ' p.i.p.{ { ! K K Z FXGXPXGXGXGXPX$X=X6X5X0X-X-X9XmXPXLXFXMX9X9X>X;X@X;XN $.s.$.M.M.C.C.C.$.$.$.$.#.&X+.O.C O.v Z O.k.&X=X=X=X=X$.=XK.%.nXJXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzX+.C.J.=X4X4X6XqX8X8X4X=X=Xv.j.n n c y r y r r f m v.wX4XL.F.G.G.G.G.^.qXaX;XT.T.T.;X;X-X+.).|.$XzXJXLXJXJXJXJXJXJXJXGX9XK D D F T ^ ( ( ( / / J a OXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+Xa.i.i.i.i.' p.' { { P 6 gXJXJXGXGXPXGXPXGXjXoXoXOXg.g.OXxXFXBXNX9X0X9X>X@X>X>X .$.{ [ { { M.M.$.$.$.{ $.#.@.t V Z C C v C o.#.&X$.=X=X=X=X=X=Xk.%XPXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHX.X@.M.C.J.P.^.P.=X4X6X=XK.K.k. .B ).+.I i 2 5 5 5 - u z.4X4XP.P.J.L.L.L.tXZXR.T.I.T.T.;X;X/.GXHXJXJXJXJXJXJXJXJXJXJXJXOX| T ~ ~ G D J Q _ ` ` ( ( / ` ! cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJX .L ' p.i.{ i.' i.' { { { _ U O.vXPXGXGXPXGXGXPXGXGXJXJXvX).|.xXxXlX9X0X9X>X>X>X9X} $.[ ] ] ] $.[ $.] ] ] ] ! U U S C x O.S O.+.o.&XA.&X$.A.A.A.=XA.A._.GXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXvXx ~ { s.B.C.J.P.P.P.=XP.P.H.Z.v ].kXJXh.G p 6 2 2 2 5 q 5.H.4X^.P.L.J.G.=XwXR.I.I.I.T.1X1X'.%XGXJXJXJXJXJXJXJXJXJXJXJXJX> G ! ! ] G 4 J ~ _ ' _ _ / ` / ` {.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX X] P i.i.i.{ p.p.p.p.p.{ { _ [ ! t HXPXGXGXPXGXGXGXPXJXJXJXJX].9X9X0X0X,X>X3X>X>X>X| [ @.] U ! ] ] ] ] ] ! U L L K O.GXkX].S C O.o.o.k.#.Z.Z.Z.Z.N.A.C.X.v +XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX[.{.} ( ] ' p.d.B.F.G.L.J.P.J.J.D.C.n.k.kXJXBXO.a 4 3 5 = = u z.c.v.*XP.L.G.L.3X;XT.T.T.T.R.;Xj.%XJXJXJXJXJXJXJXJXJXJXJXJXJXJXM L ] ] U G G J [ { { ' ' ' ` ` ` o.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXJXJXO.d.L p.{ p.p.{ p.p.s.p.s.{ { [ L O.HXJXJXJXJXHXHXPXGXJXJXJXJXGX|.>X,X>X>X,XyX0X9X>X .#.@.@.U ......I ..U L L K .].GXGXzX-XS C C C | | O.| O.v | | o.n.Z.C.b.O.v -XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXnX..^ ^ / ( _ [ p.s.M.B.S.S.F.F.J.F.F.d.p.] t XXJXHX9XA a 3 2 < b x.;X;X}.H.J.D.c.c.T.T.T.x.T.c.0.vXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX).I U ] U G G ! a.s.{ { ' i.' ' ` N LXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX .d.a.p.p.p.s.s.p.s.{ s.{ s.[ [ @.].GXJXJXJXJXPXHXPXGXJXJXJXJXGXJX+X}.>X>X>XcXjXgX@XN #.#.....I K v K a v v v _.nXPXGX|._.k.%.O.C V v v x v v v V v v N.N.M.M.M.M.@.OXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX@.^ E W ^ ( _ J { p.d.M.B.B.B.B.B.S.d.' ' ` ) a V FXGXJXoXr + r 9.}..X;X;X}.c.c.c.c.x.x.z.z.9. XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X| X.U K G U [ a.s.d.s.p.i.' ' ' c JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXk.s.B.s.p.p.s.s.s.s.s.s.s.$.[ @.o.oXJXJXJXGXJXPXHXGXHXJXJXJXJXJXJXJXhX).}.>X>XkXX>X.Xc.x.z.z.7.5.5.).cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX } V.J.J.J.P.L.G.F.S.S.S.S.V.{.JXJXJXJXJXJXJX].OXwX0X>X@X.Xk.h. .} o.U f.n.V.F.F.G.L.J.F.S.M.[ ....j.FXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX#X].cX#XPXPXPXPXPXnXXXoX+XXX+XFXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXgX].PXPXPX].].GXHXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFXNXHXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX.XU J ~ ) ) OXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX#X+.+.C.J.J.K.K.=XK.H.H.H.K.H.&X*X*XH.P.P.^.^.^.6X*X#XGXHXJXJXJXJXJX+Xh.+._.OX Xn c {.-XKXIXIXUXUXUXIXIXIXmXZ HXGXJXJXJX{.D.G.F.S.F.S.S.S.M.M.n.n.n.V.V.V.H.H.h.c a v 1 `.+.HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX[.'.z h.4X4XH.b. .v I a a 6 5 v 2 3 I U ] U I 3 a *XlXJXJXJXFXA h. .t 5 K f.P.=X4X6X7X7X^.^.^.^.P.K.'.S JXJXJXJXJXJX]..X;X;X;X.X.X{.{.k.h.j.f.f.f.b.A.V.V.J.L.G.G.S.d.s.{ i.) +.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX].PX#XPXPXPXPXPXPXPXPXPXPXPXBX].|.JXJXJXJXJXJXJXJXJXJXJXJXGXGXJXJXJXJXJXJXJXJXGX$X+XPXPXPXPXPXcXS bXKXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNXJXJXlXOXh.'. X,XcXJXJXlXk.o.I L J J L U k.{.,XBXJXPXPXJXJXJXJXJXJXJXJXJXJXJXJXJXHXBXj.~ / ` / / / I BXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX-Xc m.C.C.J.J.J.K.K.K.=X=XK.*X*X=X*X*X=X=X4X7X7X8X8X6XmX#X+XHXJXJXJXJXJX`.Z 9X@XOXXX`.6 N {.wXSXSXSXSXAXSXSXKXKX].%XPXJXGX[.F.G.G.G.G.F.F.B.M.b.X.I I .A.A.K.K.K.=X5Xh. .b.F.m.].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoXP.6Xh.qXP.G.D.Z.f.I a a t i 5 k l 6 5 2 3 K K a f.K.Z.9XJXLX_.c.j.N 5 @.Z.*X8XqXqXaXaXaXqX8X8X4X5X;X,X@XXJXJXJXJXFX%.jX,X@X.X}.`./.t c }.iXsXMXpXCXVXCXCXxXxX|.BXLXv.G.G.G.G.G.L.F.F.F.C...K a 6 5 o.Z.H.H.H.P.P.L.F.d.p.{ } JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh.=Xk.AX8X~.S.S.V.Z.f.I a a a = 1 6 j 6 c a 6 6 c .+.Z.k.+X|.x.z.j.c n.P.4XqXaXVXAXVXVXSXVXpXpXwX>X>XMXCX{.nXJXJX].5.7.9.x.}.}.#X 6 o.f.Z.J.P.L.F.d.` ( E P '.LXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X+.h.CXaX^.~.G.S.F.A.Z.f.I a p > # 1 1 6 a c v c c v v .b.a 7.l.z.9.N L.7XqXaXSXIXIXIXUXIXIXSXZXiXyXyXZXPXKXC ,XS 5.4.5.5.9.`.|.>XXoXoX|.].h.k.Z.Z.H.H.A.J.J.J.S.d.i.` / / f.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXoXPXPXPXPXPXPXPXPXPXPXPXPXcXXXcXX0X0X 1 6 k c c k z c M .o.a T.z.z.5.b t H.pXVXSXIXIXSXAXSXSXSXZXZXuXiXCXIXIXM 6.4.;.m h b B 9.x.}.;X;Xc.x.}.}.g.A S g.{.&X*X=XH.A.L.J.F.d.p.i./ / f.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXoXGXPXPXPXPXPXPXPXPXPXPXPXPXS %.#XGXJXJXJXJXJXGX].PXPXPXPXPXPXPXPXPXPXPXGXoXkXPX].BXGXPXPXPXPXPXPXPXPXPXoXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXLXFXh./ / / / / / / / ` / / ` / / / / / / / / / / / / ` / / / / / / / K cXJXJXJXJXJXJXJXvX .R R E E W W W W E ^ / / / ` ] h.GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX[.d.S.F.G.L.J.J.=XJ.=X=X*X&X-X-X9XjX,XoX X>XwXVXSXVXVXCXCXCXZXZXsXiX;Xj.A S XX}.e.}.9.}.`.}.}.j.: 7.c.}.>X@X@X|.`.+.0.m m.G.S.G.G.G.L.G.J.J.J.F.N. .C v v l 5 | K.P.P.L.L.M.[ ~ L k.#XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXg.CXIXSXaXtXtX7X^.4X6X6X*X+.v a ).A 5 1 5 k k l l z z v N v 5.l.c.j.B u < r *XiXZXZXZXdXdXdXAXAXZXiXsXiXZXSXIXA ;.m h f d b 4.5.e.l.z.e.e.5.9.b x z l Z '..X*X-X*XK.K.J.J.S.d.i.` / o.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXcXJXJXJXJXJXJXGXhXkXPXPXPXPXPXPXPXPXPXPXPXPXnXXXGXJXJXJXJXJXJX$XcXPXPXPXPXPXPXPXPXPXJXPXJXJX%X].vX).FXFXGXPXPXPXPXPXPXPXGXoXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX .( / / / / / / ` / / / / / / / / / / / / / / ` ` / / / / / / / / / .LXJXJXJXJXJXJXJXvXI R R R E E E E E ^ ^ / / / / ( o. XDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh.B.G.G.J.L.J.P.J.K.=X&X&X_._.{.oXOX}.x.j.j.>X0XVXAXCXmXMXZXLXLXZXZXZXLXZX1XZ }.}.}.9.}.x.}.}.}.z d 7.z.9.M r 0.S Z B x N.S.G.G.G.G.G.J.L.J.F.F.C...l N M M : o.K.P.K.V.V.m.] I .c.}.].kXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXgX'.pXSXIXSXAXdXpXtX7X8XqXaX-X+.v C %.t t 6 j k l l k l z z t - b h.h. .v r , 5.z.;XrXrXuXuXpXuXuXpXsXuXsXuXsXSXSXn m 4.m h h f ;.4.6.q.6.6.;.m ).XX+XXX).k A ].OX-X*X*X=XJ.A.C.d.p.i.` I NXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX`.).].].oXkXBXkXoX).PXPXPXPXPXPXPXPXPXPXPXPXPXJX`.FXJXJXJXJXJXJX].PXPXPXPXPXGXPXPXPXPXPXFXFXFXFXBX_.).%XFXFXPXPXPXPXPXPXPXPX#XnXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXcXK / / / / / / / / / / / / / / / / / / / / / / / / / ` / / / / / / / ) zXJXJXJXJXJXJXJXJXh.Y Y Y R E E E E W ^ ^ ^ / / / / [ f. XDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh.S.G.J.J.J.P.J.K.K.Z.[.&X%.O.]. XS ).S 0.9.z.;X0XCXMXZXMXZXZXLXIXSXSXSXSXiX}.m 9.e.x.9.x.}.}.x.0.1 b n .XAXAX9.C M n l V.F.G.S.F.G.L.G.G.G.J.F.C.n.c : Z v 5 K.A.D.b.b.f...I v z 9.;XyX}.B V < y M h.O.N c z z 0 z.;X1XrXuXrXrXuXrXrXuXuXuXsXuXsXAXyX0 4.6.6.4.h h ;.;.>.>.;.;.m `.JXJXJXJXJXXXj g.oX9X-X*X*X=XJ.F.S.d.p.' a.#XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXXXJXPXPXPXcX+X+XhXFXPXPXFXFXGXFXPXPXPXPXPXPXPXGXGXXXoXJXJXJXJXJXJX].GXGXGXFXGXGXGXGXGXGXGXFXBXBXBXFXFX#Xk vXFXPXPXPXPXPXPXPXPXPXoXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJX", +"LXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX.XU / / / / / / / / / / / / / / / / / / / / / / / / ` / ` / ` / / / / / / n.JXJXJXJXJXJXJXJX%XL Y Y R R R E E W W ^ W ^ / ^ / / / ) ..'.GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX .F.G.J.J.P.J.J.K.A.Z.Z.h.+.V ].].JXJXJXJX`.5.x.>X,X0XyXZXZXLXZXLXIXSXZXZXZX>X7.m 9.e.x.x.}.}.9.b r .XSXSXIXLXlXn n l q d } V.A.G.L.G.L.G.G.F.F.C.B.p j Z v < a | | | N N a r < 0 5.x.;X1XyX>Xl z BXJXJXJXJXJXJXJXJXJXJXJXcX|.>XCX'.pXSXZXIXIXSXSXIXIXSXIXSXxXOX X'._.k 6 x l x z z k Z j.z.R.c.j.S Z N z u t d 9.R.1XeXrXrXuXrXuXuXuXpXpXsXuXrXeXz h 4.6.6.>.>.h h ;.;.;.h m C JXJXJXJXJXJXvX0 0. XhXxXwX5X=X=XP.J.S.p.i.` ].LXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXX0X9X@Xh.IXSXZXIXSXSXSXUXUXPXCX>X0XgX-X-X-X%.6 k z z k S >Xc.z.z.7.B M M M c z 0 5.e.;X2XeXrXuXuXuXuXtXuXdXuXpXuXQ.m m m ;.4.>.>.;.g f -.h ;.c /.JXJXJXJXJXJXJXJXk 9. X9XxXmXwXwX6X=XP.S.d.i.` h.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXkXXXPXPXPXPXPXPXPXPXPXJXPXPXPXPXFXFXFXFXPXPXGXGXFXFXnX%.FXJXJXJXJXJXJXkXGXGXPXFXGXGXPXGXPXGXGXGXFXBXnXvXcXvXnXBXBXFXPXPXPXPXPXPXPXPXPXPXS FXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHX| / / ^ / ^ ^ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / ( / {.JXPXGXGXPXPXGXGX| F F F Y Y R R R E E W W ^ ^ ^ / / / / / / / / L h.GXLXJXJXJXJXJXJXJXJXJXJXJXJX-XZ.L.P.L.P.P.P.P.P.=X=X&Xk.M 0 $XJXJXJXJXJXJXJX`.m 5.5.5.5.}.MXZXZXZXZXiXuXyX1X1X1X>Xm }.B B yXAXAXAXdXZXAXSXZXMX>Xx , 0 m 5.e.;X}.B f.F.G.S.S.d.B.a.U < C C A r r 9 < 9 9 q u d m 5.5.z.x.;X;XyXMXyXA 9XJXJXJXJXJXJXGXZ 9.}.}.}.'.j.n LXZXZXZXLXSXSXPXLXZXyX0XMXMXVXCXmX0X{.x j l M .Xx.z.7.4.m b b N n b y u z.c.R.1XeXrXrXuXuXpXdXuXdXdXsXrX6.h 5.>.m >.;.h h g e e q b XXJXJXJXJXJXJXJXJXNXV B j.>XjXxXmXwXwX8XP.F.d.p.i.b.LXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXKXzX%.PXPXPXPXPXPXPXPXPXPXGXGXPXPXGXGXFXFXGXPXPXGXGXFXBX%.FXGXJXJXJXJXJXJXJX+XcXGXFXJXPXJXJXFXPXFXFXFXFXnXnXcXcXnXnXnXnXFXGXGXPXPXPXPXPXPXPXhXV JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXOX) / / / ^ / ^ / / / / / / / / / / / / / ` / / / / / / / / / / / / / / / ^ ~ %XJXGXGXPXFXGXKX#XT F F F F Y R R R R W W W W W ^ ^ / / / / / / / / ` U ,XJXJXJXJXJXJXJXJXJXJXJXJX$XS.L.P.P.^.4X4X4X=X4X=X&Xk.M k BXJXJXJXJXJXJXJXJX`.B m m 4.7.yXZXZXZXiXuXuX2X1X1XeXyXj.M MXSXSXAXZXdXdXsXZXAXZXiX>X}.1 q f 6.e.c.;X>X}.N m.S.d.d.d.] L 1 Z 9.0.x n u u 0 9 u d h m 4.4.4.e.T.;XyXMXMXlXS nXJXJXJXJXJX0.9.9.9.9.5.B n x j.ZXiXMXZXZXZXZXMXyXyXiXMXCXAXAXwXwX5XOX5 t '.9.5.4.m f u u u b d u , m T.R.1XQ.2XrXuXsXsXuXdXdXsXuXrXT.d m 5.5.>.h h g s e q u A lXJXJXJXJXJXJXJXJXJXhX9.5.}.@XcXCXFXKXVXqX^.F.B.p.i.{.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXcX/.PXPXPXPXPXPXPXPXPXPXPXGXGXGXGXGXGXFXFXFXGXPXGXFXFX|.hXGXJXJXJXJXJXJXJXJXJX#XgXFXFXJXPXGXPXGXPXFXFXFXnXnXcXcXcXcXvXvXvXFXFXGXGXGXPXPXPXPXPXgX`.%XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLX} / / / ^ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / P } +.GXJXGXPXFXPXPXFXU H D D F F Y Y R R E E W W ^ ^ ^ ^ ^ / / / ^ / / / / / ] j.GXJXJXJXJXJXJXJXJXJXGX0XF.P.^.^.P.=X4X4X4X6X=X&Xh.Z k cXJXJXJXJXJXJXJXJXJXXXl b b 4.>XZXZXiXuXrX2X1X1X1X;X>X XIXIXSXAXAXdXsXuXpXsXZXZXiXyX;X`.9 f 6.x.;X;X.X}.c.a n.d.B.a.U p n A A 0.'.A z u r y d d h ;.4.4.4.l.;X>XyXMXMXxXlX).JXJXJXJXoX0.b B m B B n b l l ZXrXuXuXiXiXyX1X1XiXuXyXiXiXyXaXCXwX0XZ : b B m d f d q u y u u 9 m 7.T.Q.1X1XeXrXuXsXZXdXZXdXuX2XT.y.m m z.;.m g s e g n |.zXJXJXJXJXJXJXJXJXJXJXJXoXb 9.}.9XCXVXSXVXVXqX4XG.G.d.p.}.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX].JXPXPXPXPXPXPXPXPXPXPXPXGXPXPXGXGXGXFXFXFXFXBXGXFXnX).PXPXJXJXJXJXJXJXJXJXJXJX%XXXFXFXPXGXPXGXPXGXFXFXnXnXnXcXcXcXcXcXcXvXFXBXFXFXFXPXPXPXPXPXPX#XnXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX.X~ / / / / ^ / / / / / / / / / / / / ` / / ` / / / / / / / / / / / / ) O.XLXZXrX1X2X1XQ.R.T.T.z.9XIXSXSXAXsXsXuXuXuXuXuXiXiX1X>X>X0.9 m T.;X;Xc.c.x.A 5 X.a.a.U 5 B 9.9.`..X{.[.B r 9 q u b m h h 7.T.;X1XyXyXmXxXzX$X$XJXJXvX).9.b u d m b b 0 0 0 x.uX2XrX2X1X2X2X2X2XrX1XrX3XyXZXIXKXmXOX< A 9.u s d u 9 7 9 9 0 u z.x.R.Q.eXQ.eXuXsXZXZXZXAXAXQ.I.r.I.W.7.;.;.f s e s h j.`.hXJXJXcXlXJXJXJXJXJXJXJX`.m 9.+XxXCXVXVXVXVXpX7X~.L.G.f.MXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXJXPXPXPXPXPXPXPXPXPXPXPXPXGXGXGXGXGXGXGXFXFXFXvXvXBXoXvXPXGXJXJXJXJXJXJXJXJXJXJXJX#X|.GXPXPXPXPXPXGXPXFXBXC oX%XhXOXX>X7.g u u b b b 9.9.B n y K 3 6 z B 9. X.X[. .O.N .N 9 d f d 4.l.R.;X3XyXMXbXmXxX-X$XGXJX0.0.B d 9 q b b 0 0 9 0 0 rXrX2XeXQ.Q.eX2X2X2X2X2XuXiXZXUXUXKXlXk 5.5.B 9 q s q ; 7 9 7 c.;XR.1XeXeXQ.eXpXAXSXAXSXSXiXr.w.w.r.T.T.e.f q q y g f B .Xk.j.nXC C %XJXJXJXJXJXgX9.B ].-XxXaXaXpXqXaXpXtX7X~.L.j.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzXGXPXPXPXPXPXPXPXPXPXPXGXPXPXGXGXPXGXPXGXFXFXFXFXFXFXFXoXJXGXGXJXJXJXJXJXJXJXJXJXJXJXJXC GXPXPXPXPXPXPXGXFXFX].FXPXPXPXoXS %XcXcXcXnXBXFXFXGXPXPXPXPXPX$XXXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXGXU / / / / / / / / / / / / / / / / / / / / / / / / / / / ` / / / / ^ T I JXJXJXJXJXJXGXPXPXBXL R Y H F F F D D D F Y T T T Q R Q E W ^ ^ / ^ ^ ^ ^ ^ / / / / P cXJXJXJXJXJXJXJXJXJXJXO.P.4X4X4X4X7X8XqX8XqX5X*Xk.Z x hXJXJXJXJXJXGX0.V NX.e.T.R.;X;XyXyXxXbXwXwX=XOX+XB 5.b u 0 0 l q 9 7 , , 9 5.rXrXrXeXeXQ.2X2X2X2X2XrXiXZXIXUXPXMXl m 5.7.0 9 9 9 , % 7 , c.;XQ.1XQ.eXQ.Q.R.eXiXiXiXyXr.<.<.<.y.I.Q.1X4.7 q e s g f 9..Xk.j u b M ].JXJXJXBXZ b 5.}.-XjXaXaXaXpXdXaXtX7X^.L.'.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoXPXPXPXPXPXPXPXPXPXGXGXGXFXFXFXFXGXGXGXPXGXGXGXFXGXFXGX|.GXPXPXJXJXJXJXJXJXJXJXJXJXJXGXXXGXGXGXPXGXFXFXGXFXFX].GXGXPXPXPXBXC %.cXcXvXnXBXFXGXGXGXPXPXPXoXFXoXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX X) ^ / / / / / / / / / / / / ^ ^ / / / / / / / / / / / ` / / / / ( R T I oXJXJXJXJXJXPXGXGXo.R Y Y Y Y Y D D 4 D D F Y Y ! Y Y ! T Q ( ^ ^ ( ( ^ ^ ^ ^ / / / / {.JXJXJXJXJXJXJXJXJXJX|.K.^.4X7X7X8X8X8XqXqX6X*Xk.O.x 0.NXJXJXJXcXS V z x k.7X8XtXyX0X;Xu.T.I.Y.Y.T.Y.e.h c.uXuXrXrXrXQ.Q.Q.rXrXQ.rXeXrXiXyXMX>X7.4.4.4.B B M n 0 < A '.j.g.O.b '.}.B 0XUXUXUXSXVXVXc.l.l.l.I.T.R.R.1X0XyXxXmXmX-X,XXX9.b b d q 9 , , , : ; , , 9 ;XrXrXeXeXeXrX2X2X2XrXuXsXSXUXUXIXMXf h ;.5.m ; ; , ; + % ; ;XR.Q.Q.Q.Q.Q.T.I.t.t.l.r.r.<.<.<.y.T.Q.2X2XeXy e s s s f m j.n 0 b 5.5.B ).oX#Xx l 4.9. XX3X-X@X9XkXb k d b q 0 9 , % , , , , 9 0 5.rXrXrXrXrXrXQ.rXrXuXiXAXIXIXLXyXg s h ;.4.9 % , 9 ; ; % b R.R.W.W.W.E.Y.U.U.u.y.y.t.1.t.Y.Q.Q.rXuXuXdXiXz 8 8 q q d ; 0 Z g.}.|..X}._.A B Z j.}.;X,XMXmXAXSXAXAXAXaXtXH.9XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.PXPXPXPXPXPXPXPXPXPXPXGXBXvXzXkXzXnXnXGXFXGXFXFXPXPXPXPXPXPXPXoXJXJXJXJXJXJXJXJXJXJXJXJXJXXXGXFXFXFXFXFXFXFXFXgXFXPXPXPXPXPXPXPXPXPXvXnXBXFXGXGXPXJXPXPXPX].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX9X~ / / / / / / / / / ^ ^ W W E E E E E E E E E R R ^ / / / ` / / / ( ( _ ( ( ^ _.JXJXJXGXGXOXT Y Y Y F H F F F D D c OXJXJXJXJXJXBXC i J ! ) _ ' ' ' ' i.i.` ` i.X.cXJXJXJXJXJXJXJXJXJXJXzXk.7X8XtXtXaXaXmXjX$X].g.S C Z '.OXOXOX X).Z x M aXmXwXyXR.R.Y.Y.I.I.Y.T.R.Q.;X7.f d 9 w.Y.Y.W.W.E.Q.Q.Q.Q.uXuXuXiXsXyX;X;XT.;X;X;X+XOX{.&Xk. .u S g.].S +XIXUXUXIXLXCXyX,X}.z.7.l.e.r.e.e.e.l.T.c.x.j.|.GXBXj 0 0 q q 9 , , , * , , , , u d z.uXuXrXuXrXrXrX2XrXuXZXZXiXyX7.e s g ;.;.7 7 ; ; ; ; & ; h T.W.W.E.E.W.E.U.U.U.y.y.I.U.W.Q.Q.Q.uXsXAXyX9 8 q q y f d , 0 n 0.x.;X}.j.V x x B 9.}.}.>XyXMXSXSXIXSXdXtXh.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.PXPXPXPXPXPXPXPXPXPXGXFXnXzXkXhXoX).oXnXnXBXBXPXPXPXPXPXPXPXPX%XnXJXJXJXJXJXJXJXJXJXJXJXJX|.FXFXFXFXFXFXFXFXzX].PXPXPXPXPXPXPXPXPXPXvXFXFXGXGXGXJXJXPXPXPX|.BXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX'./ ^ / / ^ / / / / / ^ W W W R R Q Q ~ Q ~ Q E E R ^ / / / / / / ^ ` ' _ ` ( / +.PXJXJXPXPXoX! Y H Y F F H Y F F v gXGXJXJXJXJXJXJXvXM 4 L Q ' ' i.p.p.i.i.i.i.i.p. XJXJXJXJXJXJXJXJXJXJXJX_.8XtXaXaXVXVXBXxX%X|.`.S A C +.h.[.'.g.Z n k z mXCXMXeX1XR.R.Y.u.I.Y.W.R.1Xx.j.m s e 9 >.y.y.I.I.l.Y.T.Q.Q.rXyXsXsXyX;XR.T.R.}.;X.X9X0X5X=XZ.O.r z Z j.5.MXSXSXZXZXiX>X;Xx.7.5.7.6.q.6.;.;.4.h f l Z FXJXJXvX0.b 0 0 9 , , , : , , 9 9 u 4.N 5.yXuXuXuXuXQ.2X2XrXuXeX1Xe.q q d h 4.h 9 ; & ; & & ; O % ;.I.Y.E.E.E.E.E.E.U.U.Y.Y.W.Q.Q.eXuXuXdXsX;X; q q e s h 4.z., 0 k B C Z %.]./.k l k n z.c.;X>XZXIXUXSXAXwX+XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.PXPXPXPXPXPXPXPXPXPXGXBXcXkX+XS hXGXhX).].vXnXGXPXPXPXPXPXPXPXGX#XJXJXJXJXJXJXJXJXJXJXJXGXzXoXGXGXGXFXFXFXFX%.GXPXPXPXPXPXPXPXPXPXPXGXFXGXGXGXPXPXPXPXPXPXGX).JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh./ / / / / / / / ^ ^ W W W W E R Q Q ~ ~ ~ ~ ) E E ^ ^ / / / / / ( ' ' ' ' _ W h.PXJXJXGXGXGX+.Y Y H F F Y F F D v PXGXJXJXJXJXJXJXJX].4 G ! { ' i.i.p.i.d.i.i.i.i.}.JXJXJXJXJXJXJXJXJXJXJXXmXKXqXwX{.A r u M b ;XuXiXiXuXyX;X}.z.5.5.6.6.6.;.e 7 9 q < %XJXJXJXJXJXJXcXx b q 0 9 , * , 9 0 u m 7.z.5.r R.eXuXpXuX1XQ.1XQ.R.I.:.h m 6.e.z.x.0 ; % % $ % ; 7 % 7 <.l.U.E.E.E.E.W.E.E.W.Q.Q.Q.rXrXuXsXsXsXf 7 q s f f h ;.9.].NXX>X}.9.C ].FXnXzX+X).S /.cXj.1XyX1X1XW.Y.T.Y.W.R.eXVX@Xz.m b f g ;.h 9 g r.y.r.r.r.r.Y.Q.ZXAXyX;XR.l.l.e.l.x.}..XyXHXKXmX@Xg.n 0 u b 4.;XeX2X1X;XR.x.z.5.5.6.;.;.e 7 ; q g h `.JXJXJXJXJXJXkX9.T.z.e.d , , , 9 u m 5.j.z.j.A q l.l.R.eX;XR.T.T.I.Y.W.;XT.T.;XyX>Xj.* $ $ $ & ; ; % 7 q r.y.U.Y.E.E.E.Q.Q.Q.Q.Q.eXrXuXpXsXsXyX, 7 s f 4.4.f h v DXJXJXJXJXJXJXhXN k 0 k b Z j.c.;X>XZXZXSXAX|.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX$XcXPXPXPXPXPXPXPXPXPXPXFXcXkX).FXGXGXGXGXPXGX].vXFXPXPXPXPXPXPXPXGX+XGXJXJXJXJXJXJXJXJXJXJXGXJX%XX}.}.'.OXJXJXJXJXJXHXGX`.;XR.1XW.W.W.W.Q.Q.eXmX0X'.B d d d s ;.;.;.- w.y.y.y.y.y.l.yXVXdX0X;XT.l.e.r.e.z.T.c.;XyXKXCX,Xz.B u 9 0 u T.1X1XR.R.T.l.7.5.4.4.h g q ; ; 7 e h 4.,XLXJXJXJXJX0.e.e.e.7.4.q , , 9 u m 5.z.z.}.}.y l.l.l.l.T.c.y.y.W.R.2X2XeXyXyXiXyXA & $ * & ; 7 9 o & 7 -.u.Y.W.E.Q.Q.Q.Q.Q.Q.^.rXuXpXuXuXeXl.l.u e f ;.;.f d /.JXJXJXJXJXJXJX).l 1 k n 0.9.x.}.;XyXZXAXSXAX].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXPXGXBXcXhX].GXPXGXPXGXPXPX].vXFXPXPXPXPXPXPXPXGX+XGXJXJXJXJXJXJXJXJXJXJXJXJXPX].FXFXFXBX).nXFXFXBXFXGXPXPXPXPXPXPXPXPXPXPXPXPXPXJXPXPXPXPXPXPXPXFX).GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X) / / / / ^ ^ W W W W E R R R R T Q Q ~ [ [ [ [ [ [ { _ ( ` / / ` ` i.' ` ` _ ) |.JXJXPXGXPXoX! F F F F Y Y H F F .GXJXJXJXJXJXJXJXJXlX6 K ! [ { p.' i.p.d.d.d.p.p.`.JXJXJXJXJXJXJXJXJXJXJX$XbXUXUXUXIXLXiXiXyX1X>X;X0XmX_.JXJXJXJXJXHXPXzX9.R.2XQ.W.W.Q.Q.eXaXAX0Xg.M d f s d -.;.;.8 I.U.U.U.y.U.y.yXSXpX1Xc.l.6.6.6.6.6.7.j.x.;XMXsX>Xj.5.q 9 , 9 ;X1X3XR.T.c.z.z.5.m m g e ; $ ; 7 e s -.9.GXJXJXJX|.5.6.7.5.4.4.b q , & 9 m 5.7.j.x.h.q l.Y.l.I.I.y.y.Y.R.1X2XZXiXZXZXZXyXl ; $ & & ; 7 % $ $ & 8 t.W.E.Q.Q.Q.Q.Q.Q.rXuXrXuXuXuX3XT.l.l.c.u g ;.h e b vXJXJXJXJXJXJXJXn < u b 5.z.x.}.}.1XyXCXAXAXAX'.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXGXFXBXzX%X#XGXGXGXGXPXGXGX#XgXBXGXPXPXPXPXPXPXPX%XvXJXJXJXJXJXJXJXJXGXGXJXJXJXoXnXBXnXnX|.FXBXnXBXBXFXPXPXPXPXPXPXGXGXPXPXPXGXPXJXPXPXPXPXPXPXPXPXGX).FXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXNXU / / ^ / ^ ^ W W E R R R T R T R Q ) Q Q Q [ [ [ { { ' _ ' ' ` ` ` ` ` ' ` ' _ @.gXJXJXJXcX| T H F F F H F H H F D @XJXJXJXJXJXJXJXJXGXM p L ] [ { i.i.i.p.d.p.d.s.f.FXJXJXJXJXJXJXJXJXJXJX#XbXUXUXUXIXZXiXuXrXyXyXyXsXpX.XhXJXJXJXJXHXHXHXO.T.2X2XQ.Q.Q.2XiXAXSX3X9.m f f d g h ;.;.e E.E.U.U.U.I.r.CXSXsX;Xx.q.6.;.4.6.m , < 5.c.3XyX;Xz.5.q , $ , >X1X1XR.T.c.z.7.N b d s 7 $ & 7 e s ;.;.;.).JXJXBXB ;.4.4.4.;.m e e q , 9 h m 5.5.0.O.d Y.T.T.R.W.Y.T.Y.W.2XuXZXZXSXIXZXyX; ; ; $ & ; ; O O . $ 7 i E.E.Q.Q.Q.Q.uXrXrXuXuXuXuX1Xq.>.r.l.T.R.s ;.g e S JXJXJXJXJXJXJXgX0 r b 5.x.c.c.R.;X1XiXNXSXAXAXh.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFXXXPXPXPXPXPXPXPXGXFXFXBXnXkXoXnXGXPXGXPXGXPXPXnX].nXFXGXPXPXPXPXPXPXBX].JXJXJXJXnXXX.h ;.h u ; < f 7.;X1XR.l.4.q ; $ : }.>X1X;X;Xx.e.5.b u 7 7 & 7 8 e -.;.6.6.6.u vXJX0.d h h ;.m e e e b e e , q 4.m b b k 4.I.T.R.Q.W.T.W.W.Q.eXuXZXIXIXIXiX9.7 7 & $ $ & ; o . X . $ 7 q.W.Q.Q.Q.Q.Q.eXeXeXeXyXR.:.;.,.r.I.T.R.h ;.s m `.JXJXJXJXJXJXJXV 9 b 4.x.}.T.;X;X1XrXMXSXSXSXAX`.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX].JXPXPXPXPXPXGXGXFXnXnXvXcXkXXXFXJXGXJXJXJXJXPXGX).cXnXBXGXPXPXPXPXPXPXV JXJXGXkXC V ].].nXnXnXoXx V S kXGXFXBXnXvXzXcXcXnXFXFXGXFXGXGXGXBXFXFXFXFXFXPXPXPXPXPXPXPXPXPXPXPXGX].PXGXPXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXA / / / / / ^ W W E R R Y R Y F Y T T T T T ~ Q [ _ [ _ { ' ' ' _ ' ' ' i.i.i.i.d.i.B JX}.! T Y J H H J H F H H Y H {.GXJXJXJXJXJXJXJXJXJXZ K L ] [ { ' ' p.p.p.d.p.i.j.CXJXJXJXJXJXJXJXJXJXX}.b DXJXJXJXJXJXJXJXj.2XuXuXrXrXrXrXyXeX;X7.h d s s s g h ;.h E.U.u.u.I.y.r.yXuX;Xc.<.>.:.h 4.h q , , d 7.T.1XR.l.m q ; + : 0 @X.X}.9.5.B u , ; 7 7 7 e -.:.:.>.q.l.e.s |.JXb s f h h f f e e e f e m m m b f l , r.T.1XeX3X3XQ.R.Q.2XrXuXsXSXSXZX>Xq 8 8 7 7 $ ; ; . . o X . O 7 W.Q.R.W.T.T.l.e.q.>.h e g :.<.l.T.Q.R.y -.e h ).JXJXJXJXJXJXkX9 l 9.x.;X;X;X;X1XeXiXZXLXIXSXCX|.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX#XgXPXPXPXPXPXPXPXGXFXvXcXzXkXgX+XvXJXPXJXJXJXJXGXPX].zXcXvXFXPXPXPXPXPXPXcX).XXoX%XGXGXFXnXnXnXnXoX%.nXGXGXGXFXBXvXzXkXhXkXzXcXBXBXFXFXGXGXnXnXnXnXBXFXGXGXPXPXPXPXPXPXPXPXPXPXvXoXPXGXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX XJ / / / / / ^ ^ W W E R Y R Y F F J D D D D J ~ Q _ _ _ _ _ ` ' ' ' i.i.i.i.p.d.d.U Z I ! J J H H Y H H H H H Y K jXJXJXJXJXJXJXJXJXJXJXzXM K L ] ) _ ' ' i.i.i.i.i.U cXJXJXJXJXJXJXJXJXJXGX XUXIXIXZXsXiXuXrXuXuXiXyX;X}.b ).JXJXJXJXJXJXGXj.iXsXsXuXeX2XeXeX1X1Xc.5.h g s s s f s q.E.U.U.U.y.y.y.T.1XR.x.<.<.;.>.6.;.y 9 ; h e.T.1XR.l.4.q * O % : < B B l 9 , ; 7 8 8 e g -.:.:.>.w.l.l.l.h XJXb q u s b e e q e b e m ;.;.m f d b % x.T.yXeXeXeXeX1X1XeXuXiXZXZXsXyX5.8 8 e 8 8 7 & ; X o o + O o o 7.T.T.5.q & & 7 7 ; 8 s -.:.<.r.Y.Q.R.8 s 7 s 9.GXJXJXJXJXJXC , m `.>X>X>X>XyXuXZXZXZXIXIXSXOXFXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX`.PXPXPXPXPXPXPXPXGXBXnXcXkXkXkX.6.7.4.u d u 4.l.T.R.R.l.4.q o % + % : 9 0 + 8 8 7 7 q e -.-.:.<.<.w.w.r.r.q.6.oXJXz 7 9 q q e q q e e f -.m ;.m e b b u T.;XmXwXpXuXyXyXeXuXuXiXsXyXeXc.7 7 8 e -.g e & $ % X o O o O $ % 0 O o O $ $ & ; 7 8 g ;.>.1.l.T.R.T.9 ; 7 e 5.+XJXJXJXJX$Xk q 9.>XyXyXiXiXuXiXZXLXPXUXIXSX XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXXXnXPXPXPXPXPXPXPXPXGXFXnXcXzXkXcXkX/.#XzXcXkX.q.7.4.5.6.6.q.I.T.R.T.l.4.7 o # # # , d 9.n 8 7 7 8 e g -.,.<.w.r.w.1.6.6.w.6.'.kXk & 7 9 9 8 8 9 q e e ;.e m -.m f x 9.c.;XwXaXaXsXZXiXiXuXiXiXyX;Xx.9 7 7 e -.-.-.g e o $ X X X o X + $ ; O o o $ & ; 7 e h :.6.l.l.I.T.T.f $ & 7 8 4.j.JXJXJXzXS ; m }.yXNXNXZXZXiXZXLXLXUXUXIX XGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXGXFXBXcXkXgXcXBXBXkX#XoX+X+X].gXGXGXzXoXcXBXPXPXPXPXPXPXPXPXPX].BXnXnXnXnXnXvXx cXGXGXPXPXPXPXoXV hXhXgXhX.h g s s e Q.Q.E.E.E.Y.U.Y.E.W.E.U.y.1.q.q.l.7.q.l.r.l.T.R.T.l.e.h % % : : , b b 9.V 7 7 8 e -.>.w.w.r.r.r.w.q.<.w.<.6.5.).0 % % ; 7 , ; ; 9 e e e -.;.;.;.h z `.x.c.5X8XpXpXmXyXyXyXlX>X}.b & ; 7 7 s :.>.:.:.e % O O o + + o o x.;XQ.h 7 s h -.:.w.r.I.T.R.Q.eX3X3Xj.; ; ; 8 m ).JXJXcXC : q 9..XMXZXZXZXZXZXSXZXZXSXZX.XnXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXPXGXBXcX].C %XcXnXBXBXBXcXhXhX].#X+X%XC %XFXPXPXPXPXPXPXPXPXPXoXnXnXvXvXvXnXvX%.oXGXPXPXPXPXPXPXcX%.).hXkXkX`.PXGXGXnXkX.1.w.<.-.8 o O + + % $ 9.LXZXyXR.q.r.l.I.T.W.W.W.1XeXuXZXAXAXVX*X, & 7 f ].JXlXx O , d 7.}.yXZXZXZXZXZXZXuXrXrXeX|.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXPXGXFXFX).cX%.+XcXcXvXcXkXhXoXV GXFXgXgXFXPXPXPXPXPXPXPXPXPXGX].bXbXnXnXvXnXnX#X_.GXPXPXPXPXPXPXPXPX].kXkXgXoXGXGXGXJXGXPXPXGXcX.w.r.r.r.y.r.y.r.y.r.r.w.<.<.; , : % % + O O O 7 d g -.;.>.>.4.h u u n : A j.9.5.N u 9 , & $ o $ O & 7 8 s >.r.r.r.>.s $ + + $ % 0 JXUXUXZX1X;XR.R.Q.Q.Q.1XeXuXsXZXIXIXIXAXaX!.9 ; u XXgXk $ $ q m 9.;XMXCXZXZXZXiXeX1XR.2X.XvXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXPXPXPXPXPXPXGXoXPX#X].kXkXkXhX|.`.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFX].cXnXnXvXnXnXnXnX%.FXPXPXPXPXPXPXPXPX].kXzX].BXGXPXPXGXXX).|.+XvXcXkXzXvXnXFXGXPXPXPXPXPXGX%X].kXFXgXzXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXkXK ^ ( ^ ^ ^ W E Y D |.JXJXJXJXJXJXJXJX_.[ P J 4 H Q _ i.p.p.d.d.S.S.S.S.S.S.S.S.S.d.i 4 i i p K G K K L U P U O.JXJXJXJXJXJXJXJXJXJXS p U [ a.[ [ a.[ s.s.s.p.d.d.h.JXJXJXJXJXJXJXJXJXJXJXJX`.sXuXsXuXuXuXrXuXuXrX1X;Xe.m q 9 0 ].DX/.yXyXeXQ.T.U.U.I.y.I.Q.eXT.7.h f s s e e u Q.Q.Q.E.E.Y.E.E.E.E.E.E.U.t.1.<.1.1.w.I.W.Q.R.6.- . o % : z V z l m m ;.>.6.w.w.r.I.I.r.I.I.y.I.I.y.I.1.1.<.9 , : % + + O X + q e -.;.:.4.>.4.f 9 , 9 0 j V z z 0 , % + O O & & $ O ; 8 e <.w.r.w.,.e & X % % : b PXUXUXIXZXrXQ.eXeXQ.eXeXrXiXZXSXIXIXSXAXpX5X; ; l nXn % $ % 9 d 5.}.yXyXiXZXZXuXyX1XW.2X;XhXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoXPXPXPXPXPXPXPXPXPXPXPXPXPXgX%XJXJX.>.<.<.r.I.Q.W.W.I.r.1.>.; 7 9 7 % + # q g -.g g ;.h f q 7 + % , 9 9 0 x l 8 q s g d 9 , $ O o & 7 e :.:.:.:.g q 7 O % , + q }.MXSXIXIXAXsXuXuXrXrXrXuXsXSXSXSXIXAXaX5X% % 7 f 4.4.9 * 7 7 q h 5.e.c.1XiXAXZXsXuX2XQ.uXOXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXvXXXPXPXPXPXPXPXPXPXPXPXPXGXnX|.JXJXJXJXJXJX.>.>.<.1XZX0Xc.6.d s g g g s e 4.I.Y.I.r.<.,.E.E.Q.Q.E.E.U.U.U.U.U.U.U.r.w.q.* o . . X % % m >XZXZXyXeXR.T.T.I.w.<.g g h >.<.T.2X2XQ.T.I.w.>.e 9 e e q , ; e s s e d s e q 8 & + % ; 9 9 # ; e s 7 u b b u 0 & O o $ 7 e g -.-.g e 8 8 & % % & q 5.;XMXZXZXZXdXsXuXrXeXrXuXAXIXIXSXSXdXaX> O q g ;.7.x.1X0 7 9 q d h 6.x.;XuXZXZXAXZXrXeXuX0XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX%.JXPXPXPXPXPXPXPXPXPXPXFXnXkXXXJXJXJXJXJXJXGXXXPXPXPXPXPXGXGXPXGXGXPXGXGXPXGXGXFXGXgXk %.].gXnXvXcX.uXiX1Xc.4.s s g g g g e s u 7 h <.;.g 1.Q.Q.E.E.E.E.E.E.u.u.t.t.<.>.m $ o . o ; 9 * 5.MXLXZXMXyX;XT.I.l.q.:.s 8 e g >.T.1XeXQ.R.T.q.6.e , s e e q q e e e s e e q 7 ; % + % * 7 : % e g 9 n l 0 7 , : % O o ; 7 q q s g e q q 7 $ O , % 9 d 4.l.;X1XrXeXQ.Q.Q.Q.2XuXsXSXIXIXVXaXN 7 8 e h 4.e.T.1X9.9 q 9 s d 4.7.;XrXZXAXAXAXuXrXuX0XFXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXcX%.GXPXPXPXPXPXPXPXPXPXPXFXFXcX.>.y o o ; 7 ; * 9 }.yXiXiXeXR.T.l.q.>.g 7 & & 7 s 6.R.1X1XR.l.7.;.q % q q q 8 8 e e e e e q 8 7 $ . + # % ; O e -.s 0.|.}.9.5.5.7.e.z.z.e.z.9.9.B d q 8 8 ; o , ).k ; s 4.6.w.l.I.I.I.I.I.I.T.;XyXZXCX,XB h g s s f ;.e.;X;XyXc.q d q 9 u m 7.}.yXAXSXAXAXsXrXsXSXcXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzX%.PXPXPXPXPXPXPXPXPXPXGXFXBXbXcXoXhXJXJXJXJXJXPX+XkXPXPXPXPXPXGXGXGXGXGXPXPXPXHXFXzX%.FXPXPXPXPXPXPXPXPXPXzX#X).`.Z OXX.Q %XBXoXJXJXJXJXGXPXBXGXJXJXkX#XzXkXkXkXkXkXhXkXkXcXBXPXPX+XGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXo.E W W ^ ^ ^ W +.GXnXo.! [ U o.$XGXJXJXJX-X@.J 4 i i G 3 L ] m.s.M.B.B.B.d.d.S.d.d.d.d.d.S.d.S.p a I I } I .I o.o.+._.JXJXJXJXJXJXGXJXS i U m.F.C.C.F.F.F.G.G.G.G.G.G.j.KXJXJXJXJXJXJXJXJX+XUXSXZXuXrXrX^.~.F.b.I N M m 4.4.m e 0 , # l 0 q 0 , 7 , 7 4.2XyX1XI.q.;.-.g e e 8 8 8 ; O X # lXx l.Q.Q.Q.Q.E.U.u.t.1.,.:.:.:.>.:.-.7 ; 7 7 ; & $ m x.;X;X;XR.x.l.>.f ; $ O o O & 9 4.c.R.T.e.5.b 9 9 q 7 7 8 8 q q q e e q 7 & . X + + # + e ,.-.n 9XyXlX>X;X;X1X;X1X;X;X;X;X>XyX>Xz.f 7 $ O 0 FX.g g h -.g g g 5.c.;XZXZXx.d d u 9 9 d B }.yXCXSXSXAXdXuXsXSXkXJXJXJXJXJXJXJXJXJXJXJXJXJXJX).PXPXPXPXPXPXPXPXPXPXPXPXFXnXcXkXXXzXJXJXJXJXGXGX].GXPXGXPXPXPXGXHXPXHXHXnXnXPXHXnX].PXPXPXPXPXPXPXPXPXPXPXPXPXGX-XU E ~ k.zXnXS gXgX.<.6.>.h h ;.6.6.;.7.e.;XZXIXIX4.0 u u u 0 u 9..XMXKXIXIXIXAXsXdXIXkXJXJXJXJXJXJXJXJXJXJXJXJXhXzXPXPXPXPXPXPXPXPXPXPXPXPXPXGXFXvXkX#XgXGXPXGXGXPXvX+XJXPXPXPXPXPXPXPXPXJXPX-XwX%XFXcX].nXPXPXPXPXPXPXPXPXjXb.~ ) ] -XFX].kXhXkX).PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXvXS k +XBXGXGXPXXXzXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX#XG / / ^ ^ ^ W W R 6 ! K K p p p p | FXGXGXGX| Q J G G G 4 i 2 U a.a.m.M.B.B.B.S.B.S.S.d.d.S.S.S.S.G.G.G.3 a a a I } } .O.g.JXJXJXJXJXJXJXGXS 2 K U m.C.F.F.G.L.G.G.~.G.~.L.f.GXJXJXJXJXJXJXJX.;.f s 9 ].JXNX5.Y.W.Y.y.w.<.-.g g g s g s -.;.4.;.h w 8 ; & O O 7 ; & 8 m e 7 o o o $ & 7 e e -.-.e 7 & ; 7 q e d s d q ; ; 7 8 e e 8 7 $ O * * ; g :.:.-.-.q >XMXiXsXsXuXsXsXsXZXsXZXZXSXIXIXIXIXIXLX0 s 0.JXJX< . & 8 g :.,.<.<.,.<.,.<.<.l.l.l.7.q.6.7.j.j.}.;XyXZXUXSX9 9 u u u 0 n g.OXxXIXUXUXUXuXsXdXCXgXJXJXJXJXJXJXJXJXJXJXJXFXoXPXPXPXPXPXPXPXPXPXPXPXPXGXGXGXnXcXkXgXoXGXGXGXPXXJXJXJXJXJXJXJX#XUXSXZXdXsXdXAXAXaX*Xv. .5.5.h 4.4.h d u 9 % 0 0 j 0 0 0 # 6.Y.W.Q.W.Y.U.y.y.r.r.w.w.w.>.h s 0.JXJX5.r.U.y.r.:.-.-.e e s s e g e.T.R.R.}.x.d 7 & O O & * ; ; % , : O o $ 8 e -.-.:.:.:.h e 7 7 8 8 q e e q 9 & & 7 8 e e 8 7 o * X q :.-.-.-.g g q >XZXiXiXuXuXiXsXZXAXSXZXSXIXIXIXIXIXIXUX>X, j nXhX# o 7 8 g -.:.,.,.<.,.,.<.l.I.T.T.l.l.x.x.h.}.>XiXLXIXUXg.& , y u u k B _.@XmXKXUXUXSXsXsXAX9XvXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXPXPXPXPXPXFXnXFXFXnXcXkXgX].PXPXGX).gXPXPXPXPXPXPXPXPXPXPXLXPXgXFXOXwXOXkX%X].PXPXBXY o.@.^ T &XBXPXPXPXHXBX).gXhXoXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXoXPXPXPXPXGXS GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXPX0XD / ^ / / / ^ W E T | %.a K K p 6 p p O.FXBX| Q Q ! L G 4 4 3 3 2 b.m.N.C.S.F.S.S.S.S.S.S.S.S.S.S.G.G.G.L.L.a 5 a p K a N . .`.JXJXJXJXJXJXJX].= 2 K U a.m.C.F.G.L.G.G.G.~.~.~.].JXJXJXJXJXJXLX].UXSXZXZXsXAXIXIXKXmX*Xx.5.5.;.;.h h h d 0 , 0 0 u u 0 0 7 r.U.E.W.W.Y.U.y.y.r.y.y.I.y.r.>.s r DXgXj <.r.w.,.:.-.e e e s s f ;XyXrXyXyXyXyXyXl & $ o O $ % & 7 9 n $ & 8 e -.:.,.1.r.6.;.e q q 8 q q d q 9 7 & % ; 8 e e e 7 $ * u 6.6.:.-.-.s e 8 ;XuXuXuXuXuXiXZXZXZXAXSXSXSXSXIXIXIXIXIXiXm 7 < k O o 7 e e e -.-.-.:.:.>.1.l.l.T.T.T.v.c.v.k.Z.5XmXIXIX9X0 % ; 9 u l x O.'.@XmXSXIXIXsXuXsXAXoXJXJXJXJXJXJXJXJXJXJXJXhX#XPXPXPXPXPXPXPXPXPXPXPXBXcXcXnXnXvXkXgXgX].PXGX].FXPXPXPXPXPXPXPXPXPXPXFXOX] R -XjX.X*XkXkXS GXPXnXE ( T {.FXPXLXPXPXPXFXnX).gXhX$X%XPXPXPXPXPXPXPXPXPXPXPXPXGXGXPXPXPXPXPXPXGXPXPXoXPXPXPXPXPX$XvXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX{.H / / / / ^ ^ ^ R Y | FX%Xv I p 4 t t a O.C [ _ ! ! L G G i 4 3 = p n.V.B.F.S.F.F.C.S.S.F.S.S.G.S.G.G.G.~.G.G.Z.a t a I v . .Z FXHXGXJXJXJXPXoX= = p L ] m.M.B.F.G.G.G.G.G.~.^.{.JXJXJXJXJXJXJXoXCXSXSXZXZXSXUXUXUXKX0X;Xj.5.>.h h h f u 9 , 0 0 d u j 0 q u.I.Y.W.W.Y.U.r.r.r.y.I.U.I.r.>.g 7 : l < 6.r.w.,.:.-.e e s g m ZXZXZXsXZXZXZXZXyX,X, $ o X O O % % $ $ ; q g -.:.<.r.y.y.w.-.e 8 7 7 8 q 9 7 ; ; X $ % 8 e -.w 7 & $ h q.>.;.-.e e 8 7 0.iXuXrXeX2X1X2XeXrXiXAXZXAXZXSXZXsXZXZXT.6.f ; $ o o 7 e e -.-.-.-.,.<.1.l.I.T.T.T.z.v.j. .H.J.=X0XyXj.* O & 9 9 l m 0.].OX-XwXCXCXAXuXuXsXSX0.JXJXJXJXJXJXJXJXGXJXJX).PXPXPXPXPXPXPXPXPXPXFXBXcXzXkXvXcXcXkXgXXx.z.4.h s d d r > 0 z l b d u < ;.I.y.U.W.W.E.U.y.y.y.y.y.y.U.y.<.h ; O 1 1 h r.y.w.<.:.g g -.u yXSXIXIXIXIXSXZXiXiX>Xb o . o O $ O O $ q g ;.:.:.<.r.y.r.y.6.g 8 7 7 ; & ; & + $ o % O O 8 e e 8 7 & & h e.>.;.g e 8 7 & b iXuXrX2XW.T.T.T.T.1XrXsXiXiXiXyXuXuX2XT.I.r.h o $ o ; e -.-.-.:.:.<.1.I.Y.T.R.R.T.l.5.N f.P.J.C.v.C + o O % < u b B c.>X>X3X>X1X2XrXrXuXAXSXC JXJXJXJXJXJXJXJXPXGXzXXXPXPXPXPXPXPXPXPXPXFXnXzXkXkXkXcXzXkXgXXv.z.4.m d 9 q 9 5 5.9.4.b b u , 6.l.q.I.W.E.Y.I.y.r.y.U.y.y.U.y.<.g ; o * 1 q y.I.y.1.:.g g ;.5.IXIXUXUXUXUXUXIXZXyX>X}.. . o o O O $ 8 ;.:.w.<.<.1.r.r.r.w.:.e 7 ; $ O O O O O O $ O O O ; 7 e 7 ; & ; h 7.4.f e ; * ; ; 9 MXyX2XR.T.r.r.r.q.e.T.1XeXyX;X1X;X1XR.R.Q.T.r.q . o & e g :.,.:.,.1.l.Y.Y.W.Q.R.T.l.m m f.L.G.S.h.JXn O * u 9.[..X;XyXyX1XR.R.1XrXrXsXAX X].JXJXJXJXJXJXJXJXPXGX`.PXPXPXPXPXPXPXPXPXPXnXcXzXkXkXcXzXzXkXgXgX%X%.GX].PXPXPXPXFXZ.Q R U +.{.aXaXaXaXaXaXqXaXqXaXqX{.S GXxXE ^ Y T OXPXPXPXPXPXJXBX).zXvXnXBX%X].PXPXPXPXPXPXPXPXGXcXgXgXgXzXFXGXGXGXGXGXvXC GXGXJXPXPXzX.w.w.1.w.1.1.w.1.<.;.q 7 $ o 0 1 + o X O O , , v -Xv.q 7 & ; * f 4.h s ; y 5.5.m d +X1X1XT.l.q.6.>.:.4.;.6.z.c.T.T.I.T.Y.W.W.Y.y.-.. o $ 8 -.:.,.,.1.I.Y.W.Q.R.W.R.c.z.l.N b.L.G.S.h.DXV . : z g.-XxXmXyXyXeX1XQ.Q.2XrXuXsXSXz FXJXJXJXJXJXJXJXJXGXPX].PXPXPXPXPXPXPXPXPXPXnXcXkXzXzXzXcXzXgXgX.d 8 > _.JX+.'.oXb b % m 4.e.2X2XW.T.Y.Y.U.y.y.y.y.U.r.<.s ; o o + y I.U.r.<.:.-.-.h 9.mXSXSXIXUXIXLXiXyX;Xc.b + . . o O & q -.<.<.w.<.,.<.w.1.>.;.e 8 $ % V kXJX Xk % + ; 7 u wXwXiXMXz & & & & f d 9 9 j.z.5.m f q z.R.T.l.w.<.,.;.g h -.;.;.6.w.r.r.I.Y.Y.U.r.e o o o 7 -.6.<.w.r.T.W.Q.R.R.R.T.l.r.>.a f.^.G.G.j.JXj X n oX.XyXKXSXiXuX2X2XQ.Q.eXrXsXAX}.|.JXJXJXJXJXJXJXJXJXPXGX].PXPXPXPXPXPXPXPXPXPXBXcXzXzXzXzXcXzXhXgXXc.z.z.4.h u , /.JX_.+.O.l 0 ; d f 1XuXrX1XW.T.W.Y.U.y.U.U.I.r.>.e & o o X l.W.Y.I.1.<.:.,.<.:.z.;XyXsXZXiX1X;Xc.e.d , 9 o . . % e -.>.w.<.-.-.g g -.:.:.g 8 ; % V JXJXJXJXnX1 ; ; * u aXaXZXZXiX0 & & $ ; s ; 5.7.z.z.4.b q 7 4.I.r.w.1.6.:.g h ;.;.;.:.<.r.r.y.y.U.y.w.7 . o o & e 6.q.r.T.Q.1XQ.R.R.l.q.q.:.y .H.^.~.G.f.vX: < `.9XDXSXIXSXiXuX2XeXQ.eXrXuXsXyXA JXJXJXJXJXJXJXJXJXJXGXGXXXPXPXPXPXPXPXPXPXPXPXFXvXcXcXzXzXzXzXkXgX%XS hX%.BXFXFXGX] Q -XHX$X*X&X XPXGXnXvX%X[.&X6XwXqXqXqXqXqX*XF Y Y R Y Y R R ! -XGXBXcXzXC kXFXBXBXoXJXPXPXPXPXPXPXcXk kXkX%.].vXnXGXGXPXXX%XcXvXnXvX|.nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXgXb.C.C.C.C.C.o.C.C.C.C.C.M.C.C.B.C.C.N.b.Z.gXPXGXPXGXHXPXPXJXJXGXPXHXPXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXi.` / / / / ^ ^ ^ ^ _ Q _ _ _ [ ~ .PXPXGX-XL L L K i i p p p i t - - 2 5 2 5 2 ..V.J.L.L.L.P.P.^.^.~.~.~.~.~.L.G.G.G.F.F.J.b.5 t a N JXJXJXPXGXC = 2 4 G L P ] ] [ [ s.d.S.F.G.L.L.~.f.|.JXJXJXJXJX9X@XZXsXiXSXIXIXIXZXyX;Xc.z.l.7.4.d 9 n JXS O.k d 0 : 7 b sXZXsXrX1XW.W.W.U.y.U.I.r.q.>.q & o . ; R.Q.W.U.r.1.>.,.<.<.<.4.z.;X;XT.x.e.4.9 % , 7 % X $ e g :.w.<.:.e 7 7 8 e q e 8 & $ $ , 0.BXJXvXx ; 7 0 5.y aXAXAXAXZX}.& % $ o O , B 7.7.7.4.h s q ; e r.1.w.6.:.-.h h -.:.>.<.r.r.r.y.U.y.w.$ o $ $ % 9 4.l.;X1X1X1X1XR.l.<.>.>.s a f.K.7X^.L.[.`.% l XXNXIXIXIXAXpXrX2X2X2XQ.uXuXyXg.GXJXJXJXJXJXJXJXJXJXJXGXPXoXPXPXPXPXPXPXPXPXPXGXBXvXcXcXzXcXzXzXkXgX%XC hX).cXnXBXBX! R ..bX{.{.*X{.JXFXvXvXkXkXhX[.&XqXqXwXqXqXOXo.Y Y Y Y Y Y R R R b.BXcXzXcX).%XnXnX|.GXPXPXPXPXPXGXoXj kXkXS hXvXGXGXPXPXJXS XX#XoXhXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX+.J.C.C.C.C.C.C.C.B.C.B.C.C.C.M.C.M.M.C.M.C.$.o.-XPXPXGXGXGXJXJXGXPXHXGXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXb./ ` / / / / / ( ( ( ( _ _ _ _ [ U #XBXoXkX| K K K i a p p p a i 3 = = = = - 5 5 V.J.J.P.P.^.^.^.^.^.^.^.^.~.G.G.G.S.S.S.F.J.X.5 t N JXJXJXHXGX%X5 5 t G K P ] ] [ [ s.M.C.G.G.L.L.~.L.N JXJXJXJXJXLX$X>XsXeXSXUXIXSXsX3X;Xc.l.l.z.7.h d z #XA x u d q & , }.UXUXZXiX2XQ.Q.T.Y.I.r.r.w.6.;.8 & o o 5.rXeXQ.Y.l.r.<.,.<.<.<.<.;.f g f g e q % % : , % & e -.:.r.w.:.e 7 $ & & ; ; & $ $ O $ & 7 n vX0 ; ; q 5.4.u .XyXZXZXsX;X0 O O o o , d 4.7.6.4.h e 8 ; $ w.w.1.w.:.;.b b h ;.:.<.r.r.y.I.y.y.w.o $ & & & ; m c.1XuXuXrX1XT.:.>.,.<.s a f.K.^.^.^.v.0 o q }.yXZXSXAXsXrX2X2XQ.2XQ.rX1X].JXJXJXJXJXJXJXJXJXJXJXJXPXGXXXPXPXPXPXPXPXPXPXPXGXBXvXvXcXcXcXkXkXkXhX|.].gX%XoXcXbXvXJ T Q L {.*X*X*XFXnXvXcXcXkXkXzXhX+.*X5X6XOXGXPXo.Y Y Y Y Y T T T T U cXcXcXzX].gXnX).vXPXPXPXPXGXvXV %.kX 6 a K.J.P.P.P.^.4X4X7X7X^.^.~.G.G.G.S.S.S.S.S.G.V.K 6 gXJXJXJXJXGXk - i p G L P ! ] a.a.B.B.S.G.G.L.G.G.k.LXJXJXJXJXJXJXXIXSXSXuXeXR.W.Y.Y.v.l.r.q.6.h q $ $ o y 1XrXQ.T.Y.l.1.<.<.<.<.<.,.-.g g e e g 8 & O O & q -.:.<.r.,.g 7 $ O o o o o o O O & ; ; ; ; z ; 7 & ;.q.5.d t }.iXuXiXc.% $ o o o . ; d 4.4.4.s q 7 & & r.y.r.r.<.9.}.}.j.4.;.w.r.r.U.y.y.r.6.o O & & & & u x.yXZXiXrXR.>.-.w.w.w.-.a f.H.^.7X8Xk.o o % u 9.1XsXsXrXQ.Q.Q.Q.Q.eXR.OXLXJXJXJXJXJXJXJXJXJXJXJXJXPXGXoXPXPXPXPXPXPXPXPXGXFXBXvXcXcXcXcXzXkXhXgXC gXgXhX).zXzXcXL T T ~ J [.*X*XzXvXvXcXcXzXzXzX).oXh.5XOXGXPXHXGXk.J H H Y Y Y H Y R @XcXcXkXzX].cX%.`.PXPXPXGXFX).|.kXkX%.zXFXPXPXPXPXPXPXPXPXPX].+XGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXvX..C.C.C.C.C.M.C.C.M.C.M.M.M.M.M.$.C.A.$.C.$.C.C.$.C.$._.FXGXHXJXJXJXJXGXPXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX9X` / / ` / / / / / _ ( ( _ ' ' _ ] L U I I I I p 3 G i 3 3 p a K a p = = = = = 5 5 } A.P.L.^.^.4X7X7X7X7X^.^.~.G.F.S.d.d.d.S.F.J.F.V.o._.kXJXJXFX/.= 5 p K L U ] [ [ a.s.B.C.F.G.G.G.~.f.JXJXJXJXJXJXJXLXzXb IXIXIXAXiX1X;XT.l.w.q.6.>.h s q , XOXl 9 0 & B iXsXsXuXeXQ.T.T.1X-X X7.6.4.s 8 & $ O $ x.uXrXQ.T.I.I.1.<.<.,.,.:.-.-.-.:.:.-.-.8 7 7 q e :.<.w.r.:.7 $ O $ $ o o o $ & 9 7 8 7 ; ; $ % $ q 5.7.5.f * b ;XrXrXe.O o X X + ; O , f ;.h s 7 & & % r.y.r.r.}.>XMXLXMX;Xc.e.w.r.1.y.r.w.:.o o $ $ & $ ; 9.;XZXyXe.g -.:.<.<.<.w.y f.A.P.7X8X=X+ O o 7 4.;XpXrXQ.Q.Q.Q.Q.Q.Q.3X#XJXJXJXJXJXJXJXJXJXJXJXJXJXGXPX.>.;.d d CXIX9Xl * q , , ;XeXeX1X1XW.Y.Q.mXmX-X}.5.h q 7 ; & & O z.uXrXeXR.T.I.w.<.,.:.:.-.-.:.:.-.:.:.-.e 8 e e g :.w.1.1.,.; o o o o o o $ & ; B 4.7 8 7 & $ . O f 5.7.;.f ; * x.2X1Xh o + + + O q 7 O 9 s s 9 ; & $ ; r.I.r.T.yXLXIXUXUXLXiX3XI.1.1.1.1.6.-.o o $ $ $ $ $ 0 .X}.s 7 8 e g -.>.:.6.y o.H.=X7X8X4X* $ $ ; ;.T.Q.Q.Q.W.Q.W.W.W.R.ZXoXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXGXV PXPXPXPXPXPXPXFXBXnXvXcXkXkXzXzXkXgX).oX.>.;.s 9.AXSX0Xl O q 0 , l.R.R.Q.Q.T.T.yXSXKX0X*Xj.h q ; 7 & & & m eXuXrXQ.T.I.r.<.:.-.-.g -.-.-.-.-.-.e e e e -.-.:.1.y.<.:.q 0 o o o $ $ & & 4.z.;Xu 7 & $ O . O d ;.4.h f & , R.2X2Xe.O j # + % e s 8 ; ; ; $ $ $ ; ; r.U.r.1XiXSXIXIXUXIXSXiX;Xq.q.w.1.<.:.$ $ O o O O o $ % ; ; 8 8 e g :.<.6.q.r.l.k.H.P.H. .& & ; 7 e e.Y.I.l.Y.Y.W.W.Q.1XZXoXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXhXzXPXPXPXPXPXPXPXFXBXnXvXcXkXgXkXkXgXgX`. > 6 a 6 v b.K.P.^.4X4X^.^.^.L.L.G.G.S.S.d.S.d.S.L.L.L.L.F.S.m.[.JX%X< @ 5 K U ] a.a.B.B.C.F.G.L.~.~.G.f.DXJXJXJXJXJXJXJXJX].UXIXSXSXZXuX1XR.I.l.1.<.>.;.7.yXAXSX0Xk . q q 9 0 e.R.W.W.I.Y.ZXSXVX9X.Xj.m q ; 7 ; ; & 7 R.1XeXQ.T.I.<.:.-.g g g g g g -.;.:.:.-.g :.,.,.1.1.1.<.-.q X o O O $ & & 4.T.1XyXMXwX`.r O . $ 9 h h s , j.2X2X2XQ.4.O # O 8 e e e 8 7 & $ $ & & 4.I.I.l.R.uXsXAXSXSXIXSXdXrXT.l.1.<.6.>.y O O O o o o & 7 7 e s h :.<.w.l.l.T.T.c.Z.v.$ & & 7 7 e g ;.<.1.l.I.Y.W.Q.Q.Q.dXoXGXJXJXJXJXJXJXJXJXJXJXJXJXJXBXoXPXPXPXPXPXPXJXGXFXBXBXvXcXkXgXgXgX+X%.%XgXgXgXgXgX 5 t c 5 .Z.K.P.P.4X^.P.P.L.J.F.S.S.d.S.d.B.d.L.L.L.L.L.F.C.b.NXGXM @ 2 p U U ] m.B.B.C.G.G.L.~.~.~.k.JXJXJXJXJXJXJXJXJX].UXIXSXSXdXiX1XQ.T.I.r.w.6.l.eXuXAXpX>X0 o 7 0 9 , b y.I.I.y.I.AXSXZX3X}.z.;.q 7 8 8 ; & & m c.R.1XT.q.;.g g s :.l.l.r.r.<.<.,.>.>.,.,.<.t.t.t.1.,.-.$ o O $ & & 4.;XyXZXZXAXVXVXaXwX*XO.r 0 y 9 0 9.eXeXeXeXeXR.b o + q e e q q 7 7 & & & ; l.W.I.1.T.eXuXsXZXAXAXAXdXuX1XI.l.6.<.6.>.$ O o o . $ 7 7 e s ;.>.<.l.l.I.T.R.Q.eXrXR.& 7 8 8 8 e g ;.<.q.r.I.T.W.Q.eX2XsX XJXJXJXJXJXJXJXJXJXJXJXJXJXJX).PXPXPXPXPXPXPXGXFXFXBXnXvXzXhX * = 5 t v c t o.Z.V.P.P.P.L.L.J.G.F.S.S.B.d.S.S.d.G.P.L.L.L.G.F.S.h.JXS + = 6 K U n.M.B.F.F.G.L.L.^.^.7Xk.JXJXJXJXJXJXJXJXJX].UXIXSXAXZXiXrXQ.R.Y.I.l.I.Q.eXsXiX0X}., X , 0 9 , , f w.w.r.I.SXIXmX;Xv.5.f q 7 8 e 8 7 7 7 e.T.T.l.6.g e q 6.Q.eXQ.Q.W.E.U.1.>.>.>.<.,.1.t.1.<.-.g $ o o $ $ $ 9 z.eXZXAXSXIXSXVXaXqXqXqXaXwXpXyX3X1XrXrXrXeXrXeXc., ; q e e e q 7 7 ; & & , l.T.I.1.I.Q.rXrXuXsXsXuXpXuXQ.T.l.l.q.q.7.m % o o o $ ; q s g >.6.l.I.I.T.R.3X1XuXuXf & 7 8 e g g -.:.>.1.l.I.T.Q.Q.eXrXyXoXJXJXJXJXJXJXJXJXJXJXJXJXJX|.zXPXPXPXPXPXPXJXGXFXFXBXBXvXzXhX > 5 l c c r .f.b.A.J.J.G.J.F.F.F.S.F.S.S.S.S.S.L.^.^.~.L.L.G.S.h.JX`.% - 2 p U ] m.C.F.G.L.L.^.7X7X8X{.JXJXJXJXJXJXJXJXJX].UXSXSXAXsXsXrX3XQ.T.I.U.W.eXeXuXuX>Xx.& o % q 0 9 9 & -.w.w.T.SXSXZX;Xx.5.h q 8 8 q 8 7 7 7 h l.T.r.;.s 8 q uXtXuXuXrXQ.Q.Y.I.<.:.:.:.,.1.,.,.:.-.e o o O $ & o q z.yXZXAXSXSXSXVXpXtXaXaXVXCXVXZXZXiXrXuXrXuXiXZX0Xx.; 9 q d e e 8 ; ; & % ; z.W.I.r.r.W.Q.eXrXrXrXeXeXQ.W.Y.W.R.R.R.R.R.7.o . o o % 8 s h >.6.r.l.I.T.T.Q.Q.rXuX1X; & 7 e e g -.-.:.>.1.1.I.W.Q.Q.rXuXc.FXJXJXJXJXJXJXJXJXJXJXJXJXFX].PXPXPXPXPXPXPXJXGXFXBXBXnXnXzXhX].kXFXx %XcXkXhXhXXsXZXdXsXuXrXeXQ.W.Y.Y.W.2XrXuXyX1X9.% O , 0 9 9 9 9 q y.y.1XCXIXZX>Xc.5.h s 8 8 q 8 7 & & s l.l.q.-.8 7 ; eXuXuXuXQ.Q.Q.E.1.>.;.-.:.,.,.,.,.-.-.; . o $ & 7 7 7 4.T.1X;X3X0XCXwXwX*XaXVXSXIXSXZXZXiXrXuXuXuXsXZXMX;X0 ; 7 q 7 7 7 $ $ $ $ * 7.T.I.l.w.Y.W.Q.Q.Q.Q.Q.W.W.Y.T.Q.Q.eXQ.eXeXQ.x.@ $ o ; 9 h ;.6.e.I.T.I.Y.R.Q.Q.rXrXq.; ; 7 e e g g ;.;.<.l.l.l.Y.Q.rXrXuX`.JXJXJXJXJXJXJXJXJXJXJXJXJX+XcXPXPXPXPXPXPXPXGXFXFXBXBXnXvXzXhX].GX].cXnXBXzXkXgXM o.[.J / / E ~ @.k.k.K 3 D Y R D =X XPXPXPXPXPXxX^ W E Y J .X9XbXbXvXcXcXcXzXhXcXnXFXPXPXPXGXcX%.cXBX%. 5 < 5 5 k z v : a o.f.n.n.n.C.C.F.F.F.J.J.F.L.L.P.^.4X^.^.^.^.~.L.[.JXFX< = = - p @.N.F.L.L.P.4X7X8XqXqX{.GXJXJXJXJXJXJXJXJXHX'.uXiXsXuXuXrXQ.Q.W.W.E.Q.2XrXrX1X;Xe.% + 9 9 9 0 9 q q Y.Y.1XiXAXAXuX;X7.;.s q 8 8 7 7 $ $ ; l.r.:.q 7 & & x.uXuXuXQ.Q.E.U.1.:.g -.-.-.-.-.-.-.e o o o $ & 7 8 8 y >.5.B b 9.@X@X*Xk {.mXVXCXDXZXsXuXuXrXuXuXZXZXMX1X9.$ % & $ o $ O $ $ $ O B l.l.q.<.w.l.W.W.Q.W.W.I.Y.Y.Y.W.Q.Q.eXeXeXeXeXT.O o ; y h 4.e.l.T.T.T.T.R.R.1X2XQ.-.s e 8 8 e s g ;.:.>.<.1.y.T.Q.Q.uXx.kXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXJXPXGXBXBXnXvXvXzXgXoX#X%XzXvX`.kXhXC {.[. .W ^ ! OXFXPXGXFXkXM a I G } {.hXJXPXPXPXPXjXR Y F H H L OXnXnXvXvXcXkXgX%X 5 1 5 6 c v Z z ...f.b.n.V.V.F.F.F.F.L.L.L.^.^.^.7X^.7X^.^.^.P.}.JXJXV @ * 2 i I n.V.F.P.^.7X8XqXaXAXpX+XJXJXJXJXJXJXJXJXJX9X;XrXuXrXrXrXeXQ.Q.Q.Q.Q.Q.rX2X1X;Xe.; $ ; , , 9 u 9 q Y.R.2XrXiXsXuX1Xz.;.g q 8 8 7 7 $ O % r.s $ o o . f 2XeXeXQ.Q.E.t.,.-.g -.g -.g g e 8 o . o O & 7 8 e e 8 8 9 , ; 0 Z `.0.A z -X9XxXlXiXiXuXrXrXrXsXsXZXMX>Xz., O $ o o . % 7 8 8 & d z.e.q.>.>.w.l.I.I.I.I.l.u.U.I.E.Q.Q.rXeXrXuXrXeXT.@ ; q m 7.z.x.T.T.T.T.T.R.W.Q.I.:.:.-.h h f q q h 7.6.>.l.l.W.Q.Q.rXg.JXJXJXGXJXJXJXJXJXJXJXJXJXJXXXPXPXPXPXPXPXPXJXGXFXBXnXvXvXcXzXgXC oXjX#X).|.kXC oXOXk.I E G vXPXHXGXGXGXFX-XA &X=X&X`.S gXGXJXJXJXxXH H F F H H Y U #XnXbXzXgX.-.s e 8 ; & O o s s O O X o X X ; W.eXQ.Q.E.U.1.:.g g g e e e e e 8 . . o O $ & w e g e 7 $ O X . O : |.JXoX+.oX,X>XyXyX2XeXrXrXuXiXsXiX3Xc.9 O O o $ h ;.g s e e d f 4.6.>.;.:.<.r.r.1.1.1.1.r.U.E.W.Q.Q.rXuXpXrXuXrXo & q h q.l.l.l.l.l.l.I.Y.W.Q.y.r.r.<.1.r.l.l.T.1X3X6.l.l.I.W.Q.Q.v.NXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXJXGXGXFXBXvXvXcXkXjXS OX/.].hXhX`.].cXoX[.Y D 4 h.PXHXGXGXGXFXBXkX[.[.].%X%XZ vXBXBXGXKXK H F F F F H Y D _.cXhX.g g y 7 - & <.U.r.% + + X o X o e.W.Y.E.U.t.,.-.g e 8 8 8 8 -.-.e o . . O o $ 8 e s s 8 o . . . o + kXJXoXu k }..X;X;X1XQ.eX2XeXrXuXuX;Xc.f o $ o h 4.;.h g g g -.g s g m ;.-.-.,.<.<.<.<.w.y.y.U.W.Q.Q.Q.uXuXpXuXuX& o 9 h >.l.l.l.<.l.w.r.I.W.W.U.U.U.E.U.Y.Y.W.Q.rXuX9.r.l.Y.T.W.x.nXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXPXPXGXFXnXnXcXzXkXzX/.%.].].hX,XA {.*Xv. .F D 4 4 -XPXFXGXGXFXFXnXcXhXgX%X#X%.S cXnXBXPX%XD F F H F Y Y F Y ..%XkXgXkXcXBXGXPXFXBXcXzXgX%XgXXXPXPXPXPXPXPXPXPXPXkXkXkXkXzXnXBXFXGXPXJXGXk JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGX].PXGXFXFXJXPXPXPXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXPXPXPXPXPXPXPXPXPXPXPXPXPXJXV oXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX%XN.C.C.C.C.$.C.C.$.A.C.$.$.C.$.$.$.#.#.o.@.$.........U | U ..| | | | | | | ....| ..| o.#.#.&X$.@.L @.M.N.$.@...{.FXHXPXHXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXnXM } K K p p $...@.3 a a - 2 K U N.m.m.a.[ [ { { { ' ' { p.p.p.s.B.B.B.X.I o.k.k.k.k.[.}.`.x + l v hXJXJX`.: t } X.n.n.V.F.J.L.P.4X7X7X7X7X8X7X7X7X7X7X8XH.cXJXJXC @ = 2 5 y K f.Z.P.6XaXSXIXUXUXxX#XJXJXJXJXJXJXJXJXJXJXzXR.Q.Q.Q.rXrXQ.^.eXQ.Q.Q.Q.R.I.m ; $ O * , 9 f W.Q.Q.Q.Q.Q.Q.Q.tXuXeXQ.l.:.g s 8 e <.t.U.r.; , * + + + . ; I.U.t.r.<.,.g e 8 7 7 $ 7 e -.g & . o o o o $ 8 8 8 & . . o . . o 1 n q q q 7 9 B R.T.Q.Q.Q.Q.Q.eXeXR.T.4.o $ ; ;.4.;.-.-.g -.-.-.e e e 5 & & g -.:.:.<.1.1.y.U.Y.Q.Q.rXrXuXuXdXuX* o $ y f ;.>.>.>.>.w.r.I.Y.Y.W.Y.E.E.E.E.Q.Q.Q.rXpXj.>.<.r.u.T.zXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXXXPXPXPXPXPXPXPXPXGXGXFXnXBXnXvXFXPXPXKXcX].-X{.tXqXqXqX5XD D D H p vXGXGXGXGXFXBXvXzXhX%XXXx %.#XkXGXPXPX{.F Y Y H Y Y F F Y J hXcXnXFXFXGXPXFXvXzXhX.<.<.:.u y , ; * % . $ 6.,.1.<.:.g g & . . . o $ 8 e e 8 & o . . o & & ; o o o o . . . 9 q q 7 7 ; ; ; q T.W.W.Q.Q.Q.Q.W.c.4.O O y ;.;.4.;.h g g g e e 8 7 ; & & & e g :.<.w.y.u.U.W.W.Q.Q.tXuXsXsX3X. o o O s d f h ;.:.<.q.I.Y.W.E.E.W.E.E.E.Q.Q.Q.tXuX4.s :.1.u.XXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXoXPXPXPXPXPXPXPXPXPXFXPXPXPXPXPXPXPXPXBX$X%.{.aXqXqXtXqX5XF F D D D K {.gXBXGXGXGXnXvXhX$Xx #XPXC FXPXPXPXPX+XG H H F H H F F Y -XFXFXGXGXPXFXBXcXkXXQ.uXrXrXQ.Q.Q.rXrXQ.R.T.l.h 8 & & $ . 3XiXuXQ.Q.Q.T.Y.I.Q.rXeXeXQ.W.y.1.<.:.<.<.1.l.>.h 0 u < , % o & e 1.1.<.,.-.g 7 $ $ o & 7 8 8 e 8 & o . $ $ . & 8 q q q & & ; 7 7 8 7 7 ; ; q T.W.W.W.Q.Q.Q.W.l.;.O $ q h ;.;.h g s e e 8 8 8 8 7 & & $ 8 g <.r.y.Y.Y.Y.W.Q.eXrXpXsXdXuXd . . . o - g h h h ;.>.r.I.T.W.W.W.E.Q.E.E.E.Q.Q.eXuXy g >.y.U.I..<.l.Y.W.W.E.W.E.E.E.Y.W.Q.eX7.e -.<.y.U.W.z.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXJXcX|.A qXH.+.{..XH.f.T / / / / ( W W W W W E L hXvXkX].GXGXS FXPXPXPXLXxX-Xf.] T R ) / ^ ( W Z.PXHXGXGXFXnXzXhXgX].).%.FXPXPXGXGXGXFXFXnXvXvXnXFXFXFXGXGXPXPXPXPXPXGXGX`.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGX#X%XJXXXFXnXoXhXcXcXkXcX = 2 i a I ..f.Z.H.5XmXKXUXUXUX$XnXJXJXJXJXJXJXJXJXJXGX_.sXdXsXuXrXeXQ.rXQ.eXQ.T.w.>.h e 8 ; & & 5 eXrXQ.W.l.w.:.l.Q.eXeXQ.Q.Q.Y.u.u.U.E.W.Q.Q.Q.I.<.* 1 $ $ & 8 e t.u.u.t.1.,.,.-.g g g g -.-.,.-.e . o o o -.y.U.l.z.c.c.}.;X>X}.M 0 ; 7 7 q ;XR.W.W.W.E.W.W.I.q.;.o o 9 h -.f s e 8 7 & & & & & 7 ; y yXZXiXeX1XR.R.Q.Q.Q.2XrXuXdXdXuXu X X . $ ; q h g s g ;.<.l.T.W.Q.Q.E.U.U.y.U.T.Q.Q.s g -.<.1.U.W.T.hXJXJXJXJXJXJXJXJXJXJXJXJXJXcX].kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXcX#XZ H.O.A Z JXKXOXT / / / / / / ^ ^ ^ W W W R [.kX#X|.GXcXXXPXPXPX.X] E ( ( ( / / / ` ( W W OXPXFXBXnXBXcXkX#XC ].oX%.GXGXGXGXFXFXFXBXvXvXcXnXBXFXFXFXGXFXGXGXGXGXGXGXXXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX%X].FXhX].%.XXXXGXgXcXcXkXkXcX).x nXPXPXPXPXPXPXPXGXFXFXFXFXBXnXnXnXvXcXnXBXFXFXFXFXGXGXPXPXGXGXPXGXPXPXPXcXhXgXkXzXnXnXFXbXoXPXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX&XA.C.C.A.C.$.C.$.C.$.$.$.$.$.C.U [ [ $.[ [ [ [ [ $.$.L a | +.%.bXFXFXKXFXFXFXkX#X].]._.%.o...o.| U 2 4 D F H T Y F p $.$.C.X.x ] { s.{ { { [ ~ L g.JXJXJXJXJXJXJXJXJXJXHXJX).| K @ A.f.v 6 v .v c < i 6 2 O.JXA n.B.d.d.d.d.d.d.d.B.d.d.M.d.B.B.S.S.B.M.m.n.n.X.I v l c vXJXJXJXS < > + 5 v f.k.Z.H.=X6XqXqXqX8X8X8X8XqXaXVXSXKXKXoXk = = 5 i I I ..X.b.H.*XwXCXIXUXUXPX+XJXJXJXJXJXJXJXJXJXzX}.SXAXsXuXuXrXrXrXeX!.Q.T.q.h s e 8 7 7 ; ; R.R.T.l.r.>.;.r.Q.Q.Q.Q.Q.Q.W.Y.Y.Y.W.Q.Q.Q.Q.W.I.e $ & 7 7 8 ,.u.u.u.t.t.1.,.,.-.-.-.,.,.<.<.,.g 7 . o o O 8 u.u.r.T.;XyXyX|.g. X@X}.Z < 7 ; e.;XQ.Q.W.W.W.W.E.I.6.h o . 7 h h s e 8 7 & & $ $ & & & ; HXUXUXIXCXiXeXeXeXeX1XuXsXZXAXAXuX9 o o o . * 8 g h g g g ;.<.l.W.Q.Q.E.E.y.1.g s ;.4.f s -.:.<.y.U.W.2XoXJXJXJXJXJXJXJXJXJXJXJXFX`.oXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXvX 5 @ < v f.k.H.=X=X6XqX8X6X6X8XtXqXqXaXSXIXUXKXS * = 2 t a } ..f.n.Z.=XwXVXKXIXIXUXoXJXJXJXJXJXJXJXJXJX`.SXSXAXdXpXuXrXrXQ.Q.Q.Q.I.<.g y e 8 8 7 7 ; y l.l.l.>.h >.W.rXeXrXQ.Q.Q.E.Y.Y.W.W.Q.E.Q.W.Y.r.2 & & 7 8 8 -.u.E.u.u.t.t.1.,.-.,.,.1.t.t.t.1.-.8 . . . o O 1.U.I.T.1XuXZXsX>X_.'.$X}./.9.7 9 ;X1XQ.Q.W.Q.W.W.E.Y.w.-.O X O d g s 8 7 & $ o o $ $ $ & O MXIXUXUXIXSXsXuXuXuXiXZXSXSXIXIXAXB + O O $ 8 d h ;.h g h h l.T.Q.Q.Q.E.:.; & ; 7 q q s ;.:.w.y.U.E.Q.eX.XJXJXJXJXJXJXJXJXJXJXJX).BXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXBXgX`._.k.f.h.h.`.GX,X) / ~ X.] L J T R ^ W W E R J zX 2 6 M HXHXjXI m.M.B.M.d.s.M.s.s.s.s.s.d.d.d.B.B.S.F.F.S.C.C.n.I a x JXJXJXkXk 1 > > : > z .k.Z.=X4X6X8X8X6X8X6XqXaXAXAXIXUXUXPXl = 6 p a } ..f.n.A.=X5XaXKXIXIXIXoXJXJXJXJXJXJXJXJXJX`.sXdXsXuXrXeXQ.Q.Q.Q.Q.1XT.e.4.e s e 8 8 8 7 & ; f ;.f s l.eXrXrXQ.W.W.W.W.Y.E.Y.E.Q.W.W.Y.r.>.% & 7 7 8 8 8 t.E.u.u.u.u.t.t.t.t.t.t.u.u.u.t.-.w o o o o & t.:.T.eXiXsXZXsXyX.X'.9X@X.X,X% 9.>X1X2X2XQ.Q.Q.E.W.E.y.>.$ X o ; s 8 ; & $ $ O ; q 9 ; & $ }.ZXIXIXIXIXAXZXsXsXSXIXIXUXUXUXIXyX0 o % ; d h ;.4.;.g ;.;.z.R.eXeXQ.w.; $ $ ; ; 7 9 f >.l.I.Y.W.Q.Q.rX@XJXJXJXJXJXJXJXJXJXJX#XX,XJX].hXBXPXPXPXPXPXPXPXPXPXGXFXcXcXcXkXkXzXgXXX+XPXPXPXPXPXPXPXPXPXXXPXGXhX]. > < @ : , l N f.Z.=X4X6X8X8X6X8XqXaXVXVXSXUXUXUXBX6 5 t K | o.b.Z.Z.K.5XwXVXSXSXUX%XkXJXJXJXJXJXJXJXJX`.uXuXrXeXeXQ.Q.W.W.Q.1X1X}.9.4.e e e e 8 8 7 ; $ $ 7 8 9 uXuXuXrXeXT.Y.T.W.Y.W.W.W.W.W.E.U.,.e $ ; 7 8 8 7 & -.u.E.u.E.u.t.t.u.t.t.u.u.u.u.t.-.7 o O $ $ e ,.T.rXsXsXZXdXZXsXyX9XjXlXMXMXB 0XyXyX1X1X2XQ.Q.Q.Q.E.Y.w.% . X O . $ $ $ & $ 7 h d 9 ; % % b 0XiXMXZXZXZXiXZXZXIXUXUXUXUXUXUXLXyXj.n f h ;.4.6.;.h h ;.l.R.eXsXuXs & $ O $ & ; s ;.w.l.W.W.Q.Q.Q.rX|.JXJXJXJXJXJXJXJXJXPX).PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXFXvXgX$X$X{.k.h.+.h.O.BXU H i FXHXPXJXGXOX-X{.U F F L gX%X#X%. 1 : > * < z .k.H.=X=X6X4X8XqXqXaXaXAXSXUXUXUXUX'.> 6 a I ..f.Z.D.K.5XwXVXSXSXIXIX`.JXJXJXJXJXJXJXJX|.rXrXeXQ.Q.W.Y.W.W.Q.1XyX.X9.m e q e e q 8 7 7 & O o o 3XsXsXuXrXQ.l.U.Y.Y.Y.Y.W.E.E.E.Y.1.h 7 $ 7 8 7 8 7 & & u.u.u.u.u.u.u.u.E.u.u.u.u.t.,.-.& o o $ $ ,.g 1XAXAXAXAXAXZXZXSXbX,X,X}.9.XX>XyX1XeX1X2XQ.Q.Q.Q.E.Y.w.$ . . o & $ $ $ $ 4.;.s 9 ; & & , 4.}.1X1X1X1X1X1XyXiXMXZXPXUXUXUXIXZX0XMXA 4.6.q.q.4.;.6.6.z.;XuXdXdX; % o o $ % 9 f 6.l.T.Q.Q.eXQ.Q.j.BXJXJXJXJXJXJXJXJXJXGXhXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXFXbXjXoX_.{.h.k. . .+.k. XF D 4 _.PXPXPXFXvXFXBXnXjXOX%X%X#X#XS ).|.GXGXFXU W ^ R J k.OX%XlXcXBXBXnX-X*Xg.#X+X+X$X%.gX#XV %.+X).%X%.).+X`.].%.).#XhXkXkXhX > : 1 z .f.K.P.=X4X4X6XqXaXaXVXSXUXUXUXUXUXl 5 d I } f.b.Z.K.=X6XmXVXAXIXSXj.JXJXJXJXJXJXJXJXGX;XeXQ.Q.T.Y.r.Y.W.W.CXCX>Xz.m d e e e e 8 7 ; $ O o , ZXdXsXuX1XQ.Y.I.Y.Y.T.Y.Y.E.W.E.U.<.g $ % & 7 7 7 7 7 e t.u.E.E.u.u.E.u.u.u.u.u.u.1.,.e o o o o & w t.>.;XSXSXAXSXSXZX0Xj.f ; 7 7 7 B }.}.;X1XQ.Q.Q.Q.Q.Q.E.w.o . o O $ o & $ o 7 7.6.d 7 7 & ; 7 ; b 7.e.x.x.c.}.;X;XyXMXCXUXUXIXIXSXMXZXj.5.7.z.l.e.6.7.7.z.;XyXdXSX; $ o o O ; y m l.R.Q.eXrXeXQ.v.HXKXJXJXJXJXJXJXJXJXJXGX < : j x .Z.H.K.=X=X6X8XqXaXVXVXIXUXUXUXUXCX5 6 c .o.Z.Z.H.K.=XaXaXVXSXSXh.DXJXJXJXJXJXJXJXJXCX}.Q.W.U.r.1.y.u.;XIXSX1Xx.4.f s s e e q 9 7 $ o o z AXZXsXrX2XQ.Y.I.U.Y.Y.Y.W.W.E.E.Y.:.q $ O & 7 7 & & & ,.t.E.u.E.E.E.u.u.t.t.u.t.,.,.-.& o O o $ 7 7 1.r.f >XZXZXMXyX0.* & ; ; 7 7 7 7 q m x.R.Q.Q.eXrXQ.Q.W.q.O o O $ O O $ & $ 4.c.z.f , ; 7 7 q q 9 d 7.7.e.z.x.c.}.;X,XxXBXKXVXSXSXIXIXxXm x.x.c.x.l.z.z.c.>X0XAXIXB ;.; X + ; s 7.c.R.2XrXuXQ.9.DXJXJXJXJXJXJXJXJXJXJXJXJX+XPXPXPXPXPXPXPXPXPXPXPXPXGXGXBXNX|.*XqXtX8X8X6X6X=X=X*XH.O.H F D 4 K JXJXGXnXcXzXkXkX.6.s % ; f z.R.eXuXsXsXA PXJXJXJXJXJXJXJXJXJXJXJXJXJXoXPXPXPXHXPXPXPXPXPXPXPXPXFXNXBX wXqX6X*XZ.+.v > z k.k.+. .c = > t l Z S gXJX 1 k x O.h.[.&X*X*X6X6XqXqXaXVXSXUXIXUXIX6XZ.v .h.k.Z.K.P.6X6XqXAXaXtX].JXJXJXJXJXJXJXJXJXJXHXg.I.1.<.<.r.iXAXiX1XI.6.g e e s q q 8 , * O . z ZXsXyX1XQ.W.I.u.U.I.I.Y.Y.W.E.E.l.g 7 O o & 7 7 & & w t.u.E.u.u.u.t.1.,.,.,.-.-.e e o $ $ $ & 7 7 7 7 1.r.r.r.w.-.8 . . o $ $ & & ; 7 7 ; y T.Q.Q.eXrXpXeXR.q.o $ O $ 7 ; O & 9 z.;Xc.9.y 9 q q d e d f v.c.}.}.}.}..XOX.X@XxXxXwXaXVXIXUXA j.z.x.c.c.x.v.x.}.@XwXVXeXI.w.r.r.s , b z.;XuXsXdX@XnXJXJXJXJXJXJXJXJXJXJXJXJXJXJX 6 ..L [ _ [ [ [ [ @. .vXJXJXJXJXJXJXhXX.( ` ` _ _ Q G c v | ..U #.#.#.#.N.N...$.S.S.C.B.V.n.V.n.n.n.n.b.@.X...v ).HXJXJXJXJXJXHX> *XqX6X=XZ.k. .5 < _.[.k.h.N 6 @ 1 1 r x C BX Xg._.| a.B.M.B.M.B.S.F.F.J.L.J.L.J.J.F.C.F.C.V.C.V.A.K.*X.X6 : : < 5 6 1 : 1 r c O.h.[.*X*X=X6X6X6X6XqXVXSXKXSXUXSXqXo.v .f.Z.H.H.4X4XqXqXaX8X'.JXJXJXJXJXJXJXJXJXJXJXJXS R.q.I.Q.AXAXuXeXR.r.;.s e q 7 ; $ % O o + * yXyXeX;X1XR.Y.I.Y.Y.I.I.Y.Y.E.E.U.-.8 ; O o & 7 7 & -.t.E.E.E.u.t.,.-.-.-.-.-.-.e 7 $ $ o & & 7 7 7 & 1.y.U.r.1.e 7 o . o & & ; ; ; 7 7 ; d ;X1XrXuXuXuXuX;Xz.. $ $ q q e q 8 9 z.3X.X9.r q q q q s q j.v..X.X}. X}.+X.X XxXbXmXmXVXVXIXS B 9.j.9.z.7.z.x.x.}.@X3XyXeXQ.W.I.Y.e.* n z.;XpXAXAXZ NXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX].GXFXBXGXPXPXPXPXPXGXnXcXzX].H.O.O. + : > < 6 j : 6 k v +.[.&X*X=X=X5X6X6X8XqXaXVXSXIXIXVX7Xt N .f.k.H.P.4X4X8X8X7X{.JXJXJXJXJXJXJXJXJXJXJXJXN j.Q.rXsXAXdXuXeXQ.I.<.:.g g s 8 $ O o X + + z >X;X}.;XR.Y.I.I.Y.I.I.u.U.Y.E.U.:.q 8 - o O & $ . :.y.U.E.u.t.<.-.-.e -.e 8 8 8 o o $ & 7 7 7 & & & 1.u.I.r.6.s 7 $ o O & 7 ; 7 7 7 7 ; m >XrXiXZXZXZXZX>X}.O % q d f h g e q 9..X}.O.l u 9 9 e s 9 h.v..X.X.X.X{.}.}.0XCXCXCXBXmXaX-X+ n B 9.B B B m 5.z.z.x.R.1XrXeX2XQ.Q.W.u Z `.e.pXSX0Xe.j.|.JXJXJXJXGXJXJXJXJXJXJXPXJXJX`.GXBXFXGXHXJXPXJXGXvXvXzXzXk.k.k.h.+X].).PXPXgXOX&XH.H / / / / / / ^ W W W E T F #X : : 1 S HXS _.].|.%.a n.B.C.S.C.F.J.F.F.J.J.J.L.L.J.J.F.C.B.C.C.D.J.P.H.+.# + + @ : 5 6 : k x S h.{.*X*X-X=X=X4X4X6XtXqXaXSXIXIXaXv x N O.o.f.K.P.4X8X7XtXH.vXJXJXJXJXJXJXJXJXJX.;.g s q O + + + % + x.}.c.T.T.I.I.T.I.l.l.r.U.Y.Y.y.<.s e 8 & $ 0 u % <.y.u.u.1.<.,.-.e 8 8 w 7 & o $ $ & 7 7 7 7 7 7 $ ,.y.y.r.w.e & & o $ 7 8 7 7 8 8 , , B yXZXZXSXIXIXLXyX}.. u b ;.4.;.h s d b S g.Z z q 7 9 e q + h.{.{.&X X{.'.c.>XMXLXSXSXKXKXmX6 9 : 9 0 0 u u u b 5.j.j.c.;XeXrXrXeXeX2X7., 9 q Z AXj.I.Q.yXMXJXJXJXJXJXJXJXJXnXoXoX].).V %XBXBXFXPXPXPXJXBXcXzXzX#XH.k.k.k.|.0.vXPXPXPXPXHXL / / / / ^ W ^ ^ ( W W E T D .:.g q & + + + + : , j.j.x.v.l.l.I.I.l.U.U.E.E.Y.U.<.g s q 7 $ ; b & :.r.y.t.,.,.-.e e 8 8 8 8 w e 8 8 8 8 7 7 & 7 7 $ w.y.I.e.e.>.q ; o $ 7 7 7 8 q q 0 9 j.yXsXAXSXAXSXmX@Xj. m 4.5.7.6.4.;.f f B A Z b 0 9 9 q 9 % < +.h.h._._.}.>XZXAXIXIXIXIXSXVX, y d 9 : 1 0 0 l B 9.j.x.}.;XyXuXuXuXuXuXeX8 ;.>.6.4.6.R.eXZX`.JXJXJXJXJXJXJXJXS JXPXPXPXPXPXPXHXPXPXPXPXJXnXcXzXhXXX*Xk.k.k.h.0.FXPXPXPXPX{.( / E ] f.-X+.D D F T W R Y O..>.;.<.T.W.W.W.W.Q.E.E.l.-.f s 8 ; O , < g <.<.<.:.-.e w e e 8 e e -.,.,.-.-.8 8 7 7 & & q y.T.W.T.T.l.;.8 ; o o $ ; e b 5.B b v.5XqXaXaXaXpX5Xk.t . B B 7.e.e.q.4.m m B B Z n 0 0 < 9 % % @ N +.h. X>XlXZXLXIXIXIXUXUXSXAXv u u * < < 0 0 z B 9.x.}.;X1XyXiXsXsXZXZXZXq ;.6.e.T.T.1XiXCXO.JXJXJXJXJXJXJXGXS PXPXPXPXPXPXPXPXPXPXPXPXGXvXcXkXhX+X&Xk.k.k.h.).JXPXPXPXKXT R k.xXPXPXPX@X| G Y D F Y D -XgXgX).BXPXPX{.*XGXFX].J ( / / / / / / / / F 4 Y K k. ._.%X#XV XXhXvXFXFXGX{.R R W ^ ^ W ^ ^ / ^ ^ ) X.%XBXnXBXvX].GX#XoXzXcXgX:X}.|.#XcX 6 : x A _.[.{.&X&XH.H.D.K.P.4X8XtXpXdXAXaXv.r c v v h.Z.P.^.^.7X7Xv.GXJXJXJXJXJXJXJXoX$XSXpXeXQ.eXQ.Q.Q.uXeXQ.R.Y.I.1.<.<.:.g 7 + # + + + + @ 4.4.h d s T.Q.Q.Q.Q.Q.Q.E.W.I.:.d s e q & $ l 8 :.-.-.-.g 8 7 e -.-.-.-.:.-.-.-.g 8 8 & & & & 1.I.Q.Q.;X;XT.e.m q O O q m 9.}.].S K.4X8X8XtX8X4XH.k.* + B 5.9.9.e.7.7.4.4.5.B M B B n 6 k 6 6 f.*X5X0XxXCXLXIXIXIXIXUXIXSXaX8X5Xq , = < < 1 6 x Z 9.}.;X>XyXiXiXZXAXSXIXIX0 6.6.r.T.;XeXZXOXnXJXJXJXJXJXJXJXnX|.PXPXPXPXPXPXPXPXPXPXPXPXFXvXzXkXkX 6 : M +._.[.k.k.Z.Z.Z.D.P.^.7X7X8XtXaXtX8X5 z c v o.k.P.^.4X4X7XH. < k x S g.oX,XyXiXZXAXSXIXIXUXPXd w.r.T.R.2XuXZXkXJXJXJXJXJXJXJXJXFX+XPXPXPXPXPXPXPXPXPXPXPXJXBXvXzXkXkXgXk.O.O.k.k.k.oXnXFXFXo.D D zXPXPXPXPXxX-X#XzX#X{. XgX b.G.F.] 3 . . .N | v v l 1 1 S JXJX].{. X[.k.+. .+._._.C r V.J.J.J.D.V.K.^.^.^.^.^.^.^.~.L.J.G.F.F.C.C.V.X.v 6 @ @ : > 5 < C h.h.k.k.Z.k.Z.Z.Z.P.^.7X7X8XtX8X7Xk.6 c v } k.K.P.^.^.7X7X_.JXJXJXJXJXGXJXJXhX>XuXQ.Q.E.Q.Q.Q.rXQ.Q.R.Y.U.r.r.r.l.l.h ; @ # + # @ + h h f h 3XuXpXQ.rXQ.Q.Q.Q.W.U.,.s s f f 8 $ 0 l * 7 8 7 & & & 8 e -.g -.,.<.<.w.,.g 8 7 & $ >.T.1XeXuXsXsXiX>X}.e.m o , 5.c.9XxX$X_. .n.D.L.P.L.F.f.- $ O ; A 9.j.x.z.z.7.7.9.9.5.Z C N M O.c A.P.4XqXVXVXSXIXIXSXSXSXAXaX7XL.L.v.u * 5 < : < 6 Z +.{..XlXZXLXIXIXIXIXUXUX.Xq.l.T.Q.2XuXsX,XJXJXJXJXJXJXJXJXJXJX#XPXPXPXPXPXPXPXPXPXPXPXJXGXcXkXjXzXkX0.K.H.f. .h.+.#XvXcXL 4 D v GXPXPXPXJX-X&XzXhXgXgXgX k.k.N.&X+.k.| o.o.k.#.k.&Xf.Z.d n.S.D.n.X.U I I X.n.V.n.A.D.H.K.*X&X[.+.C nXJXGXg.6 y F.F.n.i N . . . .Z N z r 0 k hXJXvX+. X{.h.k.+.+.].`.Z z a D.K.J.J.D.V.~.^.7X^.7X^.7X^.^.~.L.L.L.L.P.P.J.L.K.X.5 : < 6 6 < Z h.h.k.f.f.f.f.Z.A.P.^.^.7X7X7X7X^.t c c N o.Z.P.~.^.^.7Xg.GXJXJXJXJXJXJXGXGX,XtXrXQ.Q.Q.rXrXrXrXQ.W.T.I.r.y.I.I.T.4.9 + * # @ # = - h f 7.uXpXuXuXQ.Q.Q.Q.W.E.U.,.g e s e & % n z < ; $ o $ $ & 7 e e 8 1.R.R.I.l.l.>.8 & & 7 l.R.eXuXAXSXSXSXiX;Xc.9.o O B ;X0X9X XA O = U @.n.m.X.9 g 7 $ O $ 0 B 9.z.z.j.9.j.9.0.N M c v +.c b.P.=X6XqXVXAXAXSXAXaXaXpXtX^.d.^.N f < 3 = = > k .k.*X0XxXLXIXIXIXUXUXLX9Xc.x.T.R.2XrXiXOXGXJXJXJXJXJXJXJXJXJXJX$XPXPXPXPXPXPXPXPXPXPXPXGXBXvX9XOX.X.Xk.5X=XH.*XZ.o.Z oXzXI D D D .JXPXJXnX{.*X{.{.#X = < d s .XyXiXrX1XQ.R.W.W.W.Y.y.<.g e q 7 $ r N c r * & $ $ $ & & 8 w d ;XyXpX0X;XR.r.-.& ; q T.1XyXdXSXIXIXUXCXyX.Xx.O X 1 .X@X Xx * $ $ * 2 p G 7 g g q ; ; ; ; , , 0 B 5.9.9.j.0.Z z c O.+.N a A.L.P.8XtXtXaXAXaXqXtX8X^.L.p.N b b < 2 = = 5 t .Z.*X6XVXKXUXUXIXBXg.n z.c.c.;X1XiXiX XJXJXJXJXJXJXJXJXJXJXJXJX#XPXPXPXPXPXPXPXPXPXPXHXGXzX.X5X5X*X.XP.4X=X=X=X=XH.k. .XX&XF D D D | bXjX5XwXwXwXwX[.$X#X#X$XkXFXFXBXnXhXk.'.cXU H D D ..#XbX%Xo.o.+.+.o.%.].oX+X#X#X#X#X#X#X%X].%.#X#X#X#X].4 F F F Y Y @.PXPXGXGXFXBXS %X%X#X0.6.;.6.9.`.7.r.u.Y.E.Y.E.Y.Y.Y.u.Y.7.S PXPXPXPXPXPXPXPXPXPXPXPXPXPXGXS oX].zXFXGXkXS S ).%.).|.BXFXBXnXcXkX).C %.].hXcXkXgXhX = 5 5 k l 9 -XxXyX1XR.W.T.W.Y.Y.U.t.,.-.e 7 % ; .N c r $ $ o o $ & 7 7 & z.0XKXSXaXyX1XT.q.8 & g T.3XMXSXIXUXUXUXPXMX>X}.% , 0 '.{.: o o $ & & ; & ; g -.g q 7 7 9 0 b V r X * z B 0.j.O.c c O.O.O.t X.F.P.^.7X8XtXaXVXVXwXwX5Xf.a B m n zXM 5 = - 3 I b.J.8XVXKXKXHXmXz 9.z.}.}.;XyXMXZX}.JXJXJXJXJXJXJXJXJXJXJXJXJXXXPXPXPXPXPXPXPXPXPXHXbX-X*X5X.X,XgX#XH.4X=X=XH.=XH.H.H.[.nXG F F F D 4 |.&XwXwXwX6X&XzXzXcXcX > 5 5 z kX|., A g.A 7.T.I.I.I.U.I.U.y.<.,.-.-.e y 9 * > 5 % $ o o & & 7 7 & ).0XIXKXmXyX;Xq.g 7 o o m c.lXCXVXSXKXKXKXMX>XA k l 1 h.h.= $ $ $ ; ; ; 7 q g g g 8 7 9 q d 5.9.Z 0 % ; < l +.g.6 Z O.O.O.v t b.J.P.4X6X8XwXmXmXxXZ u b B B b -XJXGX).a 2 4 U D.L.4XqXVXwXmXwXZ z |.@X>X,XLXZX1X0.GXJXJXJXJXJXJXJXJXJXJXJXJXXX].PXPXPXPXPXPXGXjX@X5X5X.XgXnXcXkXoXH.5X5XK.*XH.*X=X=X.XLX} D H T R ^ E E T L Z.wX-X-XFXPXPXV V 1 ).`.=XH.=XK.=XP R Y Y Y Y ..kXO.o.Z.h. < t c x .C Z S O.%.h.g.A B b u u < 9 1 k |.].`.O.O.OXoX].0.Z M M a K.D.D.Z.Z.H.4X7X8X8X8X7X7X4X4X^.^.L.~.P.^.^.^.^.^.^.P.X.y x 1 a } @.f.f.b.n.f.b.@.V.D.J.L.L.~.~.L.n.5 I ..n.C.F.L.L.L.~..XJXJXJXJXJXJXJXJXJXGXoXiXdXdXsXrXQ.Q.T.T.I.I.l.<.6.7.h 9 % > 5 t g.BXJXBXA wXpXiX>Xx.x.I.r.l.y.U.U.t.t.t.1.1.1.<.<.-.9 % $ o & 7 7 7 7 & B XKXCXyX3XT.;.7 O o O b .XxXwXqXaXVXBX0X+X1 0.n < v v < O & 7 q s q q 8 e g g q 8 8 e h 5.j.9.0 + 0 r 1 1 z c Z A Z Z I a y f.A.P.=X5XwX XM u n B B B b M oXJXJXGXoXt K ] B.F.P.6X5XqXqX*XH.k.Z $XcXMXrX1XuX}.JXJXJXJXJXJXJXJXJXJXJXJXJX).vXPXPXPXPXPXPX-XwX5X@XzXGXBXnXvXkX+XS A k.=XK.H.H.=X[.9Xf.E / / / / / / ^ W ( F 5XwX{.FXGXgX%.oX#XXXf.=X4X6X6X6XZ.R R Y R R R L o.&Xk.S S %.V %.V oXgXhXkXL Y ..{.kXcXcXcXcXzXhX} F D D H nXGXBXnXnXoXS %X#X#X5.6.6.q.e.9.kXzXzXcXnX|.7.y.Y.w.XXcX%.BXBXFXPXPXPXPXPXPXPXPXPXPXPXnX].zXzXXXkXkXzXcXzXzXkXzXkXcXkXkXkXgXoX.7 $ $ & 7 8 8 7 7 u g.-X0X0X;Xq.7 o . . . o o u .X-X=X=XwX0X-X).oXz 0 X : 5 @ $ ; y d h m d q e g g e 8 8 s ;.7.x.b o.*X*X-XOXoXO.n z M M v a v K = a .k.{.x M 0.g.9.B B N z g.h.FXJXJXJXA J [ s.d.G.P.4X4X4X=X=X6X5Xh.c.T.T.1XuX@XgXJXJXJXJXJXJXJXJXJXJXBX`.BXPXPXPXPXPXPXPXkXkXFXGXGXFXBXnXcXhX).oX#X#Xg.k.H.*Xk.h.Q / / / / / / / ^ ^ W E Y -X5X*XzXbX].%X$X#Xh.6X4X6X8X8X8X8X! R R R R R E J k.].gXgXhXkXkXzXcXcXcX..Y R E E T ..[.OX-XkXjXhXG D H ] nXnXvXvXzX%.gXhX 5 z c z x C S _.).S B n d u 0 9 > : j x /.O.Z x B ).'.g.g.O.O.O.a ..Z.b.f.f.H.6X8XtX8XtX7X7X4X^.^.^.L.L.L.~.~.G.G.~.~.G.p k 2 a I U U b.X.n.@.n.n.V.C.G.G.L.L.L.L.G.p I U n.B.F.F.F.G.Z.zXJXJXJXJXJXJXJXJXJXoXIXIXZXiXrX1XR.T.l.I.I.l.6.h e 9 O 6 a t NXJXJXGXk.aXSXSXSXZXZXuXrXeXT.r.y.y.u.u.u.u.u.u.U.I.Y.y.;.$ $ 7 7 8 8 8 7 , Z X-X@Xc.9 o o . . . . . 0 X-X*X*X*Xx + k , . . @ 2 o * q d 4.7.7.4.s 8 s g 8 e e g 4.7.5.a J.P.=X=X-XOX|.j.B M x c a I , , * 5 A _.C _.].'.j.0.B u A A h. XGXJXJXFX+.U { a.n.D.P.P.^.P.4X4X7X6XN l.R.1XiX.XlXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXPXGXJXGXFXFXBXnXzXhXS $X#X#X$XS *Xv.9XP / / / / / ^ / / ^ ^ W R L 5X-X-X-XzX|.gX$X#X[.4X6X6XtXtX8X7XZ.Y Y R Y Y R E K kXkXkXzXcXvXnXnXnX{.Y Y E E E E E ^ ( ( R ) T T E W +.zXzXzXkXXXS kXkXgX + p I I U f.] n.U n.n.n.N.S.G.G.G.G.L.G.F.i U a.B.F.F.S.F.F.|.HXHXJXJXJXJXJXJXJX].IXIXAXiXeX1XQ.T.T.T.I.v.q.h y ; o } N $XJXJXJX+X5XAXSXAXAXAXZXsXuXyX1XI.r.y.y.U.U.U.U.E.Y.I.I.T.:.$ $ & 7 8 8 8 8 8 9 B '.x.6.& . o o . . o & & & x {.k.v 7 ; $ . o o o o = o 7 d 4.5.z.c.q.s 8 e e e 8 e h ;.7.4.K K.P.4X6X5X-X-Xh.g.v.h.g.v + O $ .k.&X*X+.g.|.}.}.9.B z Z Z g._.JXJXJXJXJX Xt a X.Z.J.P.P.P.P.^.4X6X6XN ;XeXZX|.JXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXGXPXGXFXFXFXFXnXkXgXS $X#X#X#X).PXJXOX/ / / / / W ( ^ ^ ^ ^ W R I -XwX-XOXvX].gX%X#X`. . .k.6X8X8X8X4XU H F F H Y Y T [.cXzXcXnXBXFXFXnXF Y R E E E E ( W ( / / / / W W U 6 j t N +.+.S S A B b d u l k k + k 1 : k C ).0.z l b O.h.h.h.k.h.g.O.x l .k.4X8X8X8X7X7X^.^.~.L.G.G.G.S.G.G.G.G.G.G.m.o 2 a I U U X.X.n.U ] n.N.C.S.S.G.G.G.G.G.B.i n.m.B.B.C.S.S.h.HXHXHXJXJXJXJXJXJX%XjXLXZXiXyXeX1XT.T.T.T.Y.l.4.5.n > < FXJXJXJXLX[.qXIXSXSXAXAXsXuXuXuXeX;XI.y.I.y.u.E.W.Q.eX1X}.T.q.& & & 7 8 e e e e s g g :.-.& $ o o o $ & 7 7 & ; u q s s 8 & o o O $ . @ ; q h 6.l.c.R.l.g 8 8 8 8 8 s g ;.6.5.v A.P.P.4X5X5X*X&XH.Z.k.H.&XH.Z.f.K.*X=X=X*XS ].}.`.9.k B C Z O.k.cXJXJXJXJXS - a ..f.V.J.J.J.P.^.^.4X6X=Xz.iX>XhXJXJXJXJXJXJXJXJXJXPXGX|.PXPXPXPXPXPXPXPXGXGXPXGXGXGXGXGXnXkX#X%.$X#X#X#X%.FXGXk./ / ] k.-XjXjXOXa D D Y Y k.5X*X*XOXvXx gX.;.m ;.4.6.}.vXvXcXkX).k zXGXDXPXGXPXPXHXGXGXGXJXHXFXcXhXkXkXzXzXvXcX%. = @ 5 1 k c . .A S A Z b b d z u r # < + : * x S _.S n l Z h.v.h.[.h._.g.g.S z c k.7X8X7X7X7X^.^.L.L.G.F.S.S.G.S.G.G.S.S.S.I - a K I L U U X.] ] n.m.N.B.B.S.F.G.G.G.G.B.i a.S.B.B.d.S.f.cXJXJXJXJXJXJXJXJXJX'.SXMXyX;X1X1XR.c.T.T.x.R.yXmX@Xg.V gXJXJXJXJXh.aXIXIXSXAXZXsXsXuXuXyX1XT.I.u.Y.Y.W.2XrXuXdXsX3X}.; & 7 8 e e e g g g -.-.,.g & & $ $ $ $ & 7 7 7 q d h ;.;.s ; $ $ & & & & 8 s h q.x.T.R.l.h ; 7 7 7 q e g ;.7.7.p D.J.P.P.P.K.P.^.^.L.G.C.Z.K.A.K.H.K.K.=X*X{.O.j.9.z x B Z Z S *XXXJXJXJXkXj 5 p ..b.n.D.G.J.P.~.~.^.^.^.!.dX'.JXJXJXJXJXJXJXJXJXJXJXcX#XPXPXPXPXPXGXPXGXGXGXGXGXGXGXGXGXBXkX%.+X#X#X#X$X%.vXnX] ^ k.FXGXJXPXHXGXPXjX{.o.| wX5X-X-X{.mXk |. 6 6 6 .+. . .Z Z Z B N b d u 0 # : : : : 1 N g._.S x S |.[.v.[.[.'._.).S S M 6 H.7X8X7X7X7X^.L.L.F.F.S.S.S.S.S.S.S.S.S.d.- t p K K U U U X.] ] a.M.B.B.B.S.S.S.S.G.S.n.p d.B.B.B.S.D.|.JXJXJXJXJXJXJXJXJXzX>XyX>X;X}.;XT.R.R.c.R.yXCXmX9X].C ).JXJXJXBXN VXUXIXSXAXZXsXuXsXuXuXeX1XT.T.R.W.Q.eXuXdXAXSXSX9Xs % 7 7 8 e g e g -.-.-.-.g 7 $ $ $ & & 7 7 8 y f ;.6.q.7.h ; $ & 7 & 7 7 8 s ;.q.l.c.l.l.h 7 ; 7 7 8 e e f 6.9.u f.A.K.K.J.D.K.^.^.~.~.G.D.K.A.A.A.K.P.P.K.K.&XO.z , B B C A h..XvXJXJXnX< @ a I f.b.n.V.S.F.F.G.L.~.~.L.v. XNXJXJXJXJXJXJXJXJXJXJXJXzXgXPXPXPXPXPXGXFXGXGXGXGXFXGXGXPXGXcXgX%.$X#X#X$X$X/.vXvXR F [.HXGXPXGXPXPXPXPXPXPX-XOX{.&XjXvXzXS k S %.].$X).h.k.h. .| K ^ / / / ^ / ^ ^ ! FXGXGXFXFXnXhX%X%X%.D D D F Y R W W W W T o.#XgXgXhXhXkXkX9.u.u.u.u.u.E.u.E.E.E.E.E.E.w.:.8.hXkX%X).nXFXPXPXPXPXPXPXJXPXPXJXPXPXPXJXBXvXvXcXcXcXS $X$X$X$X$X%XS oXvXvXnXFXFXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXBX].gXJXJXJXJXJXPXGXGXGXPXGXGXJXJXJXJXJXS cXBXFXFXvXcXnXPXPXGXoXJXJXJXJXJX", +"JXJX].p.p.{ p.{ { { Q ! ! ! L L J L L L ! _ { [ s.s.M.s.M.B.S.S.C.F.C.F.C.V.n.X.I v o.k.a b.~.L.G.J.L.G.L.G.G.G.L.G.L.L.L.J.L.P.=X=X*X&X.Xk.k.k.H.f.I c c v c c c k 5 c | O.PXGX X-XwXVXwXmXmX-X-X*XOX&X&X&X*X=X*X*X{._.O.v 6 < > > 6 6 6 .+.o.O.N Z C Z B B m l 0 0 k : # # # l Z A O.M A }.'.'.'.'.'._.).+.S Z V r K.7X7X7X^.^.~.L.F.S.S.S.B.d.d.d.d.d.S.d.[ * 2 i K L U ] ] U ] a.a.M.d.d.B.d.S.S.G.S.S.U L B.B.d.B.d.h.JXJXJXJXJXJXJXJXJXJXoXyX;X}..X;X;X;X;X;XyXKXCXmX9X XA C oXFXHXHX%X.XSXSXSXZXsXsXiXuXuXuXrXeX1XR.1X1X1XrXZXSXSXSXmXz.;.; ; 7 8 8 e e g -.-.:.:.-.e 7 ; 7 7 8 8 e g h 6.z.r.l.e.h & $ & & 7 & 7 e g g >.l.l.l.7.6.s 7 ; 7 q 9 % 9 b B 0 = b.D.K.J.K.V.D.G.L.G.A.D.J.J.L.J.J.L.P.P.P.K.H.k.r u A 9.)..X+XJXJX#Xk 5 3 K I ..b.N.C.F.B.S.S.F.G.S.a.9.,XJXJXJXJXJXJXJXJXJXJXJXJXcXkXGXGXGXPXPXGXGXGXFXFXFXBXBXFXGXFXkX].].$X#X#X#X#X).cXX;X>X;X3XyXyXCXSXKXVXxX-X{.S x z x zXHXHXoX,XyXZXZXsXiXuXiXiXiXZXiXeX1X1XrXrXiXZXIXVXVX@X4.>.s & & 7 8 e e e -.-.,.:.:.-.e e e e d -.:.q.l.l.l.l.l.6.g & & & 7 & 7 7 e -.-.>.q.e.l.l.q.h d q 9 7 q % , ; : ; * 5 Z.K.J.K.K.A.J.F.J.J.J.L.G.G.G.G.L.L.L.P.P.J.D.V.f.a n '.Z JX#XV 5 2 6 K I U U n.V.F.C.B.d.d.d.d.i.I S FXJXJXJXJXJXJXJXJXJXJXJXJXBXhXFXFXFXGXGXGXFXGXGXFXBXvXvXnXFXvXhXV %X$X$X$X#XS gXvX{.F 4 D O.GXPXGXPXPXPXPXPXPXHX-X*XoX#XoX#X 1 t t l 6 . . .+.h.+.g.A B y a u f u r V C 0 # # : , : # 9.}.`.j.`.`.`.].`.).+.A N x z a K.^.^.^.L.G.G.S.d.d.p.p.p.p.p.p.p.p.i.! z M M l K G L ! ! ! [ { s.s.s.d.d.d.S.d.d.p.~ = s.s.s.d.S.'.JXJXJXJXJXJXJXLXGX C zXHX).yXSXLXZXZXiXiXZXZXZXNXZXyXiXiXZXZXHXSXVX0X9.6.e.6.7 & & 7 8 8 e g g :.:.>.:.;.-.h h h 6.e.T.T.T.T.l.l.>.g % & & & & & - s ;.;.>.6.6.l.l.l.>.>.m b u q & 9 9 7 , ; % a n.K.J.D.J.L.J.J.L.J.G.G.G.G.F.F.G.G.G.S.G.S.B.V.C.M.K +XZ @ @ 2 4 K K ] #.m.m.V.C.C.d.d.p.i.' ` v h.h.k.GXJXJXJXJXJXJXJXJXXX`.%X#XFXFXnXFXGXFXGXGXFXFXBXcXzXzXzXgX%.].%X$X%X%X#X%.zXzX{.F D D D [.GXGXJXPXPXPXPXJXvX{.[.OX%X|.|.oXgXhXhXcXo.E I BX[.&Xk.j.R / / W ( W Y OXnXnXvXvXcX%XV +X#X#X#X#X#XO.D D F F Y Y +.`.).`.oX#X%X%X].;.6.4.4.,X jX'.VXVXmXMXmXMXiXMXZXZXLXLXDXMXNXNXNXCXVXwXv.;.l.q.6.7 & & 7 7 7 8 e ;.;.,.>.q.6.7.6.q.q.l.T.R.R.R.T.l.6.s $ & $ $ $ o $ 7 g :.>.6.>.q.l.l.l.l.z.9.A 9.b , 9 , , * * * * U V.V.F.J.J.P.P.L.L.L.L.G.G.G.G.G.S.F.F.S.S.S.F.S.M.[.gX5 - 3 4 G L ] [ s.d.S.B.B.s.s.p.' ` / a *X=XK.v.JXJXJXJXJXJXJXJXJXoXPX#X`.FXGXFXFXFXFXFXGXGXFXvXzXhXgXgX].`.%X%X%X.>.>.0.gXhXlXhX$X}.e.y.E.E.e.].#X$X%XX+X'.9.x 0 , + X @ S {.pXwXVXmXmXxX9X0XyXMXZXLXZXZXMXMXyXMXwX3XN m e.s 7 7 & & & & $ & 7 ;.6.w.l.T.T.T.I.T.c.T.Q.;XR.c.l.f & . O O O o o o & e g :.>.q.6.l.q.l.l.l.z.x.|.XX`.l ; ; % ; , ; , y K m.B.S.G.G.L.^.4X4X6XP.P.F.J.G.G.G.S.S.S.G.G.F.f.xXk 3 3 4 D T [ { p.s.d.M.s.s.p.' ' / ) I O.wX6X[.nXJXJXJXJXJXJXJXJXJX#XPXPXPXGXGXGXPXGXGXFXFXFXBXcXhXgX%X%.). > z A O.g.g.g.g.g.m ..L.D.a 1 l j 1 , + : 1 1 , 0 9.}.;X}.}.{. X.X{.k.o. . . .v v a 9 n.F.S.d.d.p.i.' ` ` / ' ` ' i.` ( p Z Z C XHX].3 D J ~ ) ' ' { i.i.i.i.i.i.` / ) ] p.s.p.s.d.].JXJXJXJXJXJXJXJX,XK.7XaXVXVXVXCXaXsXmXyX0X>X@X].9.b 0 = + @ @ 6 6XqXqXaXaXVXqX*X*X0XMXMXZXZXZXZXMX0XyX3Xj.h f m & $ $ & $ $ $ O $ 7 h 6.l.T.Q.eX2XQ.Q.Q.Q.;XR.T.;.* o o o . o o o o & 8 s g h ;.6.<.<.<.l.l.c.c.;X>XlX.0.zXkXkXgX%X$X+X%.XX#X#X$X%X + < z Z S 0./.+.0.5.v P.P.A.t 0 x k k 6 # : 1 : # B }..X.X.X.X&X.X&XZ... . .N v a I v 2 n.M.d.p.{ { ' ` / / ` ` ` i.i.` G V S Z %.JXJXS 3 F Y R _ _ _ _ _ ' ' ' ` ` / / [ a.{ p.p.d.[.JXJXJXJXJXJXJXJXJX@X6XqXwXxXMXyXyXyXyX>X;X;X.X}.9.b 9 * + @ = } 7XtXtXtXtX^.7X^.J.m.Z.yXiXZXZXiXiXyX>Xj.0 q d u O $ O $ $ $ O o O 7 s 6.x.Q.eXeXQ.eXQ.eX1X;Xl.u O O + + % + o o O 7 8 8 e q s g ;.>.6.q.w.l.c.;XyXxXBXNXhX% ; 9 9 u m 5.9.}.;X}.}.}.`.x.z.9.d 3 G L n.n.V.V.B.B.d.p.d.U +.jXjX .3 F Y E _ ( / / ) i 5 6 6 t | X.H.qX{.9XVX_.GXHXJXJXJXJXJXJXJXJXFXcXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXoXk ].].S oXhXnXGXJX].XXkXGXJXk.R / / / / / / / / / E I oX%XC H.8X6X=X=X=X=X=X=XF Y Y H Y Y Y G oX > r r 0 6 v M Z A 0.GXLXGX%.Z -X-XgXgXgXgX-X-X-X-X_.O.c c l r r , + + < z 9.9.j.j.9.5.a ^.P.J.n.6 0 M z k t 6 : # : : N }..X{..X*X&XZ.k.j.f.I } I I a v a 5 ] a.{ { ' _ ` / / / / / / ` ` E t S Z S JXJXHXv 4 F T R W ( ( _ _ _ ` ` ` / ` [ [ { { { s.k.JXJXJXJXJXJXJXJXJXhX5XwX0X9X9X0X0X0X>X;X;X.X;X.X9.b 0 ; O @ = Z.^.^.^.^.^.^.^.~.F.s.T 0XyXiXiXiXiXyX>Xx.B d f m 0 o o $ & $ $ o $ 7 u 7.T.2XeXeXeXQ.2XQ.Q.c.q o O O O * % * O ; q q e q q s s g h >.6.q.l.l.R.1XyXyXMXMX`.% , 9 b B 9.x.e.e.5.5.5.4.4.4.5.6.4.3 G ~ a.m.N.a.p.` _ U y `.$XhXwX*X> h.a 4 Y Y G 3 < x x x V .f.H.6X0XiXCXKX'.JXJXJXJXJXJXJXJXJXJXXXPXPXPXPXPXPXPXPXPXPXPXPXPXPXJXBXkXS FXvXGXPXJXJXJXJXS V %XGXo./ / ` / / / / ` / / ^ R | gX%X%.h.*X6X=X=X=XK.=X=X} Y Y Y Y Y Y T L {.jXgXhXzXzXgXK T R E E E E E E W W W T ! @.Z.+.F Y Y ].XX].,X$X'.z.9.5.h e ;.;.;.4.5.hXgX v v 6 J.L.P.4X4X8XaXAXVXSXVXAXSXSXIXIXUXUXUXPXPXBXmX9X-X[.h.t M S Z b 1 1 l x x Z S BXGX%.Z &X-XgXgX-X-XOXOX*X[.+. .Z C x l 5 1 k o % 0 n 5.7.5.5.5.f ^.P.P.J.b.a 1 x c l k j 5 # + : 9. X X{.{.k.k.f.f.I I I N v a a a p 6 L ] _ _ ( ( W / / ` / / ` ` / 6 O.S C DXJXHXGXC D F R E W W ( W ( ( ( / / ( ] { { { { s.H.NXHXJXJXJXJXJXJXJXlX*X5XpXlXyX>XyXyXfX>X;X;X>XXX9.b 0 ; > A $XO.^.^.^.^.~.~.~.G.G.d._ 3XyXMXiXZXiXMXyX;X}.z.6.7.e.z 0 O $ $ $ $ & - s 7.R.Q.eXeX1XQ.Q.R.T.r O o X X X + % O 7 s h f h f d s h f h ;.>.w.r.l.T.;X1XyXyX>X, , 9 d 4.z.c.l.b j : # # 9 9 d h h 4.y J [ [ p.[ p.i.i.K m u @X-X-X5Xh.cXoXx . + , 1 k z x V C A o.Z.*X@X>XyXCXKX].HXJXJXJXJXJXJXJXJX#X#XPXPXGXGXPXPXPXPXPXPXPXJXJXJXFXnXzXXXGXJXJXGXPXJXzX].oXGX+X..^ / / ^ ^ / / / ` / / ^ T {.hX%XS S #X[.k.H.=X*X=X=X&XG Y Y Y F Y Y Y Y X.#XkXkXhX#XF Y R E E E E E W W W ^ W W ^ W W W R I -X%X%X`.:.1.1.r.U.E.u.>.-.e 4.6.9.gXX;X.X}.g.b < * 1 ).).gXv.^.^.^.~.~.~.G.S.d.' .X>XyXiXiXiXyXiXyX;X.X;Xx.c.c.j # o $ $ $ ; 8 f e.T.Q.Q.1XQ.R.R.x.9 % o . X o + o + % s m 4.5.7.5.4.4.;.>.;.;.>.w.r.l.T.Q.1X1X;Xy * 9 u ;.7.c.x.4.k x 0 , : , 0 e f m ;.h 2 ( _ ) G 2 ) G y B b 9X-X-X5X].Z = = = = @ > : k x Z A g.[.k.>X;X>X>XNXPX].HXJXJXJXJXJXJXJXgX).PXPXPXGXGXGXPXPXPXPXPXGXGXGXFXBXcXhX$XGXJXJXJXGXXX%XPXPXPXgXR ^ / / ) Q ~ Y Y H Y R W L zXgX%X%.C +X$XC |.N o.k.K.*XZ.F Y Y Y H H Y Y T L hXzXhX#XL Y R E E E E E E W W ^ ^ ^ W W W R I oX#X#XZ <.<.t.t.t.u.u.u.u.u.u.,.-.h 9.oX].nXnXnXvXnXvXvXnXvXhX].oXzXgXC kXzXhX].5.q.r.r.u.u.Y.E.4.;XXX|.q.e.e.w.e.9.S .I K p a a p K p K I @ 3 L J H H ( / / / / / / / G A O.S #XJXJXJXJXGXS D F Y R R W W ^ ^ ^ ^ ^ P [ { { { d.F.L.k.@XJXJXJXJXJXJX].BXSXZX0XyXyXyXyXeX;X;X}.}.`.0.b > + > = * x }.K.~.~.~.G.G.S.d.p._ v.>X0XyXyXiXyXsXiXyX9X>X;X;X;XB k $ $ $ & 7 s h q.T.Q.2XR.R.T.e.* % O X X o O X O X 9 f 5.z.c.T.x.l.e.q.6.6.:.:.6.q.r.T.T.R.R.5.$ , q d 4.z.x.7.n j , : # , 9 q d f f h m d 3 H 4 y B , 0 b B B {.*X=Xk.S @ = 2 2 3 4 i a 6 x C O._.}. X;X;X>X>XyXHX].JXJXzX|.].hXJXoXoXJXPXPXPXFXGXGXGXGXPXPXPXGXFXnXnXvXzXhXoXJXJXJXGX`.NXPXPXPXPXk./ T o.-XzXFXHXbX[.J T H T h.jXhX$X).%X1 1 gXGXgXk.&XZ.+.jXjXU H Y Y Y Y Y H R OXzXhX$X#X%.D H E E E W W W W W W W R R T o.BXS oXC :.<.1.t.t.u.u.u.u.u.u.u.u.y.r.:.`.vXBXnXnXvXvXvXvXvXhX`.$X+X}.}.}.z.}.}.7.7.6.q.r.I.Y.u.Y.T.}.+X,X6.6.q.e.e.e.S X>X;X+X.Xj.Z c < * > : @ @ 9Xh.D.G.G.S.F.d.p.' [ +X.X>XyXMXiXMXyXsXiXMXlX0X0X>X0.% $ & & 8 e f 6.l.R.2XQ.Q.R.T.y O % O O O O + O O o q m j.v.c.;X;XR.x.l.6.4.:.4.:.6.e.l.T.;XT., ; 9 9 d m 5.e.4.f 1 , , , 9 q b e f b m h h d u b B 5.b , u b 9.O.*X[.zXV @ 2 3 p K K L K I 5 C Z S g.}.@X>X0XyXyXCXnXoXGXJXPXJX$XS oXPXPXPXPXPXGXGXPXPXGXGXGXGXFXFXnXvXcXhXgXS GXJXJXC GXKXPXPXPXPX] Y '.cXFXHXPXJXPXHXFX{.k.{.kXkXhX%X).kXx $XDXcX{.=X*X*X{.GXKXPXo.F T R Y Y W R -XvXgX%.oX#X%.G 4 D D F Y Y T T P U o.OXcXPXPXBXV -.<.<.t.t.u.u.u.u.E.u.u.u.u.u.u.y.8.nXnXvXvXcXcXcXcXzX).|.R.;X}.T.}.}.x.}.7.4.h w.y.u.Y.Y.e.yXr.w.`.4.8.4.6.r.e.V %XzXJX : @ @ `.LX@X'. .v v a c .X>X.X.X>X0XpXmXwXtX8X8XyXbXMXlXyX9.% & 7 8 e s ;.e.I.Q.Q.eXQ.R.l.$ O O O X + % + $ % o u 5.z.c.;X;X1X;XR.l.6.:.4.;.6.7.e.z.c.R.6.% , 9 q u b 4.7.5.f 9 , 9 9 9 u f b f h ;.m h b b b n B x b : l 9.k +XGXGX6 = 2 D G ! ] ] ] ] K 6 C C g.'.>X9XyXyXMXSXUXoX].PXJXPXPXPXPXPXPXPXPXGXPXGXPXPXGXGXFXFXFXFXnXcXkXhX c x O.4X6X8XqXqXVXVXVXSXSXSXIXIXVXVXVXaX6X6X=X=X=X=XK.H.H.k.h.B u + # + : , , k M 9X-X).V 1 > 1 c v +.{.{.{. Xz l B b 7 0.JXJXJX|.b N .X.X}.c 4X4X4X7X4X4X4XP.=Xk.O.: 6 6 j k k x n y ..f.f.X...X...X.X.U I L I L K l k k x v t D E / / / / / / / c +.S JX%XJXJXJXJXJXGXoXK F R W W W W W E R W ^ T G s.M.B.S.F.L.L.g.JXJX @ S JXJXJXg.>X}.7.b < $X-X-X3XwXmX6X8X7X7X4X4XwXCXMXlX7.& 8 8 e e g >.e.T.R.1XQ.R.x.m + o X O O O $ $ O O O f 7.}..X;X;X1X1XR.l.7.4.4.5.5.9.9.9.j.z.9 , , 9 0 d f 4.6.7.4.h 9 0 9 u q f f m m m m b m b b b n B C x r M `.JXJX$Xk 2 4 D D ! ~ ! ~ Q [ ] 6 M A ].$X0XMXMXMXIXUXUX.X[.#XPXPXPXPXPXPXGXGXPXPXPXPXPXGXGXFXFXFXBXvXcXhXX(.1.u.E.E.E.E.9.zXzXzXkXkXkXhXhX l x v =X=X=X=X=XaXaXVXSXSXSXSXAXaXqXqX8X6X6X6X4X=XP.P.A.f.k.0.z ; + % : > : : n >X>X0.k , < k l c O.]. XOX Xk * N d 9 O.JXJXJXJX0.5.OX.X}.5.k.^.4X4X7X7X7X7X=XK.k.z < j j k k x z r } f.f.n.X.f.@.@.X.U U I U I L N 1 r l V 6 Y W ^ / / / / / / v +.Z GXGXJXJXJXJXJXJXJXZ F R W W W W W W E ^ / / Y ] s.B.C.F.L.L.o.g.h.*XtXtXpXaXVXVXKXKXVXqX6XP.P.J.J.n.b.I a * + : # @ C GXJXGXC OX}.5.z * g.9X-X5X=X=X=XP.P.L.P.P.P.mXnXlXb 7 7 8 e e g >.l.T.R.1X;XT.7., X o O O O O O O o o 9 m 9.v.}.;X;X;X;XT.l.e.7.7.9.z.9.9.9.9.e.* ; , q l d f 4.5.7.5.4.e q q u d f h m ;.b m m b b b n n B Z S C M @XJXLX0.5 2 4 D J ~ ) ) ) _ { { 6 M S {.-XmXmXKXHXKXUXUXaX6X8X_.PXPXPXPXPXPXPXPXGXPXPXGXFXFXFXFXFXnXcXkXgXX@XPXPX].hXhXzXcXzX).].].#XhX k x x {.h. .O.[.5XaXAXSXSXSXAXaXqXqX6X4X4X7X7XP.=XP.P.Z.f.j.A u + : : < 1 # 9.>X}.x 1 : < 1 k x O._. XoXoXg.* } ..< C JXJXJXJX).'..X}.R.c.v K.P.=X7X7X8X7X4X4X&X+.1 r 6 1 k u l 0 a I f.b.n.b.n.n.n.n.U U U L P +.g.j l V D E W / / / / / / / a +.v GXGXJXJXJXJXJXJXHX].D R E W ^ W ^ W ^ ^ / / / J s.M.B.S.F.J.b.4X^.7X^.tX8XtXaXaXaXaX6X7X^.P.J.J.V.b.X.} c ; % + + @ r oXJXJX|._.'.A l 9 N 0X*X=XP.J.G.G.G.G.L.4X=XMX,Xj.7 7 7 8 8 e f 6.v.;XR.R.T.7.u . O O X O O o % % % f 5.6.e.z.x.x.c.;XT.e.e.e.x.x.}.}..X.X}.}.7.d : 9 q q d f m 6.e.7.5.m l d d f b f d b f b b b b b n u x n x x ).HXJX9Xt 3 3 D F T Q _ _ ' ' ! 2 k v h.*X-XmXVXKXSXIXUXSXqX6X6X1 PXPXPXPXPXPXPXPXPXPXGXGXFXFXFXFXBXvXzXhXX;Xl : : 1 1 k c Z _.]. X|.g.r 6 Z.v 1 X>X3X0X-X*X}.7.; 9 0 e b b m 6.7.7.e.5.b m b d b d u b m d b u l b 0 n x B A C FXHXoXz 3 p D D H T _ _ ` _ G * 5 z .k.*XwXVXKXSXSXSXSXVX8X4X7Xj PXPXPXPXPXPXPXPXPXPXPXPXFXFXnXnXvXcXhXX.X>X Xm m 4.m e.x.zX S JXJXJXJXhX[.AXSXAXAXaXaX8X7X4XP.=XP.4XP.P.L.L.J.V.b. .g.n 1 * : : 1 ;X;X>Xg.0 j : 1 6 l V O.].'.}.j.B , .Z.r 6 kXJXJX0.v.x.x.z.l.e.7.v Z.H.^.4X4X4X4X4XH.k.r < 0 0 0 q u 5 } f.b.n.n.D.V.V.m.a.n.U U P ] _.kXx v 4 W ^ / / / / / / ( I o.K n._.JXJXJXJXGXGXPXHX_.F E W ^ ^ ^ ^ E H G G ! p.{ M.d.B.S.F.G.f.^.^.^.^.^.^.^.^.^.^.~.L.L.L.F.V.f.f.| N 0 * , 7 , , < < 1 S vXLX`.).Z M r A.s.' ' i.i.i.p.p.p.K.0X}.q e e q 7 8 q b j.v.3XqX=X=Xz.q 7 & o O X o * q b 4.7.l.r.l.l.w.6.6.6.6.6.5.7.c.>X0XyXwXsXaXwX5XK.z.4., q b q d b 4.4.7.7.5.m m f u 0 u w u d b d l b b n n V 0.C zX+XC # a p K L F R R _ { { J # > 6 z | Z.=XwXVXVXVXSXSXAXpX7XP.K.#XPXPXPXPXPXPXPXPXPXPXPXPXGXFXnXcXcXhXgX%X%XC ].$X).V cXPXPXPXPXPXPXk.) H / / / / ^ W ^ E ! XhX{.6X6X6X=X=X+.].OXOX#XL W E F I hXjXzXzXcXcXcXzXXXD Y R R R E T K oXGXPXPXKXO.4 F K ].|.k oXBX_.oXFXNX;.h g h ;.;.vXPXPXPXPXS ].%.%.+X#Xx .6.;.4.4.z.}.3XMXZXZXaXaXtX7X=Xj.7.q 9 q b b f ;.m 4.7.7.n b b u 0 9 k u u u l b b n l V S ).oX#X1 : 6 K K I U ] [ [ [ { s.X.t 5 6 c o.K.6XqXVXVXKXVXaXaX4XP.J.3XPXPXPXPXPXPXPXPXPXPXPXPXPXPXJXBXzXkXgXXyXyXiXiXpX3X5X3Xz.z.7.; q q d d d d m 4.8.4.b b d u l x b b u 0 b b 9.Z GXJXJXJXS < > a I K U U $.[ m.M.M.B.b.@.X...| Z.=XqXmXmXVXVXaX6XL.J.N.-X].PXPXPXPXPXPXPXPXPXPXPXPXPXPXGXBXcXhX.6.q.7.7.b < K.P.4X^.7X^.P.P.f.t 9 0 l z b 5 I f.n.D.D.V.V.V.n.] U U U [ I GX#Xv x 2 W / / / / / / ` N o.+.Z.N.M.+.GXGXJXJXJXJXJXJX_.# = 3 3 p i G L ~ ~ { { p.s.d.d.B.B.f.NXv b.S.~.~.~.L.L.L.G.G.F.C.V.n.k.f.M z r 0 u 0 u u 0 q l x V C `.JXJXJXJXg.V.d.M.C.V.V.D.H..X;X;Xq.d . ; s d h B h..XwX6XC.A.N ; e s s % & s N z.x.T.R.T.I.l.r.w.w.e.6.5.7.T.R.eXyXyXrXeX3X3X3X;X7.e.z.0 9 q 9 9 7 , b m 5.4.m b b n B 9.B V V 1 b 9.[.$XJXJXJXGXC : 5 a I ..U ] n.n.m.M.s.M.m.] ] ] [ K.K.*X-X[.+.k.P.J.A.k.9XHX#XcXPXPXPXPXPXPXPXPXPXPXPXGXGXFXnXzXhX$XS %X%XS GX|.PXPXPXPXPXPXPXHX0X) -XPXPX&XT Y H / / ^ ^ W T H.+XFX].k $X$X$X$X`.+.F F D F Y F [.zXhX%X#X).S K F Y R R R R R R R R R R R Y o.FXnXFXFXhXgXgX`.;.<.y.t.u.u.u.1.-.e ;.9.PXJX+Xk V ].hXkXoX$XzXcXvX%X.XuXuXuXuXuX;XdXyXpXdXdXdXdXdXdXdXsX;XT.m ;.+XgX u z b n 1 a X.b.b.D.n.n.n.n.@.@.] ! ~ 4 v k c x x 4 ^ / / / ` / / K Z.o.&XZ.A.N.|.JXJXJXJXJXJXJXJX].a 3 6 4 G G J ! ~ [ { { p.d.B.d.d...JXLX.Xj.D.G.~.L.L.G.G.F.C.A.V.Z.k.+.Z n b l l b d l b l b V V n `.JXJXJXJXNXk.V.V.Z.H.*X*X*X3X;XR.m f o ; q d f B g.{.*X*XA.H.9.; e e s 8 . ; y b 7.x.c.T.T.l.I.r.r.w.q.6.q.e.T.R.1X1XeX1X1XeX1X3Xc.q.z.z.4.& , , : # + & q 0 b b b m B 9.0.9.`.). .r }.].JXJXJXJXJXM # t I U X.U b.n.m.m.a.a.a.[ ] [ [ _ $.v H.K.K.P.=X=Xk. XJXJXJXGX].PXPXPXPXPXPXPXPXPXGXGXGXFXBXvXzX S FXJXJXJXZ c =XwXqXqXqXqX8X6X=X=XH.K.P.P.^.P.J.Z.f.k.k..X.X.X.Xc.c.z.5.y r t * : 0 r l l x V x x r 6XqXqXqX=Xc + ; f h h ;.>.4.6.7.5.B r K.P.L.^.7X^.^.K.Z...< x M < X a } b.b.n.b.n.b.] ] ] ~ ~ D D J J 6 v x x 2 W ^ / / / / G o.+.{.&XV.$.+.GXPXPXGXHXPXGXJXGXc t 3 K G G L L ! Q [ [ [ $.d.d.d.U FXPXJXJX#Xh.H.G.G.F.S.C.F.D.Z.Z.f.j.O.Z Z n n b n b n M v V x x ).HXJXJXJXJX9Xk.H.H.*X-X-X>X3X;XT.6.s 7 o ; 7 9 q b A h.*X*X&X.XA ; e s e q $ $ 9 d B z.x.c.l.l.l.1.q.q.6.6.e.e.T.T.1X;X1XR.Q.1X1X1Xx.e.z.z.9.* % + + + , : , 9 u b m 5.9.0.0.`.}.{.k.M A GXJXJXJXJXJXn * a } ..@.b.n.m.m.m.a.[ [ { [ _ { _ K k.H.K.=XP.k.].BXJXJXJXJXJX%XhXPXPXPXPXPXPXPXGXFXFXBXBXvXcXkX#X%.$X%X$X%.kX#XPXPXPXPXPXPXPXPX-XR jXPXPXPXPXFXH.J R ^ ^ T vXOX&XBXkX).$X#X%X%X%X a } ..X.U U U U U ] ! G J R T R 4 z x 5 D R ^ ^ / / / J o.+.+.*XH.N.N.oXGXHXPXHXGXGXJXJX%.4 4 p 4 G G G ! ! _ [ { { d.s.s.X.cXJXJXJXJXJX-Xj.n.d.B.B.B.M.m.b.f.f.h.h.g.A N Z M M M C C C x k %.HXJXJXJXJXJX].&X-X-X-X0X3XeX;XT.R.z.h q & $ & ; y m j.{.OX*X.Xj.9 e q e e $ . ; 9 d 4.e.l.e.1.<.q.>.6.>.5.e.e.e.T.T.R.W.R.R.R.1X;XT.x.}.c.9.9 . % 0 B B Z V q b m B B 9.9.9.9.'. X.Xg.cXHXJXJXJXJXDX1 5 v } X.@.m.N.M.m.] $.[ [ { _ _ _ _ K Z.H.P.H._.nXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXGXFXBXnXnXcXcXhXgX].%.#X%X$X%.JXXXPXPXPXPXPXPXPXPXX3XdX9.u.nXJXGXXXcXcXcXkXzXkX).S %.zXnXBXBXcX].oX > 1 1 k j -XwXwXaXaXwX*X> * 9 q h ;.6.5.j.5.9.B l a J.J.L.^.P.P.J.D.n.@ % # + + O * 5 p I I L K J i # K Y R Y R i x c 3 Y E W ^ / / / G o.O.+._.&X$.$.+.GXPXGXHXHXPXHXGX_.i 4 p 4 G G G L Q _ [ { M.M.s.p.n..4.;.N 9.9.e.z.I.T.T.T.T.T.R.;XT.T.}..X}.0 O * b 5.0.0.`.V m f m 8.5.4.5.9.`. XOX[.].JXJXJXJXJXJX: l | o.f.n.m.M.$.$.$.[ [ _ _ _ _ ' ` K k.H.J.[.HXJXJXJXJXJXJXJXJXGX`.PXPXPXPXPXGXGXFXBXnXcXkXgX%X%X%XS oX$X#X].JX].PXPXPXPXPXPXPXPX-X) jXPXPXPXPXPXPXPXPXvXnXPXPXGX*X XS $X%XgXgXgXgX 5 j k O.wXVXwXVXqXwXk.* ; 0 h 5.5.7.z.j.g.9.n l 5 A.J.L.P.L.J.J.V...X + : + * * # + = y t v M O.h.a F Y F Y i v x 4 E W W W / ` / 3 .O.O.+.{.Z.N.o.kXGXGXPXHXGXPXPX).p p 4 p G G G J ! _ [ { { s.p.s.a. XJXJXJXJXJXJXJXJXh.i.i.' p.s.[ b.b.b.k.k.f.h.+.N c l t c c j 1 %.PXJXJXJXJXGX< >XhX9XyXyXyXeXeX2X1XeX;X>X>Xm . ; s 6.j. X-X9X9X_.7 q q 8 $ . O 9 u f 4.>.6.6.>.;.;.m M 9.j.z.e.l.I.I.I.I.T.T.T.T.T.;X>X>Xz O 7 e 9.9.}.}.}.b f m m m m B 9.x.|.@XoXZ JXJXJXJXJXJXj c .f.b.V.N.M.$.$.[ ] ] [ _ ( ( _ ` | Z.H.+.BXGXJXJXJXJXJXJXGXGXPX%XPXPXPXPXGXFXBXBXnXcXhXgX%X%X%X%X#XS %X%.FXGXnX].PXPXPXPXPXPXPXOXR cXPXPXPXPXPXPXPXPXPXPXPXPXPXOX[.`.%X 1 6 l l 0XwXaXaXaXwX=X .% q B 6.z.z.z.x./.g.0.B , y ..F.J.L.L.J.C.@.r z > : # : 1 1 1 1 x C %.+.).Z .5 6 2 @ t z F ^ ^ ^ / / / J a .O.Z O.+.{.$.$.g.GXPXGXPXGXHXGX].a p 4 4 4 G G G ! Q _ ' { { p.i.p.h.HXJXJXJXJXJXJXJXbXF.' ` ' { [ ] b.b.f.f.f.f. .v i 2 = 2 2 6 : /.GXJXJXJXJXBX0 }.hXlXMXyX1X1XeXiXyXyXyXMXCX>X8 7 ;.6.z..X,XjXxX.X9 8 8 8 $ . O 7 9 d h 4.6.;.h f n V Z 9.x.x.x.r.y.e.l.l.l.x.T.x.;X>X0XM % 7 f 5.z.;X;X+X4.q m m m m 4.7.x..X%XOXZ JXJXJXJXJXJXl c .f.b.N.a.$.$.[ [ [ [ [ _ _ ` ` ) .Z.Z.k.vXJXJXJXJXJXJXJXJXGX+XGXPXPXPXPXGXFXBXnXzXkXhXgXgX < l b n V C C x x x %.6 F R E Y 2 J ^ / / / / ~ 6 v O.A C .+.[.| $.| GXJXJXGXPXPXGX].v 3 4 G p 4 G J ! [ _ ' ' { { i.p...HXJXJXJXJXJXJXJX Xs.' ( ` _ ) ] ] f.f.f.f.f.o.I y = @ @ < : + x FXJXJXJXJXhXb l ;X>X>X;X1X1XrXiXiXiXZXZXLXyX5.8 h 6.q.l..X0XlX0X@Xu 8 8 7 $ o . o o & 7 q h h g y n B 0.0.`.}.}.}.;X}.T.T.c.7.z.9.9.}.,XMXC % q f 7.z.}..X>X9.l e b m m 5.9.}..X#X XZ JXJXJXJXJXGXr c ...n.a.a.[ [ { [ Q Q Q _ _ ` ` { Z.Z.V.V._.JXJXJXJXJXJXJX].).GXPXPXPXPXPXGXFXnXnXzXkXhXgXgXgXgXgX%X%X%.].JXJX].kXBX).#XPXPXPXPXZ.T JXPXPXPXPXJXJXPXPXPXJXGXnXcXgX*X{.gXhXjXOXT ^ ^ W ^ / ^ ^ / W F E T J h.#X%X_.].FXGXo.F G G F _..>.4.x.5.7.z.m x.kXBX4.z.;XR.c.c.;XeXc.$XFXGXHXPXJXGXnXkX).V V S $X%XcXvXcX|..X;XuXuXsXsXdXsXdXdXAX3X+XPXPXXXy.0.|.`.+XkX%.FXJXJXJXGXPXGXGXGXPXPXnXS $XkXnXBXFXFXGXPXPXPXPXPXPXPXPXPXPXPXPXPX].GXGXJXJXGXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXvX .d.d.d.d.S.S.S.F.F.F.F.F.J.J.J.V.b.X.} K M ].JXJXJXJXJXJXJXJXcX: c v a f.Z.K.P.4X7X4X=X4X6X-XmXmXnXBXcX0X>X>X[.g.V l > j x k k k j wXqXqXqX6X5X=XH.H.H.H.K.8XqXaX5X3X;X}.x.x.c.T.c.c.z.v qX8X5X*Xt + @ : : > < r 1 OX-XwXwXqX6X5XH.N b 9.j.}.}.}. X X'.0.0 b b 9.M } N.N.U t '.'.O.C x c x < x n M M V z x x 2 R W W ^ / E E / / / ^ ) * > 6 t C C Z O.%.| #.K HXJXJXPXGXGXPX).c 4 4 3 4 4 G L ! Q _ ' { { ' ' p.] gXJXJXJXJXJXJXBX} { { ' ( E ~ [ ] n.f.U b.X.} a 5 @ @ @ 1 : + x JXJXJXJXJXgXd 9 x.;X;X;XR.1XrXiXZXZXLXIXLXKXz.>.,.w.l.I.T.>XyXlX@Xu q e 7 O o o . $ & y u y m 9.0.`.}.|.oX;X;X;X.X;X;X.Xx.j.9.5.9.+XhXx * q h 7.e.}.;X>X;Xb q b m m 5.9.x.oX#XoXA JXJXJXJXJXFXk c | ..b.a.[ [ _ _ Q Q Q Q _ ` ` _ p.m.N.N.M.X.vXJXJXJXJXJXoX].PXPXPXPXPXPXPXGXFXBXnXcXkXkXhXgXgXhXgX k k k k k .qXqX6X=X=X&XH.k.k.k.Z.4XqXVXVX0X>X}.c.l.x.c.T.c.c.j.qXqXwX*X*X: # : : : > 1 r v -X-X6XqX8X6X=X&Xj.g.}.}.|.;X.X}.h.O.r m B B 0.9.v I l x.`.).).O.C Z O.O.r l M n x x x x D R W ^ / / / / / ^ ^ Q i % 5 K a M C C C +._.I U cXJXJXGXGXGXKX%.6 2 D H 3 G G ! Q Q _ ' _ ' { { { p.h.JXJXJXJXJXJXS Q ' ' ' ( ) [ [ ] ] ] X.] X.I a a 3 = > k : + j JXJXJXJXJXcX0 q 9 x.T.T.T.;XuXZXLXIXPXIXKXCXz.6.>.r.r.x.R.;X3X9X>Xb q d q $ . . o . . o $ 7 d 4.5.z.x..X;X>X>X0X,X>X;X+X;X>X;X}.j.B 9.`.|.# ; s m 6.e.x.;X;X;X7.b e m B 4.9.z..X@X XS JXJXJXJXJXJX1 a v U U [ ) _ _ Q Q Q Q Q _ ( ( _ ' M.M.s.{ { '.JXJXJXJXJX%XPXPXPXPXPXPXPXGXFXFXBXnXnXcXkXhXhXgXhXgXgXgX.-.-.;.:.-.+XPXPXPXPXPXPXFXFXzXx OXv /.fXhX7.e.4.m w.7.z.j.z.x.R.6.U.w.4.m c.R.c.v.c.x.z.x.9. k k l &X5X5X*X&X&XZ.k.h.k.k.Z.qXVXKXmX3X.Xc.z.z.c.}.}.z.H.qXqXqX5X-Xl @ : : * * < 0 0 g.*X5X4X6X6X=X&X*X X|.|.}..X@X.Xv.O.r B b B A B B B 9.9.`.`.`.`.+.).g.h.o.x l x b x x n 2 T W W ^ ^ ^ ^ W E G a x @ t K K x C C O.S | @.U jXPXGXPXGXGXGXx 3 R E D 3 G J ! Q _ _ _ ' { ' p.{ p.o.LXJXJXJXJXJXZ ^ / ( ( ( ( _ ) [ ] ] ] U U K a t 5 = l r > # l JXJXJXJXhXDX1 9 4.T.T.R.R.1XMXZXZXIXLXLXZX0X7.e.<.<.w.l.I.T.;X;X;Xj.d f d % . . o o % m 7.7.z.c.}.;X>X;X0XyXfXyX0X>X0XfX3X>X@X.XN 0 % & 7 s 4.7.x.T.;X1X;Xz.5.f m B 5.9.}.OX@X}.C |._.FXJXJXJXM c a I ] ~ _ _ Q _ Q Q Q Q Q ( _ ' ' s.s.s.{ | FXJXJXJXJXJX#XPXPXPXPXPXJXGXGXFXFXBXnXnXvXzXkXhXhXhXhXgXgXgXgXS GXoXcXzXkXX}.9.z k 0 1 : # : 1 l .*X*X&X[.[.k.h.+.+.o.=XVXSXCX0X;Xv.x.z.c.c.}.9.=X6XqXqXwX*Xg.+ > : : % , 0 9 < {.-X6X6X6X=XH.&X-X X]. XOX@X*Xk.h.t B b b m n b b N 5.j.j.'.}.].[.[.h.k.+.k l x n x x c 3 R W ^ ^ ^ ^ D 6 v v t 6 a } K x C O.O.O.v #...FXGXPXPXFXPXkX@ F Y E 2 G G J T Q _ _ ' { ' { ' { p.h.JXJXJXJXJXJXFXn./ / ^ ( ( W E Q ~ ! P K p 6 3 6 t t t 6 > + C JXJXJXS 9 b z d I.T.R.1X1XeXuXiXZXZXLXiX>X}.>.q.>.<.<.w.e.x.c.R.c.x.m u s & . . X o O B 7.7.z.c..X.X;X>XyXyXyXyXiXMXyX5X3X3X5X*X*Xk.* & 7 q f 4.e.x.R.;X;X;Xc.x.B 4.5.9.j.{..X*Xg.0 l j /.JXJXJX).a p L ! ~ ) _ Q Q Q Q Q Q Q _ _ ' { s.p.{ [ -XPXJXJXJXJXJX%XgXPXPXPXPXGXGXGXFXBXBXnXnXnXcXzXzXkXhXhXhXgXhXgX#X).#XzXkXhX#X].GXGXGXGXGXPXPXPXPXPXFXnXkXgXhXkXgXoX]..,.:.#XvXvXnXBXBXnXnXgXg B 5.7.c.e.I.I.T.hXGXPXPX].X}.j.B k 0 u 0 < % + + 1 1 c v M Z O.O.A Z O.+.wXVXKXxX>X}.x.x.}.;X.Xj.P.8XqXqXqX5X_.: < , : , , , % , r -X5X8X8X4X*X&X&X X[.{.-X*X3X*Xh.r m z u u u d n b B 5.9.z.|. X X.X{.{.h.+.r n V V z l k 4 R W W ^ 2 6 c c c 5 6 I ..| k C O.O.O.K #.o.GXGXPXGXPXPXx 3 4 D E D G F J ! Q _ _ ' ' ' ' ' p.{ [.JXJXJXJXJXJXJX#XJ / ^ ( W ( E R Y L K p 6 5 2 5 6 t a 6 > : |.JXJX].u d d f 6.x.T.1X1XeXuXuXiXiXiXyX>X}.7.g <.,.>.<.w.q.e.l.T.x.x.e.u 9 & . o $ O o X y 7.e.z.x.c.H.H.*X0X0XiXMXZXCXqX6XP.P.J.J.A.A.A.X.- q s h 4.z.T.;X;X1X;Xc.}.c.j.}.}.v..X&X{.h.0 r k k ].JXJXoXt K G ! Q Q Q Q Q Q Q Q Q Q _ ' ' ' p.[ o.9XJXJXJXJXJXJXJXJX`.PXPXPXPXPXJXGXFXBXnXnXnXnXvXcXzXkXhXhXhXhXhXhXhX%.#XkXzXhXS FXGXGXFXGXPXPXPXPXPXPXFXBXnXkXzX#XZ ].%X.1.u.u.E.E.E.u.t.,.-.e e ;.%XJXnXcXV 0 8.X Xj.9.b k u l k 0 , + + 5 k + x 6 > : @ k N .k.VXKXBX9X;Xc.c.}..X;Xg.4X8XtXqXtX8X6Xl < < , , < , d q % O.5X8X6X4X=X*X*X&X{.{.-X-X5X*X .r b u 9 0 u N 5.5.m m 5.9.}. XoXOX{.{.H.+.r M x V l k x N a J R i | c z c x 5 6 } ..} t Z O.O.O.@.#.+.PXGXGXPXGXBX> 3 D F Y T J ! T T Q _ _ ' ' ' p.{ { p.[.JXJXJXJXJXJXJXJXzXO.P ( ( W W Q ! G G 3 2 2 = 5 6 i t 5 = 1 BXJX+Xz b m h h e.x.;X>XeXeXrXuXiXiXyX>XR.z.;.e -.-.>.<.<.<.6.q.e.r.e.e.4., $ $ ; % O . % 6.7.e.z.c..X>X0X=X-X9XlXCXCXqX4XP.F.B.M.d.M.s.M.m.3 y s h 6.l.R.;X1X;X;XT.R..X@X>X-X&X&X&XH.{.z u l l k XXJXBXz p L ! ~ Q Q Q Q Q Q Q _ _ _ ' ' { v '.HXJXJXJXJXJXJXJXJXJX S %.XXXX).FXhX+XXX|.XX%X : : t C O.*XCXCXxX>X;X}.}.;X>Xj.4X8X8XqX8X8X8Xt : < 9 1 % u s q 7 < *X5X8X6X6X=X*X*X*X{.{.*X{.o.r d u 0 9 0 z B j.j.9.5.m 5.j.|.|. X X.Xk.N z V x x k k n Z o.f.K } X.t c v 6 6 a | ..| x O. .o.&X#.#.k.GXPXGXGXKX#Xa 3 D Y Q E Q Q Q Q _ _ _ ' ' ' ' p.{ p..XJXJXJXJXJXJXJXJXJXJXzX+.~ ( W E T J G 2 2 2 2 3 6 i i 5 @ A JXjXB 5.4.4.4.6.x.T.1XeXeXeXuXuXuXyX>X;Xx.6.q q e -.:.:.:.;.>.>.q.w.l.l.q.f $ $ $ o o y 6.l.e.x.;X>XlXxXwX6X*X-XxX0X1X3X!.^.G.d.s.p.s.s.s.m.5 s g m 7.l.R.;XeX;XT.x.c.}..X0X.X&X{.Z.Z.k.u b b b l x %XGX%.G G ! Y ! Q ! Q ! ~ Q Q _ _ ' ] O.vXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXJXFXFXFXBXBXnXvXcXhXhXhXzXkXzXkXgXhX).PXGXJX].# FXPXPXPXPXPXPXPXPXPXPXPXPXGXFXBXnX).oX$X$X%X$X$X > + z .+.-XCXFXxX0X;X.X+X>X0.^.8XtXtX8X8X6XO.: , < : , 0 q 9 7 % O.=X8XqXqXqXwX=X-X&X+.v 6 r b b l 0 9 0 b 9.c.x.z.5.B m 7.j.}.|.}.{.k.z M n x x z x v .o.k.f.f.| r c c 6 t a ..| K v | O.o.o.&X#.h.PXPXGXGXPX|.a 4 F T E _ E E E Q _ _ _ _ ' ' { ' { ] kXJXJXJXJXJXJXJXJXJXJXGXJX X] W E R H G 2 2 4 i G G i i 2 6 gXnXg.z.7.6.4.7.z.T.R.1X1X2XeXrXuXuX1XR.T.q.s & ; e -.-.-.h g h B .k.o.l.6.6.;.f h h 6.7.e.l.T.;X>XxXFXCXwXqX6X-X0X1X;XeXyX5XL.S.p.p.p.s.p.m.9 s f ;.e.T.R.1X1X;Xx.z.x.}.;X.X-X.Xk.k.f.k.u b n n n z V hXhXa G J ! T ~ ! ! ! ! ~ Q Q _ V.gXJXJXJXJXJXJXJXJXJXJXJXJXJXJX$XkXPXPXJXFXFXFXBXBXnXnXcXzXhXX@XBXBX)./.$X%X%XXX;.-.x.1XuXuXsXuXuX3X.X.X|.s g 4.eX.XzXzXcXzXkXkXkXzXcXvX%.].%X$X%X > = X Z O.O.-XmXmXlX+X;X>X9X@Xk.7XtX8X8X8X4XH.0 : : , u u q 9 7 ; r -XwXaXVXVXaXwX=X{.< k * , r b u u 9 u m 9.}.}.x.z.5.4.B 5.j.'.'.[.+.l n b x z x x v O.f.Z.Z.f.t c c 6 t t a v K a v | +.&X#.o.#.+.GXGXGXGXGX].t 4 F T R E Q E _ W _ _ _ _ ' ' ' { { ] JXJXJXJXJXJXJXJXJXJXJXJXJXJXvXZ.W R Y F I c K D G G G D 4 Z GX).z.z.e.e.x.;XR.;XR.1X2XeXeXrXrX1X;XT.e.h $ $ ; 8 e g g g h 7.x.;XH.Z.Z.6.6.>.6.>.4.>.q.l.x.;X>XlXBXPXmXVXaXqXwX3X;XR.eXyXyX5XG.p.' i.p.p.U 8 g h >.l.T.;X1XyX;Xl.z.z.x.}..X{.[.k.k.f.} b m B B C x x C zX%.K J J T ! | [.{._.L ] [ @.vXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJX+XPXPXGXBXnXvXnXvXcXcXzXhX%X$X%X @ j O.+.+.OXFXMX0X9X0XlXxXh.7XtXtX7X8X4XK.z z k u l d u q 9 7 < -XwXVXSXSXVXaX6X{.1 GXk * 0 z r 5 9 b m z.}.;X;Xz.7.B 4.5.9.j.h.[.+.r b z k k z v Z +.k.k.f.o.z N c 5 6 v ..o.| c | +.+.o.&X#.#.h.PXGXPXPXGX#X6 4 F Y T Q E Q E ( W ( _ _ ' _ ' ' p.f.JXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXo.W Y v oXBXgXS | T H L v bX].x.c.c.c.;X0X>X>X1X1X1X1X1X1X2X2XQ.T.e.;.; o o & 7 e e e h 7.;X1X3X5X3X5Xv.;.-.-.;.:.q.q.l.c.;X>X0XmXKXKXVXVXaXqX;XR.1XeXyXyXyX4XJ.d.B.S.d.y s g ;.>.l.T.R.eXeX;Xe.7.e.e.c.}.v._.k.o.f.v m m B 5.Z C V x S %XC L ! Q v FXGXGXbX| L K kXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXX}.r.1.u.u.y.XX_.7.5.;.6.w.w.r.r.7.;XT.x.m b 4.4.4.b M j.z.;Xx.c.5.oXPXJXPXJXJXFXhX%XnXcX%.$X%.$X%X%X%XhXj.1XrXuXuXsXsXdXdXsXdXdXdXdXdXOXnXvXnXvXcXcXcXnXBXBXFXPX). x z : a 5 5 5 < + A 0.`.g.9XmXxXxXmXnXbX].=X6X7X7X7X4XP.c A x z l l d q 9 7 , XmXKXIXSXSXVXqX*Xz DXX0X0XyX1X1XR.1X1X2XQ.Q.R.I.l.>.7 . O & 7 8 h 6.c.;XyXiXpXaXwX8Xv.-.-.;.;.6.q.z.}.*X5X5XwXwXVXVXqXqXqX;XQ.1XrXrXyX;X3X6X=X=XJ.a q g ;.:.6.l.R.1X1XeXT.e.7.z.7.z.v.h.[.{.&XZ.N m 5.7.9.0.9.Z n r l K ! Q Q | JXJXJXJXGXoX$XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXkXGXPXFXBXkXgXgXgXgXgXgXoXXX].%.#X%X$XXX%XJXJXJXJXJXJXoXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXGX).x +X#X$X$X%X%X t 6 6 6 % n 9.).).g.$XmXmXFXmXmXxXv =X7X7X4X^.P.v _.S M x b u q 0 7 ; n jXVXSXSXSXVXVX*X, Z JXx , b k 9 9 d B 7.x.R.;X;XR.c.x.x.c.}.}..Xv. .k l j j z Z [.{.*XH.o.a _.k y a .@.o.Z.o.c O. .+.+.Z.Z.#.).PXGXPXJXJXGXx 2 D F Y T R R _ ( W W _ _ _ ' ' ' { OXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXJXJXJXJXJX| ~ K 6 5 9.}.XX.X;X>XyXyX1XyX1X>X;X;X1X1XR.R.Y.I.r.w.-.O . O 7 -.6.l.T.1XiXZXCXAXaXqX6X7.g h >.6.9.g..XH.P.L.^.8XqXqXqX6X=XR.Q.1X1XeXR.4.5.j. . .y 8 g h :.6.e.x.R.1X1XR.z.6.7.5.5.9.B j.[.&Xk.Z.7.m 5.9.9.9.0.O.N c t G L Q _ U vXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX%XJXFXcXkXgX k r c c < t a t = z Z 9.0.9.).jXKXKXmXmXbXZ | ^.^.^.^.L.I [.[.O.Z n b b q 9 , : OXxXSXIXSXSXVXqX% < S S , l B N d d m 7.z.;X;X3X1X;X}.}.;X;X.X.X.Xk.c l k j x A [.&X*XH.+.| l c a } f.@.Z.#.o.a .+.o.o.Z.Z.A.%.JXGXPXJXJXGXO.2 D F ! Y T Q E W _ _ _ ' _ ' ' ' p. XHXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXnX..@.o.v C .X>X*X3X0XMXMXyXeX1X;X;X;X1X1X;XR.l.l.w.6.:.h 7 o & s 6.6.q.T.R.1XMXZXIXIXSXqX7X*X4.g 4.4.9.'.H.P.P.G.G.L.L.J.=X7XR.T.T.R.R.l.9 O ; 8 s g -.s g -.:.q.e.x.R.1X1Xc.l.6.4.5.4.N @ 5 g.{.H.f.5.B 4.7.9.9.9.g.N a K G J Q _ { |.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX%.FXBXcXhX t a 5 k B B Z M B g.jXKXCXmXmXxXO. .L.~.~.J.I {..X{.j.9.B b d q 7 , 1 -XmXSXSXVXaXqXr % , : , 9 b B b d h 4.z.c.;X1X3X;X;X;X1X1X>X9X9X5X*Xt k j l Z _..X*XH.k.| c 6 | ..o.o.o.Z.#.t | +.+.k.k.o.&X+.JXGXGXJXGXPXoX> 4 G F Y Y Q E _ W W E ( ' ` ' ' p.].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXjX..V.o.N }.0X=X5XqXVXJXMXeX>X>X>XyXyXyX3XR.T.l.r.l.l.l.l.T.x.x.z.z.l.l.T.R.1XZXIXIXUXSXaXtX7XH.B B 9.S H.P.~.L.G.G.S.S.S.J.Z.e.x.R.R.1Xd $ 7 s s g ;.>.;.g ;.6.q.l.T.;X3X;Xx.4.4.6.5.B y @ @ l [..Xk.B m m 5.9.9.0.j. . .I K L Q { +.BXJXJXJXJXJXJXJXJXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJX|.XX].XX+XkXzXkXgXgXgXhXhX.0.%.#X+XoX].JXcXw.r.I.w.m 5.7.5.7.x.nXcXkXnXPXPXJXJXPXPXPXPXPXPX#XkXkXhXgXgXzXvXBXGXPXPXPXPXGXPXPXPXPXPXPXPXFX].gXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXg.4X^.^.^.L.L.L.~.G.J.F.A.A.Z.k.f.k.k.k.h.N z z `.JXJXJX].#X%.O.M v v c 6 OXVXIXUXIXSXaXdXaXqXwX-X-XoXoXOXOXOX{.[.O.).0X|.1 z v M 6 a > k Z Z B n b b S mXKXmXmXwX-X3XO.A.F.F.} .X*X.X'.j.B m b u y 9 o S 0XmXVXaX8X8XN % : * * , u m m d f 5.z.x.;X;XeXyX1X1XyXyXyX0X0X0X5Xo.r r l M g.{..X{.Z.o. . .} .f.o.Z.@.o.K k +.o.k.k.Z.Z.k.HXHXHXPXGXGXGXx 4 G F J T T Q Q E E _ ( ( ' ' ' ' '.HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX Xb.k.g.;X0X=X6XwXKXKXNX0X>XMXZXLXZXyXeX1XR.I.T.T.R.R.R.R.;X>XyX>Xc.T.T.1XiXLXSXPXIXSXaXtX7X5X&X&X{.[.*X^.^.~.~.G.G.F.G.Z.g q.c.;X;XeXc.s h h >.>.6.q.q.f ;.4.q.l.R.;X>X;Xl.4.5.5.7.7.b @ @ c v.{.{.j.m m B 5.5.9.9. . .+...@.$.k.nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX].zXzXzXkXhXhXgXX5.6.6.6.6.f V hXgXgXB 5.4.5.5.5.|.'.z.5.B m 7.5.`.C JXPXPXGXBX.X;XR.}.R.c.c.;Xc.x.$XPXPXHXC d 7.Z +X#X$X/._.%X7.y.I.U.>.B 7.7.j.c.gXkXBXPXPXPXPXPXPXPXPXPXnX].%XhXkXhXX9.5 v N 6 c > l C Z C B k : < _.mXmXxX-X-X0X5Xh.X.C.U k.-X*Xv.j.A B B m u 0 + + g.OXqXqX7X^.f.; , * + 7 u d h h d 4.z.x.c.;X;XyX>XyXrXiXMXMXmXxXwX*X6 r l M 0.].{.{.k.k.o.} } } } o.o.X...} 5 | +.k.+.Z.Z._.PXPXHXHXGXPXGXO.3 G D J T Q Q _ Q _ Q _ _ _ _ ' ' {.HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].Z.[.;XpX6X6XwXVXKXMX0XZXLXIXIXZXiXrX1X1XW.Q.R.Q.1X2X1XeXyXiXCXCX0X;XyXZXZXLXIXIXSXaXtX7X4X6X=X*X=X=X7X^.^.^.~.G.G.G.y f 6.x. X>X0X3X6.>.>.7.l.l.x.l.f g 4.7.z.}.;X>X}.z.5.7.l.e.7.7.2 @ 9.}.}.v.v.m f m B B 5.A j.j.k.o.n.H.#XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXvX).nXcXzXkXgX r C A B C 0 * < : mXmXxX-X-X-XwX5X[...] k.-X*X{.h.j.5.m b f d , < % Z *X7X7X^.f.7 ; ; $ < q u d f d m 7.z.x.}.1XeXyXyXuXyXiXMXmXNXbXwXk.< l M 0.'. X.Xk.f.h. . .} } ..o...} } t a o.+.&XZ.&X+.PXHXHXHXPXGXGXoX2 D G J ! ! [ Q Q Q Q _ _ _ _ _ [ .XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX0.}.;XiX5X=X5XmXCXbXCXIXIXIXSXZXuXuX2X2XQ.2XQ.Q.Q.1XrXuXZXIXVXqXqXiXiXZXZXIXIXSXAXaXtX7X7X^.4X7X7X7X7X7X^.^.^.L.G.I e g 4.j.}.OX-X0XR.l.z.z.x.c.R.R.f h 4.7.T.}.;X.Xx.z.z.z.7.z.z.7.y - j.x.c.}.`.B d f m B B A g.h.k.Z.*X{.HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX%.BXnXcXkXgX$X).#XGXPXPXGXPXGXJXXX.6.6.6.4.].|.7.B f x.uXsXuXuXuXeX1X1X;Xc.d oXPXPXPXhX@X>X.X3X3XyX3X3X0XuXeXnXPXPXkX/.C 6.9.).oXgXhXhX#X$XhXr..XFX0.m 5.j.kXJXPXPXPXnXXX#Xx S ].%X%X%XgXgXhX > > ].mX-X-X-X-X-X5X*Xk.K +.wX-X-XoX[.9.5.B h f 0 : , @ O.H.4X~.f.& & * % ; 9 q q y d f 7.j.x.}.;X;XyXyXuXyXyXyXMXMXmXxX5Xa l M 9.`.]. Xk.h.o.f. .} ...o...o.} } c 6 c o.Z.H.+.GXGXPXHXGXPXPXFXx 3 D D J J ! ] ! Q Q Q ) _ _ [ +.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX).;XyXMX=X*X-X-XxXHXIXLXIXLXZXsXiXrX2X2XQ.2XQ.1X1XeXrXsXIXIXVX8X7XpXZXLXSXLXSXSXAXtX8X7X^.P.P.^.^.^.^.7X^.^.^.~.f.8 e s m 9.'.-X0XmX3Xl.x.R.R.R.;X;X7.m 4.7.}.>X>Xj.z.l.7.7.z.7.z.z.a 3 2 v 7.j.x.j.b q b b b B 5.h.h.{.&X-XXdXdXdXdXdXdXdXdXdXsX3XFXPXPXJX: 5.4.4.7.9.OXkXzXcXcXnXe.r.BX+Xb x..XJXPXPXPXPX%.GX`.+X+X#X%X j C M M > 6 6 5 x jX-XOXOX{.*X-X*X*Xh.+.wXwX-X-X#X}.j.5.B m b + , , * 0 .D.b.; % & & % 9 7 9 9 s d 9.z.c..X;X>XyXeXyXeXyXyXyXyX0XbXwX=X5 x A g.`.].[.k.o.k.o.| ..} .} f.o.f...o.o.| +.&X .bXGXHXGXPXGXGXGX|.3 4 G G J ! ! ~ Q Q Q ~ Q ] _.PXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.0XyXMXyX5X0X{.9.9XIXIXSXIXAXsXuXuXrXQ.eXrXQ.rXeXeXeXsXSXIXSXVXqX7XqXZXZXLXSXSXVXqX8X4X^.^.L.L.~.~.~.~.^.^.^.^.K.- 7 8 s B j.{.-XwXaXaXR.R.;X1X;X1XyXe.m 5.9.}.>X9.m 4.m m 9.z.z.9.7.a G i i p a t A j.u 0 u l b M A g.[.*X-X'.nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX%.FXBXcXzXS ].vXGX$XoX].].X0X0XxXxX5Xo.k B 9.g.`.'.h.o. .o.f.} } } } ..f.f.f.f.f.@.o. .O.oXHXHXPXGXGXPXPXGXM 3 4 D L L ! ~ Q ! ~ ~ U _.GXGXHXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X3XMXCXiXyX3X.Xg.5 VXSXSXSXZXsXuXuXrXrXQ.eXQ.Q.uXrXuXsXAXIXIXSXAXqX7XqXmXLXIXLXKXdXqX7XL.L.G.G.G.G.G.G.~.^.7X4XqXy O ; 8 f ;.j..X5XaXaXaXyX;X1XeXeXyXyXc.h 5.z.;X|.9 u q u 4.9.j.z.7.5.I J L I I a v N M 0 9 0 k b n B g.[.*X5X5X_.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFXoXFXBXzXXGXPXPXPXPX%.$Xk #X%X%XgXgXhXgXkXkXhXzXcXnXGXPXPXPXPXPXPXPXPXPXcXkXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXg.K.~.L.G.S.S.G.S.G.L.J.L.P.=X=X=X*X5X*X=X=X6X6X5X*X[.M 6 . .a t t l a 6 j @XwXaXqX7X8X8X8X8XqXaXaXVXKXVXVXVXKXCXVXmX-X{.+.6 @ 6 a a t 5 a p t 6 5 > # # > Z _.{.{.{.{. XOX-X*X-X-XOX Xx.5.4.m b d b B M M l $ , ; , ; % $ , 7 ; , 9 9.}.}..X}.;X;X>X1X3XeX3X3X0X0X0XmXwX=Xa n A 0.).`.h.h.+.| } | I } } o.o.f.b.Z.Z.b.X. .v O.cXHXPXGXGXPXGXJXFXz 3 4 G G J J L F L K #XGXHXGXHXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX_.MXZXyX0X;X.X0.N 3XtXdXAXZXZXiXuXrXeXQ.eXeXrXuXuXiXZXSXUXIXSXAXaXtX8X8XCXLXIXPXKXaXqX^.G.S.F.S.S.F.G.G.L.~.^.pXiXo . ; q d 5.v.*XwXqXqXaXAXuXuXiXiXiXyXR.m 4.9.}.0 O % , d 5.j.x.j.9.m I P ] U o.f.k.{.&X'.x 1 5 u b n 9._.*X0XwX*X$XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].FXcXkXhXzXnXnXnXnXBXnXnXvXcXcXzXvXBXFXGXGXGXGXGXGXPXPXPXJXHXGXvX = K p a K a a a 6 > : : + r O._.]. X X X{. X{.=X=X*X*X}.z.j.5.4.b m B 5.9.j.0., 7 ; ; ; % % ; ; * 9 9..X@X>X>X>X>X>X3X>X3X3X3X-X0X-XjXxX5XH.u Z 9.0.O.+.O. .} } } } .o.| f.f.k.Z.b.b.@.o. .+.O.oXGXGXPXGXJXJXJXJX].C c c v p D a v BXPXGXPXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].mXCXMX>X;X}.}.9.N tXpXAXAXIXZXiXiXuXrXuXuXuXuXpXpXAXSXIXUXUXIXSXSXaXtXVXMXZXZXsXpX8X7X^.L.L.G.G.G.G.G.L.L.^.7XAXVX, O ; 8 f j.{.wXaXaXaXAXsXiXiXuXiXiXiX;X4.4.9.`.X + + ; 0 B 9.`.h.5.m f ] f.f.f.k..X-XxX0X9X-X+.r b b A _.OX-XqXqX'.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].FXcXvXFXFXFXBXBXBXBXBXnXvXvXvXnXnXBXFXGXGXPXGXGXPXPXPXPXPXPXHXGXgX#X%X%XhXcXnXFXPXPXGXQ &XnXkXgXx V +X#X#X#X#XoXK L L J J +.#XgXS S #X#X+X#X+X,.,.-.e -.kXBXFXFXFXfX}.e.u.E.t.-.5.5.7.9.%XZ 7.W.W.e.1Xx.e.}.zXkXkXhXgXgXgXhX|..XrXg.vX.XyX+XFX0XyX3X3X3X3X;X;Xv.$XPXPXgXk #X].M c.1XeXrXuXuXsXsXsXdXdXZXdXdX#XGXPXPXPXGXS +X$XkXBXFXGXFXGXPXFXnXFXFXBXFXGXPXPXPXPXPXPXPXPXPXBXcXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoXa L.G.G.S.G.S.G.L.L.G.L.P.P.=X=X5X5X5X6XqXqXaXqX6X.XN < = a t t t t a a 6 N =X=XP.4X^.7X7X7X7X8XqXqXaXVXwXVXwXaXqX6X=X*Xk.o.v k > p K p a a t 6 5 > : # r B 9._.].|.|.}.'.[.*X4X6X*XOX}.x.z.9.4.m 5.9.9.}.+X9.7 7 ; ; & % * ; ; < 9.+X>X>X9X>X0X3X3X>X3X*X=X-X-X-X-XxX-X*X .n B A A O. .| N I } f.f.f.f. .f.f.f.b.b.@.@.o.O.O.+._.].oXcXGXFXGXFXPXGXPXGXGX+X).kXJXJXJXJXHXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX'.CXMXyX>X;X;X`.0.t tXdXdXSXIXUXSXSXZXsXsXZXdXZXAXAXIXUXUXUXUXUXUXIXSXVXCXrXeXeXyXpX3XH.k.o.f.A.J.J.P.P.=X7X7XdXaXdXN & 7 d m z.@X0XVXSXSXAXiXuXiXsXiXiXiX1X7.4.7.B : + + ; q B 9.x.`.5.m d } f.f.k.k.OX9XmXmXxX-X-Xh.r b Z g.{.-X5XqX*XhXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXPXJXGXGXPX`.BXnXFXPXGXGXFXFXFXFXFXFXFXFXFXBXBXGXGXGXPXPXPXPXPXPXPXPXPXPXJXJXkX#X#XXBXBXPXPXPXoX).#X%XcXGXPXPXPXPXPXPXPXPXGXGXPXPXPXPXPXPXPXPXPXPXPXhXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXHXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXlXO.D.G.G.G.G.G.L.G.G.L.P.=X6X=X5X5XwX6XqXqXwXVXwX5Xv.l < : t t k 6 6 z l t N H.K.P.P.^.^.4X7X7X8X6X6XqXqXqXqX6X6X6X=X*XH.k. .v l 5 K p p a t 6 5 5 > * 0 z m A ).}.}.'.}.h.h.=X7X7X-X-X.Xc.z.9.7.7.j.c.;X>X>X0 9 7 * % % % % * 9 g.+X,X9X9X0X0X>X.X3X*X*X*X*X*X-X*X-X-X-XH.c B B C N c a a a I .} j.f.f.f.f.f.b.b.n.@.@. .| v v c c c c O.h.oXFXPXPXGXJXJXHXPXJXJXJXJXHXPXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoX-XVXMX0X;X>X|.g.M < 8XdXAXIXUXUXUXIXIXIXSXIXSXSXSXIXUXUXUXUXUXUXUXUXUXSX1XT.4.q * X X o + ; y O..X5X=X6XqXtXaXaXpX4X, q d 5.c.>XbXIXIXSXZXuXiXiXsXZXsXiXR.l.5.5.B : O + % 0 m 9.x.j.5.f u a f.k.h.{.-XmXmXbXmXmXjX-X[.l B O.'.*X=X8XqX].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXJXJXPX K K p p t 6 6 5 # 1 u b m B 0.j.z.x.g.g.k.K.P.4X*X-X}.x.x.x.x.c.R.1X0X>X0 9 7 * & % * + % 1 ).+X$X9X9X0X-X-X*X*X*X*X=XK.*X=X*X*X-X-X*Xk.k x x z l r y t v v } I .o.f.f.f.f.@.b.#.#.o.| v v v | v v a a } | -XGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXA 6XwX3X;X;X+X'.g.b 1 h.AXSXIXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXPXR.w.7 o X o o X * 9 9 m v.0XmXaXpXqXpXpXtXP.t s h 6.c.>XyXCXCXiX3XuXiXZXsXsXiXiX;XT.e.e.9.+ + O % 9 m 9.9.9.4.b q q .k.h.{.9XmXmXmXmXxXwX9X-Xh.t A h.{.=X6X8X{.BXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXFXoX].gXGXGXGXGXPXPXPXPXPXPXPXPXPXGXPXPXGXPXPXPXPXPXPXPXPXPXPXPXcXZ.@.! Q J ].#X$XgXcXcXnXFXGXFXBXL D Q _ _ _ _ ( E Q Q E Q Q Q Q T x gXkXcXzXzXkXhXkXkXkX}.,.:.e -.:.e 6.q.e.w.6.0.XXkXoXe.e.7.6.7.7.hXhXk gXcXhX;X}.cXcXzXhXgX 1 1 u b B 5.9.j.z.`.j.O.h.Z.K.-X0X.X}.x.c.R.R.;X1X>X,Xj 7 , ; % & % ; < l ].OXOX-X0X9X5X*X-X-X=XK.=X=X=XP.=X=X*X3X-X&Xv l k k 6 6 6 a N o.f.j.f.h.o. .o.o.X.@.b.] X.o.o.| v K | K } | K I ..].PXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXO.4X3X3X>X@X*Xk.f.v 5 5 pXSXIXUXUXUXUXUXUXUXUXUXIXIXUXUXIXUXUXUXUXUXLX1XT.T.< o . . o o o o < 0 0 Z }.jXVXaXaXaXaXtX7XZ.d 7.l.e.6.x.}.;X;Xx.R.yXpXdXdXsXpXsX2X;XR.e.x.0 + % * 9 B 7.9.7.m g q q 7 z j.}.-XxXmXwXwXwX-X-X*X&X+.5 j..X-XwXqX8X].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXGX#X].FXPXPXPXPXPXPXPXGXGXPXPXPXPXGXGXPXPXGXPXGXPXPXPXPXPXPXPXPXPXPX{.W W W ^ H ..gXzXcXcXnXnXFXFXnXcXK Y { ' _ _ W ( _ _ E _ E Q Q Q L %XkXcXcXcXcXzX#X}.9.7.8.-.-.-.-.:.:.e q.e.r.y.y.w.7.).7.e.e.8.5.5.#XhX%.S zXcX.X;X#XkXzXzXgX%X#XC S %XBXBXDX+XgXnXnXnXBXGXJXkX8.I.I.Y.I.u.u.I.u.E.Y.E.W.e.cXvX 6 t 6 6 6 6 6 > 5 I C.J.L.L.~.^.^.^.^.4X4X7X4X4X4XP.K.H.k.f. . .a k n ...K p a 6 6 : j 5 b m B B 5.9.j.`.'._.+.&XjXwX-X;X.X;X;X;X1XeX>XA 1 7 , ; & % * & : x ].OX-X-XwX-X-X-X=X*X=X=XP.=X=X=X6X6X=X5X-X*X&Xx k 6 1 1 l N O.h.k.&X&X.XZ.+.o.o...U b.] @...o.o.} | I | I } I U ....{.PXGXPXGXPXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX XK.H.*X3X0X0X=XZ.k. .l > N SXSXIXUXUXUXUXUXUXUXUXIXSXSXSXAXAXAXSXUXNX1X1X1XR.B . . . o o o . < y z j..XxXCXVXAXVXaXaX5XB h e.R.I.q & $ O * s x.3XtXtXtXpXpXtXyX1X;XT.x.z.* % : u m 5.7.7.4.d q q 9 7 9.c..X0XwXwXqX5X=X*XH.Z.Z...1 A 3XwXqX6Xk.BXJXJXJXJXJXJXJXJXJXJXJXGXPXGXPXGXGXPXnX%.kXPXPXPXPXPXPXPXPXGXFXFXPXPXPXPXPXPXFXGXGXFXPXPXPXPXPXPXPXPXPXPXJX*X/ / ^ ^ P cXnXBXnXnXnXBXFXFXvX|.U Y ' { _ _ E E Q _ E E Q Q E ! `.zXcXvXnXBXnXoX6.u.u.E.u.E.t.,.-.g -.-.-.q.e.y.e.y.y.y.8.m 8.5.4.4.0.1 |.].|.cXgX>X}.hXkXzXzXgX).x _.%XBXnX|.z.|.nXBXnXnXBXGXPX$X4.w.u.Y.U.Y.Y.Y.E.W.W.y.hXcXcXvX 1 k k j 6 5 > > # t C.G.L.L.~.P.^.^.^.^.4X7X4X4XP.P.K.Z.b.f.} a y j nX+.} K p p a = z k M m m B 5.9.g.`.|.].{.-XwXmX0X0X3X;X;X>X1XyXyXN : 7 , ; * * & % # x [.*X*X5X-X5X5X*X*X&XH.K.=X=X6X6X6X6XwX5X5X-X*X+.k j 1 r x A h.k.{.-X-X-X*Xk.o. ...X.U U U U ......} } } } ..X.X.U ] ..OXGXGXPXGXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh.K.5XwXwXqX=XP.A.Z.k.v r : -XSXSXUXUXUXUXUXUXUXUXIXSXAXAXdXdXpXaX;X;XyX>X1X;X`.+ O + % O . . y c A '.,XBXKXKXIXSXAXSXlXz.7.z.R.e.; $ . % h T.!.5X!.4X8X8XyXeX;XR.c.z.n ; ; u m 4.7.6.;.f q q q 0 4.x.@XjXwX=X=X=X&Xn.m.[ [ m.f.A g.-X5X6X=X XHXJXJXJXJXJXJXJXJXJXJXPXGXGXHXGXPXzXC GXPXPXPXGXPXFXFXGXGXFXFXFXPXPXPXFXFXFXFXFXFXFXGXGXPXPXPXPXPXPXPXGXBX9X^ / / / / k.BXFXBXBXBXFXGXFXBX%.L H _ ' _ _ _ _ _ E _ Q Q Q Q +.zXcXnXBXBXFXgX:.U.E.E.E.E.E.E.E.E.1.-.e g ;.w.w.r.y.T.u.W.T.r.6.5.4.%XGX|.C 1 kXcX.XyX XhXkXzXC nX+X).zXhXz.T.T.I.}.DXDXBXBXFXJXJX7.4.6.r.I.Y.E.W.W.Y.x.hXcXcXcXvXnX,Xj.z.7.9.x.vXcXkXgXgXhXkXkXzXzXzXcXFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXHXoXC GXGXPXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX).4X~.L.~.~.~.~.^.^.4X8XqXqXqX6X4XP.K.Z.b.| t 6 < 5 > : k k 5 > > : = = a V.J.G.L.L.L.^.^.^.4X4X^.4X=XP.K.K.Z.f.f.I t l ].JX+Xa c p a > j C V b B m B 5.0.`.}.{.{.OXwXwXwXmXyXyXyXyXyXyXyX0Xx 7 ; ; * ; & % # c [.*X*X-X=X5X0X5X*X&XH.=X=X6XwXmXwXwX6X6X5X5X=X*XN k 6 0 V 0.`.v..X,X9XwX-X*XZ.o.o.X.U U U U X.............X.@.X.U ] ] o.X{.> * , ; @ o . t ._. XjXFXPXUXIXIXIXPXCX;X;X;X1X6.; & $ . y 5.c.!.K.P.4X4X8XyX1X3X;Xz.5.u 7 q h 4.7.4.;.g q q q d 4.z..X0XwX6X=XK.m.[ Q ) [ s.M.b.O.+.=X=XP.+.HXJXJXJXJXJXJXJXJXJXJXGXPXPXPXFX].oXPXPXGXGXGXGXFXFXBXnXnXcXzXzXPXPXPXPXPXPXPXPXPXFXGXPXPXPXPXPXPXPXFXGXBXzX) / / / / Q OXFXFXFXFXFXGXnXnX%.F K G G J ! T Q Q _ _ Q Q ! k.nXvXnXnXFXFXFXe.y.U.E.E.E.E.E.E.E.E.E.E.u.-.e q.e.y.u.u.W.u.W.W.Y.Y.6.vXPXHXFX%.V cXgX;X}.zXcX%XoXPXPXPXBX9.y.T.y.T.e.XhXvXcXzXzXcXnXBXFX+Xz.9.7.c.vXnXcXhXhXkXkXzXcXcXcXnXnXFXGXPXPXPXPXPXPXPXPXPXPXPXPXPXnXx /.GXJXGXGXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXj.7X^.L.~.~.~.P.^.4X8X8X8X6X4XP.P.K.A.b.f.I t 5 5 < > # 1 6 5 > : : > = p C.F.G.L.L.L.L.^.^.^.4X^.^.^.P.K.A.Z.b.o.} a l DXJX].t 6 6 a p k Z b m N B A 0.g._._.[.&X-XwXmXVXZXyXyX0XyXMXiX0Xx ; ; , ; ; : ; % c k.&X*X5X5X*X5X*X*X=XA.=X5XwXVXmXKXmXwXwX=X=X-X=Xh.k 0 0 z 9.`..X;X>X0XjX0X=X*Xf.o.o...U U U U X.X.X.o...X.X.@.@.#.] ] [ | hXGXGXGXPXGXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXo.C.J.4X8XqX6XP.P.P.H.H.o.c 6 N VXAXSXIXIXUXUXUXUXIXSXAXaXtX6Xj.c .v.v.3X3XwX0XN * 7 9 9 ; O o O t h.&X0XaXVXKXIXIXUXIXCXyX;X1XyX4.7 ; $ o . . $ m x.D.F.P.J.L.qXuXyXyX1XT.7.B 9 q f ;.4.4.h s s u s f 4.z..X;X0XwX5XZ.$.] Q Q _ { s.M.@.v k.H.P.K. XJXJXJXJXJXJXJXJXJXJXGXGXGXoXoXGXPXGXGXGXGXGXGXFXBXnXvXkXoX_.kXPXPXPXPXPXPXPXPXPXPXFXGXGXGXPXPXFXGXFXBXnXvXX./ / / / W T #XFXFXGXFX#XD +.I D +X+Xo.! L L G K G G | kXBXFXBXnXnXBXFXDX}.u.u.t.U.E.E.E.E.E.E.E.E.E.E.E.y.5.y.y.T.W.W.W.Y.W.Y.e.gXPXPXHXFXBX+X#XgX.XyX+XvX%.FXPXPXPXoXT.I.8.e.T.I.`.FXPXJXJXJXPXvX6.7.e.q.`.zX`.9.XXkXzXkXzXcXnXBXFXJX.X5.5.v.JXFXnXcXhXkXzXcXvXvXvXnXBXFXFXGXGXPXPXPXPXPXPXPXPXPX%XC V hXJXJXJXHXPXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX`.5X7XP.~.^.^.^.7X7X7X7X7X^.P.L.J.D.n.b...K t 5 2 5 = + : 5 > > = = > @ p N.S.F.F.J.L.L.^.^.7X^.4XP.P.P.K.K.A.Z.f.} | h.oX%.K a 6 6 K _.C z b b N B A S +.+.+.{.-XwXVXKXmXdXMXxXyXmXiX0Xz * ; * * ; & * O c k.&X*X5X*X*X=X=X*X*X*X*X5XwXmXBXKXmXwX5X6X=X*X=Xk.0 0 9 l B z.}.;X>X0X0X0X0X5XZ.o.o.....U I U L U X.X.X.X.b.@.#.] #.$.[ [ +.nXGXPXGXPXGXPXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX#Xn.B.L.7X8X8X8X^.P.J.J.K.k. .v k.aXaXVXSXUXUXUXLXCXZXIX9Xx * * f N j.v.H.!.6X5X5X< 9 y u u y ; O o O 6 k.*XwXVXKXIXIXIXSXVXaX0XyXMX6.7 7 & $ o o o d j.D.d.S.S.S.qXZXiXiXyX;Xl.5.u q s h ;.;.g s s s d 4.6.l.}.;X9X0X3Xn.] ! R Q ) { { s.M.@.> o.Z.J.+.GXJXJXJXJXJXJXJXJXJXJXcX/.nXPXPXPXJXGXGXFXFXBXBXvXcXzX].PXPXPXPXPXPXPXPXPXPXPXPXPXPXFXFXGXPXGXFXBXvXzXhX > = @ > > > > = > = t N.C.C.C.F.L.P.^.^.4X^.^.^.=XP.P.K.A.A.V.Z.b.f.o.a 6 p t p %.V u z b b M M Z N Z O.+.*X-XmXVXVXVXCXCXMXmX0X-Xc * ; ; & * * ; % a +.*X*X5X=X=X*X-X*X*X*X-XwXwXmXmXnXmXmX5X6X=X=X-X0X, 0 9 0 B j.}.;X>XyXMXMX0X3X=XZ.o...U U U P ! ! ! X.X.@.@.#.@.] ] [ [ Q [ | cXGXPXGXGXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXj.d.S.L.^.7X7X7X^.^.L.J.J.Z.A.Z.K.6X8XaXVXVXAXVXmX*X[., , , < u v O.f.Z.H.G.L.=Xv.9 u u b f d 9 & O O O X > ].-XmXKXIXSXAXaXqX6XCXCX4.7 7 ; & O o o 9 } m.i.i.p.p.4XSXZXZXZX1XT.z.b q d h h h f g f g g ;.q.l.T..X;X>X{.X.] T R R _ _ { p.s.M.I 5 ..N.N. XJXJXJXJXJXJXJXJXJXhX].PXPXPXPXPXUXPXPXGXGXBXnXzXkX].%XPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXFXBXBXFXFXnXOXoX$X%X%XK ( ` / ( / / R o.#XX.R R Y W F X > = > = > = v n.m.m.n.J.L.P.^.4X^.4X4X^.^.P.J.K.D.D.A.A.A.A.A.b.a 3 1 k 1 k u z n x v v c v .+.[.-XmXVXKXKXIXKXCXBXxXxXc * ; ; ; ; & ; * c .&X5X-X*X-X-X-X-X-X-X-XwXwXmXmXmXmXmXwX5XK.=X-X*Xr , , u B 9.c.;X>XyXyXmXnX0X-X*Xf...X.] ] ! ~ ~ T ! X.@.b.#.#.#.$.$.[ ] ] ! +.%..6.l.x.c.}.}.v.o.U T R Q W _ _ ' s.{ M.4 } m.M.[.JXJXJXJXJXJXJXJXvX].PXPXPXPXPXPXPXPXPXHXGXFXnXzXgX%.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXGXFXnXbXnXcX_.).#X%XgXY F ( W / / ^ ^ W R R R R Q R Y +.#X+XS %X).K G K G L [.GXFXFXBXnXvXcX<.,.:.:.-.hXcXcXvXmXlXc.r.E.E.t.,.BXJXFXcXS gX%XoX#X%X`.FXJXPXJXJXGXFXvXcX}.}.gXyX X`.JXPXPXPXPXXX7.8.4.W.6.4.PXPX = > = = = z N X.X.] n.F.P.^.4X4X4X4X^.4XP.L.K.K.K.K.J.J.J.J.K.A.} = K 5 k l z x M v c c c v O._.*XwXmXKXIXIXIXKXFXmX-Xl * ; * * * ; ; * v k.*X*X*X-X-X-X-XOXOX-X-X-X5XwXwXwXwXxX5X*XK.H.{.O.< 7 ; 0 m 9.x.;XyXyXyXMXBXHXwX-XH.f.b.@.#.] ] Q ~ ! ] X.@.@.#.$.$.[ $.] ] ! ! [ L v #XGXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXf.p.S.G.~.7X7X^.7X7X^.P.P.L.P.P.L.~.4X7XtXtXaXaXVXwXwXh.l , * M h.{.=XP.^.L.K.a u B N B Z 5.5.d 9 * ; $ O % 9.'.+XxXVXaXaXaXaXaXSX.X8 e q 8 7 7 & $ $ a C.d.S.S.4XdXZXIXIXLXyX;Xx.m q s g g g g h h h ;.6.e.e.l.z.x.x.j.j.U P R E E W _ ' { { p.[ K B.M.oXJXJXJXJXJXJXJXHX].PXPXPXPXPXPXPXPXPXPXPXGXFXnXkX 5 2 > = c #XZ o.X.U N.J.^.4X7X6X7X4X4XP.P.K.K.P.K.P.L.P.J.L.A.V...2 5 r l x v C N c a t c Z +.&X-XmXKXIXUXUXKXmXmX-Xl : * * * * ; ; * o.k.&X-X-X-X-X$X#X#X-X*X*X=X=X6X6X5X=X=X=XH.Z.V.O.9 9 9 ; y m 9.j.;X;XyXiXMXCXHXmXxX*X*XZ.A.n.#.[ [ ] ] ! U X.@.@.#.#.[ [ ] ] ] ] Q [ _ U O. = = = = 2 t gX9XA o.f.@.A.P.6X8X8X8X7X^.P.P.K.P.P.P.=XP.P.P.P.J.A.n.} * r z x x v N c t 6 6 v +.[.OXwXmXKXUXUXKXmXmX-Xt : # * % % * % < k.v.*X*X-X-X#X@XOXOX&X&X&X&X=X=X=X=X=XJ.A.N.m.m.] < 9 7 ; r b B 9.}.;XyXuXMXZXFXKXmX0X=X=XK.A.A.n.$.#.[ ] ] ] U @.b.#.#.#.] ] ] ] ] ] [ { { X.{.FXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJX'.' p.S.G.~.7X7X^.tX7XtX8X7X7X^.^.~.^.^.^.7X7X8X7X4XP.H.h.; 9 9 > a I a y < 0 u M j.k.f.h.v.9.b u 0 q , % X N X9XxXxXbXxXjXXMXSXIXZXyXR.m s g h g f s f f m 4.9.j.z.4.9 ; 0 q d q K T R E W / ( / / / ` ` ! o.JXJXJXJXJXJXJXJX$XvXPXPXPXPXPXPXPXPXPXPXPXGXBXvXhX c c c a a a t 6 5 c S _.{.-XmXKXIXUXUXmXmX-X1 * + + + % % X t h.k.{.*X@X>X@XOXoXOX&X&XH.A.N.A.A.F.C.V.M.C.M.[ [ ! 2 7 ; 0 d B 9.x.;X1XyXiXiXCXKXmXmX-X5X*XH.Z.V.n.#.#.] ] P P U #.@.@.U @.U X.] ] ] [ { { { { #.-XKXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXMXU p.d.S.~.^.7XtXtXtXpXqX8X7X4X^.^.^.^.^.7X7X4X^.~.L.A.h.; , 9 , 7 , , ; 9 q u M h.v.Z.k.v.g.b u b d u * $ c h.-XwX0XjX$X`.5.0. XMXCX9Xg.C z 0 q 7 u l q % ; f 5.c.!.3XwXVXVXyX;X5.s f ;.h f f f m B Z j.h.j.5.* 0 u d f b d L R R W ^ / / / / ` ` K OXJXJXJXJXJXJXJXJX`.FXPXPXPXPXPXPXPXPXPXPXPXGXBXcXhX%XgXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXGXnXvXnXo.Y vXnXnXBXnXnXvXnXBXBXcXk.{ { ~ ! ! U L L L ! ! Q _ Q OXNXx.t.u.E.E.E.E.E.u.,.e g -.XXNXcXcXvXBXJXDX5.5.`.gX N h.k..X.X@X+X@X X{.{.{.&XZ.A.A.C.V.C.N.C.C.C.N.s._ R H * * , u B 7.z.;X;XyXyXiXMXCXKXmXxX-X*XH.Z.Z.b.#.@.@.] U P P P ] I K U U U U U L L L ! { { { [ *XHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLX5.a.S.G.G.^.^.^.tXpXaXaXqX8X7X7X^.^.^.^.^.7X4X^.L.L.H. .I * * 9 9 9 7 9 q 0 u v +.&X*Xk.k.j.M x n u 0 , : # > M '.OX}.0.z.c.}.>X0XBXzX Xg.0.g.N z 0 C d , 7 B x.;X3X!.P.4XqXtX3Xc.f h ;.;.m f f b N ..b.o.9.B ; 0 u b b m d v T R W / / / / / ` ' r LXJXJXJXJXJXJXJXJXx GXPXPXPXPXPXPXPXPXPXPXPXJXBXzXgX%XnXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXnX! ! FXFXFXGXGXGXBXBXnXBXcXX.{ { _ ' ' _ _ Q Q _ _ [ Q ! t i y 5 2 > 5 5 t V S _.{.-XmXKXIXUXKXmX-X1 < * % % + + 6 O.h.[..X.XOXOX X X].[.k.f.#.A.C.C.C.B.C.C.C.F.B.s.' ^ E 3 $ * y m 7.z.T.;X3XyXyXiXmXFXmXjX-X-X&XZ.b.b.] b.U ] P L L J L J D G D 4 D D D T R R D { M.{ { k.FXHXHXHXJXJXJXJXJXJXJXJXJXJXJX$XK d.S.G.~.~.^.7X7XpXVXVXaXqX7X7X7X^.^.7X^.^.^.~.L.Z.f.X.U 2 @ 2 - q q u u u n O.{.*X*XZ.v.N 0.x d y , < 1 < 0 k g.}.}.}.}.}.;X,XlXlX#XOX.X&XH.Z.o.%.n q 9 5.v.;X~.G.S.S.J.4X6X0Xj.f m m b u t K T ( ) ] A B * 0 u b m m b v U Q ^ / / / / / i.) oXJXJXJXJXJXJXJXJXBXx FXPXPXPXPXPXPXPXPXPXPXPXGXNXcXhXhXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXBXU o.FXGXPXPXPXHXHXFXnXnXoX{ { ' ' ' { { _ _ _ _ _ Q Q k.%.u.u.E.E.E.E.E.E.E.E.E.E.E.1.-.:.BXnXvX]./.A 6.$XgXhXzXcXvXvXnXvXnXnXvX].FXPXPXPXPXPXPXJXGXJX}.e.gXx.W.T.W.T.T.T.Y.9.DXPXPXPXPXPXvX].oXS +X + : % % % X k O.k.[.{.{..X XOX X{.j.f.X.b.V.C.C.S.F.F.F.J.J.S.s.' _ ( R @ * 0 b 5.z.c.;X1X>XyXMXMXmXbX9X-X-X*X&Xk.f.f.b.X.U L K K D F J H T Y R Y T Y R E E Y ] { { { { +.BXJXHXJXJXJXJXJXJXJXJXJXJXJXBXN U d.d.S.G.L.D. .*X5XVXVXVXtX7X7X7X^.7X7X^.~.G.G.V.n.m.[ ~ T Q H - q e d u b A [.*X*XH.h.Z g. .u m 9 0 z x Z C 9.j.9.9.9.7.9.}.+X9XjX0X5X=XP.J.s.K x u 0 A v.H.K.S.p.i.{ F.4XwX.Xh m b y 3 Y / / ^ ^ R L l < 0 u z b B b n | ~ ( / / / ` ` i.U cXJXJXJXJXJXJXJXJX].oXBXPXPXPXPXPXPXPXPXPXPXPXPXPXDXcXDXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXFXPXPXPXPXPXPXPXPXJXnXvXk.{ { ' { ' ' _ _ _ _ { { [ o.h.7.-.g `.x.t.E.E.E.E.E.E.E.E.E.u.1.].FXvX/._.7.'.gXgXhXzXvXBXBXBXBXnXnXnXcX%.GXPXPXPXPXPXPXPXPXzXe.8.e.W.R.1XW.W.x.+X$X`.).].].XX].].hX%.oX = = * = 5 6 v V C O._.*XwXVXVXVXVXmXOXx l k k : + O : +._.h.k.[.{.].[.k.+. .I ] n.m.C.F.G.J.G.L.L.P.P.P.F.B.p.p.{ = , u M 5.x.c.;XyXyXvXBXxXxX-X-X-X*X&XZ.b.b.f.b.U K G i D J Y T T R R R R R R R R R Q Q Q ( J L { c BXJXJXJXJXJXJXJXJXJXJXJXJXv.G.a.i.i.p.U v N v =X6XqXqXqX8XtX7X7X^.~.G.G.G.b.U m.[ { ) W 4 , 9 9 u u z B O.k.&XH.Z.N `._.A Z B 5.j x Z 9.0.0.9.`.9.j.j.j.|.+X9X*X=XP.P.L.B.M.+.BXM b d j.,X5XF.d.p.^.6XaXVX[.5.A A N p R ^ ^ ^ W R o., 5 r y z N .c N X.n.{ ' ` ` i.` i.j.JXJXJXJXJXJXJXJX%XgXnXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXnXbXbX].I L P ! ! T ! ! L I jXcXcX:.<.:.-.4.vXNXNXFX+Xt.u.E.E.E.E.E.1.`.9.e.y.y.e.l.y.I.T.9.oXnXkX].k ).].C oXcXcX/.].PXPXPXPXPXPXPXPXPXMXW.T.XX(.DXnXvXcXzXzXzXzXhX|.).V X0XmXBXbXxX0X0X*X*X=XH.Z.Z.n.f.f.] U i p D J Y Y R R R R R Y R R R R R E E W ^ Y J ] `.JXJXJXJXJXJXJXJXJXJXJXJXXXV.P i.{ ] ....} X.K.4X7X8XtX7X7X7X^.^.~.~.D.f.p I ] ' ' ( 4 : , , < 0 k b N O.k.k.Z.k.v cX].h.j.A n 6 z C Z 0.9.0.`.`.j.`.].oXOX*X=XK.P.P.L.F.J.].JXS B f 7.;XjXwX=XaXaXAXKXmX.Xv.h.k.h..._ ( ^ ( W ) c 6 < 6 t v .j.t } n.V.d.i.i.i.i.` p.'.DXJXJXJXJXJXJXJX].vX#XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXcXcXvXvX-Xo.] ] $.] ] ] P *X%X#X1.1.,.,.-.0XnXBXFXGXiX.Xr.E.E.E.t.:.e.z.|.B 6.XXhXhX%X|.x.y. : * : : 1 k z N O.h.k.f.b.Z GXBXZ '.`.Z l l C B S 0.0.j.`.'.].{.oX*X*XH.J.J.L.L.P.Z.OXJX).B b 5.c.>XiXIXIXIXIXUXKXxX-X*X*Xb.n.{ / ( W ^ G 6 6 5 < y I ..I p I n.S.S.S.i.i.i.` f.cXLXJXJXJXJXJXJXJX].S PXPXPXPXPXnX|.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXcXcXvXnXnXnX-Xo.$.[ ] P >.t.U.u.,.-.-.,.:.}.nXBXFXFXGXJXcXx.u.1.r.z.$XvXBXA e.oXkXkXzXcX k t z c | a I U ] ] m.B.B.M.B.B.4XqXaXwXqXqX6X4XL.G.B.@.> l n N g. X-XwXwXwX*X*X=XH.K.K.A.D.n.n.n.n.n.U K G K G J T T R R R R Y E R W ^ ^ ^ / / / / / T 5 DXJXJXJXJXJXJXJXJXJXJXJXLXg.K _ i.p.d.s.m.N.S.G.L.L.^.^.^.^.^.^.H.k.f.i p G ~ ) ^ 4 : O + # > 1 k z v .f.f.f.X.O.%XJXZ [. XS M l v A 9.j.`.`.v.'.[.{.&X&XH.A.J.J.L.J.L.N GX].Z M B 7..X0XiXSXSXSXIXIXKXwX5X6XP.V.n.[ p.{ T D t l a a i K X...i K ] M.B.S.S.S.d.i.U #XJXJXJXJXJXJXJXJXJX).`.PXPXFXnXzXkXgXzXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXPXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXcXcXvXnXBXFXFXFXk.$.K 1.E.E.E.E.E.E.,.g -.4.nXFXFXFXNXJXJXJX6.w.}.vXnXnXvXS e.8.%XzXzXvXhXy.}.kX ] p.d.B.B.S.S.G.G.L.G.~.~.^.~.~.Z.n.n.n.P ! _ ( ^ D + @ = 5 6 6 l c v .o.....X.U o.N l v &XZ 6 > l O.g.j.`.'.k.v.k.k.Z.A.A.J.F.F.C.J.A.1 x < 6 z A j.;X0XaXKXVXKXSXIXVXwX6X=Xo.5 = = > = 5 k t c I } X.A...4 G ! s.d.d.d.d.N.v j.zXJXJXJXJXJXJXJXJXJXJX|.x PXPXPXBXvXzXkXvXGXPXPXPXPXPXPXPXPXPXPXPXUXPXPXGXFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXzXcXvXvXvXnXBXFXcXN u.E.E.E.E.E.E.E.E.E.,.e ,XFXGXFXjXBXFXz.r.|.BXNXnXnX#X].].e.e.9.}.|.9.Y.XXhXgXgXgX < t M 0.'.@XmXmXVXSXSXSXaX8X6XH.o.t 5 5 6 6 t 6 t a I | Z.a 4 H H T _ ' ` n.).nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXGX%.HXPXPXFXFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBX 2 3 a K ..N.A.A.b.K L ~ T L h.lXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXPXGXoX#XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXkXgXgXgXgXgXkXkXkXcXBXGXGXPXGXGX%X/.#XGXPXPXPXcX&X..k.vXPXPXPXPXHXBXnXGXPXPXR.E.u.t.,.! @.PXmXc.E.E.E.E.E.E.E.E.R.>XY.Y.u.u.}.FXFXFXbX-X`.$XXX$X%XgXzXcXnXvXzXzXzXkXkXzXzXzXzXzXzXzXzX%.PXPXPXPXPXPXPXPXPXPXPXPXPXPX/.cXcXBXkX+XGXPXGXJXhX%XPXPXPXPXPXPXPXPXPXPXPXPX@X>XGXJXC cXvXcX).PXPX|.].FXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLX9Xv.^.7X7X^.7X^.^.P.L.P.J.D.V.V.n.b...I K @ 2 @ @ v GXJXJXJXJXJXJXJX#XX./ / / / / / / / ( ' i.d.S.S.F.S.S.S.F.G.J.J.V.I = = $ 2 v } .f.Z.K.P.=X4X=X=X=XZ.| S O.).).A r o . : : : = L T Q Q ( ^ ^ ^ ^ ^ / / ` d.^.^.P.P.P.L.L.P.^.4XaXaXwXwX-X X X*XH.K.Z.b.b.b.X.n.n.n.n.n.b.f.] b.X.X...U P ! T R E E E E W E T Y Y R ^ / / / / ` I JXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXLXlXj.- f a.p.p.i.d.d.p.p.' ` / ^ ^ ^ T 3 3 G P U ] n.] b.n.U b.] U U P ! ! Q Q W H 6 | p ] M.A..X*Xf.f.U U m.s.d.B.d.d.s.p.i.G p p p K | f.{.*X-XmXqX6X8X7X4X^.P.Z.} I K K G D 4 3 a K ] M.C.J.J.G.f.A `.vXGXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXPXHXFX/.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXzX).S gXhXkXzXcXnXBXFXGXPXPXGXFX%.+X#X]._.GX-X! ! #.~ L XPXPXPXPXPXJXPXPXLXcX}.u.K ] { k.PXPXJX|.r.E.E.E.E.E.E.@X>XU.U.Y.Y.e.FXGXFXcX/.#X#X|.C %.%XkXcXvXcXkXkXkXkXkXkXzXzXzXzXcXzXzXS cXPXGXFXPXPXPXPXPXPXPXPXPXPXcX%.%.oXcXGXPXGXJXJXGX].GXPXPXPXPXPXPXPXPXPXPXPXJXGXJX).oXnXvXnX].vX].cX/.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX,Xv.~.^.7X^.^.^.~.L.L.L.J.F.V.V.N.#.@.K K @ 2 2 @ A JXHXHXJXJXJXJXJXoX! ^ / / / / / / / ` i.i.d.S.S.S.S.F.S.G.G.J.V.Z.a @ O = v o.f.Z.Z.D.P.P.P.=X4XP.A.f.Z S O.%.Z x : # < * 3 J Q ) W W W ^ W ^ ^ ^ ( ' B.P.L.L.L.L.L.L.^.^.8XVXVXVXVXmX0XOX-X*X*X*Xb...} } X.X.n.n.n.X.X.f.@.X.X.X.U P ~ T E E ( ( _ Q J J J G Y R W ^ ^ ^ ` U lXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXoXO.I s.p.i.i.i.` / / E T J Y L L L ] ] n.n.n.n.n.a.n.] ] ] ] ! ~ Q Q E D K A.A...K _ M.3XH.X.K ! [ s.s.s.d.d.p.i.' ~ p K K } o.f.Z.*X3XwXwX6X4X4XP.P.J.A.n.] P J J F F J G K U M.B.V.k.'.zXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXfX$XPXPXPXPXJXPXJXPXJXPXJXPXPXJXPXJXPXJXPXJXBX$X:X%.kXcXcXvXnXFXGXJXJXGXGXGXnXS +X+XOXoXM ! ] hXPX + D Y T R W W W W W ^ W W W [ s.C.S.C.S.S.S.S.S.G.L.8XtXVXSXUXUXUXUXKXKXmX0X0X*X&Xk.a a I f.b.n.n.b.f.X.X.f.X.U ] [ ( ` _ { [ ] L P J G D D F Y W ^ ^ / / ~ HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHX_.) / ( / ( ( ( ) ) [ [ ] [ [ [ a.[ p.[ { _ _ ) R Q T ! N s.M.N.N.N.N.C.J.F.I ] ) i.i.p.p.p.p.i.` K v J L L U U n.m.V.J.=X6X=XP.L.J.C.C.B.s.{ { { { ' ' _ +.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXJXJXJXJXDXJXGXPXGXJXJXJXPXJXJXJXJXJXJXJXPXJXJXJXJXJXJXPXPXPXGXGXPXGXHXHXHXJXJXvXXX].%X$X%XoXU ! O.].%XXBXnXkXkXzXzXzXcXvXnXnXBXBXXXFXJXJXJXJXPXGXGX C gXJXJXJXJXJXJXJXJXJXJX+X .{ ' _ _ ( ' { p.s.d.s.M.s.s.d.s.p.p.{ { { { [ ) [ ' _ ^ ^ / / ^ / / / / / ^ / ^ ^ ^ / / / / / / / p.C.C.C.F.=XJXJXJXHXJXJXJXJXGXJXJXJXJXJXJXJXJXJXmXwXJ.S.J.K.H.Z.Z.X.] ] [ ` ( ^ ^ / G 4 p K vXGXPXGXGXGXPXGXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBXU ^ / ^ / ^ / / / N.JXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXJXJXJXJXGXHXHXPXJXJXJXJXJXJXJXJXGXGXJXJXJXDX,X/.2.2.3.3./.fXDXJXPXJXJXJXGXFXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXPXPX#X$X$X$X$X$X%XgXgXhXkXzXnXFXJXJXJXJXPXPXJX3.2.2.fXPXJXJXJXPXPXPXPXPXPXPXPXBX$X).kXzXcXvXnXBXGXzXT.Y.e.XXBXnXBXnXBXnXnXzXzXcXvX#Xe.E.e.hXGXGXJXPXGXGXFXBXnXzXzXzXzXcXvXnXBXBXFXnX].JXJXJXJXJXGXPXXr.u.$XFXFXBXBXBXBXcXcXcX_.r.y.}.FXGXJXGXPXJXFXFXBXFXBXcXzXzXcXcXnXnXBXFXBX$X$XGXJXJXJXJXJXnX`.XXS $XkXzXkXkXcXkXcXcXnXFXFXFXnXvXkX].kXkXkXzXzXcX$X|.oXhXhXXX|.k S JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNXj.i.i.i.i.' ` ( ^ ( ^ ^ ^ / W R T H D 3 3 3 3 3 2 2 5 2 2 2 5 2 V FXJXJXJXJXJXJXJXJXJXh.N.s.{ ' _ { p.s.s.s.s.s.s.s.p.{ { _ ( _ _ W ( W W ^ ^ ^ / / / / / / / / / / / ^ ^ ^ ^ / / / / / _ s.C.C.J.6XJXJXHX+XS %.%.].oX+X%XkXvXBXJXJXJXJXJXJXJXLXJXmXJ.M.s.s.a.[ _ _ ( / ^ 4 K K G -XJXJXBX%XoX`.).S V x x k j k 1 1 : # 1 FXJXJXJXJXJXJXJXJXJXJXJXJXJXGXXPXGXPXPXPXJXvXhXgXgX%X#X#X#XS zXDXPX.XE.,XPXPXPXPXBXcXcXvXnXFXFXnXvXnXBXnXvXvXvXcXcXcXcXkXnXvXnXnXnXnXBXzXhXhXgXgX+XJXJXGX).hXkXvXBXFXGXGXPXPXPXGXGXPXGXGXPXFXBXzXXPXPXPXPXGXnXcXvXzXC %.#X HXJX[.] X.b.[ ( ( ( W ^ ^ ^ ( ( _ _ ( ( ( ^ ^ / ^ ^ ^ ^ ^ ^ / / / / / / / / ` { G * # * @ @ = 4 L ! ) ( ^ ^ / / ( ' J.GXPXGX].. X %.JXPXGXA.W E =XPXPXGXGXx Z JXJXJXJ.( ( _ W ( Q Q Q XJXJXJX%. # zXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].X S JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXgX. . 1 JXJXJXJXJXJXJXJXJXJXJXJXJXJXGXx X X V fX/.3.3.(.NXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.+ X `.JXJXJXJXJXJXJXJXJXJXJXJXFX1 . 1 %.: . : gXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXC . 1 FXJXJXJXPXPXPXPXPXPXPXGXPXGXGXFXBXFXFXGXGXFX#X%X/. > * @ + X @ i K ] [ J.JXJXJX].. X %.JXJXJXA.E N.GXPXPXGXoX: x JXJXJXwX{ ' ' ' ` ` ` _ ` C.JXGXJX%.. # cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJX].X %.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXkXX . k PXJXJXJXJXJXJXJXJXJXJXJXDXx . . . /.nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXV . . V GXJXJXJXJXJXJXJXJXJXJXcX: 1 BXGXGXJX|.# . j cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXx . k JXJXJXJXPXPXPXPXPXPXPXPXPXPXPXJXGXGXGXgXV hXGX).GXJXJXJXDX3.2.&.&.2.2.DXJXJXGXFXGXGXFXnXcXgXgX%X > = : > > = * @ + @ * K ! ] =XJXJXJX].. X /.JXJXJXA.$.GXGXGXHXcXx X # gXJXJXJXF.{ p.p.' ' ' _ ( _ A.JXJXJX%.. # cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPX].X %.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzX. . k JXJXJXJXJXJXJXJXJXJXGXPXXX: . # nXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX`.X 1 JXJXJXJXJXJXJXJXJXJXJXcX: . . 1 BXPXGXGXGX|.# X k vXJXJXJXJXJXJXJXJXJXJXJXJXJXk . x JXJXJXPXPXPXPXPXPXPXPXPXPXPXPXPXGXFX#X).PXPXGX%XJXJXJXJX(.2.&.&.&.2.8.JXJXJXGXGXGXPXGXGXnXkX/.x gX#XS GXPXPXPXPXPXcXl.Y.FXBXcXXX%X%XgXkXcXvXvXvXcXzXkXnXBXvXzXkXkXnXnXkXkXzXzXcXcXvXcXcXkX 5 = = @ @ @ = L ] &XGXGXPX|.. X %.KXGXGX5XGXPXGXGXGXS # X S JXJXJXbX{ { p.p.i.' ' ' ( _ A.PXJXJX%.X # cXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].X ).JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzX. . j JXJXJXJXJXJXJXJXJXJXGX:XV . C JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXGX$XX # GXJXJXJXJXJXJXJXJXJXJXkX: . . 1 BXGXPXJXJXJX].# X k nXJXJXJXJXJXJXJXJXJXGXPXGXk . x JXJXJXJXPXPXPXUXPXPXPXPXPXPXPXPXGXnX`.PXPXGXJXGXPXJXGXfX2.&.&.&.&.2.:XJXJXJXGXGXJXJXPXPXPXFXV ).kXgX#XcXJXPXPXPXJXcXr.I.GXBXcXgX%XgXkXcXcXvXvXcXzXkXkXFXFXnXvXzXhXkXnXkXkXkXcXcXcXcXcXcXkXgXXJXPXPXPXPXDXzXhXkXkXcXcXcXkXzXkXkXhXhXgXhXvXnXFXPXGXPXGXGXGX$XXXhXkXzXcXnXFXGXoXXJXJXJXJXPXPXPXPXPX].oXXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.BXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXFXBXnXgXkXcXcXcXvXBXFXFXnXnXBXvXkXkXcXBXGXPXPXPXPXPXPXPXPXPXoXPXnX].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX2 T / / / / / / / / / / / / / ^ / / / / / / / / W +.JXJXJX].G D D D F H H R R W W W W W ^ ^ W ^ ^ ^ C.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX3.&.&.(.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXfX/.3.2.&.&.&.&.&.=.2.&.&.&.&.&.&.2.:XJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX).GXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXGXvXkXvXFXFXFX%X].].oXXXX|.%.C FXPXPXPXPXPXPXPXPXPXPXPXPXXXhXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXO.@ H / / / / / / / / / / / / / f.kXJXJXJXJXJXJXJXJXJXJX).Y T R W ^ ^ / / / / / / / / / / T .NXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX3.&.&.&.2./.PXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXfX(.3.2.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2./.NXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX|.PXPXPXPXPXPXPXPXPXPXPX%X|.hXJXJXJXJXJXJXHXHXJXJXJXJXJXJXPXgX).|.$XoX#X%XkXBXPXPXPXPX|.kXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].i 4 G J G H H G I P P J K [.GXJXJXJXJXJXJXJXJXJXJXLXkXG R E W ^ / / / / / / / / / / U v.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX(.2.&.&.&.2.(.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXfX/.3.3.2.&.&.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.3.(.NXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXPXPX%X|.kXJXJXJXJXJXJXJXJXGXPXJXJXJXJXJXJXGXGXGXPXPXGXcXgXoX`.%.%.+X].BXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXk.J / / / / _ I gXBXlXkXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJX| T W ^ ^ / / / / / / / / ^ } 9XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXNX2.&.&.&.&.*./.NXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXDXMX:X/.3.2.*.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.*.&.2.3.:XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX#XXXXX].XXoXoXoXoXvXJXJXJXJXJXJXJXJXJXJXHXGXHXPXJXJXJXJXJXJXJXGXGXGXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXxX[.h.h.'.oXFXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX+.R W ^ ^ / / / / / / / ] [.JXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXPX8.&.&.&.&.&.2.3.:XPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX:X(.3.2.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.2.2./.fXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXHXPXHXGXJXJXJXJXJXJXJXPXGXPXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX%XF ( ^ / / / / / / { k.vXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX2.&.&.&.&.&.&.2.8.fXPXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXDXfX(.8.3.3.2.2.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.3.:XNXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXJXJXJXJXJXJXGXGXPXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXK W / / / / / / n.@XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXXX&.&.&.&.&.2.2.&.2.3.:XGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXPXNXfX/.3.2.2.2.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=./.fXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXGXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX'.J / / / / / U +XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX/.2.&.&.&.&.&.&.&.&.&.2./.fXDXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXJXJXJXPXJXJXJXJXJXJXJXJXDXJXJXJXPXJXJXNX>X(.3.2.&.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.2.2.3.fXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDX- T / / ` U @XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX(.=.&.&.&.&.&.&.&.&.&.=.=.3.3.(.>XNXDXJXPXJXJXJXJXJXDXJXJXJXPXJXJXJXLXJXJXDXDXJXJXJXJXJXJXNXNXfX:X(.8.3.2.=.&.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.2.8.:XDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLX%XA b.wXlXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX:X2.&.&.&.&.&.&.&.&.&.&.&.&.&.=.2.3.3.8.8.(.:X:XfXfXfXfXfXfXfXfXfXfXfX>XfX:X(././.3.8.3.3.2.2.&.&.&.2.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=.*.3./.fXNXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXPXPXNX3.=.&.&.&.&.&.&.&.&.&.&.&.&.=.=.&.2.2.&.2.2.2.2.2.3.2.2.3.2.2.2.2.2.2.2.2.&.2.2.&.&.&.&.=.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=.2.3.(.lXFXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPX:X3.*.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=.=.=.2.3.3.:XNXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXPXJX:X3.2.*.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=.=.2.2.3./.:XfXJXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXGXfX/.3.2.=.*.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.&.&.=.=.2.2.3.3.(.>XNXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXfX/.3.2.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.*.2.2.2.3./.(.>XNXJXPXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX:X(.8.3.2.2.&.&.&.&.=.&.&.&.&.&.2.&.&.=.&.&.2.&.&.&.&.&.=.&.&.=.=.2.3.3.3.8.:XfXfXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXDXNXfX:X/.8.3.3.3.2.3.3.2.2.3.2.2.3.2.3.2.3.3.3.3././.:X:XfXMXNXDXJXLXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXDXNXNXlXlXfXfXfXfXlXMXNXNXFXGXHXHXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXDXGXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJX", +"JXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXHXHXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXPXHXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJX+X`.GXJXJXJXJXJXJXJXJXJX GXJXJXJXJXJXkX %.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXk XXJXcXA kXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX: gXJXJXC /.FXHXHXHX. JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDX . JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX#XS DXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJX: hXJXJXJXGXPXJXJXJXJXx ].JXJXJXJXJX1 #XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX%X `.JXJXJXJXJXJXC cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXx XXJX]. S JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXHXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXPXGX: . gXJXJXC /.JXJXJXLX JXJXJXJXJXJXJXJXJXJXJXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDX . JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXl $XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJX: gXJXJXJXJXGXJXJXJXJXx ].JXJXJXJXJX1 +XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXV X nXJXJXJXJXJXDX+ x JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXk ].JXFXoXBXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXHXGXKXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXKXPXPX: gXJXJXZ O.JXJXJXJX JXJXJXJXJXJXJXJXJXJXGXPXPXJXJXJXJXJXJXJXJXJXJXJXGXJXJXFX . JXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXvX+XJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJX1 %X%.x j C kXJXJX).j + : 1 V JXS j X : j Z JXnXk S gXC k k S cXJXJXJXJXzXS k j V +XJXJXJXJXJXJXJXJXJXJXBXX x JXJXJXJXJXJXXX +XJXJXgXk ].].k k ).GXJXGXoXx j k Z $XJXJXJXk |.JX#Xk XXJXJXPX . oXJX%X. # nXJXJXJX`. k GXx `.JXJXJXx ].JXJXJXJXJXj +XGXk ).JXJXJXFX1 ].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", +"JXJXJXJXJXJXJXJX: . ).JXGXPXGXx ).JXJXx ].JXJXJXJXJXj +XJXJXJXdirectory(current_browse_directory.c_str()); - else - G_chooser->directory(joboption.directory.c_str()); + FileName mydir = joboption.directory; + mydir.replaceAllSubstrings("CURRENT_ODIR", current_browse_directory); + G_chooser->directory(mydir.c_str()); G_chooser->color(GUI_BACKGROUND_COLOR); G_chooser->show(); diff --git a/src/gui_entries.h b/src/gui_entries.h index 87741dadf..e50a35989 100644 --- a/src/gui_entries.h +++ b/src/gui_entries.h @@ -127,18 +127,19 @@ extern bool create_scheduler_gui; //version-3.1 #define GUI_BUTTON_DARK_COLOR (fl_rgb_color(200, 110, 200)) //version-3.1 #define GUI_RUNBUTTON_COLOR (fl_rgb_color(170, 0, 170)) // Dont forget GUI runbutton colour in src/displayer.h! -//version-3.2 -#define GUI_BUTTON_COLOR (fl_rgb_color(200,80,110)) -#define GUI_BUTTON_DARK_COLOR (fl_rgb_color(170, 40, 70)) -#define GUI_RUNBUTTON_COLOR (fl_rgb_color(160, 30, 60)) +//version-4.0 +//#define GUI_BUTTON_COLOR (fl_rgb_color(200,80,110)) +//#define GUI_BUTTON_DARK_COLOR (fl_rgb_color(170, 40, 70)) +//#define GUI_RUNBUTTON_COLOR (fl_rgb_color(160, 30, 60)) +#define GUI_BACKGROUND_COLOR (fl_rgb_color(230,230,240)) // slightly blue because of blue buttons in 2.0! +#define GUI_BACKGROUND_COLOR2 (fl_rgb_color(180,180,190)) // slightly blue because of blue buttons in 2.0! +//version-5.0 +#define GUI_BUTTON_COLOR (fl_rgb_color(120, 120, 255)) +#define GUI_BUTTON_DARK_COLOR (fl_rgb_color(80, 80, 255)) +// Dont forget to change runbutton in src/displayer.h too!! +#define GUI_RUNBUTTON_COLOR (fl_rgb_color(100, 60, 255)) #define GUI_BACKGROUND_COLOR (fl_rgb_color(230,230,240)) // slightly blue because of blue buttons in 2.0! #define GUI_BACKGROUND_COLOR2 (fl_rgb_color(180,180,190)) // slightly blue because of blue buttons in 2.0! -// devel-version -//#define GUI_BUTTON_COLOR (fl_rgb_color(255, 150, 150)) -//#define GUI_BUTTON_DARK_COLOR (fl_rgb_color(200, 120, 120)) -//#define GUI_RUNBUTTON_COLOR (fl_rgb_color(170, 0, 0)) -//#define GUI_BACKGROUND_COLOR (fl_rgb_color(255,200,200)) // slightly red -//#define GUI_BACKGROUND_COLOR2 (fl_rgb_color(230,180,180)) // slightly red //possible?#define GUI_BUTTON_COLOR (fl_rgb_color(50, 200, 255)) //devel-version //possible #define GUI_RUNBUTTON_COLOR (fl_rgb_color(205,0,155)) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 029b83300..43e2d98cc 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -361,7 +361,7 @@ void JobWindow::initialise(int my_job_type, bool _is_tomo) } else if (my_job_type == PROC_3DCLASS) { - myjob.initialise(my_job_type); + myjob.initialise(my_job_type); initialiseClass3DWindow(); } else if (my_job_type == PROC_3DAUTO) @@ -409,11 +409,46 @@ void JobWindow::initialise(int my_job_type, bool _is_tomo) myjob.initialise(my_job_type); initialiseCtfrefineWindow(); } + else if (my_job_type == PROC_MODELANGELO) + { + myjob.initialise(my_job_type); + initialiseModelAngeloWindow(); + } + else if (my_job_type == PROC_DYNAMIGHT) + { + myjob.initialise(my_job_type); + initialiseDynaMightWindow(); + } else if (my_job_type == PROC_TOMO_IMPORT) { myjob.initialise(my_job_type); initialiseTomoImportWindow(); } + else if (my_job_type == PROC_TOMO_EXCLUDE_TILT_IMAGES) + { + myjob.initialise(my_job_type); + initialiseTomoExcludeTiltImagesWindow(); + } + else if (my_job_type == PROC_TOMO_RECONSTRUCT_TOMOGRAM) + { + myjob.initialise(my_job_type); + initialiseTomoReconstructTomogramsWindow(); + } + else if (my_job_type == PROC_TOMO_ALIGN_TILTSERIES) + { + myjob.initialise(my_job_type); + initialiseTomoAlignTiltseriesWindow(); + } + else if (my_job_type == PROC_TOMO_DENOISE_TOMOGRAM) + { + myjob.initialise(my_job_type); + initialiseTomoDenoiseTomogramsWindow(); + } + else if (my_job_type == PROC_TOMO_PICK_TOMOGRAM) + { + myjob.initialise(my_job_type); + initialiseTomoPickTomogramsWindow(); + } else if (my_job_type == PROC_TOMO_SUBTOMO) { myjob.initialise(my_job_type); @@ -521,29 +556,32 @@ void JobWindow::initialiseMotioncorrWindow() tab1->label("I/O"); resetHeight(); - place("input_star_mics", TOGGLE_DEACTIVATE); + place("input_star_mics", TOGGLE_DEACTIVATE); // Add a little spacer current_y += STEPY/2; - place("first_frame_sum", TOGGLE_DEACTIVATE); - place("last_frame_sum", TOGGLE_DEACTIVATE); - place("dose_per_frame", TOGGLE_DEACTIVATE); - place("pre_exposure", TOGGLE_DEACTIVATE); + if (!is_tomo) place("first_frame_sum", TOGGLE_DEACTIVATE); + if (!is_tomo) place("last_frame_sum", TOGGLE_DEACTIVATE); + if (!is_tomo) place("dose_per_frame", TOGGLE_DEACTIVATE); + if (!is_tomo) place("pre_exposure", TOGGLE_DEACTIVATE); place("eer_grouping", TOGGLE_DEACTIVATE); place("do_float16", TOGGLE_DEACTIVATE); + if (is_tomo) place("do_even_odd_split"); // Add a little spacer current_y += STEPY/2; + if (!is_tomo) + { + group1 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group1->end(); + place("do_dose_weighting", TOGGLE_DEACTIVATE, group1); + group1->begin(); + place("do_save_noDW", TOGGLE_DEACTIVATE); + group1->end(); - group1 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); - group1->end(); - place("do_dose_weighting", TOGGLE_DEACTIVATE, group1); - group1->begin(); - place("do_save_noDW", TOGGLE_DEACTIVATE); - group1->end(); - - guientries["do_dose_weighting"].cb_menu_i(); // make default active + guientries["do_dose_weighting"].cb_menu_i(); // make default active + } group2 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); group2->end(); @@ -589,8 +627,8 @@ void JobWindow::initialiseCtffindWindow() tab1->label("I/O"); resetHeight(); - place("input_star_mics", TOGGLE_DEACTIVATE); - place("use_noDW", TOGGLE_DEACTIVATE); + place("input_star_mics", TOGGLE_DEACTIVATE); + if (!is_tomo) place("use_noDW", TOGGLE_DEACTIVATE); // Add a little spacer current_y += STEPY/2; @@ -712,13 +750,7 @@ void JobWindow::initialiseManualpickWindow() place("highpass"); place("angpix"); - group2 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); - group2->end(); - place("do_topaz_denoise", TOGGLE_DEACTIVATE, group2); - group2->begin(); - place("fn_topaz_exec", TOGGLE_DEACTIVATE); - group2->end(); - guientries["do_topaz_denoise"].cb_menu_i(); + place("do_topaz_denoise", TOGGLE_DEACTIVATE); tab2->end(); tab3->begin(); @@ -783,7 +815,6 @@ void JobWindow::initialiseAutopickWindow() tab3->label("Topaz"); resetHeight(); - place("fn_topaz_exec"); place("topaz_particle_diameter", TOGGLE_DEACTIVATE); // Add a little spacer @@ -827,6 +858,7 @@ void JobWindow::initialiseAutopickWindow() // Add a little spacer current_y += STEPY/2; + place("fn_topaz_exe", TOGGLE_DEACTIVATE); place("topaz_other_args", TOGGLE_DEACTIVATE); tab3->end(); @@ -1064,7 +1096,7 @@ Pixels values higher than this many times the image stddev will be replaced with void JobWindow::initialiseSelectWindow() { - setupTabs(4); + setupTabs(5); tab1->begin(); tab1->label("I/O"); @@ -1089,7 +1121,6 @@ void JobWindow::initialiseSelectWindow() place("rank_threshold", TOGGLE_DEACTIVATE); place("select_nr_parts", TOGGLE_DEACTIVATE); place("select_nr_classes", TOGGLE_DEACTIVATE); - place("python_exe", TOGGLE_DEACTIVATE); group6->end(); guientries["do_class_ranker"].cb_menu_i(); @@ -1164,6 +1195,21 @@ void JobWindow::initialiseSelectWindow() guientries["do_remove_duplicates"].cb_menu_i(); tab4->end(); + tab5->begin(); + tab5->label("Filaments"); + resetHeight(); + + group3 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group3->end(); + place("do_filaments", TOGGLE_DEACTIVATE, group3); + group3->begin(); + place("dendrogram_threshold", TOGGLE_LEAVE_ACTIVE); + place("dendrogram_minclass", TOGGLE_LEAVE_ACTIVE); + group3->end(); + guientries["do_filaments"].cb_menu_i(); + tab5->end(); + + // Always deactivate the queue option guientries["do_queue"].deactivate_option = TOGGLE_ALWAYS_DEACTIVATE; myjob.joboptions["do_queue"].setString("No"); @@ -1351,7 +1397,17 @@ void JobWindow::initialiseInimodelWindow() tab1->label("I/O"); resetHeight(); - place("fn_img", TOGGLE_DEACTIVATE); + if (is_tomo) + { + place("in_optimisation", TOGGLE_DEACTIVATE); + current_y += STEPY /2 ; + } + + place("fn_img", TOGGLE_DEACTIVATE); + if (is_tomo) + { + place("fn_tomo", TOGGLE_DEACTIVATE); + } place("fn_cont", TOGGLE_REACTIVATE); tab1->end(); @@ -1431,7 +1487,17 @@ void JobWindow::initialiseClass3DWindow() tab1->label("I/O"); resetHeight(); - place("fn_img", TOGGLE_DEACTIVATE); + if (is_tomo) + { + place("in_optimisation", TOGGLE_DEACTIVATE); + current_y += STEPY /2 ; + } + + place("fn_img", TOGGLE_DEACTIVATE); + if (is_tomo) + { + place("fn_tomo", TOGGLE_DEACTIVATE); + } place("fn_cont", TOGGLE_REACTIVATE); place("fn_ref", TOGGLE_DEACTIVATE); place("fn_mask"); @@ -1495,6 +1561,11 @@ void JobWindow::initialiseClass3DWindow() place("highres_limit", TOGGLE_DEACTIVATE); + // Add a little spacer + current_y += STEPY/2; + + place("do_blush", TOGGLE_DEACTIVATE); + tab4->end(); tab5->begin(); @@ -1619,6 +1690,10 @@ void JobWindow::initialiseAutorefineWindow() } place("fn_img", TOGGLE_DEACTIVATE); + if (is_tomo) + { + place("fn_tomo", TOGGLE_DEACTIVATE); + } place("fn_cont", TOGGLE_REACTIVATE); place("fn_ref", TOGGLE_DEACTIVATE); place("fn_mask"); @@ -1663,6 +1738,11 @@ void JobWindow::initialiseAutorefineWindow() place("do_solvent_fsc"); + // Add a little spacer + current_y += STEPY/2; + + place("do_blush", TOGGLE_DEACTIVATE); + tab4->end(); tab5->begin(); tab5->label("Auto-sampling"); @@ -1767,6 +1847,10 @@ void JobWindow::initialiseMultiBodyWindow() current_y += STEPY/2; place("do_subtracted_bodies", TOGGLE_DEACTIVATE); + // Add a little spacer + current_y += STEPY/2; + + place("do_blush", TOGGLE_DEACTIVATE); tab1->end(); tab2->begin(); @@ -2279,6 +2363,124 @@ void JobWindow::initialiseCtfrefineWindow() tab2->end(); } +void JobWindow::initialiseDynaMightWindow() +{ + setupTabs(2); + + tab1->begin(); + tab1->label("I/O"); + resetHeight(); + + place("fn_star", TOGGLE_DEACTIVATE); + place("fn_map", TOGGLE_DEACTIVATE); + //place("fn_mask", TOGGLE_DEACTIVATE); + + current_y += STEPY /2 ; + + place("nr_gaussians", TOGGLE_DEACTIVATE); + place("initial_threshold", TOGGLE_DEACTIVATE); + place("reg_factor", TOGGLE_DEACTIVATE); + + current_y += STEPY /2 ; + + place("fn_dynamight_exe", TOGGLE_DEACTIVATE); + place("gpu_id"); + place("do_preload"); + + tab1->end(); + + + + + tab2->begin(); + tab2->label("Tasks"); + resetHeight(); + + place("fn_checkpoint", TOGGLE_REACTIVATE); + current_y += STEPY /2 ; + + group1 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group1->end(); + + place("do_visualize", TOGGLE_REACTIVATE, group1, false); + group1->begin(); + place("halfset"); + group1->end(); + + current_y += STEPY /2 ; + + group2 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group2->end(); + + place("do_inverse", TOGGLE_REACTIVATE, group2, false); + group2->begin(); + place("nr_epochs"); + place("do_store_deform"); + group2->end(); + + current_y += STEPY /2 ; + + group3 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group3->end(); + place("do_reconstruct",TOGGLE_REACTIVATE, group3, false); + group3->begin(); + place("backproject_batchsize"); + group3->end(); + + // Make defaults active + guientries["do_visualize"].cb_menu_i(); + guientries["do_inverse"].cb_menu_i(); + guientries["do_reconstruct"].cb_menu_i(); + + tab2->end(); +} + +void JobWindow::initialiseModelAngeloWindow() +{ + setupTabs(2); + + tab1->begin(); + tab1->label("I/O"); + resetHeight(); + + // I/O + place("fn_map", TOGGLE_DEACTIVATE); + place("p_seq", TOGGLE_DEACTIVATE); + place("d_seq", TOGGLE_DEACTIVATE); + place("r_seq", TOGGLE_DEACTIVATE); + + current_y += STEPY /2 ; + place("fn_modelangelo_exe", TOGGLE_DEACTIVATE); + place("gpu_id", TOGGLE_DEACTIVATE); + + tab1->end(); + + tab2->begin(); + tab2->label("Hmmer"); + resetHeight(); + + group1 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group1->end(); + + place("do_hhmer", TOGGLE_LEAVE_ACTIVE, group1, false); + current_y += STEPY /2 ; + + group1->begin(); + place("fn_lib", TOGGLE_LEAVE_ACTIVE); + place("alphabet", TOGGLE_LEAVE_ACTIVE); + + current_y += STEPY /2 ; + place("F1", TOGGLE_LEAVE_ACTIVE); + place("F2", TOGGLE_LEAVE_ACTIVE); + place("F3", TOGGLE_LEAVE_ACTIVE); + place("E", TOGGLE_LEAVE_ACTIVE); + + + group1->end(); + //guientries["do_hmmer"].cb_menu_i(); + + tab2->end(); +} void JobWindow::initialiseExternalWindow() { @@ -2347,10 +2549,48 @@ void JobWindow::placeTomoInput(bool has_tomograms, bool has_particles, void JobWindow::initialiseTomoImportWindow() { - setupTabs(3); + setupTabs(5); - tab1->begin(); - tab1->label("Tomograms"); + tab1->begin(); + tab1->label("General"); + resetHeight(); + + place("angpix", TOGGLE_DEACTIVATE); + place("kV", TOGGLE_DEACTIVATE); + place("Cs", TOGGLE_DEACTIVATE); + place("Q0", TOGGLE_DEACTIVATE); + + tab1->end(); + + tab2->begin(); + tab2->label("Tilt series"); + resetHeight(); + + group4 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group4->end(); + place("do_tiltseries", TOGGLE_DEACTIVATE, group4, false); + group4->begin(); + + place("movie_files", TOGGLE_DEACTIVATE); + place("mdoc_files", TOGGLE_DEACTIVATE); + place("prefix", TOGGLE_DEACTIVATE); + + // Add a little spacer + current_y += STEPY/2; + place("dose_rate", TOGGLE_DEACTIVATE); + place("dose_is_per_movie_frame", TOGGLE_DEACTIVATE); + + current_y += STEPY/2; + place("tilt_axis_angle", TOGGLE_DEACTIVATE); + place("mtf_file", TOGGLE_DEACTIVATE); + place("flip_tiltseries_hand", TOGGLE_DEACTIVATE); + place("images_are_motion_corrected", TOGGLE_DEACTIVATE); + + group4->end(); + guientries["do_tiltseries"].cb_menu_i(); // make default active + + tab3->begin(); + tab3->label("Tomograms"); resetHeight(); group1 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); @@ -2360,15 +2600,10 @@ void JobWindow::initialiseTomoImportWindow() place("tomo_star", TOGGLE_DEACTIVATE); place("io_tomos", TOGGLE_DEACTIVATE); - place("angpix", TOGGLE_DEACTIVATE); - place("kV", TOGGLE_DEACTIVATE); - place("Cs", TOGGLE_DEACTIVATE); - place("Q0", TOGGLE_DEACTIVATE); // Add a little spacer current_y += STEPY/2; - place("dose", TOGGLE_DEACTIVATE); place("order_list", TOGGLE_DEACTIVATE); place("do_flipYZ", TOGGLE_DEACTIVATE); place("do_flipZ", TOGGLE_DEACTIVATE); @@ -2377,10 +2612,10 @@ void JobWindow::initialiseTomoImportWindow() group1->end(); guientries["do_tomo"].cb_menu_i(); // make default active - tab1->end(); + tab3->end(); - tab2->begin(); - tab2->label("Coordinates"); + tab4->begin(); + tab4->label("Coordinates"); resetHeight(); group2 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); @@ -2397,10 +2632,10 @@ void JobWindow::initialiseTomoImportWindow() group2->end(); guientries["do_coords"].cb_menu_i(); // make default active - tab2->end(); + tab4->end(); - tab3->begin(); - tab3->label("Others"); + tab5->begin(); + tab5->label("Others"); resetHeight(); group3 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); @@ -2423,7 +2658,185 @@ void JobWindow::initialiseTomoImportWindow() group3->end(); guientries["do_other"].cb_menu_i(); // make default active - tab3->end(); + tab5->end(); +} + +void JobWindow::initialiseTomoAlignTiltseriesWindow() +{ + setupTabs(3); + + tab1->begin(); + tab1->label("I/O"); + resetHeight(); + + place("in_tiltseries", TOGGLE_DEACTIVATE); + + // Add a little spacer + current_y += STEPY/2; + + tab1->end(); + tab2->begin(); + tab2->label("IMOD"); + resetHeight(); + + group1 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group1->end(); + place("do_imod_fiducials", TOGGLE_DEACTIVATE, group1, false); + group1->begin(); + + place("fiducial_diameter", TOGGLE_DEACTIVATE); + group1->end(); + guientries["do_imod_fiducials"].cb_menu_i(); // make default active + + // Add a little spacer + current_y += STEPY/2; + + group2 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group2->end(); + place("do_imod_patchtrack", TOGGLE_DEACTIVATE, group2, false); + group2->begin(); + + place("patch_size", TOGGLE_DEACTIVATE); + place("patch_overlap", TOGGLE_DEACTIVATE); + group2->end(); + guientries["do_imod_patchtrack"].cb_menu_i(); // make default active + + tab2->end(); + tab3->begin(); + tab3->label("AreTomo"); + resetHeight(); + + group3 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group3->end(); + place("do_aretomo", TOGGLE_DEACTIVATE, group3, false); + group3->begin(); + +// place("aretomo_resolution", TOGGLE_DEACTIVATE); + place("aretomo_thickness", TOGGLE_DEACTIVATE); + place("aretomo_tiltcorrect", TOGGLE_DEACTIVATE); + + place("gpu_ids"); + group2->end(); + guientries["do_aretomo"].cb_menu_i(); // make default active + + tab3->end(); + + +} + +void JobWindow::initialiseTomoReconstructTomogramsWindow() +{ + setupTabs(2); + + tab1->begin(); + tab1->label("I/O"); + resetHeight(); + + place("in_tiltseries", TOGGLE_DEACTIVATE); + + tab2->begin(); + tab2->label("Reconstruct"); + resetHeight(); + + + place("xdim", TOGGLE_DEACTIVATE); + place("ydim", TOGGLE_DEACTIVATE); + place("zdim", TOGGLE_DEACTIVATE); + place("binned_angpix", TOGGLE_DEACTIVATE); + + current_y += STEPY /2 ; + + place("generate_split_tomograms", TOGGLE_DEACTIVATE); + + current_y += STEPY /2 ; + + place("tomo_name"); + + tab2->end(); + +} + +void JobWindow::initialiseTomoDenoiseTomogramsWindow() +{ + setupTabs(3); + + tab1->begin(); + tab1->label("I/O"); + resetHeight(); + + place("in_tomoset", TOGGLE_DEACTIVATE); + + current_y += STEPY/2; + + place("gpu_ids"); + + tab2->begin(); + tab2->label("CryoCARE: Train"); + resetHeight(); + + group1 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group1->end(); + place("do_cryocare_train", TOGGLE_DEACTIVATE, group1, false); + + current_y += STEPY /2 ; + + group1->begin(); + place("tomograms_for_training", TOGGLE_DEACTIVATE); + place("number_training_subvolumes", TOGGLE_DEACTIVATE); + place("subvolume_dimensions", TOGGLE_DEACTIVATE); + group1->end(); + guientries["do_cryocare_train"].cb_menu_i(); + + tab2->end(); + + tab3->begin(); + tab3->label("CryoCARE: Predict"); + resetHeight(); + + group2 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group2->end(); + place("do_cryocare_predict", TOGGLE_DEACTIVATE, group2, false); + + current_y += STEPY /2 ; + + group2->begin(); + place("care_denoising_model", TOGGLE_DEACTIVATE); + place3("ntiles_x", "ntiles_y", "ntiles_z", "Number of tiles in X,Y,Z:", TOGGLE_DEACTIVATE); + + current_y += STEPY /2 ; + + place("denoising_tomo_name", TOGGLE_DEACTIVATE); + + group2->end(); + guientries["do_cryocare_predict"].cb_menu_i(); + + tab3->end(); +} + +void JobWindow::initialiseTomoPickTomogramsWindow() +{ + + setupTabs(1); + + tab1->begin(); + tab1->label("I/O"); + resetHeight(); + + place("in_tomoset", TOGGLE_DEACTIVATE); + //place("cache_size", TOGGLE_DEACTIVATE); + + // Add a little spacer + current_y += STEPY/2; + + place("pick_mode", TOGGLE_DEACTIVATE); + + // Add a little spacer + current_y += STEPY/2; + + place("particle_spacing", TOGGLE_DEACTIVATE); + + tab1->end(); + } void JobWindow::initialiseTomoSubtomoWindow() @@ -2442,6 +2855,7 @@ void JobWindow::initialiseTomoSubtomoWindow() current_y += STEPY /2 ; + place("do_stack2d", TOGGLE_DEACTIVATE); place("do_float16", TOGGLE_DEACTIVATE); current_y += STEPY /2 ; @@ -2643,3 +3057,19 @@ void JobWindow::initialiseTomoReconParWindow() tab2->end(); } +void JobWindow::initialiseTomoExcludeTiltImagesWindow() +{ + setupTabs(1); + + tab1->begin(); + tab1->label("I/O"); + resetHeight(); + + place("in_tiltseries", TOGGLE_DEACTIVATE); + + // Add a little spacer + current_y += STEPY/2; + + place("cache_size", TOGGLE_DEACTIVATE); + tab1->end(); +} diff --git a/src/gui_jobwindow.h b/src/gui_jobwindow.h index 9da752da0..f6b428ce6 100644 --- a/src/gui_jobwindow.h +++ b/src/gui_jobwindow.h @@ -127,13 +127,20 @@ class JobWindow : public Fl_Box void initialiseLocresWindow(); void initialiseMotionrefineWindow(); void initialiseCtfrefineWindow(); + void initialiseModelAngeloWindow(); + void initialiseDynaMightWindow(); void initialiseExternalWindow(); - // relion-3.2: add subtomogram averaging programs by Jasenko + // relion-4.0: add subtomogram averaging programs by Jasenko void placeTomoInput(bool has_tomograms, bool has_particles, bool has_trajectories, bool has_manifolds, bool has_halfmaps, bool has_postprocess); void initialiseTomoImportWindow(); - void initialiseTomoSubtomoWindow(); + void initialiseTomoExcludeTiltImagesWindow(); + void initialiseTomoReconstructTomogramsWindow(); + void initialiseTomoAlignTiltseriesWindow(); + void initialiseTomoDenoiseTomogramsWindow(); + void initialiseTomoPickTomogramsWindow(); + void initialiseTomoSubtomoWindow(); void initialiseTomoCtfRefineWindow(); void initialiseTomoAlignWindow(); void initialiseTomoReconParWindow(); diff --git a/src/gui_mainwindow.cpp b/src/gui_mainwindow.cpp index 2295641c1..7d037d130 100644 --- a/src/gui_mainwindow.cpp +++ b/src/gui_mainwindow.cpp @@ -416,6 +416,55 @@ GuiMainWindow::GuiMainWindow(int w, int h, const char* title, FileName fn_pipe, nr_browse_tabs++; browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); + browser->add("Motion correction"); + gui_jobwindows[nr_browse_tabs] = new JobWindow(); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_MOTIONCORR, _do_tomo); + browse_grp[nr_browse_tabs]->end(); + nr_browse_tabs++; + + browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); + browser->add("CTF estimation"); + gui_jobwindows[nr_browse_tabs] = new JobWindow(); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_CTFFIND, _do_tomo); + browse_grp[nr_browse_tabs]->end(); + nr_browse_tabs++; + + browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); + browser->add("Exclude tilt-images"); + gui_jobwindows[nr_browse_tabs] = new JobWindow(); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_TOMO_EXCLUDE_TILT_IMAGES); + browse_grp[nr_browse_tabs]->end(); + nr_browse_tabs++; + + browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); + browser->add("Align tilt-series"); + gui_jobwindows[nr_browse_tabs] = new JobWindow(); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_TOMO_ALIGN_TILTSERIES); + browse_grp[nr_browse_tabs]->end(); + nr_browse_tabs++; + + browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); + browser->add("Reconstruct tomograms"); + gui_jobwindows[nr_browse_tabs] = new JobWindow(); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_TOMO_RECONSTRUCT_TOMOGRAM); + browse_grp[nr_browse_tabs]->end(); + nr_browse_tabs++; + + browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); + browser->add("Denoise tomograms"); + gui_jobwindows[nr_browse_tabs] = new JobWindow(); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_TOMO_DENOISE_TOMOGRAM); + browse_grp[nr_browse_tabs]->end(); + nr_browse_tabs++; + + browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); + browser->add("Pick tomograms"); + gui_jobwindows[nr_browse_tabs] = new JobWindow(); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_TOMO_PICK_TOMOGRAM); + browse_grp[nr_browse_tabs]->end(); + nr_browse_tabs++; + + browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); browser->add("Make pseudo-subtomos"); gui_jobwindows[nr_browse_tabs] = new JobWindow(); gui_jobwindows[nr_browse_tabs]->initialise(PROC_TOMO_SUBTOMO); @@ -477,16 +526,16 @@ GuiMainWindow::GuiMainWindow(int w, int h, const char* title, FileName fn_pipe, } browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); - browser->add("3D initial model"); + browser->add("3D initial reference"); gui_jobwindows[nr_browse_tabs] = new JobWindow(); - gui_jobwindows[nr_browse_tabs]->initialise(PROC_INIMODEL); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_INIMODEL, _do_tomo); browse_grp[nr_browse_tabs]->end(); nr_browse_tabs++; browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); browser->add("3D classification"); gui_jobwindows[nr_browse_tabs] = new JobWindow(); - gui_jobwindows[nr_browse_tabs]->initialise(PROC_3DCLASS); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_3DCLASS, _do_tomo); browse_grp[nr_browse_tabs]->end(); nr_browse_tabs++; @@ -550,6 +599,14 @@ GuiMainWindow::GuiMainWindow(int w, int h, const char* title, FileName fn_pipe, gui_jobwindows[nr_browse_tabs]->initialise(PROC_MOTIONREFINE); browse_grp[nr_browse_tabs]->end(); nr_browse_tabs++; + + browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); + browser->add("DynaMight flexibility"); + gui_jobwindows[nr_browse_tabs] = new JobWindow(); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_DYNAMIGHT); + browse_grp[nr_browse_tabs]->end(); + nr_browse_tabs++; + } browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); @@ -587,6 +644,15 @@ GuiMainWindow::GuiMainWindow(int w, int h, const char* title, FileName fn_pipe, browse_grp[nr_browse_tabs]->end(); nr_browse_tabs++; + if (!_do_tomo) + { + browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); + browser->add("ModelAngelo building"); + gui_jobwindows[nr_browse_tabs] = new JobWindow(); + gui_jobwindows[nr_browse_tabs]->initialise(PROC_MODELANGELO); + browse_grp[nr_browse_tabs]->end(); + nr_browse_tabs++; + } browse_grp[nr_browse_tabs] = new Fl_Group(WCOL0, 2, 550, 615-MENUHEIGHT); browser->add("External"); gui_jobwindows[nr_browse_tabs] = new JobWindow(); @@ -1046,9 +1112,9 @@ void GuiMainWindow::fillStdOutAndErr() in.close(); } // Scroll to the bottom - disp_stdout->insert_position(textbuff_stdout->length()-1); + disp_stdout->insert_position(textbuff_stdout->length()); disp_stdout->show_insert_position(); - disp_expand_stdout->insert_position(textbuff_stdout->length()-1); + disp_expand_stdout->insert_position(textbuff_stdout->length()); disp_expand_stdout->show_insert_position(); } else @@ -1071,9 +1137,9 @@ void GuiMainWindow::fillStdOutAndErr() in.close(); } // Scroll to the bottom - disp_stderr->insert_position(textbuff_stderr->length()-1); + disp_stderr->insert_position(textbuff_stderr->length()); disp_stderr->show_insert_position(); - disp_expand_stderr->insert_position(textbuff_stderr->length()-1); + disp_expand_stderr->insert_position(textbuff_stderr->length()); disp_expand_stderr->show_insert_position(); } else @@ -1406,6 +1472,44 @@ void GuiMainWindow::cb_display_io_node_i() // Other arguments for extraction command += " " + manualpickjob.joboptions["other_args"].getString() + " &"; } + else if (pipeline.nodeList[mynode].type.find(LABEL_TOMO_TILTSERIES) != std::string::npos ) + { + + command = "relion_tomo_view tilt-series --tilt-series-star-file " + pipeline.nodeList[mynode].name; + + // Read cache-size from gui_tomo_exclude_tilt_imagesjob.star if that file exists + RelionJob excludetiltseriesjob; + FileName fn_job = ".gui_tomo_exclude_tilt_images"; + bool iscont=false; + if (exists(fn_job+"job.star")) + { + excludetiltseriesjob.read(fn_job.c_str(), iscont, true); // true means do initialise + command += " --cache-size " + excludetiltseriesjob.joboptions["cache_size"].getString(); + } + + // Run in the background + command += " &"; + + } + else if (pipeline.nodeList[mynode].type.find(LABEL_TOMO_TOMOGRAMS) != std::string::npos ) + { + + command = "relion_tomo_view tomograms --tilt-series-star-file " + pipeline.nodeList[mynode].name; + + // Read cache-size from gui_tomo_exclude_tilt_imagesjob.star if that file exists + RelionJob excludetiltseriesjob; + FileName fn_job = ".gui_tomo_exclude_tilt_images"; + bool iscont=false; + if (exists(fn_job+"job.star")) + { + excludetiltseriesjob.read(fn_job.c_str(), iscont, true); // true means do initialise + command += " --cache-size " + excludetiltseriesjob.joboptions["cache_size"].getString(); + } + + // Run in the background + command += " &"; + + } else if (pipeline.nodeList[mynode].type.find(LABEL_LOGFILE_CPIPE) != std::string::npos || pipeline.nodeList[mynode].type.find(NODE_PDF_LOGFILE_LABEL) != std::string::npos ) { @@ -2392,19 +2496,8 @@ void GuiMainWindow::cb_about(Fl_Widget* o, void* v) void GuiMainWindow::cb_about_i() { #define HELPTEXT ("RELION " RELION_SHORT_VERSION " \n\n\ -RELION is developed in the groups of:\n\n\ -Sjors H.W. Scheres at the MRC Laboratory of Molecular Biology\n\ - - Sjors H.W. Scheres\n\ - - Shaoda He\n\ - - Takanori Nakane\n\ - - Jasenko Zivanov\n\ - - Liyi Dong\n\ - - Dari Kimanius\n\ -\n\ -and Erik Lindahl at Stockholm University\n\ - - Erik Lindahl\n\ - - Björn O. Forsber\n\ -\n\ +RELION is mainly developed in the group of Sjors Scheres at the MRC Laboratory of Molecular Biology. They are grateful for contributions by other labs and companies. \n\ +\ Note that RELION is completely free, open-source software. You can redistribute it and/or modify it for your own purposes, but please do make sure \ the contribution of the developers are acknowledged appropriately. In order to maintain an overview of existing versions, a notification regarding \ any redistribution of (modified versions of) the code is appreciated (contact Sjors directly).\n\n\ @@ -2435,6 +2528,12 @@ If RELION is useful in your work, please cite us. Relevant papers are:\n \n \ Zivanov et al. (2020) IUCrJ (PMID: 32148853)\n\n\ * Amyloid structure determination:\n\ Scheres (2020) Acta Cryst. D (PMID: 32038040)\n\n\ + * Blush regularization:\n\ + Kimanius et al. (2023) preprint (DOI: 10.1101/2023.10.23.563586)\n\n\ + * ModelAngelo:\n\ + Jamali et al. (2023) preprint (DOI: 10.1101/2023.05.16.541002)\n\n\ + * DynaMight:\n\ + Schwab et al. (2023) preprint (DOI: 10.1101/2023.10.18.562877)\n\n\ \ Please also cite relevant papers when you used external programs or their algorithms re-implemented in RELION: \n \n \ * MOTIONCOR2 algorithm for beam-induced motion correction:\n\ @@ -2453,12 +2552,9 @@ Please also cite relevant papers when you used external programs or their algori * Postscript plots are made using CPlot2D from http://www.amzsaki.com\n\n\ \ About the start up screen:\n\n\ -The map shown is the cryo-EM map of mouse heavy-chain apoferritin\n\ -at 1.22 A (EMDB-11638) collected on a new Titan Krios microscope with\n\ -cold FEG, Selectris X energy filter and Falcon4 direct electron detector.\n\ -Densities for hydrogen atoms are visible in the hydrogen difference map\n\ -(orange mesh). See Nakane et al, Nature (2020) (doi:10.1038/s41586-020-2829-0)\n\ -for details. The raw dataset is available at EMPIAR-10424.\ +The map shown is the cryo-EM map of a fungal pheromone receptor STE2 dimer\n\ +without G proteins. Application of Blush algorithm improved the map\n\ +from the left to the middle panel and allowed automatic chain tracing by ModelAngelo (right panel).\n\ ") ShowHelpText *help = new ShowHelpText(HELPTEXT); diff --git a/src/jaz/archive/programs/frame_alignment_program.cpp b/src/jaz/archive/programs/frame_alignment_program.cpp index fc53af321..4af3234e9 100644 --- a/src/jaz/archive/programs/frame_alignment_program.cpp +++ b/src/jaz/archive/programs/frame_alignment_program.cpp @@ -327,9 +327,10 @@ void FrameAlignmentProgram::writeTempData( { temp_positions.addObject(); - temp_positions.setValue(EMDL_IMAGE_COORD_X, pos[p].x - 1, p); - temp_positions.setValue(EMDL_IMAGE_COORD_Y, pos[p].y - 1, p); - temp_positions.setValue(EMDL_IMAGE_COORD_Z, pos[p].z - 1, p); + // SHWS 25nov22 : not sure whether this change is needed in archive, but as of relion-4.1, the origin is back at 0,0,0 + temp_positions.setValue(EMDL_IMAGE_COORD_X, pos[p].x, p); + temp_positions.setValue(EMDL_IMAGE_COORD_Y, pos[p].y, p); + temp_positions.setValue(EMDL_IMAGE_COORD_Z, pos[p].z, p); } temp_positions.write(temp_filename_root + "_positions.star"); diff --git a/src/jaz/archive/programs/polish.cpp b/src/jaz/archive/programs/polish.cpp index 8b46f2c09..396ae02f1 100644 --- a/src/jaz/archive/programs/polish.cpp +++ b/src/jaz/archive/programs/polish.cpp @@ -395,9 +395,14 @@ void PolishProgram::writeTempData( { temp_positions.addObject(); - temp_positions.setValue(EMDL_IMAGE_COORD_X, pos[p].x - 1, p); - temp_positions.setValue(EMDL_IMAGE_COORD_Y, pos[p].y - 1, p); - temp_positions.setValue(EMDL_IMAGE_COORD_Z, pos[p].z - 1, p); + // SHWS25nov22 not sure if this is needed in archive code, but as of relion-4.1, the origin is back at 0,0,0 + //temp_positions.setValue(EMDL_IMAGE_COORD_X, pos[p].x - 1, p); + //temp_positions.setValue(EMDL_IMAGE_COORD_Y, pos[p].y - 1, p); + //temp_positions.setValue(EMDL_IMAGE_COORD_Z, pos[p].z - 1, p); + + temp_positions.setValue(EMDL_IMAGE_COORD_X, pos[p].x, p); + temp_positions.setValue(EMDL_IMAGE_COORD_Y, pos[p].y, p); + temp_positions.setValue(EMDL_IMAGE_COORD_Z, pos[p].z, p); } temp_positions.write(temp_filename_root + "_positions.star"); diff --git a/src/jaz/hip/kernels/add.h b/src/jaz/hip/kernels/add.h new file mode 100644 index 000000000..7d6ebcd87 --- /dev/null +++ b/src/jaz/hip/kernels/add.h @@ -0,0 +1,8 @@ + +// HIP Kernel function to add the elements of two arrays on the GPU +__global__ +void add(int n, float *x, float *y) +{ + for (int i = 0; i < n; i++) + y[i] = x[i] + y[i]; +} diff --git a/src/jaz/hip/test00.h b/src/jaz/hip/test00.h new file mode 100644 index 000000000..54f157b7f --- /dev/null +++ b/src/jaz/hip/test00.h @@ -0,0 +1,11 @@ +#ifndef JAZ_HIP_TEST_00_H +#define JAZ_HIP_TEST_00_H + +class HipTest00 +{ + public: + + void run(); +}; + +#endif //JAZ_HIP_TEST_00_H diff --git a/src/jaz/hip/test00.hip.cpp b/src/jaz/hip/test00.hip.cpp new file mode 100644 index 000000000..07bf6a940 --- /dev/null +++ b/src/jaz/hip/test00.hip.cpp @@ -0,0 +1,39 @@ +#include +#include "test00.h" +#include "kernels/add.h" +#include + +void HipTest00 :: run() +{ + int N = 1<<20; // 1M elements + + // Allocate Unified Memory -- accessible from CPU or GPU + float *x, *y; + hipMallocManaged(&x, N*sizeof(float)); + hipMallocManaged(&y, N*sizeof(float)); + + // initialize x and y arrays on the host + for (int i = 0; i < N; i++) { + x[i] = 1.0f; + y[i] = 2.0f; + } + + // Run kernel on 1M elements on the GPU + hipLaunchKernelGGL(add, 1, 1, 0, 0, N, x, y); + + // Wait for GPU to finish before accessing on host + hipDeviceSynchronize(); + + // Check for errors (all values should be 3.0f) + float maxError = 0.0f; + for (int i = 0; i < N; i++) + { + maxError = fmax(maxError, fabs(y[i]-3.0f)); + } + + std::cout << "Max error: " << maxError << std::endl; + + // Free memory + hipFree(x); + hipFree(y); +} diff --git a/src/jaz/image/normalization.h b/src/jaz/image/normalization.h index 7696d413f..153d2d0b1 100644 --- a/src/jaz/image/normalization.h +++ b/src/jaz/image/normalization.h @@ -44,7 +44,7 @@ BufferedImage Normalization::toUnitInterval(const RawImage& img) const size_t d = img.zdim; T minVal = std::numeric_limits::max(); - T maxVal = -std::numeric_limits::max(); + T maxVal = std::numeric_limits::lowest(); for (size_t z = 0; z < d; z++) for (size_t y = 0; y < h; y++) diff --git a/src/jaz/math/fcc.cpp b/src/jaz/math/fcc.cpp index b22f625b9..3dc31c00a 100644 --- a/src/jaz/math/fcc.cpp +++ b/src/jaz/math/fcc.cpp @@ -54,7 +54,7 @@ BufferedImage FCC::compute3( FCC_by_thread[th].fill(0.0); } - AberrationsCache aberrationsCache(dataSet.optTable, s, dataSet.getOriginalPixelSize(0)); + AberrationsCache aberrationsCache(dataSet.optTable, s, dataSet.getTiltSeriesPixelSize(0)); BufferedImage doseWeights = tomogram.computeDoseWeight(s, 1.0); diff --git a/src/jaz/membrane/blob_2d.cpp b/src/jaz/membrane/blob_2d.cpp index bb0d25e66..70ed734ce 100644 --- a/src/jaz/membrane/blob_2d.cpp +++ b/src/jaz/membrane/blob_2d.cpp @@ -774,7 +774,7 @@ double Blob2D::scanForMinimalRadius(int samples) const double Blob2D::scanForMaximalRadius(int samples) const { - double max = -std::numeric_limits::max(); + double max = std::numeric_limits::lowest(); for (int i = 0; i < samples; i++) { diff --git a/src/jaz/membrane/phaseline_average.cpp b/src/jaz/membrane/phaseline_average.cpp index b37cd8328..76f4ecf28 100644 --- a/src/jaz/membrane/phaseline_average.cpp +++ b/src/jaz/membrane/phaseline_average.cpp @@ -100,7 +100,7 @@ f2Vector PhaseLineAverage::findBounds( const int d = phase.zdim; float minVal = std::numeric_limits::max(); - float maxVal = -std::numeric_limits::max(); + float maxVal = std::numeric_limits::lowest(); for (int z = 0; z < d; z++) for (int y = 0; y < h; y++) diff --git a/src/jaz/optics/damage.cpp b/src/jaz/optics/damage.cpp index db4fae750..78c047c5b 100644 --- a/src/jaz/optics/damage.cpp +++ b/src/jaz/optics/damage.cpp @@ -731,7 +731,7 @@ void Damage::renormalise( { const int tc = B_t.size(); - double B_max = -std::numeric_limits::max(); + double B_max = std::numeric_limits::lowest(); for (int t = 0; t < tc; t++) { diff --git a/src/jaz/optics/dual_contrast/dual_contrast_solution.h b/src/jaz/optics/dual_contrast/dual_contrast_solution.h index 02c968318..f9373fffd 100644 --- a/src/jaz/optics/dual_contrast/dual_contrast_solution.h +++ b/src/jaz/optics/dual_contrast/dual_contrast_solution.h @@ -30,7 +30,7 @@ class DualContrastSolution template DualContrastSolution::ConditionInfo::ConditionInfo() : minimum(std::numeric_limits::max()), - maximum(-std::numeric_limits::max()), + maximum(std::numeric_limits::lowest()), mean(0), std_deviation(0) { diff --git a/src/jaz/optics/optics_data.h b/src/jaz/optics/optics_data.h index ef74e9af9..863186ab2 100644 --- a/src/jaz/optics/optics_data.h +++ b/src/jaz/optics/optics_data.h @@ -10,7 +10,7 @@ class OpticsData OpticsData(); OpticsData(std::string optFn, int verbosity = 1); - double voltage, pixelSize, Cs; + double voltage, pixelSize, Cs, Q0; }; #endif diff --git a/src/jaz/scripts/align_2d_classes.cpp b/src/jaz/scripts/align_2d_classes.cpp index ac0b6110f..a03a1a31e 100644 --- a/src/jaz/scripts/align_2d_classes.cpp +++ b/src/jaz/scripts/align_2d_classes.cpp @@ -299,7 +299,8 @@ int main(int argc, char *argv[]) ObservationModel::saveNew( particles_table, - obs_model.opticsMdt, + obs_model.opticsMdt, + obs_model.generalMdt, outDir+"rotated_particles.star"); diff --git a/src/jaz/scripts/fit_class_blobs.cpp b/src/jaz/scripts/fit_class_blobs.cpp index 4942e13dc..692968ba1 100644 --- a/src/jaz/scripts/fit_class_blobs.cpp +++ b/src/jaz/scripts/fit_class_blobs.cpp @@ -307,7 +307,7 @@ int main(int argc, char *argv[]) Log::endProgress(); - ObservationModel::saveNew(output_particles, obs_model.opticsMdt, outDir + "particles.star"); + ObservationModel::saveNew(output_particles, obs_model.opticsMdt, obs_model.generalMdt, outDir + "particles.star"); return 0; } diff --git a/src/jaz/single_particle/archive/motion_em.cpp b/src/jaz/single_particle/archive/motion_em.cpp index 90209629b..e397f801f 100644 --- a/src/jaz/single_particle/archive/motion_em.cpp +++ b/src/jaz/single_particle/archive/motion_em.cpp @@ -286,7 +286,7 @@ void MotionEM::consolidateVelocities(int maxPc) } } - double maxVal = -std::numeric_limits::max(); + double maxVal = std::numeric_limits::lowest(); for (int y = 0; y < s_vel[f]; y++) for (int x = 0; x < s_vel[f]; x++) diff --git a/src/jaz/single_particle/img_proc/filter_helper.cpp b/src/jaz/single_particle/img_proc/filter_helper.cpp index 92d9afbb9..b09111855 100644 --- a/src/jaz/single_particle/img_proc/filter_helper.cpp +++ b/src/jaz/single_particle/img_proc/filter_helper.cpp @@ -693,7 +693,7 @@ RFLOAT FilterHelper::averageValue(Image& img) RFLOAT FilterHelper::maxValue(Image &img) { - RFLOAT vMax = -std::numeric_limits::max(); + RFLOAT vMax = std::numeric_limits::lowest(); FOR_ALL_DIRECT_NZYX_ELEMENTS_IN_MULTIDIMARRAY(img.data) { @@ -1848,7 +1848,7 @@ Image FilterHelper::normaliseToUnitInterval(const Image &img) const int c = img.data.ndim; RFLOAT minVal = std::numeric_limits::max(); - RFLOAT maxVal = -std::numeric_limits::max(); + RFLOAT maxVal = std::numeric_limits::lowest(); for (int n = 0; n < c; n++) for (int z = 0; z < d; z++) diff --git a/src/jaz/single_particle/micrograph_handler.cpp b/src/jaz/single_particle/micrograph_handler.cpp index 8894c859e..387399bf6 100644 --- a/src/jaz/single_particle/micrograph_handler.cpp +++ b/src/jaz/single_particle/micrograph_handler.cpp @@ -414,7 +414,9 @@ std::vector>> MicrographHandler::loadMovie( { if (eer_upsampling < 0) eer_upsampling = micrograph.getEERUpsampling(); - EERRenderer::loadEERGain(gainFn, lastGainRef(), eer_upsampling); + EERRenderer renderer; + renderer.read(movieFn, eer_upsampling); + renderer.loadEERGain(gainFn, lastGainRef()); } else { diff --git a/src/jaz/single_particle/motion/motion_estimator.cpp b/src/jaz/single_particle/motion/motion_estimator.cpp index 37b0a3f4f..8e51438c0 100644 --- a/src/jaz/single_particle/motion/motion_estimator.cpp +++ b/src/jaz/single_particle/motion/motion_estimator.cpp @@ -436,6 +436,7 @@ void MotionEstimator::prepMicrograph( for (int f = 0; f < fc; f++) { + MotionHelper::noiseNormalize(movie[p][f], sigma2, movie[p][f]); } } diff --git a/src/jaz/single_particle/motion/motion_refiner.cpp b/src/jaz/single_particle/motion/motion_refiner.cpp index 240c9bbe9..e6123b9b5 100644 --- a/src/jaz/single_particle/motion/motion_refiner.cpp +++ b/src/jaz/single_particle/motion/motion_refiner.cpp @@ -92,7 +92,7 @@ void MotionRefiner::read(int argc, char **argv) movie_toReplace = parser.getOption("--mov_toReplace", "Replace this string in micrograph names...", ""); movie_replaceBy = parser.getOption("--mov_replaceBy", "..by this one", ""); - micrographHandler.eer_upsampling = textToInteger(parser.getOption("--eer_upsampling", "EER upsampling (1 = 4K or 2 = 8K)", "-1")); + micrographHandler.eer_upsampling = textToInteger(parser.getOption("--eer_upsampling", "EER upsampling (1 = physical or 2 = 2x super-resolution)", "-1")); micrographHandler.eer_grouping = textToInteger(parser.getOption("--eer_grouping", "EER grouping", "-1")); if (micrographHandler.eer_upsampling > 0 || micrographHandler.eer_grouping > 0) diff --git a/src/jaz/single_particle/noise_helper.cpp b/src/jaz/single_particle/noise_helper.cpp index 8da80e139..eb279fc79 100644 --- a/src/jaz/single_particle/noise_helper.cpp +++ b/src/jaz/single_particle/noise_helper.cpp @@ -106,7 +106,7 @@ Image NoiseHelper::predictCCNoise(Projector &prj, double sigma2, const double sigmaCC = sqrt(sigma2CC); RFLOAT vMin = std::numeric_limits::max(); - RFLOAT vMax = -std::numeric_limits::max(); + RFLOAT vMax = std::numeric_limits::lowest(); for (long int y = 0; y < s; y++) for (long int x = 0; x < s; x++) diff --git a/src/jaz/single_particle/obs_model.cpp b/src/jaz/single_particle/obs_model.cpp index 617ff52ba..efbdb7de6 100644 --- a/src/jaz/single_particle/obs_model.cpp +++ b/src/jaz/single_particle/obs_model.cpp @@ -931,6 +931,52 @@ void ObservationModel::sortOpticsGroups(MetaDataTable& partMdt) } } +void ObservationModel::removeUnusedOpticsGroups(MetaDataTable& partMdt) +{ + std::set usedGroups; + for (long int i = 0; i < partMdt.numberOfObjects(); i++) + { + int og; + partMdt.getValue(EMDL_IMAGE_OPTICS_GROUP, og, i); + if (usedGroups.find(og) == usedGroups.end()) + { + usedGroups.insert(og); + } + } + + // Now renumber the remaining optics groups + std::map old2new; + int found = 0; + for (int i = 0; i < opticsMdt.numberOfObjects(); i++) + { + int og; + opticsMdt.getValue(EMDL_IMAGE_OPTICS_GROUP, og, i); + if (usedGroups.find(og) == usedGroups.end()) + { + old2new[og] = -1; + } + else + { + old2new[og] = found + 1; + found++; + } + + opticsMdt.setValue(EMDL_IMAGE_OPTICS_GROUP, old2new[og], i); + } + + // Remove all unused optics groups from the opticsMdt + opticsMdt = subsetMetaDataTable(opticsMdt, EMDL_IMAGE_OPTICS_GROUP, 1, 99999); + + // Change all optics_groups entries in the particle table + for (long int i = 0; i < partMdt.numberOfObjects(); i++) + { + int og; + partMdt.getValue(EMDL_IMAGE_OPTICS_GROUP, og, i); + partMdt.setValue(EMDL_IMAGE_OPTICS_GROUP, old2new[og], i); + } + +} + std::vector ObservationModel::getOptGroupsPresent_oneBased(const MetaDataTable& partMdt) const { const int gc = opticsMdt.numberOfObjects(); @@ -1291,6 +1337,10 @@ void ObservationModel::loadSafely(std::string filename, ObservationModel& obsMod { mytablename = "movies"; } + else if (particlesMdt.read(filename, "tilt_images")) + { + mytablename = "tilt_images"; + } } else { @@ -1319,6 +1369,8 @@ void ObservationModel::loadSafely(std::string filename, ObservationModel& obsMod particlesMdt.setName("particles"); else if (particlesMdt.containsLabel(EMDL_MICROGRAPH_MOVIE_NAME)) particlesMdt.setName("movies"); + else if (particlesMdt.containsLabel(EMDL_TOMO_TILT_MOVIE_INDEX)) + particlesMdt.setName("tilt_images"); else particlesMdt.setName("micrographs"); } @@ -1376,17 +1428,32 @@ void ObservationModel::loadSafely(std::string filename, ObservationModel& obsMod } } } + + // Deal with subtomogram_stack2d table + obsModel.generalMdt.read(filename, "general"); + if (obsModel.generalMdt.numberOfObjects() > 0) + obsModel.generalMdt.getValue(EMDL_TOMO_SUBTOMOGRAM_STACK2D, obsModel.isTomoStack2D); + else + obsModel.isTomoStack2D = false; + } void ObservationModel::saveNew( MetaDataTable &particlesMdt, MetaDataTable &opticsMdt, + MetaDataTable &generalMdt, std::string filename, std::string tablename) { std::string tmpfilename = filename + ".tmp"; std::ofstream of(tmpfilename); + if (generalMdt.numberOfObjects() > 0) + { + generalMdt.setName("general"); + generalMdt.write(of); + } + opticsMdt.setName("optics"); opticsMdt.write(of); @@ -1401,6 +1468,12 @@ void ObservationModel::save(MetaDataTable &particlesMdt, std::string filename, s std::string tmpfilename = filename + ".tmp"; std::ofstream of(tmpfilename); + if (generalMdt.numberOfObjects() > 0) + { + generalMdt.setName("general"); + generalMdt.write(of); + } + opticsMdt.setName("optics"); opticsMdt.write(of); diff --git a/src/jaz/single_particle/obs_model.h b/src/jaz/single_particle/obs_model.h index 90810e734..f4f0724e6 100644 --- a/src/jaz/single_particle/obs_model.h +++ b/src/jaz/single_particle/obs_model.h @@ -42,11 +42,11 @@ class ObservationModel ObservationModel(const MetaDataTable &opticsMdt, bool do_die_upon_error = true); - MetaDataTable opticsMdt; + MetaDataTable opticsMdt, generalMdt; bool hasEvenZernike, hasOddZernike, hasMagMatrices, - hasBoxSizes, hasMultipleMtfs; + hasBoxSizes, hasMultipleMtfs, isTomoStack2D; @@ -150,7 +150,7 @@ class ObservationModel int verb = 0, bool do_die_upon_error = true); static void saveNew( - MetaDataTable& particlesMdt, MetaDataTable& opticsMdt, + MetaDataTable& particlesMdt, MetaDataTable& opticsMdt, MetaDataTable& generalMdt, std::string filename, std::string _tablename = "particles"); void save( @@ -219,6 +219,10 @@ class ObservationModel (Merely changing the order in opticsMdt would fail if groups were missing.) */ void sortOpticsGroups(MetaDataTable& partMdt); + /* Remove unused optics groups, e.g. after a subset of the particles have been selected + and also renumber the groups in the particle table partMdt */ + void removeUnusedOpticsGroups(MetaDataTable& partMdt); + /* Return the set of optics groups present in partMdt */ std::vector getOptGroupsPresent_oneBased(const MetaDataTable& partMdt) const; diff --git a/src/jaz/tomography/apps/import_particles.cpp b/src/jaz/tomography/apps/import_particles.cpp index 968137999..f925ce33a 100644 --- a/src/jaz/tomography/apps/import_particles.cpp +++ b/src/jaz/tomography/apps/import_particles.cpp @@ -218,6 +218,7 @@ int main(int argc, char *argv[]) opticsTable.setValue(EMDL_IMAGE_OPTICS_GROUP_NAME, opticsGroupName, optGroup); opticsTable.setValue(EMDL_CTF_CS, tomogram.optics.Cs, optGroup); opticsTable.setValue(EMDL_CTF_VOLTAGE, tomogram.optics.voltage, optGroup); + opticsTable.setValue(EMDL_CTF_Q0, tomogram.optics.Q0, optGroup); opticsTable.setValue(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, tomogram.optics.pixelSize, optGroup); // _rlnMicrographBinning, _rlnCtfDataAreCtfPremultiplied, _rlnImageDimensionality, diff --git a/src/jaz/tomography/apps/import_tomograms.cpp b/src/jaz/tomography/apps/import_tomograms.cpp index 8ad2fb8bc..f85bc8a85 100644 --- a/src/jaz/tomography/apps/import_tomograms.cpp +++ b/src/jaz/tomography/apps/import_tomograms.cpp @@ -381,17 +381,12 @@ int main(int argc, char *argv[]) ctfs[i] = ctfs[mapping.oldFrameIndex[i]]; } - std::string opticsGroupName = "opticsGroup1"; - - perTomoArguments.getValue(EMDL_IMAGE_OPTICS_GROUP_NAME, opticsGroupName, tomo_index); - - - tomograms.addTomogram( - name, tsFn, - mapping.projections, - mapping.w, mapping.h, mapping.d, - cumulativeDose, fractionalDose, - ctfs, hand, pixelSize, opticsGroupName); + tomograms.addTomogramFromIMODStack( + name, tsFn, + mapping.projections, + mapping.w, mapping.h, mapping.d, + cumulativeDose, fractionalDose, + ctfs, hand, pixelSize); Log::endSection(); } diff --git a/src/jaz/tomography/apps/predict_tilt_series.cpp b/src/jaz/tomography/apps/predict_tilt_series.cpp index e205198a2..ba525abb5 100644 --- a/src/jaz/tomography/apps/predict_tilt_series.cpp +++ b/src/jaz/tomography/apps/predict_tilt_series.cpp @@ -101,7 +101,7 @@ void run(int argc, char *argv[]) tomogram.validateParticleOptics(particles, particle_set); - AberrationsCache aberrations_cache(particle_set.optTable, box_size, particle_set.getOriginalPixelSize(0)); + AberrationsCache aberrations_cache(particle_set.optTable, box_size, particle_set.getTiltSeriesPixelSize(0)); BufferedImage doseWeights = tomogram.computeDoseWeight(box_size, 1); diff --git a/src/jaz/tomography/apps/reconstruct_tomogram.cpp b/src/jaz/tomography/apps/reconstruct_tomogram.cpp index c1e69e9a7..e9aa639a3 100644 --- a/src/jaz/tomography/apps/reconstruct_tomogram.cpp +++ b/src/jaz/tomography/apps/reconstruct_tomogram.cpp @@ -9,7 +9,9 @@ int main(int argc, char *argv[]) TomoBackprojectProgram program; program.readParameters(argc, argv); + program.initialise(); program.run(); + program.writeOutput(); } catch (RelionError e) { diff --git a/src/jaz/tomography/apps/reconstruct_tomogram_mpi.cpp b/src/jaz/tomography/apps/reconstruct_tomogram_mpi.cpp new file mode 100644 index 000000000..c83c4f965 --- /dev/null +++ b/src/jaz/tomography/apps/reconstruct_tomogram_mpi.cpp @@ -0,0 +1,54 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * 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. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#include +#include +#include + + +int main(int argc, char *argv[]) +{ + try + { + int rank, size; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + // Handle errors + MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN); + + TomoBackprojectProgram program; + + program.readParameters(argc, argv); + program.initialise(rank==0); + program.run(rank, size); + MPI_Barrier(MPI_COMM_WORLD); + if (rank==0) program.writeOutput(true); + } + catch (RelionError e) + { + return RELION_EXIT_FAILURE; + } + + MPI_Barrier(MPI_COMM_WORLD); + MPI_Finalize(); + + return RELION_EXIT_SUCCESS; +} diff --git a/src/jaz/tomography/extraction.h b/src/jaz/tomography/extraction.h index 98c77077d..ca7cc3945 100644 --- a/src/jaz/tomography/extraction.h +++ b/src/jaz/tomography/extraction.h @@ -45,6 +45,7 @@ class TomoExtraction static void extractAt2D_Fourier( const RawImage& stack, int s, double bin, const std::vector& projIn, + const std::vector& selectedFrameIndex, const std::vector& centers, const std::vector& isVisible, RawImage>& out, @@ -113,8 +114,8 @@ void TomoExtraction::extractFrameAt3D_Fourier( const gravis::d2Vector center2D = tomogram.projectPoint(center, f); extractAt2D_Fourier( - stack.getConstSliceRef(f), s, bin, {tomogram.projectionMatrices[f]}, - {center2D}, {true}, out, projVec, num_threads, circle_crop); + stack.getConstSliceRef(f), s, bin, {tomogram.projectionMatrices[f]}, {tomogram.selectedFrameIndex}, + {center2D}, {true}, out, projVec, num_threads, circle_crop); projOut = projVec[0]; } @@ -139,7 +140,7 @@ void TomoExtraction::extractAt3D_Fourier( } extractAt2D_Fourier( - stack, s, bin, tomogram.projectionMatrices, centers, isVisible, + stack, s, bin, tomogram.projectionMatrices, tomogram.selectedFrameIndex, centers, isVisible, out, projOut, num_threads, circle_crop); } @@ -147,6 +148,7 @@ template void TomoExtraction::extractAt2D_Fourier( const RawImage& stack, int s, double bin, const std::vector& projIn, + const std::vector& selectedFrameIndex, const std::vector& centers, const std::vector& isVisible, RawImage>& out, @@ -155,23 +157,26 @@ void TomoExtraction::extractAt2D_Fourier( bool circle_crop) { const int sh = s/2 + 1; - const int fc = stack.zdim; - const int sb = (int)(s / bin + 0.5); - const int sbh = sb/2 + 1; - - BufferedImage smallStack(s,s,fc); - projOut.resize(fc); - - std::vector integralShift(fc); + + std::vector integralShift; - for (int f = 0; f < fc; f++) + int nr_selected_frames = 0; + for (int f = 0; f < selectedFrameIndex.size(); f++) { - integralShift[f] = gravis::d2Vector( - round(centers[f].x) - s/2, - round(centers[f].y) - s/2); + int fp = selectedFrameIndex[f]; + if (fp >= 0) + { + integralShift.push_back(gravis::d2Vector( + round(centers[f].x) - s/2, + round(centers[f].y) - s/2)); + nr_selected_frames++; + } } - + + BufferedImage smallStack(s,s,nr_selected_frames); + projOut.resize(nr_selected_frames); + extractSquares(stack, s, s, integralShift, isVisible, smallStack, false, num_threads); if (circle_crop) @@ -179,21 +184,25 @@ void TomoExtraction::extractAt2D_Fourier( cropCircle(smallStack, 0, EDGE_FALLOFF, num_threads); } - std::vector posInNewImg(fc); + std::vector posInNewImg(nr_selected_frames); - for (int f = 0; f < fc; f++) + for (int f = 0; f < selectedFrameIndex.size(); f++) { - projOut[f] = projIn[f]; - - projOut[f](0,3) += sb/2 - centers[f].x; - projOut[f](1,3) += sb/2 - centers[f].y; - - posInNewImg[f] = (centers[f] - integralShift[f]) / bin; - } - - BufferedImage> smallStackFS(sh,s,fc); + int fp = selectedFrameIndex[f]; + if (fp >= 0) + { + projOut[fp] = projIn[f]; + + projOut[fp](0,3) += sb/2 - centers[f].x; + projOut[fp](1,3) += sb/2 - centers[f].y; + + posInNewImg[fp] = (centers[f] - integralShift[fp]) / bin; + } + } - NewStackHelper::FourierTransformStack_fast(smallStack, smallStackFS, true, num_threads); + BufferedImage> smallStackFS(sh,s,nr_selected_frames); + + NewStackHelper::FourierTransformStack_fast(smallStack, smallStackFS, true, num_threads); if (bin != 1.0) { @@ -251,7 +260,7 @@ void TomoExtraction::extractAt2D_real( BufferedImage smallStack(s,s,fc); projOut.resize(fc); - + std::vector integralShift(fc); for (int f = 0; f < fc; f++) diff --git a/src/jaz/tomography/particle_set.cpp b/src/jaz/tomography/particle_set.cpp index ecc3f7a50..f16259f1f 100644 --- a/src/jaz/tomography/particle_set.cpp +++ b/src/jaz/tomography/particle_set.cpp @@ -13,11 +13,106 @@ using namespace gravis; ParticleSet::ParticleSet() {} -ParticleSet::ParticleSet(std::string filename, std::string motionFilename, bool verbose) +ParticleSet::ParticleSet(std::string filename, std::string motionFilename, bool verbose, const TomogramSet *tomogramSet) { - optTable.read(filename, "optics"); - partTable.read(filename, "particles"); - + if (!read(filename, motionFilename, verbose, tomogramSet)) + REPORT_ERROR("ERROR: there are no particles in " + filename); +} + +bool ParticleSet::read(std::string filename, std::string motionFilename, bool verbose, const TomogramSet *tomogramSet) +{ + + if (genTable.read(filename,"general")) + { + genTable.getValueSafely(EMDL_TOMO_SUBTOMOGRAM_STACK2D, is_stack2d); + if (genTable.containsLabel(EMDL_TOMO_SUBTOMOGRAM_MAXDOSE)) + genTable.getValueSafely(EMDL_TOMO_SUBTOMOGRAM_MAXDOSE, max_dose); + else + max_dose = -1.; + } + else + { + is_stack2d = false; + max_dose = -1.; + genTable.setIsList(true); + genTable.addObject(); + genTable.setValue(EMDL_TOMO_SUBTOMOGRAM_STACK2D, is_stack2d); + } + + optTable.read(filename, "optics"); + partTable.read(filename, "particles"); + + // subtomo can call to this function without a good particleSet yet, when coming straight out of subtomogram particle picking + // In that case, initialise optics groups from the tomogramSet + if (optTable.numberOfObjects() == 0) + { + + // Check which tomo_names are present in the partTable + std::vector tomo_names; + std::string my_prev_name=""; + std::map tomoname_to_optics_group; + FOR_ALL_OBJECTS_IN_METADATA_TABLE(partTable) + { + std::string myname; + partTable.getValue(EMDL_TOMO_NAME, myname); + if (myname != my_prev_name) + { + bool is_new = true; + for (size_t i = 0; i < tomo_names.size(); i++) + { + if (myname == tomo_names[i]) is_new = false; + } + if (is_new) + { + tomo_names.push_back(myname); + tomoname_to_optics_group.insert(std::make_pair(myname, tomo_names.size())); + } + } + my_prev_name = myname; + } + + // construct optics table with those tomo_names that are present in the partTable + optTable.setName("optics"); + if (!tomogramSet->globalTable.containsLabel(EMDL_CTF_VOLTAGE)) + REPORT_ERROR("ERROR: tomogramSet->globalTable does not contain rlnVoltage label"); + if (!tomogramSet->globalTable.containsLabel(EMDL_CTF_Q0)) + REPORT_ERROR("ERROR: tomogramSet->globalTable does not contain rlnAmplitudeContrast label"); + if (!tomogramSet->globalTable.containsLabel(EMDL_CTF_CS)) + REPORT_ERROR("ERROR: tomogramSet->globalTable does not contain rlnSphericalAberration label"); + if (!tomogramSet->globalTable.containsLabel(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE)) + REPORT_ERROR("ERROR: tomogramSet->globalTable does not contain rlnTomoTiltSeriesPixelSize label"); + for (size_t i = 0; i < tomo_names.size(); i++) + { + int idx = tomogramSet->getTomogramIndex(tomo_names[i]); + double Q0, Cs, kV, tiltSeriesPixelSize; + tomogramSet->globalTable.getValue(EMDL_CTF_VOLTAGE, kV, idx); + tomogramSet->globalTable.getValue(EMDL_CTF_CS, Cs, idx); + tomogramSet->globalTable.getValue(EMDL_CTF_Q0, Q0, idx); + tomogramSet->globalTable.getValue(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, tiltSeriesPixelSize, idx); + + optTable.addObject(); + optTable.setValue(EMDL_CTF_VOLTAGE, kV); + optTable.setValue(EMDL_CTF_CS, Cs); + optTable.setValue(EMDL_CTF_Q0, Q0); + optTable.setValue(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, tiltSeriesPixelSize); + optTable.setValue(EMDL_IMAGE_OPTICS_GROUP, tomoname_to_optics_group[ tomo_names[i] ]); + optTable.setValue(EMDL_IMAGE_OPTICS_GROUP_NAME, tomo_names[i]); + } + + // Now also set optics groups in partTable + FOR_ALL_OBJECTS_IN_METADATA_TABLE(partTable) + { + std::string myname; + partTable.getValue(EMDL_TOMO_NAME, myname); + partTable.setValue(EMDL_IMAGE_OPTICS_GROUP, tomoname_to_optics_group[myname]); + } + + } + + + long int pc = partTable.numberOfObjects(); + if (pc == 0) return false; + if (!optTable.containsLabel(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE)) { REPORT_ERROR("ParticleSet::ParticleSet: " @@ -66,6 +161,8 @@ ParticleSet::ParticleSet(std::string filename, std::string motionFilename, bool { motionTrajectories = Trajectory::read(motionFilename, *this); } + + return true; } void ParticleSet::reserve(int particleNumber) @@ -124,7 +221,7 @@ std::vector > ParticleSet::splitByTomogram(const Tomo std::map name_to_index; - for (int t = 0; t < tc; t++) + for (int t = 0; t < tc; t++) { const std::string name = tomogramSet.globalTable.getString(EMDL_TOMO_NAME, t); name_to_index[name] = t; @@ -247,16 +344,18 @@ d3Vector ParticleSet::getPosition(ParticleIndex particle_id) const { const int og = getOpticsGroup(particle_id); - const double originalPixelSize = optTable.getDouble(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, og); + const double tiltSeriesPixelSize = getTiltSeriesPixelSize(og); const d3Matrix A_subtomogram = getSubtomogramMatrix(particle_id); - d3Vector out = getParticleCoord(particle_id) - (A_subtomogram * getParticleOffset(particle_id)) / originalPixelSize; - - out.x += 1.0; - out.y += 1.0; - out.z += 1.0; - + d3Vector out = getParticleCoord(particle_id) - (A_subtomogram * getParticleOffset(particle_id)) / tiltSeriesPixelSize; + + // /* SHWS & ABurt 19Jul2022: let's no longer do this in relion-4.1 + //out.x += 1.0; + //out.y += 1.0; + //out.z += 1.0; + // */ + return out; } @@ -365,18 +464,30 @@ std::string ParticleSet::getName(ParticleIndex particle_id) const int ParticleSet::getHalfSet(ParticleIndex particle_id) const { - int s; - partTable.getValueSafely(EMDL_PARTICLE_RANDOM_SUBSET, s, particle_id.value); + if (!hasHalfSets()) + REPORT_ERROR("ERROR: function getHalfSet was called without having halfsets in the particle star file."); + + int s; + partTable.getValue(EMDL_PARTICLE_RANDOM_SUBSET, s, particle_id.value); return s - 1; } +bool ParticleSet::hasHalfSets() const +{ + return partTable.containsLabel(EMDL_PARTICLE_RANDOM_SUBSET); +} void ParticleSet::moveParticleTo(ParticleIndex particle_id, gravis::d3Vector pos) { - partTable.setValue(EMDL_IMAGE_COORD_X, pos.x - 1.0, particle_id.value); - partTable.setValue(EMDL_IMAGE_COORD_Y, pos.y - 1.0, particle_id.value); - partTable.setValue(EMDL_IMAGE_COORD_Z, pos.z - 1.0, particle_id.value); - + // SHWS 25nov22: as of relion-4.1, the origin is now at 0,0,0 again + //partTable.setValue(EMDL_IMAGE_COORD_X, pos.x - 1.0, particle_id.value); + //partTable.setValue(EMDL_IMAGE_COORD_Y, pos.y - 1.0, particle_id.value); + //partTable.setValue(EMDL_IMAGE_COORD_Z, pos.z - 1.0, particle_id.value); + + partTable.setValue(EMDL_IMAGE_COORD_X, pos.x, particle_id.value); + partTable.setValue(EMDL_IMAGE_COORD_Y, pos.y, particle_id.value); + partTable.setValue(EMDL_IMAGE_COORD_Z, pos.z, particle_id.value); + partTable.setValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, 0.0, particle_id.value); partTable.setValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, 0.0, particle_id.value); partTable.setValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, 0.0, particle_id.value); @@ -395,10 +506,14 @@ void ParticleSet::shiftParticleBy(ParticleIndex particle_id, gravis::d3Vector sh partTable.setValue(EMDL_IMAGE_COORD_Z, z + shift.z, particle_id.value); } -void ParticleSet::write(const std::string& filename) const +void ParticleSet::write(const std::string& filename) { std::ofstream ofs(filename); + genTable.setName("general"); + genTable.setValue(EMDL_TOMO_SUBTOMOGRAM_STACK2D, is_stack2d); + genTable.setValue(EMDL_TOMO_SUBTOMOGRAM_MAXDOSE, max_dose); + genTable.write(ofs); optTable.write(ofs); partTable.write(ofs); } @@ -445,17 +560,27 @@ void ParticleSet::writeTrajectories(const std::string &filename) const void ParticleSet::setImageFileNames(std::string data, std::string weight, ParticleIndex particle_id) { partTable.setValue(EMDL_IMAGE_NAME, data, particle_id.value); - partTable.setValue(EMDL_CTF_IMAGE, weight, particle_id.value); + if (weight != "") + partTable.setValue(EMDL_CTF_IMAGE, weight, particle_id.value); } d3Vector ParticleSet::getParticleOffset(ParticleIndex particle_id) const { d3Vector out; - partTable.getValueSafely(EMDL_ORIENT_ORIGIN_X_ANGSTROM, out.x, particle_id.value); - partTable.getValueSafely(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, out.y, particle_id.value); - partTable.getValueSafely(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, out.z, particle_id.value); - + if (partTable.containsLabel(EMDL_ORIENT_ORIGIN_X_ANGSTROM)) + partTable.getValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, out.x, particle_id.value); + else + out.x = 0.; + if (partTable.containsLabel(EMDL_ORIENT_ORIGIN_Y_ANGSTROM)) + partTable.getValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, out.y, particle_id.value); + else + out.y = 0.; + if (partTable.containsLabel(EMDL_ORIENT_ORIGIN_Z_ANGSTROM)) + partTable.getValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, out.z, particle_id.value); + else + out.z = 0.; + return out; } @@ -488,7 +613,7 @@ int ParticleSet::getOpticsGroup(ParticleIndex particle_id) const { if (!partTable.containsLabel(EMDL_IMAGE_OPTICS_GROUP)) { - REPORT_ERROR("ParticleSet::getPixelSize: pixel size (rlnImagePixelSize) missing from optics table"); + REPORT_ERROR("ParticleSet::getOpticsGroup: optics group (rlnOpticsGroup) is missing from optics table"); } int out; @@ -506,23 +631,11 @@ int ParticleSet::numberOfOpticsGroups() const return optTable.numberOfObjects(); } -double ParticleSet::getBinnedPixelSize(int opticsGroup) const -{ - if (!optTable.containsLabel(EMDL_IMAGE_PIXEL_SIZE)) - { - REPORT_ERROR("ParticleSet::getBinnedPixelSize: pixel size (rlnImagePixelSize) missing from optics table"); - } - - double out; - optTable.getValueSafely(EMDL_IMAGE_PIXEL_SIZE, out, opticsGroup); - return out; -} - -double ParticleSet::getOriginalPixelSize(int opticsGroup) const +double ParticleSet::getTiltSeriesPixelSize(int opticsGroup) const { if (!optTable.containsLabel(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE)) { - REPORT_ERROR("ParticleSet::getOriginalPixelSize: tilt series pixel size (rlnTomoTiltSeriesPixelSize) missing from optics table"); + REPORT_ERROR("ParticleSet::getTiltSeriesPixelSize: tilt series pixel size (rlnTomoTiltSeriesPixelSize) missing from optics table"); } double out; diff --git a/src/jaz/tomography/particle_set.h b/src/jaz/tomography/particle_set.h index 65057a18a..1b8daff07 100644 --- a/src/jaz/tomography/particle_set.h +++ b/src/jaz/tomography/particle_set.h @@ -25,10 +25,14 @@ class ParticleSet public: ParticleSet(); - ParticleSet(std::string filename, std::string motionFilename = "", bool verbose = true); - - MetaDataTable partTable, optTable; + ParticleSet(std::string filename, std::string motionFilename = "", bool verbose = true, const TomogramSet *tomogramSet = NULL); + bool read(std::string filename, std::string motionFilename = "", bool verbose = true, const TomogramSet *tomogramSet = NULL); + + MetaDataTable partTable, optTable, genTable; + + bool is_stack2d; + double max_dose; bool hasMotion; std::vector motionTrajectories; @@ -51,11 +55,12 @@ class ParticleSet std::string getName(ParticleIndex particle_id) const; int getHalfSet(ParticleIndex particle_id) const; + bool hasHalfSets() const; void moveParticleTo(ParticleIndex particle_id, gravis::d3Vector pos); void shiftParticleBy(ParticleIndex particle_id, gravis::d3Vector shift); - void write(const std::string& filename) const; + void write(const std::string& filename); void writeTrajectories(const std::string& filename) const; void setImageFileNames(std::string data, std::string weight, ParticleIndex particle_id); @@ -70,8 +75,7 @@ class ParticleSet void setOpticsGroup(ParticleIndex particle_id, int zeroBasedId); int numberOfOpticsGroups() const; - double getBinnedPixelSize(int opticsGroup) const; - double getOriginalPixelSize(int opticsGroup) const; + double getTiltSeriesPixelSize(int opticsGroup) const; std::vector getTrajectoryInPixels(ParticleIndex particle_id, int fc, double pixelSize, bool from_original_coordinate = false) const; diff --git a/src/jaz/tomography/programs/align.cpp b/src/jaz/tomography/programs/align.cpp index 8e967e2bd..78714f722 100644 --- a/src/jaz/tomography/programs/align.cpp +++ b/src/jaz/tomography/programs/align.cpp @@ -41,7 +41,7 @@ void AlignProgram::run() initialise(); - AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getOriginalPixelSize(0)); + AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getTiltSeriesPixelSize(0)); Log::endSection(); @@ -374,9 +374,14 @@ void AlignProgram::writeTempAlignmentData( { temp_positions.addObject(); - temp_positions.setValue(EMDL_IMAGE_COORD_X, pos[p].x - 1, p); - temp_positions.setValue(EMDL_IMAGE_COORD_Y, pos[p].y - 1, p); - temp_positions.setValue(EMDL_IMAGE_COORD_Z, pos[p].z - 1, p); + // SHWS 25nov22: relion-4.1 has origins at 0,0,0, no longer at 1,1,1 like relion-4.0 and some old imod. + //temp_positions.setValue(EMDL_IMAGE_COORD_X, pos[p].x - 1, p); + //temp_positions.setValue(EMDL_IMAGE_COORD_Y, pos[p].y - 1, p); + //temp_positions.setValue(EMDL_IMAGE_COORD_Z, pos[p].z - 1, p); + + temp_positions.setValue(EMDL_IMAGE_COORD_X, pos[p].x, p); + temp_positions.setValue(EMDL_IMAGE_COORD_Y, pos[p].y, p); + temp_positions.setValue(EMDL_IMAGE_COORD_Z, pos[p].z, p); } temp_positions.write(temp_filename_root + "_positions.star"); diff --git a/src/jaz/tomography/programs/align_mpi.cpp b/src/jaz/tomography/programs/align_mpi.cpp index 828d9edf6..b6b59588b 100644 --- a/src/jaz/tomography/programs/align_mpi.cpp +++ b/src/jaz/tomography/programs/align_mpi.cpp @@ -45,7 +45,7 @@ void AlignProgramMpi::run() initialise(); - AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getOriginalPixelSize(0)); + AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getTiltSeriesPixelSize(0)); if (verbosity > 0) { diff --git a/src/jaz/tomography/programs/ctf_refinement.cpp b/src/jaz/tomography/programs/ctf_refinement.cpp index 080fd095c..dccd1e976 100644 --- a/src/jaz/tomography/programs/ctf_refinement.cpp +++ b/src/jaz/tomography/programs/ctf_refinement.cpp @@ -46,7 +46,7 @@ void CtfRefinementProgram::run() initTempDirectories(); - AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getOriginalPixelSize(0)); + AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getTiltSeriesPixelSize(0)); Log::endSection(); diff --git a/src/jaz/tomography/programs/ctf_refinement_mpi.cpp b/src/jaz/tomography/programs/ctf_refinement_mpi.cpp index 3b9079ec2..1e8528e6c 100644 --- a/src/jaz/tomography/programs/ctf_refinement_mpi.cpp +++ b/src/jaz/tomography/programs/ctf_refinement_mpi.cpp @@ -41,7 +41,7 @@ void CtfRefinementProgramMpi::run() initTempDirectories(); } - AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getOriginalPixelSize(0)); + AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getTiltSeriesPixelSize(0)); if (verbosity > 0) { diff --git a/src/jaz/tomography/programs/local_particle_refine.cpp b/src/jaz/tomography/programs/local_particle_refine.cpp index a94ec61e3..0b269e44b 100644 --- a/src/jaz/tomography/programs/local_particle_refine.cpp +++ b/src/jaz/tomography/programs/local_particle_refine.cpp @@ -67,7 +67,7 @@ void LocalParticleRefineProgram::run() const int tc = particles.size(); - AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getOriginalPixelSize(0)); + AberrationsCache aberrationsCache(particleSet.optTable, boxSize, particleSet.getTiltSeriesPixelSize(0)); Log::endSection(); diff --git a/src/jaz/tomography/programs/reconstruct_particle.cpp b/src/jaz/tomography/programs/reconstruct_particle.cpp index 2534e0144..dd0b1d283 100644 --- a/src/jaz/tomography/programs/reconstruct_particle.cpp +++ b/src/jaz/tomography/programs/reconstruct_particle.cpp @@ -19,7 +19,6 @@ #include #include - using namespace gravis; @@ -62,6 +61,7 @@ void ReconstructParticleProgram::readBasicParameters(int argc, char *argv[]) cropSize = textToInteger(parser.getOption("--crop", "Size of (additionally output) cropped image", "-1")); do_whiten = parser.checkOption("--whiten", "Whiten the noise by flattening the power spectrum"); + do_ctf = !parser.checkOption("--no_ctf", "Do not apply CTFs"); binning = textToDouble(parser.getOption("--bin", "Binning factor", "1")); taper = textToDouble(parser.getOption("--taper", "Taper against the sphere by this number of pixels (only if cropping)", "10")); @@ -101,9 +101,12 @@ void ReconstructParticleProgram::run() Log::beginSection("Initialising"); TomogramSet tomoSet(optimisationSet.tomograms, true); - ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, true); + ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, true, &tomoSet); + + if (!particleSet.hasHalfSets()) + Log::warn("The input particles in "+optimisationSet.particles+ " have no rlnRandomSubset to specify halfsets, joining all particles in half1"); - std::vector> particles = particleSet.splitByTomogram(tomoSet, true); + std::vector> particles = particleSet.splitByTomogram(tomoSet, true); const int tc = particles.size(); const int s = boxSize; @@ -112,7 +115,6 @@ void ReconstructParticleProgram::run() const int s02D = (int)(binning * s + 0.5); const bool flip_value = true; - const bool do_ctf = true; Tomogram tomo0 = tomoSet.loadTomogram(0, false); const double binnedOutPixelSize = tomo0.optics.pixelSize * binning; @@ -168,7 +170,7 @@ void ReconstructParticleProgram::run() if (no_reconstruction) return; - finalise(dataImgFS, ctfImgFS, binnedOutPixelSize); + finalise(dataImgFS, ctfImgFS, particleSet, binnedOutPixelSize); // Delete temporary files // No error checking - do not bother the user if it fails @@ -197,7 +199,7 @@ void ReconstructParticleProgram::processTomograms( const int sh = s/2 + 1; const int tc = tomoIndices.size(); - if (verbosity > 0 && !per_tomogram_progress) + if (verbosity > 0 && !per_tomogram_progress) { int total_particles_on_first_thread = 0; @@ -352,13 +354,14 @@ void ReconstructParticleProgram::processTomograms( const d4Matrix particleToTomo = particleSet.getMatrix4x4(part_id, s,s,s); - const int halfSet = particleSet.getHalfSet(part_id); + const int halfSet = (particleSet.hasHalfSets()) ? particleSet.getHalfSet(part_id) : 0; const int og = particleSet.getOpticsGroup(part_id); const BufferedImage* gammaOffset = aberrationsCache.hasSymmetrical? &aberrationsCache.symmetrical[og] : 0; + const float sign = flip_value? -1.f : 1.f; for (int f = 0; f < fc; f++) { if (!isVisible[f]) continue; @@ -372,13 +375,12 @@ void ReconstructParticleProgram::processTomograms( BufferedImage ctfImg(sh,s); ctf.draw(s, s, binnedPixelSize, gammaOffset, &ctfImg(0,0,0)); - const float scale = flip_value? -1.f : 1.f; for (int y = 0; y < s; y++) { for (int x = 0; x < xRanges(y,f); x++) { - const float c = scale * ctfImg(x,y) * doseWeights(x,y,f); + const float c = sign * ctfImg(x,y) * doseWeights(x,y,f); particleStack[th](x,y,f) *= c; weightStack[th](x,y,f) = c * c; @@ -391,8 +393,13 @@ void ReconstructParticleProgram::processTomograms( } } } + + // If we're not doing CTF premultiplication, we may still want to invert the contrast + if (!do_ctf) particleStack[th] *= sign; + } + if (aberrationsCache.hasAntisymmetrical) { aberrationsCache.correctObservations(particleStack[th], og); @@ -436,8 +443,8 @@ void ReconstructParticleProgram::processTomograms( } //Save temporary files - - for (int half = 0; half < 2; half++) + int halfmax = particleSet.hasHalfSets() ? 2 : 1; + for (int half = 0; half < halfmax; half++) { BufferedImage tmpDataImg(sh, s, s*2); @@ -496,6 +503,7 @@ void ReconstructParticleProgram::processTomograms( void ReconstructParticleProgram::finalise( std::vector>& dataImgFS, std::vector>& ctfImgFS, + const ParticleSet& particleSet, const double binnedOutPixelSize) { const int s = dataImgFS[0].ydim; @@ -504,27 +512,41 @@ void ReconstructParticleProgram::finalise( std::vector> dataImgRS(2), dataImgDivRS(2); - BufferedImage dataImgFS_both = dataImgFS[0] + dataImgFS[1]; - BufferedImage ctfImgFS_both = ctfImgFS[0] + ctfImgFS[1]; + BufferedImage dataImgFS_both; + BufferedImage ctfImgFS_both; Log::beginSection("Reconstructing"); - for (int half = 0; half < 2; half++) - { - Log::print("Half " + ZIO::itoa(half)); + if (particleSet.hasHalfSets()) + { + dataImgFS_both = dataImgFS[0] + dataImgFS[1]; + ctfImgFS_both = ctfImgFS[0] + ctfImgFS[1]; - dataImgRS[half] = BufferedImage(s,s,s); - dataImgDivRS[half] = BufferedImage(s,s,s); + for (int half = 0; half < 2; half++) + { + Log::print("Half " + ZIO::itoa(half)); - reconstruct( - dataImgRS[half], dataImgDivRS[half], ctfImgFS[half], - dataImgFS[half]); + dataImgRS[half] = BufferedImage(s,s,s); + dataImgDivRS[half] = BufferedImage(s,s,s); - writeOutput( - dataImgDivRS[half], dataImgRS[half], ctfImgFS[half], - "half"+ZIO::itoa(half+1), binnedOutPixelSize); - } + reconstruct( + dataImgRS[half], dataImgDivRS[half], ctfImgFS[half], + dataImgFS[half]); + + writeOutput( + dataImgDivRS[half], dataImgRS[half], ctfImgFS[half], + "half"+ZIO::itoa(half+1), binnedOutPixelSize); + } + } + else + { + dataImgFS_both = dataImgFS[0]; + ctfImgFS_both = ctfImgFS[0]; + + dataImgRS[0] = BufferedImage(s,s,s); + dataImgDivRS[0] = BufferedImage(s,s,s); + } reconstruct( dataImgRS[0], dataImgDivRS[0], ctfImgFS_both, @@ -534,12 +556,7 @@ void ReconstructParticleProgram::finalise( dataImgDivRS[0], dataImgRS[0], ctfImgFS[0], "merged", binnedOutPixelSize); - optimisationSet.refMap1 = outDir + "half1.mrc"; - optimisationSet.refMap2 = outDir + "half2.mrc"; - optimisationSet.refFSC = ""; - optimisationSet.write(outDir + "optimisation_set.star"); - - Log::endSection(); + Log::endSection(); } void ReconstructParticleProgram::symmetrise( diff --git a/src/jaz/tomography/programs/reconstruct_particle.h b/src/jaz/tomography/programs/reconstruct_particle.h index 1029a132d..e03250318 100644 --- a/src/jaz/tomography/programs/reconstruct_particle.h +++ b/src/jaz/tomography/programs/reconstruct_particle.h @@ -25,7 +25,7 @@ class ReconstructParticleProgram bool do_whiten, no_reconstruction, only_do_unfinished, run_from_GUI, run_from_MPI, - no_backup, do_circle_crop; + no_backup, do_circle_crop, do_ctf; int boxSize, cropSize, num_threads, outer_threads, inner_threads, max_mem_GB; @@ -60,7 +60,8 @@ class ReconstructParticleProgram void finalise( std::vector>& dataImgFS, std::vector>& ctfImgFS, - const double binnedOutPixelSize); + const ParticleSet& dataSet, + const double binnedOutPixelSize); void symmetrise( std::vector>& dataImgFS, diff --git a/src/jaz/tomography/programs/reconstruct_particle_mpi.cpp b/src/jaz/tomography/programs/reconstruct_particle_mpi.cpp index f4c531ce3..f2810456a 100644 --- a/src/jaz/tomography/programs/reconstruct_particle_mpi.cpp +++ b/src/jaz/tomography/programs/reconstruct_particle_mpi.cpp @@ -66,7 +66,10 @@ void ReconstructParticleProgramMpi::run() } TomogramSet tomoSet(optimisationSet.tomograms, verb > 0); - ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, verb > 0); + ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, verb > 0, &tomoSet); + + if (verb > 0 && !particleSet.hasHalfSets()) + Log::warn("The input particles in "+optimisationSet.particles+ " have no rlnRandomSubset to specify halfsets, joining all particles in half1"); std::vector> particles = particleSet.splitByTomogram(tomoSet, verb > 0); @@ -153,7 +156,8 @@ void ReconstructParticleProgramMpi::run() } } - for (int i = 0; i < 2; i++) + int halfmax = (particleSet.hasHalfSets()) ? 2 : 1; + for (int i = 0; i < halfmax; i++) { if (node->isLeader()) { @@ -171,7 +175,7 @@ void ReconstructParticleProgramMpi::run() { if (no_reconstruction) return; - finalise(sumDataImgFS, sumCtfImgFS, binnedOutPixelSize); + finalise(sumDataImgFS, sumCtfImgFS, particleSet, binnedOutPixelSize); } // Delete temporary files (or try to; no error checking intentional) diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.cpp b/src/jaz/tomography/programs/reconstruct_tomogram.cpp index c8f5cec15..95d9a59bb 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.cpp +++ b/src/jaz/tomography/programs/reconstruct_tomogram.cpp @@ -3,12 +3,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include @@ -32,34 +32,45 @@ void TomoBackprojectProgram::readParameters(int argc, char *argv[]) int gen_section = parser.addSection("General options"); - tomoName = parser.getOption("--tn", "Tomogram name"); + tomoName = parser.getOption("--tn", "Tomogram name", "*"); + outFn = parser.getOption("--o", "Output filename (or output directory in case of reconstructing multiple tomograms)"); + do_even_odd_tomograms = parser.checkOption("--generate_split_tomograms", "Reconstruct tomograms from even/odd movie frames or tilt image index for denoising"); + + w = textToInteger(parser.getOption("--w", "Width")); + h = textToInteger(parser.getOption("--h", "Height" )); + d = textToInteger(parser.getOption("--d", "Thickness")); applyWeight = !parser.checkOption("--no_weight", "Do not perform weighting in Fourier space using a Wiener filter"); applyPreWeight = parser.checkOption("--pre_weight", "Pre-weight the 2D slices prior to backprojection"); - FourierCrop = parser.checkOption("--Fc", "Downsample the 2D images by Fourier cropping"); - - SNR = textToDouble(parser.getOption("--SNR", "SNR assumed by the Wiener filter", "10")); + FourierCrop = parser.checkOption("--Fc", "Downsample the 2D images by Fourier cropping"); + do_only_unfinished = parser.checkOption("--only_do_unfinished", "Only reconstruct those tomograms that haven't finished yet"); + SNR = textToDouble(parser.getOption("--SNR", "SNR assumed by the Wiener filter", "10")); applyCtf = !parser.checkOption("--noctf", "Ignore the CTF"); + doWiener = !parser.checkOption("--skip_wiener", "Do multiply images with CTF, but don't divide by CTF^2 in Wiener filter"); + + if (!doWiener) applyCtf = true; - zeroDC = !parser.checkOption("--keep_mean", "Do not zero the DC component of each frame"); + zeroDC = !parser.checkOption("--keep_mean", "Do not zero the DC component of each frame"); taperDist = textToDouble(parser.getOption("--td", "Tapering distance", "0.0")); taperFalloff = textToDouble(parser.getOption("--tf", "Tapering falloff", "0.0")); - x0 = textToDouble(parser.getOption("--x0", "X origin", "1.0")); - y0 = textToDouble(parser.getOption("--y0", "Y origin", "1.0")); - z0 = textToDouble(parser.getOption("--z0", "Z origin", "1.0")); + // SHWS & Aburt 19Jul2022: use zero-origins from relion-4.1 onwards.... + //x0 = textToDouble(parser.getOption("--x0", "X origin", "1.0")); + //y0 = textToDouble(parser.getOption("--y0", "Y origin", "1.0")); + //z0 = textToDouble(parser.getOption("--z0", "Z origin", "1.0")); - w = textToInteger(parser.getOption("--w", "Width", "-1.0")); - h = textToInteger(parser.getOption("--h", "Height", "-1.0")); - d = textToInteger(parser.getOption("--d", "Thickness", "-1.0")); + x0 = textToDouble(parser.getOption("--x0", "X origin", "0.0")); + y0 = textToDouble(parser.getOption("--y0", "Y origin", "0.0")); + z0 = textToDouble(parser.getOption("--z0", "Z origin", "0.0")); - spacing = textToDouble(parser.getOption("--bin", "Binning", "8.0")); + + spacing = textToDouble(parser.getOption("--bin", "Binning", "1.0")); + angpix_spacing = textToDouble(parser.getOption("--binned_angpix", "OR: desired pixel size after binning", "-1")); n_threads = textToInteger(parser.getOption("--j", "Number of threads", "1")); - outFn = parser.getOption("--o", "Output filename"); Log::readParams(parser); @@ -75,31 +86,222 @@ void TomoBackprojectProgram::readParameters(int argc, char *argv[]) ZIO::ensureParentDir(outFn); } +void TomoBackprojectProgram::initialise(bool verbose) +{ + if (!tomogramSet.read(optimisationSet.tomograms)) + REPORT_ERROR("ERROR: there was a problem reading the tomogram set"); + + tomoIndexTodo.clear(); + + if (tomoName == "*") + { + do_multiple = true; + + for (int idx = 0; idx < tomogramSet.size(); idx++) + { + if (do_only_unfinished && exists(getOutputFileName(idx,false,false))) + continue; + tomoIndexTodo.push_back(idx); + } + + if (outFn[outFn.size()-1] != '/') outFn += '/'; + FileName fn_dir = outFn + "tomograms/"; + + } + else + { + int myidx = tomogramSet.getTomogramIndex(tomoName); + if (myidx < 0) REPORT_ERROR("ERROR: cannot find specific tomogram name \"" + tomoName + "\" in the input star file"); + tomoIndexTodo.push_back(myidx); + do_multiple = false; + } + + if (verbose) + { + std::cout << " + Reconstructing " << tomoIndexTodo.size() << " tomograms: " << std::endl; + for (int idx = 0; idx < tomoIndexTodo.size(); idx++) + { + std::cout << " - " << tomogramSet.getTomogramName(tomoIndexTodo[idx]) << std::endl; + } + } + +} -void TomoBackprojectProgram::run() +void TomoBackprojectProgram::run(int rank, int size) { - TomogramSet tomogramSet(optimisationSet.tomograms); - const int tomoIndex = tomogramSet.getTomogramIndex(tomoName); - Tomogram tomogram = tomogramSet.loadTomogram(tomoIndex, true); + long my_first_idx, my_last_idx; + divide_equally(tomoIndexTodo.size(), size, rank , my_first_idx, my_last_idx); + + int barstep, nr_todo = my_last_idx-my_first_idx+1; + if (rank == 0) + { + std::cout << " + Reconstructing ... " << std::endl; + init_progress_bar(nr_todo); + barstep = XMIPP_MAX(1, nr_todo / 60); + } + for (long idx = my_first_idx; idx <= my_last_idx; idx++) + { + // Abort through the pipeline_control system + if (pipeline_control_check_abort_job()) + exit(RELION_EXIT_ABORTED); + + if (do_even_odd_tomograms) + { + reconstructOneTomogram(tomoIndexTodo[idx],true,false); // true/false indicates to reconstruct tomogram from even frames + reconstructOneTomogram(tomoIndexTodo[idx],false,true); // false/true indicates from odd frames + } + else + { + reconstructOneTomogram(tomoIndexTodo[idx],false,false); + } - const int w0 = tomogram.w0; - const int h0 = tomogram.h0; - const int d0 = tomogram.d0; + if (rank == 0 && idx % barstep == 0) + progress_bar(idx); + } + + if (rank == 0) progress_bar(nr_todo); + +} + + +void TomoBackprojectProgram::writeOutput(bool do_all_metadata) +{ + // If we were doing multiple tomograms, then also write the updated tomograms.star. + if (do_multiple) + { + // for MPI program + if (do_all_metadata) setMetaDataAllTomograms(); + + tomogramSet.write(outFn + "tomograms.star"); + + std::cout << " Written out: " << outFn << "tomograms.star" << std::endl; + + } + +} + + +void TomoBackprojectProgram::getProjectMatrices(Tomogram &tomogram, MetaDataTable &tomogramTable) +{ +/* From Alister Burt + * + * tilt_image_center = tilt_image_dimensions / 2 + * specimen_center = tomogram_dimensions / 2 + * + * # Transformations, defined in order of application + * s0 = S(-specimen_center) # put specimen center-of-rotation at the origin + * r0 = Rx(euler_angles['rlnTomoXTilt']) # rotate specimen around X-axis + * r1 = Ry(euler_angles['rlnTomoYTilt']) # rotate specimen around Y-axis + * r2 = Rz(euler_angles['rlnTomoZRot']) # rotate specimen around Z-axis + * s1 = S(specimen_shifts) # shift projected specimen in xy (camera) plane + * s2 = S(tilt_image_center) # move specimen back into tilt-image coordinate system + * + * # compose matrices + * transformations = s2 @ s1 @ r2 @ r1 @ r0 @ s0 + * + */ + + if (!(tomogramTable.containsLabel(EMDL_TOMO_XTILT) && + tomogramTable.containsLabel(EMDL_TOMO_YTILT) && + tomogramTable.containsLabel(EMDL_TOMO_ZROT) && + tomogramTable.containsLabel(EMDL_TOMO_XSHIFT_ANGST) && + tomogramTable.containsLabel(EMDL_TOMO_YSHIFT_ANGST))) + + REPORT_ERROR("ERROR: at least one of the input tilt series star file(s) does not contain projections matrices, NOR rlnTomoXTilt, rlnTomoYtilt, rlnTomoZRot, rlnTomoXShiftAngst or rlnTomoYShiftAng."); + + + double pixelSizeAct = tomogram.optics.pixelSize; + const int fc = tomogram.frameCount; + for (int f = 0; f < fc; f++) + { + d4Matrix s0, s1, s2, r0, r1, r2, out; + + // Get specimen center + t3Vector specimen_center((double)int(w/2), (double)int(h/2), (double)int(d/2) ); + s0 = s0. translation(-specimen_center); + + // Get specimen shifts (in pixels) + double xshift, yshift; + tomogramTable.getValueSafely(EMDL_TOMO_XSHIFT_ANGST, xshift, f); + tomogramTable.getValueSafely(EMDL_TOMO_YSHIFT_ANGST, yshift, f); + t3Vector specimen_shifts(xshift/pixelSizeAct, yshift/pixelSizeAct, 0.); + s1 = s1.translation(specimen_shifts); + + // Get tilt image center + std::vector tilt_image_center_int = tomogram.stack.getSizeVector(); + t3Vector tilt_image_center((double)int(tilt_image_center_int[0]/2), (double)int(tilt_image_center_int[1]/2), 0.); + s2 = s2.translation(tilt_image_center); + + // Get rotation matrices + t3Vector xaxis(1., 0., 0.), yaxis(0., 1., 0.), zaxis(0., 0., 1.); + double xtilt, ytilt, zrot; + tomogramTable.getValueSafely(EMDL_TOMO_XTILT, xtilt, f); + tomogramTable.getValueSafely(EMDL_TOMO_YTILT, ytilt, f); + tomogramTable.getValueSafely(EMDL_TOMO_ZROT, zrot, f); + + r0 = r0.rotation(xaxis, xtilt); + r1 = r1.rotation(yaxis, ytilt); + r2 = r2.rotation(zaxis, zrot); + tomogram.projectionMatrices[f] = s2 * s1 * r2 * r1 * r0 * s0; + + // Set the four rows of the projectionMatrix back into the metaDataTable + std::vector rows({ + EMDL_TOMO_PROJECTION_X, + EMDL_TOMO_PROJECTION_Y, + EMDL_TOMO_PROJECTION_Z, + EMDL_TOMO_PROJECTION_W }); + + for (int i = 0; i < 4; i++) + { + std::vector vals(4); + for (int j = 0; j < 4; j++) + { + vals[j] = tomogram.projectionMatrices[f](i,j); + } + tomogramTable.setValue(rows[i], vals, f); + } + + } +} + +void TomoBackprojectProgram::reconstructOneTomogram(int tomoIndex, bool doEven, bool doOdd) +{ + Tomogram tomogram; + + if (doEven) + { + tomogram = tomogramSet.loadTomogram(tomoIndex, true, true, false); + } + else if (doOdd) + { + tomogram = tomogramSet.loadTomogram(tomoIndex, true, false, true); + } + else + { + tomogram = tomogramSet.loadTomogram(tomoIndex, true); + } + if (zeroDC) Normalization::zeroDC_stack(tomogram.stack); const int fc = tomogram.frameCount; BufferedImage stackAct; std::vector projAct(fc); - double pixelSizeAct = tomogram.optics.pixelSize; - - - const int w1 = w > 0? w : w0 / spacing + 0.5; - const int h1 = h > 0? h : h0 / spacing + 0.5; - const int t1 = d > 0? d : d0 / spacing; - + double pixelSizeAct = tomogramSet.getTiltSeriesPixelSize(tomoIndex); + + if (angpix_spacing > 0.) + { + spacing = angpix_spacing / pixelSizeAct; + } + + if (!tomogram.hasMatrices) getProjectMatrices(tomogram, tomogramSet.tomogramTables[tomoIndex]); + + const int w1 = w / spacing + 0.5; + const int h1 = h / spacing + 0.5; + const int t1 = d / spacing; + if (std::abs(spacing - 1.0) < 1e-2) { projAct = tomogram.projectionMatrices; @@ -115,7 +317,7 @@ void TomoBackprojectProgram::run() if (std::abs(spacing - 1.0) > 1e-2) { - Log::print("Resampling image stack"); + if (!do_multiple) Log::print("Resampling image stack"); if (FourierCrop) { @@ -129,6 +331,8 @@ void TomoBackprojectProgram::run() } pixelSizeAct *= spacing; + + tomogramSet.globalTable.setValue(EMDL_TOMO_TOMOGRAM_BINNING, spacing, tomoIndex); } else { @@ -197,14 +401,14 @@ void TomoBackprojectProgram::run() stackAct = RealSpaceBackprojection::preWeight(stackAct, projAct, n_threads); } - Log::print("Backprojecting"); + if (!do_multiple) Log::print("Backprojecting"); RealSpaceBackprojection::backproject( stackAct, projAct, out, n_threads, orig, spacing, RealSpaceBackprojection::Linear, taperFalloff, taperDist); - if (applyWeight || applyCtf) + if ((applyWeight || applyCtf) && doWiener) { BufferedImage psf(w1, h1, t1); psf.fill(0.f); @@ -224,9 +428,93 @@ void TomoBackprojectProgram::run() Reconstruction::correct3D_RS(out, psf, out, 1.0 / SNR, n_threads); } - Log::print("Writing output"); - - const double samplingRate = tomogram.optics.pixelSize * spacing; + if (!do_multiple) Log::print("Writing output"); + + const double samplingRate = tomogramSet.getTiltSeriesPixelSize(tomoIndex) * spacing; + + if (doEven) + out.write(getOutputFileName(tomoIndex, true, false), samplingRate); + else if (doOdd) + out.write(getOutputFileName(tomoIndex, false, true), samplingRate); + else + out.write(getOutputFileName(tomoIndex, false, false), samplingRate); + + + // Also add the tomogram sizes and name to the tomogramSet + tomogramSet.globalTable.setValue(EMDL_TOMO_SIZE_X, w, tomoIndex); + tomogramSet.globalTable.setValue(EMDL_TOMO_SIZE_Y, h, tomoIndex); + tomogramSet.globalTable.setValue(EMDL_TOMO_SIZE_Z, d, tomoIndex); + + if (doEven) + tomogramSet.globalTable.setValue(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_HALF1_FILE_NAME, getOutputFileName(tomoIndex, true, false), tomoIndex); + else if (doOdd) + tomogramSet.globalTable.setValue(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_HALF2_FILE_NAME, getOutputFileName(tomoIndex, false, true), tomoIndex); + else + { + tomogramSet.globalTable.setValue(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME, getOutputFileName(tomoIndex, false, false), tomoIndex); + } +} + +void TomoBackprojectProgram::setMetaDataAllTomograms() +{ + + for (int tomoIndex = 0; tomoIndex < tomogramSet.size(); tomoIndex++) + { + + // SHWS 19apr2023: need to do this again for all tomograms: after completion of MPI job, leader does not know about the tomograms of the followers. + Tomogram tomogram; + tomogram = tomogramSet.loadTomogram(tomoIndex, false); + if (!tomogram.hasMatrices) getProjectMatrices(tomogram, tomogramSet.tomogramTables[tomoIndex]); + + double pixelSizeAct = tomogramSet.getTiltSeriesPixelSize(tomoIndex); + if (angpix_spacing > 0.) spacing = angpix_spacing / pixelSizeAct; + if (std::abs(spacing - 1.0) > 1e-2) + tomogramSet.globalTable.setValue(EMDL_TOMO_TOMOGRAM_BINNING, spacing, tomoIndex); + + // Also add the tomogram sizes and name to the tomogramSet + tomogramSet.globalTable.setValue(EMDL_TOMO_SIZE_X, w, tomoIndex); + tomogramSet.globalTable.setValue(EMDL_TOMO_SIZE_Y, h, tomoIndex); + tomogramSet.globalTable.setValue(EMDL_TOMO_SIZE_Z, d, tomoIndex); + + if (do_even_odd_tomograms) + { + tomogramSet.globalTable.setValue(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_HALF1_FILE_NAME, + getOutputFileName(tomoIndex, true, false), tomoIndex); + tomogramSet.globalTable.setValue(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_HALF2_FILE_NAME, + getOutputFileName(tomoIndex, false, true), tomoIndex); + } + else + { + tomogramSet.globalTable.setValue(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME, + getOutputFileName(tomoIndex, false, false), tomoIndex); + } + } + +} + +FileName TomoBackprojectProgram::getOutputFileName(int index, bool nameEven, bool nameOdd) +{ + // If we're reconstructing many tomograms, or the output filename is a directory: use standardized output filenames + FileName fn_result = outFn; + + if (do_even_odd_tomograms) + { + if (nameEven) + { + fn_result += "tomograms/rec_" + tomogramSet.getTomogramName(index)+"_half1.mrc"; + } + else if (nameOdd) + { + fn_result += "tomograms/rec_" + tomogramSet.getTomogramName(index)+"_half2.mrc"; + } + } + else + { + fn_result += "tomograms/rec_" + tomogramSet.getTomogramName(index)+".mrc"; + } + + if (!exists(fn_result.beforeLastOf("/"))) mktree(fn_result.beforeLastOf("/")); + + return fn_result; - out.write(outFn, samplingRate); } diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.h b/src/jaz/tomography/programs/reconstruct_tomogram.h index 6c31c6548..15e1d7923 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.h +++ b/src/jaz/tomography/programs/reconstruct_tomogram.h @@ -3,9 +3,12 @@ #include #include +#include +#include #include #include #include +#include class TomoBackprojectProgram { @@ -15,16 +18,26 @@ class TomoBackprojectProgram int n_threads; int w, h, d; - double spacing, x0, y0, z0, taperDist, taperFalloff; - std::string tomoName, outFn; - bool applyPreWeight, applyWeight, applyCtf, zeroDC, FourierCrop; + double spacing, angpix_spacing, x0, y0, z0, taperDist, taperFalloff; + FileName tomoName, outFn; + bool applyPreWeight, applyWeight, applyCtf, doWiener, zeroDC, FourierCrop; + bool do_multiple, do_only_unfinished; + bool do_even_odd_tomograms; double SNR; + std::vector tomoIndexTodo; OptimisationSet optimisationSet; - - + TomogramSet tomogramSet; + void readParameters(int argc, char *argv[]); - void run(); + void initialise(bool verbose = true); + void run(int rank = 0, int size = 1); + void writeOutput(bool do_all_metadata = false); + void getProjectMatrices(Tomogram &tomogram, MetaDataTable &tomogramTable); + void reconstructOneTomogram(int tomoIndex, bool doEven, bool doOdd); + void setMetaDataAllTomograms(); + private: + FileName getOutputFileName(int index, bool nameEven, bool nameOdd); }; #endif diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index de0108b16..ad494b1c5 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -47,8 +47,12 @@ void SubtomoProgram::readBasicParameters(IOParser& parser) boxSize = textToInteger(parser.getOption("--b", "Binned projection box size")); cropSize = textToInteger(parser.getOption("--crop", "Output box size", "-1")); binning = textToDouble(parser.getOption("--bin", "Binning factor", "1")); + do_stack2d = parser.checkOption("--stack2d", "Write out 2D stacks of cropped images for each particle, instead of pseudo-subtomograms"); + rescale_coords = textToDouble(parser.getOption("--rescale_coords", "Rescale input particles by this factor", "1.")); write_multiplicity = parser.checkOption("--multi", "Write out multiplicity volumes"); SNR = textToDouble(parser.getOption("--SNR", "Assumed signal-to-noise ratio (negative means use a heuristic)", "-1")); + min_frames = textToInteger(parser.getOption("--min_frames", "Minimum number of lowest-dose tilt series frames that needs to be inside the box (default is half the number of frames)", "-1")); + maxDose = textToDouble(parser.getOption("--max_dose", "Only include tilt series frames with a dose (in e-/A2) that is lower or equal to this number (default is no selection)", "-1")); do_cone_weight = parser.checkOption("--cone_weight", "Weight down a double cone along Z"); const double alpha = 0.5 * textToDouble(parser.getOption("--cone_angle", "Opening angle of the cone in degrees", "10")); @@ -67,13 +71,14 @@ void SubtomoProgram::readBasicParameters(IOParser& parser) do_center = !parser.checkOption("--no_center", "Do not subtract the mean from the voxel values"); flip_value = !parser.checkOption("--no_ic", "Do not invert contrast (keep particles dark)"); + do_ctf = !parser.checkOption("--no_ctf", "Do not apply CTFs"); write_combined = !parser.checkOption("--no_comb", "Do not write the concatenated CTF-multiplicity image"); write_ctf = parser.checkOption("--ctf", "Write 3D CTFs"); write_divided = parser.checkOption("--div", "Write CTF-corrected subtomograms"); write_normalised = parser.checkOption("--nrm", "Write multiplicity-normalised subtomograms"); apply_offsets = !parser.checkOption("--dont_apply_offsets", "By default, rlnOriginX/Y/ZAngst are combined with rlnCoordinateX/Y/Z to construct the particles in their refined translations. Use this argument to skip that."); - apply_orientations = parser.checkOption("--apply_orientations", "rlnAngle are combined with rlnTomoSubtomogram to construct the particles in their refined orientations. This will also apply translations!"); + apply_orientations = parser.checkOption("--apply_orientations", "rlnAngle are combined with rlnTomoSubtomogram to construct the particles in their refined orientations. This will also apply translations; No longer recommended in relion5!"); if (apply_orientations) apply_offsets = true; only_do_unfinished = parser.checkOption("--only_do_unfinished", "Only process undone subtomograms"); @@ -122,13 +127,12 @@ void SubtomoProgram::run() { TomogramSet tomogramSet(optimisationSet.tomograms, true); - ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, true); + // TODO: Introduce optics parameters from tomogramSet if they are not present yet in particleSet + ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, true, &tomogramSet); std::vector > particles = particleSet.splitByTomogram(tomogramSet, true); if (cropSize < 0) cropSize = boxSize; - bool do_ctf = true; - const long int s2D = boxSize; const long int s3D = cropSize; @@ -137,12 +141,14 @@ void SubtomoProgram::run() const long int s02D = (int)(binning * s2D + 0.5); const double relative_box_scale = cropSize / (double) boxSize; - const double binned_pixel_size = binning * particleSet.getOriginalPixelSize(0); + const double binned_pixel_size = binning * particleSet.getTiltSeriesPixelSize(0); initialise(particleSet, particles, tomogramSet); BufferedImage sum_data, sum_weights; + if (do_stack2d) do_sum_all = false; + if (do_sum_all) { sum_data.resize(s3D,s3D,s3D); @@ -167,7 +173,6 @@ void SubtomoProgram::run() s2D, s3D, relative_box_scale, - do_ctf, 1, sum_data, sum_weights); @@ -175,7 +180,7 @@ void SubtomoProgram::run() if (do_sum_all) { - const double pixel_size = binning * tomogramSet.getPixelSize(0); + const double pixel_size = binning * tomogramSet.getTiltSeriesPixelSize(0); sum_data.write(outDir + "sum_data.mrc", pixel_size); Centering::fftwHalfToHumanFull(sum_weights).write(outDir + "sum_weight.mrc", pixel_size); @@ -201,9 +206,10 @@ void SubtomoProgram::run() } void SubtomoProgram::initialise( - const ParticleSet& particleSet, + ParticleSet& particleSet, const std::vector>& particles, - const TomogramSet& tomogramSet) + const TomogramSet& tomogramSet, + bool verbose) { const int tc = tomogramSet.size(); @@ -222,14 +228,17 @@ void SubtomoProgram::initialise( directoriesPerTomogram = firstName.find_first_of('/') == std::string::npos; - if (directoriesPerTomogram) - { - Log::print("No slashes found in first particle name: creating subdirectories for each tomogram"); - } - else - { - Log::print("Slash found in first particle name: not creating subdirectories for each tomogram"); - } + if (verbose) + { + if (directoriesPerTomogram) + { + Log::print("No slashes found in first particle name: creating subdirectories for each tomogram"); + } + else + { + Log::print("Slash found in first particle name: not creating subdirectories for each tomogram"); + } + } for (int t = 0; t < tc; t++) { @@ -240,7 +249,30 @@ void SubtomoProgram::initialise( } } - writeParticleSet(particleSet, particles, tomogramSet); + if (std::abs(rescale_coords - 1.0) > 1e-2) + { + if (verbose) Log::print("Rescaling input coordinates ... "); + + for (int t = 0; t < tc; t++) + { + const int pc = particles[t].size(); + + if (pc == 0) continue; + + for (int p = 0; p < pc; p++) + { + const ParticleIndex part_id = particles[t][p]; + + d3Vector pos = particleSet.getParticleCoord(part_id); + + pos *= rescale_coords; + + particleSet.setParticleCoord(part_id, pos); + } + } + } + + if (verbose) writeParticleSet(particleSet, particles, tomogramSet); } std::string SubtomoProgram::getOutputFilename( @@ -270,6 +302,8 @@ void SubtomoProgram::writeParticleSet( ParticleSet copy = particleSet; copy.clearParticles(); + copy.is_stack2d = do_stack2d; + copy.max_dose = maxDose; int particles_removed = 0; @@ -283,30 +317,31 @@ void SubtomoProgram::writeParticleSet( { const ParticleIndex part_id = particles[t][p]; - Tomogram tomogram = tomogramSet.loadTomogram(t, false); + Tomogram tomogram = tomogramSet.loadTomogram(t, false, false, false, maxDose); const std::vector traj = particleSet.getTrajectoryInPixels( part_id, tomogram.frameCount, tomogram.optics.pixelSize, !apply_offsets); - if (tomogram.isVisibleAtAll(traj, boxSize / 2.0)) + int my_min_frames = (min_frames < 0 ) ? tomogram.frameCount / 2 : min_frames; + if (tomogram.isVisibleFirstFrames(traj, boxSize / 2.0, my_min_frames)) { const ParticleIndex new_id = copy.addParticle(particleSet, part_id); const int opticsGroup = particleSet.getOpticsGroup(part_id); - const double originalPixelSize = particleSet.getOriginalPixelSize(opticsGroup); + const double tiltSeriesPixelSize = particleSet.getTiltSeriesPixelSize(opticsGroup); const std::string filenameRoot = getOutputFilename( part_id, t, particleSet, tomogramSet); - std::string outData = filenameRoot + "_data.mrc"; - std::string outWeight = filenameRoot + "_weights.mrc"; + std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; + std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; copy.setImageFileNames(outData, outWeight, new_id); if (apply_offsets) { const d3Matrix A_subtomogram = particleSet.getSubtomogramMatrix(part_id); - const d3Vector pos = particleSet.getParticleCoord(part_id) - (A_subtomogram * particleSet.getParticleOffset(part_id)) / originalPixelSize; + const d3Vector pos = particleSet.getParticleCoord(part_id) - (A_subtomogram * particleSet.getParticleOffset(part_id)) / tiltSeriesPixelSize; copy.setParticleOffset(new_id, d3Vector(0,0,0)); copy.setParticleCoord(new_id, pos); } @@ -346,8 +381,10 @@ void SubtomoProgram::writeParticleSet( const double ps_img = copy.optTable.getDouble(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, og); const double ps_out = binning * ps_img; - copy.optTable.setValue(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED, true, og); - copy.optTable.setValue(EMDL_IMAGE_DIMENSIONALITY, 3, og); + bool is_premultiplied = (do_stack2d) ? do_ctf : true; + copy.optTable.setValue(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED, is_premultiplied, og); + int datadim = (do_stack2d) ? 2 : 3; + copy.optTable.setValue(EMDL_IMAGE_DIMENSIONALITY, datadim, og); copy.optTable.setValue(EMDL_TOMO_SUBTOMOGRAM_BINNING, binning, og); copy.optTable.setValue(EMDL_IMAGE_PIXEL_SIZE, ps_out, og); copy.optTable.setValue(EMDL_IMAGE_SIZE, cropSize, og); @@ -375,7 +412,6 @@ void SubtomoProgram::processTomograms( long int s2D, long int s3D, double relative_box_scale, - bool do_ctf, int verbosity, BufferedImage& sum_data, BufferedImage& sum_weights ) @@ -410,7 +446,7 @@ void SubtomoProgram::processTomograms( Log::print("Loading"); } - Tomogram tomogram = tomogramSet.loadTomogram(t, true); + Tomogram tomogram = tomogramSet.loadTomogram(t, true, false, false, maxDose); tomogram.validateParticleOptics(particles[t], particleSet); const int fc = tomogram.frameCount; @@ -436,8 +472,8 @@ void SubtomoProgram::processTomograms( if (verbosity > 0) { - Log::beginProgress( - "Backprojecting subtomograms", + Log::beginProgress( + "Extracting particles", (int)ceil(pc/(double)outer_thread_num)); } @@ -445,271 +481,271 @@ void SubtomoProgram::processTomograms( if (do_sum_all) omp_init_lock(&writelock); #pragma omp parallel for num_threads(outer_thread_num) - for (int p = 0; p < pc; p++) - { - const int th = omp_get_thread_num(); + for (int p = 0; p < pc; p++) { + const int th = omp_get_thread_num(); - if (verbosity > 0 && th == 0) - { - Log::updateProgress(p); - } + if (verbosity > 0 && th == 0) { + Log::updateProgress(p); + } - const ParticleIndex part_id = particles[t][p]; + const ParticleIndex part_id = particles[t][p]; - const std::string filenameRoot = getOutputFilename( - part_id, t, particleSet, tomogramSet); + const std::string filenameRoot = getOutputFilename( + part_id, t, particleSet, tomogramSet); - std::string outData = filenameRoot + "_data.mrc"; - std::string outWeight = filenameRoot + "_weights.mrc"; - std::string outCTF = filenameRoot + "_CTF2.mrc"; - std::string outDiv = filenameRoot + "_div.mrc"; - std::string outMulti = filenameRoot + "_multi.mrc"; - std::string outNrm = filenameRoot + "_data_nrm.mrc"; - std::string outWeightNrm = filenameRoot + "_CTF2_nrm.mrc"; + std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; + std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; + std::string outCTF = filenameRoot + "_CTF2.mrc"; + std::string outDiv = filenameRoot + "_div.mrc"; + std::string outMulti = filenameRoot + "_multi.mrc"; + std::string outNrm = filenameRoot + "_data_nrm.mrc"; + std::string outWeightNrm = filenameRoot + "_CTF2_nrm.mrc"; - if (only_do_unfinished && ZIO::fileExists(outData)) - { - continue; - } - - const std::vector traj = particleSet.getTrajectoryInPixels( - part_id, fc, tomogram.optics.pixelSize, !apply_offsets); + if (only_do_unfinished && ZIO::fileExists(outData)) { + continue; + } - if (!tomogram.isVisibleAtAll(traj, s2D / 2.0)) - { - continue; - } + const std::vector traj = particleSet.getTrajectoryInPixels( + part_id, fc, tomogram.optics.pixelSize, !apply_offsets); - const std::vector isVisible = tomogram.determineVisiblity(traj, s2D / 2.0); + int my_min_frames = (min_frames < 0 ) ? tomogram.nr_selected_frames / 2 : min_frames; + if (!tomogram.isVisibleFirstFrames(traj, s2D / 2.0, my_min_frames)) { + continue; + } - std::vector projCut(fc), projPart(fc); + const std::vector isVisible = tomogram.determineVisiblity(traj, s2D / 2.0); - BufferedImage particleStack = BufferedImage(sh2D,s2D,fc); - BufferedImage weightStack(sh2D,s2D,fc); + std::vector projCut(tomogram.nr_selected_frames), projPart(tomogram.nr_selected_frames); - TomoExtraction::extractAt3D_Fourier( - tomogram.stack, s02D, binning, tomogram, traj, isVisible, - particleStack, projCut, inner_thread_num, do_circle_precrop); + BufferedImage particleStack = BufferedImage(sh2D, s2D, tomogram.nr_selected_frames); + BufferedImage weightStack(sh2D, s2D, tomogram.nr_selected_frames); - if (!do_ctf) weightStack.fill(1.f); + TomoExtraction::extractAt3D_Fourier( + tomogram.stack, s02D, binning, tomogram, traj, isVisible, + particleStack, projCut, inner_thread_num, do_circle_precrop); + if (!do_ctf) weightStack.fill(1.f); - const int og = particleSet.getOpticsGroup(part_id); + const int og = particleSet.getOpticsGroup(part_id); - const BufferedImage* gammaOffset = - aberrationsCache.hasSymmetrical? &aberrationsCache.symmetrical[og] : 0; + const BufferedImage *gammaOffset = + aberrationsCache.hasSymmetrical ? &aberrationsCache.symmetrical[og] : 0; - for (int f = 0; f < fc; f++) - { - if (!isVisible[f]) continue; + const float sign = flip_value ? -1.f : 1.f; + for (int f = 0; f < fc; f++) + { + int fp = tomogram.selectedFrameIndex[f]; + if (fp >= 0 && isVisible[f]) + { - d3Matrix A; + d3Matrix A; - if (apply_orientations) - { - A = particleSet.getMatrix3x3(part_id); - } - else - { - A = particleSet.getSubtomogramMatrix(part_id); - } + if (apply_orientations) { + A = particleSet.getMatrix3x3(part_id); + } else { + A = particleSet.getSubtomogramMatrix(part_id); + } - projPart[f] = projCut[f] * d4Matrix(A); + projPart[fp] = projCut[fp] * d4Matrix(A); - if (do_ctf) - { - const d3Vector pos = (apply_offsets) ? particleSet.getPosition(part_id) : particleSet.getParticleCoord(part_id); + if (do_ctf) { + const d3Vector pos = (apply_offsets) ? particleSet.getPosition(part_id) + : particleSet.getParticleCoord(part_id); - CTF ctf = tomogram.getCtf(f, pos); - BufferedImage ctfImg(sh2D, s2D); - ctf.draw(s2D, s2D, binnedPixelSize, gammaOffset, &ctfImg(0,0,0)); + CTF ctf = tomogram.getCtf(f, pos); + BufferedImage ctfImg(sh2D, s2D); + ctf.draw(s2D, s2D, binnedPixelSize, gammaOffset, &ctfImg(0, 0, 0)); - const float sign = flip_value? -1.f : 1.f; + for (int y = 0; y < s2D; y++) { + for (int x = 0; x < xRanges(y, f); x++) { + const double c = ctfImg(x, y) * doseWeights(x, y, f); - for (int y = 0; y < s2D; y++) - for (int x = 0; x < xRanges(y,f); x++) - { - const double c = ctfImg(x,y) * doseWeights(x,y,f); + particleStack(x, y, fp) *= sign * c; + weightStack(x, y, fp) = c * c; + } + // SHWS 23nov22: for writing out 2D stacks: need to set everything outside the xRanges to zero! + for (int x = xRanges(y, f); x < sh2D; x++) { + particleStack(x, y, fp) = 0.; + weightStack(x, y, fp) = 0.; + } + } + } // end if do_ctf + } // end if tomogram.hasImagePerFrame[f] + } // end for f - particleStack(x,y,f) *= sign * c; - weightStack(x,y,f) = c * c; - } - } - } + // If we're not doing CTF premultiplication, we may still want to invert the contrast + if (!do_ctf) particleStack *= sign; - aberrationsCache.correctObservations(particleStack, og); + aberrationsCache.correctObservations(particleStack, og); - if (do_whiten) - { - particleStack *= noiseWeights; - weightStack *= noiseWeights; - } + if (do_whiten) { + particleStack *= noiseWeights; + weightStack *= noiseWeights; + } - const int boundary = (boxSize - cropSize) / 2; + const int boundary = (boxSize - cropSize) / 2; - if (do_gridding_precorrection || do_circle_crop) - { - BufferedImage particlesRS; + BufferedImage particlesRS; + if (do_gridding_precorrection || do_circle_crop || do_stack2d) { - particlesRS = NewStackHelper::inverseFourierTransformStack(particleStack); + particlesRS = NewStackHelper::inverseFourierTransformStack(particleStack); - if (do_circle_crop) - { - const double crop_boundary = do_narrow_circle_crop? boundary : 0.0; - TomoExtraction::cropCircle(particlesRS, crop_boundary, 5, num_threads); - } + } - if (do_gridding_precorrection) - { - TomoExtraction::griddingPreCorrect(particlesRS, boundary, num_threads); - } + if (do_circle_crop) { + const double crop_boundary = do_narrow_circle_crop ? boundary : 0.0; + TomoExtraction::cropCircle(particlesRS, crop_boundary, 5, num_threads); + } - particleStack = NewStackHelper::FourierTransformStack(particlesRS); - } + if (do_gridding_precorrection) { + TomoExtraction::griddingPreCorrect(particlesRS, boundary, num_threads); + } - BufferedImage dataImgFS(sh3D,s3D,s3D); - dataImgFS.fill(fComplex(0.0, 0.0)); + if (do_stack2d) { - BufferedImage ctfImgFS(sh3D,s3D,s3D), - dataImgRS(s3D,s3D,s3D), dataImgDivRS(s3D,s3D,s3D), - multiImageFS(sh3D,s3D,s3D); + BufferedImage cropParticlesRS = Padding::unpadCenter2D_full(particlesRS, boundary); + cropParticlesRS.write(outData, binnedPixelSize, write_float16); - ctfImgFS.fill(0.0); - dataImgRS.fill(0.0); - dataImgDivRS.fill(0.0); + } else { - for (int f = 0; f < fc; f++) - { - if (isVisible[f]) - { - FourierBackprojection::backprojectSlice_forward_with_multiplicity( - &xRanges(0,f), - particleStack.getSliceRef(f), - weightStack.getSliceRef(f), - projPart[f] * relative_box_scale, - dataImgFS, - ctfImgFS, - multiImageFS); - } - } + if (do_gridding_precorrection || do_circle_crop) + { + particleStack = NewStackHelper::FourierTransformStack(particlesRS); + } - Centering::shiftInSitu(dataImgFS); + // Write 3D subtomograms + BufferedImage dataImgFS(sh3D, s3D, s3D); + dataImgFS.fill(fComplex(0.0, 0.0)); - // correct FT scale after the implicit cropping: + BufferedImage ctfImgFS(sh3D, s3D, s3D), + dataImgRS(s3D, s3D, s3D), dataImgDivRS(s3D, s3D, s3D), + multiImageFS(sh3D, s3D, s3D); - if (s3D != s2D) - { - dataImgFS *= (float) sqrt(s2D / (double) s3D); - } + ctfImgFS.fill(0.0); + dataImgRS.fill(0.0); + dataImgDivRS.fill(0.0); - FFT::inverseFourierTransform(dataImgFS, dataImgRS, FFT::Both); + for (int f = 0; f < fc; f++) + { + int fp = tomogram.selectedFrameIndex[f]; + if (fp >= 0 && isVisible[f]) + { + FourierBackprojection::backprojectSlice_forward_with_multiplicity( + &xRanges(0, f), + particleStack.getSliceRef(fp), + weightStack.getSliceRef(fp), + projPart[fp] * relative_box_scale, + dataImgFS, + ctfImgFS, + multiImageFS); + } + } - if (do_cone_weight) - { - FFT::FourierTransform(dataImgRS, dataImgFS); + Centering::shiftInSitu(dataImgFS); - d3Matrix R = particleSet.getMatrix3x3(part_id); + // correct FT scale after the implicit cropping: + if (s3D != s2D) { + dataImgFS *= (float) sqrt(s2D / (double) s3D); + } - for (int z = 0; z < s3D; z++) - for (int y = 0; y < s3D; y++) - for (int x = 0; x < sh3D; x++) - { - const d3Vector p0( - x, - y < s3D/2? y : y - s3D, - z < s3D/2? z : z - s3D); + FFT::inverseFourierTransform(dataImgFS, dataImgRS, FFT::Both); - const d3Vector p = R * p0; + if (do_cone_weight) { + FFT::FourierTransform(dataImgRS, dataImgFS); - const double rho = sqrt(p.x*p.x + p.y*p.y); - const double t = rho / (std::abs(p.z) * cone_slope + cone_sig0); + d3Matrix R = particleSet.getMatrix3x3(part_id); - const double m = 1.0 - exp(-0.5*t*t); + for (int z = 0; z < s3D; z++) + for (int y = 0; y < s3D; y++) + for (int x = 0; x < sh3D; x++) { + const d3Vector p0( + x, + y < s3D / 2 ? y : y - s3D, + z < s3D / 2 ? z : z - s3D); - dataImgFS(x,y,z) *= m; - ctfImgFS(x,y,z) *= m; - multiImageFS(x,y,z) *= m; // apply to both multiplicity and weight? - } + const d3Vector p = R * p0; - FFT::inverseFourierTransform(dataImgFS, dataImgRS); - } + const double rho = sqrt(p.x * p.x + p.y * p.y); + const double t = rho / (std::abs(p.z) * cone_slope + cone_sig0); - // What if we didn't? The 2D image is already tapered. - //Reconstruction::taper(dataImgRS, taper, do_center, inner_thread_num); + const double m = 1.0 - exp(-0.5 * t * t); - if (do_sum_all) - { - omp_set_lock(&writelock); + dataImgFS(x, y, z) *= m; + ctfImgFS(x, y, z) *= m; + multiImageFS(x, y, z) *= m; // apply to both multiplicity and weight? + } - sum_data += dataImgRS; - sum_weights += ctfImgFS; + FFT::inverseFourierTransform(dataImgFS, dataImgRS); + } - omp_unset_lock(&writelock); - } + // What if we didn't? The 2D image is already tapered. + //Reconstruction::taper(dataImgRS, taper, do_center, inner_thread_num); - if (do_not_write_any) continue; + if (do_sum_all) { + omp_set_lock(&writelock); + sum_data += dataImgRS; + sum_weights += ctfImgFS; - dataImgRS.write(outData, binnedPixelSize, write_float16); + omp_unset_lock(&writelock); + } - if (write_combined) - { - BufferedImage ctfAndMultiplicity(sh3D,s3D,2*s3D); - ctfAndMultiplicity.getSlabRef(0,s3D).copyFrom(ctfImgFS); - ctfAndMultiplicity.getSlabRef(s3D,s3D).copyFrom(multiImageFS); + if (do_not_write_any) continue; - ctfAndMultiplicity.write(outWeight, 1.0 / binnedPixelSize, write_float16); - } - if (write_ctf) - { - Centering::fftwHalfToHumanFull(ctfImgFS).write(outCTF, 1.0 / binnedPixelSize, write_float16); - } + dataImgRS.write(outData, binnedPixelSize, write_float16); - if (write_multiplicity) - { - Centering::fftwHalfToHumanFull(multiImageFS).write(outMulti, 1.0 / binnedPixelSize, write_float16); - } + if (write_combined) { + BufferedImage ctfAndMultiplicity(sh3D, s3D, 2 * s3D); + ctfAndMultiplicity.getSlabRef(0, s3D).copyFrom(ctfImgFS); + ctfAndMultiplicity.getSlabRef(s3D, s3D).copyFrom(multiImageFS); - if (write_normalised) - { - BufferedImage ctfImgFSnrm = ctfImgFS; - BufferedImage dataImgCorrFS; + ctfAndMultiplicity.write(outWeight, 1.0 / binnedPixelSize, write_float16); + } - FFT::FourierTransform(dataImgRS, dataImgCorrFS, FFT::Both); + if (write_ctf) { + Centering::fftwHalfToHumanFull(ctfImgFS).write(outCTF, 1.0 / binnedPixelSize, write_float16); + } - for (long int i = 0; i < ctfImgFSnrm.getSize(); i++) - { - const float n = multiImageFS[i]; - ctfImgFSnrm[i] = n > 0.f? ctfImgFS[i] / n : 0.f; - dataImgCorrFS[i] = n > 0.f? dataImgCorrFS[i] / n : fComplex(0.f,0.f); - } + if (write_multiplicity) { + Centering::fftwHalfToHumanFull(multiImageFS).write(outMulti, 1.0 / binnedPixelSize, write_float16); + } - FFT::inverseFourierTransform(dataImgCorrFS, dataImgDivRS, FFT::Both); + if (write_normalised) { + BufferedImage ctfImgFSnrm = ctfImgFS; + BufferedImage dataImgCorrFS; - dataImgDivRS.write(outNrm, binnedPixelSize, write_float16); - Centering::fftwHalfToHumanFull(ctfImgFSnrm).write(outWeightNrm, 1.0 / binnedPixelSize, write_float16); - } + FFT::FourierTransform(dataImgRS, dataImgCorrFS, FFT::Both); - if (write_divided) - { - if (SNR > 0.0) - { - Reconstruction::ctfCorrect3D_Wiener( - dataImgRS, ctfImgFS, dataImgDivRS, - 1.0 / SNR, inner_thread_num); - } - else - { - Reconstruction::ctfCorrect3D_heuristic( - dataImgRS, ctfImgFS, dataImgDivRS, - 0.001, inner_thread_num); - } + for (long int i = 0; i < ctfImgFSnrm.getSize(); i++) { + const float n = multiImageFS[i]; + ctfImgFSnrm[i] = n > 0.f ? ctfImgFS[i] / n : 0.f; + dataImgCorrFS[i] = n > 0.f ? dataImgCorrFS[i] / n : fComplex(0.f, 0.f); + } - Reconstruction::taper(dataImgDivRS, taper, do_center, inner_thread_num); - dataImgDivRS.write(outDiv, binnedPixelSize, write_float16); - } - } + FFT::inverseFourierTransform(dataImgCorrFS, dataImgDivRS, FFT::Both); + + dataImgDivRS.write(outNrm, binnedPixelSize, write_float16); + Centering::fftwHalfToHumanFull(ctfImgFSnrm).write(outWeightNrm, 1.0 / binnedPixelSize, + write_float16); + } + + if (write_divided) { + if (SNR > 0.0) { + Reconstruction::ctfCorrect3D_Wiener( + dataImgRS, ctfImgFS, dataImgDivRS, + 1.0 / SNR, inner_thread_num); + } else { + Reconstruction::ctfCorrect3D_heuristic( + dataImgRS, ctfImgFS, dataImgDivRS, + 0.001, inner_thread_num); + } + + Reconstruction::taper(dataImgDivRS, taper, do_center, inner_thread_num); + dataImgDivRS.write(outDiv, binnedPixelSize, write_float16); + } + } + } // end if do_stack2d if (verbosity > 0) { @@ -728,8 +764,8 @@ BufferedImage SubtomoProgram::cropAndTaper(const BufferedImage& im ctfImgRS = Centering::fftwFullToHumanFull(ctfImgRS); ctfImgRS = Padding::unpadCenter3D_full(ctfImgRS, boundary); - - Reconstruction::GaussEnvelope(ctfImgRS, env_sigma, do_center, num_threads); + + Reconstruction::GaussEnvelope(ctfImgRS, env_sigma, do_center, num_threads); Reconstruction::taper(ctfImgRS, taper, do_center, num_threads); BufferedImage ctfImgRS_cent = Centering::humanFullToFftwFull(ctfImgRS); diff --git a/src/jaz/tomography/programs/subtomo.h b/src/jaz/tomography/programs/subtomo.h index 334325117..5f01ea46f 100644 --- a/src/jaz/tomography/programs/subtomo.h +++ b/src/jaz/tomography/programs/subtomo.h @@ -27,21 +27,26 @@ class SubtomoProgram int boxSize, - cropSize, - num_threads; + cropSize, + min_frames, + num_threads; double SNR, - binning, + binning, + rescale_coords, taper, env_sigma, cone_slope, cone_sig0, - freqCutoffFract; + freqCutoffFract, + maxDose; bool flip_value, - diag, + diag, + do_ctf, + do_stack2d, do_whiten, do_center, do_rotate, @@ -72,9 +77,10 @@ class SubtomoProgram protected: void initialise( - const ParticleSet& particleSet, + ParticleSet& particleSet, const std::vector>& particles, - const TomogramSet& tomogramSet); + const TomogramSet& tomogramSet, + bool verbose = true); void processTomograms( const std::vector& tomoIndices, @@ -86,7 +92,6 @@ class SubtomoProgram long int s2D, long int s3D, double relative_box_scale, - bool do_ctf, int verbose, BufferedImage& sum_data, BufferedImage& sum_weights ); diff --git a/src/jaz/tomography/programs/subtomo_mpi.cpp b/src/jaz/tomography/programs/subtomo_mpi.cpp index 2a03e1af8..a138082d8 100644 --- a/src/jaz/tomography/programs/subtomo_mpi.cpp +++ b/src/jaz/tomography/programs/subtomo_mpi.cpp @@ -70,16 +70,13 @@ void SubtomoProgramMpi::readParameters(int argc, char *argv[]) void SubtomoProgramMpi::run() { - TomogramSet tomogramSet(optimisationSet.tomograms, verb > 0); + TomogramSet tomogramSet(optimisationSet.tomograms, node->isLeader()); - ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, verb > 0); - std::vector > particles = particleSet.splitByTomogram(tomogramSet, verb > 0); + ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, node->isLeader(), &tomogramSet); + std::vector > particles = particleSet.splitByTomogram(tomogramSet, node->isLeader()); if (cropSize < 0) cropSize = boxSize; - bool do_ctf = true; - - const long int tc = particles.size(); const long int s2D = boxSize; const long int s3D = cropSize; @@ -88,12 +85,9 @@ void SubtomoProgramMpi::run() const long int s02D = (int)(binning * s2D + 0.5); const double relative_box_scale = cropSize / (double) boxSize; - const double binned_pixel_size = binning * particleSet.getOriginalPixelSize(0); + const double binned_pixel_size = binning * particleSet.getTiltSeriesPixelSize(0); - if (node->isLeader()) - { - initialise(particleSet, particles, tomogramSet); - } + initialise(particleSet, particles, tomogramSet, node->isLeader()); MPI_Barrier(MPI_COMM_WORLD); @@ -115,7 +109,6 @@ void SubtomoProgramMpi::run() s2D, s3D, relative_box_scale, - do_ctf, verb, dummy, dummy); diff --git a/src/jaz/tomography/tilt_geometry.cpp b/src/jaz/tomography/tilt_geometry.cpp index 8853b3d4f..ec62dafbc 100644 --- a/src/jaz/tomography/tilt_geometry.cpp +++ b/src/jaz/tomography/tilt_geometry.cpp @@ -118,7 +118,7 @@ std::pair TiltGeometry::findPresentWedge( d3Vector tilt_axis(Q[0][d_min], Q[1][d_min], Q[2][d_min]); double min_cross = std::numeric_limits::max(); - double max_cross = -std::numeric_limits::max(); + double max_cross = std::numeric_limits::lowest(); int max_cross_f = fc/2; int min_cross_f = fc/2; diff --git a/src/jaz/tomography/tomogram.cpp b/src/jaz/tomography/tomogram.cpp index 9720cdc0a..55249ef8a 100644 --- a/src/jaz/tomography/tomogram.cpp +++ b/src/jaz/tomography/tomogram.cpp @@ -76,6 +76,25 @@ bool Tomogram::isVisibleAtAll(const std::vector &trajectory, double ra return !all_outside; } +bool Tomogram::isVisibleFirstFrames(const std::vector &trajectory, double radius, int min_frames) const +{ + std::vector vis = determineVisiblity(trajectory, radius); + + bool all_inside = true; + std::vector sorted_frames = IndexSort::sortIndices(cumulativeDose); + + for (int i = 0; i < min_frames; i++) + { + if (!vis[sorted_frames[i]]) + { + all_inside = false; + break; + } + } + + return all_inside; +} + std::vector Tomogram::determineVisiblity(const std::vector& trajectory, double radius) const { const int fc = trajectory.size(); @@ -95,7 +114,10 @@ double Tomogram::getFrameDose() const return fractionalDose; //cumulativeDose[frameSequence[1]] - cumulativeDose[frameSequence[0]]; } - +double Tomogram::getCumulativeDose(int frame) const +{ + return cumulativeDose[frame]; +} BufferedImage Tomogram::computeDoseWeight(int boxSize, double binning) const { // @TODO: add support for B/k factors @@ -110,32 +132,36 @@ BufferedImage Tomogram::computeNoiseWeight(int boxSize, double binning, d const int sh = s/2 + 1; const int fc = stack.zdim; - BufferedImage out(sh, s, fc); + BufferedImage out(sh, s, nr_selected_frames); for (int f = 0; f < fc; f++) { - BufferedImage powSpec = PowerSpectrum::periodogramAverage2D( - stack, s0, s0, overlap, f, false); - - std::vector powSpec1D = RadialAvg::fftwHalf_2D_lin(powSpec); - - std::vector frqWghts1D(powSpec1D.size()); - - for (int i = 0; i < powSpec1D.size(); i++) - { - if (powSpec1D[i] > 0.0) - { - frqWghts1D[i] = (float)(1.0 / sqrt(powSpec1D[i])); - } - else - { - frqWghts1D[i] = 0.f; - } - } - - RawImage outSlice = out.getSliceRef(f); - RadialAvg::toFftwHalf_2D_lin(frqWghts1D, sh, s, outSlice, binning); - } + int fp = selectedFrameIndex[f]; + if (fp >= 0) + { + BufferedImage powSpec = PowerSpectrum::periodogramAverage2D( + stack, s0, s0, overlap, f, false); + + std::vector powSpec1D = RadialAvg::fftwHalf_2D_lin(powSpec); + + std::vector frqWghts1D(powSpec1D.size()); + + for (int i = 0; i < powSpec1D.size(); i++) + { + if (powSpec1D[i] > 0.0) + { + frqWghts1D[i] = (float)(1.0 / sqrt(powSpec1D[i])); + } + else + { + frqWghts1D[i] = 0.f; + } + } + + RawImage outSlice = out.getSliceRef(f); + RadialAvg::toFftwHalf_2D_lin(frqWghts1D, sh, s, outSlice, binning); + } + } return out; } @@ -270,6 +296,16 @@ bool Tomogram::validateParticleOptics( Cs_good = std::abs(Cs_tomogram - Cs_particles) < eps; } + bool Q0_good = true; + + if (particleSet.optTable.labelExists(EMDL_CTF_Q0)) + { + const double Q0_particles = particleSet.optTable.getDouble(EMDL_CTF_Q0, g); + const double Q0_tomogram = optics.Q0; + + Q0_good = std::abs(Q0_tomogram - Q0_particles) < eps; + } + bool u_good = true; if (particleSet.optTable.labelExists(EMDL_CTF_VOLTAGE)) @@ -290,7 +326,7 @@ bool Tomogram::validateParticleOptics( s_good = std::abs(s_particles - s_tomogram) < eps; } - if (Cs_good && u_good && s_good) + if (Cs_good && u_good && s_good && Q0_good) { valid[g] = true; } diff --git a/src/jaz/tomography/tomogram.h b/src/jaz/tomography/tomogram.h index 4159cfe7c..9b80becdd 100644 --- a/src/jaz/tomography/tomogram.h +++ b/src/jaz/tomography/tomogram.h @@ -19,7 +19,9 @@ class Tomogram Tomogram(); - bool hasOptics, hasImage, hasDeformations; + bool hasOptics, hasImage, hasDeformations, hasMatrices; + std::vector selectedFrameIndex; + int nr_selected_frames; OpticsData optics; gravis::i2Vector imageSize; int frameCount; @@ -50,6 +52,9 @@ class Tomogram bool isVisibleAtAll( const std::vector& trajectory, double radius) const; + + bool isVisibleFirstFrames( + const std::vector& trajectory, double radius, int min_frames) const; std::vector determineVisiblity( const std::vector& trajectory, double radius) const; @@ -57,7 +62,8 @@ class Tomogram double getFrameDose() const; - + double getCumulativeDose(int frame) const; + BufferedImage computeDoseWeight(int boxSize, double binning) const; BufferedImage computeNoiseWeight(int boxSize, double binning, double overlap = 2.0) const; diff --git a/src/jaz/tomography/tomogram_set.cpp b/src/jaz/tomography/tomogram_set.cpp index 0147567e4..ef5552df6 100644 --- a/src/jaz/tomography/tomogram_set.cpp +++ b/src/jaz/tomography/tomogram_set.cpp @@ -18,108 +18,319 @@ TomogramSet::TomogramSet() globalTable.setName("global"); } -TomogramSet::TomogramSet(std::string filename, bool verbose) +TomogramSet::TomogramSet(FileName filename, bool verbose) { - std::ifstream ifs(filename); - - bool namesAreOld = false; - - if (!ifs) - { - REPORT_ERROR_STR("TomogramSet::TomogramSet: Unable to read " << filename); - } - else - { - globalTable.readStar(ifs, "global"); - - const int tc = globalTable.numberOfObjects(); - - tomogramTables.resize(tc); - - std::vector allTables = MetaDataTable::readAll(ifs, tc+1); - - for (int t = 0; t < tc; t++) - { - const std::string expectedOldName = "tomo_" + ZIO::itoa(t); - const std::string expectedNewName = globalTable.getString(EMDL_TOMO_NAME, t); - const std::string name = allTables[t+1].getName(); + bool success = read(filename, verbose); +} - if (name == expectedOldName) - { - namesAreOld = true; - } - else if (name != expectedNewName) - { - REPORT_ERROR_STR("TomogramSet::TomogramSet: file is corrupted " << filename); - } +bool TomogramSet::read(FileName filename, bool verbose) +{ - tomogramTables[t] = allTables[t+1]; - tomogramTables[t].setName(expectedNewName); - } - } - - globalTable.setName("global"); + globalTable.read(filename, "global"); + globalTable.setName("global"); + + const int tc = globalTable.numberOfObjects(); + tomogramTables.resize(tc); + if (tc == 0) return false; + + if (!globalTable.containsLabel(EMDL_TOMO_NAME)) + { + REPORT_ERROR("ERROR: input starfile for TomogramSet " + filename + " does not contain rlnTomoName label "); + } + + if (!globalTable.containsLabel(EMDL_TOMO_TILT_SERIES_STARFILE)) + { + if (verbose) std::cerr << "Warning: " << filename + << " does not have rlnTomoTiltSeriesStarFile labels. It may be written in an old format. If so, will try to convert ..." + << std::endl; + + // This may be a tomograms.star file in the old, original relion-4.0 format. Try to convert + FileName mydir = filename.beforeLastOf("/"); + + std::ifstream ifs(filename); + if (!ifs) + { + REPORT_ERROR_STR("TomogramSet::TomogramSet: Unable to read " << filename); + } + else + { + std::vector allTables = MetaDataTable::readAll(ifs, tc+1); + for (int t = 0; t < tc; t++) + { + FileName expectedNewName = globalTable.getString(EMDL_TOMO_NAME, t); + FileName name = allTables[t+1].getName(); + + + if (name != expectedNewName) + { + REPORT_ERROR_STR("TomogramSet::TomogramSet: file is corrupted " << filename); + } + + tomogramTables[t] = allTables[t+1]; + tomogramTables[t].setName(expectedNewName); + + // Also set the name for the titlseries STAR file + FileName fn_star = filename.beforeLastOf("/") + "/tilt_series/" + expectedNewName + ".star"; + globalTable.setValue(EMDL_TOMO_TILT_SERIES_STARFILE, fn_star, t); + + } + } + } + else + { + // The new way of reading in the tomogram STAR files + for (int t = 0; t < tc; t++) + { + FileName name = globalTable.getString(EMDL_TOMO_NAME, t); + std::string fn_star = globalTable.getString(EMDL_TOMO_TILT_SERIES_STARFILE, t); + tomogramTables[t].read(fn_star, name); + + if (!tomogramTables[t].containsLabel(EMDL_MICROGRAPH_PRE_EXPOSURE)) + REPORT_ERROR("ERROR: tomogramTable does not contain compulsory rlnMicrographPreExposure label"); + } + } + return true; - if (verbose && namesAreOld) - { - Log::warn("Tomogram set " + filename + " is out of date. You are recommended to run relion_exp_update_tomogram_set on it."); - } } -Tomogram TomogramSet::loadTomogram(int index, bool loadImageData) const +void TomogramSet::write(FileName filename) { - Tomogram out; + FileName fn_outdir = filename.beforeLastOf("/") + "/"; - std::string tomoName, stackFn; + const int tc = tomogramTables.size(); - globalTable.getValueSafely(EMDL_TOMO_NAME, tomoName, index); - globalTable.getValueSafely(EMDL_TOMO_TILT_SERIES_NAME, stackFn, index); - globalTable.getValueSafely(EMDL_TOMO_FRAME_COUNT, out.frameCount, index); + // Change all the filenames in tomograms.star + for (int t = 0; t < tc; t++) + { + FileName fn_star; + globalTable.getValue(EMDL_TOMO_TILT_SERIES_STARFILE, fn_star, t); + FileName fn_newstar = getOutputFileWithNewUniqueDate(fn_star, fn_outdir); + globalTable.setValue(EMDL_TOMO_TILT_SERIES_STARFILE, fn_newstar, t); - i3Vector stackSize; + // Create output directory if necessary + FileName newdir = fn_newstar.beforeLastOf("/"); + if (!exists(newdir)) mktree(newdir); - if (loadImageData) - { - out.stack.read(stackFn); - out.hasImage = true; - - stackSize.x = out.stack.xdim; - stackSize.y = out.stack.ydim; - stackSize.z = out.stack.zdim; - } - else - { - out.hasImage = false; - - t3Vector isl = ImageFileHelper::getSize(stackFn); + // Write the individual tomogram starfile + tomogramTables[t].write(fn_newstar); + } - stackSize.x = isl.x; - stackSize.y = isl.y; - stackSize.z = isl.z; - } + // Also write the (now modified with fn_newstars) tilt_series.star file in the root directory + globalTable.write(filename); - out.imageSize = stackSize.xy(); +} - out.tiltSeriesFilename = stackFn; +Tomogram TomogramSet::loadTomogram(int index, bool loadImageData, bool loadEvenFramesOnly, bool loadOddFramesOnly, RFLOAT max_dose) const //Set loadEven/OddFramesOnly to True to loadImageData from rlnTomoMicrographNameEven/Odd rather than rlnMicrographName +{ + Tomogram out; - globalTable.getValueSafely(EMDL_TOMO_SIZE_X, out.w0, index); - globalTable.getValueSafely(EMDL_TOMO_SIZE_Y, out.h0, index); - globalTable.getValueSafely(EMDL_TOMO_SIZE_Z, out.d0, index); + std::string tomoName; + i3Vector stackSize; + + globalTable.getValueSafely(EMDL_TOMO_NAME, tomoName, index); + const MetaDataTable& m = tomogramTables[index]; + + if (globalTable.containsLabel(EMDL_TOMO_SIZE_X) && + globalTable.containsLabel(EMDL_TOMO_SIZE_Y) && + globalTable.containsLabel(EMDL_TOMO_SIZE_Z)) + { + globalTable.getValueSafely(EMDL_TOMO_SIZE_X, out.w0, index); + globalTable.getValueSafely(EMDL_TOMO_SIZE_Y, out.h0, index); + globalTable.getValueSafely(EMDL_TOMO_SIZE_Z, out.d0, index); + } + else + { + out.w0 = out.h0 = out.d0 = -999; + } + + // Select only a subset of the tilt series images with the lowest dose + out.frameCount = tomogramTables[index].numberOfObjects(); + + out.nr_selected_frames = out.frameCount; + if (max_dose > 0.) + { + out.nr_selected_frames = 0; + for (int f = 0, fp = 0; f < out.frameCount; f++) + { + double mydose; + tomogramTables[index].getValueSafely(EMDL_MICROGRAPH_PRE_EXPOSURE, mydose, f); + if (mydose <= max_dose) + { + out.selectedFrameIndex.push_back(out.nr_selected_frames); + out.nr_selected_frames++; + } + else + { + out.selectedFrameIndex.push_back(-1); + } + } + } + else + { + // all frames are selected + for (int f = 0; f < out.frameCount; f++) + { + out.selectedFrameIndex.push_back(f); + } + } + + if (globalTable.containsLabel(EMDL_TOMO_TILT_SERIES_NAME)) + { + // option A: Kino's original IMOD import functionality + globalTable.getValueSafely(EMDL_TOMO_TILT_SERIES_NAME, out.tiltSeriesFilename, index); + + t3Vector isl = ImageFileHelper::getSize(out.tiltSeriesFilename); + stackSize.x = isl.x; + stackSize.y = isl.y; + stackSize.z = out.nr_selected_frames; + + if (loadImageData) + { + if (max_dose > 0.) + { + Image myStack(stackSize.x, stackSize.y, stackSize.z); + for (int f = 0; f < out.frameCount; f++) + { + if (out.selectedFrameIndex[f] >= 0) + { + Image I2; + FileName fn_img; + fn_img.compose(f+1,out.tiltSeriesFilename+":mrcs"); + I2.read(fn_img); + + if (XSIZE(I2()) != stackSize.x || YSIZE(I2()) != stackSize.y) + { + REPORT_ERROR("ERROR: unequal image dimensions in the individual tilt series images of tomogram: " + tomoName); + } + myStack().setSlice(out.selectedFrameIndex[f], I2()); + } + } + out.stack.copyDataAndSizeFrom(myStack); + } + else + { + out.stack.read(out.tiltSeriesFilename); + } + + out.hasImage = true; + + } + else + { + // Still set image size in header, as this is used for example in TomoBackprojectProgram::getProjectMatrices + out.stack.xdim = stackSize.x; + out.stack.ydim = stackSize.y; + out.stack.zdim = stackSize.z; + out.hasImage = false; + } + + } + else + { + // option B: new functionality to work directly with images from RELION's motioncorr runner + + out.tiltSeriesFilename = ""; + + // Get image size from the first image in the tomogramTable + if (!m.containsLabel(EMDL_MICROGRAPH_NAME) && !loadEvenFramesOnly && !loadOddFramesOnly) + { + REPORT_ERROR("ERROR: tomogramTable for " + tomoName + " does not contain a rlnMicrographName label, yet the globalTable also does not contain a rlnTomoTiltSeriesName label!"); + } + + if (!m.containsLabel(EMDL_MICROGRAPH_ODD) && loadOddFramesOnly) + { + REPORT_ERROR("ERROR: tomogramTable for " + tomoName + " does not contain a rlnTomoMicrographNameOdd label"); + } + + if (!m.containsLabel(EMDL_MICROGRAPH_EVEN) && loadEvenFramesOnly) + { + REPORT_ERROR("ERROR: tomogramTable for " + tomoName + " does not contain a rlnTomoMicrographNameEven label"); + } + + std::string fn_img; + if (loadEvenFramesOnly) + { + m.getValueSafely(EMDL_MICROGRAPH_EVEN, fn_img, 0); + } + else if (loadOddFramesOnly) + { + m.getValueSafely(EMDL_MICROGRAPH_ODD, fn_img, 0); + } + else + { + m.getValueSafely(EMDL_MICROGRAPH_NAME, fn_img, 0); + } + + Image I; + I.read(fn_img,false); // false means don't read the actual image data, only the header + + stackSize.x = XSIZE(I()); + stackSize.y = YSIZE(I()); + stackSize.z = out.nr_selected_frames; + + if (loadImageData) + { + Image myStack(stackSize.x, stackSize.y, stackSize.z); + for (int f = 0; f < out.frameCount; f++) + { + if (out.selectedFrameIndex[f] >= 0) + { + + if (loadEvenFramesOnly) + { + m.getValueSafely(EMDL_MICROGRAPH_EVEN, fn_img, f); + } + else if (loadOddFramesOnly) + { + m.getValueSafely(EMDL_MICROGRAPH_ODD, fn_img, f); + } + else + { + m.getValueSafely(EMDL_MICROGRAPH_NAME, fn_img, f); + } + + Image I2; + I2.read(fn_img); + + if (XSIZE(I2()) != stackSize.x || YSIZE(I2()) != stackSize.y) + { + REPORT_ERROR("ERROR: unequal image dimensions in the individual tilt series images of tomogram: " + tomoName); + } + myStack().setSlice(out.selectedFrameIndex[f], I2()); + } + } + + out.stack.copyDataAndSizeFrom(myStack); + out.hasImage = true; + } + else + { + // Still set image size in header, as this is used for example in TomoBackprojectProgram::getProjectMatrices + out.stack.xdim = stackSize.x; + out.stack.ydim = stackSize.y; + out.stack.zdim = stackSize.z; + out.hasImage = false; + } + + } + out.imageSize = stackSize.xy(); out.centre = d3Vector(out.w0/2.0, out.h0/2.0, out.d0/2.0); globalTable.getValueSafely(EMDL_TOMO_HANDEDNESS, out.handedness, index); - double Q0; + // Now that we do notioncorrection on tomogram_sets, the micrograph pixel size may not have been set yet... + if (globalTable.containsLabel(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE)) + globalTable.getValueSafely(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, out.optics.pixelSize, index); + else + out.optics.pixelSize = -999.; - globalTable.getValueSafely(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, out.optics.pixelSize, index); - globalTable.getValueSafely(EMDL_CTF_VOLTAGE, out.optics.voltage, index); - globalTable.getValueSafely(EMDL_CTF_CS, out.optics.Cs, index); - globalTable.getValueSafely(EMDL_CTF_Q0, Q0, index); + globalTable.getValueSafely(EMDL_CTF_VOLTAGE, out.optics.voltage, index); + globalTable.getValueSafely(EMDL_CTF_CS, out.optics.Cs, index); + globalTable.getValueSafely(EMDL_CTF_Q0, out.optics.Q0, index); - out.hasDeformations = ( - globalTable.containsLabel(EMDL_TOMO_DEFORMATION_GRID_SIZE_X) && - globalTable.containsLabel(EMDL_TOMO_DEFORMATION_GRID_SIZE_Y) ); + out.hasDeformations = (globalTable.containsLabel(EMDL_TOMO_DEFORMATION_GRID_SIZE_X) && + globalTable.containsLabel(EMDL_TOMO_DEFORMATION_GRID_SIZE_Y) ); i2Vector deformationGridSize; std::string deformationType = ""; @@ -140,32 +351,38 @@ Tomogram TomogramSet::loadTomogram(int index, bool loadImageData) const } } - const MetaDataTable& m = tomogramTables[index]; - out.cumulativeDose.resize(out.frameCount); out.centralCTFs.resize(out.frameCount); out.projectionMatrices.resize(out.frameCount); + out.hasMatrices = (m.containsLabel(EMDL_TOMO_PROJECTION_X) && + m.containsLabel(EMDL_TOMO_PROJECTION_Y) && + m.containsLabel(EMDL_TOMO_PROJECTION_Z) && + m.containsLabel(EMDL_TOMO_PROJECTION_W)); for (int f = 0; f < out.frameCount; f++) { d4Matrix& P = out.projectionMatrices[f]; - std::vector rows({ - EMDL_TOMO_PROJECTION_X, - EMDL_TOMO_PROJECTION_Y, - EMDL_TOMO_PROJECTION_Z, - EMDL_TOMO_PROJECTION_W }); - - for (int i = 0; i < 4; i++) - { - std::vector vals; - m.getValueSafely(rows[i], vals, f); - - for (int j = 0; j < 4; j++) - { - P(i,j) = vals[j]; - } - } + if (out.hasMatrices) + { + // Old way of defining tilt series alignments, designed by Jasenko Zivanov + std::vector rows({ + EMDL_TOMO_PROJECTION_X, + EMDL_TOMO_PROJECTION_Y, + EMDL_TOMO_PROJECTION_Z, + EMDL_TOMO_PROJECTION_W }); + + for (int i = 0; i < 4; i++) + { + std::vector vals; + m.getValueSafely(rows[i], vals, f); + + for (int j = 0; j < 4; j++) + { + P(i,j) = vals[j]; + } + } + } CTF& ctf = out.centralCTFs[f]; @@ -173,7 +390,7 @@ Tomogram TomogramSet::loadTomogram(int index, bool loadImageData) const m.getValueSafely(EMDL_CTF_DEFOCUSV, ctf.DeltafV, f); m.getValueSafely(EMDL_CTF_DEFOCUS_ANGLE, ctf.azimuthal_angle, f); - ctf.Q0 = Q0; + ctf.Q0 = out.optics.Q0; ctf.Cs = out.optics.Cs; ctf.kV = out.optics.voltage; @@ -186,8 +403,7 @@ Tomogram TomogramSet::loadTomogram(int index, bool loadImageData) const m.getValueSafely(EMDL_MICROGRAPH_PRE_EXPOSURE, out.cumulativeDose[f], f); - if (out.hasDeformations && - m.containsLabel(EMDL_TOMO_DEFORMATION_COEFFICIENTS)) + if (out.hasDeformations && m.containsLabel(EMDL_TOMO_DEFORMATION_COEFFICIENTS)) { const std::vector coeffs = m.getDoubleVector( EMDL_TOMO_DEFORMATION_COEFFICIENTS, f); @@ -221,16 +437,6 @@ Tomogram TomogramSet::loadTomogram(int index, bool loadImageData) const out.fractionalDose = out.cumulativeDose[out.frameSequence[1]] - out.cumulativeDose[out.frameSequence[0]]; } - if (globalTable.containsLabel(EMDL_IMAGE_OPTICS_GROUP_NAME)) - { - out.opticsGroupName = globalTable.getString(EMDL_IMAGE_OPTICS_GROUP_NAME, index); - } - else - { - out.opticsGroupName = "opticsGroup1"; - } - - out.name = tomoName; if (globalTable.containsLabel(EMDL_TOMO_FIDUCIALS_STARFILE)) @@ -254,7 +460,7 @@ Tomogram TomogramSet::loadTomogram(int index, bool loadImageData) const return out; } -void TomogramSet::addTomogram( +void TomogramSet::addTomogramFromIMODStack( std::string tomoName, std::string stackFilename, const std::vector& projections, int w, int h, int d, @@ -262,8 +468,7 @@ void TomogramSet::addTomogram( double fractionalDose, const std::vector& ctfs, double handedness, - double pixelSize, - const std::string& opticsGroupName) + double pixelSize) { const int index = globalTable.numberOfObjects(); const int fc = projections.size(); @@ -272,22 +477,21 @@ void TomogramSet::addTomogram( globalTable.setValue(EMDL_TOMO_NAME, tomoName, index); globalTable.setValue(EMDL_TOMO_TILT_SERIES_NAME, stackFilename, index); - globalTable.setValue(EMDL_TOMO_FRAME_COUNT, fc, index); - + globalTable.setValue(EMDL_TOMO_SIZE_X, w, index); globalTable.setValue(EMDL_TOMO_SIZE_Y, h, index); globalTable.setValue(EMDL_TOMO_SIZE_Z, d, index); globalTable.setValue(EMDL_TOMO_HANDEDNESS, handedness, index); - - const CTF& ctf0 = ctfs[0]; - - globalTable.setValue(EMDL_IMAGE_OPTICS_GROUP_NAME, opticsGroupName, index); - globalTable.setValue(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, pixelSize, index); - globalTable.setValue(EMDL_CTF_VOLTAGE, ctf0.kV, index); - globalTable.setValue(EMDL_CTF_CS, ctf0.Cs, index); - globalTable.setValue(EMDL_CTF_Q0, ctf0.Q0, index); - globalTable.setValue(EMDL_TOMO_IMPORT_FRACT_DOSE, fractionalDose, index); - + + const CTF& ctf0 = ctfs[0]; + + globalTable.setValue(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, pixelSize, index); + globalTable.setValue(EMDL_CTF_VOLTAGE, ctf0.kV, index); + globalTable.setValue(EMDL_CTF_CS, ctf0.Cs, index); + globalTable.setValue(EMDL_CTF_Q0, ctf0.Q0, index); + globalTable.setValue(EMDL_TOMO_IMPORT_FRACT_DOSE, fractionalDose, index); + + if (tomogramTables.size() != index) { REPORT_ERROR_STR("TomogramSet::add: corrupted tomogram set: tomogramTables.size() = " @@ -314,31 +518,6 @@ int TomogramSet::size() const return tomogramTables.size(); } -void TomogramSet::write(std::string filename) const -{ - const int tc = tomogramTables.size(); - - if (filename.find_last_of('/') != std::string::npos) - { - std::string path = filename.substr(0, filename.find_last_of('/')); - mktree(path); - } - - std::ofstream ofs(filename); - - if (!ofs) - { - REPORT_ERROR("TomogramSet::write: unable to write to "+filename); - } - - globalTable.write(ofs); - - for (int t = 0; t < tc; t++) - { - tomogramTables[t].write(ofs); - } -} - void TomogramSet::setProjections(int tomogramIndex, const std::vector& proj) { MetaDataTable& m = tomogramTables[tomogramIndex]; @@ -455,6 +634,7 @@ std::string TomogramSet::getTomogramName(int index) const return name; } + int TomogramSet::getTomogramIndexSafely(std::string tomogramName) const { int t = getTomogramIndex(tomogramName); @@ -491,9 +671,18 @@ int TomogramSet::getMaxFrameCount() const return max_val; } -double TomogramSet::getPixelSize(int index) const +double TomogramSet::getOriginalPixelSize(int index) const { - return globalTable.getDouble(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, index); + if (!globalTable.containsLabel(EMDL_MICROGRAPH_ORIGINAL_PIXEL_SIZE)) + REPORT_ERROR("ERROR: cannot find rlnMicrographOriginalPixelSize label in star file."); + return globalTable.getDouble(EMDL_MICROGRAPH_ORIGINAL_PIXEL_SIZE, index); +} + +double TomogramSet::getTiltSeriesPixelSize(int index) const +{ + if (!globalTable.containsLabel(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE)) + REPORT_ERROR("ERROR: cannot find rlnTomoTiltSeriesPixelSize label in star file."); + return globalTable.getDouble(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, index); } std::string TomogramSet::getOpticsGroupName(int index) const @@ -507,3 +696,68 @@ std::string TomogramSet::getOpticsGroupName(int index) const return globalTable.getString(EMDL_IMAGE_OPTICS_GROUP_NAME, index); } } + +void TomogramSet::generateSingleMetaDataTable(MetaDataTable &MDout, ObservationModel &obsModel) +{ + MDout.clear(); + for (long int t = 0; t < tomogramTables.size(); t++) + { + // Store all the necessary optics stuff in an opticsGroup per tomogram + RFLOAT moviePixelSize, voltage, Cs, Q0; + std::string tomo_name = getTomogramName(t); + globalTable.getValueSafely(EMDL_MICROGRAPH_ORIGINAL_PIXEL_SIZE, moviePixelSize, t); + globalTable.getValueSafely(EMDL_CTF_VOLTAGE, voltage, t); + globalTable.getValueSafely(EMDL_CTF_CS, Cs, t); + globalTable.getValueSafely(EMDL_CTF_Q0, Q0, t); + obsModel.opticsMdt.addObject(); + obsModel.opticsMdt.setValue(EMDL_IMAGE_OPTICS_GROUP_NAME, tomo_name); + obsModel.opticsMdt.setValue(EMDL_IMAGE_OPTICS_GROUP, t+1); + obsModel.opticsMdt.setValue(EMDL_MICROGRAPH_ORIGINAL_PIXEL_SIZE, moviePixelSize); + obsModel.opticsMdt.setValue(EMDL_CTF_VOLTAGE, voltage); + obsModel.opticsMdt.setValue(EMDL_CTF_CS, Cs); + obsModel.opticsMdt.setValue(EMDL_CTF_Q0, Q0); + + if (globalTable.containsLabel(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE)) + { + RFLOAT pixSize; + globalTable.getValueSafely(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, pixSize, t); + obsModel.opticsMdt.setValue(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, pixSize); + } + + FOR_ALL_OBJECTS_IN_METADATA_TABLE(tomogramTables[t]) + { + tomogramTables[t].setValue(EMDL_IMAGE_OPTICS_GROUP, t+1); + } + + MDout.append(tomogramTables[t]); + } +} + +void TomogramSet::convertBackFromSingleMetaDataTable(MetaDataTable &MDin) +{ + if (!MDin.containsLabel(EMDL_MICROGRAPH_PRE_EXPOSURE)) + { + REPORT_ERROR("BUG: MDin should contain a rlnMicrographPreExposure label"); + } + + for (long int t = 0; t < tomogramTables.size(); t++) + { + MetaDataTable MDsub = subsetMetaDataTable(MDin, EMDL_IMAGE_OPTICS_GROUP, t+1, t+1); + + // Make sure no one unsorted the tilt images in each serie... + MDsub.newSort(EMDL_MICROGRAPH_PRE_EXPOSURE); + + if (MDsub.numberOfObjects() != tomogramTables[t].numberOfObjects()) + { + REPORT_ERROR("ERROR: unequal number of Objects in tiltserie starfiles"); + } + + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDsub) + { + tomogramTables[t].setObject(MDsub.getObject(), current_object); + } + + tomogramTables[t].deactivateLabel(EMDL_IMAGE_OPTICS_GROUP); + + } +} diff --git a/src/jaz/tomography/tomogram_set.h b/src/jaz/tomography/tomogram_set.h index d8d30c298..3e09e82b0 100644 --- a/src/jaz/tomography/tomogram_set.h +++ b/src/jaz/tomography/tomogram_set.h @@ -4,27 +4,29 @@ #include #include #include +#include #include #include #include - class TomogramSet { public: - - TomogramSet(); - TomogramSet(std::string filename, bool verbose = true); - - - MetaDataTable globalTable; - std::vector tomogramTables; + MetaDataTable globalTable; + std::vector tomogramTables; + + TomogramSet(); + TomogramSet(FileName filename, bool verbose = true); + // return false if this is not a TomogramSet + bool read(FileName filename, bool verbose = true); + void write(FileName filename); - Tomogram loadTomogram(int index, bool loadImageData) const; + // If max_dose is positive, then only images with cumulativeDose less than or equal to max_dose will be loaded. + Tomogram loadTomogram(int index, bool loadImageData, bool loadEvenFrames = false, bool loadOddFrames = false, RFLOAT max_dose = -1.) const; - void addTomogram( + void addTomogramFromIMODStack( std::string tomoName, std::string stackFilename, const std::vector& projections, int w, int h, int d, @@ -32,13 +34,10 @@ class TomogramSet double fractionalDose, const std::vector& ctfs, double handedness, - double pixelSize, - const std::string& opticsGroupName); + double pixelSize); int size() const; - - void write(std::string filename) const; - + void setProjections(int tomogramIndex, const std::vector& proj); void setProjection(int tomogramIndex, int frame, const gravis::d4Matrix& P); void setCtf(int tomogramIndex, int frame, const CTF& ctf); @@ -56,12 +55,20 @@ class TomogramSet void clearDeformation(); int getTomogramIndex(std::string tomogramName) const; - std::string getTomogramName(int index) const; + std::string getTomogramName(int index) const; int getTomogramIndexSafely(std::string tomogramName) const; int getFrameCount(int index) const; int getMaxFrameCount() const; - double getPixelSize(int index) const; + double getOriginalPixelSize(int index) const; + double getTiltSeriesPixelSize(int index) const; std::string getOpticsGroupName(int index) const; + + // SHWS 6Apr2022: Make one big metadatatable with all movies/micrographs (to be used for motioncorr and ctffind runners) + void generateSingleMetaDataTable(MetaDataTable &MDout, ObservationModel &obsModel); + + // SHWS 6Apr2022: Convert back from one big metadatatable into separate STAR files for each tilt serie + void convertBackFromSingleMetaDataTable(MetaDataTable &MDin); + }; #endif diff --git a/src/macros.h b/src/macros.h index c46a9c4dd..56c74a349 100644 --- a/src/macros.h +++ b/src/macros.h @@ -45,7 +45,7 @@ #ifndef MACROS_H #define MACROS_H -#define RELION_SHORT_VERSION "4.0.1" +#define RELION_SHORT_VERSION "5.0-beta-0" extern const char *g_RELION_VERSION; #include @@ -80,7 +80,9 @@ extern const char *g_RELION_VERSION; #define MY_MPI_COMPLEX MPI_C_DOUBLE_COMPLEX #endif -#if defined CUDA and DEBUG_CUDA +#if defined _CUDA_ENABLED and DEBUG_CUDA +#define CRITICAL(string) raise(SIGSEGV); +#elif _HIP_ENABLED and DEBUG_HIP #define CRITICAL(string) raise(SIGSEGV); #else #define CRITICAL(string) REPORT_ERROR(string); @@ -408,7 +410,7 @@ static void SINCOSF(float x, float *s, float *c) { *s = sinf(x); *c = cosf(x); } static void PRINT_VERSION_INFO() { std::cout << "RELION version: " << g_RELION_VERSION << " " -#if defined(DEBUG) || defined(DEBUG_CUDA) +#if defined(DEBUG) || defined(DEBUG_CUDA) || defined(DEBUG_HIP) << "(debug-build) " #endif @@ -420,12 +422,20 @@ static void PRINT_VERSION_INFO() << "BASE=double" #endif -#if defined(CUDA) || defined(ALTCPU) +#if defined(_CUDA_ENABLED) || defined(_HIP_ENABLED) || defined(_SYCL_ENABLED) || defined(ALTCPU) #ifdef _CUDA_ENABLED << ", CUDA-ACC=" #endif + #ifdef _HIP_ENABLED + << ", HIP-ACC=" + #endif + + #ifdef _SYCL_ENABLED + << ", SYCL-ACC=" + #endif + #ifdef ALTCPU << ", VECTOR-ACC=" #endif diff --git a/src/manualpicker.cpp b/src/manualpicker.cpp index 563f4f904..384c51d63 100644 --- a/src/manualpicker.cpp +++ b/src/manualpicker.cpp @@ -42,7 +42,6 @@ RFLOAT global_coord_scale; RFLOAT global_lowpass; RFLOAT global_highpass; bool global_do_topaz_denoise; -FileName global_topaz_exe; RFLOAT global_particle_diameter; RFLOAT global_sigma_contrast; RFLOAT global_black_val; @@ -137,7 +136,7 @@ void cb_viewmic(Fl_Widget* w, void* data) command += " --particle_radius " + floatToString(rad); if (global_do_topaz_denoise) { - command += " --topaz_denoise --topaz_exe " + global_topaz_exe; + command += " --topaz_denoise "; } command += " --lowpass " + floatToString(global_lowpass); command += " --highpass " + floatToString(global_highpass); @@ -698,7 +697,6 @@ void ManualPicker::read(int argc, char **argv) global_lowpass = textToFloat(parser.getOption("--lowpass", "Lowpass filter in Angstroms for the micrograph (0 for no filtering)","0")); global_highpass = textToFloat(parser.getOption("--highpass", "Highpass filter in Angstroms for the micrograph (0 for no filtering)","0")); global_do_topaz_denoise = parser.checkOption("--topaz_denoise", "Or instead of filtering, use Topaz denoising before picking (on GPU 0)"); - global_topaz_exe = parser.getOption("--topaz_exe", "Name of topaz executable", "topaz"); global_ctfscale = textToFloat(parser.getOption("--ctf_scale", "Relative scale for the CTF-image display", "1")); global_ctfsigma = textToFloat(parser.getOption("--ctf_sigma_contrast", "Sigma-contrast for the CTF-image display", "3")); global_minimum_fom = textToFloat(parser.getOption("--minimum_pick_fom", "Minimum value for rlnAutopickFigureOfMerit to display picks", "-9999.")); diff --git a/src/mask.cpp b/src/mask.cpp index cc3899c3a..5b273aa4e 100644 --- a/src/mask.cpp +++ b/src/mask.cpp @@ -138,8 +138,13 @@ void softMaskOutsideMapForHelix( if ( (cosine_width < 0.) || (mask_sphere_radius_pix < 1.) || (mask_sphere_radius_pix > boxsize) || (mask_cyl_radius_pix < 1.) || (mask_cyl_radius_pix > boxsize) - || (mask_sphere_radius_pix < mask_cyl_radius_pix) ) - REPORT_ERROR("mask.cpp::softMaskOutsideMapForHelix(): Invalid radii of spherical and cylindrical masks or soft cosine widths!"); + || (mask_sphere_radius_pix < mask_cyl_radius_pix) ) { + std::cerr << " cosine_width= " << cosine_width << std::endl; + std::cerr << " boxsize= " << boxsize << std::endl; + std::cerr << " mask_sphere_radius_pix= " << mask_sphere_radius_pix << std::endl; + std::cerr << " mask_cyl_radius_pix= " << mask_cyl_radius_pix << std::endl; + REPORT_ERROR("mask.cpp::softMaskOutsideMapForHelix(): Invalid radii of spherical and cylindrical masks or soft cosine widths! Is boxsize > mask_sphere_radius > mask_cyl_radius?"); + } // Spherical mask: 0 < R1 < R2 R1 = mask_sphere_radius_pix; diff --git a/src/metadata_label.h b/src/metadata_label.h index 19ea20b71..ecdd6919e 100644 --- a/src/metadata_label.h +++ b/src/metadata_label.h @@ -432,6 +432,9 @@ enum EMDLabel EMDL_OPTIMISER_NR_ITERATIONS, EMDL_OPTIMISER_NR_ITER_WO_RESOL_GAIN, EMDL_OPTIMISER_NR_ITER_WO_HIDDEN_VAR_CHANGES, + EMDL_OPTIMISER_OFFSET_RANGE_X, + EMDL_OPTIMISER_OFFSET_RANGE_Y, + EMDL_OPTIMISER_OFFSET_RANGE_Z, EMDL_OPTIMISER_OPTICS_STARFILE, EMDL_OPTIMISER_OUTPUT_ROOTNAME, EMDL_OPTIMISER_PARTICLE_DIAMETER, @@ -606,27 +609,47 @@ enum EMDLabel EMDL_TOMO_NAME, EMDL_TOMO_TILT_SERIES_NAME, + EMDL_TOMO_TILT_SERIES_STARFILE, + EMDL_TOMO_TILT_MOVIE_FRAMECOUNT, + EMDL_TOMO_ETOMO_DIRECTIVE_FILE, EMDL_TOMO_FRAME_COUNT, - EMDL_TOMO_SIZE_X, + EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME, +EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_HALF1_FILE_NAME, + EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_HALF2_FILE_NAME, + EMDL_TOMO_DENOISED_TOMOGRAM_FILE_NAME, + EMDL_TOMO_SIZE_X, EMDL_TOMO_SIZE_Y, EMDL_TOMO_SIZE_Z, EMDL_TOMO_PROJECTION_X, EMDL_TOMO_PROJECTION_Y, EMDL_TOMO_PROJECTION_Z, EMDL_TOMO_PROJECTION_W, + EMDL_TOMO_XTILT, + EMDL_TOMO_YTILT, + EMDL_TOMO_ZROT, + EMDL_TOMO_XSHIFT_ANGST, + EMDL_TOMO_YSHIFT_ANGST, EMDL_TOMO_HANDEDNESS, EMDL_TOMO_FIDUCIALS_STARFILE, EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, - EMDL_TOMO_SUBTOMOGRAM_ROT, + EMDL_MICROGRAPH_EVEN, + EMDL_MICROGRAPH_ODD, + EMDL_TOMO_SUBTOMOGRAM_MAXDOSE, + EMDL_TOMO_SUBTOMOGRAM_ROT, EMDL_TOMO_SUBTOMOGRAM_TILT, EMDL_TOMO_SUBTOMOGRAM_PSI, + EMDL_TOMO_SUBTOMOGRAM_STACK2D, EMDL_TOMO_SUBTOMOGRAM_BINNING, - EMDL_TOMO_PARTICLE_NAME, + EMDL_TOMO_TOMOGRAM_BINNING, + EMDL_TOMO_PARTICLE_NAME, EMDL_TOMO_PARTICLE_ID, EMDL_TOMO_MANIFOLD_INDEX, EMDL_TOMO_MANIFOLD_TYPE, EMDL_TOMO_MANIFOLD_PARAMETERS, EMDL_TOMO_DEFOCUS_SLOPE, + EMDL_TOMO_NOMINAL_TILT_STAGE_ANGLE, + EMDL_TOMO_NOMINAL_TILT_AXIS_ANGLE, + EMDL_TOMO_NOMINAL_DEFOCUS, EMDL_TOMO_PARTICLES_FILE_NAME, EMDL_TOMO_TOMOGRAMS_FILE_NAME, @@ -952,7 +975,7 @@ class StaticInitialization EMDL::addLabel(EMDL_MICROGRAPH_SHIFT_Y, EMDL_DOUBLE, "rlnMicrographShiftY", "Y shift of a (patch of) micrograph"); EMDL::addLabel(EMDL_MICROGRAPH_MOTION_COEFFS_IDX, EMDL_INT, "rlnMotionModelCoeffsIdx", "Index of a coefficient of a motion model"); EMDL::addLabel(EMDL_MICROGRAPH_MOTION_COEFF, EMDL_DOUBLE, "rlnMotionModelCoeff", "A coefficient of a motion model"); - EMDL::addLabel(EMDL_MICROGRAPH_EER_UPSAMPLING, EMDL_INT, "rlnEERUpsampling", "EER upsampling ratio (1 = 4K, 2 = 8K)"); + EMDL::addLabel(EMDL_MICROGRAPH_EER_UPSAMPLING, EMDL_INT, "rlnEERUpsampling", "EER upsampling ratio (1 = physical, 2 = 2x super-resolution)"); EMDL::addLabel(EMDL_MICROGRAPH_EER_GROUPING, EMDL_INT, "rlnEERGrouping", "The number of hardware frames to group"); EMDL::addLabel(EMDL_MLMODEL_ACCURACY_ROT, EMDL_DOUBLE, "rlnAccuracyRotations", "Estimated accuracy (in degrees) with which rotations can be assigned"); @@ -1119,7 +1142,10 @@ class StaticInitialization EMDL::addLabel(EMDL_OPTIMISER_NR_ITERATIONS, EMDL_INT, "rlnNumberOfIterations", "Maximum number of iterations to be performed"); EMDL::addLabel(EMDL_OPTIMISER_NR_ITER_WO_RESOL_GAIN, EMDL_INT, "rlnNumberOfIterWithoutResolutionGain", "Number of iterations that have passed without a gain in resolution"); EMDL::addLabel(EMDL_OPTIMISER_NR_ITER_WO_HIDDEN_VAR_CHANGES, EMDL_INT, "rlnNumberOfIterWithoutChangingAssignments", "Number of iterations that have passed without large changes in orientation and class assignments"); - EMDL::addLabel(EMDL_OPTIMISER_OPTICS_STARFILE, EMDL_STRING, "rlnOpticsStarFile", "STAR file with metadata for the optical groups (new as of version 3.1)"); + EMDL::addLabel(EMDL_OPTIMISER_OFFSET_RANGE_X,EMDL_DOUBLE, "rlnOffsetRangeX", "Search range for offsets in the X-direction (in Angstrom)"); + EMDL::addLabel(EMDL_OPTIMISER_OFFSET_RANGE_Y,EMDL_DOUBLE, "rlnOffsetRangeX", "Search range for offsets in the Y-direction (in Angstrom)"); + EMDL::addLabel(EMDL_OPTIMISER_OFFSET_RANGE_Z,EMDL_DOUBLE, "rlnOffsetRangeX", "Search range for offsets in the Z-direction (in Angstrom)"); + EMDL::addLabel(EMDL_OPTIMISER_OPTICS_STARFILE, EMDL_STRING, "rlnOpticsStarFile", "STAR file with metadata for the optical groups (new as of version 3.1)"); EMDL::addLabel(EMDL_OPTIMISER_OUTPUT_ROOTNAME, EMDL_STRING, "rlnOutputRootName", "Rootname for all output files (this may include a directory structure, which should then exist)"); EMDL::addLabel(EMDL_OPTIMISER_PARTICLE_DIAMETER, EMDL_DOUBLE, "rlnParticleDiameter", "Diameter of the circular mask to be applied to all experimental images (in Angstroms)"); EMDL::addLabel(EMDL_OPTIMISER_RADIUS_MASK_3D_MAP, EMDL_INT, "rlnRadiusMaskMap", "Radius of the spherical mask to be applied to all references (in Angstroms)"); @@ -1290,11 +1316,26 @@ class StaticInitialization EMDL::addLabel(EMDL_TOMO_NAME, EMDL_STRING, "rlnTomoName", "Arbitrary name for a tomogram"); EMDL::addLabel(EMDL_TOMO_TILT_SERIES_NAME, EMDL_STRING, "rlnTomoTiltSeriesName", "Tilt series file name"); + EMDL::addLabel(EMDL_TOMO_TILT_SERIES_STARFILE, EMDL_STRING, "rlnTomoTiltSeriesStarFile", "Tilt series starfile"); + EMDL::addLabel(EMDL_TOMO_TILT_MOVIE_FRAMECOUNT, EMDL_INT, "rlnTomoTiltMovieFrameCount", "Number of frames in the tilt series movies"); + EMDL::addLabel(EMDL_TOMO_ETOMO_DIRECTIVE_FILE, EMDL_STRING, "rlnEtomoDirectiveFile", "Location of the etomo directive file (.edf) from tilt series alignment"); + EMDL::addLabel(EMDL_TOMO_FRAME_COUNT, EMDL_INT, "rlnTomoFrameCount", "Number of tilts in a tilt series"); + EMDL::addLabel(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME, EMDL_STRING, "rlnTomoReconstructedTomogram", "File name of a reconstructed tomogram"); + EMDL::addLabel(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_HALF1_FILE_NAME, EMDL_STRING, "rlnTomoReconstructedTomogramHalf1", "File name of a reconstructed tomogram from even numbered movie frames or tilt image index"); + EMDL::addLabel(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_HALF2_FILE_NAME, EMDL_STRING, "rlnTomoReconstructedTomogramHalf2", "File name of a reconstructed tomogram from odd numbered movie frames or tilt image index"); + EMDL::addLabel(EMDL_TOMO_DENOISED_TOMOGRAM_FILE_NAME, EMDL_STRING, "rlnTomoDenoisedTomogram", "File name of a denoised tomogram"); + EMDL::addLabel(EMDL_TOMO_FRAME_COUNT, EMDL_INT, "rlnTomoFrameCount", "Number of tilts in a tilt series"); EMDL::addLabel(EMDL_TOMO_SIZE_X, EMDL_INT, "rlnTomoSizeX", "Width of a bin-1 tomogram in pixels"); EMDL::addLabel(EMDL_TOMO_SIZE_Y, EMDL_INT, "rlnTomoSizeY", "Height of a bin-1 tomogram in pixels"); EMDL::addLabel(EMDL_TOMO_SIZE_Z, EMDL_INT, "rlnTomoSizeZ", "Depth of a bin-1 tomogram in pixels"); + EMDL::addLabel(EMDL_TOMO_XTILT, EMDL_DOUBLE, "rlnTomoXTilt", "Euler angle for rotation of tomogram around X-axis"); + EMDL::addLabel(EMDL_TOMO_YTILT, EMDL_DOUBLE, "rlnTomoYTilt", "Euler angle for rotation of tomogram around Y-axis"); + EMDL::addLabel(EMDL_TOMO_ZROT, EMDL_DOUBLE, "rlnTomoZRot", "Euler angle for rotation of tomogram around Z-axis"); + EMDL::addLabel(EMDL_TOMO_XSHIFT_ANGST, EMDL_DOUBLE, "rlnTomoXShiftAngst", "X-translation (in A) to align the projection of a tomogram with the tilt series image"); + EMDL::addLabel(EMDL_TOMO_YSHIFT_ANGST, EMDL_DOUBLE, "rlnTomoYShiftAngst", "Y-translation (in A) to align the projection of a tomogram with the tilt series image"); + EMDL::addLabel(EMDL_TOMO_PROJECTION_X, EMDL_DOUBLE_VECTOR, "rlnTomoProjX", "First row of the projection matrix"); EMDL::addLabel(EMDL_TOMO_PROJECTION_Y, EMDL_DOUBLE_VECTOR, "rlnTomoProjY", "Second row of the projection matrix"); EMDL::addLabel(EMDL_TOMO_PROJECTION_Z, EMDL_DOUBLE_VECTOR, "rlnTomoProjZ", "Third row of the projection matrix"); @@ -1304,11 +1345,14 @@ class StaticInitialization EMDL::addLabel(EMDL_TOMO_FIDUCIALS_STARFILE, EMDL_STRING, "rlnTomoFiducialsStarFile", "STAR file containing the 3D locations of fiducial markers"); EMDL::addLabel(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, EMDL_DOUBLE, "rlnTomoTiltSeriesPixelSize", "Pixel size of the original tilt series"); - EMDL::addLabel(EMDL_TOMO_SUBTOMOGRAM_ROT, EMDL_DOUBLE, "rlnTomoSubtomogramRot", "First Euler angle of a subtomogram (rot, in degrees)"); + EMDL::addLabel(EMDL_TOMO_SUBTOMOGRAM_MAXDOSE, EMDL_DOUBLE, "rlnTomoSubTomosMaxDose", "Maximum dose (in e/A2) used to extract subtomogram particles"); + EMDL::addLabel(EMDL_TOMO_SUBTOMOGRAM_ROT, EMDL_DOUBLE, "rlnTomoSubtomogramRot", "First Euler angle of a subtomogram (rot, in degrees)"); EMDL::addLabel(EMDL_TOMO_SUBTOMOGRAM_TILT, EMDL_DOUBLE, "rlnTomoSubtomogramTilt", "Second Euler angle of a subtomogram (tilt, in degrees)"); EMDL::addLabel(EMDL_TOMO_SUBTOMOGRAM_PSI, EMDL_DOUBLE, "rlnTomoSubtomogramPsi", "Third Euler angle of a subtomogram (psi, in degrees)"); + EMDL::addLabel(EMDL_TOMO_SUBTOMOGRAM_STACK2D, EMDL_BOOL, "rlnTomoSubTomosAre2DStacks", "This flag is set to true if subtomograms are saved as 2D image stacks"); EMDL::addLabel(EMDL_TOMO_SUBTOMOGRAM_BINNING, EMDL_DOUBLE, "rlnTomoSubtomogramBinning", "Binning level of a subtomogram"); - EMDL::addLabel(EMDL_TOMO_PARTICLE_NAME, EMDL_STRING, "rlnTomoParticleName", "Name of each individual particle"); + EMDL::addLabel(EMDL_TOMO_TOMOGRAM_BINNING, EMDL_DOUBLE, "rlnTomoTomogramBinning", "Binning level of a reconstructed tomogram"); + EMDL::addLabel(EMDL_TOMO_PARTICLE_NAME, EMDL_STRING, "rlnTomoParticleName", "Name of each individual particle"); EMDL::addLabel(EMDL_TOMO_PARTICLE_ID, EMDL_INT, "rlnTomoParticleId", "Unique particle index"); EMDL::addLabel(EMDL_TOMO_MANIFOLD_INDEX, EMDL_INT, "rlnTomoManifoldIndex", "Index of a 2D manifold in a tomogram"); @@ -1324,6 +1368,10 @@ class StaticInitialization EMDL::addLabel(EMDL_TOMO_REFERENCE_MAP_2_FILE_NAME, EMDL_STRING, "rlnTomoReferenceMap2File", "Name of second reference map file"); EMDL::addLabel(EMDL_TOMO_REFERENCE_MASK_FILE_NAME, EMDL_STRING, "rlnTomoReferenceMaskFile", "Name of mask file corresponding to a pair of reference maps"); EMDL::addLabel(EMDL_TOMO_REFERENCE_FSC_FILE_NAME, EMDL_STRING, "rlnTomoReferenceFscFile", "Name of FSC STAR file corresponding to a pair of reference maps"); + EMDL::addLabel(EMDL_TOMO_NOMINAL_TILT_STAGE_ANGLE, EMDL_DOUBLE, "rlnTomoNominalStageTiltAngle", "Nominal value for the stage tilt angle"); + EMDL::addLabel(EMDL_TOMO_NOMINAL_TILT_AXIS_ANGLE, EMDL_DOUBLE, "rlnTomoNominalTiltAxisAngle", "Nominal value for the angle of the tilt axis"); + EMDL::addLabel(EMDL_TOMO_NOMINAL_DEFOCUS, EMDL_DOUBLE, "rlnTomoNominalDefocus", "Nominal value for the defocus in the tilt series image"); + EMDL::addLabel(EMDL_TOMO_IMPORT_OFFSET_X, EMDL_DOUBLE, "rlnTomoImportOffsetX", "X offset of a tomogram"); EMDL::addLabel(EMDL_TOMO_IMPORT_OFFSET_Y, EMDL_DOUBLE, "rlnTomoImportOffsetY", "Y offset of a tomogram"); @@ -1352,6 +1400,8 @@ class StaticInitialization EMDL::addLabel(EMDL_TOMO_TILT_MOVIE_INDEX, EMDL_INT, "rlnTomoTiltMovieIndex", "Chronological index of a tilt movie"); EMDL::addLabel(EMDL_TOMO_TILT_MOVIE_FILE_NAME, EMDL_STRING, "rlnTomoTiltMovieFile", "Movie containing the frames of a tilt"); + EMDL::addLabel(EMDL_MICROGRAPH_EVEN, EMDL_STRING, "rlnMicrographNameEven", "Micrograph summed from even frames of motion corrected movie"); + EMDL::addLabel(EMDL_MICROGRAPH_ODD, EMDL_STRING, "rlnMicrographNameOdd", "Micrograph summed from odd frames of motion corrected movie"); EMDL::addLabel(EMDL_UNKNOWN_LABEL, EMDL_UNKNOWN, "rlnUnknownLabel", "NON-RELION label: values will be ignored, yet maintained in the STAR file."); } diff --git a/src/metadata_table.cpp b/src/metadata_table.cpp index a4981a863..f0053eaee 100644 --- a/src/metadata_table.cpp +++ b/src/metadata_table.cpp @@ -1980,6 +1980,7 @@ MetaDataTable subsetMetaDataTable(MetaDataTable &MDin, EMDLabel label, std::stri REPORT_ERROR("subsetMetadataTable ERROR: input MetaDataTable does not contain label: " + EMDL::label2Str(label)); MetaDataTable MDout; + MDout.setName(MDin.getName()); FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDin) { std::string val; @@ -2098,3 +2099,5 @@ MetaDataTable removeDuplicatedParticles(MetaDataTable &MDin, EMDLabel mic_label, return MDout; } + + diff --git a/src/metadata_table.h b/src/metadata_table.h index 7f780f43b..88602eb07 100644 --- a/src/metadata_table.h +++ b/src/metadata_table.h @@ -501,4 +501,5 @@ bool MetaDataTable::setValue(EMDLabel label, const T &value, long int objectID) } } + #endif diff --git a/src/ml_model.cpp b/src/ml_model.cpp index b5bd8de3c..8eb1ee2d3 100644 --- a/src/ml_model.cpp +++ b/src/ml_model.cpp @@ -1798,15 +1798,7 @@ void MlWsumModel::initZeros() } -//#define DEBUG_PACK -#ifdef DEBUG_PACK -#define MAX_PACK_SIZE 100000 -#else -// Approximately 1024*1024*1024/8/2 ~ 0.5 Gb -#define MAX_PACK_SIZE 67101000 -#endif - -void MlWsumModel::pack(MultidimArray &packed) +unsigned long long MlWsumModel::getPackSize() { unsigned long long packed_size = 0; int spectral_size = (ori_size / 2) + 1; @@ -1823,15 +1815,32 @@ void MlWsumModel::pack(MultidimArray &packed) // for all class-related stuff // data is complex: multiply by two! - packed_size += nr_classes * nr_bodies * 2 * (unsigned long long)BPref[0].getSize(); - packed_size += nr_classes * nr_bodies * (unsigned long long)BPref[0].getSize(); - packed_size += nr_classes * nr_bodies * (unsigned long long)nr_directions; + packed_size += nr_classes * nr_bodies * 2 * (unsigned long long) BPref[0].getSize(); // BPref.data + packed_size += nr_classes * nr_bodies * (unsigned long long) BPref[0].getSize(); // BPref.weight + packed_size += nr_classes * nr_bodies * (unsigned long long) nr_directions; // pdf_directions + // for pdf_class packed_size += nr_classes; + // for priors for each class if (ref_dim==2) packed_size += nr_classes*2; + return packed_size; +} + +//#define DEBUG_PACK +#ifdef DEBUG_PACK +#define MAX_PACK_SIZE 100000 +#else +// Approximately 1024*1024*1024/8/2 ~ 0.5 Gb +#define MAX_PACK_SIZE 67101000 +#endif + +void MlWsumModel::pack(MultidimArray &packed) +{ + unsigned long long packed_size = getPackSize(); + // Get memory for the packed array packed.clear(); packed.resize(packed_size); diff --git a/src/ml_model.h b/src/ml_model.h index 037dcdcca..6a5f020ec 100644 --- a/src/ml_model.h +++ b/src/ml_model.h @@ -525,6 +525,9 @@ class MlWsumModel: public MlModel // Initialize all weighted sums to zero (with resizing the BPrefs to current_size) void initZeros(); + // Return the current pack size + unsigned long long getPackSize(); + // Pack entire structure into one large MultidimArray for reading/writing to disc // To save memory, the model itself will be cleared after packing. void pack(MultidimArray &packed); diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index cd9b7e549..395d9e59e 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -24,11 +24,11 @@ //#define DEBUG_BODIES #ifdef TIMING - #define RCTIC(timer,label) (timer.tic(label)) - #define RCTOC(timer,label) (timer.toc(label)) + #define RCTIC(timer,label) (timer.tic(label)) + #define RCTOC(timer,label) (timer.toc(label)) #else - #define RCTIC(timer,label) - #define RCTOC(timer,label) + #define RCTIC(timer,label) + #define RCTOC(timer,label) #endif #include @@ -47,14 +47,22 @@ #include "src/acc/cuda/cuda_ml_optimiser.h" #include #include -#endif -#ifdef ALTCPU - #include - #include - #include - #define TBB_PREVIEW_GLOBAL_CONTROL 1 - #include - #include "src/acc/cpu/cpu_ml_optimiser.h" +#elif _HIP_ENABLED +#include "src/acc/hip/hip_ml_optimiser.h" +#include +#elif _SYCL_ENABLED + #include + #include + #include + #include + #include "src/acc/sycl/sycl_ml_optimiser.h" +#elif ALTCPU + #include + #include + #include + #define TBB_PREVIEW_GLOBAL_CONTROL 1 + #include + #include "src/acc/cpu/cpu_ml_optimiser.h" #endif #define NR_CLASS_MUTEXES 5 @@ -67,464 +75,529 @@ static omp_lock_t global_mutex; void globalThreadExpectationSomeParticles(void *self, int thread_id) { - MlOptimiser *MLO = (MlOptimiser*)self; + MlOptimiser *MLO = (MlOptimiser*)self; - try - { -#ifdef _CUDA_ENABLED - if (MLO->do_gpu) - ((MlOptimiserCuda*) MLO->cudaOptimisers[thread_id])->doThreadExpectationSomeParticles(thread_id); - else + try + { +#if defined _CUDA_ENABLED || defined _HIP_ENABLED || defined _SYCL_ENABLED + if (MLO->do_gpu || MLO->do_sycl) + ((MlOptimiserAccGPU*) MLO->gpuOptimisers[thread_id])->doThreadExpectationSomeParticles(thread_id); + else #endif - MLO->doThreadExpectationSomeParticles(thread_id); - } - catch (RelionError XE) - { - RelionError *gE = new RelionError(XE.msg, XE.file, XE.line); - gE->msg = XE.msg; - MLO->threadException = gE; - } + MLO->doThreadExpectationSomeParticles(thread_id); + } + catch (RelionError XE) + { + RelionError *gE = new RelionError(XE.msg, XE.file, XE.line); + gE->msg = XE.msg; + MLO->threadException = gE; + } } +#ifdef _SYCL_ENABLED +MlOptimiser::~MlOptimiser() +{ + for (int i = 0; i < syclDeviceList.size(); i++) + delete syclDeviceList[i]; + + syclDeviceList.clear(); +} +#endif /** ========================== I/O operations =========================== */ void MlOptimiser::usage() { - parser.writeUsage(std::cout); + parser.writeUsage(std::cout); } void MlOptimiser::read(int argc, char **argv, int rank) { //#define DEBUG_READ - parser.setCommandLine(argc, argv); + parser.setCommandLine(argc, argv); - if (checkParameter(argc, argv, "--continue")) - { - // Do this before reading in the data.star file below! - do_preread_images = checkParameter(argc, argv, "--preread_images"); - do_parallel_disc_io = !checkParameter(argc, argv, "--no_parallel_disc_io"); - - parser.addSection("Continue options"); - FileName fn_in = parser.getOption("--continue", "_optimiser.star file of the iteration after which to continue"); - // Read in previously calculated parameters - if (fn_in != "") - read(fn_in, rank); - - // And look for additional command-line options... - parseContinue(argc, argv); - } - else - { - // Start a new run from scratch - parseInitial(argc, argv); - } + if (checkParameter(argc, argv, "--continue")) + { + // Do this before reading in the data.star file below! + do_preread_images = checkParameter(argc, argv, "--preread_images"); + do_parallel_disc_io = !checkParameter(argc, argv, "--no_parallel_disc_io"); + + parser.addSection("Continue options"); + FileName fn_in = parser.getOption("--continue", "_optimiser.star file of the iteration after which to continue"); + // Read in previously calculated parameters + if (fn_in != "") + read(fn_in, rank); + + // And look for additional command-line options... + parseContinue(argc, argv); + } + else + { + // Start a new run from scratch + parseInitial(argc, argv); + } } void MlOptimiser::parseContinue(int argc, char **argv) { #ifdef DEBUG - std::cerr << "Entering parseContinue" << std::endl; + std::cerr << "Entering parseContinue" << std::endl; #endif - int general_section = parser.addSection("General options"); - // Not all parameters are accessible here... - FileName fn_out_new = parser.getOption("--o", "Output rootname", "OLD_ctX"); - if (fn_out_new == "OLD_ctX" || fn_out_new == fn_out ) - fn_out += "_ct" + integerToString(iter); - else - fn_out = fn_out_new; - - do_force_converge = parser.checkOption("--force_converge", "Force an auto-refinement run to converge immediately upon continuation."); - - // For multi-body refinement - bool fn_body_masks_was_empty = (fn_body_masks == "None"); - std::string fnt; - fnt = parser.getOption("--multibody_masks", "STAR file with masks and metadata for multi-body refinement", "OLD"); - if (fnt != "OLD") - fn_body_masks = fnt; - // Don't use _ctXX at start of a multibody refinement - if (fn_body_masks_was_empty && fn_body_masks != "") - fn_out = parser.getOption("--o", "Output rootname", "run"); - - // Also allow change of padding... - fnt = parser.getOption("--pad", "Oversampling factor for the Fourier transforms of the references", "OLD"); - if (fnt != "OLD") - { - if (textToInteger(fnt) != mymodel.padding_factor) - { - if (mymodel.nr_bodies > 1) - REPORT_ERROR("ERROR: cannot change padding factor in a continuation of a multi-body refinement..."); - mymodel.padding_factor = textToInteger(fnt); - // Re-initialise the model to get the right padding factors in the PPref vectors - mymodel.initialise(); - } - } + int general_section = parser.addSection("General options"); + // Not all parameters are accessible here... + FileName fn_out_new = parser.getOption("--o", "Output rootname", "OLD_ctX"); + if (fn_out_new == "OLD_ctX" || fn_out_new == fn_out ) + fn_out += "_ct" + integerToString(iter); + else + fn_out = fn_out_new; - // Is this a new multi-body refinement? - if (fn_body_masks_was_empty && fn_body_masks != "None") - do_initialise_bodies = true; - else - do_initialise_bodies = false; + do_force_converge = parser.checkOption("--force_converge", "Force an auto-refinement run to converge immediately upon continuation."); - if (do_initialise_bodies) - { - ini_high = textToFloat(parser.getOption("--ini_high", "Resolution (in Angstroms) to which to limit refinement in the first iteration ", "-1")); + // For multi-body refinement + bool fn_body_masks_was_empty = (fn_body_masks == "None"); + std::string fnt; + fnt = parser.getOption("--multibody_masks", "STAR file with masks and metadata for multi-body refinement", "OLD"); + if (fnt != "OLD") + fn_body_masks = fnt; + // Don't use _ctXX at start of a multibody refinement + if (fn_body_masks_was_empty && fn_body_masks != "") + fn_out = parser.getOption("--o", "Output rootname", "run"); - mymodel.norm_body_mask_overlap = parser.checkOption("--multibody_norm_overlap", "Overlapping regions between bodies are normalized. This reduces memory requirements."); - } - do_reconstruct_subtracted_bodies = parser.checkOption("--reconstruct_subtracted_bodies", "Use this flag to perform reconstructions with the subtracted images in multi-body refinement"); + // Also allow change of padding... + fnt = parser.getOption("--pad", "Oversampling factor for the Fourier transforms of the references", "OLD"); + if (fnt != "OLD") + { + if (textToInteger(fnt) != mymodel.padding_factor) + { + if (mymodel.nr_bodies > 1) + REPORT_ERROR("ERROR: cannot change padding factor in a continuation of a multi-body refinement..."); + mymodel.padding_factor = textToInteger(fnt); + // Re-initialise the model to get the right padding factors in the PPref vectors + mymodel.initialise(); + } + } - fnt = parser.getOption("--iter", "Maximum number of iterations to perform", "OLD"); - if (fnt != "OLD") - nr_iter = textToInteger(fnt); + // Is this a new multi-body refinement? + if (fn_body_masks_was_empty && fn_body_masks != "None") + do_initialise_bodies = true; + else + do_initialise_bodies = false; - fnt = parser.getOption("--tau2_fudge", "Regularisation parameter (values higher than 1 give more weight to the data)", "OLD"); - if (fnt != "OLD") - { - mymodel.tau2_fudge_factor = textToFloat(fnt); - tau2_fudge_arg = mymodel.tau2_fudge_factor; - } + if (do_initialise_bodies) + { + ini_high = textToFloat(parser.getOption("--ini_high", "Resolution (in Angstroms) to which to limit refinement in the first iteration ", "-1")); - fnt = parser.getOption("--tau2_fudge_scheme", "Tau2 fudge factor updates scheme. Valid values are plain or -step. Where is the deflate factor during initial stage.", "OLD"); - if (fnt != "OLD") - tau2_fudge_scheme = fnt; - - auto_ignore_angle_changes = parser.checkOption("--auto_ignore_angles", "In auto-refinement, update angular sampling regardless of changes in orientations for convergence. This makes convergence faster."); - auto_resolution_based_angles= parser.checkOption("--auto_resol_angles", "In auto-refinement, update angular sampling based on resolution-based required sampling. This makes convergence faster."); - allow_coarser_samplings = parser.checkOption("--allow_coarser_sampling", "In 2D/3D classification, allow coarser angular and translational samplings if accuracies are bad (typically in earlier iterations."); - - // Solvent flattening - if (parser.checkOption("--flatten_solvent", "Switch on masking on the references?", "OLD")) - do_solvent = true; - - // Check whether the mask has changed - fnt = parser.getOption("--solvent_mask", "User-provided mask for the references", "OLD"); - if (fnt != "OLD") - fn_mask = fnt; - - // Check whether the secondary mask has changed - fnt = parser.getOption("--solvent_mask2", "User-provided secondary mask", "OLD"); - if (fnt != "OLD") - fn_mask2 = fnt; - - // These are still experimental; so not in the optimiser.star yet. - fn_lowpass_mask = parser.getOption("--lowpass_mask", "User-provided mask for low-pass filtering", "None"); - lowpass = textToFloat(parser.getOption("--lowpass", "User-provided cutoff for region specified above", "0")); - - // Check whether tau2-spectrum has changed - fnt = parser.getOption("--tau", "STAR file with input tau2-spectrum (to be kept constant)", "OLD"); - if (fnt != "OLD") - fn_tau = fnt; - - // Check whether particle diameter has changed - fnt = parser.getOption("--particle_diameter", "Diameter of the circular mask that will be applied to the experimental images (in Angstroms)", "OLD"); - if (fnt != "OLD") - particle_diameter = textToFloat(fnt); - - // Gradient related - fnt = parser.getOption("--grad_em_iters", "Number of iterations at the end of a gradient refinement using Expectation-Maximization", "OLD"); - if (fnt != "OLD") - grad_em_iters = textToInteger(fnt); - - fnt = parser.getOption("--grad_stepsize", "Step size parameter for gradient optimisation.", "OLD"); - if (fnt != "OLD") - grad_stepsize = textToFloat(fnt); - - fnt = parser.getOption("--grad_stepsize_scheme", "Gradient step size updates scheme. Valid values are plain or -step. Where is the initial factor during initial stage.", "OLD"); - if (fnt != "OLD") - grad_stepsize_scheme = fnt; - - fnt = parser.getOption("--grad_ini_frac", "Fraction of iterations in the initial phase of refinement", "OLD"); - if (fnt != "OLD") - grad_ini_frac = textToFloat(fnt); - - fnt = parser.getOption("--grad_fin_frac", "Fraction of iterations in the final phase of refinement", "OLD"); - if (fnt != "OLD") - grad_fin_frac = textToFloat(fnt); - - if (grad_ini_frac <= 0 || 1 <= grad_ini_frac) - REPORT_ERROR("Invalid value for --grad_ini_frac."); - if (grad_fin_frac <= 0 || 1 <= grad_fin_frac) - REPORT_ERROR("Invalid value for --grad_fin_frac."); - - if (grad_ini_frac + grad_fin_frac > 0.9) { - RFLOAT sum = grad_ini_frac + grad_fin_frac + 0.1; - grad_ini_frac /= sum; - grad_fin_frac /= sum; - } + mymodel.norm_body_mask_overlap = parser.checkOption("--multibody_norm_overlap", "Overlapping regions between bodies are normalized. This reduces memory requirements."); + } + do_reconstruct_subtracted_bodies = parser.checkOption("--reconstruct_subtracted_bodies", "Use this flag to perform reconstructions with the subtracted images in multi-body refinement"); - grad_ini_iter = nr_iter * grad_ini_frac; - grad_fin_iter = nr_iter * grad_fin_frac; - grad_inbetween_iter = nr_iter - grad_ini_iter - grad_fin_iter; - if (grad_inbetween_iter < 0) - grad_inbetween_iter = 0; + fnt = parser.getOption("--iter", "Maximum number of iterations to perform", "OLD"); + if (fnt != "OLD") + nr_iter = textToInteger(fnt); + + fnt = parser.getOption("--tau2_fudge", "Regularisation parameter (values higher than 1 give more weight to the data)", "OLD"); + if (fnt != "OLD") + { + mymodel.tau2_fudge_factor = textToFloat(fnt); + tau2_fudge_arg = mymodel.tau2_fudge_factor; + } - fnt = parser.getOption("--grad_ini_resol", "Resolution cutoff during the initial SGD iterations (A)", "OLD"); - if (fnt != "OLD") - grad_ini_resol = textToFloat(fnt); + fnt = parser.getOption("--tau2_fudge_scheme", "Tau2 fudge factor updates scheme. Valid values are plain or -step. Where is the deflate factor during initial stage.", "OLD"); + if (fnt != "OLD") + tau2_fudge_scheme = fnt; - fnt = parser.getOption("--grad_fin_resol", "Resolution cutoff during the final SGD iterations (A)", "OLD"); - if (fnt != "OLD") - grad_fin_resol = textToFloat(fnt); + auto_ignore_angle_changes = parser.checkOption("--auto_ignore_angles", "In auto-refinement, update angular sampling regardless of changes in orientations for convergence. This makes convergence faster."); + auto_resolution_based_angles= parser.checkOption("--auto_resol_angles", "In auto-refinement, update angular sampling based on resolution-based required sampling. This makes convergence faster."); + allow_coarser_samplings = parser.checkOption("--allow_coarser_sampling", "In 2D/3D classification, allow coarser angular and translational samplings if accuracies are bad (typically in earlier iterations."); - fnt = parser.getOption("--grad_ini_subset", "Mini-batch size during the initial SGD iterations", "OLD"); - if (fnt != "OLD") - grad_ini_subset_size = textToInteger(fnt); + // Solvent flattening + if (parser.checkOption("--flatten_solvent", "Switch on masking on the references?", "OLD")) + do_solvent = true; - fnt = parser.getOption("--grad_fin_subset", "Mini-batch size during the final SGD iterations", "OLD"); - if (fnt != "OLD") - grad_fin_subset_size = textToInteger(fnt); + // Check whether the mask has changed + fnt = parser.getOption("--solvent_mask", "User-provided mask for the references", "OLD"); + if (fnt != "OLD") + fn_mask = fnt; - fnt = parser.getOption("--mu", "Momentum parameter for SGD updates", "OLD"); - if (fnt != "OLD") - mu = textToFloat(fnt); + // Check whether the secondary mask has changed + fnt = parser.getOption("--solvent_mask2", "User-provided secondary mask", "OLD"); + if (fnt != "OLD") + fn_mask2 = fnt; - fnt = parser.getOption("--grad_write_iter", "Write out model every so many iterations in SGD", "OLD"); - if (fnt != "OLD") - write_every_grad_iter = textToInteger(fnt); + // These are still experimental; so not in the optimiser.star yet. + fn_lowpass_mask = parser.getOption("--lowpass_mask", "User-provided mask for low-pass filtering", "None"); + lowpass = textToFloat(parser.getOption("--lowpass", "User-provided cutoff for region specified above", "0")); - fnt = parser.getOption("--class_inactivity_threshold", "Replace classes with little activity during gradient based classification.", "OLD"); + // Check whether tau2-spectrum has changed + fnt = parser.getOption("--tau", "STAR file with input tau2-spectrum (to be kept constant)", "OLD"); if (fnt != "OLD") - class_inactivity_threshold = textToFloat(fnt); + fn_tau = fnt; - fnt = parser.getOption("--maxsig", "Maximum number of poses & translations to consider", "OLD"); - if (fnt != "OLD") - maximum_significants_arg = textToInteger(fnt); + // Check whether particle diameter has changed + fnt = parser.getOption("--particle_diameter", "Diameter of the circular mask that will be applied to the experimental images (in Angstroms)", "OLD"); + if (fnt != "OLD") + particle_diameter = textToFloat(fnt); - do_join_random_halves = parser.checkOption("--join_random_halves", "Join previously split random halves again (typically to perform a final reconstruction)."); + // Gradient related + fnt = parser.getOption("--grad_em_iters", "Number of iterations at the end of a gradient refinement using Expectation-Maximization", "OLD"); + if (fnt != "OLD") + grad_em_iters = textToInteger(fnt); - // ORIENTATIONS - int orientations_section = parser.addSection("Orientations"); + fnt = parser.getOption("--grad_stepsize", "Step size parameter for gradient optimisation.", "OLD"); + if (fnt != "OLD") + grad_stepsize = textToFloat(fnt); - fnt = parser.getOption("--oversampling", "Adaptive oversampling order to speed-up calculations (0=no oversampling, 1=2x, 2=4x, etc)", "OLD"); - if (fnt != "OLD") - adaptive_oversampling = textToInteger(fnt); + fnt = parser.getOption("--grad_stepsize_scheme", "Gradient step size updates scheme. Valid values are plain or -step. Where is the initial factor during initial stage.", "OLD"); + if (fnt != "OLD") + grad_stepsize_scheme = fnt; - // Check whether angular sampling has changed - // Do not do this for auto_refine, but make sure to do this when initialising multi-body refinement! - if (!(do_auto_refine || do_auto_sampling) || do_initialise_bodies) - { - directions_have_changed = false; - fnt = parser.getOption("--healpix_order", "Healpix order for the angular sampling rate on the sphere (before oversampling): hp2=15deg, hp3=7.5deg, etc", "OLD"); - if (fnt != "OLD") - { - int _order = textToInteger(fnt); - if (_order != sampling.healpix_order) - { - directions_have_changed = true; - sampling.healpix_order = _order; - } - } + fnt = parser.getOption("--grad_ini_frac", "Fraction of iterations in the initial phase of refinement", "OLD"); + if (fnt != "OLD") + grad_ini_frac = textToFloat(fnt); - fnt = parser.getOption("--psi_step", "Angular sampling (before oversampling) for the in-plane angle (default=10deg for 2D, hp sampling for 3D)", "OLD"); - if (fnt != "OLD") - sampling.psi_step = textToFloat(fnt); + fnt = parser.getOption("--grad_fin_frac", "Fraction of iterations in the final phase of refinement", "OLD"); + if (fnt != "OLD") + grad_fin_frac = textToFloat(fnt); - fnt = parser.getOption("--offset_range", "Search range for origin offsets (in pixels)", "OLD"); - if (fnt != "OLD") - { - sampling.offset_range = textToFloat(fnt); - sampling.offset_range *= mymodel.pixel_size; // sampling.offset_range is in Angstroms, but command line in pixels! - } + if (grad_ini_frac <= 0 || 1 <= grad_ini_frac) + REPORT_ERROR("Invalid value for --grad_ini_frac."); + if (grad_fin_frac <= 0 || 1 <= grad_fin_frac) + REPORT_ERROR("Invalid value for --grad_fin_frac."); - fnt = parser.getOption("--offset_step", "Sampling rate for origin offsets (in pixels)", "OLD"); - if (fnt != "OLD") - { - sampling.offset_step = textToFloat(fnt); - sampling.offset_step *= mymodel.pixel_size; // sampling.offset_step is in Angstroms, but command line in pixels! - } - } + if (grad_ini_frac + grad_fin_frac > 0.9) { + RFLOAT sum = grad_ini_frac + grad_fin_frac + 0.1; + grad_ini_frac /= sum; + grad_fin_frac /= sum; + } - fnt = parser.getOption("--auto_local_healpix_order", "Minimum healpix order (before oversampling) from which auto-refine procedure will use local searches", "OLD"); - if (fnt != "OLD") - autosampling_hporder_local_searches = textToInteger(fnt); + grad_ini_iter = nr_iter * grad_ini_frac; + grad_fin_iter = nr_iter * grad_fin_frac; + grad_inbetween_iter = nr_iter - grad_ini_iter - grad_fin_iter; + if (grad_inbetween_iter < 0) + grad_inbetween_iter = 0; - // Check whether the prior mode changes - RFLOAT _sigma_rot, _sigma_tilt, _sigma_psi, _sigma_off; - int _mode; - fnt = parser.getOption("--sigma_ang", "Stddev on all three Euler angles for local angular searches (of +/- 3 stddev)", "OLD"); - if (fnt != "OLD") - { - mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; - mymodel.sigma2_rot = mymodel.sigma2_tilt = mymodel.sigma2_psi = textToFloat(fnt) * textToFloat(fnt); - } - fnt = parser.getOption("--sigma_rot", "Stddev on the first Euler angle for local angular searches (of +/- 3 stddev)", "OLD"); - if (fnt != "OLD") - { - mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; - mymodel.sigma2_rot = textToFloat(fnt) * textToFloat(fnt); - } - fnt = parser.getOption("--sigma_tilt", "Stddev on the first Euler angle for local angular searches (of +/- 3 stddev)", "OLD"); - if (fnt != "OLD") - { - mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; - mymodel.sigma2_tilt = textToFloat(fnt) * textToFloat(fnt); - } - fnt = parser.getOption("--sigma_psi", "Stddev on the in-plane angle for local angular searches (of +/- 3 stddev)", "OLD"); - if (fnt != "OLD") - { - mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; - mymodel.sigma2_psi = textToFloat(fnt) * textToFloat(fnt); - } - fnt = parser.getOption("--sigma_off", "Stddev. on the translations", "OLD"); - if (fnt != "OLD") - { - mymodel.sigma2_offset = textToFloat(fnt) * textToFloat(fnt); - } - fnt = parser.getOption("--helical_inner_diameter", "Inner diameter of helical tubes in Angstroms (for masks of helical references and particles)", "OLD"); - if (fnt != "OLD") - { - helical_tube_inner_diameter = textToFloat(fnt); - } - fnt = parser.getOption("--helical_outer_diameter", "Outer diameter of helical tubes in Angstroms (for masks of helical references and particles)", "OLD"); - if (fnt != "OLD") - { - helical_tube_outer_diameter = textToFloat(fnt); - } - fnt = parser.getOption("--perturb", "Perturbation factor for the angular sampling (0=no perturb; 0.5=perturb)", "OLD"); - if (fnt != "OLD") - { - sampling.perturbation_factor = textToFloat(fnt); - } + fnt = parser.getOption("--grad_ini_resol", "Resolution cutoff during the initial SGD iterations (A)", "OLD"); + if (fnt != "OLD") + grad_ini_resol = textToFloat(fnt); - if (parser.checkOption("--skip_align", "Skip orientational assignment (only classify)?")) - do_skip_align = true; - else - do_skip_align = false; // do_skip_align should normally be false... + fnt = parser.getOption("--grad_fin_resol", "Resolution cutoff during the final SGD iterations (A)", "OLD"); + if (fnt != "OLD") + grad_fin_resol = textToFloat(fnt); - if (parser.checkOption("--skip_rotate", "Skip rotational assignment (only translate and classify)?")) - do_skip_rotate = true; - else - do_skip_rotate = false; // do_skip_rotate should normally be false... + fnt = parser.getOption("--grad_ini_subset", "Mini-batch size during the initial SGD iterations", "OLD"); + if (fnt != "OLD") + grad_ini_subset_size = textToInteger(fnt); - if (parser.checkOption("--bimodal_psi", "Do bimodal searches of psi angle?")) // Oct07,2015 - Shaoda, bimodal psi - do_bimodal_psi = true; - else - do_bimodal_psi = false; + fnt = parser.getOption("--grad_fin_subset", "Mini-batch size during the final SGD iterations", "OLD"); + if (fnt != "OLD") + grad_fin_subset_size = textToInteger(fnt); - if (parser.checkOption("--center_classes", "Re-center classes based on their center-of-mass?")) - do_center_classes = true; - else - do_center_classes = false; - - do_skip_maximization = parser.checkOption("--skip_maximize", "Skip maximization step (only write out data.star file)?"); - - int corrections_section = parser.addSection("Corrections"); - - do_ctf_padding = parser.checkOption("--pad_ctf", "Perform CTF padding to treat CTF aliaising better?"); - if (do_ctf_padding) - REPORT_ERROR("--pad_ctf currently disabled."); - - // Can also switch the following option OFF - if (parser.checkOption("--scale", "Switch on intensity-scale corrections on image groups", "OLD")) - do_scale_correction = true; - if (parser.checkOption("--no_scale", "Switch off intensity-scale corrections on image groups", "OLD")) - do_scale_correction = false; - - // Can also switch the following option OFF - if (parser.checkOption("--norm", "Switch on normalisation-error correction","OLD")) - do_norm_correction = true; - if (parser.checkOption("--no_norm", "Switch off normalisation-error correction","OLD")) - do_norm_correction = false; - - int subtomogram_section = parser.addSection("Subtomogram averaging"); - normalised_subtomos = parser.checkOption("--normalised_subtomo", "Have subtomograms been multiplicity normalised? (Default=False)"); - do_skip_subtomo_correction = parser.checkOption("--skip_subtomo_multi", "Skip subtomo multiplicity correction"); - ctf3d_squared = !parser.checkOption("--ctf3d_not_squared", "CTF3D files contain sqrt(CTF^2) patterns"); - subtomo_multi_thr = textToFloat(parser.getOption("--subtomo_multi_thr", "Threshold to remove marginal voxels during expectation", "0.01")); - - int computation_section = parser.addSection("Computation"); - - x_pool = textToInteger(parser.getOption("--pool", "Number of images to pool for each thread task", "1")); - nr_threads = textToInteger(parser.getOption("--j", "Number of threads to run in parallel (only useful on multi-core machines)", "1")); - do_parallel_disc_io = !parser.checkOption("--no_parallel_disc_io", "Do NOT let parallel (MPI) processes access the disc simultaneously (use this option with NFS)"); - combine_weights_thru_disc = !parser.checkOption("--dont_combine_weights_via_disc", "Send the large arrays of summed weights through the MPI network, instead of writing large files to disc"); - do_shifts_onthefly = parser.checkOption("--onthefly_shifts", "Calculate shifted images on-the-fly, do not store precalculated ones in memory"); - do_preread_images = parser.checkOption("--preread_images", "Use this to let the leader process read all particles into memory. Be careful you have enough RAM for large data sets!"); - fn_scratch = parser.getOption("--scratch_dir", "If provided, particle stacks will be copied to this local scratch disk prior to refinement.", ""); - keep_free_scratch_Gb = textToFloat(parser.getOption("--keep_free_scratch", "Space available for copying particle stacks (in Gb)", "10")); - do_reuse_scratch = parser.checkOption("--reuse_scratch", "Re-use data on scratchdir, instead of wiping it and re-copying all data. This works only when ALL particles have already been cached."); - keep_scratch = parser.checkOption("--keep_scratch", "Don't remove scratch after convergence. Following jobs that use EXACTLY the same particles should use --reuse_scratch."); + fnt = parser.getOption("--mu", "Momentum parameter for SGD updates", "OLD"); + if (fnt != "OLD") + mu = textToFloat(fnt); + + fnt = parser.getOption("--grad_write_iter", "Write out model every so many iterations in SGD", "OLD"); + if (fnt != "OLD") + write_every_grad_iter = textToInteger(fnt); + + fnt = parser.getOption("--class_inactivity_threshold", "Replace classes with little activity during gradient based classification.", "OLD"); + if (fnt != "OLD") + class_inactivity_threshold = textToFloat(fnt); + + fnt = parser.getOption("--maxsig", "Maximum number of poses & translations to consider", "OLD"); + if (fnt != "OLD") + maximum_significants_arg = textToInteger(fnt); + + do_join_random_halves = parser.checkOption("--join_random_halves", "Join previously split random halves again (typically to perform a final reconstruction)."); + + // ORIENTATIONS + int orientations_section = parser.addSection("Orientations"); + + fnt = parser.getOption("--oversampling", "Adaptive oversampling order to speed-up calculations (0=no oversampling, 1=2x, 2=4x, etc)", "OLD"); + if (fnt != "OLD") + adaptive_oversampling = textToInteger(fnt); + + // Check whether angular sampling has changed + // Do not do this for auto_refine, but make sure to do this when initialising multi-body refinement! + if (!(do_auto_refine || do_auto_sampling) || do_initialise_bodies) + { + directions_have_changed = false; + fnt = parser.getOption("--healpix_order", "Healpix order for the angular sampling rate on the sphere (before oversampling): hp2=15deg, hp3=7.5deg, etc", "OLD"); + if (fnt != "OLD") + { + int _order = textToInteger(fnt); + if (_order != sampling.healpix_order) + { + directions_have_changed = true; + sampling.healpix_order = _order; + } + } + + fnt = parser.getOption("--psi_step", "Angular sampling (before oversampling) for the in-plane angle (default=10deg for 2D, hp sampling for 3D)", "OLD"); + if (fnt != "OLD") + sampling.psi_step = textToFloat(fnt); + + fnt = parser.getOption("--offset_range", "Search range for origin offsets (in pixels)", "OLD"); + if (fnt != "OLD") + { + sampling.offset_range = textToFloat(fnt); + sampling.offset_range *= mymodel.pixel_size; // sampling.offset_range is in Angstroms, but command line in pixels! + } + + fnt = parser.getOption("--offset_step", "Sampling rate for origin offsets (in pixels)", "OLD"); + if (fnt != "OLD") + { + sampling.offset_step = textToFloat(fnt); + sampling.offset_step *= mymodel.pixel_size; // sampling.offset_step is in Angstroms, but command line in pixels! + } + } + + fnt = parser.getOption("--auto_local_healpix_order", "Minimum healpix order (before oversampling) from which auto-refine procedure will use local searches", "OLD"); + if (fnt != "OLD") + autosampling_hporder_local_searches = textToInteger(fnt); + + // Check whether the prior mode changes + RFLOAT _sigma_rot, _sigma_tilt, _sigma_psi, _sigma_off; + int _mode; + fnt = parser.getOption("--sigma_ang", "Stddev on all three Euler angles for local angular searches (of +/- 3 stddev)", "OLD"); + if (fnt != "OLD") + { + mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; + mymodel.sigma2_rot = mymodel.sigma2_tilt = mymodel.sigma2_psi = textToFloat(fnt) * textToFloat(fnt); + } + fnt = parser.getOption("--sigma_rot", "Stddev on the first Euler angle for local angular searches (of +/- 3 stddev)", "OLD"); + if (fnt != "OLD") + { + mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; + mymodel.sigma2_rot = textToFloat(fnt) * textToFloat(fnt); + } + fnt = parser.getOption("--sigma_tilt", "Stddev on the first Euler angle for local angular searches (of +/- 3 stddev)", "OLD"); + if (fnt != "OLD") + { + mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; + mymodel.sigma2_tilt = textToFloat(fnt) * textToFloat(fnt); + } + fnt = parser.getOption("--sigma_psi", "Stddev on the in-plane angle for local angular searches (of +/- 3 stddev)", "OLD"); + if (fnt != "OLD") + { + mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; + mymodel.sigma2_psi = textToFloat(fnt) * textToFloat(fnt); + } + fnt = parser.getOption("--sigma_off", "Stddev. on the translations", "OLD"); + if (fnt != "OLD") + { + mymodel.sigma2_offset = textToFloat(fnt) * textToFloat(fnt); + } + fnt = parser.getOption("--helical_inner_diameter", "Inner diameter of helical tubes in Angstroms (for masks of helical references and particles)", "OLD"); + if (fnt != "OLD") + { + helical_tube_inner_diameter = textToFloat(fnt); + } + fnt = parser.getOption("--helical_outer_diameter", "Outer diameter of helical tubes in Angstroms (for masks of helical references and particles)", "OLD"); + if (fnt != "OLD") + { + helical_tube_outer_diameter = textToFloat(fnt); + } + fnt = parser.getOption("--perturb", "Perturbation factor for the angular sampling (0=no perturb; 0.5=perturb)", "OLD"); + if (fnt != "OLD") + { + sampling.perturbation_factor = textToFloat(fnt); + } + + if (parser.checkOption("--skip_align", "Skip orientational assignment (only classify)?")) + do_skip_align = true; + else + do_skip_align = false; // do_skip_align should normally be false... + + if (parser.checkOption("--skip_rotate", "Skip rotational assignment (only translate and classify)?")) + do_skip_rotate = true; + else + do_skip_rotate = false; // do_skip_rotate should normally be false... + + if (parser.checkOption("--bimodal_psi", "Do bimodal searches of psi angle?")) // Oct07,2015 - Shaoda, bimodal psi + do_bimodal_psi = true; + else + do_bimodal_psi = false; + + if (parser.checkOption("--center_classes", "Re-center classes based on their center-of-mass?")) + do_center_classes = true; + else + do_center_classes = false; + + do_skip_maximization = parser.checkOption("--skip_maximize", "Skip maximization step (only write out data.star file)?"); + + int corrections_section = parser.addSection("Corrections"); + + do_ctf_padding = parser.checkOption("--pad_ctf", "Perform CTF padding to treat CTF aliaising better?"); + if (do_ctf_padding) + REPORT_ERROR("--pad_ctf currently disabled."); + + // Can also switch the following option OFF + if (parser.checkOption("--scale", "Switch on intensity-scale corrections on image groups", "OLD")) + do_scale_correction = true; + if (parser.checkOption("--no_scale", "Switch off intensity-scale corrections on image groups", "OLD")) + do_scale_correction = false; + + // Can also switch the following option OFF + if (parser.checkOption("--norm", "Switch on normalisation-error correction","OLD")) + do_norm_correction = true; + if (parser.checkOption("--no_norm", "Switch off normalisation-error correction","OLD")) + do_norm_correction = false; + + int subtomogram_section = parser.addSection("Subtomogram averaging"); + normalised_subtomos = parser.checkOption("--normalised_subtomo", "Have subtomograms been multiplicity normalised? (Default=False)"); + do_skip_subtomo_correction = parser.checkOption("--skip_subtomo_multi", "Skip subtomo multiplicity correction"); + ctf3d_squared = !parser.checkOption("--ctf3d_not_squared", "CTF3D files contain sqrt(CTF^2) patterns"); + subtomo_multi_thr = textToFloat(parser.getOption("--subtomo_multi_thr", "Threshold to remove marginal voxels during expectation", "0.01")); + + int computation_section = parser.addSection("Computation"); + + x_pool = textToInteger(parser.getOption("--pool", "Number of images to pool for each thread task", "1")); + nr_threads = textToInteger(parser.getOption("--j", "Number of threads to run in parallel (only useful on multi-core machines)", "1")); + do_parallel_disc_io = !parser.checkOption("--no_parallel_disc_io", "Do NOT let parallel (MPI) processes access the disc simultaneously (use this option with NFS)"); + combine_weights_thru_disc = !parser.checkOption("--dont_combine_weights_via_disc", "Send the large arrays of summed weights through the MPI network, instead of writing large files to disc"); + do_shifts_onthefly = parser.checkOption("--onthefly_shifts", "Calculate shifted images on-the-fly, do not store precalculated ones in memory"); + do_preread_images = parser.checkOption("--preread_images", "Use this to let the leader process read all particles into memory. Be careful you have enough RAM for large data sets!"); + fn_scratch = parser.getOption("--scratch_dir", "If provided, particle stacks will be copied to this local scratch disk prior to refinement.", ""); + keep_free_scratch_Gb = textToFloat(parser.getOption("--keep_free_scratch", "Space available for copying particle stacks (in Gb)", "10")); + do_reuse_scratch = parser.checkOption("--reuse_scratch", "Re-use data on scratchdir, instead of wiping it and re-copying all data. This works only when ALL particles have already been cached."); + keep_scratch = parser.checkOption("--keep_scratch", "Don't remove scratch after convergence. Following jobs that use EXACTLY the same particles should use --reuse_scratch."); #ifdef ALTCPU do_cpu = parser.checkOption("--cpu", "Use intel vectorisation implementation for CPU"); #else - do_cpu = false; + do_cpu = false; +#endif + + failsafe_threshold = textToInteger(parser.getOption("--failsafe_threshold", "Maximum number of particles permitted to be drop, due to zero sum of weights, before exiting with an error (GPU only).", "40")); + +#ifdef _SYCL_ENABLED + char* pEnvSyclCuda = std::getenv("relionSyclUseCuda"); + std::string strSyclCuda = (pEnvSyclCuda == nullptr) ? "0" : pEnvSyclCuda; + std::transform(strSyclCuda.begin(), strSyclCuda.end(), strSyclCuda.begin(), [](unsigned char c){return static_cast(std::tolower(c));}); + const bool isSyclCuda = (strSyclCuda == "1" || strSyclCuda == "on") ? true : false; + + char* pEnvSyclHip = std::getenv("relionSyclUseHip"); + std::string strSyclHip = (pEnvSyclHip == nullptr) ? "0" : pEnvSyclHip; + std::transform(strSyclHip.begin(), strSyclHip.end(), strSyclHip.begin(), [](unsigned char c){return static_cast(std::tolower(c));}); + const bool isSyclHip = (strSyclHip == "1" || strSyclHip == "on") ? true : false; + + do_sycl = parser.checkOption("--gpu", "Use available SYCL Level Zero GPU resources for some calculations"); + if(! do_sycl) do_sycl = parser.checkOption("--sycl", "Use available SYCL Level Zero GPU resources for some calculations"); + do_sycl_levelzero = parser.checkOption("--sycl-levelzero", "Use available SYCL Level Zero GPU resources for some calculations"); + do_sycl_cuda = parser.checkOption("--sycl-cuda", "Use available SYCL CUDA GPU resources for some calculations"); + do_sycl_hip = parser.checkOption("--sycl-hip", "Use available SYCL HIP GPU resources for some calculations"); + do_sycl_opencl = parser.checkOption("--sycl-opencl", "Use available SYCL OpenCL GPU resources for some calculations"); + do_sycl_cpu = parser.checkOption("--sycl-cpu", "Use available SYCL OpenCL CPU resources for some calculations"); + + if(do_sycl_levelzero) + gpu_ids = parser.getOption("--sycl-levelzero", "Device ids for each MPI-thread","default"); + else if(do_sycl_cuda) + gpu_ids = parser.getOption("--sycl-cuda", "Device ids for each MPI-thread","default"); + else if(do_sycl_hip) + gpu_ids = parser.getOption("--sycl-hip", "Device ids for each MPI-thread","default"); + else if(do_sycl_opencl) + gpu_ids = parser.getOption("--sycl-opencl", "Device ids for each MPI-thread","default"); + else if(do_sycl_cpu) + gpu_ids = parser.getOption("--sycl-cpu", "Device ids for each MPI-thread","default"); + else if (do_sycl) + { + gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); + if (gpu_ids == "default") + gpu_ids = parser.getOption("--sycl", "Device ids for each MPI-thread","default"); + } + + if (isSyclCuda && do_sycl) + do_sycl_cuda = true; + else if (isSyclHip && do_sycl) + do_sycl_hip = true; + else if (do_sycl) + do_sycl_levelzero = true; + if (do_sycl_levelzero || do_sycl_cuda || do_sycl_hip || do_sycl_opencl || do_sycl_cpu) + do_sycl = true; +#else + do_sycl = false; + do_sycl_levelzero = do_sycl_cuda = do_sycl_hip = do_sycl_opencl = do_sycl_cpu = false; + + do_gpu = parser.checkOption("--gpu", "Use available gpu resources for some calculations"); + gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); #endif +#if !defined _CUDA_ENABLED && !defined _HIP_ENABLED + if(do_gpu) + { + std::cerr << "+ WARNING : Relion was compiled without CUDA >= 7.0 or HIP with ROCm >= 4.0 - you do NOT have support for GPUs" << std::endl; + do_gpu = false; + } +#endif + double temp_reqSize = textToDouble(parser.getOption("--free_gpu_memory", "GPU device memory (in Mb) to leave free after allocation.", "0")); + if(!do_zero_mask) + temp_reqSize += 100; + temp_reqSize *= 1000*1000; + if(temp_reqSize<0) + REPORT_ERROR("Invalid free_gpu_memory value."); + else + requested_free_gpu_memory = temp_reqSize; - failsafe_threshold = textToInteger(parser.getOption("--failsafe_threshold", "Maximum number of particles permitted to be drop, due to zero sum of weights, before exiting with an error (GPU only).", "40")); + // only allow switching ON solvent_fsc, not off + if (parser.checkOption("--solvent_correct_fsc", "Correct FSC curve for the effects of the solvent mask?")) + do_phase_random_fsc = true; + verb = textToInteger(parser.getOption("--verb", "Verbosity (1=normal, 0=silent)", "1")); - do_gpu = parser.checkOption("--gpu", "Use available gpu resources for some calculations"); - gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); -#ifndef _CUDA_ENABLED -if(do_gpu) - { - std::cerr << "+ WARNING : Relion was compiled without CUDA of at least version 7.0 - you do NOT have support for GPUs" << std::endl; - do_gpu = false; - } -#endif - double temp_reqSize = textToDouble(parser.getOption("--free_gpu_memory", "GPU device memory (in Mb) to leave free after allocation.", "0")); - if(!do_zero_mask) - temp_reqSize += 100; - temp_reqSize *= 1000*1000; - if(temp_reqSize<0) - REPORT_ERROR("Invalid free_gpu_memory value."); - else - requested_free_gpu_memory = temp_reqSize; - - // only allow switching ON solvent_fsc, not off - if (parser.checkOption("--solvent_correct_fsc", "Correct FSC curve for the effects of the solvent mask?")) - do_phase_random_fsc = true; - verb = textToInteger(parser.getOption("--verb", "Verbosity (1=normal, 0=silent)", "1")); - - int expert_section = parser.addSection("Expert options"); - - fnt = parser.getOption("--strict_highres_exp", "Resolution limit (in Angstrom) to restrict probability calculations in the expectation step", "OLD"); - if (fnt != "OLD") - strict_highres_exp = textToFloat(fnt); - - do_trust_ref_size = parser.checkOption("--trust_ref_size", "Trust the pixel and box size of the input reference; by default the program will die if these are different from the first optics group of the data"); - - // Debugging/analysis/hidden stuff - do_map = !checkParameter(argc, argv, "--no_map"); - minres_map = textToInteger(getParameter(argc, argv, "--minres_map", "5")); - abort_at_resolution = textToFloat(parser.getOption("--abort_at_resolution", "Abort when resolution reaches beyond this value", "-1", true)); - gridding_nr_iter = textToInteger(getParameter(argc, argv, "--gridding_iter", "10")); - debug1 = textToFloat(getParameter(argc, argv, "--debug1", "0.")); - debug2 = textToFloat(getParameter(argc, argv, "--debug2", "0.")); - debug3 = textToFloat(getParameter(argc, argv, "--debug3", "0.")); - do_bfactor = checkParameter(argc, argv, "--bfactor"); - // Read in initial sigmaNoise spectrum - fn_sigma = getParameter(argc, argv, "--sigma",""); - sigma2_fudge = textToFloat(getParameter(argc, argv, "--sigma2_fudge", "1.")); - do_acc_currentsize_despite_highres_exp = checkParameter(argc, argv, "--accuracy_current_size"); - do_sequential_halves_recons = checkParameter(argc, argv, "--sequential_halves_recons"); - do_always_join_random_halves = checkParameter(argc, argv, "--always_join_random_halves"); - do_use_all_data = checkParameter(argc, argv, "--use_all_data"); - do_always_cc = checkParameter(argc, argv, "--always_cc"); - do_only_sample_tilt = checkParameter(argc, argv, "--only_sample_tilt"); - minimum_angular_sampling = textToFloat(getParameter(argc, argv, "--minimum_angular_sampling", "0")); - maximum_angular_sampling = textToFloat(getParameter(argc, argv, "--maximum_angular_sampling", "0")); - asymmetric_padding = parser.checkOption("--asymmetric_padding", "", "false", true); - skip_gridding = !parser.checkOption("--dont_skip_gridding", "Perform gridding in the reconstruction step (obsolete?)"); - nr_iter_max = textToInteger(parser.getOption("--auto_iter_max", "In auto-refinement, stop at this iteration.", "999")); - debug_split_random_half = textToInteger(getParameter(argc, argv, "--debug_split_random_half", "0")); + int expert_section = parser.addSection("Expert options"); + + fnt = parser.getOption("--strict_highres_exp", "Resolution limit (in Angstrom) to restrict probability calculations in the expectation step", "OLD"); + if (fnt != "OLD") + strict_highres_exp = textToFloat(fnt); + + do_trust_ref_size = parser.checkOption("--trust_ref_size", "Trust the pixel and box size of the input reference; by default the program will die if these are different from the first optics group of the data"); + + // Debugging/analysis/hidden stuff + do_map = !checkParameter(argc, argv, "--no_map"); + minres_map = textToInteger(getParameter(argc, argv, "--minres_map", "5")); + abort_at_resolution = textToFloat(parser.getOption("--abort_at_resolution", "Abort when resolution reaches beyond this value", "-1", true)); + gridding_nr_iter = textToInteger(getParameter(argc, argv, "--gridding_iter", "10")); + debug1 = textToFloat(getParameter(argc, argv, "--debug1", "0.")); + debug2 = textToFloat(getParameter(argc, argv, "--debug2", "0.")); + debug3 = textToFloat(getParameter(argc, argv, "--debug3", "0.")); + do_bfactor = checkParameter(argc, argv, "--bfactor"); + // Read in initial sigmaNoise spectrum + fn_sigma = getParameter(argc, argv, "--sigma",""); + sigma2_fudge = textToFloat(getParameter(argc, argv, "--sigma2_fudge", "1.")); + do_acc_currentsize_despite_highres_exp = checkParameter(argc, argv, "--accuracy_current_size"); + do_sequential_halves_recons = checkParameter(argc, argv, "--sequential_halves_recons"); + do_always_join_random_halves = checkParameter(argc, argv, "--always_join_random_halves"); + do_use_all_data = checkParameter(argc, argv, "--use_all_data"); + do_always_cc = checkParameter(argc, argv, "--always_cc"); + do_only_sample_tilt = checkParameter(argc, argv, "--only_sample_tilt"); + minimum_angular_sampling = textToFloat(getParameter(argc, argv, "--minimum_angular_sampling", "0")); + maximum_angular_sampling = textToFloat(getParameter(argc, argv, "--maximum_angular_sampling", "0")); + asymmetric_padding = parser.checkOption("--asymmetric_padding", "", "false", true); + skip_gridding = !parser.checkOption("--dont_skip_gridding", "Perform gridding in the reconstruction step (obsolete?)"); + nr_iter_max = textToInteger(parser.getOption("--auto_iter_max", "In auto-refinement, stop at this iteration.", "999")); + debug_split_random_half = textToInteger(getParameter(argc, argv, "--debug_split_random_half", "0")); skip_realspace_helical_sym = parser.checkOption("--skip_realspace_helical_sym", "", "false", true); + + do_blush = parser.checkOption("--blush", "Perform the reconstruction with the Blush algorithm."); + if (parser.checkOption("--blush_skip_spectral_trailing", "Skip spectral trailing during Blush reconstruction (WARNING: This could potentially lead to an exaggeration of resolution estimates.)")) + blush_args += " --skip-spectral-trailing "; + do_external_reconstruct = parser.checkOption("--external_reconstruct", "Perform the reconstruction step outside relion_refine, e.g. for learned priors?)"); - // We read input optimiser set to create the output one - fn_OS = parser.getOption("--ios", "Input tomo optimiser set file. It is used to set --i, --ref or --solvent_mask if they are not provided. Updated output optimiser set is created.", ""); - if (fn_OS != "") - { - optimisationSet.read(fn_OS); - } + min_sigma2_offset = textToFloat(parser.getOption("--min_sigma2_offset", "Lower bound for sigma2 for offset", "2.", true)); + + // We read input optimiser set to create the output one + fn_OS = parser.getOption("--ios", "Input tomo optimiser set file. It is used to set --i, --ref or --solvent_mask if they are not provided. Updated output optimiser set is created.", ""); + if (fn_OS != "") + { + optimisationSet.read(fn_OS); + } - do_print_metadata_labels = false; - do_print_symmetry_ops = false; + do_print_metadata_labels = false; + do_print_symmetry_ops = false; #ifdef DEBUG - std::cerr << "Leaving parseContinue" << std::endl; + std::cerr << "Leaving parseContinue" << std::endl; #endif } @@ -535,408 +608,477 @@ void MlOptimiser::parseInitial(int argc, char **argv) std::cerr<<"MlOptimiser::parseInitial Entering "< 0 ? tau2_fudge_arg : 1; - tau2_fudge_scheme = parser.getOption("--tau2_fudge_scheme", "Tau2 fudge factor updates scheme. Valid values are plain or -step. Where is the deflate factor during initial stage.",""); - mymodel.nr_classes = textToInteger(parser.getOption("--K", "Number of references to be refined", "1")); - particle_diameter = textToFloat(parser.getOption("--particle_diameter", "Diameter of the circular mask that will be applied to the experimental images (in Angstroms)", "-1")); - do_zero_mask = parser.checkOption("--zero_mask","Mask surrounding background in particles to zero (by default the solvent area is filled with random noise)"); - do_solvent = parser.checkOption("--flatten_solvent", "Perform masking on the references as well?"); - fn_mask = parser.getOption("--solvent_mask", "User-provided mask for the references (default is to use spherical mask with particle_diameter)", "None"); - fn_mask2 = parser.getOption("--solvent_mask2", "User-provided secondary mask (with its own average density)", "None"); - fn_lowpass_mask = parser.getOption("--lowpass_mask", "User-provided mask for low-pass filtering", "None"); - lowpass = textToFloat(parser.getOption("--lowpass", "User-provided cutoff for region specified above", "0")); - fn_tau = parser.getOption("--tau", "STAR file with input tau2-spectrum (to be kept constant)", "None"); - fn_local_symmetry = parser.getOption("--local_symmetry", "Local symmetry description file containing list of masks and their operators", "None"); - do_split_random_halves = parser.checkOption("--split_random_halves", "Refine two random halves of the data completely separately"); - low_resol_join_halves = textToFloat(parser.getOption("--low_resol_join_halves", "Resolution (in Angstrom) up to which the two random half-reconstructions will not be independent to prevent diverging orientations","-1")); - do_center_classes = parser.checkOption("--center_classes", "Re-center classes based on their center-of-mass?"); - - // Initialisation - int init_section = parser.addSection("Initialisation"); - fn_ref = parser.getOption("--ref", "Image, stack or star-file with the reference(s). (Compulsory for 3D refinement!)", "None"); - is_3d_model = parser.checkOption("--denovo_3dref", "Make an initial 3D model from randomly oriented 2D particles"); - mymodel.sigma2_offset = textToFloat(parser.getOption("--offset", "Initial estimated stddev for the origin offsets (in Angstroms)", "10")); - mymodel.sigma2_offset *= mymodel.sigma2_offset; - - // If tomo optimiser set is passed, we use it to initialise data, reference and mask - if (fn_OS != "") - { - optimisationSet.read(fn_OS); + tau2_fudge_scheme = parser.getOption("--tau2_fudge_scheme", "Tau2 fudge factor updates scheme. Valid values are plain or -step. Where is the deflate factor during initial stage.",""); + mymodel.nr_classes = textToInteger(parser.getOption("--K", "Number of references to be refined", "1")); + particle_diameter = textToFloat(parser.getOption("--particle_diameter", "Diameter of the circular mask that will be applied to the experimental images (in Angstroms)", "-1")); + do_zero_mask = parser.checkOption("--zero_mask","Mask surrounding background in particles to zero (by default the solvent area is filled with random noise)"); + do_solvent = parser.checkOption("--flatten_solvent", "Perform masking on the references as well?"); + fn_mask = parser.getOption("--solvent_mask", "User-provided mask for the references (default is to use spherical mask with particle_diameter)", "None"); + fn_mask2 = parser.getOption("--solvent_mask2", "User-provided secondary mask (with its own average density)", "None"); + fn_lowpass_mask = parser.getOption("--lowpass_mask", "User-provided mask for low-pass filtering", "None"); + lowpass = textToFloat(parser.getOption("--lowpass", "User-provided cutoff for region specified above", "0")); + fn_tau = parser.getOption("--tau", "STAR file with input tau2-spectrum (to be kept constant)", "None"); + fn_local_symmetry = parser.getOption("--local_symmetry", "Local symmetry description file containing list of masks and their operators", "None"); + do_split_random_halves = parser.checkOption("--split_random_halves", "Refine two random halves of the data completely separately"); + low_resol_join_halves = textToFloat(parser.getOption("--low_resol_join_halves", "Resolution (in Angstrom) up to which the two random half-reconstructions will not be independent to prevent diverging orientations","-1")); + do_center_classes = parser.checkOption("--center_classes", "Re-center classes based on their center-of-mass?"); + + // Initialisation + int init_section = parser.addSection("Initialisation"); + fn_ref = parser.getOption("--ref", "Image, stack or star-file with the reference(s). (Compulsory for 3D refinement!)", "None"); + is_3d_model = parser.checkOption("--denovo_3dref", "Make an initial 3D model from randomly oriented 2D particles"); + mymodel.sigma2_offset = textToFloat(parser.getOption("--offset", "Initial estimated stddev for the origin offsets (in Angstroms)", "10")); + mymodel.sigma2_offset *= mymodel.sigma2_offset; + + // If tomo optimiser set is passed, we use it to initialise data, reference and mask + if (fn_OS != "") + { + optimisationSet.read(fn_OS); - if (fn_data == "") - { - if (!optimisationSet.getValue(EMDL_TOMO_PARTICLES_FILE_NAME, fn_data)) - REPORT_ERROR("No particles filename was found in file " + fn_OS); - } - if (fn_ref == "None") - { - if (!optimisationSet.getValue(EMDL_TOMO_REFERENCE_MAP_1_FILE_NAME, fn_ref)) - REPORT_ERROR("No reference half map filenames were found in file " + fn_OS); - } - if (fn_mask == "None") - { - if (!optimisationSet.getValue(EMDL_TOMO_REFERENCE_MASK_FILE_NAME, fn_mask)) - std::cout << " WARNING: No reference mask filename was found in file " + fn_OS + ". Continuing without mask." << std::endl; - } - } + if (fn_data == "") + { + if (!optimisationSet.getValue(EMDL_TOMO_PARTICLES_FILE_NAME, fn_data)) + REPORT_ERROR("No particles filename was found in file " + fn_OS); + } + if (fn_tomo == "") + { + if (!optimisationSet.getValue(EMDL_TOMO_TOMOGRAMS_FILE_NAME, fn_tomo)) + REPORT_ERROR("No tomograms filename was found in file " + fn_OS); + } + if (fn_motion == "") + { + if (!optimisationSet.getValue(EMDL_TOMO_TRAJECTORIES_FILE_NAME, fn_motion)) + std::cout << " No motion trajectories were found in file " + fn_OS + ". Continuing without mask." << std::endl; + } + if (fn_mask == "None") + { + if (!optimisationSet.getValue(EMDL_TOMO_REFERENCE_MASK_FILE_NAME, fn_mask)) + std::cout << " WARNING: No reference mask filename was found in file " + fn_OS + ". Continuing without mask." << std::endl; + } + } - // Perform cross-product comparison at first iteration - do_firstiter_cc = parser.checkOption("--firstiter_cc", "Perform CC-calculation in the first iteration (use this if references are not on the absolute intensity scale)"); - ini_high = textToFloat(parser.getOption("--ini_high", "Resolution (in Angstroms) to which to limit refinement in the first iteration ", "-1")); + // Perform cross-product comparison at first iteration + do_firstiter_cc = parser.checkOption("--firstiter_cc", "Perform CC-calculation in the first iteration (use this if references are not on the absolute intensity scale)"); + ini_high = textToFloat(parser.getOption("--ini_high", "Resolution (in Angstroms) to which to limit refinement in the first iteration ", "-1")); - // Set the orientations - int orientations_section = parser.addSection("Orientations"); - // Move these to sampling - adaptive_oversampling = textToInteger(parser.getOption("--oversampling", "Adaptive oversampling order to speed-up calculations (0=no oversampling, 1=2x, 2=4x, etc)", "1")); - sampling.healpix_order = textToInteger(parser.getOption("--healpix_order", "Healpix order for the angular sampling (before oversampling) on the (3D) sphere: hp2=15deg, hp3=7.5deg, etc", "2")); - sampling.psi_step = textToFloat(parser.getOption("--psi_step", "Sampling rate (before oversampling) for the in-plane angle (default=10deg for 2D, hp sampling for 3D)", "-1")); - sampling.limit_tilt = textToFloat(parser.getOption("--limit_tilt", "Limited tilt angle: positive for keeping side views, negative for keeping top views", "-91")); + // Set the orientations + int orientations_section = parser.addSection("Orientations"); + // Move these to sampling + adaptive_oversampling = textToInteger(parser.getOption("--oversampling", "Adaptive oversampling order to speed-up calculations (0=no oversampling, 1=2x, 2=4x, etc)", "1")); + sampling.healpix_order = textToInteger(parser.getOption("--healpix_order", "Healpix order for the angular sampling (before oversampling) on the (3D) sphere: hp2=15deg, hp3=7.5deg, etc", "2")); + sampling.psi_step = textToFloat(parser.getOption("--psi_step", "Sampling rate (before oversampling) for the in-plane angle (default=10deg for 2D, hp sampling for 3D)", "-1")); + sampling.limit_tilt = textToFloat(parser.getOption("--limit_tilt", "Limited tilt angle: positive for keeping side views, negative for keeping top views", "-91")); - std::string sym_ = parser.getOption("--sym", "Symmetry group", "c1"); + std::string sym_ = parser.getOption("--sym", "Symmetry group", "c1"); - //Check if a comma separated list was provided - if (sym_.find(",") != std::string::npos) - { - std::stringstream ss(sym_); - std::string item; - while (std::getline(ss, item, ',')) - fn_multi_sym.push_back(item); - } - else - sampling.fn_sym = sym_; - - //Check for relax_symmetry option - std::string sym_relax_ = parser.getOption("--relax_sym", "Symmetry to be relaxed", ""); - sampling.fn_sym_relax = sym_relax_; - - sampling.offset_range = textToFloat(parser.getOption("--offset_range", "Search range for origin offsets (in pixels)", "6")); - sampling.offset_step = textToFloat(parser.getOption("--offset_step", "Sampling rate (before oversampling) for origin offsets (in pixels)", "2")); - // Jun19,2015 - Shaoda, Helical refinement - sampling.helical_offset_step = textToFloat(parser.getOption("--helical_offset_step", "Sampling rate (before oversampling) for offsets along helical axis (in Angstroms)", "-1")); - sampling.perturbation_factor = textToFloat(parser.getOption("--perturb", "Perturbation factor for the angular sampling (0=no perturb; 0.5=perturb)", "0.5")); - do_auto_refine = parser.checkOption("--auto_refine", "Perform 3D auto-refine procedure?"); - do_auto_sampling = parser.checkOption("--auto_sampling", "Perform auto-sampling (outside the 3D auto-refine procedure)?"); - autosampling_hporder_local_searches = textToInteger(parser.getOption("--auto_local_healpix_order", "Minimum healpix order (before oversampling) from which autosampling procedure will use local searches", "4")); - parser.setSection(orientations_section); - RFLOAT _sigma_ang = textToFloat(parser.getOption("--sigma_ang", "Stddev on all three Euler angles for local angular searches (of +/- 3 stddev)", "-1")); - RFLOAT _sigma_rot = textToFloat(parser.getOption("--sigma_rot", "Stddev on the first Euler angle for local angular searches (of +/- 3 stddev)", "-1")); - RFLOAT _sigma_tilt = textToFloat(parser.getOption("--sigma_tilt", "Stddev on the second Euler angle for local angular searches (of +/- 3 stddev)", "-1")); - RFLOAT _sigma_psi = textToFloat(parser.getOption("--sigma_psi", "Stddev on the in-plane angle for local angular searches (of +/- 3 stddev)", "-1")); - - if (_sigma_ang > 0.) - { - mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; - // the sigma-values for the orientational prior are in model (and not in sampling) because one might like to estimate them - // from the data by calculating weighted sums of all angular differences: therefore it needs to be in wsum_model and thus in mymodel. - mymodel.sigma2_rot = mymodel.sigma2_tilt = mymodel.sigma2_psi = _sigma_ang * _sigma_ang; - } - else if (_sigma_rot > 0. || _sigma_tilt > 0. || _sigma_psi > 0.) - { - mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; - mymodel.sigma2_rot = (_sigma_rot > 0. ) ? _sigma_rot * _sigma_rot : 0.; - mymodel.sigma2_tilt = (_sigma_tilt > 0.) ? _sigma_tilt * _sigma_tilt : 0.; - mymodel.sigma2_psi = (_sigma_psi > 0. ) ? _sigma_psi * _sigma_psi : 0.; - } - else - { - //default - // Very small to force the algorithm to take the current orientation - if (sym_relax_ != "") - { - mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; - _sigma_ang = 0.0033; - mymodel.sigma2_rot = mymodel.sigma2_tilt = mymodel.sigma2_psi = _sigma_ang * _sigma_ang; - } - else - { - mymodel.orientational_prior_mode = NOPRIOR; - mymodel.sigma2_rot = mymodel.sigma2_tilt = mymodel.sigma2_psi = 0.; - } - } - do_skip_align = parser.checkOption("--skip_align", "Skip orientational assignment (only classify)?"); - do_skip_rotate = parser.checkOption("--skip_rotate", "Skip rotational assignment (only translate and classify)?"); - do_bimodal_psi = parser.checkOption("--bimodal_psi", "Do bimodal searches of psi angle?"); // Oct07,2015 - Shaoda, bimodal psi - do_skip_maximization = false; - - // Helical reconstruction - int helical_section = parser.addSection("Helical reconstruction (in development...)"); - do_helical_refine = parser.checkOption("--helix", "Perform 3D classification or refinement for helices?"); - ignore_helical_symmetry = parser.checkOption("--ignore_helical_symmetry", "Ignore helical symmetry?"); - mymodel.helical_nr_asu = textToInteger(parser.getOption("--helical_nr_asu", "Number of new helical asymmetric units (asu) per box (1 means no helical symmetry is present)", "1")); - helical_twist_initial = textToFloat(parser.getOption("--helical_twist_initial", "Helical twist (in degrees, positive values for right-handedness)", "0.")); - mymodel.helical_twist_min = textToFloat(parser.getOption("--helical_twist_min", "Minimum helical twist (in degrees, positive values for right-handedness)", "0.")); - mymodel.helical_twist_max = textToFloat(parser.getOption("--helical_twist_max", "Maximum helical twist (in degrees, positive values for right-handedness)", "0.")); - mymodel.helical_twist_inistep = textToFloat(parser.getOption("--helical_twist_inistep", "Initial step of helical twist search (in degrees)", "0.")); - helical_rise_initial = textToFloat(parser.getOption("--helical_rise_initial", "Helical rise (in Angstroms)", "0.")); - mymodel.helical_rise_min = textToFloat(parser.getOption("--helical_rise_min", "Minimum helical rise (in Angstroms)", "0.")); - mymodel.helical_rise_max = textToFloat(parser.getOption("--helical_rise_max", "Maximum helical rise (in Angstroms)", "0.")); - mymodel.helical_rise_inistep = textToFloat(parser.getOption("--helical_rise_inistep", "Initial step of helical rise search (in Angstroms)", "0.")); - helical_nstart = textToInteger(parser.getOption("--helical_nstart", "N-number for the N-start helix (only useful for rotational priors)", "1")); - helical_z_percentage = textToFloat(parser.getOption("--helical_z_percentage", "This box length along the center of Z axis contains good information of the helix. Important in imposing and refining symmetry", "0.3")); - helical_tube_inner_diameter = textToFloat(parser.getOption("--helical_inner_diameter", "Inner diameter of helical tubes in Angstroms (for masks of helical references and particles)", "-1.")); - helical_tube_outer_diameter = textToFloat(parser.getOption("--helical_outer_diameter", "Outer diameter of helical tubes in Angstroms (for masks of helical references and particles)", "-1.")); - do_helical_symmetry_local_refinement = parser.checkOption("--helical_symmetry_search", "Perform local refinement of helical symmetry?"); - helical_sigma_distance = textToFloat(parser.getOption("--helical_sigma_distance", "Sigma of distance along the helical tracks", "-1.")); - helical_keep_tilt_prior_fixed = parser.checkOption("--helical_keep_tilt_prior_fixed", "Keep helical tilt priors fixed (at 90 degrees) in global angular searches?"); - if (ignore_helical_symmetry) - { - mymodel.helical_nr_asu = 1; // IMPORTANT ! - do_helical_symmetry_local_refinement = false; - helical_twist_initial = mymodel.helical_twist_min = mymodel.helical_twist_max = mymodel.helical_twist_inistep = 0.; - helical_rise_initial = mymodel.helical_rise_min = mymodel.helical_rise_max = mymodel.helical_rise_inistep = 0.; - helical_z_percentage = 0.; - } - mymodel.initialiseHelicalParametersLists(helical_twist_initial, helical_rise_initial); - mymodel.is_helix = do_helical_refine; - RFLOAT tmp_RFLOAT = 0.; - if (mymodel.helical_rise_min > mymodel.helical_rise_max) - SWAP(mymodel.helical_rise_min, mymodel.helical_rise_max, tmp_RFLOAT); - if (mymodel.helical_twist_min > mymodel.helical_twist_max) - SWAP(mymodel.helical_twist_min, mymodel.helical_twist_max, tmp_RFLOAT); - helical_fourier_mask_resols = parser.getOption("--helical_exclude_resols", "Resolutions (in A) along helical axis to exclude from refinement (comma-separated pairs, e.g. 50,5)", ""); - fn_fourier_mask = parser.getOption("--fourier_mask", "Originally-sized, FFTW-centred image with Fourier mask for Projector", "None"); - - // CTF, norm, scale, bfactor correction etc. - int corrections_section = parser.addSection("Corrections"); - do_ctf_correction = parser.checkOption("--ctf", "Perform CTF correction?"); - do_ctf_padding = parser.checkOption("--pad_ctf", "Perform CTF padding to treat CTF aliaising better?"); - if (do_ctf_padding) - REPORT_ERROR("--pad_ctf currently disabled."); - intact_ctf_first_peak = parser.checkOption("--ctf_intact_first_peak", "Ignore CTFs until their first peak?"); - refs_are_ctf_corrected = !parser.checkOption("--ctf_uncorrected_ref", "Have the input references not been CTF-amplitude corrected?"); - if (checkParameter(argc, argv, "--ctf_corrected_ref")) - std::cerr << "Warning: the option --ctf_corrected_ref has been removed. By default, this is assumed to be true. If the reference is not CTF corrected, use --ctf_uncorrected_ref.\n" << std::endl; - - ctf_phase_flipped = parser.checkOption("--ctf_phase_flipped", "Have the data been CTF phase-flipped?"); - only_flip_phases = parser.checkOption("--only_flip_phases", "Only perform CTF phase-flipping? (default is full amplitude-correction)"); - do_norm_correction = parser.checkOption("--norm", "Perform normalisation-error correction?"); - do_scale_correction = parser.checkOption("--scale", "Perform intensity-scale corrections on image groups?"); - // Allow switching off norm and scale (which is on by default in the GUI) - if (parser.checkOption("--no_norm", "Switch off normalisation-error correction?")) - do_norm_correction = false; - if (parser.checkOption("--no_scale", "Switch off intensity-scale corrections on image groups?")) - do_scale_correction = false; - - // SGD stuff - int grad_section = parser.addSection("Stochastic Gradient Descent"); - gradient_refine = parser.checkOption("--grad", "Perform gradient based optimisation (instead of default expectation-maximization)"); - grad_em_iters = textToInteger(parser.getOption("--grad_em_iters", "Number of iterations at the end of a gradient refinement using Expectation-Maximization", "0")); - // Stochastic EM is implemented as a variant of SGD, though it is really a different algorithm! - - grad_ini_frac = textToFloat(parser.getOption("--grad_ini_frac", "Fraction of iterations in the initial phase of refinement", "0.3")); - grad_fin_frac = textToFloat(parser.getOption("--grad_fin_frac", "Fraction of iterations in the final phase of refinement", "0.2")); - - if (grad_ini_frac <= 0 || 1 <= grad_ini_frac) - REPORT_ERROR("Invalid value for --grad_ini_frac."); - if (grad_fin_frac <= 0 || 1 <= grad_fin_frac) - REPORT_ERROR("Invalid value for --grad_fin_frac."); - - if (grad_ini_frac + grad_fin_frac > 0.9) { - RFLOAT sum = grad_ini_frac + grad_fin_frac + 0.1; - grad_ini_frac /= sum; - grad_fin_frac /= sum; - } - grad_ini_iter = nr_iter * grad_ini_frac; - grad_fin_iter = nr_iter * grad_fin_frac; - grad_inbetween_iter = nr_iter - grad_ini_iter - grad_fin_iter; - if (grad_inbetween_iter < 0) - grad_inbetween_iter = 0; - - grad_min_resol = textToInteger(parser.getOption("--grad_min_resol", "Adjusting the signal under-estimation during gradient optimization to this resolution.", "20")); - grad_ini_resol = textToInteger(parser.getOption("--grad_ini_resol", "Resolution cutoff during the initial gradient refinement iterations (A)", "-1")); - grad_fin_resol = textToInteger(parser.getOption("--grad_fin_resol", "Resolution cutoff during the final gradient refinement iterations (A)", "-1")); - grad_ini_subset_size = textToInteger(parser.getOption("--grad_ini_subset", "Mini-batch size during the initial gradient refinement iterations", "-1")); - grad_fin_subset_size = textToInteger(parser.getOption("--grad_fin_subset", "Mini-batch size during the final gradient refinement iterations", "-1")); - mu = textToFloat(parser.getOption("--mu", "Momentum parameter for gradient refinement updates", "0.9")); - - grad_stepsize = textToFloat(parser.getOption("--grad_stepsize", "Step size parameter for gradient optimisation.", "-1")); - grad_stepsize_scheme = parser.getOption("--grad_stepsize_scheme", - "Gradient step size updates scheme. Valid values are plain or -step . Where is the initial inflate.",""); - - write_every_grad_iter = textToInteger(parser.getOption("--grad_write_iter", "Write out model every so many iterations in SGD (default is writing out all iters)", "10")); - maximum_significants_arg = textToInteger(parser.getOption("--maxsig", "Maximum number of most significant poses & translations to consider", "-1")); - do_init_blobs = !parser.checkOption("--no_init_blobs", "Use this to switch off initializing models with random Gaussians (which is new in relion-4.0)."); - do_som = parser.checkOption("--som", "Calculate self-organizing map instead of classification."); - som_starting_nodes = textToInteger(parser.getOption("--som_ini_nodes", "Number of initial SOM nodes.", "2")); - som_connectivity = textToFloat(parser.getOption("--som_connectivity", "Number of average active neighbour connections.", "5.0")); - som_inactivity_threshold = textToFloat(parser.getOption("--som_inactivity_threshold", "Threshold for inactivity before node is dropped.", "0.01")); - som_neighbour_pull = textToFloat(parser.getOption("--som_neighbour_pull", "Portion of gradient applied to connected nodes.", "0.2")); - class_inactivity_threshold = textToFloat(parser.getOption("--class_inactivity_threshold", "Replace classes with little activity during gradient based classification.", "0")); - - if (do_som && !gradient_refine) - REPORT_ERROR("SOM can only be calculated with a gradient optimization."); - - if (do_som && mymodel.nr_classes < 3) - REPORT_ERROR("Too low maximum class limit for SOM calculations."); - - if (do_som && som_starting_nodes > mymodel.nr_classes) - REPORT_ERROR("Cannot initiate more nodes than maximum number of nodes."); - - if (som_neighbour_pull < 0 || 1 <= som_neighbour_pull) - REPORT_ERROR("--som_neighbour_pull should be more then or equal to zero and less than one."); - - // Subtomo Avg stuff - int subtomogram_section = parser.addSection("Subtomogram averaging"); - normalised_subtomos = parser.checkOption("--normalised_subtomo", "Have subtomograms been multiplicity normalised? (Default=False)"); - do_skip_subtomo_correction = parser.checkOption("--skip_subtomo_multi", "Skip subtomo multiplicity correction"); - ctf3d_squared = !parser.checkOption("--ctf3d_not_squared", "CTF3D files contain sqrt(CTF^2) patterns"); - subtomo_multi_thr = textToFloat(parser.getOption("--subtomo_multi_thr", "Threshold to remove marginal voxels during expectation", "0.01")); - - // Computation stuff - // The number of threads is always read from the command line - int computation_section = parser.addSection("Computation"); - x_pool = textToInteger(parser.getOption("--pool", "Number of images to pool for each thread task", "1")); - nr_threads = textToInteger(parser.getOption("--j", "Number of threads to run in parallel (only useful on multi-core machines)", "1")); - combine_weights_thru_disc = !parser.checkOption("--dont_combine_weights_via_disc", "Send the large arrays of summed weights through the MPI network, instead of writing large files to disc"); - do_shifts_onthefly = parser.checkOption("--onthefly_shifts", "Calculate shifted images on-the-fly, do not store precalculated ones in memory"); - do_parallel_disc_io = !parser.checkOption("--no_parallel_disc_io", "Do NOT let parallel (MPI) processes access the disc simultaneously (use this option with NFS)"); - do_preread_images = parser.checkOption("--preread_images", "Use this to let the leader process read all particles into memory. Be careful you have enough RAM for large data sets!"); - fn_scratch = parser.getOption("--scratch_dir", "If provided, particle stacks will be copied to this local scratch disk prior to refinement.", ""); - keep_free_scratch_Gb = textToFloat(parser.getOption("--keep_free_scratch", "Space available for copying particle stacks (in Gb)", "10")); - do_reuse_scratch = parser.checkOption("--reuse_scratch", "Re-use data on scratchdir, instead of wiping it and re-copying all data."); - keep_scratch = parser.checkOption("--keep_scratch", "Don't remove scratch after convergence. Following jobs that use EXACTLY the same particles should use --reuse_scratch."); - do_fast_subsets = parser.checkOption("--fast_subsets", "Use faster optimisation by using subsets of the data in the first 15 iterations"); + //Check if a comma separated list was provided + if (sym_.find(",") != std::string::npos) + { + std::stringstream ss(sym_); + std::string item; + while (std::getline(ss, item, ',')) + fn_multi_sym.push_back(item); + } + else + sampling.fn_sym = sym_; + + //Check for relax_symmetry option + std::string sym_relax_ = parser.getOption("--relax_sym", "Symmetry to be relaxed", ""); + sampling.fn_sym_relax = sym_relax_; + + sampling.offset_range = textToFloat(parser.getOption("--offset_range", "Search range for origin offsets (in pixels)", "6")); + sampling.offset_step = textToFloat(parser.getOption("--offset_step", "Sampling rate (before oversampling) for origin offsets (in pixels)", "2")); + + // SHWS 25apr2023 for subtomogram averaging + offset_range_x = textToFloat(parser.getOption("--offset_range_x", "Range for sampling offsets in X-direction (in Angstrom; default=auto)", "-1")); + offset_range_y = textToFloat(parser.getOption("--offset_range_y", "Range for sampling offsets in X-direction (in Angstrom; default=auto)", "-1")); + offset_range_z = textToFloat(parser.getOption("--offset_range_z", "Range for sampling offsets in X-direction (in Angstrom; default=auto)", "-1")); + + // Jun19,2015 - Shaoda, Helical refinement + sampling.helical_offset_step = textToFloat(parser.getOption("--helical_offset_step", "Sampling rate (before oversampling) for offsets along helical axis (in Angstroms)", "-1")); + sampling.perturbation_factor = textToFloat(parser.getOption("--perturb", "Perturbation factor for the angular sampling (0=no perturb; 0.5=perturb)", "0.5")); + do_auto_refine = parser.checkOption("--auto_refine", "Perform 3D auto-refine procedure?"); + do_auto_sampling = parser.checkOption("--auto_sampling", "Perform auto-sampling (outside the 3D auto-refine procedure)?"); + autosampling_hporder_local_searches = textToInteger(parser.getOption("--auto_local_healpix_order", "Minimum healpix order (before oversampling) from which autosampling procedure will use local searches", "4")); + parser.setSection(orientations_section); + RFLOAT _sigma_ang = textToFloat(parser.getOption("--sigma_ang", "Stddev on all three Euler angles for local angular searches (of +/- 3 stddev)", "-1")); + RFLOAT _sigma_rot = textToFloat(parser.getOption("--sigma_rot", "Stddev on the first Euler angle for local angular searches (of +/- 3 stddev)", "-1")); + RFLOAT _sigma_tilt = textToFloat(parser.getOption("--sigma_tilt", "Stddev on the second Euler angle for local angular searches (of +/- 3 stddev)", "-1")); + RFLOAT _sigma_psi = textToFloat(parser.getOption("--sigma_psi", "Stddev on the in-plane angle for local angular searches (of +/- 3 stddev)", "-1")); + + if (_sigma_ang > 0.) + { + mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; + // the sigma-values for the orientational prior are in model (and not in sampling) because one might like to estimate them + // from the data by calculating weighted sums of all angular differences: therefore it needs to be in wsum_model and thus in mymodel. + mymodel.sigma2_rot = mymodel.sigma2_tilt = mymodel.sigma2_psi = _sigma_ang * _sigma_ang; + } + else if (_sigma_rot > 0. || _sigma_tilt > 0. || _sigma_psi > 0.) + { + mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; + mymodel.sigma2_rot = (_sigma_rot > 0. ) ? _sigma_rot * _sigma_rot : 0.; + mymodel.sigma2_tilt = (_sigma_tilt > 0.) ? _sigma_tilt * _sigma_tilt : 0.; + mymodel.sigma2_psi = (_sigma_psi > 0. ) ? _sigma_psi * _sigma_psi : 0.; + } + else + { + //default + // Very small to force the algorithm to take the current orientation + if (sym_relax_ != "") + { + mymodel.orientational_prior_mode = PRIOR_ROTTILT_PSI; + _sigma_ang = 0.0033; + mymodel.sigma2_rot = mymodel.sigma2_tilt = mymodel.sigma2_psi = _sigma_ang * _sigma_ang; + } + else + { + mymodel.orientational_prior_mode = NOPRIOR; + mymodel.sigma2_rot = mymodel.sigma2_tilt = mymodel.sigma2_psi = 0.; + } + } + do_skip_align = parser.checkOption("--skip_align", "Skip orientational assignment (only classify)?"); + do_skip_rotate = parser.checkOption("--skip_rotate", "Skip rotational assignment (only translate and classify)?"); + do_bimodal_psi = parser.checkOption("--bimodal_psi", "Do bimodal searches of psi angle?"); // Oct07,2015 - Shaoda, bimodal psi + do_skip_maximization = false; + + // Helical reconstruction + int helical_section = parser.addSection("Helical reconstruction (in development...)"); + do_helical_refine = parser.checkOption("--helix", "Perform 3D classification or refinement for helices?"); + ignore_helical_symmetry = parser.checkOption("--ignore_helical_symmetry", "Ignore helical symmetry?"); + mymodel.helical_nr_asu = textToInteger(parser.getOption("--helical_nr_asu", "Number of new helical asymmetric units (asu) per box (1 means no helical symmetry is present)", "1")); + helical_twist_initial = textToFloat(parser.getOption("--helical_twist_initial", "Helical twist (in degrees, positive values for right-handedness)", "0.")); + mymodel.helical_twist_min = textToFloat(parser.getOption("--helical_twist_min", "Minimum helical twist (in degrees, positive values for right-handedness)", "0.")); + mymodel.helical_twist_max = textToFloat(parser.getOption("--helical_twist_max", "Maximum helical twist (in degrees, positive values for right-handedness)", "0.")); + mymodel.helical_twist_inistep = textToFloat(parser.getOption("--helical_twist_inistep", "Initial step of helical twist search (in degrees)", "0.")); + helical_rise_initial = textToFloat(parser.getOption("--helical_rise_initial", "Helical rise (in Angstroms)", "0.")); + mymodel.helical_rise_min = textToFloat(parser.getOption("--helical_rise_min", "Minimum helical rise (in Angstroms)", "0.")); + mymodel.helical_rise_max = textToFloat(parser.getOption("--helical_rise_max", "Maximum helical rise (in Angstroms)", "0.")); + mymodel.helical_rise_inistep = textToFloat(parser.getOption("--helical_rise_inistep", "Initial step of helical rise search (in Angstroms)", "0.")); + helical_nstart = textToInteger(parser.getOption("--helical_nstart", "N-number for the N-start helix (only useful for rotational priors)", "1")); + helical_z_percentage = textToFloat(parser.getOption("--helical_z_percentage", "This box length along the center of Z axis contains good information of the helix. Important in imposing and refining symmetry", "0.3")); + helical_tube_inner_diameter = textToFloat(parser.getOption("--helical_inner_diameter", "Inner diameter of helical tubes in Angstroms (for masks of helical references and particles)", "-1.")); + helical_tube_outer_diameter = textToFloat(parser.getOption("--helical_outer_diameter", "Outer diameter of helical tubes in Angstroms (for masks of helical references and particles)", "-1.")); + do_helical_symmetry_local_refinement = parser.checkOption("--helical_symmetry_search", "Perform local refinement of helical symmetry?"); + helical_sigma_distance = textToFloat(parser.getOption("--helical_sigma_distance", "Sigma of distance along the helical tracks", "-1.")); + helical_keep_tilt_prior_fixed = parser.checkOption("--helical_keep_tilt_prior_fixed", "Keep helical tilt priors fixed (at 90 degrees) in global angular searches?"); + if (ignore_helical_symmetry) + { + mymodel.helical_nr_asu = 1; // IMPORTANT ! + do_helical_symmetry_local_refinement = false; + helical_twist_initial = mymodel.helical_twist_min = mymodel.helical_twist_max = mymodel.helical_twist_inistep = 0.; + helical_rise_initial = mymodel.helical_rise_min = mymodel.helical_rise_max = mymodel.helical_rise_inistep = 0.; + helical_z_percentage = 0.; + } + mymodel.initialiseHelicalParametersLists(helical_twist_initial, helical_rise_initial); + mymodel.is_helix = do_helical_refine; + RFLOAT tmp_RFLOAT = 0.; + if (mymodel.helical_rise_min > mymodel.helical_rise_max) + SWAP(mymodel.helical_rise_min, mymodel.helical_rise_max, tmp_RFLOAT); + if (mymodel.helical_twist_min > mymodel.helical_twist_max) + SWAP(mymodel.helical_twist_min, mymodel.helical_twist_max, tmp_RFLOAT); + helical_fourier_mask_resols = parser.getOption("--helical_exclude_resols", "Resolutions (in A) along helical axis to exclude from refinement (comma-separated pairs, e.g. 50,5)", ""); + fn_fourier_mask = parser.getOption("--fourier_mask", "Originally-sized, FFTW-centred image with Fourier mask for Projector", "None"); + + // CTF, norm, scale, bfactor correction etc. + int corrections_section = parser.addSection("Corrections"); + do_ctf_correction = parser.checkOption("--ctf", "Perform CTF correction?"); + do_ctf_padding = parser.checkOption("--pad_ctf", "Perform CTF padding to treat CTF aliaising better?"); + if (do_ctf_padding) + REPORT_ERROR("--pad_ctf currently disabled."); + intact_ctf_first_peak = parser.checkOption("--ctf_intact_first_peak", "Ignore CTFs until their first peak?"); + refs_are_ctf_corrected = !parser.checkOption("--ctf_uncorrected_ref", "Have the input references not been CTF-amplitude corrected?"); + if (checkParameter(argc, argv, "--ctf_corrected_ref")) + std::cerr << "Warning: the option --ctf_corrected_ref has been removed. By default, this is assumed to be true. If the reference is not CTF corrected, use --ctf_uncorrected_ref.\n" << std::endl; + + ctf_phase_flipped = parser.checkOption("--ctf_phase_flipped", "Have the data been CTF phase-flipped?"); + only_flip_phases = parser.checkOption("--only_flip_phases", "Only perform CTF phase-flipping? (default is full amplitude-correction)"); + do_norm_correction = parser.checkOption("--norm", "Perform normalisation-error correction?"); + do_scale_correction = parser.checkOption("--scale", "Perform intensity-scale corrections on image groups?"); + // Allow switching off norm and scale (which is on by default in the GUI) + if (parser.checkOption("--no_norm", "Switch off normalisation-error correction?")) + do_norm_correction = false; + if (parser.checkOption("--no_scale", "Switch off intensity-scale corrections on image groups?")) + do_scale_correction = false; + + // SGD stuff + int grad_section = parser.addSection("Stochastic Gradient Descent"); + gradient_refine = parser.checkOption("--grad", "Perform gradient based optimisation (instead of default expectation-maximization)"); + grad_em_iters = textToInteger(parser.getOption("--grad_em_iters", "Number of iterations at the end of a gradient refinement using Expectation-Maximization", "0")); + // Stochastic EM is implemented as a variant of SGD, though it is really a different algorithm! + + grad_ini_frac = textToFloat(parser.getOption("--grad_ini_frac", "Fraction of iterations in the initial phase of refinement", "0.3")); + grad_fin_frac = textToFloat(parser.getOption("--grad_fin_frac", "Fraction of iterations in the final phase of refinement", "0.2")); + + if (grad_ini_frac <= 0 || 1 <= grad_ini_frac) + REPORT_ERROR("Invalid value for --grad_ini_frac."); + if (grad_fin_frac <= 0 || 1 <= grad_fin_frac) + REPORT_ERROR("Invalid value for --grad_fin_frac."); + + if (grad_ini_frac + grad_fin_frac > 0.9) { + RFLOAT sum = grad_ini_frac + grad_fin_frac + 0.1; + grad_ini_frac /= sum; + grad_fin_frac /= sum; + } + grad_ini_iter = nr_iter * grad_ini_frac; + grad_fin_iter = nr_iter * grad_fin_frac; + grad_inbetween_iter = nr_iter - grad_ini_iter - grad_fin_iter; + if (grad_inbetween_iter < 0) + grad_inbetween_iter = 0; + + grad_min_resol = textToInteger(parser.getOption("--grad_min_resol", "Adjusting the signal under-estimation during gradient optimization to this resolution.", "20")); + grad_ini_resol = textToInteger(parser.getOption("--grad_ini_resol", "Resolution cutoff during the initial gradient refinement iterations (A)", "-1")); + grad_fin_resol = textToInteger(parser.getOption("--grad_fin_resol", "Resolution cutoff during the final gradient refinement iterations (A)", "-1")); + grad_ini_subset_size = textToInteger(parser.getOption("--grad_ini_subset", "Mini-batch size during the initial gradient refinement iterations", "-1")); + grad_fin_subset_size = textToInteger(parser.getOption("--grad_fin_subset", "Mini-batch size during the final gradient refinement iterations", "-1")); + mu = textToFloat(parser.getOption("--mu", "Momentum parameter for gradient refinement updates", "0.9")); + + grad_stepsize = textToFloat(parser.getOption("--grad_stepsize", "Step size parameter for gradient optimisation.", "-1")); + grad_stepsize_scheme = parser.getOption("--grad_stepsize_scheme", + "Gradient step size updates scheme. Valid values are plain or -step . Where is the initial inflate.",""); + + write_every_grad_iter = textToInteger(parser.getOption("--grad_write_iter", "Write out model every so many iterations in SGD (default is writing out all iters)", "10")); + maximum_significants_arg = textToInteger(parser.getOption("--maxsig", "Maximum number of most significant poses & translations to consider", "-1")); + do_init_blobs = !parser.checkOption("--no_init_blobs", "Use this to switch off initializing models with random Gaussians (which is new in relion-4.0)."); + do_som = parser.checkOption("--som", "Calculate self-organizing map instead of classification."); + som_starting_nodes = textToInteger(parser.getOption("--som_ini_nodes", "Number of initial SOM nodes.", "2")); + som_connectivity = textToFloat(parser.getOption("--som_connectivity", "Number of average active neighbour connections.", "5.0")); + som_inactivity_threshold = textToFloat(parser.getOption("--som_inactivity_threshold", "Threshold for inactivity before node is dropped.", "0.01")); + som_neighbour_pull = textToFloat(parser.getOption("--som_neighbour_pull", "Portion of gradient applied to connected nodes.", "0.2")); + class_inactivity_threshold = textToFloat(parser.getOption("--class_inactivity_threshold", "Replace classes with little activity during gradient based classification.", "0")); + + if (do_som && !gradient_refine) + REPORT_ERROR("SOM can only be calculated with a gradient optimization."); + + if (do_som && mymodel.nr_classes < 3) + REPORT_ERROR("Too low maximum class limit for SOM calculations."); + + if (do_som && som_starting_nodes > mymodel.nr_classes) + REPORT_ERROR("Cannot initiate more nodes than maximum number of nodes."); + + if (som_neighbour_pull < 0 || 1 <= som_neighbour_pull) + REPORT_ERROR("--som_neighbour_pull should be more then or equal to zero and less than one."); + + // Subtomo Avg stuff + int subtomogram_section = parser.addSection("Subtomogram averaging"); + normalised_subtomos = parser.checkOption("--normalised_subtomo", "Have subtomograms been multiplicity normalised? (Default=False)"); + do_skip_subtomo_correction = parser.checkOption("--skip_subtomo_multi", "Skip subtomo multiplicity correction"); + ctf3d_squared = !parser.checkOption("--ctf3d_not_squared", "CTF3D files contain sqrt(CTF^2) patterns"); + subtomo_multi_thr = textToFloat(parser.getOption("--subtomo_multi_thr", "Threshold to remove marginal voxels during expectation", "0.01")); + + // Computation stuff + // The number of threads is always read from the command line + int computation_section = parser.addSection("Computation"); + x_pool = textToInteger(parser.getOption("--pool", "Number of images to pool for each thread task", "1")); + nr_threads = textToInteger(parser.getOption("--j", "Number of threads to run in parallel (only useful on multi-core machines)", "1")); + combine_weights_thru_disc = !parser.checkOption("--dont_combine_weights_via_disc", "Send the large arrays of summed weights through the MPI network, instead of writing large files to disc"); + do_shifts_onthefly = parser.checkOption("--onthefly_shifts", "Calculate shifted images on-the-fly, do not store precalculated ones in memory"); + do_parallel_disc_io = !parser.checkOption("--no_parallel_disc_io", "Do NOT let parallel (MPI) processes access the disc simultaneously (use this option with NFS)"); + do_preread_images = parser.checkOption("--preread_images", "Use this to let the leader process read all particles into memory. Be careful you have enough RAM for large data sets!"); + fn_scratch = parser.getOption("--scratch_dir", "If provided, particle stacks will be copied to this local scratch disk prior to refinement.", ""); + keep_free_scratch_Gb = textToFloat(parser.getOption("--keep_free_scratch", "Space available for copying particle stacks (in Gb)", "10")); + do_reuse_scratch = parser.checkOption("--reuse_scratch", "Re-use data on scratchdir, instead of wiping it and re-copying all data."); + keep_scratch = parser.checkOption("--keep_scratch", "Don't remove scratch after convergence. Following jobs that use EXACTLY the same particles should use --reuse_scratch."); + do_fast_subsets = parser.checkOption("--fast_subsets", "Use faster optimisation by using subsets of the data in the first 15 iterations"); #ifdef ALTCPU - do_cpu = parser.checkOption("--cpu", "Use intel vectorisation implementation for CPU"); + do_cpu = parser.checkOption("--cpu", "Use intel vectorisation implementation for CPU"); #else - do_cpu = false; -#endif + do_cpu = false; +#endif + +#ifdef _SYCL_ENABLED + char* pEnvSyclCuda = std::getenv("relionSyclUseCuda"); + std::string strSyclCuda = (pEnvSyclCuda == nullptr) ? "0" : pEnvSyclCuda; + std::transform(strSyclCuda.begin(), strSyclCuda.end(), strSyclCuda.begin(), [](unsigned char c){return static_cast(std::tolower(c));}); + const bool isSyclCuda = (strSyclCuda == "1" || strSyclCuda == "on") ? true : false; + + char* pEnvSyclHip = std::getenv("relionSyclUseHip"); + std::string strSyclHip = (pEnvSyclHip == nullptr) ? "0" : pEnvSyclHip; + std::transform(strSyclHip.begin(), strSyclHip.end(), strSyclHip.begin(), [](unsigned char c){return static_cast(std::tolower(c));}); + const bool isSyclHip = (strSyclHip == "1" || strSyclHip == "on") ? true : false; + + do_sycl = parser.checkOption("--gpu", "Use available SYCL Level Zero GPU resources for some calculations"); + if(! do_sycl) do_sycl = parser.checkOption("--sycl", "Use available SYCL Level Zero GPU resources for some calculations"); + do_sycl_levelzero = parser.checkOption("--sycl-levelzero", "Use available SYCL Level Zero GPU resources for some calculations"); + do_sycl_cuda = parser.checkOption("--sycl-cuda", "Use available SYCL CUDA GPU resources for some calculations"); + do_sycl_hip = parser.checkOption("--sycl-hip", "Use available SYCL HIP GPU resources for some calculations"); + do_sycl_opencl = parser.checkOption("--sycl-opencl", "Use available SYCL OpenCL GPU resources for some calculations"); + do_sycl_cpu = parser.checkOption("--sycl-cpu", "Use available SYCL OpenCL CPU resources for some calculations"); + + if(do_sycl_levelzero) + gpu_ids = parser.getOption("--sycl-levelzero", "Device ids for each MPI-thread","default"); + else if(do_sycl_cuda) + gpu_ids = parser.getOption("--sycl-cuda", "Device ids for each MPI-thread","default"); + else if(do_sycl_hip) + gpu_ids = parser.getOption("--sycl-hip", "Device ids for each MPI-thread","default"); + else if(do_sycl_opencl) + gpu_ids = parser.getOption("--sycl-opencl", "Device ids for each MPI-thread","default"); + else if(do_sycl_cpu) + gpu_ids = parser.getOption("--sycl-cpu", "Device ids for each MPI-thread","default"); + else if (do_sycl) + { + gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); + if (gpu_ids == "default") + gpu_ids = parser.getOption("--sycl", "Device ids for each MPI-thread","default"); + } + + if (isSyclCuda && do_sycl) + do_sycl_cuda = true; + else if (isSyclHip && do_sycl) + do_sycl_hip = true; + else if (do_sycl) + do_sycl_levelzero = true; + if (do_sycl_levelzero || do_sycl_cuda || do_sycl_hip || do_sycl_opencl || do_sycl_cpu) + do_sycl = true; +#else + do_sycl = false; + do_sycl_levelzero = do_sycl_cuda = do_sycl_hip = do_sycl_opencl = do_sycl_cpu = false; - do_gpu = parser.checkOption("--gpu", "Use available gpu resources for some calculations"); - gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); -#ifndef _CUDA_ENABLED -if(do_gpu) - { - std::cerr << "+ WARNING : Relion was compiled without CUDA of at least version 7.0 - you do NOT have support for GPUs" << std::endl; - do_gpu = false; - } + do_gpu = parser.checkOption("--gpu", "Use available gpu resources for some calculations"); + gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); #endif - double temp_reqSize = textToDouble(parser.getOption("--free_gpu_memory", "GPU device memory (in Mb) to leave free after allocation.", "0")); - if(!do_zero_mask) - temp_reqSize += 100; - temp_reqSize *= 1000*1000; - if(temp_reqSize<0) - REPORT_ERROR("Invalid free_gpu_memory value."); - else - requested_free_gpu_memory = temp_reqSize; - - // Expert options - int expert_section = parser.addSection("Expert options"); - mymodel.padding_factor = textToFloat(parser.getOption("--pad", "Oversampling factor for the Fourier transforms of the references", "2")); - - ref_angpix = textToFloat(parser.getOption("--ref_angpix", "Pixel size (in A) for the input reference (default is to read from header)", "-1.")); - mymodel.interpolator = (parser.checkOption("--NN", "Perform nearest-neighbour instead of linear Fourier-space interpolation?")) ? NEAREST_NEIGHBOUR : TRILINEAR; - mymodel.r_min_nn = textToInteger(parser.getOption("--r_min_nn", "Minimum number of Fourier shells to perform linear Fourier-space interpolation", "10")); - verb = textToInteger(parser.getOption("--verb", "Verbosity (1=normal, 0=silent)", "1")); - random_seed = textToInteger(parser.getOption("--random_seed", "Number for the random seed generator", "-1")); - max_coarse_size = textToInteger(parser.getOption("--coarse_size", "Maximum image size for the first pass of the adaptive sampling approach", "-1")); - adaptive_fraction = textToFloat(parser.getOption("--adaptive_fraction", "Fraction of the weights to be considered in the first pass of adaptive oversampling ", "0.999")); - width_mask_edge = textToInteger(parser.getOption("--maskedge", "Width of the soft edge of the spherical mask (in pixels)", "5")); - // If we're doing helical, and maskedge is not given, use a default maskedge of 10 - if (helical_tube_outer_diameter > 0. && !checkParameter(argc, argv, "--maskedge")) width_mask_edge = 10.; - fix_sigma_noise = parser.checkOption("--fix_sigma_noise", "Fix the experimental noise spectra?"); - fix_sigma_offset = parser.checkOption("--fix_sigma_offset", "Fix the stddev in the origin offsets?"); - incr_size = textToInteger(parser.getOption("--incr_size", "Number of Fourier shells beyond the current resolution to be included in refinement", "10")); - do_print_metadata_labels = parser.checkOption("--print_metadata_labels", "Print a table with definitions of all metadata labels, and exit"); - do_print_symmetry_ops = parser.checkOption("--print_symmetry_ops", "Print all symmetry transformation matrices, and exit"); - strict_highres_exp = textToFloat(parser.getOption("--strict_highres_exp", "High resolution limit (in Angstrom) to restrict probability calculations in the expectation step", "-1")); - strict_lowres_exp = textToFloat(parser.getOption("--strict_lowres_exp", "Low resolution limit (in Angstrom) to restrict probability calculations in the expectation step", "-1")); - dont_raise_norm_error = parser.checkOption("--dont_check_norm", "Skip the check whether the images are normalised correctly"); - do_always_cc = parser.checkOption("--always_cc", "Perform CC-calculation in all iterations (useful for faster denovo model generation?)"); - do_phase_random_fsc = parser.checkOption("--solvent_correct_fsc", "Correct FSC curve for the effects of the solvent mask?"); - do_skip_maximization = parser.checkOption("--skip_maximize", "Skip maximization step (only write out data.star file)?"); - failsafe_threshold = textToInteger(parser.getOption("--failsafe_threshold", "Maximum number of particles permitted to be handled by fail-safe mode, due to zero sum of weights, before exiting with an error (GPU only).", "40")); - do_external_reconstruct = parser.checkOption("--external_reconstruct", "Perform the reconstruction step outside relion_refine, e.g. for learned priors?)"); - nr_iter_max = textToInteger(parser.getOption("--auto_iter_max", "In auto-refinement, stop at this iteration.", "999")); - auto_ignore_angle_changes = parser.checkOption("--auto_ignore_angles", "In auto-refinement, update angular sampling regardless of changes in orientations for convergence. This makes convergence faster."); - auto_resolution_based_angles= parser.checkOption("--auto_resol_angles", "In auto-refinement, update angular sampling based on resolution-based required sampling. This makes convergence faster."); - allow_coarser_samplings = parser.checkOption("--allow_coarser_sampling", "In 2D/3D classification, allow coarser angular and translational samplings if accuracies are bad (typically in earlier iterations."); - do_trust_ref_size = parser.checkOption("--trust_ref_size", "Trust the pixel and box size of the input reference; by default the program will die if these are different from the first optics group of the data"); - minimum_nr_particles_sigma2_noise = textToInteger(parser.getOption("--nr_parts_sigma2noise", "Number of particles (per optics group) for initial noise spectra estimation.", "1000")); - ///////////////// Special stuff for first iteration (only accessible via CL, not through readSTAR //////////////////// - - // When reading from the CL: always start at iteration 1 and subset 1 - iter = 0; - // When starting from CL: always calculate initial sigma_noise - do_calculate_initial_sigma_noise = true; - // Start average norm correction at 1! - mymodel.avg_norm_correction = 1.; - // Always initialise the PDF of the directions - directions_have_changed = true; - - // Only reconstruct and join random halves are only available when continuing an old run - do_join_random_halves = false; - - // For auto-sampling and convergence check - nr_iter_wo_resol_gain = 0; - nr_iter_wo_large_hidden_variable_changes = 0; - current_changes_optimal_classes = 9999999; - current_changes_optimal_offsets = 999.; - current_changes_optimal_orientations = 999.; - smallest_changes_optimal_classes = 9999999; - smallest_changes_optimal_offsets = 999.; - smallest_changes_optimal_orientations = 999.; - acc_rot = acc_trans = 999.; - - best_resol_thus_far = 1./999.; - has_converged = false; - has_high_fsc_at_limit = false; - has_large_incr_size_iter_ago = 0; - do_initialise_bodies = false; - - // By default, start with nr_bodies to 1 - mymodel.nr_bodies = 1; - fn_body_masks = "None"; - - // Debugging/analysis/hidden stuff - do_map = !checkParameter(argc, argv, "--no_map"); - minres_map = textToInteger(getParameter(argc, argv, "--minres_map", "5")); - abort_at_resolution = textToFloat(parser.getOption("--abort_at_resolution", "Abort when resolution reaches beyond this value", "-1", true)); - do_bfactor = checkParameter(argc, argv, "--bfactor"); - gridding_nr_iter = textToInteger(getParameter(argc, argv, "--gridding_iter", "10")); - debug1 = textToFloat(getParameter(argc, argv, "--debug1", "0")); - debug2 = textToFloat(getParameter(argc, argv, "--debug2", "0")); - debug3 = textToFloat(getParameter(argc, argv, "--debug3", "0")); - // Read in initial sigmaNoise spectrum - fn_sigma = getParameter(argc, argv, "--sigma",""); - do_calculate_initial_sigma_noise = (fn_sigma == "") ? true : false; - sigma2_fudge = textToFloat(getParameter(argc, argv, "--sigma2_fudge", "1")); - do_acc_currentsize_despite_highres_exp = checkParameter(argc, argv, "--accuracy_current_size"); - do_sequential_halves_recons = checkParameter(argc, argv, "--sequential_halves_recons"); - do_always_join_random_halves = checkParameter(argc, argv, "--always_join_random_halves"); - do_use_all_data = checkParameter(argc, argv, "--use_all_data"); - do_only_sample_tilt = checkParameter(argc, argv, "--only_sample_tilt"); - minimum_angular_sampling = textToFloat(getParameter(argc, argv, "--minimum_angular_sampling", "0")); - maximum_angular_sampling = textToFloat(getParameter(argc, argv, "--maximum_angular_sampling", "0")); - asymmetric_padding = parser.checkOption("--asymmetric_padding", "", "false", true); - skip_gridding = !parser.checkOption("--dont_skip_gridding", "Perform gridding in the reconstruction step (obsolete?)"); - debug_split_random_half = textToInteger(getParameter(argc, argv, "--debug_split_random_half", "0")); +#if !defined _CUDA_ENABLED && !defined _HIP_ENABLED + if(do_gpu) + { + std::cerr << "+ WARNING : Relion was compiled without CUDA >= 7.0 or HIP with ROCm >= 4.0 - you do NOT have support for GPUs" << std::endl; + do_gpu = false; + } +#endif + double temp_reqSize = textToDouble(parser.getOption("--free_gpu_memory", "GPU device memory (in Mb) to leave free after allocation.", "0")); + if(!do_zero_mask) + temp_reqSize += 100; + temp_reqSize *= 1000*1000; + if(temp_reqSize<0) + REPORT_ERROR("Invalid free_gpu_memory value."); + else + requested_free_gpu_memory = temp_reqSize; + + // Expert options + int expert_section = parser.addSection("Expert options"); + mymodel.padding_factor = textToFloat(parser.getOption("--pad", "Oversampling factor for the Fourier transforms of the references", "2")); + + ref_angpix = textToFloat(parser.getOption("--ref_angpix", "Pixel size (in A) for the input reference (default is to read from header)", "-1.")); + mymodel.interpolator = (parser.checkOption("--NN", "Perform nearest-neighbour instead of linear Fourier-space interpolation?")) ? NEAREST_NEIGHBOUR : TRILINEAR; + mymodel.r_min_nn = textToInteger(parser.getOption("--r_min_nn", "Minimum number of Fourier shells to perform linear Fourier-space interpolation", "10")); + verb = textToInteger(parser.getOption("--verb", "Verbosity (1=normal, 0=silent)", "1")); + random_seed = textToInteger(parser.getOption("--random_seed", "Number for the random seed generator", "-1")); + max_coarse_size = textToInteger(parser.getOption("--coarse_size", "Maximum image size for the first pass of the adaptive sampling approach", "-1")); + adaptive_fraction = textToFloat(parser.getOption("--adaptive_fraction", "Fraction of the weights to be considered in the first pass of adaptive oversampling ", "0.999")); + width_mask_edge = textToInteger(parser.getOption("--maskedge", "Width of the soft edge of the spherical mask (in pixels)", "5")); + // If we're doing helical, and maskedge is not given, use a default maskedge of 10 + if (helical_tube_outer_diameter > 0. && !checkParameter(argc, argv, "--maskedge")) width_mask_edge = 10.; + fix_sigma_noise = parser.checkOption("--fix_sigma_noise", "Fix the experimental noise spectra?"); + fix_sigma_offset = parser.checkOption("--fix_sigma_offset", "Fix the stddev in the origin offsets?"); + incr_size = textToInteger(parser.getOption("--incr_size", "Number of Fourier shells beyond the current resolution to be included in refinement", "10")); + do_print_metadata_labels = parser.checkOption("--print_metadata_labels", "Print a table with definitions of all metadata labels, and exit"); + do_print_symmetry_ops = parser.checkOption("--print_symmetry_ops", "Print all symmetry transformation matrices, and exit"); + strict_highres_exp = textToFloat(parser.getOption("--strict_highres_exp", "High resolution limit (in Angstrom) to restrict probability calculations in the expectation step", "-1")); + strict_lowres_exp = textToFloat(parser.getOption("--strict_lowres_exp", "Low resolution limit (in Angstrom) to restrict probability calculations in the expectation step", "-1")); + dont_raise_norm_error = parser.checkOption("--dont_check_norm", "Skip the check whether the images are normalised correctly"); + do_always_cc = parser.checkOption("--always_cc", "Perform CC-calculation in all iterations (useful for faster denovo model generation?)"); + do_phase_random_fsc = parser.checkOption("--solvent_correct_fsc", "Correct FSC curve for the effects of the solvent mask?"); + do_skip_maximization = parser.checkOption("--skip_maximize", "Skip maximization step (only write out data.star file)?"); + failsafe_threshold = textToInteger(parser.getOption("--failsafe_threshold", "Maximum number of particles permitted to be handled by fail-safe mode, due to zero sum of weights, before exiting with an error (GPU only).", "40")); + + do_blush = parser.checkOption("--blush", "Perform the reconstruction step outside relion_refine, e.g. for learned priors?)"); + if (parser.checkOption("--blush_skip_spectral_trailing", "Skip spectral trailing during Blush reconstruction (WARNING: This may inflate resolution estimates)")) + blush_args += " --skip-spectral-trailing "; + + do_external_reconstruct = parser.checkOption("--external_reconstruct", "Perform the reconstruction with the Blush algorithm."); + nr_iter_max = textToInteger(parser.getOption("--auto_iter_max", "In auto-refinement, stop at this iteration.", "999")); + auto_ignore_angle_changes = parser.checkOption("--auto_ignore_angles", "In auto-refinement, update angular sampling regardless of changes in orientations for convergence. This makes convergence faster."); + auto_resolution_based_angles= parser.checkOption("--auto_resol_angles", "In auto-refinement, update angular sampling based on resolution-based required sampling. This makes convergence faster."); + allow_coarser_samplings = parser.checkOption("--allow_coarser_sampling", "In 2D/3D classification, allow coarser angular and translational samplings if accuracies are bad (typically in earlier iterations."); + do_trust_ref_size = parser.checkOption("--trust_ref_size", "Trust the pixel and box size of the input reference; by default the program will die if these are different from the first optics group of the data"); + minimum_nr_particles_sigma2_noise = textToInteger(parser.getOption("--nr_parts_sigma2noise", "Number of particles (per optics group) for initial noise spectra estimation.", "1000")); + ///////////////// Special stuff for first iteration (only accessible via CL, not through readSTAR //////////////////// + + // When reading from the CL: always start at iteration 1 and subset 1 + iter = 0; + // When starting from CL: always calculate initial sigma_noise + do_calculate_initial_sigma_noise = true; + // Start average norm correction at 1! + mymodel.avg_norm_correction = 1.; + // Always initialise the PDF of the directions + directions_have_changed = true; + + // Only reconstruct and join random halves are only available when continuing an old run + do_join_random_halves = false; + + // For auto-sampling and convergence check + nr_iter_wo_resol_gain = 0; + nr_iter_wo_large_hidden_variable_changes = 0; + current_changes_optimal_classes = 9999999; + current_changes_optimal_offsets = 999.; + current_changes_optimal_orientations = 999.; + smallest_changes_optimal_classes = 9999999; + smallest_changes_optimal_offsets = 999.; + smallest_changes_optimal_orientations = 999.; + acc_rot = acc_trans = 999.; + + best_resol_thus_far = 1./999.; + has_converged = false; + has_high_fsc_at_limit = false; + has_large_incr_size_iter_ago = 0; + do_initialise_bodies = false; + + // By default, start with nr_bodies to 1 + mymodel.nr_bodies = 1; + fn_body_masks = "None"; + + // Debugging/analysis/hidden stuff + do_map = !checkParameter(argc, argv, "--no_map"); + minres_map = textToInteger(getParameter(argc, argv, "--minres_map", "5")); + abort_at_resolution = textToFloat(parser.getOption("--abort_at_resolution", "Abort when resolution reaches beyond this value", "-1", true)); + do_bfactor = checkParameter(argc, argv, "--bfactor"); + gridding_nr_iter = textToInteger(getParameter(argc, argv, "--gridding_iter", "10")); + debug1 = textToFloat(getParameter(argc, argv, "--debug1", "0")); + debug2 = textToFloat(getParameter(argc, argv, "--debug2", "0")); + debug3 = textToFloat(getParameter(argc, argv, "--debug3", "0")); + // Read in initial sigmaNoise spectrum + fn_sigma = getParameter(argc, argv, "--sigma",""); + do_calculate_initial_sigma_noise = (fn_sigma == "") ? true : false; + sigma2_fudge = textToFloat(getParameter(argc, argv, "--sigma2_fudge", "1")); + do_acc_currentsize_despite_highres_exp = checkParameter(argc, argv, "--accuracy_current_size"); + do_sequential_halves_recons = checkParameter(argc, argv, "--sequential_halves_recons"); + do_always_join_random_halves = checkParameter(argc, argv, "--always_join_random_halves"); + do_use_all_data = checkParameter(argc, argv, "--use_all_data"); + do_only_sample_tilt = checkParameter(argc, argv, "--only_sample_tilt"); + minimum_angular_sampling = textToFloat(getParameter(argc, argv, "--minimum_angular_sampling", "0")); + maximum_angular_sampling = textToFloat(getParameter(argc, argv, "--maximum_angular_sampling", "0")); + asymmetric_padding = parser.checkOption("--asymmetric_padding", "", "false", true); + skip_gridding = !parser.checkOption("--dont_skip_gridding", "Perform gridding in the reconstruction step (obsolete?)"); + debug_split_random_half = textToInteger(getParameter(argc, argv, "--debug_split_random_half", "0")); skip_realspace_helical_sym = parser.checkOption("--skip_realspace_helical_sym", "", "false", true); + min_sigma2_offset = textToFloat(parser.getOption("--min_sigma2_offset", "Lower bound for sigma2 for offset", "2.", true)); + #ifdef DEBUG_READ - std::cerr<<"MlOptimiser::parseInitial Done"<