diff --git a/CMakeLists.txt b/CMakeLists.txt index 762782b6730..a6f5cc420f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -275,7 +275,6 @@ macro (config_hook) if(dune-fem_FOUND) opm_need_version_of ("dune-fem") endif() - opm_need_version_of ("opm-models") if(NOT fmt_FOUND) include(DownloadFmt) @@ -293,6 +292,21 @@ macro (config_hook) if(BUILD_FLOW_FLOAT_VARIANTS) set(FLOW_INSTANTIATE_FLOAT 1) endif() + + # The parameter system can leverage std::from_chars() for + # floating-point types if available. Detect support for this + # feature. + try_compile( + have_float_from_chars + ${CMAKE_BINARY_DIR} + ${PROJECT_SOURCE_DIR}/cmake/test/testFloatFromChars.cpp + CXX_STANDARD 17 + ) + + set(HAVE_FLOATING_POINT_FROM_CHARS 0) + if(have_float_from_chars) + set(HAVE_FLOATING_POINT_FROM_CHARS 1) + endif() endmacro (config_hook) macro (prereqs_hook) @@ -371,6 +385,44 @@ opm_add_test(test_tuning_tsinit_nextstep ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} opmcommon ONLY_COMPILE) +# this test is identical to the simulation of the lens problem that +# uses the element centered finite volume discretization in +# conjunction with automatic differentiation +# (lens_immiscible_ecfv_ad). The only difference is that it uses +# multiple compile units in order to ensure that eWoms code can be +# used within libraries that use the same type tag within multiple +# compile units. +opm_add_test(lens_immiscible_ecfv_ad_mcu + SOURCES + examples/lens_immiscible_ecfv_ad_cu1.cpp + examples/lens_immiscible_ecfv_ad_cu2.cpp + examples/lens_immiscible_ecfv_ad_main.cpp + LIBRARIES + opmsimulators opmcommon + ONLY_COMPILE) + +if(QuadMath_FOUND) + foreach(tapp co2injection_flash_ni_ecfv + co2injection_flash_ni_vcfv + co2injection_flash_ecfv + co2injection_flash_vcfv) + opm_add_test(${tapp}_quad + EXE_NAME ${tapp}_quad + SOURCES + examples/${tapp}.cpp + ONLY_COMPILE) + target_link_libraries(${tapp}_quad QuadMath::QuadMath) + target_compile_definitions(${tapp}_quad PRIVATE HAVE_QUAD=1) + endforeach() +endif() + +target_sources(test_outputdir PRIVATE $) +target_sources(test_equil PRIVATE $) +target_sources(test_RestartSerialization PRIVATE $) +target_sources(test_glift1 PRIVATE $) + +include (${CMAKE_CURRENT_SOURCE_DIR}/modelTests.cmake) + if (HAVE_OPM_TESTS) include (${CMAKE_CURRENT_SOURCE_DIR}/compareECLFiles.cmake) endif() @@ -379,11 +431,6 @@ if(MPI_FOUND) include (${CMAKE_CURRENT_SOURCE_DIR}/parallelUnitTests.cmake) endif() -target_sources(test_outputdir PRIVATE $) -target_sources(test_equil PRIVATE $) -target_sources(test_RestartSerialization PRIVATE $) -target_sources(test_glift1 PRIVATE $) - include(OpmBashCompletion) if (NOT BUILD_FLOW) diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index bc4cb6a7dc5..75153ec92a9 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -326,6 +326,10 @@ endif() # originally generated with the command: # find tests -name '*.cpp' -a ! -wholename '*/not-unit/*' -printf '\t%p\n' | sort list (APPEND TEST_SOURCE_FILES + tests/models/test_quadrature.cpp + tests/models/test_propertysystem.cpp + tests/models/test_tasklets.cpp + tests/models/test_tasklets_failure.cpp tests/test_ALQState.cpp tests/test_aquifergridutils.cpp tests/test_blackoil_amg.cpp @@ -480,12 +484,226 @@ list (APPEND TEST_DATA_FILES tests/include/well_vfp.ecl tests/test10.partition tests/parametersystem.ini + tests/data/co2injection.dgf + tests/data/cuvette_11x4.dgf + tests/data/cuvette_44x24.dgf + tests/data/fracture.art.dgf + tests/data/fracture-raw.art + tests/data/groundwater_1d.dgf + tests/data/groundwater_2d.dgf + tests/data/groundwater_3d.dgf + tests/data/infiltration_50x3.dgf + tests/data/infiltration_250x20.dgf + tests/data/obstacle_24x16.dgf + tests/data/obstacle_48x32.dgf + tests/data/outflow.dgf + tests/data/reservoir.dgf + tests/data/richardslens_24x16.dgf + tests/data/richardslens_48x32.dgf + tests/data/richardslens_96x64.dgf + tests/data/test_stokes.dgf + tests/data/test_stokes2c.dgf + tests/data/test_stokes2cni.dgf + tests/data/waterair.dgf ) # originally generated with the command: # find opm -name '*.h*' -a ! -name '*-pch.hpp' -printf '\t%p\n' | sort list (APPEND PUBLIC_HEADER_FILES + opm/models/blackoil/blackoilboundaryratevector.hh + opm/models/blackoil/blackoilbrinemodules.hh + opm/models/blackoil/blackoilbrineparams.hh + opm/models/blackoil/blackoildarcyfluxmodule.hh + opm/models/blackoil/blackoildiffusionmodule.hh + opm/models/blackoil/blackoildispersionmodule.hh + opm/models/blackoil/blackoilenergymodules.hh + opm/models/blackoil/blackoilextbomodules.hh + opm/models/blackoil/blackoilextboparams.hh + opm/models/blackoil/blackoilextensivequantities.hh + opm/models/blackoil/blackoilfoammodules.hh + opm/models/blackoil/blackoilfoamparams.hh + opm/models/blackoil/blackoilindices.hh + opm/models/blackoil/blackoilintensivequantities.hh + opm/models/blackoil/blackoillocalresidual.hh + opm/models/blackoil/blackoillocalresidualtpfa.hh + opm/models/blackoil/blackoilmicpmodules.hh + opm/models/blackoil/blackoilmicpparams.hh + opm/models/blackoil/blackoilmodel.hh + opm/models/blackoil/blackoilnewtonmethod.hh + opm/models/blackoil/blackoilnewtonmethodparameters.hh + opm/models/blackoil/blackoilonephaseindices.hh + opm/models/blackoil/blackoilpolymermodules.hh + opm/models/blackoil/blackoilpolymerparams.hh + opm/models/blackoil/blackoilprimaryvariables.hh + opm/models/blackoil/blackoilproblem.hh + opm/models/blackoil/blackoilproperties.hh + opm/models/blackoil/blackoilratevector.hh + opm/models/blackoil/blackoilsolventmodules.hh + opm/models/blackoil/blackoilsolventparams.hh + opm/models/blackoil/blackoiltwophaseindices.hh + opm/models/common/darcyfluxmodule.hh + opm/models/common/diffusionmodule.hh + opm/models/common/directionalmobility.hh + opm/models/common/energymodule.hh + opm/models/common/flux.hh + opm/models/common/forchheimerfluxmodule.hh + opm/models/common/multiphasebaseextensivequantities.hh + opm/models/common/multiphasebasemodel.hh + opm/models/common/multiphasebaseparameters.hh + opm/models/common/multiphasebaseproblem.hh + opm/models/common/multiphasebaseproperties.hh + opm/models/common/quantitycallbacks.hh + opm/models/common/transfluxmodule.hh + opm/models/discretefracture/discretefractureextensivequantities.hh + opm/models/discretefracture/discretefractureintensivequantities.hh + opm/models/discretefracture/discretefracturelocalresidual.hh + opm/models/discretefracture/discretefracturemodel.hh + opm/models/discretefracture/discretefractureprimaryvariables.hh + opm/models/discretefracture/discretefractureproblem.hh + opm/models/discretefracture/discretefractureproperties.hh + opm/models/discretefracture/fracturemapper.hh + opm/models/discretization/common/baseauxiliarymodule.hh + opm/models/discretization/common/fvbaseadlocallinearizer.hh + opm/models/discretization/common/fvbaseboundarycontext.hh + opm/models/discretization/common/fvbaseconstraints.hh + opm/models/discretization/common/fvbaseconstraintscontext.hh + opm/models/discretization/common/fvbasediscretization.hh + opm/models/discretization/common/fvbasediscretizationfemadapt.hh + opm/models/discretization/common/fvbaseelementcontext.hh + opm/models/discretization/common/fvbaseextensivequantities.hh + opm/models/discretization/common/fvbasefdlocallinearizer.hh + opm/models/discretization/common/fvbasegradientcalculator.hh + opm/models/discretization/common/fvbaseintensivequantities.hh + opm/models/discretization/common/fvbaselinearizer.hh + opm/models/discretization/common/fvbaselocalresidual.hh + opm/models/discretization/common/fvbasenewtonconvergencewriter.hh + opm/models/discretization/common/fvbasenewtonmethod.hh + opm/models/discretization/common/fvbaseparameters.hh + opm/models/discretization/common/fvbaseprimaryvariables.hh + opm/models/discretization/common/fvbaseproblem.hh + opm/models/discretization/common/fvbaseproperties.hh + opm/models/discretization/common/linearizationtype.hh + opm/models/discretization/common/restrictprolong.hh + opm/models/discretization/common/tpfalinearizer.hh + opm/models/discretization/ecfv/ecfvbaseoutputmodule.hh + opm/models/discretization/ecfv/ecfvdiscretization.hh + opm/models/discretization/ecfv/ecfvgridcommhandlefactory.hh + opm/models/discretization/ecfv/ecfvproperties.hh + opm/models/discretization/ecfv/ecfvstencil.hh + opm/models/discretization/vcfv/vcfvbaseoutputmodule.hh + opm/models/discretization/vcfv/vcfvdiscretization.hh + opm/models/discretization/vcfv/vcfvgridcommhandlefactory.hh + opm/models/discretization/vcfv/vcfvproperties.hh + opm/models/discretization/vcfv/vcfvstencil.hh + opm/models/discretization/vcfv/p1fegradientcalculator.hh + opm/models/flash/flashboundaryratevector.hh + opm/models/flash/flashextensivequantities.hh + opm/models/flash/flashindices.hh + opm/models/flash/flashintensivequantities.hh + opm/models/flash/flashlocalresidual.hh + opm/models/flash/flashmodel.hh + opm/models/flash/flashratevector.hh + opm/models/flash/flashparameters.hh + opm/models/flash/flashprimaryvariables.hh + opm/models/flash/flashproperties.hh + opm/models/immiscible/immiscibleboundaryratevector.hh + opm/models/immiscible/immiscibleextensivequantities.hh + opm/models/immiscible/immiscibleindices.hh + opm/models/immiscible/immiscibleintensivequantities.hh + opm/models/immiscible/immisciblelocalresidual.hh + opm/models/immiscible/immisciblemodel.hh + opm/models/immiscible/immiscibleprimaryvariables.hh + opm/models/immiscible/immiscibleproperties.hh + opm/models/immiscible/immiscibleratevector.hh + opm/models/io/baseoutputmodule.hh + opm/models/io/baseoutputwriter.hh + opm/models/io/basevanguard.hh + opm/models/io/cubegridvanguard.hh + opm/models/io/dgfvanguard.hh + opm/models/io/restart.hh + opm/models/io/simplexvanguard.hh + opm/models/io/structuredgridvanguard.hh + opm/models/io/unstructuredgridvanguard.hh + opm/models/io/vtkblackoilenergymodule.hh + opm/models/io/vtkblackoilmicpmodule.hh + opm/models/io/vtkblackoilmodule.hh + opm/models/io/vtkblackoilpolymermodule.hh + opm/models/io/vtkblackoilsolventmodule.hh + opm/models/io/vtkcompositionmodule.hh + opm/models/io/vtkdiffusionmodule.hh + opm/models/io/vtkdiscretefracturemodule.hh + opm/models/io/vtkenergymodule.hh + opm/models/io/vtkmultiphasemodule.hh + opm/models/io/vtkmultiwriter.hh + opm/models/io/vtkphasepresencemodule.hh + opm/models/io/vtkprimaryvarsmodule.hh + opm/models/io/vtkptflashmodule.hh + opm/models/io/vtkscalarfunction.hh + opm/models/io/vtktemperaturemodule.hh + opm/models/io/vtktensorfunction.hh + opm/models/io/vtkvectorfunction.hh + opm/models/ncp/ncpboundaryratevector.hh + opm/models/ncp/ncpextensivequantities.hh + opm/models/ncp/ncpindices.hh + opm/models/ncp/ncpintensivequantities.hh + opm/models/ncp/ncplocalresidual.hh + opm/models/ncp/ncpmodel.hh + opm/models/ncp/ncpnewtonmethod.hh + opm/models/ncp/ncpprimaryvariables.hh + opm/models/ncp/ncpproperties.hh + opm/models/ncp/ncpratevector.hh + opm/models/nonlinear/newtonmethod.hh + opm/models/nonlinear/newtonmethodparameters.hh + opm/models/nonlinear/newtonmethodproperties.hh + opm/models/nonlinear/nullconvergencewriter.hh + opm/models/parallel/gridcommhandles.hh + opm/models/parallel/mpibuffer.hh + opm/models/parallel/mpiutil.hh + opm/models/parallel/tasklets.hh + opm/models/parallel/threadedentityiterator.hh + opm/models/parallel/threadmanager.hh + opm/models/ptflash/flashindices.hh + opm/models/ptflash/flashintensivequantities.hh + opm/models/ptflash/flashlocalresidual.hh + opm/models/ptflash/flashmodel.hh + opm/models/ptflash/flashnewtonmethod.hh + opm/models/ptflash/flashparameters.hh + opm/models/ptflash/flashprimaryvariables.hh + opm/models/pvs/pvsboundaryratevector.hh + opm/models/pvs/pvsextensivequantities.hh + opm/models/pvs/pvsindices.hh + opm/models/pvs/pvsintensivequantities.hh + opm/models/pvs/pvslocalresidual.hh + opm/models/pvs/pvsmodel.hh + opm/models/pvs/pvsnewtonmethod.hh + opm/models/pvs/pvsprimaryvariables.hh + opm/models/pvs/pvsproperties.hh + opm/models/pvs/pvsratevector.hh + opm/models/richards/richardsboundaryratevector.hh + opm/models/richards/richardsextensivequantities.hh + opm/models/richards/richardsindices.hh + opm/models/richards/richardsintensivequantities.hh + opm/models/richards/richardslocalresidual.hh + opm/models/richards/richardsmodel.hh + opm/models/richards/richardsnewtonmethod.hh + opm/models/richards/richardsprimaryvariables.hh + opm/models/richards/richardsproperties.hh + opm/models/richards/richardsratevector.hh + opm/models/utils/alignedallocator.hh + opm/models/utils/basicparameters.hh + opm/models/utils/basicproperties.hh + opm/models/utils/genericguard.hh + opm/models/utils/parametersystem.hh + opm/models/utils/pffgridvector.hh + opm/models/utils/prefetch.hh + opm/models/utils/propertysystem.hh + opm/models/utils/quadraturegeometries.hh + opm/models/utils/signum.hh + opm/models/utils/simulator.hh + opm/models/utils/start.hh + opm/models/utils/timer.hh + opm/models/utils/timerguard.hh opm/simulators/flow/ActionHandler.hpp opm/simulators/flow/AluGridCartesianIndexMapper.hpp opm/simulators/flow/AluGridVanguard.hpp @@ -564,33 +782,65 @@ list (APPEND PUBLIC_HEADER_FILES opm/simulators/aquifers/BlackoilAquiferModel_impl.hpp opm/simulators/aquifers/SupportsFaceTag.hpp opm/simulators/linalg/amgcpr.hh + opm/simulators/linalg/bicgstabsolver.hh + opm/simulators/linalg/blacklist.hh + opm/simulators/linalg/combinedcriterion.hh + opm/simulators/linalg/convergencecriterion.hh opm/simulators/linalg/DILU.hpp - opm/simulators/linalg/twolevelmethodcpr.hh + opm/simulators/linalg/domesticoverlapfrombcrsmatrix.hh + opm/simulators/linalg/elementborderlistfromgrid.hh + opm/simulators/linalg/extractMatrix.hpp opm/simulators/linalg/ExtractParallelGridInformationToISTL.hpp opm/simulators/linalg/ExtraSmoothers.hpp + opm/simulators/linalg/findOverlapRowsAndColumns.hpp + opm/simulators/linalg/fixpointcriterion.hh opm/simulators/linalg/FlexibleSolver.hpp opm/simulators/linalg/FlexibleSolver_impl.hpp opm/simulators/linalg/FlowLinearSolverParameters.hpp + opm/simulators/linalg/foreignoverlapfrombcrsmatrix.hh + opm/simulators/linalg/getQuasiImpesWeights.hpp + opm/simulators/linalg/globalindices.hh opm/simulators/linalg/GraphColoring.hpp + opm/simulators/linalg/ilufirstelement.hh opm/simulators/linalg/ISTLSolver.hpp + opm/simulators/linalg/istlpreconditionerwrappers.hh + opm/simulators/linalg/istlsolverwrappers.hh + opm/simulators/linalg/istlsparsematrixadapter.hh + opm/simulators/linalg/linalgparameters.hh + opm/simulators/linalg/linalgproperties.hh + opm/simulators/linalg/linearsolverreport.hh + opm/simulators/linalg/matrixblock.hh opm/simulators/linalg/MatrixMarketSpecializations.hpp + opm/simulators/linalg/nullborderlistmanager.hh + opm/simulators/linalg/overlappingbcrsmatrix.hh + opm/simulators/linalg/overlappingblockvector.hh + opm/simulators/linalg/overlappingoperator.hh + opm/simulators/linalg/overlappingpreconditioner.hh + opm/simulators/linalg/overlappingscalarproduct.hh + opm/simulators/linalg/overlaptypes.hh opm/simulators/linalg/OwningBlockPreconditioner.hpp opm/simulators/linalg/OwningTwoLevelPreconditioner.hpp + opm/simulators/linalg/parallelamgbackend.hh + opm/simulators/linalg/parallelbasebackend.hh + opm/simulators/linalg/parallelbicgstabbackend.hh + opm/simulators/linalg/parallelistlbackend.hh + opm/simulators/linalg/ParallelIstlInformation.hpp opm/simulators/linalg/ParallelOverlappingILU0.hpp opm/simulators/linalg/ParallelRestrictedAdditiveSchwarz.hpp - opm/simulators/linalg/ParallelIstlInformation.hpp opm/simulators/linalg/PressureSolverPolicy.hpp opm/simulators/linalg/PressureTransferPolicy.hpp opm/simulators/linalg/PreconditionerFactory.hpp opm/simulators/linalg/PreconditionerWithUpdate.hpp opm/simulators/linalg/PropertyTree.hpp + opm/simulators/linalg/residreductioncriterion.hh opm/simulators/linalg/SmallDenseMatrixUtils.hpp + opm/simulators/linalg/setupPropertyTree.hpp + opm/simulators/linalg/superlubackend.hh + opm/simulators/linalg/twolevelmethodcpr.hh + opm/simulators/linalg/vertexborderlistfromgrid.hh + opm/simulators/linalg/weightedresidreductioncriterion.hh opm/simulators/linalg/WellOperators.hpp opm/simulators/linalg/WriteSystemMatrixHelper.hpp - opm/simulators/linalg/extractMatrix.hpp - opm/simulators/linalg/findOverlapRowsAndColumns.hpp - opm/simulators/linalg/getQuasiImpesWeights.hpp - opm/simulators/linalg/setupPropertyTree.hpp opm/simulators/timestepping/AdaptiveSimulatorTimer.hpp opm/simulators/timestepping/AdaptiveTimeStepping.hpp opm/simulators/timestepping/ConvergenceReport.hpp @@ -754,10 +1004,62 @@ if(HDF5_FOUND) endif() list (APPEND EXAMPLE_SOURCE_FILES + examples/art2dgf.cpp + examples/co2injection_flash_ecfv.cpp + examples/co2injection_flash_ni_ecfv.cpp + examples/co2injection_flash_ni_vcfv.cpp + examples/co2injection_flash_vcfv.cpp + examples/co2injection_immiscible_ecfv.cpp + examples/co2injection_immiscible_ni_ecfv.cpp + examples/co2injection_immiscible_ni_vcfv.cpp + examples/co2injection_immiscible_vcfv.cpp + examples/co2injection_ncp_ecfv.cpp + examples/co2injection_ncp_ni_ecfv.cpp + examples/co2injection_ncp_ni_vcfv.cpp + examples/co2injection_ncp_vcfv.cpp + examples/co2injection_pvs_ecfv.cpp + examples/co2injection_pvs_ni_ecfv.cpp + examples/co2injection_pvs_ni_vcfv.cpp + examples/co2_ptflash_ecfv.cpp + examples/co2injection_pvs_vcfv.cpp + examples/cuvette_pvs.cpp + examples/diffusion_flash.cpp + examples/diffusion_ncp.cpp + examples/diffusion_pvs.cpp + examples/groundwater_immiscible.cpp + examples/infiltration_pvs.cpp + examples/lens_immiscible_ecfv_ad.cpp + examples/lens_immiscible_ecfv_ad_23.cpp + examples/lens_immiscible_ecfv_ad_trans.cpp + examples/lens_immiscible_vcfv_ad.cpp + examples/lens_immiscible_vcfv_fd.cpp + examples/lens_richards_ecfv.cpp + examples/lens_richards_vcfv.cpp + examples/obstacle_immiscible.cpp + examples/obstacle_ncp.cpp + examples/obstacle_pvs.cpp + examples/outflow_pvs.cpp + examples/powerinjection_darcy_ad.cpp + examples/powerinjection_darcy_fd.cpp + examples/powerinjection_forchheimer_ad.cpp + examples/powerinjection_forchheimer_fd.cpp + examples/reservoir_blackoil_ecfv.cpp + examples/reservoir_blackoil_vcfv.cpp + examples/reservoir_ncp_ecfv.cpp + examples/reservoir_ncp_vcfv.cpp examples/printvfp.cpp + examples/tutorial1.cpp + examples/waterair_pvs_ni.cpp ) if(HDF5_FOUND) list (APPEND EXAMPLE_SOURCE_FILES examples/opmrst_inspect.cpp ) endif() +if(dune-alugrid_FOUND) + list (APPEND EXAMPLE_SOURCE_FILES + examples/finger_immiscible_ecfv.cpp + examples/finger_immiscible_vcfv.cpp + examples/fracture_discretefracture.cpp + ) +endif() diff --git a/cmake/test/testFloatFromChars.cpp b/cmake/test/testFloatFromChars.cpp new file mode 100644 index 00000000000..859ef3f1192 --- /dev/null +++ b/cmake/test/testFloatFromChars.cpp @@ -0,0 +1,11 @@ +// CMake feature test for floating-point std::from_chars() support + +#include +#include + +int main() +{ + const auto s = std::string_view { "2.71828" }; + auto e = 0.0; + std::from_chars(s.data(), s.data() + s.size(), e); +} diff --git a/doc/handbook/EPS/Ex2_Boundary.eps b/doc/handbook/EPS/Ex2_Boundary.eps new file mode 100644 index 00000000000..652614b5e56 --- /dev/null +++ b/doc/handbook/EPS/Ex2_Boundary.eps @@ -0,0 +1,238 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: Ex2_Boundary.fig +%%Creator: fig2dev Version 3.2 Patchlevel 5 +%%CreationDate: Fri Nov 14 16:44:03 2008 +%%For: melanie@alvine (Melanie Darcis) +%%BoundingBox: 0 0 557 252 +%Magnification: 1.0000 +%%EndComments +/MyAppDict 100 dict dup begin def +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +newpath 0 252 moveto 0 0 lineto 557 0 lineto 557 252 lineto closepath clip newpath +-72.8 457.3 translate +1 -1 scale + +% left30 +<< + /PatternType 1 + /PaintType 2 + /TilingType 2 + /BBox [-2 -4 10 5] + /XStep 8 + /YStep 4 + /PaintProc + { + pop + newpath + .7 setlinewidth + -2 -1 moveto + 12 6 rlineto + stroke + } bind + +>> + +matrix +makepattern +/P1 exch def + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def + +$F2psBegin +10 setmiterlimit +0 slj 0 slc + 0.06299 0.06299 sc +% +% Fig objects follow +% +% +% here starts figure with depth 50 +% Polyline +0 slj +0 slc +7.500 slw +n 3015 3870 m 8775 3870 l 8775 6615 l 3015 6615 l + cp gs col0 s gr +% Polyline +n 4095 4770 m 7650 4770 l 7650 5760 l 4095 5760 l + cp gs col31 1.00 shd ef gr gs col0 s gr +% Polyline + [15 45] 45 sd +n 3015 6615 m 8775 6615 l 8775 6795 l 3015 6795 l + cp +% Fill with pattern background color +gs /DeviceRGB setcolorspace 1.00 1.00 1.00 setcolor fill gr + +% Fill with pattern pen color +gs /DeviceRGB setcolorspace 0.00 0.00 0.00 P1 setpattern fill gr + +gs col0 s gr [] 0 sd +% Polyline + [15 45] 45 sd +n 3015 3690 m 8775 3690 l 8775 3870 l 3015 3870 l + cp +% Fill with pattern background color +gs /DeviceRGB setcolorspace 1.00 1.00 1.00 setcolor fill gr + +% Fill with pattern pen color +gs /DeviceRGB setcolorspace 0.00 0.00 0.00 P1 setpattern fill gr + +gs col0 s gr [] 0 sd +% Polyline +gs clippath +9320 4462 m 9465 4462 l 9465 4357 l 9320 4357 l 9320 4357 l 9440 4410 l 9320 4462 l cp +eoclip +n 9000 4410 m + 9450 4410 l gs col0 s gr gr + +% arrowhead +n 9320 4462 m 9440 4410 l 9320 4357 l col0 s +% Polyline +gs clippath +9320 4867 m 9465 4867 l 9465 4762 l 9320 4762 l 9320 4762 l 9440 4815 l 9320 4867 l cp +eoclip +n 9000 4815 m + 9450 4815 l gs col0 s gr gr + +% arrowhead +n 9320 4867 m 9440 4815 l 9320 4762 l col0 s +% Polyline +gs clippath +9320 5272 m 9465 5272 l 9465 5167 l 9320 5167 l 9320 5167 l 9440 5220 l 9320 5272 l cp +eoclip +n 9000 5220 m + 9450 5220 l gs col0 s gr gr + +% arrowhead +n 9320 5272 m 9440 5220 l 9320 5167 l col0 s +% Polyline +gs clippath +9320 5722 m 9465 5722 l 9465 5617 l 9320 5617 l 9320 5617 l 9440 5670 l 9320 5722 l cp +eoclip +n 9045 5670 m + 9450 5670 l gs col0 s gr gr + +% arrowhead +n 9320 5722 m 9440 5670 l 9320 5617 l col0 s +% Polyline +gs clippath +9320 6127 m 9465 6127 l 9465 6022 l 9320 6022 l 9320 6022 l 9440 6075 l 9320 6127 l cp +eoclip +n 9045 6075 m + 9450 6075 l gs col0 s gr gr + +% arrowhead +n 9320 6127 m 9440 6075 l 9320 6022 l col0 s +% Polyline +gs clippath +9320 6532 m 9465 6532 l 9465 6427 l 9320 6427 l 9320 6427 l 9440 6480 l 9320 6532 l cp +eoclip +n 9045 6480 m + 9450 6480 l gs col0 s gr gr + +% arrowhead +n 9320 6532 m 9440 6480 l 9320 6427 l col0 s +% Polyline +gs clippath +9320 4057 m 9465 4057 l 9465 3952 l 9320 3952 l 9320 3952 l 9440 4005 l 9320 4057 l cp +eoclip +n 9000 4005 m + 9450 4005 l gs col0 s gr gr + +% arrowhead +n 9320 4057 m 9440 4005 l 9320 3952 l col0 s +/Times-Roman ff 190.50 scf sf +5535 7245 m +gs 1 -1 sc (no flow) col0 sh gr +/Times-Roman ff 190.50 scf sf +5535 3420 m +gs 1 -1 sc (no flow) col0 sh gr +/Times-Roman ff 190.50 scf sf +9720 4995 m +gs 1 -1 sc (qw) col0 sh gr +/Times-Roman ff 190.50 scf sf +9765 5490 m +gs 1 -1 sc (qo) col0 sh gr +/Times-Roman ff 190.50 scf sf +1170 4815 m +gs 1 -1 sc (pw) col0 sh gr +/Times-Roman ff 190.50 scf sf +1170 5310 m +gs 1 -1 sc (S) col0 sh gr +% here ends figure; +$F2psEnd +rs +end +showpage +%%Trailer +%EOF diff --git a/doc/handbook/EPS/Ex2_Domain.eps b/doc/handbook/EPS/Ex2_Domain.eps new file mode 100644 index 00000000000..2d1dea64474 --- /dev/null +++ b/doc/handbook/EPS/Ex2_Domain.eps @@ -0,0 +1,245 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: Ex2_Domain.fig +%%Creator: fig2dev Version 3.2 Patchlevel 5 +%%CreationDate: Wed Oct 6 11:40:41 2010 +%%For: faigle@indo (Benjamin Faigle, Diplomarbeiter bei Jenny) +%%BoundingBox: 0 0 493 283 +%Magnification: 1.0000 +%%EndComments +%%BeginProlog +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +newpath 0 283 moveto 0 0 lineto 493 0 lineto 493 283 lineto closepath clip newpath +-132.3 508.2 translate +1 -1 scale + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def + +$F2psBegin +10 setmiterlimit +0 slj 0 slc + 0.06299 0.06299 sc +%%EndProlog +% +% Fig objects follow +% +% +% here starts figure with depth 50 +% Polyline +0 slj +0 slc +7.500 slw +n 3015 3870 m 8775 3870 l 8775 6615 l 3015 6615 l + cp gs col0 s gr +% Polyline +n 2610 3870 m + 2610 4950 l gs col0 s gr +% Polyline +n 2610 5535 m + 2610 6615 l gs col0 s gr +% Polyline +n 6435 3690 m + 8775 3690 l gs col0 s gr +% Polyline +n 4860 5805 m + 4860 6075 l gs col0 s gr +% Polyline +n 4860 6390 m + 4860 6615 l gs col0 s gr +% Polyline +n 3870 4770 m + 3870 5085 l gs col0 s gr +% Polyline +n 3870 5445 m + 3870 5760 l gs col0 s gr +% Polyline +n 3915 4770 m + 3780 4770 l gs col0 s gr +% Polyline +n 3915 5760 m + 3780 5760 l gs col0 s gr +% Polyline +n 3015 3690 m + 5355 3690 l gs col0 s gr +% Polyline +n 4140 4590 m + 5580 4590 l gs col0 s gr +% Polyline +n 6390 4590 m + 7650 4590 l gs col0 s gr +% Polyline +n 8550 5220 m + 8775 5220 l gs col0 s gr +% Polyline +n 7695 5220 m + 7920 5220 l gs col0 s gr +% Polyline +n 4095 4770 m 7650 4770 l 7650 5760 l 4095 5760 l + cp gs col31 1.00 shd ef gr gs col0 s gr +% Polyline +gs clippath +6113 5413 m 5991 5322 l 5955 5370 l 6077 5461 l 6077 5461 l 6023 5383 l 6113 5413 l cp +eoclip +n 7965 6840 m + 5985 5355 l gs col0 s gr gr + +% arrowhead +30.000 slw +n 6113 5413 m 6023 5383 l 6077 5461 l 6119 5455 l 6113 5413 l + cp gs 0.00 setgray ef gr col0 s +% Polyline +7.500 slw +gs clippath +6073 6363 m 6196 6273 l 6161 6224 l 6038 6314 l 6038 6314 l 6129 6286 l 6073 6363 l cp +eoclip +n 5380 6827 m + 6167 6258 l gs col0 s gr gr + +% arrowhead +30.000 slw +n 6073 6363 m 6129 6286 l 6038 6314 l 6031 6356 l 6073 6363 l + cp gs 0.00 setgray ef gr col0 s +% Polyline +7.500 slw +n 2700 3870 m + 2565 3870 l gs col0 s gr +% Polyline +n 2700 6615 m + 2565 6615 l gs col0 s gr +% Polyline +n 3015 3780 m + 3015 3600 l gs col0 s gr +% Polyline +n 8775 3780 m + 8775 3600 l gs col0 s gr +% Polyline +n 7650 4680 m + 7650 4500 l gs col0 s gr +% Polyline +n 4095 4680 m + 4095 4500 l gs col0 s gr +% Polyline + [15 45] 45 sd +n 6615 6840 m 9900 6840 l 9900 8055 l 6615 8055 l + cp gs col0 s gr [] 0 sd +% Polyline + [15 45] 45 sd +n 2835 6840 m 6075 6840 l 6075 8055 l 2835 8055 l + cp gs col0 s gr [] 0 sd +/Times-Roman ff 190.50 scf sf +7965 5265 m +gs 1 -1 sc (L3x) col0 sh gr +/Times-Roman ff 190.50 scf sf +5670 4635 m +gs 1 -1 sc (L2x) col0 sh gr +/Times-Roman ff 190.50 scf sf +5445 3780 m +gs 1 -1 sc (L1x) col0 sh gr +/Times-Roman ff 190.50 scf sf +4455 6300 m +gs 1 -1 sc (H2y) col0 sh gr +/Times-Roman ff 190.50 scf sf +3330 5310 m +gs 1 -1 sc (H3y) col0 sh gr +/Times-Roman ff 190.50 scf sf +2115 5310 m +gs 1 -1 sc (H1y) col0 sh gr +/Times-Roman ff 190.50 scf sf +2970 7695 m +gs 1 -1 sc (Lin) col0 sh gr +/Times-Roman ff 190.50 scf sf +2970 7425 m +gs 1 -1 sc (phi1) col0 sh gr +/Times-Roman ff 190.50 scf sf +2970 7155 m +gs 1 -1 sc (K1) col0 sh gr +/Times-Roman ff 190.50 scf sf +6750 7155 m +gs 1 -1 sc (K2) col0 sh gr +/Times-Roman ff 190.50 scf sf +6750 7380 m +gs 1 -1 sc (phi2) col0 sh gr +/Times-Roman ff 190.50 scf sf +6750 7650 m +gs 1 -1 sc (BC1) col0 sh gr +/Times-Roman ff 190.50 scf sf +6750 7920 m +gs 1 -1 sc (BC2) col0 sh gr +/Times-Roman ff 190.50 scf sf +2970 7920 m +gs 1 -1 sc (Lin2) col0 sh gr +% here ends figure; +$F2psEnd +rs +showpage +%%Trailer +%EOF diff --git a/doc/handbook/EPS/box_disc.eps b/doc/handbook/EPS/box_disc.eps new file mode 100755 index 00000000000..9dbb95ce24e --- /dev/null +++ b/doc/handbook/EPS/box_disc.eps @@ -0,0 +1,759 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: cairo 1.8.10 (http://cairographics.org) +%%CreationDate: Tue Jan 4 12:08:49 2011 +%%Pages: 1 +%%BoundingBox: 0 0 729 467 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%EndComments +%%BeginProlog +/cairo_eps_state save def +/dict_count countdictstack def +/op_count count 1 sub def +userdict begin +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/B { fill stroke } bind def +/B* { eofill stroke } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/pdfmark where { pop globaldict /?pdfmark /exec load put } + { globaldict begin /?pdfmark /pop load def /pdfmark + /cleartomark load def end } ifelse +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +%%EndProlog +11 dict begin +/FontType 42 def +/FontName /f-0-0 def +/PaintType 0 def +/FontMatrix [ 1 0 0 1 0 0 ] def +/FontBBox [ 0 0 0 0 ] def +/Encoding 256 array def +0 1 255 { Encoding exch /.notdef put } for +Encoding 1 /uni0073 put +Encoding 2 /uni0063 put +Encoding 3 /uni0076 put +Encoding 4 /uni0069 put +Encoding 5 /uni006B put +Encoding 6 /uni0046 put +Encoding 7 /uni0045 put +Encoding 8 /uni0020 put +Encoding 9 /uni006D put +Encoding 10 /uni0065 put +Encoding 11 /uni0068 put +Encoding 12 /uni006F put +Encoding 13 /uni006E put +Encoding 14 /uni0064 put +Encoding 15 /uni0061 put +Encoding 16 /uni0072 put +Encoding 17 /uni0079 put +Encoding 18 /uni0056 put +Encoding 19 /uni0042 put +Encoding 20 /uni006A put +Encoding 21 /uni0078 put +Encoding 22 /uni0029 put +Encoding 23 /uni0062 put +/CharStrings 24 dict dup begin +/.notdef 0 def +/uni0073 1 def +/uni0063 2 def +/uni0076 3 def +/uni0069 4 def +/uni006B 5 def +/uni0046 6 def +/uni0045 7 def +/uni0020 8 def +/uni006D 9 def +/uni0065 10 def +/uni0068 11 def +/uni006F 12 def +/uni006E 13 def +/uni0064 14 def +/uni0061 15 def +/uni0072 16 def +/uni0079 17 def +/uni0056 18 def +/uni0042 19 def +/uni006A 20 def +/uni0078 21 def +/uni0029 22 def +/uni0062 23 def +end readonly def +/sfnts [ +<00010000000a008000030020636d617000baf144000011f00000006e63767420ffd31d390000 +1260000001fc6670676de7b4f1c40000145c0000008b676c7966e58152c0000000ac00001144 +68656164dd84a2d0000014e800000036686865611045077b0000152000000024686d74786d08 +0bd000001544000000606c6f636136303068000015a4000000326d6178700453063a000015d8 +00000020707265703b07f100000015f80000056800020066fe96046605a400030007001a400c +04fb0006fb0108057f0204002fc4d4ec310010d4ecd4ec301311211125211121660400fc7303 +1bfce5fe96070ef8f27206290001006fffe303c7047b002700e7403c0d0c020e0b531f1e0809 +02070a531e1f1e420a0b1e1f041500860189041486158918b91104b925b8118c281e0a0b1f1b +0700521b080e07081422452810fcc4ecd4ece4111239393939310010e4f4ec10fef5ee10f5ee +121739304b535807100eed111739070eed1117395922b2002701015d406d1c0a1c0b1c0c2e09 +2c0a2c0b2c0c3b093b0a3b0b3b0c0b200020012402280a280b2a132f142f152a16281e281f29 +2029212427860a860b860c860d12000000010202060a060b030c030d030e030f03100319031a +031b031c041d09272f293f295f297f2980299029a029f029185d005d7101152e012322061514 +161f011e0115140623222627351e013332363534262f012e01353436333216038b4ea85a8989 +62943fc4a5f7d85ac36c66c661828c65ab40ab98e0ce66b4043fae282854544049210e2a9989 +9cb62323be353559514b50250f2495829eac1e00000000010071ffe303e7047b0019003f401b +00860188040e860d880ab91104b917b8118c1a07120d004814451a10fce432ec310010e4f4ec +10fef4ee10f5ee30400b0f1b101b801b901ba01b05015d01152e012322061514163332363715 +0e0123220011100021321603e74e9d50b3c6c6b3509d4e4da55dfdfed6012d010655a20435ac +2b2be3cdcde32b2baa2424013e010e0112013a2300000001003d0000047f0460000601124027 +03110405040211010205050402110302060006011100000642020300bf050605030201050400 +0710d4c4173931002fec3239304b5358071005ed071008ed071008ed071005ed5922014bb00a +5458bd0007ffc000010007000700403811373859014bb014544bb015545b58bd000700400001 +00070007ffc03811373859408e48026a027b027f02860280029102a402080600060109030904 +150015011a031a0426002601290329042008350035013a033a04300846004601490349044605 +48064008560056015903590450086600660169036904670568066008750074017b037b047505 +7a068500850189038904890586069600960197029a03980498059706a805a706b008c008df08 +ff083e5d005d133309013301233dc3015e015ec3fe5cfa0460fc5403acfba0000000000200c1 +00000179061400030007002b400e06be04b100bc020501080400460810fc3cec3231002fe4fc +ec30400b1009400950096009700905015d1333112311331523c1b8b8b8b80460fba00614e900 +000100ba0000049c0614000a00bc402908110506050711060605031104050402110505044208 +05020303bc009709060501040608010800460b10fcec32d4c4113931002f3cece41739304b53 +58071004ed071005ed071005ed071004ed5922b2100c01015d405f04020a081602270229052b +0856026602670873027705820289058e08930296059708a3021209050906020b030a07280327 +0428052b062b07400c6803600c8903850489058d068f079a039707aa03a705b607c507d607f7 +03f003f704f0041a5d71005d1333110133090123011123bab90225ebfdae026bf0fdc7b90614 +fc6901e3fdf4fdac0223fddd000100c90000042305d50009002940120695040295008104ad08 +050107031c00040a10fcec32d4c431002fecf4ec10ee30b20f0b01015d132115211121152111 +23c9035afd700250fdb0ca05d5aafe48aafd3700000100c90000048b05d5000b002e40150695 +0402950081089504ad0a05010907031c00040c10fcec32d4c4c431002fececf4ec10ee30b21f +0d01015d132115211121152111211521c903b0fd1a02c7fd3902f8fc3e05d5aafe46aafde3aa +0000000100ba0000071d047b0022005a4026061209180f00061d07150c871d2003b81bbc1910 +0700110f0808065011080f501c18081a462310fcec32fcfcfcec11123931002f3c3ce4f43cc4 +ec32111217393040133024502470249024a024a024bf24df24ff2409015d013e013332161511 +231134262322061511231134262322061511231133153e01333216042945c082afbeb972758f +a6b972778da6b9b93fb0797aab03897c76f5e2fd5c029ea19cbea4fd87029ea29bbfa3fd8704 +60ae67627c00000000020071ffe3047f047b0014001b00704024001501098608880515a90105 +b90c01bb18b912b80c8c1c1b1502081508004b02120f451c10fcecf4ecc4111239310010e4f4 +ece410ee10ee10f4ee1112393040293f1d701da01dd01df01d053f003f013f023f153f1b052c +072f082f092c0a6f006f016f026f156f1b095d71015d0115211e0133323637150e0123200011 +1000333200072e0123220607047ffcb20ccdb76ac76263d06bfef4fec70129fce20107b802a5 +889ab90e025e5abec73434ae2a2c0138010a01130143feddc497b4ae9e00000100ba00000464 +0614001300344019030900030e0106870e11b80c970a010208004e0d09080b461410fcec32f4 +ec31002f3cecf4c4ec1112173930b2601501015d0111231134262322061511231133113e0133 +32160464b87c7c95acb9b942b375c1c602a4fd5c029e9f9ebea4fd870614fd9e6564ef000002 +0071ffe30475047b000b0017004a401306b91200b90cb8128c1809120f51031215451810fcec +f4ec310010e4f4ec10ee3040233f197b007b067f077f087f097f0a7f0b7b0c7f0d7f0e7f0f7f +107f117b12a019f01911015d0122061514163332363534262732001110002322001110000273 +94acab9593acac93f00112feeef0f1feef011103dfe7c9c9e7e8c8c7e99cfec8feecfeedfec7 +01390113011401380000000100ba00000464047b001300364019030900030e0106870e11b80c +bc0a010208004e0d09080b461410fcec32f4ec31002f3ce4f4c4ec1112173930b46015cf1502 +015d0111231134262322061511231133153e013332160464b87c7c95acb9b942b375c1c602a4 +fd5c029e9f9ebea4fd870460ae6564ef00020071ffe3045a06140010001c003840191ab9000e +14b905088c0eb801970317040008024711120b451d10fcecf4ec323231002fece4f4c4ec10c4 +ee30b6601e801ea01e03015d0111331123350e01232202111012333216011416333236353426 +23220603a2b8b83ab17ccbffffcb7cb1fdc7a79292a8a89292a703b6025ef9eca86461014401 +080108014461fe15cbe7e7cbcbe7e7000002007bffe3042d047b000a002500bc4027191f0b17 +090e00a91706b90e1120861fba1cb923b8118c170c001703180d09080b1f030814452610fcec +ccd4ec323211393931002fc4e4f4fcf4ec10c6ee10ee11391139123930406e301d301e301f30 +20302130223f27401d401e401f402040214022501d501e501f50205021502250277027851d87 +1e871f8720872185229027a027f0271e301e301f30203021401e401f40204021501e501f5020 +5021601e601f60206021701e701f70207021801e801f80208021185d015d0122061514163332 +363d01371123350e01232226353436332135342623220607353e0133321602bedfac816f99b9 +b8b83fbc88accbfdfb0102a79760b65465be5af3f00233667b6273d9b4294cfd81aa6661c1a2 +bdc0127f8b2e2eaa2727fc00000100ba0000034a047b001100304014060b0700110b03870eb8 +09bc070a06080008461210fcc4ec3231002fe4f4ecc4d4cc11123930b450139f1302015d012e +012322061511231133153e0133321617034a1f492c9ca7b9b93aba85132e1c03b41211cbbefd +b20460ae6663050500000001003dfe56047f0460000f01a240430708020911000f0a110b0a00 +000f0e110f000f0d110c0d00000f0d110e0d0a0b0a0c110b0b0a420d0b0910000b058703bd0e +0bbc100e0d0c0a09060300080f040f0b1010d4c4c4111739310010e432f4ec11391139123930 +4b5358071005ed071008ed071008ed071005ed071008ed0705ed17325922014bb00a544bb008 +545b58bd0010ffc000010010001000403811373859014bb0145458bd00100040000100100010 +ffc0381137385940f0060005080609030d160a170d100d230d350d490a4f0a4e0d5a095a0a6a +0a870d800d930d120a000a09060b050c0b0e0b0f1701150210041005170a140b140c1a0e1a0f +2700240124022004200529082809250a240b240c270d2a0e2a0f201137003501350230043005 +380a360b360c380d390e390f30114100400140024003400440054006400740084209450a470d +490e490f40115400510151025503500450055606550756085709570a550b550c590e590f5011 +66016602680a690e690f60117b08780e780f89008a09850b850c890d890e890f9909950b950c +9a0e9a0fa40ba40cab0eab0fb011cf11df11ff11655d005d050e012b01353332363f01013309 +013302934e947c936c4c543321fe3bc3015e015ec368c87a9a488654044efc94036c00010010 +0000056805d5000600b740270411050605031102030606050311040300010002110101004203 +0401af0006040302000505010710d4c4173931002fec3239304b5358071005ed071008ed0710 +08ed071005ed5922b2500801015d406200032a03470447055a037d0383030706000702080409 +06150114021a041a052a002601260229042905250620083800330133023c043c053706480045 +014502490449054706590056066602690469057a007601760279047905750680089800970629 +5d005d21013309013301024afdc6d301d901dad2fdc705d5fb1704e9fa2b000300c9000004ec +05d5000800110020004340231900950a0995128101950aad1f110b080213191f05000e1c1605 +191c2e09001c12042110fcec32fcecd4ec111739393931002fececf4ec10ee3930b20f220101 +5d01112132363534262301112132363534262325213216151406071e01151404232101930144 +a39d9da3febc012b94919194fe0b0204e7fa807c95a5fef0fbfde802c9fddd878b8c850266fe +3e6f727170a6c0b189a21420cb98c8da0002ffdbfe5601790614000b000f0044401c0b020700 +0ebe0c078705bd00bc0cb110081005064f0d01080c00461010fc3cec32e4391239310010ece4 +f4ec10ee1112393930400b1011401150116011701105015d13331114062b0135333236351133 +1523c1b8a3b54631694cb8b80460fb8cd6c09c61990628e900000001003b000004790460000b +015a40460511060706041103040707060411050401020103110202010b110001000a11090a01 +01000a110b0a0708070911080807420a070401040800bf05020a0704010408000208060c10d4 +c4d4c411173931002f3cec321739304b5358071005ed071008ed071008ed071005ed071005ed +071008ed071008ed071005ed5922014bb00a544bb00f545b4bb010545b4bb011545b58bd000c +ffc00001000c000c00403811373859014bb0145458bd000c00400001000c000cffc038113738 +5940980a04040a1a04150a260a3d04310a55045707580a660a76017a047607740a8d04820a99 +049f049707920a900aa601a904af04a507a30aa00a1c0a03040505090a0b1a03150515091a0b +2903260525092a0b200d3a013903370534073609390b300d4903460545094a0b400d59005601 +5902590357055606590756085609590b500d6f0d78017f0d9b019407ab01a407b00dcf0ddf0d +ff0d2f5d005d09022309012309013309010464fe6b01aad9febafebad901b3fe72d901290129 +0460fddffdc101b8fe48024a0216fe71018f0000000100a4fef2026f0612000d001f400f0798 +00970e0701000b12041308000e10dc3cf4ec113939310010fcec301333161215140207233612 +353402a4a096959596a08583830612ecfe3cdfe0fe3aebe501c5e7e701c20000000200baffe3 +04a40614000b001c0038401903b90c0f09b918158c0fb81b971900121247180c06081a461d10 +fcec3232f4ec31002fece4f4c4ec10c6ee30b6601e801ea01e03015d01342623220615141633 +3236013e01333212111002232226271523113303e5a79292a7a79292a7fd8e3ab17bccffffcc +7bb13ab9b9022fcbe7e7cbcbe7e702526461febcfef8fef8febc6164a8061400000000020003 +00000000001400010000000000340004002000000004000400010000f017ffff0000f000ffff +10000001000000000006003a0000000000180000000100020003000400050006000700080009 +000a000b000c000d000e000f001000110012001300140015001600170000013500b800cb00cb +00c100aa009c01a600b800660000007100cb00a002b20085007500b800c301cb0189022d00cb +00a600f000d300aa008700cb03aa0400014a003300cb000000d9050200f4015400b4009c0139 +0114013907060400044e04b4045204b804e704cd0037047304cd04600473013303a2055605a6 +0556053903c5021200c9001f00b801df007300ba03e9033303bc0444040e00df03cd03aa00e5 +03aa0404000000cb008f00a4007b00b80014016f007f027b0252008f00c705cd009a009a006f +00cb00cd019e01d300f000ba018300d5009803040248009e01d500c100cb00f600830354027f +00000333026600d300c700a400cd008f009a0073040005d5010a00fe022b00a400b4009c0000 +0062009c0000001d032d05d505d505d505f0007f007b005400a406b80614072301d300b800cb +00a601c301ec069300a000d3035c037103db0185042304a80448008f0139011401390360008f +05d5019a0614072306660179046004600460047b009c00000277046001aa00e904600762007b +00c5007f027b000000b4025205cd006600bc00660077061000cd013b01850389008f007b0000 +001d00cd074a042f009c009c0000077d006f0000006f0335006a006f007b00ae00b2002d0396 +008f027b00f600830354063705f6008f009c04e10266008f018d02f600cd03440029006604ee +007300001400b6060504030201002c2010b002254964b040515820c859212d2cb002254964b0 +40515820c859212d2c20100720b00050b00d7920b8ffff5058041b0559b0051cb0032508b004 +2523e120b00050b00d7920b8ffff5058041b0559b0051cb0032508e12d2c4b505820b0fd4544 +59212d2cb002254560442d2c4b5358b00225b0022545445921212d2c45442d00000100000002 +00005d2045f85f0f3cf5001f080000000000bab9f0b800000000bac26791fe89fe1d0a4c076d +00000008000100000000000000010000076dfe1d00000abcfe89fe890a4c0001000000000000 +0000000000000000001804cd0066042b006f0466007104bc003d023900c104a200ba049a00c9 +050e00c9028b000007cb00ba04ec0071051200ba04e50071051200ba0514007104e7007b034a +00ba04bc003d05790010057d00c90239ffdb04bc003b031f00a4051400ba0000002200d2011e +01bc01e4025c028602b602b60318038203be0410044c0498052e0566065606c6071e075c082a +085608a20000000100000018004d000700420004000200100040000700000415056800030001 +b8028040fffbfe03fa1403f92503f83203f79603f60e03f5fe03f4fe03f32503f20e03f19603 +f02503ef8a4105effe03ee9603ed9603ecfa03ebfa03eafe03e93a03e84203e7fe03e63203e5 +e45305e59603e48a4105e45303e3e22f05e3fa03e22f03e1fe03e0fe03df3203de1403dd9603 +dcfe03db1203da7d03d9bb03d8fe03d68a4105d67d03d5d44705d57d03d44703d3d21b05d3fe +03d21b03d1fe03d0fe03cffe03cefe03cd9603cccb1e05ccfe03cb1e03ca3203c9fe03c68511 +05c61c03c51603c4fe03c3fe03c2fe03c1fe03c0fe03bffe03befe03bdfe03bcfe03bbfe03ba +1103b9862505b9fe03b8b7bb05b8fe03b7b65d05b7bb03b78004b6b52505b65d40ff03b64004 +b52503b4fe03b39603b2fe03b1fe03b0fe03affe03ae6403ad0e03acab2505ac6403abaa1205 +ab2503aa1203a98a4105a9fa03a8fe03a7fe03a6fe03a51203a4fe03a3a20e05a33203a20e03 +a16403a08a4105a096039ffe039e9d0c059efe039d0c039c9b19059c64039b9a10059b19039a +1003990a0398fe0397960d0597fe03960d03958a410595960394930e05942803930e0392fa03 +9190bb0591fe03908f5d0590bb039080048f8e25058f5d038f40048e25038dfe038c8b2e058c +fe038b2e038a8625058a410389880b05891403880b0387862505876403868511058625038511 +0384fe038382110583fe0382110381fe0380fe037ffe0340ff7e7d7d057efe037d7d037c6403 +7b5415057b25037afe0379fe03780e03770c03760a0375fe0374fa0373fa0372fa0371fa0370 +fe036ffe036efe036c21036bfe036a1142056a530369fe03687d036711420566fe0365fe0364 +fe0363fe0362fe03613a0360fa035e0c035dfe035bfe035afe0359580a0559fa03580a035716 +190557320356fe035554150555420354150353011005531803521403514a130551fe03500b03 +4ffe034e4d10054efe034d10034cfe034b4a13054bfe034a4910054a1303491d0d0549100348 +0d0347fe0346960345960344fe0343022d0543fa0342bb03414b0340fe033ffe033e3d12053e +14033d3c0f053d12033c3b0d053c40ff0f033b0d033afe0339fe033837140538fa0337361005 +37140336350b05361003350b03341e03330d0332310b0532fe03310b03302f0b05300d032f0b +032e2d09052e10032d09032c32032b2a25052b64032a2912052a250329120328272505284103 +27250326250b05260f03250b0324fe0323fe03220f03210110052112032064031ffa031e1d0d +051e64031d0d031c1142051cfe031bfa031a42031911420519fe031864031716190517fe0316 +01100516190315fe0314fe0313fe031211420512fe0311022d05114203107d030f64030efe03 +0d0c16050dfe030c0110050c16030bfe030a100309fe0308022d0508fe030714030664030401 +100504fe03401503022d0503fe0302011005022d0301100300fe0301b80164858d012b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b002b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b1d00> +] def +FontName currentdict end definefont pop +11 dict begin +/FontType 42 def +/FontName /f-1-0 def +/PaintType 0 def +/FontMatrix [ 1 0 0 1 0 0 ] def +/FontBBox [ 0 0 0 0 ] def +/Encoding 256 array def +0 1 255 { Encoding exch /.notdef put } for +Encoding 1 /uni006E put +/CharStrings 2 dict dup begin +/.notdef 0 def +/uni006E 1 def +end readonly def +/sfnts [ +<00010000000a008000030020636d61700015f07e0000018000000042637674203eb92de20000 +01c4000002526670676dc6703916000004180000008c676c796648f0b128000000ac000000d4 +68656164de68ad49000004a40000003668686561112e080d000004dc00000024686d74780a7f +011200000500000000086c6f6361006a002600000508000000066d6178700614087d00000510 +00000020707265707c61a2e700000530000007a700020066fe96046605a400030007001fbc00 +040126000000060126b6010805890204002fc4d4ec310010d4ecd4ec30131121112521112166 +0400fc73031bfce5fe96070ef8f2720629000000000100ac00000512047b0017003540180d04 +00010adb12d015ca10b30e01020d0047110d0d0f101810fcec32f4ec31002f3ce4f4e4ec1139 +393930b46019801902015d01112135113426272e012322061511211121153e013332160512fe +980d1015482e7080fe9a016651b66ec2c902aafd566f019b916e1a2327ad99fdd90460a4625d +ee00000000000002000300000000001400010000000000340004002000000004000400010000 +f001ffff0000f000ffff10000001000000000006000e00000000000200000001000001660133 +016600bc00e90000013d00a200fa031f00020002006601660002000200ac015400ec00bc0062 +01660181048501540166016d04a400020166007f04cd000000020133006200710000002504a4 +01bc00ba00e500660181018d0548055a0166016d000000000002000200f605c301f005390239 +0058046d043d04b2048104b2016601750466048100b00466043902d1049c047b04cf047b0058 +01330166014c0166014c000200ac009a014a0123009a029a01440119014402cd00c100000166 +013f019a013b05cb05cb00d500d5015000ac00ac0077020a01c701f2012f015801b2012300f6 +00f6011f012f0135023501ee01e70133009800d10358050a009a008f0112009800bc00cd00e5 +00e500f2007304000166008f05d5022b05d500c300e100d700e50000006a01020000001d032d +05d505d505f000a8006a00ec00e1010205d506140721046602f800ec018302a602f801230102 +01020112011f031f005e03cd046004c7048900ec01bc00ba01020333031f03420333035c0112 +011f05d5019a009a00e106660179046004600460047b000000ec02c302b802cd00be00dd00d5 +0000006a025c027b029a00dd01ae01ba01120000008501ae04600762041b009a069a045800ee +009a029a00d102cd019a015005cb05cb008b008b063100f6040600f0034c016004a800c10000 +002505c101000121074a06120096014a078300a800000337007b0014000000c9010005c105c1 +05c105c101000108061d00960427039e00ec0102027d0133009800d10358017900cd02390362 +009c009c009c009301b8009300b80073000014000000b6060504030201002c2010b002254964 +b040515820c859212d2cb002254964b040515820c859212d2c20100720b00050b00d7920b8ff +ff5058041b0559b0051cb0032508b0042523e120b00050b00d7920b8ffff5058041b0559b005 +1cb0032508e12d2c4b505820b80128454459212d2cb002254560442d2c4b5358b00225b00225 +45445921212d2c45442d0001000000020000031875265f0f3cf5001f080000000000bab9cc10 +00000000babc96b2fe68fe1d0b56076d00010008000100000000000000010000076dfe1d0000 +0b85fe68fe680b5600010000000000000000000000000000000204cd006605b200ac00000026 +006a0000000100000002004e0007004500040002001000400007000005ed07a7000200014184 +0280012600fe000301250011000301240121003a0005012400fa000301230016000301220121 +003a0005012200fe00030121003a0003012000fa0003011f00bb0003011e00640003011d00fe +0003011c00190003011b001e0003011a00fe0003011900fe0003011800fe0003011700fe0003 +011600fe000301150114000e0005011500fe00030114000e0003011300fe0003011200fe0003 +010f010e007d0005010f00fe0003010e007d0003010d010c008c0005010d00fe0003010d00c0 +0004010c010b00590005010c008c0003010c00800004010b010a00260005010b00590003010b +00400004010a00260003010900fe0003010800fe00030107000c00030107008000040106b297 +2e054113010600fa0003010500fa0003010400fe0003010300190003010200fa0003010100fa +0003010040ff7d03ff3e03fefe03fcfb2c05fcfe03fb2c03fafe03f9f84705f97d03f84703f7 +fa03f6fe03f5fe03f4fe03f3bb03f2fe03f1fe03f0fe03ef1e03eefe03edec0a05edfe03ec0a +03ec4004ebea0a05eb3203ea0a03e9fa03e8911605e8fe03e7fa03e6fa03e5911605e5fe03e4 +fe03e3fe03e2fe03e1fe03e0fe03dffe03defa03dddc1805dd6403dc1803dba01e05db6403da +d92505dafa03d92503d8d12505d8fa03d7d61405d71603d6d51005d61403d51003d4d30b05d4 +2003d30b03d2d12505d2fa03d1911605d12503d0940c05d02303cfce1405cf2603cecd1205ce +1403cd1203cc911605cc1d03cb1403cac9bb05cafe03c9c85d05c9bb03c98004c840ffc72505 +c85d03c84004c72503c6fe03c56403c4901005c4fe03c31c03c2fe03c1fe03c0bf3a05c0fa03 +bfad1b05bf3a03bebd1a05be3203bdbc1105bd1a03bcbb0f05bc1103bbba0c05bb0f03ba0c03 +b9911605b9fe03b8fe03b71503b61203b5fe03b4fe03b3fe03b21703b11903b01603afad1b05 +affa03aead1b05aefa03ad911605ad1b03ac911605ac7d03abfe03aa2603a9fe03a8fe03a7fe +03a6fe03a50a03a4fe03a3a20e05a3fe03a20e03a24004a1a01e05a1fa03a0911605a01e039f +9116059ffa039e940c059e1c039dfe039c9bbb059cfe039b9a5d059bbb039b80049a8f25059a +5d039a400499fe0398972e0598fe03972e0396911605961e40ff0395940c05952003940c0393 +911605934b039291160592fe03919010059116039010038f25038efe038dfe038cfe038bfe03 +8afe0389fe038887250588fe0387250386fe0385fe0384320383960382fe0381fe038019037f +0a037efe037dfe037cfe037bfa037afa0379fe037776a60577fe0376a60375741b0575fa0374 +1b0373fa03727d0371fe03706f2c056f2c036efa036dfa036cfa036bfe036afe0369fe036863 +0c0568320367fe0366320365640a0565fe03640a0364400463620a05630c03620a0361601505 +619603600111056015035f0a035efe035dfe035c0111055cfe035b5a1b055bfe035a0111055a +1b0359fe0358fa0357fe035601110540ff56fe0355fe03541e035314035251190552fa035101 +1105511903504f190550fa034f4e11054f19034e11034d1e034c4b14054c15034b4a11054b14 +034a490e054a1103490e0348fa034746140547150346140345fa0344430e05440f03430e0342 +41250542fa0341011105412503403f0f0540fe033f3e0e053f0f033e0e033d3c0d053d16033c +0d033b64033afe0339140338fe0337130336351a0536250335341405351a0335c004340a0d05 +34140334800433320c05331403334004320c033130a60531fe033001110530a6032f0c032e13 +032d2c3a052dfa032c1525052c3a032b64032a640329fe0328150327171105271e0326200325 +1e0324231105402b241e0323110322000d0522fa03210f032140042014031f0a031e1e031d1c +19051d25031c0f13051c19031cb801004091041b0d031a194b051a7d0319011105194b0318fe +031711031615250516fa031501110515250314640313110312fe031101110511fe031064030f +0e10050f13030fc0040e10030e80040d0111050dfa030c32030b0a0d050b16030b80040a0d03 +0a400409fe0308fe0307fe0306050a0506fe03050a0305400404fa030364030201110502fe03 +01000d05011103000d0301b80164858d012b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b002b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b1d0000> +] def +FontName currentdict end definefont pop +%%Page: 1 1 +%%BeginPageSetup +%%PageBoundingBox: 0 0 729 467 +%%EndPageSetup +q +0 0 1 rg +BT +32 0 0 32 665.866135 266.844329 Tm +/f-0-0 1 Tf +[<0102>-1<03>]TJ +16 0 0 16 720.110429 262.96486 Tm +<04>Tj +0.00688881 1.593486 Td +<05>Tj +ET +0 g +2.4 w +0 J +0 j +[] 0.0 d +4 M q 1 0 0 -1 0 466.009949 cm +50.02 94.457 m 368.023 94.457 l 368.562 94.457 369 94.891 369 95.43 c +369 413.023 l 369 413.562 368.562 413.996 368.023 413.996 c 50.02 +413.996 l 49.48 413.996 49.047 413.562 49.047 413.023 c 49.047 95.43 l +49.047 94.891 49.48 94.457 50.02 94.457 c h +50.02 94.457 m S Q +q 1 0 0 -1 0 466.009949 cm +209.086 93.41 m 208.184 415.445 l S Q +q 1 0 0 -1 0 466.009949 cm +49.086 254.016 m 369.086 254.016 l S Q +[ 9.6 2.4] 0 d +q 1 0 0 -1 0 466.009949 cm +130.113 46.016 m 129.086 464.238 l S Q +0.501961 0 0.501961 rg +0 291.994 m 417.086 291.276 l f* +0 g +[ 9.6 2.4] 0 d +q 1 0 0 -1 0 466.009949 cm +0 174.016 m 417.086 174.734 l S Q +[ 9.6 2.4] 0 d +q 1 0 0 -1 0 466.009949 cm +1.809 334.016 m 418.895 334.734 l S Q +[ 9.6 2.4] 0 d +q 1 0 0 -1 0 466.009949 cm +291 46.016 m 289.969 464.238 l S Q +BT +19.2 0 0 19.2 19.205015 409.065616 Tm +/f-0-0 1 Tf +[<06>-1<07>-1<0809>-1<0a010b>]TJ +6.543517 1.419601 Td +[<010a02>-1<0c0d>-1<0e0f10>-1<1108>-1<06>-1<12>1<08>-1<090a01>-1<0b>]TJ +ET +[] 0.0 d +q 1 0 0 -1 0 466.009949 cm +63.695 64.395 m 83.52 89.02 l S Q +77.5 384.467 m 70.75 385.197 l 85.027 375.119 l 78.23 391.217 l 77.5 +384.467 l h +77.5 384.467 m f* +0.934725 w +q -0.805073 1 1 0.805073 0 466.009949 cm +-87.332 7.191 m -83.592 3.452 l -96.681 7.192 l -83.594 10.931 l +-87.332 7.191 l h +-87.332 7.191 m S Q +2.4 w +q 1 0 0 -1 0 466.009949 cm +244.809 35.773 m 281.629 73.781 l S Q +274.949 399.123 m 268.164 399.229 l 283.301 390.502 l 275.059 405.908 l +274.949 399.123 l h +274.949 399.123 m f* +0.861902 w +q -0.968718 1 1 0.968718 0 466.009949 cm +-171.911 108.416 m -168.466 104.968 l -180.532 108.416 l -168.465 +111.863 l -171.911 108.416 l h +-171.911 108.416 m S Q +221.195 212.588 m 221.195 206.6 216.34 201.744 210.352 201.744 c +204.363 201.744 199.508 206.6 199.508 212.588 c 199.508 218.576 204.363 +223.428 210.352 223.428 c 216.34 223.428 221.195 218.576 221.195 +212.588 c h +221.195 212.588 m f +2.4 w +q 1 0 0 -1 0 466.009949 cm +221.195 253.422 m 221.195 259.41 216.34 264.266 210.352 264.266 c +204.363 264.266 199.508 259.41 199.508 253.422 c 199.508 247.434 +204.363 242.582 210.352 242.582 c 216.34 242.582 221.195 247.434 +221.195 253.422 c h +221.195 253.422 m S Q +BT +19.2 0 0 19.2 144.230569 228.768905 Tm +/f-0-0 1 Tf +<0d0c0e0a0804>Tj +32 0 0 32 379.681166 180.234689 Tm +<07>Tj +16 0 0 16 399.801082 177.224561 Tm +<05>Tj +ET +6.4 w +q 1 0 0 -1 0 466.009949 cm +211.324 254.969 m 368.77 254.969 l 369.309 254.969 369.742 255.406 +369.742 255.945 c 369.742 412.305 l 369.742 412.844 369.309 413.277 +368.77 413.277 c 211.324 413.277 l 210.785 413.277 210.352 412.844 +210.352 412.305 c 210.352 255.945 l 210.352 255.406 210.785 254.969 +211.324 254.969 c h +211.324 254.969 m S Q +0 0 1 rg +q 1 0 0 -1 0 466.009949 cm +130.613 174.734 m 288.055 174.734 l 288.594 174.734 289.031 175.168 +289.031 175.707 c 289.031 332.066 l 289.031 332.605 288.594 333.039 +288.055 333.039 c 130.613 333.039 l 130.074 333.039 129.637 332.605 +129.637 332.066 c 129.637 175.707 l 129.637 175.168 130.074 174.734 +130.613 174.734 c h +130.613 174.734 m S Q +BT +32 0 0 32 297.491598 260.886857 Tm +/f-0-0 1 Tf +<13>Tj +16 0 0 16 319.104642 257.007388 Tm +<04>Tj +ET +0 g +BT +32 0 0 32 671.745798 163.993741 Tm +/f-0-0 1 Tf +<07>Tj +16 0 0 16 691.865714 160.983612 Tm +<05>Tj +ET +0 0 1 rg +2.20579 w +[ 8.82316 2.20579] 0 d +q 1 0 0 -1 0 466.009949 cm +471.742 364.75 m 687.895 364.75 l S Q +2.277487 w +[ 9.109946 2.277487] 0 d +q 1 0 0 -1 0 466.009949 cm +577.023 256.781 m 578.254 465.332 l S Q +0 g +6.4 w +[] 0.0 d +q 1 0 0 -1 0 466.009949 cm +501.945 283.633 m 659.391 283.633 l 659.93 283.633 660.363 284.066 +660.363 284.605 c 660.363 440.969 l 660.363 441.508 659.93 441.941 +659.391 441.941 c 501.945 441.941 l 501.406 441.941 500.973 441.508 +500.973 440.969 c 500.973 284.605 l 500.973 284.066 501.406 283.633 +501.945 283.633 c h +501.945 283.633 m S Q +512.004 26.897 m 512.004 20.908 507.148 16.053 501.16 16.053 c 495.172 +16.053 490.316 20.908 490.316 26.897 c 490.316 32.885 495.172 37.74 +501.16 37.74 c 507.148 37.74 512.004 32.885 512.004 26.897 c h +512.004 26.897 m f +2.4 w +q 1 0 0 -1 0 466.009949 cm +512.004 439.113 m 512.004 445.102 507.148 449.957 501.16 449.957 c +495.172 449.957 490.316 445.102 490.316 439.113 c 490.316 433.125 +495.172 428.27 501.16 428.27 c 507.148 428.27 512.004 433.125 512.004 +439.113 c h +512.004 439.113 m S Q +667.41 25.815 m 667.41 19.826 662.559 14.971 656.57 14.971 c 650.582 +14.971 645.727 19.826 645.727 25.815 c 645.727 31.803 650.582 36.654 +656.57 36.654 c 662.559 36.654 667.41 31.803 667.41 25.815 c h +667.41 25.815 m f +q 1 0 0 -1 0 466.009949 cm +667.41 440.195 m 667.41 446.184 662.559 451.039 656.57 451.039 c +650.582 451.039 645.727 446.184 645.727 440.195 c 645.727 434.207 +650.582 429.355 656.57 429.355 c 662.559 429.355 667.41 434.207 667.41 +440.195 c h +667.41 440.195 m S Q +669.012 183.549 m 669.012 177.561 664.156 172.709 658.168 172.709 c +652.18 172.709 647.324 177.561 647.324 183.549 c 647.324 189.537 652.18 +194.393 658.168 194.393 c 664.156 194.393 669.012 189.537 669.012 +183.549 c h +669.012 183.549 m f +q 1 0 0 -1 0 466.009949 cm +669.012 282.461 m 669.012 288.449 664.156 293.301 658.168 293.301 c +652.18 293.301 647.324 288.449 647.324 282.461 c 647.324 276.473 652.18 +271.617 658.168 271.617 c 664.156 271.617 669.012 276.473 669.012 +282.461 c h +669.012 282.461 m S Q +511.273 181.951 m 511.273 175.963 506.418 171.108 500.43 171.108 c +494.441 171.108 489.586 175.963 489.586 181.951 c 489.586 187.94 +494.441 192.795 500.43 192.795 c 506.418 192.795 511.273 187.94 511.273 +181.951 c h +511.273 181.951 m f +q 1 0 0 -1 0 466.009949 cm +511.273 284.059 m 511.273 290.047 506.418 294.902 500.43 294.902 c +494.441 294.902 489.586 290.047 489.586 284.059 c 489.586 278.07 +494.441 273.215 500.43 273.215 c 506.418 273.215 511.273 278.07 511.273 +284.059 c h +511.273 284.059 m S Q +0 0 1 rg +6.4 w +q 1 0 0 -1 0 466.009949 cm +498.059 48.957 m 655.5 48.957 l 656.039 48.957 656.477 49.391 656.477 +49.93 c 656.477 206.289 l 656.477 206.828 656.039 207.262 655.5 207.262 +c 498.059 207.262 l 497.52 207.262 497.086 206.828 497.086 206.289 c +497.086 49.93 l 497.086 49.391 497.52 48.957 498.059 48.957 c h +498.059 48.957 m S Q +BT +32 0 0 32 670.390801 387.355153 Tm +/f-0-0 1 Tf +<13>Tj +16 0 0 16 692.003845 383.475684 Tm +<04>Tj +ET +0 g +2.218275 w +q 1 0 0 -1 0 466.009949 cm +470.402 126.016 m 689.012 126.016 l S Q +2.4 w +q 1 0 0 -1 0 466.009949 cm +577.926 28.355 m 575.758 233.285 l S Q +BT +19.2 0 0 19.2 554.028067 348.251023 Tm +/f-0-0 1 Tf +<04>Tj +ET +0 0.639216 1 rg +578.871 339.424 m 652.883 339.424 l 653.406 339.424 653.828 339.002 +653.828 338.479 c 653.828 262.377 l 653.828 261.854 653.406 261.432 +652.883 261.432 c 578.871 261.432 l 578.348 261.432 577.926 261.854 +577.926 262.377 c 577.926 338.479 l 577.926 339.002 578.348 339.424 +578.871 339.424 c h +578.871 339.424 m f +0 g +586.602 339.994 m 586.602 334.006 581.746 329.151 575.758 329.151 c +569.77 329.151 564.914 334.006 564.914 339.994 c 564.914 345.983 569.77 +350.838 575.758 350.838 c 581.746 350.838 586.602 345.983 586.602 +339.994 c h +586.602 339.994 m f +q 1 0 0 -1 0 466.009949 cm +586.602 126.016 m 586.602 132.004 581.746 136.859 575.758 136.859 c +569.77 136.859 564.914 132.004 564.914 126.016 c 564.914 120.027 569.77 +115.172 575.758 115.172 c 581.746 115.172 586.602 120.027 586.602 +126.016 c h +586.602 126.016 m S Q +BT +19.2 0 0 19.2 477.549915 188.565745 Tm +/f-0-0 1 Tf +<04>Tj +-0.0502762 -9.613125 Td +<14>Tj +ET +0 0 1 rg +6.4 w +q 1 0 0 -1 0 466.009949 cm +503.68 364.484 m 577.41 364.484 l S Q +0 g +BT +32 0 0 32 442.393665 52.940609 Tm +/f-0-0 1 Tf +<15>Tj +16 0 0 16 460.287959 49.061139 Tm +<0414>Tj +-0.154439 1.593485 Td +<05>Tj +ET +0 0 1 rg +BT +32 0 0 32 440.331461 132.529837 Tm +/f-0-0 1 Tf +<0a>Tj +16 0 0 16 460.638255 128.650368 Tm +<0414>Tj +-0.154439 1.593485 Td +<05>Tj +ET +0 g +2.4 w +q 1 0 0 -1 0 466.009949 cm +474.098 331.953 m 478.16 330.844 484.367 332.91 489.281 334.016 c +494.137 335.109 500.012 335.797 504.461 339.547 c 513.086 346.812 +513.215 348.691 515.301 351.473 c 517.215 354.02 514.492 355.82 515.301 +359.062 c S Q +512.973 116.26 m 507.152 119.752 l 515.883 104.619 l 516.465 122.08 l +512.973 116.26 l h +512.973 116.26 m f* +1.164171 w +q -0.250001 1 1 0.250001 0 466.009949 cm +-449.876 400.503 m -445.22 395.847 l -461.517 400.503 l -445.22 405.16 +l -449.876 400.503 l h +-449.876 400.503 m S Q +3.037832 w +q 1 0 0 -1 0 466.009949 cm +472.02 406.277 m 477.867 407.375 486.641 404.832 493.605 403.414 c +500.492 402.008 508.848 401.023 515.027 396.664 c 527.008 388.203 +527.105 386.094 529.957 382.902 c 532.566 379.977 528.598 378.059 +529.602 374.395 c S Q +526.383 79.897 m 530.633 72.428 l 530.406 94.545 l 518.914 75.651 l +526.383 79.897 l h +526.383 79.897 m f* +1.464627 w +q -0.274787 -1 -1 0.274787 0 466.009949 cm +224.517 -588.077 m 230.376 -593.937 l 209.869 -588.076 l 230.374 +-582.218 l 224.517 -588.077 l h +224.517 -588.077 m S Q +544.824 102.479 m 544.824 98.002 541.68 94.373 537.797 94.373 c 533.918 +94.373 530.77 98.002 530.77 102.479 c 530.77 106.959 533.918 110.588 +537.797 110.588 c 541.68 110.588 544.824 106.959 544.824 102.479 c h +544.824 102.479 m f +BT +19.2 0 0 19.2 10.90625 451.441199 Tm +/f-0-0 1 Tf +<0f16>Tj +22.82549 -0.0994533 Td +<1716>Tj +0.144399 -12.367117 Td +<0216>Tj +ET +3.162158 w +q 1 0 0 -1 0 466.009949 cm +537.418 398.488 m 537.27 370.441 l 537.27 370.441 l 537.27 370.441 l +537.27 370.441 l S Q +537.395 71.315 m 534.219 74.463 l 537.438 63.412 l 540.543 74.494 l +537.395 71.315 l h +537.395 71.315 m f* +0.790529 w +q -0.00521464 1 1 0.00521464 0 466.009949 cm +-397.487 535.322 m -394.322 532.163 l -405.389 535.324 l -394.324 +538.487 l -397.487 535.322 l h +-397.487 535.322 m S Q +BT +32 0 0 32 546.004061 58.040995 Tm +/f-1-0 1 Tf +<01>Tj +16 0 0 16 565.835855 54.161524 Tm +/f-0-0 1 Tf +<0414>Tj +-0.154439 1.593485 Td +<05>Tj +ET +0 0 1 rg +BT +32 0 0 32 532.228949 137.155305 Tm +/f-0-0 1 Tf +<17>Tj +16 0 0 16 552.404493 131.675836 Tm +<04>Tj +0.00688881 1.593485 Td +<05>Tj +ET +Q +showpage +%%Trailer +count op_count sub {pop} repeat +countdictstack dict_count sub {end} repeat +cairo_eps_state restore +%%EOF diff --git a/doc/handbook/EPS/car-hierarchy.eps b/doc/handbook/EPS/car-hierarchy.eps new file mode 100644 index 00000000000..c23f347a67a --- /dev/null +++ b/doc/handbook/EPS/car-hierarchy.eps @@ -0,0 +1,8053 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: cairo 1.8.10 (http://cairographics.org) +%%CreationDate: Mon Nov 8 16:18:45 2010 +%%Pages: 1 +%%BoundingBox: 0 0 828 466 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%EndComments +%%BeginProlog +/cairo_eps_state save def +/dict_count countdictstack def +/op_count count 1 sub def +userdict begin +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/B { fill stroke } bind def +/B* { eofill stroke } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/pdfmark where { pop globaldict /?pdfmark /exec load put } + { globaldict begin /?pdfmark /pop load def /pdfmark + /cleartomark load def end } ifelse +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +%%EndProlog +%%Page: 1 1 +%%BeginPageSetup +%%PageBoundingBox: 0 0 828 466 +%%EndPageSetup +q +0 g +2 w +0 J +0 j +[] 0.0 d +4 M q 1 0 0 -1 0 465.428497 cm +20.43 139.094 m 227.285 139.094 l 238.051 139.094 246.715 147.762 +246.715 158.523 c 246.715 168.809 l 246.715 179.574 238.051 188.238 +227.285 188.238 c 20.43 188.238 l 9.664 188.238 1 179.574 1 168.809 c 1 +158.523 l 1 147.762 9.664 139.094 20.43 139.094 c h +20.43 139.094 m S Q +q 1 0 0 -1 0 465.428497 cm +310.715 277.191 m 517.57 277.191 l 528.336 277.191 537 285.855 537 +296.617 c 537 306.906 l 537 317.668 528.336 326.332 517.57 326.332 c +310.715 326.332 l 299.949 326.332 291.285 317.668 291.285 306.906 c +291.285 296.617 l 291.285 285.855 299.949 277.191 310.715 277.191 c h +310.715 277.191 m S Q +q 1 0 0 -1 0 465.428497 cm +601 415.285 m 807.855 415.285 l 818.621 415.285 827.285 423.949 827.285 +434.715 c 827.285 445 l 827.285 455.762 818.621 464.43 807.855 464.43 c +601 464.43 l 590.238 464.43 581.57 455.762 581.57 445 c 581.57 434.715 +l 581.57 423.949 590.238 415.285 601 415.285 c h +601 415.285 m S Q +q 1 0 0 -1 0 465.428497 cm +20.43 1 m 227.285 1 l 238.051 1 246.715 9.664 246.715 20.43 c 246.715 +30.715 l 246.715 41.477 238.051 50.145 227.285 50.145 c 20.43 50.145 l +9.664 50.145 1 41.477 1 30.715 c 1 20.43 l 1 9.664 9.664 1 20.43 1 c h +20.43 1 m S Q +q 1 0 0 -1 0 465.428497 cm +310.715 1 m 517.57 1 l 528.336 1 537 9.664 537 20.43 c 537 30.715 l 537 +41.477 528.336 50.145 517.57 50.145 c 310.715 50.145 l 299.949 50.145 +291.285 41.477 291.285 30.715 c 291.285 20.43 l 291.285 9.664 299.949 1 +310.715 1 c h +310.715 1 m S Q +q 1 0 0 -1 0 465.428497 cm +601 1 m 807.855 1 l 818.621 1 827.285 9.664 827.285 20.43 c 827.285 +30.715 l 827.285 41.477 818.621 50.145 807.855 50.145 c 601 50.145 l +590.238 50.145 581.57 41.477 581.57 30.715 c 581.57 20.43 l 581.57 +9.664 590.238 1 601 1 c h +601 1 m S Q +2.8 w +q 1 0 0 -1 0 465.428497 cm +123.855 49 m 123.855 135.285 l S Q +123.855 358.143 m 112.656 369.343 l 123.855 330.143 l 135.059 369.343 l +123.855 358.143 l h +123.855 358.143 m f* +q -0.000000000000000061 1 1 0.000000000000000061 0 465.428497 cm +-107.285 123.855 m -96.086 112.656 l -135.285 123.855 l -96.086 135.059 +l -107.285 123.855 l h +-107.285 123.855 m S Q +q 1 0 0 -1 0 465.428497 cm +414.145 51.57 m 414.145 272.715 l S Q +414.145 220.714 m 402.941 231.913 l 414.145 192.714 l 425.344 231.913 l +414.145 220.714 l h +414.145 220.714 m f* +q -0.000000000000000061 1 1 0.000000000000000061 0 465.428497 cm +-244.715 414.145 m -233.516 402.941 l -272.715 414.145 l -233.516 +425.344 l -244.715 414.145 l h +-244.715 414.145 m S Q +q 1 0 0 -1 0 465.428497 cm +704.43 50.43 m 704.43 410.566 l S Q +704.43 82.862 m 693.23 94.061 l 704.43 54.862 l 715.629 94.061 l 704.43 +82.862 l h +704.43 82.862 m f* +q -0.000000000000000061 1 1 0.000000000000000061 0 465.428497 cm +-382.566 704.43 m -371.367 693.23 l -410.566 704.43 l -371.367 715.629 +l -382.566 704.43 l h +-382.566 704.43 m S Q +q 1 0 0 -1 0 465.428497 cm +123.855 189.117 m 293.562 278.637 l S Q +268.797 199.858 m 253.664 195.175 l 293.562 186.792 l 264.117 214.987 l +268.797 199.858 l h +268.797 199.858 m f* +2.476572 w +q -1 0.52749 0.52749 1 0 465.428497 cm +-319.878 -96.838 m -309.972 -106.747 l -344.645 -96.84 l -309.974 +-86.933 l -319.878 -96.838 l h +-319.878 -96.838 m S Q +2.8 w +q 1 0 0 -1 0 465.428497 cm +414.602 327.305 m 584.305 416.824 l S Q +559.539 61.667 m 544.41 56.987 l 584.305 48.604 l 554.859 76.8 l +559.539 61.667 l h +559.539 61.667 m f* +2.476572 w +q -1 0.52749 0.52749 1 0 465.428497 cm +-604.359 -84.968 m -594.455 -94.873 l -629.124 -84.968 l -594.453 +-75.061 l -604.359 -84.968 l h +-604.359 -84.968 m S Q +Q q +q 0 0 829 466 rectclip +% Fallback Image: x=1, y=1, w=245, h=50 res=300dpi size=642675 +[ 0.24 0 0 0.24 1 414.268497 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1025 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J3Vsg3$]7K#D>EP:q1$o*=mro?P,/[<4NeM7R.QqjE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/m.nOVhYGeP2FDniIr2[MDKfeSLjS,Fr$6Ob3Ib>,k + P+oS[-(Zs42?AO%'Y9?3"-H'TdYjDLk?hg0TT_bmRIk"8O?2dAYEL4QY!F8A!f^\Qt)MG4A + uq_;/B?EQuP5u(WCBNN+!Td]Q5&_;=&QMa)VT1@[JKQM'EiM7#`CXZ:p`68b..`'1ukFM'U + 6LaaG[4AgFuAbD'G\/^/8MaYF[=&U?6-;b;a%c/rGg29p9-kZuK_ab;ho7F^E`(c2C!Al(< + 1#=?fAad?HP'O;Vb;,h*I8^^S;Db + Ls%nE86d#c#FVTa]ilCA]\(u?5Ib5GMZD`E)i<[`SZ)N2o0H5?T85hJDce4Dh)6XAeJ=53) + NP.@9@5^LZ2oSD27-G[P1Y?=Qs[_(<&"q>3&g58!;sO5#md^V(%d8'UaDZ>%QBAAXhi$Tgt + I4H*M"QkN62b;.s,[LSF#O5)rcZKon]6C"e,(W+l*bAZ=tkOdK89AViXRlh]b[XcqKdbZJ\ + n6(,,tRnhC,m>UMrU4s2j<L208<Z5s%f&4/AH31(7:6Z-n3.US^fM*L(d\U=A1RrshA]u"I#h/(5)6MSQSRQ+9@<>`Ab + c:kZO0o13K5b*7)CupjQV_^Rkr+_cC-!'C!BNF2(#j5*okC[:!'k'B*@*5j2Pq28]F!8[,d + c6TQj5Rgc_QOn=9ZoIudas4f>CUT&[D.cb\p3Vr71fU],g@8d@Xq5jY/..!e^`k0 + 4M,?km7;/Xb+61M';9Cgf7njqu,Kc.s;>r[J744ogU6SVrcV(6fn>5cuIuaWT9kh:!V\IX$ + e%u#Fl0Ua6o`T;6qMIB"4[gC/N.9?gRo:eZ;&iRoWK-8@4)2:a+60f3.NcD3dm.I$Xp4h!)@K&9h25mnUUh\V1=ui^5XW4*4J(m&#D(W + 0+)Ks]fNa$Ze/bma9j-&A+98@M).+'-sNoaNhQZGWMbISpYl.lri1WTIG]/O3d?.#3dDYW5 + 'j+d)_dJc+D2<_^IUh3q[g,La6nK6eAA`#T8I4qi))riFH?d&1nDpZVA3"0_5/.+V]nArE_ + .g4eC&2D6#h81"%#iRLtKQ+HW_`2I^NPUO.E;gos(j'4r9M@A7.,,SKrtT"e=,V'K_B7fR) + .Q?QNCFC6a!5@?j,pW$#Q?s/3XA1@Y;^_XB3k9ZNQB&A$+HXD6n!g-GP>^MWS*_XTM2_!i.YE + SP6N9[XC4$p$87'Y61r>rQ>(Fljr'Z+,k>EJ'DG=BBc\YVTXGC/,S\[kd*Lrcro*nrTtEs*; + 6<0>.\&>lX9bYEjlSc[d/2=l[63XK"1:e\A?7s:`d1:,e;*s\kjs[X1Gp)1qRP>"09MUlcQ + n[7(kn']s(,CCZN2;o"P`<^8q8;lhnNA"<6iB^sHo6bSpd<(rC6:_Q5Hslno%W0#USB`%46 + nTt(I<;T11o$*7D=iO6b1UrMbD$ESL[XGY)\[)d'G$`o`;m$c(9`6(BMb-9%3Cp_@o_TWSD + bH0#!m**\te/H]rc-Yh&bj,cjkg+5Cc`I3Ym0+$f'bG3q&?QojD&X&r.+0\#&ZmV5E?N`45 + h#*n'!4^S9j&,]#c(*KRRhe_og9+j"mU + m=GCDS^N(NFj>;1TOgK,='3(pSo7nV7u:N>YTU)0)2bZ(W%tV&Ag$)DS30nZ<`bWuH&`)j. + Ma@FQ*%3#3YW%`$^(^lS\L3WfGZ1a!VHOl.;.jQZ4!*j1kH1%5?DoI5:m+/!KU^hOI4&eP/ + I)Jc)6dLbcN'^Y8=m4oOGOs$,Ej!&C'R&QD>nhBNq1n,r"n&`H9$-skM#+]d*nHn;&mhG<\ + )P7NeTFW<@0.uh!1ng(Go8c)1&oOF^8u'gZU^p:tcWI$LK(`)up5a1dl?+O*K.1_bW@rlQc + \D5;V"kA$.^,;B&"9RcgA3;D/#qD$YH%I,lRYiF/?8FA&'4ZEb-0.:YnGit&(^\tgp6M5rf + ZWSYO)9bnGd8hs/T-QlXKo&AH8q>!72B5l[JqS*4ZH4!XkJPn0Y`N0"TCg"#)dK:cuC06Ni + =q]o1\M'0uf+dEOHd]QR0jLq`\=@2dt#K + CXN/\s$S]*4:qXnh_[MKub)EG3';5l>f$!R=%P6!:nES8Wgs2+o%hSIGbkX@Q#nSfn&<.Jg + d1ner,8$%Zdg[qY9f!E'jUtEAeBLW"9j88c51VGU'TkfuYu'(0"PGM25d=N/%A=NaB\JSa) + mgV)oH>1',sh/N68U,XoIWEe.mnD56^0GqZp@(O775Qr6o7P)FA51\;+5K7)e!%c5pd)W`! + @K!"^hN75QiNd4\&p\8$VGOoRJfMeOX)W8>;IU5[jou(6r=D)iH'e0$-^@&L"N\+mF7r:>I + +Sj\'dWG-0HRJIloeM9pdYd_gN@?WF.2&$Bb)%S?,q:c#WjODRqjZ4cI%&).#'nG]`d(^jNVZ-c4dZKefYm8q`HG.&.J + q/&If:k'4Z"u4g?!U'B'Rd.2)7>'+m/f0f[_r4P$k8f/?nK;,9GOLD?+h(%`.T/Z0A$X[0< + s79!&qp3gU4HY@SRBU4?c[ZP7sQ"\a:Bf;GpG+EA+Tk\bCC:9r#iA;1?mL3KL"s?hdTE6Pb + PAF[DCp\h2p?A[bRC24)%8/j(@^[tX!-'hm<#TW.=i\M\St=-mZbU_' + ZD;=b9Qa!9_?j\FPA)Uh^($\-#**"pG#GS0VL77:UL.Co`Fc."Xe%Yof#R?2%M*2@s)^&Fg + W)8l?r>K+XsfEpeQfGMP3r:P,f5!GI9F][6FGP5qI.JY&Cpsn%X,u"YE@KYa&3?WtbM4>uh + \K<_GAPCIp0p7^U]c0?[KP:^&P:$!8ko\.scRQ&,Pr,6Eds13_PWZR]2m0a%171UX2F>F5:G28I$!=b(R!2AOUt9K?Cg'aXKg$$G>gXCY^\m5OWj$Lk5YfJ-a>OULqE3OT#M + p'D0d%6@2!@5ptC2B'j0ojT_l@["VjFt[>bAJ"PpdM>!#oA`"jl""s0eC3rCoClg5Bo$q>5 + J`t(2If"3P@iI8B,5W&;/L'6V/"YZMR]XCpns$0Xc$;dMg2pn\A=)j`JaN3N&\q"S?E6_pEc%Z;be,Z:3SEf[l$qQo^fHc:`3N52b-\G99sUP;&'[ + cO(>,S?^GMJpJi;4nsoIKD(8V*G;?-r2)gp\3r2p/GZgRg2iB52OSd2nGupC2Q_=9ij&N;9 + HBRN=cfh^#BmV^SHdDXlQ$4o>I=,uG0fRE\YT$_&P;6.)!nZB5P;/[c21G4r"HZOc2trt&] + :a;_TX.^)QjI$M%5mnm#P]*@GT+Pg*6&Y<`V:Za[HiFNKnC-7$b/,U=?g1/#7W5fKR-@$># + A_><=\e*V79`=Ft5\B@m'O!4]XY!e(ocp6U#a?5-s]gpjm!W>2U?'jc!_,_YWV3+sgOgu3hoWrKq#I/:sP*mlhpD6Mk1F*nfn2WX+D[UT- + hXPeA:/pPcnmQT[@o#$FQ)Z&V01/Pn#:GpRFST^[ob11_F]#VVG$0<<\Upe`M^heN.bArE= + +)T!4dhXlRMUgjogtU8dG5'm^A^'rHWeO^olYX"d4j1kSZiWPf@,-_NI@!8IG + *qXlb,(LpReq^T@60#'t&Ot9n1^,T[gqb\-BeN]j2Hc>u60Io5UKU;5./7$5tQVqD2Iq04. + Y=K3CQn\jJL16TR8(3`o*;g/g@<=^jF#L%[)0>&)GC3>#G74dpG&&L43^8NsY6W#1/cG`@N + hO[(mk'fq\iV6'2r;o)pf@Dj'u4T%]4H>d\U/o&#l(=WO@#mS=ek+60*EajISg$^/jZZo1IU.jtQ4uUpO[*A(@%ZU-" + JT/7kG%A_^46@cKI@Ot"p&ZYNCC)liTX[:c2O-V?D4Wn*H<."P5P!:*;e'M40oej,i6404U + JIH0@8#od2>rk]c"3ls3_crdSElkP)%K;?+-/FQd:#G)f_7:p!biP\,AruH\X-)T@J:NGgE + kLqj3cE;n:MTRsDUuST!+?a1BsGHb6Q7pVYWdg":Yff[nTD?'CkD.A%WMT3\)&O@KH0Vcqd + X^01iWF&RTh`S4D;gR8p:4hoc0ff)(9U4(?HfI&XAf3^\q_7Ded4"o?5'3B[Qek`;URNPNTo7`Srq2AU&mZIP%M2B0^o'j-1-A<#O`qA*+ltjo=`IJ + +*BOnc++3s1e+I&,'J4"U'Cm@1[Z_)Arj9_VrlZKMEU(M++/$Ggp=c(lq/Hn_"&*74"&;jH + >?VVHGF98u<6SN4k1A64a?1YQXB>4eGO6'H,hHLrH,RknKRd/t#G3;sG)9Ptn;YX&\GbWmE + N36EaJRiK1i`3giXU?LKiMjm-]j^0]C&NPae"\m=`JS$g!+DSKC/pY5r`"bM&(nF)l/itm] + )j"^>l`N(AIb%:.sQb1B>S'L1Qg)U:94/@5f[#]'!V;VB#CN]L@L()$_8HEQ8>O:lHb0*QJj;%)W>GgC0XN$p + ,$=&JRi\S-SlC'Lbkmr3H.a%SBDL+s7^9UC*(HglGon&(m!sSV7c]=KaHAB7D95K8ZC0/kb%F#GJ'0V&[4WI(`4%GE#Pl\"bgS#E1sKYqe8q)-/DtSE;ka23)RHg@43o; + Mb%a_Yr;AlWB.9`Bd*sNDE_se"V+"82D')1b1n">_dZhEs9H"VQ3-!EsiP]B$obC%qe?qT5 + ;H63r=]QEhpH2__"(6tk5g + :!-)CnKkW[;O`R28O8O[ggE.Rt`N@FPRa5c=1)2J[]j[8,9T%hRC2cHsWZ^rbZ#OrV_6`2t + dMo@+[dECmZ&?)27%Z;1FD]B#Z*4(WZM.N8kc+3aSH@^-@_?)%h.UAB)>\P8_Ens-@6LDI8 + eJ=a"IoAqmI->bC/]GFmucnSt.67])$;+6K88UhO"-Q@G?7[\'M`)O?sulIs(NX6W:OUfqQ + Frk5OOV4%JtO$G9P!EV.'DHEnmiObnYW83+d6enn6>s>pLdlPB4'>84f;`CG(%,@g + in1uHeeAokCMN84su-GG[1h@$- + 7#0N?+ZEu&?rX;cd]@*oke5mPXpV1aqheJdIMCnX_2KndCt%qBj%iK:r8nCALFF*r":dE%) + u7-GWnVK@ilfSGDDU,_(fO[:EaZ.0U7,[ha(_E*a6k!(*LerO>K6V&XI6R7nX3g+lK8h!Y.- + #"Ed'(hA\WJX=q7+!>[FIQ!]_)J9&lC?>_H^'BG5tZ8$TN"=[;+`Q4.1'CXLE_tKC2-s%Ps + 2*We@AQdSmNtXJ2ig7YuU)?@k0j^Xl`6h=,Q6NX=9gGekFOq$R'*kq6%(Odb%apF_`Sn^pb + *Mjd_6gE3@=0]%:-S)j@OPY;Po6=5*KiC]\r;-4<">4S`FWonQ?:-8ntk!b3caN_jmX6:Sj + :To>+td.UC*fon.bN5:t1G&knHdm0[er+`Sg511?27;:")P;K/98m7]_C?&'T[CTbugD)M\ + /Y\^mVV=jMI6)5)hOc`=m,!JN6>t-E_Gk^`i*87fEE+m93;T4GnaeTS-]k.]HYrErrNq&&+Y63 + a*e!^J^LJ!Mp_"m[9p/*?G[89]=>2p + oF@'ge-0QPm`fKs7'e[^@1U(Te>J8HHG.tu!o)8d,1%O&*pLM&4\_t0LB@MD;CLLffnk`p= + #8a*dK2CHHlTbQLcofl;b!6);E(S^P15fb9-+Sa<;j%:6(LiEqpe4go[,]o'/h%c[JoSk*\ + 42>kI"!e`l+s]:-OblF1-$^^[P5LP;l&Q3!<6gomibambK+CR'9.89=VNiVD<@E5kU?-3sQ + 7>@-b?8e]jk?Ao;YIEq,_7c?W2Wm_ElW$(l;,Ik_AX5r2FTT[YHE:pr/I=WVWq<'ces=slbl]bkArDN? + U9K$``tK;A.)VG`:PeS.`2CFsD-kk\jbBu=Z5NQ.O<\piKmbsQDqEPBqh$W8Hc8u_53;`Q> + %jQa-PWt'0Oe\,C!23i[BQ4$m^4`.?E`k0gupK_S%H*?oW_:Y4Z@r+;hZ$&U[fipCGls[CT + "IML[TX)mGd!rJoDVMK:GAh?hYW?t>38C!/o)M#[!5o_)&&[fKgi$WFmZ]stOUgZp>H`506q`Ba2Xp8.iHYsH=6 + ,^CP95_;(b]uB61LE>h\aZY0=5Z44P'Mhq!DlJ$A!Zo<>kW;-0CcJ2m?m>ab`:R:Ao:)ZWt&l + TRB3`,_U;Tq>JF_A\!6%c0:hhJH; + meE@eMU-&lFgKqFcBYZgGXOYrL]qL&)ig(7' + LnEJ1(O#IEYdo8,F6tpM`@p!&jUk^_d_M/,Y;"&V1K)L-T/aP1RKq$,Cn5\#]og>R8-V$9, + m4/X(Ub]FQslhDc,[/b0Li.t]G^KJ!nsTn;Aq#V/mD>lPDW-(pTSs-0)Bp;,o\m@PR4<- + :>=!'=oqD]>TW?]A(#bM^Lp)-p/.l19tr+`(qab`2@t%ZMAsIX2hOS1-D&K`EIeuA'C>+2j + iQb]^%Qq.m#/C4>Xn(1YRf,lTdU35s9!M4aTMaCY;0=7Qn#Z.AP]UPMW[99,ifI[*kVSlCu + !TCc*//^0=LC6E)Gu<_!P^20mNUMt]_'ouBGna%UYH>^&\;GXlRpa*`/&XEmCLH,n-U8(;o + :?u#pJ7tHa9!=T'fn-d;sBh1ntaBXH,_u>DK&'0]]M@_1fGUA3%FcW%%TWADX[#W6*#f*g. + 5fAaLbRR3+I5g5P5kGXJ$\6&=Qm"kc/f^<4'88_ejGiJ\/n4"cll&In)`Y.h6-L@PdNgI;U + d>3\0,u.P.eL84oV1[t_Fp2uK9s`lqP.Nsb1nHkOUf78.ll&Z6JDPRbV(4Z+[ZX36Q@nn6O + :Rt29UoV,dL+]K+:Pb2^J!pYktK+$p;,V4g@U\_t1,Z]KeP7)EV1jZ-m#7j1%K[>;?l&];] + 4,PIs!+-,d.)`49^ho>/'Z:)=U?]L0Y4/5[di0:0m\ZOBeqP(#i0b=-5KZYW_l<*@2A>J1D + *]c:<6dltbRe>Vr#W>oAB/(r30gJ1A.`an4$F5B(O9?*1S7[m.0oP:iCCq`P?^/T#.6u]km + k_#npeP`$c[>OaXSOGNCJ[P2:gF$WlZ@Xo6Ndrc#Oo+0eV8D9JJRQ#0V+u97s,rUc1>l9gb/XPD]shWt<1\'#VRe3qPTt"@"3 + .5YO:+]1X,;>XU\r/]sh`XA7c3r1J)qZtuA$elt]r^>&K]X4(h6`[.7 + Z62"SE[a5lPp;m:;Oh*,]6*Lm_3\bN5F& + u(PK]kW&8-AoH!mkVP!-NSl'mib?HoCb:f@Zeth0B=Z#Cq%("i+91eIg0AKM_o+d1(e3D%' + !A&jZ>Da`S95%%*_F'@C>*;0(\oKo]*$(a7+q3/&<3N#U1#*D.iA0%D&RocZscJOatc*&Z;f%0jh!ptDH)6;9a++d4\F5+9 + ?fONbsbADj\IZa:bsr\(j/_FF]2#:aA'Q5:1f,E*-"PtF.\=`kjj'q!E!o27IQ;G8AsAL:;r + QbnHJd[[L$]dlFoU"V'(;WKMc9XQQaUKVLhg3be^MG)DcWS4I4bN(`G]YC:+Xta.a]*_\>b + HfUQZIA+Z>>7@EFg`W&[o>1S];7qaSOQhrc-5"W7+9"^XhRRU:e`Zjbrne6F3%>Yf3CaA@i + I:mUtSg7O&]-)gn`e3[925.i*>&Fh!NFp7-%@s7RgV;:2."Dlt<]-eLBn:.J74H]k+S`_EL + re?**L#`G2f/h-OCJA=cA'Xle]Q8n>Y1ABmq\`_U.%:ZUhJ#7+K9YSMd4%pl@MSn6gXA@#V$eXpV^F-,OLG_L8"Jjpj&u3SFomi=*"G/Yg2e0B#)0& + s]@KQ57&SWDcL'0^LgR[B.i2XV<*%iS;:Ym)*9/ZpYVI@NfZ'@7^=']]<3L8X@fd9., + 3"[e:<([6AF,;5 + d8nD%D>dhc($:Nihte8RXcd'4**GFhk10[gAd.>r#*4da3@8cLS0V7C0]u0#k?TJhUoi>BY + 31NMg(k5tc?d@i&mA$DY[2b@m4r>P2W9(`kS>#2rds+"2rV/(m[L@*AqjLe_V5_ABT,8e'= + HgH,A=bVgO>,dMn;q_7'^-[e96qtp5uYNd(CXOZID_8p50RVe([TWfAt0^k*W[mIPALd3%c.=cmKHJl+?9S$m9LpYK + (NHJFBQSR;K`Ld^3A#$YO11lmHtQp,d^`EI*6)u95`cW%rUJN7C*&njtSe# + "-ojfgIo+)^/XgGXpW7Z14YA*D$>khSX2T(Ld=c#73V@\RksmOV'ZPgn9LiqA],j\YF=`_D + d"l)Qf;/QLStS@o^AC$!QX?-Q+L#;d^cE/p\nguU]6Ch)Imh*sY)Mr3fRf2bVunJ80F/9#- + An779Vn])SgcZaSOG^!eGGCOo4O*Z#bH[!Tl#N0.s5VE1@+5Wq9p_\DR0-\ak4SVY9Jm]l+ + 5^\?Ck47*FEkdKP[u5'8M/8^sCp,J=?>&j.BM$4go-Bo)V30oT2.k/m8iN&WKV\4)QqD8ID + 0J(P7bDe-$YNKK=lYqArXS+WhSRepkI0=@7AW$u7Dh"ukOc;`2b;-#k0mQr@A`8"cK\iXlJ + ^9sFS.b0IBhc9M=1;]mgiB,7oaIdZ=d<>Hbe-kP16C6n"q[Zf)/c0;e#VYisu(D?C> + :]RTtMKFu@B\2e?KCAj%tWp3bflNhKs`f\I?ZA'UnDf5>>I%JL)O&mpJ2L + ZhgBXOe9sR3GLtCD:Y'rDf6r_2rLF04918c6?8R1XqqjLA3Pfh^cR0g"#BjL;.2I\667E4dsc0Am6AF$Z3cKhsJE9Im]mVB@rYi6Z5c#J%"!Uh0utEX^Q&g + mB*PODn09CpT:Rsos2A_nG'Xk1M)'Tjmo&2NaG/F^?P;p*Cdp?TA")T44!W3o>0GUpttIMh + k/B]om,.qmt4^!npe&K5@;1[nb$-Og5kS!s$-b"r;mn!1_>/*A[E0V6#^WS>/Dt]V$S&/U& + PM<0Z5gYQ=p#g&&^1*6s2ni0r#FN`dqTK`K>7H(WJms>oFUnTq<;>kP:_ja0F]R*u2ilSKZ + ([,`GdFP#7bs8OdDrJ'&CgUFF@)>brthP16j=+6I+I0MD!GaYD#g'[\\\1Ha2Gc'*P,36fp + :iAo34XRS>I;nViec]e;*5gQeX!CBU^/J1@t<&GO]dBm1"8CA3C+%js\[4+bt<6[Es;q0Vf + :=L"!78hVm4\8UPeP01He^[Z.>p%'h;D8EO!!-W`h%Mhq-ukLZ2Er.=>G]^#=q,=-;loj/;l?HQF%/8!.>M5#% + :&Vg^pt*k?H%J*$Kl>^'d_[FLc0=iolNTD@o)T4f,aKQ_#)1$PKIa.AWoSS.c]Vs6f;3naP8CSsqBYb#&'`daR=B$[f#)d^a0mCRZ&[r*Shi,`e*[d1Qo7tJjpDl8'a^n9e8 + ahdJZ-On"b`u4orn[rKTaIHLR-?jKCD_?"$kAN6'F+!F;E-$F>g!Xu_q:KWY*EQ1iouC4JJ + +anLA(dDbmq(9tkSJ2\GGa@'i#Q,?KRt=[:QU5QaCgq&q.R71MP@&kdHVsj\kJ7`8GZ/HV7 + ue.R3j!t`1;:tcd8:(WUJE4-n7I>UOG]WS,0,LQgh#).Z1e9X(?+NN[Os2i#ukf:`Qj/(_g + 5uZjeZ"35$.7,[\b@'emWW$lK=-@uQP41Bq+,RH$@7bgE5>`f/<6EOcF9>V.jGaqu'2C,X> + \2)in;i'W-MEZsD^3KX1%*@QSa+sGN!kB-26_oWP;1$(Q\4S>;PhDM$qo0-gL/0D4j2,FE] + 5Y)YE+F(bS&:A%Ta>jP937`Fl&=XqN-ZR!P*>YF=(@&rsG-KN[IP;7S^8dRX/AL+%8dY_!r=]6d2<3;FK^/ + `>o>qDh*Z^0%j>Y@Y2/`kcuQ]h>T9GoTM##M6*_%[ll=d66X,g@/^4`=%4V)7>/e/i[9'?u + r32V(DX#,'=fNgCU&fYli\9>u]^Q7"_\XQuG+[WkQhjZD+JjdsqlO>6"lar)c/T9K5TiEg6OH3O^d,l6Dn!cCNCpMNI-RA$:'`4k`mPG?!Dc< + HpB,iRN^B60dLP+qHYe]b2u#SuEX0Uj/&'Qa^.)kprm^J=8YLb5,I*4+4NlTYC0_r(9WV.F + n?s5H"/RMX?(I7=rFC8.bl\9F69(b/bBd+TkCH'/1aKbSYC6@URlMK91Q/65W9"N8u:=#*o + oh_c$r:N%]oTmarJWF)9,12`WSajIFtHF3]_53p0W37!Ob+%kYa_;*ainpo8^N]NU4JDD*+ + OUk3(j@Z8Aplc,_JG*&eFNTbKp7mojld8YL2c8l_q8XMN,_N(! + RNNiPACWdOjApIXt@WA<&*DQ:GT`D)ZrHdD3>Z%4m+^dS?pLccV0%SE[F&mbM+(KQ8=Qac: + qkL!e4bW#0V%`#%j90K$]g)QKVO>bLAU,d1!tAW30^,<+2fSL9%ZPQF(!sNQQ)*\SrJ'QTH:/+`J=kD)[d;\-ZaP#g646G#%.kpT&>[fBZW_+gNpX6og=\i/B + b%8Q\@37A+PYY%^^BcZUO:'TKSJ32--/*H + )iB][>m6?A*1:j:4us!RX:bKK9c,[!hn+\+&VZrQ)SZ59@W7'VVTk;GFnf8r + #J5qFY9N@3ho`'SR+eYKkmf5[I@s+ENSQ7F=0-Bp66#9Y[=!>f\7?U5DouW+;nV@p,9o+iP + W&]V39QeB$4=m1Bm.-hYtlJd;CPWB6Q<5h$qli6Wgb##.5$.,"Fn='arV[?8$$8AXjOKf/>ZO;l' + ^k'K@_(Gh?/-9_1>_+%-%`&TGW#W%q!c[$*`L$X5"9:`.(.>8`=BHGV + lAX^nPh\G@]5qMOG3Z*LP7I]lIrY@<=blMUQ3NnJEpg!@knU;m-@TIm@uZ1$DPBHGo(ie#F + dK.lXJ#Wg`k6JQ?Gs38jF.ocRDh#M,#hG*)4$Yod&m"c$mLi)ps,IZLqL>YGD""jr`CRG:M + )[$^@">1i#XsUb5._CMhit,>QDDpOX3jbrKeaVtLRW5!$o#gg6m; + M,@_Kd[Rh.+4#&&rA&8O"#+5.?^;Pd\A7sV^_jP)?EMD?k`VA%h9nO!><*bEl/?,``li;/C + M+4TaVJTr9t9gTFT^/&If8Vk7F_@@aa?jZL\XuipX2-&T"9UXJZ[C*q8\HbHDWn`eP3<*,2 + M%1!_-Zaumqp1DEVH'WO^Lmlo&N8C(oAo^>Khmo7Z[=Vbp^d?/=p$ArpkgD0D)VippBMd[86q4OpN7o%(7o&hM?4rE#(GH*EZ8L2Kk,n%HI:gW2OAL5Vg]hiPm:iQ_ZFX + G;t#SM2+A?RAdIk$VX.^WPOe71qSB1kI<0J + ktQ5#RQ8>VUZ5b8:Ur+@e@9%*]LZM>#ooe)Et9@URI%eiUn'/.kY0FXp7;'.KS,qu34nT,8 + 8dh\Z2*trQ;:<'P7'."=`.*_$I1@m.T0i7L><6pO%1[P?&8oW08(A4dDEqLM304d"8.5eAT + WNgq2;eYq2Ii,AE2b0,-lG33i>8cmqXV6B=-bS$8XKG?s3=>-M$M+(6^9=YpYOB:g9*X(7G + -MS#Yu0+T!o2LN\K3?+*fDZmX#4$Ybl7/+Zr-^(&ck/D!o8""!/OuGO:8Qq'&Ktf!H%?kZ6 + '-Hrnk'b!jX=AOtaFE1>tlq5tdmrXrft?,=5-R]6#P,CVIG63[.uK.[keUicRN3sPZ(20CBjP@5902f=U;pWYu@Q + L]:,E>]KW2g%UcM=L + 5hGn)kb:^KInl+-6-NEW3@'W;oZFh<88in.._R'd8KV+ffm4'utMp.,YXg,-ik-D@]#8lWq + dR?8+_LCTA]l9T6>-"]A-':gIFk=^TH??kbgP2?sId8RTXV-OsKlDIZ+Y*Fou0A,";[2pHb + 0d=#*NI@&i>GN4,'jaIst`$%eYi)g,hT.Ah2)`Re.9O8*O#>+0K!.g4K,K2?Yd4dJ8J^W:c + Qm0YZ_T@$KBN"YWp5;PoNtKs,K?UZ9PhTp$'(M9]0s;a8F!!.qYA7&do5*Sd-K.d"3Dd0)1 + _F/O#7GA`(GsYbdFBHX05FIoj\kg<2KnH9[dc9Lo2G$P<)l@G[f[MqTl@>n)OD#deQR?E(p + 92G3L:qnQRFo[/:O[nPF+S'7FmpbeP/`IXH#JU,Rp;[d;>T7iHgn[.J4p8Meo= + &3QqKs=-`PlY[H6YmDtT@FC*p1;SUpL@EOB3WjV!Q]bQ'E)1FH$@ffGRu`W=X>;+[?@m=H!)kIOAV3`.s`s + JPO:S$Q`ZK?B6uI(Pj&Oh=1\_2&AK)c#^=;'q0eVkpTIctQl>q,RfBTr"0e6qYhKDej\QWK + rXE#+!!,0e)4Xo>pTf5r%G=ap%olS`VFW-*?Dul$MY(hqpWE$Jn)%0[E%g!U^,$Ln,GoIs\*s3B*Gac*J'H=* + Y5sdRf,^&R$oIgBR2_!T(^c\)Sf$L'[MrMA(ces-9sm+,bad"dr5H#?f7Z9@B + tIoLo]IaCO()rNU4.2 + c]T2IT+U"t1H3/mU$Yr95$plfb:31lsI!.#U%]QI-Jpoh?a8%hd-Mqd&JdD=R;D`Tg,feL_ + KS*d(Tf%O6^F8Br!Fd'TXl+gEi_"%@d\G:9;(TAAn_6pX/R[L/.%B8QF_QESp>VTL=26,Fl + WtZ4lZkmuZ*Q3cU?G''tPV&uEpViJsXZFq5A7J";9pFFIXuTG#qWY0>7_cZ1a-gGZS?RKn5Fk"XKRY(sg#$OKEUn\3*l6>G6&$1AAe@< + 5&)a5i^mG5JC&gL#!';4+^a]D9-3ukc#Ct_A+NbC,1(n$:)T'eGrFu"1OYh&id275@\eAr5 + KqY!8DKBCAC8k-[NGj+rfc?=Q)o&/3Ii:JCeo@a11Q@<=98gF8gDX)+360e"`MM55*:"?pF + b:^gCY[\?^Y%X14kZaeJ\\jMgqDaH^#u?hr\iU$5/IYq'[8f\ + h.7pFATD+\XsVRD!(C3;#$9l!\/efX9/c$Ht.Yh7Z1-BcZdBP?,%O7nIRh/KjY?gS#Vf'dd + p>uhcbo03&%J$[]#4>%9VK3(:jfXu)X*3I4kRFt9?rL0h3B!XM1_QQ1tAnW'>VuE#YVUTAR + >2;rg=g6LoG+1Y'bAjrFmIsi@k5TYAhf-(F4uX:?`8u"7I%Ft8S^HTlqAi=t=0^BEFh=39m + r,nun9Fj0?Pf3RkOj.LSr\*!d.UqJ(hpo$.,8&V&;K0%^aK3`5R7Al5d3'U"@4_@(BoD`L` + @QBTjZe1$"XtW2OoK+0_`8aKKfu80^M:@7$/0V'L_Ij9bp/PCf_EB0aT0g$rCJQ!eBgg^E+ + KeoR@uS1),KdO-2>>*!62N/9m]j$3#7AdY+GFb&Xki/4u)!h^D-QXM$;\ZP://*n',nJ'AX + +cSM.+9\ati'9qB8:,B!74&7ZNpH-^-eGV:"1^qtjbVLOU6Vu4\Y=6VkA?:(tVu7i3dT!%r + 92d[8fhKqnV)_>DkFFV1e43e=<$$0F<,\5&E>Cm&!Q]'q!4B1r>ZWf5G6NZY+@\2H@e!u-P + R#=[E$+7ga^nV@?(5LJ`u4nk>*b_[Cg-!:WGS3Wg"UH"np/u0go(+mF4hpPeTN&/?/0AT(Q + mBqSS%ElHX@T&o6qXf`Val:(\-%,RtpgiK4,T\'82ifq9t+.(k*]cRok+jFQ44Ccto?Ljun + abq%9lmA;L]2q"kZT.PC$9k)oJ#C.BGBVX"I.[B:V7)546M)ZIX[*(G5RZY^XFdW$/Z7btMFk76=YmRD*]]Gs#D + _Id[il+t.(-i(Phn9ckq_]S%:Q,Ra5?Q7'*k"c`6njiMhad^:?0?s)nk<"kEZU!^.$pfg,9 + F-Rlm%?:7[#)h=2'tHBpZi,VCALHo;=fNJtROkqN(D#.VInAIYr`Zr$n_up9#qgnRd:TOLC + I\gffW##;1_S]g/mo?;'M/J`[.C(m>O89KA,c#!g;HZRZ7hOe%.0E6]@&RgBNIUdbol(W*O + j$R&J$L(M':6Ki+l@B;8>d,l<8\8Mmde38YLrZ)I93$j68Q&uD774?h+UXVVaOqKrKW(AD1jKa51M>g4@R4fjc'VA)48OUZ<7j@#9NK + hRp6qXF\V3M1e;p(8+.O@WcjfmqfUJsVe&Gch:O>)c;#%:1^$3U?R%LbXB.uTNn.7`=_8aA + NPXd-"GFj.EDo&Ejt[P!Jd:'&;CMKn+8/!$V=3QF-`LumF8R<,b^coNO@l + 0<>eFcNK$<>TE;.^QI*'kQ2_pNl>oo>7R>.HAu7$X$eM#^T94l4Gd(7Gc-,,D:@0]skdLl` + hjcK['&$P&=#drI96V^%iA##ZKWeo.'4iX-L:6=%`Ak/>\H75WAuM$_;($&> + l!;@C=9+)+E]*J:SLcW[8>BPhIYNV:@M>:AmX@'+M];l)]-LX + h_]j(YI&od5eImDdH%:gCl+Do##h@D97^Y$#F!de:>iZKK+oT*_"^8'-0tdaYd.0Apgm/KH + !mRioWqN,L0G+,Qna49?OMB(!s!,3MfEik"9OFSl=I":M(N]kK=L]$u19'WWPO)*H9'2)D\%7Pe + gPc4)M,2-N58>.l6.'U"\=9_V0DG(+:gAuIEgfG@<+eKpSFoU + U[6`6PMPf]S`DtCTdG2@I8QXap?[0J+YrnJs=l+"1ec\%B4ZBQLFg3$8]&i?]GJd`//UH%; + TUTaAFSi-Im!^i(V-%s-@UI^3/"8r1f_Zu&90Y1f0q=D`c.U02D8ugZNnO^%&irf)=MGr>1H5=PjD!*]M)`k9&a>K\lpjHjq><=lgIJ5*"K%"$(IU(BVD!@B!;)YeC5%l!jg(4oWK4Y + JgW16G[8$nR]j"\\*GKVjaj^:27)lY4m4d@*;Qs$6MSN60&Zplk.);+2S\?#,"s!B`Pti-kHXb#2CQ9;'CT4.&7\#J\W58 + X=LkT!'.rl3QWka$_P!M0>eJXUm*K!1S_201r@0g0`"nhEtG1IFb[snK+K]Q>StAs4H[D^& + ^WOj?o*RGDApF!Z2&Ap)K)G<\#11U4,^YTA4NqH]<[>D]@qWolRY-p/]?,T4;j6K1f(bW`P + iM1W-cRk6cgaWatn%44M*U9bK`6/X>@IO4T!Kp/(-l'QlCZDS!2>Y44PdlfQ_`a_]Ug8Hk"hl;muWk,=PZVnLeYVeJG^/D6X=m%:kk6PmDLjN9rWj]X8m67j^^;F8 + !'4Ak5mg7b;X97r!(i$KPAA9V32M:Y1Oo<%(aN,"9Mb@lfX)8+:QjH?s]Z\\*!,r-m?poYJ4GVR3(V@ + &m1UXM=[\kB>Z:h_iUrShV]<[(%]AuSiX@t_J9fre:7-cKp"2hGQ`A8Gh75Muj9[fri;PVNZij[J1g`'i6F9D;1gAU%.\T"pg8[5,4iCs4A7ro0BYFUac=3n>*,NXM + kf2ph:8DgK$uiV5E^qs.26+/]oBNa%G!=,o2=k3+d_7UbH8[eWa1Qf7,A&%0B<5"NTBg4+C + &pjUD+Ha*TMoeg!m\7pLGpsq^XN06,QB]5Mb(468CMiV4JKte#Z-ckd%q#-AV!uSYKK'XdZl<1N@VRt-W8JA\n!c81cYV.,(^gM;^F2\-l2n + d'F$g.$bXD>;K0QQ[nLQ_9S9ppMnD`b8i!>f1;$<)gs*duc8,NR]L>0+)ob;sa@/23]U[Lj + 4OuX68A*a0.3.!Hh+O_Hg)F;O.?%S%rD0dff1$b;1i2)>1uV1TA^2f!c9'l4I-!hfk.VPm4 + u@+?6/ooUGJG'EaOM6]FW4D9*7p0oUE'b73=Ju[I+OJ6anR-4ckt + 4hJC-n(!@SJ55YqS8I938!fN![Xh1cAH/cnijU`:e9*%-d?m>.h-cMLhs)d + V#N?9[HAa-0uQXj;kLnZ4;:c'$r)m"&=WRAnc1otN[hVGVKU>433EnIK + l/5OSK"*4\D4s%&U!-e&6uE@UF@])Qa]`2f;q&J7KZmVb5MDr!g9j7N`6lgE7,$J"`C/57F + [rcsf$p9/gGIP=c*Y/!9f)aV`U100b[A]Mf?N\\]q&)%6egA_RY=O);A/f_pu@u:7 + L@qhCIB7m8a,-:Aq&M2$JRE.<@"k=_UrHfL7p89l;nFI9jZMJo5,[/NBP"3flbc-p6X + `Ec4DdM]2AXe[(WBu'e?E_ErD)TltoGZ+`ACkZh!_8o9kq$dJG*JArT"W0W3!)3S!P + QUD1JtO[s5TTs+k."E^AB)B$[_2"A^[9D.Ktp94g-L=Fi0"Y!DBK5Q1+=P0#)AjnP'a+pEa + **p;V;9=YjPe(p?O#*>@5l:DSGQ#SmGP(?Y*qO".2"Lrs(I`E$0CE*s6I1Epr`+#Ccp_.!p_/DMA-p"_^1(coM4F++`G + GJ0mp@NdaoUIpeju`,2=.`XO;f'\[@qkZ/8YC4KuE_:lN>W(n&jVMfH4e/@mY@=>B=Q*N3Y + =k'mBc(:+=K1'8$VmY9ni:'#7 + /q%WPhG387:mTfJ[E4SO.?]OZ"[cd@?0N0YCJUeO>YX\g]FGAbJDmt%J7f2qorccrln-=F2 + pAf.sJFG4C#DWJ-.:mIBKGK/R#9\/Di+W=m7Kb+[J[cW7CTV7*Ti6hhe!hXk`e%Aq'kffHg + 4'm*Xf85d&oj1&e1V`i3eq-IMS)i@2TP>4^l6eB&2""iA2/7!PF)6'O47;^"ASS,(lF##\`-t + aQY2aqG2R'Q1#!#t56/@:&4$m&9W]b:5#Q-K,>V[GSBkaXS)&(r+FUZB@j=86\Abu,T'/PYoeS\gnYd! + L)?"r5AL0%n_b2IG9rW"s5WhijECbZp=F-T2IHFY;Zm(`"18jF&0HJe>m5IPpW)^r\*nOeI + W+ZRE"DO7#D)nF"F*.`n8F#1DtfQAq"S!((6EG=G[gh['7`UV!(.Q1BRgb'mahjV_$5D$+R + l='/\_UI]-8sgqlROHMoDG'[p+#+*fe:um\oc@7bj3M>uHMaNOJk/bN:SePf^kFo*rQe39e#F,e[5t>MX4CYGB!#s_>d$BIX\0Cla(;V<>';E%WW(t + dgb%nDS^ki(]A)$mpDr]!I(qKkQmWpg%0(<'nJJ?&0-&ta@PTqKqhHBSB'"(oN7N-gb^[,Z + )D)GYQ_Dm<2*/5DI?gqdla@`jq^5UVg:uCsB(I6i^>A>R:$+P9]>`h#\I<@70tY`p5MM-'Q + W'g4kqP7_[bkl3s)#"<*\S%b^r#]XgKoqJX4Y8]HlHEGK4TT5qgLa/TAon?ru3W]YlPZ]q6 + :":n2(^[(T7)4#/TeN+C7@\Ts"hk#PkH,_bfDQ%`:`b6P5cl&ZT*.nN9_a78[cQgSLmI_'W!Z[dr`hp:Xdi'4&OKB,t1_^'g5Pq^%0#s(U4-B3D`YfS#_E&*s(dMOM?J*qr^%RcTq + 6!IM'=!u2`U%U]kT!t=*"am/8AQ.q/7T)sPCl>(bF*E1#L)_21Zju + 9)FIJ=+[d]2k/nGY'4Pi&J\!dpnDHZ4L2fs3b(iJ4kFC1Q%o0c'3meB'NPi.9Nre*"OY$)F + ;7:fqJaP]V9_lXpl@.a/qEOLe!2*j*JIkdh5pKeB`^D*jd#&W+Z*#r-ptUY"Jg22TME.Dq9 + T(.4/!!%t)+W1iNT:.$O=8d)aAd\O&g5Yc.3Tr^ko>p#Gg]3i9e3'>/=.Wn=\Ouq0r?eQ1a + Mp5PY'+[8lXfAAO:T:.KWWujS5G1Jqd+E-t^Xk*A,pu*g]de?8:,[0'2`AQkKs:bZTWSVBj + 2^C`VK;os2$g<6:S-,&0ge_C>[BE,U72B$Jl)$,Hi&Y + #*ck+[XZbi0q4?(#Hr%ES6\^c%Q2D>.\,VUe!Y;QHE'"B4R!k*Wc]qsXudQ7lg"AVXA7?O]7JTn#)Hn#!H*Qba+ + j+j3=Duc0S_e-Tj>`de0C#Wlk0['8BEC<-+4<"4sFWJag:.XDmf7?_LtfoHh)W^:ISNZ9r:0]j@m_iSotq9b!g@d1ghXEUC<9o4un.%]2+I3Q$s5ETLU`Q5e1UCmrn + ;3_Dnf[,@_<,IXM]b!\?qD&pE&pRR1-Yr=)(LoUJmOWR@ + SS7T.ju'#(ep0^;C<>X.^9&$:cOJ9W#9'=-%r^(ME6;ar*)F+FtpuRcPPVHAW*+e>FCf[6p1Xg8e4R%:&"pX])7FQWdD3[csgh + ZI`*2Z>8'3YNlbZG2!:H)$,=@_:U]>bdt:qLRVQ7`HHP"k.Y\6Y@$/:JU?-gn<2tV@B<\sZ + $&'H=Sj6X$nR,l)B)kP/TYfW4$#(iNZ?6C&MCWL],,7G7.\lOY3k/)Yu^D6Y$2]fL$+D=X-o + Vrh&9+[Cq^0QqK(72TRZk6cXl5; + R..HD._t]*gi^mF5-'r:BEn;&gXNfMk:N5^Wa1?IYmG+!c44B*\+*Wb2hn-J6b2RLOJNtI: + fe=3D5WiK^d0JTN36i54,TVL+:>`AWJ+Hu6^j4frkIfuMoLf11$o(3QWT;;1^92(#E+fZd^ + Tl)@cTG&eps&H'\^0[#88'"_n+;'hpg4-^r#.quIRQ*fIkHZ`E2F=36Nm[ea;>CQY[FJ;Vu + &>$VWu=#<4.eGF>[Q4eZ[6#lNc[U^TV!\?&/=WE:"W4qb%4"!ZlkM8R:(0B@fkRs6$&Y:]r + !mri`@&gPZ2]mC]\5^9LKSI4*\&M]dHRi$iFkh1tFSVtC.$_#pgt/Lrric0F0Qo)u*fJW!F + 6,7`W>>i8?[i2A]FbAt'"E8bSW\+"?jDBRpK-AH;t\SIEh#]SfSS@UD5L^l]2#`f:*P6n1X + ]48oF%SYsiUucJnMXF`^KD%PA\@iAf$fi.a%W:HIFM=g<6dC4BUe9iGLlZG4^Wr"(B`RCXS + A>LK8PZ"rW?UVfY3PG\P0>QEWBA'N^r+AW:;NOgY0+\^>/]qNQ>Y9?%m^hH7hUkM'(IOe0e + 3oTo^NFg'6,`@%##@uU0W:a"[GNsJSYu36HnEd).?DT@,;9mOoUR:=n,]0;0*FKGo(IK(K# + ]^@&O?=E1*@:)!RJ$JI8+<8G5hp):>,Nm.U0J.2n0mpT4SnOgOo2\G307q(2aF&ZkejW#\0 + )'Oa!9V;;GOPTF8W,4Du^0=:VVRap\tennt?0?F([b5DqMf8Y=ZYM.Wl#lX5Q+DlcSJd+!H + IMSSO"FrmG@'0h:R/b3t!!4SLCA&m6$7)o?!=cPd$Okf%*o(]Z,jinZX!IiWegcb@h9.`(/ + a!6E5_+Y#h[<3_cr/b_=!t%W-gh8KE+68[B&P.,#1.2FXrb)NQo?dk#Kr/6%PJ*$G6j3e\k + nR$K<5H9O?;s'jKPBdY#;ndXU?o.js[Z'E4k=nBYBMG^$s1Y:sCV.mR3!(k\Kh*W;c#mjM4 + NVl)YQj$J(dUSJD^+ad^]RI\(DC"3;hI&N=ZEaA36!uo + +V"uD'+haf/+%^EWmR;JKV/V+YjZQZ5#]7_B + +9J)W8+4TV>iB.3d$7,8)p[:^mEF[j2=d,Z#D`mDDA*bhT6>,r/OJYV%E!cR'#S"7SH#P%H + ;qGm&p8"O6XkXn`,pr%ROD"k&(4;MbU@N.eL##;VV)0R1+5Ta3EO.]tW7o!']E\E#EV]/%, + p/%[?Qb:Kf&]TB^_ZIMZifY)0>/a1XlP2tf,k7&s30/E#cde=5,r=2ZfOL#*EN#>H5%hGu\ + lOt'"P8b'dgG08X&J5a5nJLXl2%h2Y&lXok1QPIUM,TZs;l+1c%$rhO5u[GM;hI2[he[/q=`!`6G=bW^UJ49YbhAek15>g + H*cb2R1@360bWpORDWjb8Ks$;_!6*K5iZ4CAc-"4(q@HW^`!:Mq%UEX_.s/i&+/ZD,fq'2V + %gGDU.YSSR*9**OJ!_puBel>190h.CDZW)!91j7`=C>RHk-URJsEd:3]JOoqmBC^f8nr%1% + OPuFsKbU=@OF%6#.k4Sj#a2;_8:9I`W@O!!/'srGYbb$4Ws\13$W>;F/nIb]^?4q*/gN5JM + sF-`KeDlK^Q+?b')O?K$SVl!%aa_1/c].<9V`aE9nkPt?!t3>(Z@T!Y=-LoV"6NFFn[P4(HZe!BNX + 2;"oqM,!6bio]3;V?3d6!U[BhpAT,Ldj>F!N[2:GhIR<_l'H#qG?Fg;qeRMb0X!*.qX2maJ + (MEnT=qc!;tFnZs,"^fTr?o/F2F7PHING$QE`3CaP=">k6(CIa"tF+^UghlBa)crip1JHcZs8 + X>/f[YfadQ"^eR.CPQm2Od=^R;*GAj0kj9/$!F3SP0g)jNsf?PfgWQcYP,V/Sigl.*Uiei4 + ^neD+NK"OOoA5hWV9<08opLG3b'/!H/A]:mUnAZOq-+%dR+/_Xebb7+ZD1fM9`6EA]";[i! + (A/Ml!iNq[=M(MAeRoMTlgVq3kg\FRjpYSEiT=]_k2L&q@kTG53YOA@\IJHcRV]EqtqOr6! + 9GkmP+1DbTF%03SA)Q3BPBds[GsIF]&SfAh=JWIAKCg&\u0b0`Tm2.Mk' + `j`IJPo3nOQa;8JK5dc\VKQ_`q1m6eB'879(\'?!!(6 + [Z:@tnkS#X``haE]91F%`a%LpjLBlaM[;h^k.OC^\E<'\83S_+iRM:,^IYGbGQPj\GI>D>* + (L2#C(Us).060R!%AW?DeO_eZZ`8fo]YLZ[G^J@$D=Q\ + Y"ShGD$ + f$i&5m9pe<0&KqR'u8_/lp3YS3#?V665.QD+P=sUZrEJG#f*C5i(JUeH.-5D([&:lqU!:ga + Fq@2Bl%UdQoT^iPC1(]!$(*P@CiLUfN4!0GWMki&=Uj/.V[,7\IF,AUN%.MI^ + !Kn2VMA2a,(-sJ_5t2FdmLk'F%=o!!!5oK-mL91R&0TmGJKPJsTG>9o*eBVNpos!^rGL!JV + Xk*)35MM[a/@H!_O?)gG6Ys0f;Ol^176su(upWt1NCqt@e!b>+i"k%.UIZ#'gWB@+ZL"*ck + 53s$fMR)J@#qsljQ0H&0TO>!?)HAWYIf>+!HXYg=oO`;A5*:&Ku0-JG'\N#S"U$%fj_u^qL + B\g'`=2$Xk.f@)*EuoF/Q(&4"k`_'BO7U(lQ%*MIioh)Ef*E#O:,&YWqP+S4*5g_4)j%,iB + ?JA]q9?e%Fp%j9$>JT_[AqANd?&[cVA)QoR`Z`SZ"SD?E7op?&9H3SeQXj`3W]g("08(>4J + eNFGsSQjZ.B@ab7f0+?0]mnZthq[?0+/+o6+h/9g-f1Bo%O!qF^sh![_pKU6&V/dITf_7"D + A#5,)SoGdm%VXNiS^P*dfE/8$@2M$PX$A:M0#5D>7qtXr1rJ"JsLAE,f)CJ0DTGG4Y%KOWW+E.Nm + .KWBUmrto=5ZS$'jpXMX(njh?_>G654,@q'U_^q+)G5s/h8Ym]^-T=H+[cnbjpZd5#[hfY! + &i;G9,.Fj%:HIJ]_E$=p)4(%VOY=R35*O:p.@),S]!?I4kj:W=EF9hsLM?Esd8PkAN7ie1;,nr + +K$_'J\'3WQ9FZl;.pG^b%YIK4/#A;P.;EO/J8;`;99hr\Qt7s7q2!KYcLA + k+,qlfU0/?2)X:.;I"KME0cK59,0<-U#.#EQkkUNpC7TKjFM + MZfU5ms<:Bk>a7f01d0]TT-M,j*fr]e?5U2tES* + 0f6O8I0%5]8&u)$M\!\h"1tJc9nb&9K'#_D%c5$(aXYrsV7kbVp&"Ylm'!6cW.`&tVr_;\M + T>6s16nVjpBsTCJFe)+O.lE-\!oUj4!$Z35Y#8(&Z8,04d]K,mO'baTPCZ#$0Ob&MO($ll. + lU]O2.91V=/6`_dE9Q5!t1J:)k!/rcV9V>uap*LAb+SS + X#a]=Wcg0]j+W=c$*M8B'jSGfG5(uF]^ObN,Dd!@F\rBehOXITBAqFC>+uY[;&o99t>0ak< + ZFn>ZVBSAXCk!2=^u+Vb-j(4)I)=oNH1GP2#W?q-:XEaZC@%>8-oNX^ + `5G^^idS7[L'MW1#313"2pr\oktR.8+-A%Qd=]/O`lWa\n2qcr_JnW:#K3)o8[6:%a8*!]Ea4YQa + d8q$YKV&mcSf*N4Sc-d*QCBO7Lm@cZe7bAZp+3p] + pVki0m*4Mra[T=.3h=BDJ=dHo,7QWSl_Qa=5!9F^JL + ^L#6G`Y(Oj-6mJMHBYZpt=kPG59i%?%f!)SaHRR&&i#U"d/JO%ZM1PI66$\9]IJC"u3?#&j + fTGT?cd"!m*&;NM)Pla9]E&bh@QP63g0*JM.#f,@7K5f>Z!l.816b$J0j%r_]fMN2g4@;A1 + 7L=jc`Y?6c+p3q\-P@OY"[J"]#GS"]LCrN"?p$h0U;06R=9S\7'EF)SKGKs%*ts30F,\MT0 + uiG#)rm0&Nfh0XKTL9a@MD7lEV.ln;/R^P-j,=eJ3\j.XWO>!>`k1%7nBC8$DCu_L(MKG86 + IW:Tu9!IOSU(jfc_aOPO\qJg,[;ha?,+FT/hPN@>Zchm"(iI!&E,!Ota`dJde?8nqpbsq%O + j$n5"nX*'ab9`A18qA=7+j&>Aoj1hM=b9$'pZ9j+]ak0cqmBf^-+q7Sb"l;*l?Q>96;XY\? + )K+F[8>CZ]!"@.M0*9ug$VRc2bVp]3PoCY)@PjY-A&p-p%_Le^T"%#rgMP5nOL8_L.UhH#* + ;JL&BWHC1uPk(ZGMN3^WePSjPFX'ndd9G%6:p$#M.p%tC?`Z:rZ'I_t=nL6VXS&AMQF/eY' + _Q@G\q*ZAqV`NkQtqSA1*aV[.g6U#V\.8q"$g7>%=sU`N?NtkQm2s=N$b__-6U7ZHP`cBAW + /A[6!@Cf,d!#nPudTe>Zl,(*%]gU1PTSgL0,)j&Z-KWEK]fph1KK;BsCc`a>l^DYmk,`.=d + Kk!U>"#Fl5ecLg]Z.gb#"7mMX!spEJX&H^1su:i&.M:?QImS9.->Ad]ab)hS0H!Hq0iJC#& + 56kF/BUZ?&NGC+:8*Q*-**TOfEORo&rc[*&Xl92&MFDc`j\rVU35%1,[DThBp0>?m=kM0L) + 7=Q+?EX#ip(B]SW_%T-=s4_0i%mc"#KM[h=9?9poL01DlU3JtrYaZ9bD=G>oLA:n5:a(IAr + >AAeqr%:$KF-$!(o!*cLS2iu5mSFF\%i'.:n/&ITWA_kb^9h-9*tfhFpqJs(J0gk:A"Q6p3OYRO0]MUZORa]\3iRgp-+?J + 6;_EA.@##`[5(GeV@8g+:Y:):^Q'[MW(S7YfBRWGKCB5Hjhk="I%)=p'fM8qS3rdE+=(IDb + ^mQXNm+9UPJGDG,E*2Z^4T/6!rBmKfWBHEV8l1TfZ/O6'''bAfY9hB)J9gn3NRoB0lO6ZC. + 86('`eo[dQd]-SG(&GS?.hQ5AQ(bEr[_c3`?Xm$WKX^YIit[/f8V1nP#dX\uK-29tL6087` + lhJbbUnTeqSTgY*G2C\T!_UVf#bC".:kBYR-sZV>q$FhcpsYcI!WY2]^`C9.J1q-,Lh[:e0(83Q`-B + lKk?O@AF"V%MH>d!O4j0l#q9@[WO/D@%#N5KuG#c2ARK`>.)[$Epmhau20nf%MaI@jj!$QG + 1"6%`^ml"h&V7:%C!=F$[L_8oXPSXBt4tZu.aQoD+.@L*_+9O;t!ei]DXQnq[s_p(GR$r$KnZrMR(!?RAT1-M]*9)Z>N.q + AM-W?gr$I+8QLVidHog)FC)d7,c-CfcHF():.29FNL,'(3oV<91*AWOn&EOL\X.h(]7Wq1P + NWI'be:iJ9'(rNYbnuk@`3_4jVn4WrZ%"P+Y6E,4A(ghs;5J;so"Sb36:_J=XE[eT\CuUn8\]:9FQFd7,+aCoNfFp=?>Yo#!c+kqEc!S"-)f"'@Yf%lejr'3@8P*`p;h + Xq`R,bEB\2Z5$E!I"E-+_%tcPiK'IQ4&2&RXmDkVPVt9Al'2?NjngH49D + MD+#Wt^m;`0`gmFLO>Q_(rJYC_Y"6\s\_o2kcbBbSR>H(q89.- + 6JB/):"2R[cZGe*4`_'P>0I[&KGGWg<9XNB_I)aBXd5ro$9Vg:#*+#1P?]/l9KePqhP`h)P + *mM@L:+I7a&.PGGGQB3)!=S?c]QuE1D9eYg&>XEn]Q?omF[n7JcID2N<:;22%Y31@-[8r$O + Fg5\HF1TZ590DG'2HU.:X>J`^IqJ'KD.n]p.."8.+Eprqtr\6LD@1P'taOfS,l0U?n%lS8B + d(7gLI5T#)D_fKA\`l;lWQ\p]7<1!FTpP=fSqFQcYG48Y$MR>.,r/S2Z+l6"HS/q45Y9Hq! + O9*R_eCk[k!S:Oo_&:sf(Fcifqb:J.A/334Dm;iHFo:P<34!*(*Nb7B+G?%gis_V:BeN3o` + ck=]&83Q/EfdI>>Jm@QeYbHs4Qagd*g^,eRcKMXT/jUBR_GRTrc1-FP)r7KJ^WCDD:KJ5@6 + \jZs>44?V]-[a4`qF8+,6F(.M&,(^568$P#!<,<4Il:0$)1SjNd"^k+(@9*87edZ("b%6<`@Y + OAsJB/-!\:2H'IFYWQa40j^$Vo$DIo!Ii5?*3bqtIj`<_r>aaAS)?A,T4Cs%jk@colS1lBo + q(#/EV18EC.Kn9q;j.Y;UbeCt$)boR(n?QWKkd3K:FV%^tX'J"j-2iO>"oU`mA(n'.r/,^C + &Qu*#r0=fmn + 6Y/&@F.lS?GZ.F5_k^I=@CS*.4&o\IeCC?LFMffB1K@UcKCd/_NMiVd6d5e9XIVi7A[!KUB + SAE7endN4?)h+'Y_go.>3bldM[Wfd[os@reJNso3)>2Uq2_W%K"rU2*cA=AXh8i?mDH8o1>chJj]uii>?BYpfKD7SWGW17G1:4CVeuX7)3BYWHJ&hKah.u + ]0e_!q:pfA^:="ODR70-Ia!k#F[29c9UKEFcYH,*'^aQm=On9ui@/-:>JgN)%N!"&qO@%P# + u[ip`56'0`!'[^/#/"MICFc"b6]uh(J;6XR9=D!9&&che0hJgmDp_s6aIkK30W*4qHAscW]HFU33Zp*PiuQ6\Abu>l`#U*Z/>/.:DibUs9O/FrUc?#`l@KL? + M`mROL6+%0EaXJ^,CsNPR?e*>aMq"/n1[^!b2S`mr&M>`I"Jc-0G#bi9a!&Jd%Zn8n^3*eE + $hKr@uT-7m`V3F,%%:R0J]gjmu?6WXHDcfK/FblCg"<56]5^ZF:mNS_aZ79>[^A8>W*XU*c + GNrnf$!e\aJa:!O"*J'S(Jf#R2\0h>C-iuXW!Z2u0^_D>J&^lTM)fQ/hpg]lFlHq-p[nZ6e + ?O[l.[2OhQD2'GZJX@6-+aLBW#)mL*Z&?k[n3Vc8,=4uELG3.j>i^F1!_X0[6 + f84(gWbBo2M1>t\W*)r*WsT7"YQXCbH;''"p]m1]Yfh*LDiFi[#u[bZ%=.uK+_HgTKE;V-j + +-5Jk.PH?luU7]fX#&"2HZrQ6%_)0*"NV6rO2_^_hZR+9AUZ!SAlR+<5uH*s%`OJYK"J8-a + t"c&0]uKOuTiF2alL._;E=J;>0GBG,nN*!(FW4m"S1m`8lB11LeJj$(Q>KPH9)O + effFJ09+B+_B]*`7Dagj0h&u7Fu%nr*>9Zh3QYuVEitJ<8H#,f4B*O7bgP2"]/f$=/Fp.[R + h7u70E$@1Beg"Qo5$X/HqM="Lr[\^gr5qF$A1>mZ;2O[d45WlgE,JLBqSN86#d@49KgH!Ht + Iu?l/o@'8Nf/mZ$MnX:]$WmUaV5ak_b3Y$U!#MRUZ;Ks3gpJ23j<&qL#mK1ISGLf4G$5R)a + AK_ThILg:8\D?_55L>N>W[gIj,G(sC/##J?Rcq"8s2ZcetJ\j^W,YW`=GQq=79#,l[W*QYD + A"g1GJ>9i13)>2]3\Y5=LM11OBRbc?#_o=lng>Q6aE`MB=9a:W"Lq-Y!&oaB*bKHf!+O"S5 + [WQ*RV+M\/q#@qD?T-7"m:"`_f/Z^ph+6.nc[Sk!T4h>G('I0_#ld6O&)"s#'G40#mU#TFU\-loM3[< + 0R3j8/aN^bog%/O3np=R7J6sW")gbQTU?5$%YI83M!((#) + 8cg#Od?cn7`%"lM99MbnRemJ]YBa_cb$_/>S(DVr? + LcKg66C)?W`r)^mc?tBM?;hCA_9iDP'E$eo)'?R8_fO"C$@s?f5(@TJ?#5DbJg'rb7:5!Qf + Yk]oI(bl)j-3d"B0/.8J0+!'Ig[LO:E1c!+Y7$(,%/q)\rYOS`W*oor@tf^uK*WuN)L$gi0 + Kb;q"7S1"n[JQ3>(Wt(JcZ#W@"32.Itk5.L:7Hg2t?G\+n@;[E^0:&W*BT&C'DCsL+lXk81 + e3&ab)i.Jk_\i"9#%%J$`UFBIk08"bj:Jo`)5[gg?O=HN29]'%gGpg$n`.tkelA@Y + /qj9%o7-D;oV]I/SO^S^H_EqV8TfQ\UI55n<.*\Pq*I)F!'r=Iih2o*/,TX1-+96a7<2n-(j@ + ZPpf5pN2\8KdZh!bSJ$=YGg!Dj\M24U'YuNJdk&m%9iF1+1sM53h)NZcSDc?JKfM7h7gdW. + 2[qN1N?V136-C@sVhGJ%d55#+78^F#hJuK#Pp8lKe?(Gs*QK&t:"-K24]'WC!Q@g`Vfbd7T + BC&a<`NcA?W`l,KkFOhLBUaa*L50"J(Ji5^L1/6AV4Go7p3KSfbRSOA_GXK]4G.n?)MA,iA:i&M]@%d26XBclLuYCE"]q9= + KK\0_Z;)Rq+0F()S(rc4HpN[KZmR(:@Ri[1(@LM+)[_7EkbiO8^#VP + #mW!hDde9S%0h)?b]fa@[?2U#&BoMgj_@#rRpdIlb + m)%H)aT"oG:G_C#UDO6F#8l]]*sP!c1bFVeM,qP,:65"7N/&])OAJD_1E?[4DNTFbuK!@Ik + TTq*!o>X';Y)G\,i/d0&l9aD"T!/c=()'YSR#iP+bJe0k\jQZ2$1L+pkl,l#IU)?=SFU95H + 3A?Mo%?geW(P6`mLsYi@6cbMuhmls@0CAbRpl?'1/-LLM+pC6QJcHp@&V>!L.?o%dmXWY!5 + Ni+R"u)V[L@DJ]ObBHi_3?DRJ>Nou_CA'CnK,u+dhO&a4cD+58/+CF:hg>*-lE__P&-+MWF + [U$q'@:bU2j#7?;!kX.jj>qS#b!&oUl"$S(UpX+kfhA + C@FJu%iUR,_p<%;U8X/7)JR*+,n%b5PSQ1DUio0$R-'o+W3HH_i,q;Xk%[GX,QXF81+\krL!([A\nB_u)JUfEI!]emQr*^9;KI*:2Q/7s!PR57l]c`&-OB?^ILZt>3Bg1*dPbpfGFk+L5&W + B:2SfMW"5r]eEd`b_c?TJe^JfuE,AD06l12]@q8>I/HimH1>X!LbTkfJ3W9Qq7OefKVj8]` + oUT&fom?!C/'$&0\uANH'4*lXQKfc?sD/#=i%T>H4jOV:LW-@p[u\Y860NAr@4^%frK'&:q + 2@]I/U-#Xs^rmjcAjf:YSSOWCaR>a%.#I3maIK6a49pQ.[M?V\>gD0a/KdKhKU)*E&4j?5Lsuc*`6pD4ar)XcZ" + 0KGdX)g&ggqe::,U"):a6)cIjp+k_0#I86Xg3BOQD@VQPgiKjI(:Vjn;?7$E@PbeQXcYY#W + W%h-A1Z[56@_dTG1OC+S/Z2=hg@bfa%*Asp\!?CHS`U(XEb0;K$(S8&s7cY51$V-9IV + ;YjTk#Q%9HXZ`W@dV*S"R*HIVJt>p+HV`oB]PSAu['mQ3gpSGIWYIa/2hp`S7rZWeJ=$lKa + >OA+#TXPOpc"c+TEBp&V\3",[FXM$\Ee!PF=rR&m[EHJ/*jLs_VW%/iT`2;GVK@%NX^R/1^ + 0sZ`3HKdjmX31o'D\H,CCNsE0mhA?HF@sX[*,+,%uj"Ykf4=T*uKTgb4?U1^.+C/b.ag5DE + Iu_Ug@5h,"_+!uOtD[\!n5bHr2)YXa=`4uUF[@sp]=9ZM6S&cp?52+:?%j!]86M=C!O!e;d + (5^*B5MtXS*^NLUg'2jd`p2Fuu&7q6)_D]]Z[?ll!`uO@TUA + ^j(eN`R[,3IIEa)_I9861Bmr`Q?`]GW-h%nj^a.bniIOqIjL"-[OoKY^DWqf-foa=Rn7"T& + csI>!40K7*=F2^Gas]Wr&h:,''ImrXIJS$De)1V/9>b7[DTIAC`Bs!l-,a+flJ-K%FroVojV + Cc%,tSODTu7W@$i]m?Ig*K%Y",)1c$+6n/1-2KsZ[1ghLRU/Vd8_$80&Xf][I-U@uM8?V@2 + Y,_9Qa(!m+WK#0_Z:J.hE9qJ[# + Lmd856$k3dc$#bZ3u5uAB%HPV`)^9JeV@AFa6HM)t/aA3B;XFV7J7.Kf=8^AgVdA7bB3uYB + %)UYk]:E^[.c>\THIGdKr!'X5APkD,6orh^C9U,70R.d_(p?\IHO+-i.lBuY[pi5lK%ue]N + K)#hrRdZWg$+h==gE=VTqN%LZOqsTaq@`$d!h^kA7\$St!hX3#cu79>8dNfK+T8DVdBDQX/ + L(2B8fqKW)jGlqho/;")Zj@js"t-ta#,)iQ2_uRc"QocC?tKQb71p7o(_3S[#"K>(Il7sX.B77c + kV`Q5X#C]%2tMY[_OE@15r;M2NcA8=bJiN/9ZH3Z]R*7<.u9To**pm0=`5S;_ln`':b$Q + cS:b)u2g.@cgRq]CNPq,..8n!;3i^G+UpBoC + m;bF&_*d@F/"H.5SHC,JOd9eTct"%2M_XI"(3Y]6.-QK9iQ7R3g4/^PA<4E8QQ"Mjn%c+[q`E^mL4HZ + :.Zdi?FdRppp?5):&iQN2`L_knA$D=$uPIWbn>sHm>5I:M5m$g7[i6!kc5nAA<&b,Qr)aVm + G+*8eq1'F(E#t!&N6L]a)BiK%TcfT8X9jE:!"rp8YR;e[9[tn*JIHG+"X3_`ZGUO=_bQB<:8^`\'%5T.ohUS%/^iTr:#R^&iYK>X$K + .4sUZle2g'>2M0QsEFJ-BjpJX( + =>1Pue'+/r-GE7!XVCe(C7hLPu;u0E-_*<(\a>M.'2oe#qeU5orI21dj2pZnRa^/Cl'm7gS>jk,.TXAZFN82`AeP1RekGQ5,"* + )$iJc4,#?&eu(?]S;fl#/lD\W/1V?ALF%\[hS@.oi1c[(^XG3!Xe3A$Ka!#EBr + %k10]TWSS]>E]q-QPjZifent]4d6h)N)uODUQ(SmMjOWQHNYXA=7\YC]fVRs48%d*=5B=-S + UGddOgi4cZUD.^&3]7n-GSQt="fN + =Pi=$5h7ASRaV%RX$3`en?M'n[KBt07UTY:b-3Jl=bC=8<'h;W#2unYYT($s(k! + d)?Fh+$>KnjM^A)ON:u.g2WIR)o5:r0NXam+;W3XeD/[8e)F8*mqIEo`&.RR4;)]3s\H/h! + *8qVW0\7TYqWA8:C(W_d$^ZYX!1RfG)A#0\$Mf[J6cYL#P'a@jG@7'L"5t4HW]!.9YYaYN, + UJ_&#FPB>3e9g5@,%1qpTck6ZhlF6gC(7Lh%Oq,G=N\niFES56abZ7HL%1?@>T>Vc[2(N?a + !Iu%i(S'H^_QpRsTXp>Cmj22A/:mPeeg]PrW4'\[Kue*P8'ep'Vp@%=hOe]*tr1GRVc=+1p + /A67&XZsBDL@n(]AAstMfpg7Y + 7s+XbfUL:A6i/iS!EX"d7V"T_C(kHB>>A78J_\SYrktE#SG2jKZ9(J+_>'C)&HR[C:W$_,7 + HA/;bGGqsg#U",.WT"cr:kF"eWYj+g2WH@bX\U_*N]-WlHG-Afr!MAD#KOCa+J&k#Vl`ksX + g#&a;[@][$S_AcY*CRI8/dC0kSRf6[KG&82L'eG;?S+$[JN?R-cciH\eH:i%[E%oTl(Ml?@ + eeH&A[8L0&s6J(UC^JbTk+@@/(0-P.M_8c!;"1!mSu[h9Hs8Qq2f:!/q/2(XP;tO#`@h@AU + E#RCUlL[hlfg>BRHFJ^iG0eGW$fBj;9:&eJbN'7hEhrBJl&Tq,?8S[\Wiabu_SPqMCJqER1Ckan + _.kb6Q-q@+LRlTW566_W`#OC"NDl&b5=#6Gb$4qPHEm!drLh)%A[3-.Z:f"F3=rBl&(Q+(t + TeNX/d*s[1!8+4::&O?R4BXaJP-grqq\m9Ql6DK4.RhJ + IgTmCo'0nBO!*&]JHDo=rBF\JY=D%)B$I)1W4_CMH7.CG.Y^Q[G`$`iIB8R!D3rr(5O+<%K + 3EgY]$O9bMtMBa40a(g9V'R[r6iu[XY[B:e2Q@CqEE85S2Ri4s:Mp6j!HDKDhONN%C`Der4 + UAZ%'i*E[`9)@3_#D,qSk,^&H?F9VsU+R^rFCS5,t<[;8qa';87dJ-KbN')uPnWDq#G2PEp + G;lZ*$X\Q!$XB+WZg=:MHU-T_H[TXkWn78`Vl^#]t^>3e;h!PtOQ.GJL]i;uQ@0N,#!$dLm + pUe])&T8p)L):nP3A,S4iNei@XrfBbc"45Io!oT[O2*0j7blj54/^VDFde;5Y?t3s)iL.:Z + B5Bhf'G>b^%TKt^#8AS>[Ad"LNpPjp=(W7(Ql_]"Ms^OD4!R,$M:s$*qq%oi:$GgPrF1r&9i4iXeS5#(@iM.2jD.W-*$r\KPo=QVeV@sJBkOILMJ8n"@ + ShG]^$=^d#LAd6gtRNL\>K:?Kk^2okb-3=.=BGpesA!J=B)PWWg=XnoijlW&1l.U^fCH^CS + WN0eqTlmL'VQRFDQRDdMZ0Y[Ac'VS&(+asVSBHCUnkK<;m4!:VZL4KfPj]:Hq24ot^l+$,/aU*+O)Ft + XqMdQ?HUKH'NUi%,3.:''QeP[H`b!qh@_&)nVkq*3b^]r.C]ZgA9^dPkI8YNU)Q\at568([A2Y*X(^n(llJp,jt*3Ui&\kaV6nhlHd,cisE<"3u3o.I94>%3n- + FJHJ^g%nH+T"sZ)V"Q6RB6-.1X,5='T@,rD^d"W%I3'`.i]G6&)(d/*HgA"3pZ4ua_#=,d6 + %8o;I8a.uMV!)obiUTuAWGO:V3/!Y(Cdq,*Q6eq`h%N_?:JWFn.7Fs?TG-Zn:eDHh.+JtI< + +:\]l8_J974fBVjC_CgTQCX*+<\SO/g6qh3X3Q'*Ci/nNp>%U.ld=,e]POclO#p/)$mO69S + ZcV-#,"L(pt-jem";+D2pKg,o6R4(ZN.g!!#Oq!:'&ZnqD?!(%!GO_Y]T+p/J5m$S,=)Y@% + GZ=KnY>nUUBQ!t8@=0j/b>f_Z_W%#SIK8VG3RFN7t!-'tqRCs?+P4D/_(S2;+^d,qRr(o"T + 9JH-Yl#O>H+;h_0Wp9-J[d+2urTru\<^=d.?B5Gh\[l27_g=r.lDQV=![rMg-8(c-':&k;Y + &_8X),DMm%-,pRQ*(ssdP:"RKS.,-E:FNQ(KObrC8jkkV35Y2FE1[NXk_#Pgqgk1kuXpqSPAY"KSgmKeT+3=.8pg+XA + 7b[KRLL\5?[N-$p0]gc8?^)Cn5fRQm([a]B1;%kP"^LZq^]6qDrCCue)[2M*6PCIo/5Ec8? + $SN*OGVe[KP<_r6p/Z9N"(F&^;[h4o].2^F0D=hQ^,Q9QT!3R$3fZ+bZSi]GrATMA8[?)!r + >=^W.+_25,]*g5[/Qj$:(0lo)Z:aW.CWRklJ0+lDC%-Wc)Ec$G.?l?o;9EJ=I96?H^CmhS9 + '-X8Qpe:Pn^1-fG.C\Gusj\&(#AJ!#FC/P/S#C^1f:ElloB=FD^$&_7l7\l[VsXYmM@f)"!'CV=ioTQl5&o]jF'@5]DhD3WSZ? + 6*jhSRu=&6J<3$,,Q;L[8SBOM>!c'`CjLG@_K&%(="L9HYp<'%D.RGVVfCafc'8n_2VjH(B + V(_!WY1b+!ZVTWlXcQA*)!sr!)C9bGiqeH<)51+I)]J(iNOnIJP74SJ"Hc?T&qW&PrOnU/rq&?G?\`?E4$JrS7b!8ZVek + a[[%$dNXo98pDsf$[\S\IVh#"-oDSQ!d2g]b(i,XRV"Fr`?*s`ng\/DGP=1A%hY)EU]$L9s + ?6T@F"Q>5j'_XG:hLa81@0.VnaJ4bJ>@3KS`">Dtul$9fAO0?23Qob2LgQSJ'+s&F8"COGS + H&ObB*Msb[VDKC!PFEml$79R5\auFS$m_8\Tn^WqJCk6[WUPRc4LI?5*Q)K&<]6l]PMn-H]KL8-1\42-.L6;pU8M\Q*'Crm_BWVE'^Jeo"/+uPuppTeg^Q\IXWnKBVqikUgXjd5-A, + `rQGY0)s##JX%fJE[)//>q0?b6UJl]^MKL)&1SR>cX+A'Dl?U<_?0PY&_q33nP+h>f@69CdCb76Q9-+XX + !JD^j.#ZV"\*(%"`9X>MOgDEW6021E,-_)nGgbH\s*sS2WbpsnF+;>n'?j-+aS1N:(6o_,M + 3]gk5_njL5b:=RV5,cT!%X`hu(_2)+$(hJWJoDjX!3_/c?A.XmK6T;+\0QX0'nIOf!$am,_ + T!Z8o!Ets"StiETHSm)jN'%D`d,*)!keA*AGXRSKF_%954&T`N=8Xk%++&!"V=?1MaR\M5m- + .XaB]Io"I)J`'s.<(XV@+rA+e65pIVJ*^RZ=GN1A#G*L(B45MFsd$JrPhG>]Q"L)=$H1/3F + H5m,%08pWYCMh/ApW_0O,b/GmRddF:?nNcHE\*((qlbo+.<2,Jl!)?FRAL#>Z.,)!jt--`24S;XmWJr.b#Mp::WK"*(&5g+`r)c + \?I![o"=FPmBp>m/eKK6T#"fGB*"JPZ5Vch-k9OD"eO7ASW*!ju:[:tErQUfN0Pf(Ehdkck + pJVUdFRf*-6HaJr8]@b*\Qelff>Fa1!!DR/X1RQs[P!&`@sDq@XqcsZ1?Gq%*o'M)`n:mHY + `SIO4O%9R0OfPQ>`Lfag3ZE.FoK4$LW/V6h]B1Yd_=Rsd8FVl\^(r'mTdA3467%:<2qFT`E + 8a>*YqfPdO+M[k!fr/_@8505=9nf6;ZS\RFFK&FU?.,7C^nG=;]F2m)7mPk4Umqs!KeS70W + EPI&:$IC`i#70"'iSBr_0%]I,rh`WJ('mOSR?=PK9t'AQpL"H\Z?q.KYkS`6HhS[_G^q2A\ + ?N08"MDF"[p%'S1LpV92+*>9]I#%]J.jrd7k$*3os2D"]n#[/J[g+9:.9r5@6B\IN`dd + r=E"ru?eY^o=XJJ%C&&.\cr[ULMki\LI*N#::h\l50*;l%#Fk]-kUO]0cDR.WYtFMZ^k6/. + Toe2=hl\nYHo7D`oTT*T*6GojP._)cGM6LDZq6Q4`RceN%d>][)C4O]%H>2580go\cEL!8( + W[C^9Wc+R7AP:Oo6dDPM7"[GEWL$=M756E;`*sqhVfOk@97"RrM!KHO/"_NF\3s-Co%#?[7 + fOBO,bMQ&d$=2\Y/`bei>s?`!pg?2N8DD9./)?e,/&BZ;gkV.)_^c_dE1c#t\\f:pLmM8'9U9:mR^pY+YeY^ + dU&*W+.iSKXLO<=SF)BT4h!*BD6/!/aO<*KPQ9;\')PrT%;ZQ/1ML-U_ae[A-D[`f459H?G + d1rNV*`S8Bf3Z)Z:r\GTLBn_XD0g]nj@WURQ3)merj./oh%E?X>Xi!=bZ49%Xl/Q:KNj<1U + >h^[E[EcCB.RV`i>n'u/0OBFF*+j0K"IM7Q&l"/6A?JmD#In7I-outL-Rk'bCbnIp\1oH;8 + n>4e#4PbR2S/7:b[l?&AX6rinc/nS>_L[LW><]p!VU=._!E,ZlormlG/V`=lCSJUKT=FGcI + f._BFP^N)]!X1Aben]CURPdD=n5&rIe"mEGd,]Tt#RbNS7.=2uuP`!@.&McJ2fWH^i,P*E+pB*t!R59s6Ps(fQ/8l&I,q-CN>cRd%Ch>@*l2UBbN*+]K5M>HB)b,UDCj + 7;>;g;mrRgI*:f>bi=/PgdX=Zbtbk,Y$?FYlM`tI-9kbDZ;k/_jD_T7kCImeMMjU!btGbW+#oOfK7s4Oj;/@?SndMIg0"ShU + 5,L8.ac&200tKnt.E&^FaNSnFG^Jk"/DT!*Pb'3cl_%1ks]mIbSL]cXs?9B#dF00&Ui<8G& + e>FIqLn!"X+e(B?2L#M=-^ScnJR7Gqq-kFQte`d2OS6MGg:GiJnf7QN,Zg!#GS8G'8pupaYk23![fJ#"05G;H7FGrpY'8ib37R + ^Mmn\^M0p3E4_Ls`h(ZaTA,u]nGL5b%)u7J&o'V+e8b!CAu@@YW8&I1<5$2II8*sJ(uqU,j + 1cO#5!=]#Y]7sn"O98,Nj?9&=)14Yh"F.RhF6u]dN)rOF/W)FFm('(FfN5cK]#.;s#p0Sol + KD_"'@&T3_f3R<;?>fF"m9.7mhRN64V3fUP.rKhS/gC6G)-=Gt-A1?_j'-QH!R%O&N\Qf:m + SXTsFD\%!P-XQVqpBJSj;Id76iOY=na/Yko[(LCbO.dJ0_TCPG3MqTlY6KCeN?<+i@[=[,p + E[dI#8/aio5Fd@T$Y4H<[>OP>[r&03\,,QD;HN69Omk)SE8BCHr_ + L2\pMfI?B9C70\NG0V@VhO&K\#PSH/pDKcN/tGr'Sm-[RnF(R*VP+mbGj6r-pS`T3072tk' + R/FLGNs<3Nu*$K)gE9JGV=]=`Q2Q@)s[sUG^_q`cElRb+Gmbdpr9fC#E@eYeoVEgqOm?8.Q + =,QE.t&44MC(#!!@OKq>+F1.KD1u3Uk>'Rb(?)NjsTfcplXa0i\C0_4_FM<&PC#L.!34(o0 + nY&r]7h0mM2k3?O7%A'ue/N_LW<%##f1A@>bsQqV*if"/+.-^%5;'=f[fFZuBmkYrHH!f\@ + 4-W"343#ZF*W)RM33i,lZ=0,)YB4DYlLI\"j_;O%l8^=k@=05L2AU`HN\(bd:4-eCWGD&'q + $4AE$Ur-G%h6%_5'*j60@I[H9Ag\MaEF?(4%$joLOitN23P<@U24i*+oM4QjCJ1JPC3:H.5 + .=Openat`*N$_kZeOkk)Qa5<5&)g=YI.qNJME2G@ei"X"q-mek.N"[*63' + KGorJpgk'RfEXBi48B-ekE_0/>QR9hON0bt5Ehoddb-JjW/PCj=[kAid&YCc$Etl_PS47V; + B+:U9YnX2c;4Lr7k5go1cL\F54e(S2s$"d#o`7@H1]7Z!d.Ad#72Z@+]ggE-Qm)d\$'EMCQ + :Lb\@ae<<2bqgk,stP!FVRMuRq<[<]#j.7:L+7FfG@W(cB!J>ZXqiZ="dIrI\K[7` + g-ff_.1aJ"%36Y[7Ol#WkYU-CJC>+6KhPCtj-`\SmqUe['7#-i'f_>k]soF^9a(f6:KD$)r + 2VL\[lQhUfa8I,B[HCk(?p"oj,2KrtH/)L+!5Kbf77BGTeX + jnc!1]k':1-#R(Z);2i0$%`O3,*s:g_(rST$atkaQ__.iT;Es_ + >Y+)T1)t%o6uX=T!IA@bQ2pt>$5%o@\9&MiieW1*$mKYB2N$Kbnh>qH(t'pr_cX35%Dq'F3 + =G`@qn@"(%fsN8r%A1+Lgd_8XfH4)V4s^dd#];a?iV)K!BQ8bc#]D)3kL:/0QkO281-RLo6 + j+neb*0le1-:PWaJj>6_@N2pDq"kf(U]'WN0eTQ@H('q[UZPm/'Q0[dEsPkIU*YcO]:0Su3 + %7EBIR8`qOoH*jKA3da#5oT'sqpr>t^ZomJ@=q+@jW$_ZZCJ#ek3Z0ZfbrN`N"6N>=&!&SX + !!1T>aDU$Z;JFFu?^dn\4i";ImJk/-H0*BiF>PqONpllaOr'mSP6;QZUSRhful=G;oP95a26:1(q((G + Y'rIKE[:hC()t-38l*l%4iT1P"[IkR6"6eF!4Z>A@/NQN5f,2Xd"!*ffLuh7Q7?Q&5n;k&. + QG;U84`st#OYQg!,>@-.>9ErmY0&r[nBDe9fT",jjXUPqALc%/._iL.atDW:FY*kF&X?;:s + +AhW.c^qZsJ^=\iWFI3?b2?Q0m"GOQYL'Q$d^dZ;pbG;QDrAIEPH$$mPgrT:KtSn)=f.>3K + NJ_u-\l63fdpqd8[NJZmW3^fWZ-@8-3Q)8cGYeF8>,!UY%ae]n]B-pn:!$n&AXVA('7>mh0 + cT0Chd(nY=2C#QE[LBn+8+DY57+p:/eOU<;+8RIW5!WYIj(=Bh; + 7T"Ia`V@%^;C#hb;5uH!#gefk,]/OG`@tTMk8iT_BMD'p2236V'HjXS)_,ko8-oSGj=H+LG + KiOsaWGr\I#jFjM+fB*0W='p!0c]W!_P!LXAsqN_,?P)@+eI.j`ISho#5.@l"[ONZIc7](5R/_#>hJmeUSK#XgF6dZr(i&dV%GUF)hRGVT6%`&d6qb&AaI-(ZOUJ`R?p:h.S6)/ + p5*T7m)>O%HV,"+.mp`'AYn-Ob1'Us]k\0!4gm8dEa)8_Cs]T#[h + 0tDDVKTO`jfjo@<)_d_8_Y@;E25=1O`U8_\kE;f".!@ls6RC_=$r99:(Eg?44fk_UqZk0N` + g2hCX*CQ1.UOejZOLm='<`OM>g6rnmN)-WMJq^tc%p@(Aj]q1]R"3(F=W\S'1IqTi>)Brqh + !@OCU56!V)4FECbZdIB'8R5hOM@iSq+nXS*u"%ZU*Bt$$)fXhdu$Q\ok7G$3Dl,[qL,%OB. + Og7[QB#?H+AB)(HoaNs*J^-FeiBL(oW/gq(b,9lR>R?,RQk0/6]*CU4'6M-Pe7;oU_-usk" + [@\1ISHPLRVeYh#FY.SC"rFQ:=k"C`rQ/[I#N&>jLn'Rd66[+J1g`Vr""GQ<@>\/MG<*CQC + N>+HeJ7f2t-_86*5(@@BR;;W[ihKeWrkjC@,N55)-j.g@slJc`O=`QeBr4iV\['N%jDB2K4;#^B2U&J5`iO[=IN*tg'7pCULQ06Coc`tr_s(9rPB08<4,iqX% + XZOb4S9jmeESJe]$qf/G.0rFhFW>bfOgi)h?chuSernUHPh2houcjUo`&KoF1&A[iH-nH:QJlQ=$i,/[WCU2n,[c'so9F_t8"No]m6qota)8KhfU/l&;/cpPK^!Kself(a&@,pJkj= + k=HC\X(>EkjG\^h$4$OF"_sK&t@7.56_C8RVo:PZdLAk=KR/E1WDM_$Wqr`,&-TbYJTt;ON + WT?`[TcCh2g)C?go#lNX`^::QffHCX>Nln6:hL0X)hN+T>1gO4c[Yi>q5PgMu6PBPTf:lT + Xk,gbJC"F86o=q`jQP+A6o$BUmoEHQ_qm!9$*S!=j^tJ/+bChbkgG;Cp_p/L+(H,EOc-nj9 + tZ8OM_+,g4:U+o`i23WhW"EhHkF?r&+;od6nJ^53K_e7f,MFQXBJsB]'73>$hDU.ZI%lk*A'aK + /We:2$-`ougrOO%0'V<]/<]C`p?f0^EhPB%JBp5']FI3870^1'5W/]/FJru0GY4l0_Jfh>I + Km-2'EM)CnecU;C_B#);3R5kZV5(s;)n5-<%Og^;,KHk63D?f<#&@\2%YAi(KL97K54eoVM'o:BrVU]b&=Ym6aF2M.GQ!(Mf0nK*?6+d7A+HgIZ)8U + c0cd,]jf):Ze>Ur0*9t)1%;a1l\LPU-rEV1E]h&oSs[e'f2CO]Zr&RP)C+Mi(4iAcG*5S=) + *6?"4pPUcg9V^$.7i3GLSF?8AY7q)$Y,goR4!O`j6>&p:%#XEY^<+.,8@n(EK7eCLp'QO6T + *@IocjU;4rP^t6a%7lYE8Chh#l+DNRRI^IrUa`RM_nM^bTr);?\u,,[bC%[3DXpMA*f1K$;hHFa0rE3bS+MD`8&'622<`:ho-uW%A&_2\g:u/0P + elDPjm\pF7CgpCu.lEqO6"C;fk57mjkB;>"5$ru]_m!#=`@F:A6E]jDSi)W@P0 + iF7DSn-soi?rSKTOtT/q&OR0m"*POaef/&c*@0k=7$:Fd@<<5T/.B>$$!@-WG#C2BUDdm(66@WP* + sh6l4V8K/8^-Kc"^t*',Ib\(:;.>h.sCXC[@1"$0=q1q(Kap$G_N(2c;6C=9NMdBn3,f?uV + t$_A7\RK$a#u=&a6+1'2tNDm*am6#%&,rX(oIM71lPjs+k*]8cP]-buql^il^GCQ2HSVc^- + \-\[J'n0.SAN&O)t2b;Dqac8@nO7+B;K$/;gPBI4-G$-,7[?#L_HZs[TP5-tZfdSsJ:(q%_ + WSf%B=LSapeND<\V63Y92rNWp6c<:b7#/[9U[ + 8$piD42h[MPpCRR,:0$d:;M28uNCio5`D'P005o+*hEi5A&iC"5BfQ*Wk"Md6t_,MH#m;Pc + TX"e2eJ.r"h.X:f(:Y=<'R,*lao;LXi']BH0q*Y9L*tT?QW%*q+\mB/uA#=RH0%t+TqJd?TU-4"CN5.X99Y.Tr,7#?n_Z8Q[P)O,< + +K;Bink=3+kp0D5)8p%(\$+1tdZ + 6hkPFM0,!q2>!\rdOD04P\D!XXVg'+g;A#iY'VPdAb$GP8Z3a#N&"q$])TF;M0G#/6c[^6E + !)R^6M84G6^k#LaALj^l6c@uEWIQ6_HN4Q6a2K#dG5NAt#.MHS.E3S!_Yc2kR_Vh4`Q&sL` + Ws,Wij#U3n?_uuaZ1S/>2NGnkK3Snar9Ba4K6JNXOUM4r;b[_i52JO63H.,++_j)PMNJ?%C + $T0H.s-]3uO?0CEIh??2([32n$2>]6kUgZ[4fkjBtF7Sd=8-K#,5?-,l:<$[oRXIN)FY>?s + Uc@0?-fIdfpY3OcHoD?iF="m@KW@?0WbJA,&KSN5B5RuNup9tLZp:[65pM;qg + M!-B=-7cs2*YUg4%-T5\+\iVa?3t"a#"cH?_#(!`nD8qSE,;6X^p)g2KGQp(&"J?6*(WgE: + #"HU<*X(Z^bc,E)AO:ei$T](*RsCa*1>"UuBAn'B8l/aF^Ut-3)3EQ')a2ah4&&F/ + -la:!11Gb1s>a=(3)AMAL+N!!,+n2^oc[cfXiZYrpK!cfq^+IBhI>Z4m,\.#ZAt/^Cck+M@ + Bn>4f6<8:"n@pgDWH62"A?C[kOJ_`W3^ZMH4M?!W[.2o^*7p! + 'gX!.u]>Zhu\K9XUF,^U(eaP%;7>Rs+U9CF5Nclhu4AN$=J)W@M>F4%6phhT6^1N\+gkUpl + E8J3ce8,-q-R=1&K$`h.ME+MgPlI8Xh>[6a!9"M"HG\mNONHhQX$7d.[2ujo,%>AB&&j"h,A%3icRD + P[cd9!9`g(LU=&1aW\l6S9W(eR'\E5UZW!mS.b1!,DQ_Wkf:L.# + *21PEAR7@NBX7CX)Tr&gC8N._m%k^)7tmFV(@G#O7qJOkON'[^C@rmlf>p4RnrYVs1g;pK)` + X+e.Dj+k/i/Y6-sEi#ob2g)%+69q6::E:s"+q_rem"C_s=tH4trSldZ^gAVi6nR`3c`(rcg + VWu/k(WH0'"Aup+r`hgNNC.L*"Kd%<''Td'$;ITQ=QI9lK-d"3;:jl=(H85R[_I01dKP$s&Y2C0YmA``j)Lj+% + Vki0jLSfp=#(C#4'!lZh:\V=An7K$]T#>q>G55n01;$[lFWc4bh==@O*^@C7sL'OVGGE!IE + Fq*0YnR-$>g@MnN!O4G((Obt>@?:U8k=2h:X>?O#cDd4/l6^q$hY"b:sA^"GKja"RiTkHJ6 + JYQn)3E15?"U8FM(fE[kOX/YHWfnRZ_@*IgqW>,el[&9BY\XOG#I9ni51?iTHUc\rD7bP[\ + S^)uj1=Z*[e%.MHY1=%n+t.@<"ms>J2Ia%k,615aV%as-/=)daX0U:.\Cb'ic(P2>hY%\., + =Z\W2]'K9@>7]ij>cZC=:&(p$2t1ZRh#Tf@tJLQZBYj*THL&*erX?D-MB;)?k[W8aL>_N*@ + ":qf!S5=b1O.^ZY\Ee5?XF+$,SoaL_8\2ioW6c_k6l + g/rQe1*Ol)m:>+WWLQM'JAT[j2@I?;aaSI!g+F[,>rYoM_!PrjjJ`,#qK.'2"+UC>eTb820 + #]UEM"@WYp.O@LHV>1)o=Gr<;1IgTa+%(-pO-0e!89ojFKlHVdU#U]M + )%Kk[[E=I0)u<`2^_/#Sg)U4:Q]c]-ku`UUCr8XC*k`+=N[H";N_cP_!Zbb>:: + -DXdM22h';>sr&A\&X_OPmX?mGi)B8Qb@5R3C624BCW"<1kH%VB`ARf`(Q#NV8e'-[?J8bh + r*AI?<`!Akof5rp*?9V@VJ[T6#r7`Yb3OUj3@L^'NP&`n\KQ88#<9=YKU*%Zu0P4i1b\KN5 + pYQ0im)IBb;W&`Pta-kBTC<]>P2J$AkBrml'V#ji\-PKILN\$4!-NN&%7-gCB[WK!(i81Ai + I%F9e]M@_E46Odr#CtSJ9X"O;^_"\Hafhhkde=bo1WHO.3-6)++o^m+2nMJ&B6R(SN7P^QAo,`*;gkP$Fq"J#,3BSY)IhW'^O!]q%Y<-2O:&<7_7Xum9EmV%Zsu + @XU3g9jF$Gr@#hN00J?.*%K4lkI_AGIid(P'!-uBF1W)$l"&gSCZR1r(UVC\F(1PVjZ)ag) + L%jCdgA_ + j)k?iZYF9_0[LNN/<^mE>X(lhgaZd+$]rhHlRCbJ%BXHi\@5aG#I),\UmP-fJEH<,H]N0#: + hL,:/f0@Pn/3lJlFqJnoW]kH*5Dir'1)p#?VAhga4SM7pSR0140*mN;]ND.DHgE%(;f3e0: + GT3!+C6SL2`.lBTmG6$n9"Za^jM'F<;-Y$26V<<;ORYV\Wb>?&Wc@1WC91@1,/n5R:a7!oR + %!NC04,nJ)AODGGrIH7)+$9bMND$..c-5t;qZ;KZ,CO?2!%8BWF&(8@T`#!fdmWF[WWoZEO + _dd'U`:kfK(_F]B!A.+?)Ni3Ia3uM\6p$+\/W>.'N)B,*ki:jEN@9FYLc + _Z"0'@N + 4%(nf9"r5lhGZ7XR2X9>8ADd4cpA?c)6D\6IdDKB,#2oXqNbjfV_.+(H0gO[o&8T`d;uLUL + h9$QO[SY^d\QJee]B[$rMQ,OVKg*#p/+D;U(-lnScl*_inJB$@s#oGSS/$qlFB:1OUA6d+B + )-6ZqnsqQ%Mi=p]1F6\`Ca`%]4Dj*YuP$m@MOERm36fN4&QG(qaU!/1lu?mNn>o&4fH[cJ/ + !J++KaG;q5q?[,a$E=kSX%7VDUFWZ<>$Z^2N-Z(1R0BWF"-ol]2BWVQ + g_P(^1tnQV!s;6"9;u"_\L%D44GhNoq'L!ch/UQkW_@3h`c^A)Bu2q_4gWe(rq8(^mBp=`]g&HqN"K>=>?eUn&qp2X646]71$%ZZ*>'#lYC`PA8I + jLZMOT.6_o9Fp(C*Peq7MO?'hj[g0NCh[O@4,UD0qR`D)h)3Q8\R,Ae^0G#Ls/-7LG^q:2f + cpE@]/>Adt!n\hc6lA6`'U:?tJ^>7Li#_u]'X/MX_5nb4QrX>d3Mm'4JhZ1<>R5jJ$,EV[R + Nr/3kRF>CcImE1l + 3>jL,`Kb=9H@s/HV:e#O%n\A$dY2[R;VlS?'+\KLIYS\iZ+eW=2$9fEh/%Q%Jlf=_V<1r1P/.?J/K3#&t'.cOm + +^8,$CKdf[(E\eW^>dl&Ic#6Fi!p6DFL5\3 + ,BnJ-Fk("72:EacHYP4bGdF6B%I%gl+i;=9c99"F4gT,97Ta*]N/S8dSpZ82gV*9o*=)_Fo + p2,a1efW#$_F8TTJ$Uo0P)/"A,R8fY"W*47Ht0GjpI&qiQg/@d,:1eXo-bHhi!S@q:S^,hK + >bJU31=?n16%L?GqK"rf?k]@fuLI#OejVsG#:O7>j:IRm#$NcSP3!:&J<>4$u.edg&EV"kX,t,cXRl"t.aZMp.6gENa7ErG95'M>p_,>!P>$QdWB.f:fs]Vg2XCB6ECS$.L_Q + )1RFD]MJ4>0m[$?i5K$m5@\-W._+"m%&JT)8gF;Rj==,5^F"[ip<*5]MXYr>d1"qXVJ>"O8 + n%fj[rL(I3ZA@86>=bM#]U7t<]r?5'DZku%sK6T#rQiI3_-3=%B;IgmT:gJb4-%Y,X#aDBd + AJ_ + P.4!5JsBTYNtZ,u$\\]N!G+FAohgfF00C*WTc%QjA4j&;[9.#+5EH^rjpNc71[-`OoHh$dq + 0`iPl#[+(-N!5Tm!*:BXXe%TRICK1WbXl)Qr3X(5oKle^5mm+*FsKoBfT,k&HHnQ2g_5'p< + R*-+65oiLH^^9i%'=;-5c%SqWl2<4,FRp\GqL/5o6BUKT]`=QpCg>/$GJ?,,LV+SL`BeK + J*Ds;NQW3FWlH=UF68<=s^#AgnP!u.MgA42lh;ZKf`IpTpAe$!H8_#O=c&T*nnT8Up+*WTK + Rh@o2V;+-"W^BIA#:7Xr4XEg-loG7'`69ZeMUgg=ce."`D)l;&<8(:8R>]% + PeA]\2*`WkXCL-T',r&rGp)?G@(N;3;"p!HbI\()MJ$9_mO8i1JjkJ=] + >FVg^[5X#M]VoLX9Q83rC6!@L$:IUV%LCI41lc@J:_B$J9B+B#iNQ(\?m=SI^XEPoiR1@^>h+9A##(>)._MSd5"]e$'dee9uOq4b%,A1jE8T*IRu7@7@\(_FR3@WG,C(*hjj.!6f_U[^/qTL?ds$j3YL?BosLT;fECo%jal&7m"L.GfM)-Ls:[@[Lr.!'.!pH_:=PSL9m06C.rP.]RffbPX1l1*ZQ=SOAi\!J2!aHh)AV4)8-.JW + `i]e[_&HLGkaO!gW%$jcAP?Z;IKMXT/j:2<;3H%FNp2]ul)pMshOQZ5SnU)FdmcKfVEX;-UhZ3kA.`+b'gGB@^ + 'VeHZ9C]^5e(kL2AL5ge/_+DtWEj&Z?$&[H&: + TPAO7O&oVBKlTp>39tl%5)>$%V`oTlod=?L.G + F#&-u3lJ,qVA!;Lqrea_NIfu@.Y?USM!*ct/cF_>:[%lO-r#\.M$H"W[oB + Ii!t8gAq9smJk(!:BM2\j%Ya+]ces,si/E-KSVp#K$eWa`@,?l65)pip0\I[hklhC?Wo3+f + Q0XA=@\1d]rJ4YC70S54*Y"JF1P!aaN$:)&2CU>IB!&F\[)M32UJZ*PlXtoQnR[.&W"B + []_8XSff96&8lnR!'Rq=#B?X[krfs=LC4nn\a-FOF147KhRjQ$6Zd4c1-h4Ya=.1/)P/nkK9.nl&C$9Kb&#YSANWKLr`/=hP<6XLpj"9h>Xn1-sF%1lW%i+j%?Z:bX@daA3mi1:E/F`*rnaBM+hr]@UE=c4Npg=A62 + 6QcDWT&Z(qqsfbn'q7'jS_;,Lb1)%[Vl1;"ThfHL/9c;5GnYR?4cULO2([V=\Y1NY3'eM!- + #_OKHN36W'Gtp.mW0p4cMQhCsjY8'nXXD"].ShNL]e%L?j9!L1jHf:CKMM/qK9>"Q_0o?q> + $O+n38XJB/),?E.gn.KQ\,#B4#ef/rPW^k0WPJ_s(q2)A_gXmsJ-"6_QB3"LPk=5/'3GuB0 + muMdL^O-!C'DKnkpZ7%[ac/1^RS`3gdJhRN73Z_RWm1OjNCP45eWB]@/-&7PdWn6X<@D.LcnG*=_I0R + 8busX-HFdNGB29o$p2[Tm6D2]hbgg5&O\2Ftl,pS;6(4h\K>Fk)`FcM^XIMpO\'lKqs(3a^ + Q>8(DeS2Hq/_7O^[c)1MYZ)nm&DjsE^'On8LVk!&F1G^s3.*%t%>eC;O13>eB[B[%]pPfn: + 1kB4`Fj#]<=+b[6ceM6eV'@C`L>+c24FJe)#e+sl1WPEO>n)K58mnQ[8+VD_r5TPYLZUJEg + /_N#GWs`7T(<:Km;BIO(-o6?ml#j:aY&ne-`1:VYAgla4]eUI@gGi]ArDVI=@fF&&WBKsK4(c+!l0m>!3]kAN#b42EuN*:9B.AAZLIc1m'I3 + 88J/q`;c;/b0RRKB.]U-fP8r]+2&&NBr-]^-Z5eE>85_"#l^T8p/J4?RK*HH;BDeSU8ogo\ + DG$)AZRsAKm(k;J;Z+Qhb`\[31egJ>dl'pACsmIO1R\8'::$b`4O4\rmlo&ro_C_u<"0.!: + t-Kr6I57G))t@^>g#[]B=YrqM'k>h9_HDd&VFn_@rK7>X?(%RWLu&;.d[1rtR6G`mQH`jMXX*6,+WCg-PW\S^)6.B% + aO(DXi-gt+.#C"ucebB@/]+f7;[,7U*E!T7o87Lu8^n8=Xaq8R)U(_O]!TD1#LK4,U''PCh + KEQ*`B`ssG.TB?\9N#-%S//ABI*U9b[1u[5'(T7)DJ",Rq;3kd=C&W$^R?h;mB">IcRqFa< + O.IHUmX(U>q@8*eBDO?pUhN?p\74]W-mf3e2JLHH(+8pd@I"+0rbZG>p:INojpo#/)d>5IJ + "%bhe[B=ADW#>tqc;mjDMS%m\XNcrn\/pgmf707r"8pDjSUqE_j?lH(2K7f5"BpfAUuUdR\ + e=^b] + -"p(+q!=SE + gcH'<*"927u'a-e\Q;J4^aS_>9:*mg!AUNsC%04`h!^`hR#:n=A^^)2u#N3`>!@/Zf([tJ_ + cl<`D"?!tC-B_"Mc4luu4GFEA#6:tA08G]&Ui;b5JXdZ*!)Nq$d's^>k`lY:N*_?[r&2N=O + r_F7:QLWu.?IdhIW1Gu7-LBZcFNfdY[S&/@r2SRR9g2/pe1q_kV0sq;EF=f:Q#f)*J9GX$% + R['h>iii#m$Yb,CrZH#=/Gl)5IuW+)djUBlV+T1,1J3oY.[)urQ-"#9.+KCjPB)JFaspXER<:+Cgpp2.dTF^ + 2At*@:`KdEYI#^Lo'S9FWO(\^j!&.pk6!.k1+`']*;#%9u\C)2qd!-#9S3KLtf2o!;F,c=S + 36%u7).Q'A$R&V""H\EMJ]oj&TVr5a81-G@#%s:AT!VPhg7ZW4K<=gn\L:qMTrY_Hd1tS(f + 7c6p([X`&,Hd8R[Q)PVBNk_s(YF@cKFj%,lFY"AjOO+KQ7(_G1.t3! + U>\eBB\6:s,2K7:n*qRBroXc72rQBT3M#1a\C5lD7FZD)J5up7g'ddoC/CD6?nD_cYXqB5F + uIZW0usCI,ip$+S$:1Pd9)`+l0MoZZ*'M + H]$_/V88GLT%b-G?LN;hhopm?.r#-2:RdaW0V0HP4j]\"6o1CmAH;?r+]eZ@!`adUj$>=13 + Tc=6__DqjO^_@-,$lf\\R#hE_?]"R_pT,)j,bbDtKkW1d"^$Tk=#cA8crV-%V6'NdN1R9*&R)0P89Q:G;/h'12D@GJp,(\)o$] + buU^+hhMhcqEu5WR`5G[C^5$oD35:o5lLlNrp"U'uT<+Up_:,Dcuq(iJ9['`p)W$3QBB%NG + P0"%6(DYui3&R"u3FCb^V4KF:'g*hW4\PfI#_>HY*[],,5M?0f=OY9i>IMJDe?]@sT!NZ!? + [3N==JQ^I@;A]&e_1**'[.>1SD'>d'8auMhZo9ON`a^[E/H&X?:5(1b?VI<.)LS48TKpUKu + 6tf:.Tp1:\c[%fG-*d5*`lMWt@9Y!@X==7$MY@2q?1]dk^&`>u/dgPs,Z'J#6"k*CU"[2%d + 1tD(Q[8*=<)@9=OeON0lCHFtC^^>PX:kX`IoG?_8-hLh-NnD$'Z+6^$5"U77'ML[iV5G<@' + gWaTIpCI"&aXM6mI?g6eoo#FAujccM(mEWN>fk<>qK0RU5WdQ",2!#,NQuZWjMjMR#./eQM + 3RENSoQ]!ngW?8uGEE/kOKE>K?slgp.@2@[g]HOAqjXbq4JIF[!<7:[s?O`eSuPS#u0mHi3 + J,@%h*40bM2SaE)W8nudXGE$=YeYolo/a(j)Mei/8Z7:%GgIUs].th#HIfLV[KRe.Q%@C-1 + +riS$EBYO8TW/U!E**E/>U'eFK+pWX2FY)(Jg-q#bm&V8G#L@DbjJlk5>^gILnVeZA.#$L[pHR+1W`<]#^nON_Oq^kpKs + -V:X.b;+UdG-T:Q14ckun88+$A`^3G+K\&huJ`:nXHhRhs"n/_o[G[D!@Hs_m'r^Y*+?SCP + M\!nqP3m3?qr<.r#$G+GX#5W9MqN)Ko8=i3p7M0&-%h.ZdO=:7Tm$/"MPmnk^n2UTq,1$c6 + "AKa:N>Bo\56J,U"VlK8Z"mbuO^YAPVhV]-/i!!Fg@;b&1p-T)IU53)&nFK6IfN_JN3@'[$]j_H + X5LMZ)?AEHX#k&,![!AJanG3`l4$<(&^$"t#Rs&'qN5t<>sjPb(KiMuHe&GH]Sro10[ioQX@M[H^ + $&J7kWjDc+NlQicr;Kd*`&sEO+CHSqc2),\&ZuJ48d6b6<8E`oE[P:T-nPUhK==g`J7FCfD + 9=tQrG)J,5Lbc4 + !$c&hN&edIJ@3NuN5;F0&oD9bq1I)WR5Z0[/1^PCG2'N1(5EZp+6+e + *9dL6,_ocOE";agmU%gmj8*s')`bumE\Gh(;2#OmF8P&Wnu4q`&0[Zb.jW<#%1=3<+\BO\;5\^4\24W5gTl2*(M,*C,+?q1VuTPa$#*9*J&MMY+ + u7:0Vaha4eq)X@%IV]CK1F33Ajl;2/>@Kj;:0L%3VSTcViO5@i=gV&"puN@fe?,Cb560;Y> + Q*2)G@L3&&lF7Fa\)KC_p8"&SZk;K]HO'tQ!4r!]6s6stNM5]8jJ=^;cg5?6<$F7V[S&C0n + W?;q;=PQtNrpKeO35BZ^+OrU>b?jAr3#$%&f+:*CGapVUU*.aPsPWpKO74)ak-IsOMP&j&K + =(:.0<;&.V!MUUnWYg8H#N0`4KLV`P+rDJ$EjU#FnsJ6*O[nu9$gm%fNU:>tNCbn3-uud=6 + &R8?gI^nl8l4U-&F1o%nS&WjBi+Ta1>ur`rYiUs/o_*"M[\X/o&LM009joGY#;R3![9JTG- + Na`-[T[W2bp[q:@<4L&P#W:8gs?3DGr+N#HTKLY!<`-1ZIa,J8>Ct8h3Q%*2.:4osfkheSk + U5"oq="'6H*sUdT@)E5Ua&DZNqk&k=fZE6&0s"C"e>B6&..F2nNFUlZonb+2qjG5o"Y&2g/lB@))sS5b\QnZ5A/s@FcH.2sWl9LF2MK@ + h_pQ2E"Ona+*jc+/-Ohi=ZYdhuPNCI3Q$)J;+;[`YcQMAX%=K`aMBcUIYjRhDC"?P`">KF% + I38Bn;+RBch!;RqORtBTAM[gCb[FQOY: + OXj_XN%D(]c@%EiUH;!6Ec1,e)*mYOar02Qi[HORcS,=R7Wf":d(U!`QLK)#]F1KLl:^KAb?CR.c"9U'j5J + #D'2tR1:r2eW#sjNTeGC=QVk.l]D9PGGB)#q*U7s:R'O1/Dtm[5Z1!aU*"*mH'r;jH#%]7^ + R#b1+nrC2Q`".Imm&m[H[1BR2nbP\O-\I5Hm/9TThlCG*X89HH^Pj3nqI2GndV88@hdnO+m/WiYZ6]OT0&RTk[QC/s`PQpifE!HS9V + mjOf(G>$HfC>ATPa:2X]>NpoKrGDW]sD7&tnDMN+SX_s=WM_d/IE".`#KG4Fe%:aDZPoKgT + 'EJf]%/DHl+bUM;!fdQJ=bJC.m`l8ojcdU`PT$LsW%8QHl3o(]!I:Uo0:+a=a=\=f&ch&&) + RD8'$^FjlXomUobCg?*C'@'mXb0A*K2ST'GWcJ('>P1(Tm>UjWk4tm"/8mtU9phm + fNj4IP2Z1I/bH`nRQlP\7;@i=CCWM(#?Ra&]1m>MCf)c<\mqUb:*`4,0mK6Dc@9^-H72mr: + \_,2dkEB./2[5X1P*mh5rK)nNX'b]*VFdg5b&*BFgH(M;)="7YFdfC`l-er*^jYB,K6a2&k + NgW'Hn;'!5#Sh;NTrH#Q]YnJH`B4Z9`BNNM)XBJ49XRKa,-Ybl![MKTEB`+pX6Y\:b)Ci*m + C/":_Dic=='"F,S/hJ'&0sb%-"EUMnWLO_O-F`NW(\cY73SPLf]@krOICm-r`*f:!('-`15ctFo+C>`(6=>6Hi+*LP& + eBQ_)b-C4Sa1m>mgFFF82rQCP`85D5n)8*^jZO(Z!Y*21lhVW4F0Qs)E-Cnk_%fD>5_I:CE.DU!W&FIJ9S35l55o + KcEWJN,XN,oljR:Y$QtZ?^jH?:E!;n,0@_r3g7\@`Y#ATed_>JBA+NgG\VpN:lX1H..1SAR + AeQ?u,(]K`35+3.6T<_D72V36:dIlZE*V:EJbor=$`TdulkH"Pqe(nb>S + O<)+AbHq*e\,,ZSQGOBB=0s6p&/BdIoZ7#=IgW/*20GOTlq10-kP>7$\5/^^d\G,4UNL$"' + pWdi<0X`g)-Mo'VY9Q"fqOTUY2KSg'qE#dVE=*YIcKJSMiL_J(pOOhqR&HUuiL9!Os!j?Nn + 6f$Qu/I10%E8!!%R8i/h%t*YA^0"TgskE>3k_59-2PQ*^G)bD\o[m&0c)59>nWj@ks=RD1/ + 62l1Q'6iF\OR*6mU=b@aoh4.6<1V)6YF$OWiW0A+qEJm>_dOA/Hit@DLVpo)@\qO=f_lVGD + FfOh/Kj]D=X>W]lQ%NSR[]O`-;uO#;\6Q0q40rLJE4k$Lm>K6cqS'c'gDDInom;LE[IH6#? + 8["daL1Sa>a,,f]?Fln3P=rdFdUP>+73>DRnC6@lIrhODO#2@C2A'?/Z-p6ZJ;iHHe8-i0> + QK7GanB0)j&M.nt5:qV`16nk]9dQ8,GfWf^&63?%2*0^Ys[L(-Xr9PKi?*/3JC->Q(2\q"t + m,:U<:Oq"QA3m-j!4rkIpt>5l3_p:(8>75]^gIiZ6uhjJJUnq'$mUNNGpGG0-ZON0`R8j: + (WnL\V'nrV[&Y`MP16Zeb;/&=@2)-)r\[1Xe9N021Jh6f"2gM9-C7-!pjF"`4LmRIe'tun: + G_A35>X0?#=$S?ETCOB,pBXX=4O^4B3A9CNGi!%'t&*h746cO;jjI*&kWYO(q:jsTbo`sde7gTJV1bR-mf1mR05 + >Rjs`DYfC_"FE*$*UHR[/P'Zi.$EUBHC+B)h,3)3DJm;^o?-1?1f2m)h%?^K=?J7&'drISE + &i;,99W(C+8Ft]NoTpN6]tst2Fitl\OKYcA[sh1O(JVl + AX[3k[V))l\]/iaB=Z<;SnK#8K:V,WAlGL=qGrTQAD"mRNG86TUT.eC`ZM7P)U>d9)b*u#? + 'WBK4@t[R!:VoUK'9!V#bi_p*=$#O@MjfM85Kp%+S[o*"iA/9l'OCT\oU&`OnMZ + am`2\Y8'$t,@^&`.=La"aZFo6g!ecZCM;440SS02L#=4Z?8ZRYUoIfCdsTBA%IAH)KEk-SK + JqTB@SAhtPt.V;HNQ-]R23^/;sbpYKW)kP?S+U$;C8LSsI.Ia>LiH`%IIJ,B6V_=;of+eA- + $#8*JLRRR$k;3I`K"?VW2.(C[^!XO.`n=-p4"9L]pbYQ#aPM9&X)-,Aa[(ZHj1W@;h"m7'r:_W*LUPHA_sr^).-c@A<:F0ZH"*)#20`%( + p23#"6bVZT/t!'Q5g_U,a.Uk\CWIYo9LpgIJSpDPQiPO/E=N**EDa`ghcDE[9n?.2[/%M:E + M7Ak=#L]d6GWQ92"=9ceR>HXu[85%j.X+.5ZKjY?h$#RpjXZk"Y5Je+.Q3=3kc.YcuM:`/?+!Vb(%)GCW"UKYg@GEMhoZRjIQn&AKU_*Z949e(G)$6J0&HT[b$NV_G"lZO5 + "EfS=`V>i3j4n,O&HTs5"i-K@!\c[@^q.qqJ=mGSY + \4Ap*6n^r4PZs!59h=QW-*$W=XAuj>=), + ueX,(_>$irXW"A#[-@F.LL+MJeH^e"l!#TJFnD@;$J0*L4O'7q,:#3NE1Vr1`q!Pi4Q!=a5 + UiA;VY0[LNN."_sP>qq)-JgQ-Vmhb4HYZ9u+anXNC)_a4-nE7@nUMf2X"F.V0-[8f=I;Xl + Wp!jG4,%WBbrPp>jKLQ>/fZ?=.ubQX3N`Z^$[!:_YFXfj*_.qp:hEprEjpGI@T6Y^\@uYi& + =!d0Jf_O%^-%DQVd&iNXrtY7G_H3FRUujbBY`_$t6W4o:E*,HC0EP4kI)u*p+brc]8fMQe) + "QFtL\.L%Jjur]!Ab29,FG*+QX'3(@L;a5_dpj>&HO8CSa&q'HTg6:I)H)(L=#[M;-WD%#< + =0ns61/JIns;.6go.F@o3`"JqJ1GhQOq56&;R$_$_EGgY&^+,IoY8%H%G;#fj^W#dTT481b + 1"lcO7WU.!ULBdSdB(aZZV"rV4EWOX7Q\Z3oPJ+>^=)0T9h7E++q@;(Og6T0*FlLV:?7em& + o@54e).3%QENLCQ;PeH"FtX%Xk*h&=\AX9K@$L1QJ2R+bF=GVV;5A + =s$WO-iOe(WPhCi-kh[s$?ZgRE\4DB6j/2`B^Oqj2DA)57DF.0pk[*7\Aba*G.spL@9Jn8G + H)GPCP&hqSHeE&Nck2mqc*gX>caX4bBDQ1*]KpPE8&.=QNDqnQ;8pu$N-JN2d#_&S46iN"N-0H,;SRT(Mc["Y+I9d'o@nql@H8[nLq"l1E?C?nd + DFT93.jRGDSWbgi=+rY=X^tbB%MFa#Rd=kn\?Y> + L.;A]k?!q;d7G+4SV'2M/?S#A1cTokL)8a:.i-2dD'eX + hLkT']r7FdBTILjKoT-N + BDT:,9LGKTb(2sC2>V=f;8f2aDh!WXnZmKbM;&`OU]n@fM1GX(A*qu?>.0A"$)BCGfVU[rG + skJI8.j1UsHq!fCYs*1mM\=8G]'99jQO1>pc3>pAX1Qk"5#r&Fc\SDSWe-W[=$8D&]\YGY0 + dh>",S=]>E0k:hV',WbDU+r.5_*]7uqkZ>q(:#2$\s+&;>H5N7-b?l'16\iGTUWdX/\:PY3 + t&:).kljRWPUD6U(ask66nX62S4Ph4-M\AP<-=f4Yt9pZf=9)_nNbK^nXQ=Zq/J!@+$9X__ + dRq[$s]N@R%MO5eN#r%R\B$T]SV:bJUYa4PNfIqc(6BpUWE\f + s5V.MC-,7?P7DZ(?S6`mo?C7M]`WQ!*$i%4_?9b5m5HJ)(]Ni5,B"udi\=^a22I + _)&!Z;[ep'.>"T*&=$)840Pro.;`iL:\BZ8:hr#/0m#6,s,Tp,'53=N/aR$]#'[]_R_A:^q + pLR/YajL1aJ=p.$2N@<'8\j8sRnsT"41rJU,brXPgnDC`ccQ[HFO5\#M@<5s!8bRDTe"NS_t*ms'kb\\KMoc[2naO/4D7XWA&`+`+[bKSIO\E,-Y + eBcR=E(q;V\P4WGbQsdSeOMIHYk6O;>^'*lg..+U\cjq,1jmR&*67-ta7H6))l^mm)TXJia + ?8Q>XZaM[)+"1Sc@gsJRkZuaW]Nn4:;aNPCU_UEN^AEg:A[+K'Ius29#B*M1`S0udPXU,p. + 9>Cc]%\,KUL/m91cER8^.-/j>k?J;,qX-_o2X=m/fm3!tSB78k_Ls>c#eh6dULb`+FX_l68EJ:Vid?'%ai&Cg*rR+[c;DiR + DeK@a1G1U;LdNs.J9m'>raO3d%be5@O>pi+K!Rd4X^cd4m"9hLGK$qSVda]s8[>!',FMQ+C + \\0cCK\>"Oo@$Cfdp1a3e"kH?i(*Fu6'XLV/NcQ[XOH6`c>>eX"[L.V,#4[qe3-4:=KY367 + PfH_e9+=!V!u"7Ojp^&<5b%a%/.eZpVSQX1k[^'>uOP;S&9XP_i10eI/m93TiImP8iZFm`< + 9":VEI:MBRI1Kt5rd]%`L^>G\sde\'Yu]NauLKZH + :Y2/n@B'*/XU=3Z+o](f@/*ET(^=*8dnKuqaXH[8XWe]llBMGekPh$#7['e7i^.p+O + ZdaZ`Dm=eI:L=6P?dhLG?PhAtug=X&a=NrbEc&U82VV)'Mc7M*a//uFM4[Ii)86"bCA0=hc + kt[L1ZR`FdPjHn)-X"CUaR)N$g+>,!n_*Lt:HXiQrLjY5U0fcRUQh-G'U]D`qMiIkeO?CKQ + )N%%3r)Hc!JP7W&<*34d8G_SI60D[C.Zqg[2]/rWqGZ$/i5u@) + `Q3c3rR]%cHI[i7i@*+<(>qB7RE(QY:%9i;2'\bZ?rT>](D6]6[_YaPjQGiLV%5Q-A#]pY= + mHiVNRUm*4>qJ63i?0Q#:u;[?Z%Q;'Lq@Y>.aA_:+Df]'8^dl&c%]o5IggtgM??/uf(rSUE + kO[pMP;pF@U3Z)!nL"Tbo=`rn&XQirJRFFs"hR/j&E[TPj84a!R=oCtM$bp3toj9:GZqX?M + 4n[-l;HdB#[*).n9rLFEWF*<1g8iKP]KOfP5@=Bie_ELL,#qhc='I4F>1d(@C'T&^;2N)Z! + O,u!AWo2P,L6T([P:.J*^;pW`\ajp5TV>EjT8(EU"?f_@,lW.QoV.C!2:i3Gu-3HY??0+di + ("QD3hH+:>?Y/Asl@u5%QD\JpW9UOblF*eZ`P.#84G,)+Kf6/ccEW[[&0(CnLFLX5rjCA/( + ZFLjCPHZkNbrR/\a,i)CNaW3e]utL,%i7;SJH*&%Ut + \0fIRh)2kPX4N%,g.mJnR%ue%-2f:,Ql75?%87:4>09I6m(TZq.'OhPQ4f^=hQ68RCC-A-S + -+'qenK:#@:]%/uJk#*i_9p=1cdBlMklK21Zpb?CJ7AB^_-f^WM?Skr1S>4tg.'jnm["D(! + $d'^6G7\pr&VJ=tgmmaEt:@mpTp?uVfmp>eXG56'-H@auk!C:@K7fA+&re#+gQY9WX3fn,h + E,[?,lZWKODS'\N#4&GtCW:#%k.U7JDenc\i\i"I*[@4f-0JQeE<4q_:<%*V.:%@X0a>^.[ + X;n^gU\GT@f5'&k(.1Thp\HSm0,Uh*edoi2VqJi+kAY8-0T2o6o/*8=g58MSj#^;O(8,\D: + S,mk)nOH0CAqZ6R,'-9r)-.j2nFuBmoeaV,gCcRdPl3?e1b8VOc8OlehsJ&&+^ZG+n?dU>% + TqD\6/k+a3<4qK)B+oEP0a(I.O65kHEkoJ]21/62tb7Ns9QNambWqobJWks&JQiC1e7*E6c732\'u*ODt5(\OR4jG'EI&f3!IRiiUo( + ?HH,0b&Mk`BD0Pce_]L+)b:JZ;mhcp0%/E9jOmSa?>bR>*]_<^gN%`2c + Sm;imjciJ0Np+\eHPV`,>$4Z04^!]mTrLBp6k>t9QNj)m[?c9Y;X;p:rF-Ao9KGm,$+.*UN + =?,l:kd,R1j[D7!)doGfP^:f8KF]N;2Rsq"Kl85rXC5Y\Ob@L\Q>o:_3A1B7MZ;J1,OER&G + .W9uaY-7sBs)"EAlnWTg>VD79S)=7B:j$t-M?/tXee3XV1D.d_LQYu@Pf[OgcA(\8-W4H[08l$f_ + kma<3:Ngr):KfZ@o&I\rG;&^(ps7LdjK8:Qlc\21!6QUhGTF5m\5RC("c(!i&,+dB.hlF3,>G\lE]1d + l8/kGU,Q:1[@*0ciWD'SrfjIJ81C9;sB\?b9k;.nj@WO`)YGa99`NE3H[Yg_qrn+M4JCU + @122ba1qo4Y:VZa^u-%h2aZj0Q9Y\,/$cc0+]0*B%Z9S(?lthsAXp0icuspUhP3(RN&&$%p + 'e%qiB-n+_9YmWHZfaliR#I,D-Jn,31#&**ln^i0\g"2N8W$O+?XA/,c/6BI-V$,Y' + 50b)"hlno;@OShNY`/t0s&]F2j;&.&ICf;.hOb@!W`fZd.)+P:?EuW.sm!0\nEX[@>8.lo% + ,0:HCPp=Ck9UR9MdYsVkO<[uk/&!5B["$8^m(.11!#3C'9Rso.$6I4WS:@'K[+RB4Z[CjoS + F^jfJ'RZ0m6/c"]_ap`oCW2N:fK"Q6dTpL%7O,.>ocD_'P/R\dWCHt9@D>7.o"AhjRkW6dt + L4Ne8pUD;UhSFfi9$BAR/0N#W+am#iQ5sWG6Pp7gc$P!ELI[idr\ksD:s-h-P;r"hH-Q:HJ]OPmkpV + :jo'L"B+>#pTB?NMK&HD^&V]1GBpjS3k6_=J(F(<*R!lRgGgN3b9R1[Oj0dPP6*;:IIoCKK,r4c< + q,p5SNWh7'NfT&KF(/!8`"?3?5PG`IiDm5lrD\F$o5qpP/]SrBiK515+Rlh]J^VT9tnbrIen+?XA\Gd@TU_a)/54k$]`dp^- + "sqW]"5lLJ'1+68W^n\?i4>jd&8b4ZeEj%HigPN3NY+T[6OD]s."o`?:r14VS`%F@K2B\uX + %7'.iPN!WlIfPr3.2Nq\O7E7rg%dkj"Tb7F$ + @kr6%7+Yr"JJd9L!Z8+6g/Y)TsRRN;+`-'BZ1d_<*9tEJL2H$amfP!NX!p2\1T4!+[,n!&c + r[B&_3s=VGLln60MTKl*Mq<:o9gN\p[?'e1[b"igI\VNDX-?8V]/Q*@rUk&*e:7=0W?K?Q4/b9XlBc6hC-ac,8W4Nk608nVC-#;f]EO"p'SCL"nM2J11g\ + l3q?3n_ItCD$NOQs%=rIf"MmVS+bO0,HV\UJ_GlteO6eD)Yf75;U5V["MO+ff/g7*Rd[\ + fL92R/:4fB4$'4T`o$o&Je`-!:8eD+n>.TK2G`,Rf2-PhK3j7U8SQMV7A][eM[7.0 + e]PD[fMp[,2`[n4+j"=JoR;TW+m(H`s"b=fsrJ]Akbo^(7KoaXd_oR0;^u" + No4m0Z-T=Nom3te%)eckKSul2g'\FS/TW%h03Kl0')/=1T'WdbJu\WI_hV>b'KllfnQ)dot + `U0"mipnZZ_)Ig06dUm2`16p9rZ.]5MM&c*b$)#2c+a"AK.BO8o9XkoST_$=Q4X]D"92"@. + ?_N[:N]nZ&t947='UgeY]TG6#V[#D-nXqi&$Sr8/JI*,h?3Np;omi]-r[A-J6[Wm4&6fbnU + :LoFOf##E@KV05npVoc6YL6Bur;816!c).V\L!u5!:7!JZ1 + )u"cEeWZ9C0@1/$uO.U[kZ8JNF$9=MFhUdsek%j^DY\uGcXYM9p. + NrjhJ[B/c,JkZ#G(KnJ'n*78a614WV!p.e9]G@lX(Jn#!f51LYls\IDGUO5\JqkEBGJa-P# + +-$!)#KNPYTY$2GYrja>sLoDU,bpjZ"[A\oSi-9;k8oLCIjOYMbndiZ;WSY + 6Pi\K@?+G/5@Msrho5)Ilo1(gFQ5W!Zfa-dOIV-lYcAmXb/rXK3OT@)#,&I1`XT3,G"ON5U + Zqr19XV/#.(2[hdlcWJu$Y%P"B%"uMdd8s%0%B9M:meEBe16.#@f5YpMRa0o\Sh*rNEVfEU + @9jYFc!j0Kq#Dp$epQd&cpPWs]KB$S]+!E4 + lWU_$dKb$h2+DYfNK2GS7gggu&hTU"9bBE=O/k&hrP)i&UsJ1*3.#@.1&j:)_-uT_Zp%&2= + J&E=DZIrt-s_iQn[dE>jVX.hXCbqlGP4E@h"nT+`]mr386BOZ]aAdcB]KrJKV5cf#T'm`Xn + g'klh1n*]?N@*LA"c&3/6%,d6Z#3^em!:Ga/0G8*fPlZo5!R@;=.mFm88Jr^Od1McK0K/7% + TPcGGlqYY*0$;WNC@*T.QnI+s#8[Q#P4!0U"q=8p$^0s4C=R%$Rdb9&5m3hE<@OCE@'!I&autPW.b%f,l< + ]Rqp%28%Q+)\3;en&jA4/$;Olh<8;rR3'bB='C@M5-=T6T\YRt@E9\/BHCB*c)c4P*tXQLC + _d21%"(bGP$E#u?JN9X9*!jF=1Y-dLlT$d)(Q;5AadAmCIYdWuI)fSR$bBk2k+LhpB\8fUX + k;Md'<:>obn'TjY;;O.Wis3J71N*^DbI\%\^E74Rk+^+"P5NF%FqVcQElu + $56B%Z4/`LTf8422q4Zr[&H[iVb8kK92r/hFM1>@iYWh*!rsYL%F,J@d5a&5+&pkq[+sHpX + pMn+4DlN/'>I<6]h^KP:K6j[BR*@:`D@k+-oD;DEeb:5NBM85.21YRZf=pSkpb`a516=O;Q + 1BC"(1BH@PlX"'K2:"o@W_ET[O"^,"_%*]a&NK+b^DVh%m$iS5?;H+2)>/r2_.pO/$WM6'#,tS%ofb:<:C2Foi/43=L,>?68`7*'k]l$WG4;0[' + b$Dn2VC!VaP<3==3P@PYj,V<:Zf\oRPOOnCM + C%8K@0[i'2[(NMGK*T;G\p%_rpnT+@]F#Q+#(Hlh + `5BF;"FE^-H@@@m]&ccI^f`=j&Zt*2nGBP3?Q7mIVI<[/[GEt0s + '`hgP$Y0`%;)Unbp.G7#6W(2OAN!Uf2Hs(:k$VP8\JlU3 + !ct>*CCQ(?GdZX%1Nd>F+k%c$K`&6ogKm>ciifpITHd;2"^EFLY8'Qrf!O@>4JfL"L(6!V@ + 61:'Mh9">.op?s"LqJT_i;)OQ<\\;]5$\dEtYGds/dOW/N\/U1"[M:[`EQ]F8[4Sr3F4ZAd + eV69_ph0M'"AQPh4FX<"?@4F0GrUt#*&*GH&iHu1+5V!"cPslIr8UVA1k`XDCZlMH`Q[Ub; + /beJo2PEJmQ"k7m="lL]dA$fOIkS3`=>mg21C%%0\SOim0c%qP1DamF++c9lD:EPGQJh6A@ + r?C8>?:,4\3!pgpOi2:1j8kCOoL*eCPI(DQT9>XH$u#^JXG81G0Q6G*n9o8*;-4!merBp?\7ZVQ$2ILOF^Weop/ND>YcH-USF.L + "dka4R!OUVg5DnUNN"t$WHsW)\s.[Ra:L:0!):`Hg9$e>YY1-Q"T#X`i$&3[*0F^jM_6TNq + 7E.Xc)j%SXa8Kd(/d+mPg"b9JlTt"SniP$V%X]jJnS*d>Iml\5DSD[:Ka`&(iW&poggoJTpX32Kbb<3>YI(d^.^nG33)*q()i,T"l#Sar%]U[VIT\7 + T\#Rg^7$2aP5I-nXpn]s;_cVqrfr;FC!jdc(7d!(n\g@hr + Uihddg.dd\@AQ)=V3iX,dFXW[F[qfSn=+fK[CmY"AV;q]\C4/D>eA3B^Y8XogHE+%*PL3U^ + Y?H;66Q%3@&G,tTA;Wah.4#:S_OA(i?3[u&7G*+r9qqDs5*\JhfT1]*W=km-."aRY+,Ts\^ + tBJ&S2srd6&(RYt(CLms@(`HZj>"t]Ve:OZM$6@' + f]=V8!2?_Y5DUE+!B=9Z(fECBURdrrX\Z+[Qt)3ar-a8CRA>_fcdOYYaVfl?,+I$]K8LY=k + _g6bG<]9D#6Z.scHl[H2+T!dmu_<_*>,/IRtJEslK!o2]cm:"?.^"Mf3rY^1G!8cqpqPG#O + 4q`$+^W_Jk*WP"]!Up$ln)"F]rqXo)$h6%+^jHC&]'nU'hpHlnnoZ>>%ThXq-.4gFd^ot[\ + &BlP("8a=TU4,gSk/)C60.r3P>u&QF_-Zg,N\;'`I;B\P4QZkBuW;Q[+oH[GRqhPP=;q'kZ + iP#5)YYSO4a)GaShCG5+T1O&%E[U>T%1->(*pT"^N1ilor,[^Ua7A\,8J(r*R;=gkr#L?E]i?1+&6A + Zkac]so`j(]FLAJoEL7G`4g6)2lOE)sjL%7jjJBAb+NERjU.cDs&qFp@`PtU1@uG%IH?[;l + aEl[I)e1c2Y*'lr`"JbBJ0Dh/D&,=8rM\_W#S1bYB6/\]j5$gO-/b6&3tU2Yt+I]*k=fD0N + W'rr7h-jRY:B^5k']JH,=i]tXN5`II=P[XS6Wa6WB3J"$u@!<9hg,Q;#ba8)n@_,*fA"db< + E+D\hjU`t;W`S<*T_[th/5;nVHucVrep4jVT^>sP%EFTk+YPVHB/;+>[G7j$l-=\)FiN@.@:V- + P6b=&jBk>?FpKQ"sLQoqcY4=#K)`?WU83HXPJ!'oQ + O'p)T?,=]RtXB3DZsR;#gtSYKq'p9h5AgNLQHD-OIQ^N!K0-,XcYG;[ik?$AHdF^KIi/Ijj(BmC+k+2socfhfGqfMpZW!CR-OY6,PWA% + sI`+eiqf+j4(.I*c^DTMu,]Ok&naFjIaV#uU,KZR2QR>B<^[!BK`g"189HW?=1dC1(R$2Uo + 9s>?m^i\k4;p5VFn23D-oaM%'6k`Ft/djsI.]$P$P_4j9:+=.L#ZrS_KQ*3/_IuLIiD_7[O + J3OX\6fVPKJS?Zeel;LBau;n3JW^a+%)uX%q5r3Lf"p37!OJ"@Pg]6&M7H%Tlqda?m>cTXt + )4l?PZ/=>@__s01GZOa2_.h"4,thB1,Z-IVS:Yd%$>>MS[(h4/6LY2Bp4Ol[n6;Yggq.@09VR72kpg + 3>Qh*H:'nh+WF"PR;5';91t'Qk,&8LS\M_CCbSs&/kGAo`R"j_N*,1Muc?lKMm\oA2q4MoV + la*%?e_3*aY^4WSgNV+di(`h6J'[/pGr(]g4S0WbSrDu%cWq&EQ`Q"@OsPIr^3`$]mYln$k + 'XA4Im04Iq[u!Y8[K6-V&BpE-rO9!0D5:Z(]Ki[oE7o#)?cs+nO?o7)c-W"5uHhp"JEVmUh + X3G_72V)ku+*P&Mf\@kaP7[I57Z6cC7a9ED7n42lCc[Sl>K6:hf;aW"fr=<*GD]l;^8`=77cY\,O>_'P#`>j8M[C#CfHe,D445.ff(:JI&+'2L<@YNnD>NZH>kcc<]Bha + BOuk'[#R2r9=H9%%82tR(:D@HqdD1U4/83"1"4@qV)-qM]/rd0fhH1]2;QNpGB8)j46aXB- + PagM'9_N@a-et0EY!J;Z?KT"iB5uXoXg=Lmiph&51.((T*C1 + "]>5)EWm.t1kH(kEB9Nkju[T4o&CK(P0qdYna6.9mup'Xjjfllh#NPtf,?7g`[P/V\5I)oE + M6%:S$XL[c-$`0c;mi]Kqm@K?'?XI%B@5<6940[99sd$$+M*=Hq,-B5]^0%3Y`t>m/`>b4[+7'7=J!ntGg3M!OmdY\ + :HBJQM^KCiotR'L'];3#?48RS?CtPTn'CXNFWFhW(M?On+MHGs8e#f-X\6lA38n9geD3&_0&Q+&2DrZP\?[h=i + !B8^?3/!ohoN%A+haB8g6AFh[FjI)lFB$32Iq,FNBH-;O6LDS0G^3I%Je]W#\?(\6ZJ^?Fj + g=omDD5MK6ka)*9g]q\9-I.mG[is^ERE)4IiW7O&:THWMg"%O#>Is[.mcq:#GN[P]I%&5\h + Q:RqkW2PRl!q1eKXH4#m_>J16Xa?(M1:`/aM9WbARJ6!J[TtSn=*&T;U*t-HrfV(IOE-[ru + L0JDj0`5;os;l1$/>bQLg\\`K'J;-c+.^"T/._KtgGK]iP%'T!a^WU&F6XhkjsMJ.@.*Js* + X'Ic4W-LAUBTn9BH#YM<(:EB,&Yo%2'hgi)@qU:J=N(Eq"jTBgN%0e%BP7WjN423m-bG + 3p\^l#8'>.5`5'C,(;U/oIjFUOs0V._mVGNpq)HpYH,5f_n3>b]V%:h]@r9!!fstrAuR8C[ + Y"]:>*ZOkcFhoX6-L<7g`+)r*C9<=62Vlm$mJ"@,=3@p_CQr9:iqE<9nA*6JIC0L80LDZgss1o/X/L\]S;?geL(lpRNP%Xi!O + kZJIg_hmtf91lJ3;ZV*-n!BQ.Z"9c+X"hNb!3q3ftK5Q"Qt$KXMm!m8+<7AHp!;Kfid>ekG + eVlNk6P4C[[$Tg0R7Oq\44']d![mjU798mKs4;,(@rkUu;46sUHFDSC-(q_C;]JBc4PeoqL + _agN?bUg\+>fBBQ`6*Q)!0@EcA;$>3'?p;-LK&iL*7>\RItrOlbtQEML + ]N#;!RfUlWd-hG*p2:(38BN0?X,PGH]n:.iXR>aIGqOi`6>cA + Y6.mhB5eA%a68lMJ,C>dERr_Igp8sB9DFK3<5X<( + &<]"7/feG^#4#.j0*2,ga&*+,l^nr_.)24LsG;YDbcA35>obDT^hXL^l['#pDN7]MKg]4O_ + &D4n7R[YbU3XMN,G'25MAbafJG9bpSp(K"HSd;9^tSJb;)+9`g;GA5MO=LB?,6sTPNBH+CodW]'.oMogd]DA%*0F.ci@I[_;[h[2;TQsZ69,bBa + _A<2KLJY"2pS/E::3)R`9ahCSke=9:BR-5'Mcdro"U\lb!`)F/,[P2UsFYOc]eCTUStsQWC + ;tkb0s3`lVZ"+YHHYe:`W7`'a$o4oMkW,92/8Q1A8=q?-n577V)om)Z+#WBA-FqMK!6KJ=bqVLCBj`R;Cu` + dfQc!2*`i5S(k<>%-kQZn?^'3^;Uonfc$!"(G:X1Vf8VCB]eRepj2jsYc=B8eFYq3em)b1R + ;hL?'KU]N)4'4OJ8_i^Tj$$'4n!qWd/X0&`RUo8OoL1%u1hB#"F8a'UD*RYn8u&=;A6?5*r + 4k^)=\VOMjBYRr?Suq59.KL*3e(-BJlO'++.WYV<=$DI0\'Whd'UD5]Y%6u%7+Q/49\s:"^ + I;)DnJ2Oen&CGSZ,epAQldI>/dOGc)t7+Y?KDoLEKe(_pQ.LE.L`j#;rnih6pO(Jqp9LgPp + 0&!!UGG9nZ>-f:BTVIQ)AVm<[4rHW)MD/<)I"XaXMk6s'=G3BWa/H^7bc3S6b + ]`2u.#`J3#m:!+3-:F&`T$.DigN'G"8^XT127&\cJ7G/=9<-=A8V+Aq_PglOpciWUX$LZ7c + Ygi=K<<\i2+XC4/%a/]fW+DSjD4i!dTf^3)`h^:;[A:g[^ruCU;&*A6*,mTSYZgG_gAUMJ* + V!JgnD?D"?UaQn:%f=S=+jsBJ=$RlD7Rn^)2DMq>Aa=GfR7,j0OOdhR#!0m80P$1qV3+,?q + pi4?,5B9"[6e@gak]q9eosZIFD4*50?g1eKAt`(F-+42f$#h"B#i#iJg]5M[ + X7=Ns.C23(ITO'QUa@ER\-XccJjj&nN:9&m$Zb3[I_YLM@4^n$lH/Xe'si`Wq%g"#.dgKml + [T@I1ZeSJqm]40JWl;ajH>$;?gC(4?p'&NPqc*Ug^q2h/oXTh+$ArP? + k;T<2JABop0&E9\Fa\@T/e,TNVJK494jZ!2_r1!K2S%P)&0dkY?g82MM34LM.MKJaaY + 6W*Y]h9BAm!1Zcf&J> + iH?#C,kKLq?.:,uiN5K0"s$ul7U;$a:W?pEPl9SUs(5iu-_=u,p1W->C#0T`=g@_``T,K*A + 7k6[?>25hXlrq7kuEoJB0E)(:,1p<1<^MXBAS#OT"/X>I-\MXCWQe^5_@%c=lXmADRXRmZ>OrI;(=`$eA + 3SDUoc_aiUEH>ddYScC%qo'N<;H/UeGW*h?(R(g+Ki=N4[s1fVVP@-T<=h1Vh.Ic`\kkiua + 6`P*O*2;UTQW_b;*AuoG'[O=shj5*J=g$Md4Rp]QOEEX\AhOaC:k-`S8[a>SohVo+,7_``i + qePWsn#lS,hYhB\r9sXa((NG/fJ*Gh"OZ(WJB%ft%gHig#B4p3n;&F\Sq38h7Y#_O"3'7hoF@(3P)Vi9&V\ + Ng19kK\/Hj3"LP\$M;oeJ07+%R!nb58bg&Z(sm$mg+7f)9c/pd!0BHuO>M + _pBFji9Gu%E8;_W?$';Y&Ym5@gaK$$JW=`K0,ANu_ALDk9l+,jmJfp2T!\I/A)R.;ji2l?@&?s"*H,,^/VBlGU&N%l*4mF)M:>lP;&C<"n^p=O>YY + k=52eQ83NX??e,Sp]nKZcb=bH^O+kQqsQb)OC8:u7tZmVphtr_p](=2`s+3jPs4 + oE'Nr*TLq=9J^s/qU6`'N^TK+-iZXtn5FMIcltJo0Fe71SQ^tkifk!qu%1f+X(! + ^NsU2g1&h%nmk0;[JhVX>M!G%W.6(MC^2-u)L!l2!1lu! + e+s4PKg5Xc9b_f:-3$W8;eabXKgqn;W`HUsDp$nT-LL[VmBEOBa]fqE@]\ZVeG[,,2I?acnF#eX)LHhW\l?! + n+cF\S4g[',gh1#8$_VSgJ]Y70#VpXi%q:P["H[FW*q>F\#:VY/6p\hRNYP\+q^W6/[\3R8 + D%0XKY>RkWgO&>RVUSri`R1*Z + ?m"p?D];1YgiXVRn[s*Lq,"RFXrbP.=<0JWMdVP(Qas["g.8$nAXXb3NUpb'^ZM_4^R + `4($1/)a?d5.61eAX=?F(tVgc'+CO>gB[dX#j*5_/VH\VPO5WTC`9k>L9`;WC'N>3Mp"&oP + G[?;A>ZI>1)uIVbC".PrDF'L\[cBe27Cu;&49X:2&08=oVK%V3PCiRPfe2J!pP#D-U-7c$< + T`"bgf$=6KCO?s'i\?Y!;/I?S8GAT@]^=r*BuC"d-8UMLh!e[U*sV.I>Z>PATKELEY/]l[N + C*RJD(WG5n/hA;aEHJUTol$Qghc1ueFf@BtOi&CW=K&E"[#DD$fd_8N';#GH3Zj50t*HVG__6@!R#'Q)hE='KYbUj2i^&11S6r>42il,4d=W,4#<+Y1 + 3OjB`aa)W/NlH$8)O!mSkgn3uV7`;VaHr@Vj0`8[J_)4pD>grC@s*WW_r_pc.qYl]QnNq_( + r>#=Z&,AgH(^0n=b*RY*'i$4^4K0H4UOgRXBe%)-"P$,OK$)4DX6"I.e`t#(b6;?+AW3s(, + 0O,i5\I31Ws[G3fnIe](bLHc\=oYY>u9@#6^D-jfmGqp0`E+]#^m7n,X[C8$7&#AP7ceR9o + 4ur-,g^l'V^7ZMU>HU77ag$inR[pOa\rQ&lq4$:oG0s<'CL_U(G,%%KVPq3#4ae*5g9K*mr + fA8!rrjj5bWL`@PuL&@3M%.mAtK*2.oVZ5/%:8/-YD64T]\Pac`1DA#uD8TPg+9]V^]P'+K + 2(AB63oV=@I$,F]M&E2_\L6^%E`]3d(/C"'WE`1au*G7.hjl;r!a,SJ?(J*c9=n-Wu*Rp^Z + @kukmY]d?9g,jM#mTnS\GV`V:]HfOo`nDhd%iOH@\]"):PL;t)7WY@QX#d.Wf:C62GWhjgp + YQA>SuI;55%M+uT5=I#:W5:?o]\WuZ?r7o'g@UW"THmir."?%7DM><7,U+pJC#X%.TdWZTR + *P:YY#8=MF=RSd_19k%[99PO%S@\cDAmY@R?oj9\-j0>HkORY#oGMl/Xg0d-<>T#eNpu'h, + &Y*hlVkNDaM&9<$GkW)VKG;sKWJWg-nieXEU@Wl82Cd5Z@Z!C/.<:id%r5Tt\T+p4W0D(&g + :\.)X'+7#bGY1;CA'_.5j_6&-febl*s^]Y*4GUa4iYVD&U7O8:ieO4Fi9/a=DAE<-CPOb0d + 8m=/$XP'Ii(4F$A\Qrm=/?lM0b1"P#-YhbZc^1eP?-Dt(0d[XBQua.n]"+#Bk%h00a9H<>C + P7=PV8Ye8=U`QSYjeOOc(#S5j3C/_FQYLE]$mqL:=P7cY2e;a;pk-]<4+on,l+(!SbGqci1 + +/F#CcEYKg2"B6Jq?l^>cqk#m + *'8o&Ag`7^eJEhAH]=fDALtQ;DUrFFB:SL`g[1Z!sY79Tc)ZG4MldJ+4"TqMA(M"o:j[43$qS'R)7UM^_Id!mM^Xrbk?mm7NW2r2ilSF733%Opd1`k1+JDiAhQGNSe[gZsB.h?1Of(Br`e++`pQ%DoV + ErFTN'Cl70q/d.Pg&V7_ML&dYR!Ji*+3?oh]r^6_e%j_nESZ!(3XigHP/`jrBIe+.Ibjo^& + jF#ah!K\i9$U?%? + $i_srh^l$+(Z)@PI't#dG"J!D='K^9(l$>._DIXfIc&L\fMLSXQkQdg>2PP)Z&+HlML)r_/gL[r/TbP:'&<=(=PoY-C8qVPB@I8@9 + D`/&sZ?[(u;n%nc%QeqYT8+D&#^*#XIm1MXip\sX6,X7pjrnk4MrVuF65MuDS:\atYr;Zj@ + l;.cdn-\GH*s"&2'i5VFn/`2"qZ3oI!\bu'cmo20.0;dT%tR^_O;e:q*s=8f"40+3n4s2&p + B/cA&^XYacrLlI)[0&L"sERCYWW3,*!#B5"on9G8so*-n%&WhhT5oU+!P6bSr&V0BXOI + Z]^T*eDi$BUqf_%mO><=9AG$S]"$OL#>a\I:b>%a_TlYfR=#dLf=!%<0c*!>Z0L[14>p%WL + \J;!AJZjpmLA%q-1`YkAY`!>%Os&2c,81Y + IB'*n>0g-3_3ttOV'QHJSO\Z\=;\ZK<'bOR`;-OeJ?PZLE(6N)BZ"Fj"Eu,F4(@c_f;107C + Ii)%8(m0@dd>&S/M]!+n'@&WA!#>SX&K27X)DS-.dAn2uW>e)b!rsbf;83u'_\nTU*"g42d + E.[$h^d]Y/ + 6ZR\q_/!@Lpd_@=G_^n^+/<\1-da'KXdk+JL/X"j?db\`U_?Omi'^emI;XZ7Oo&[]U07_;# + ^_ng=i@n180Tukudgn/H'/Oc21Tg,aU9o2@l@7u2Nqo7dr./YF#R$A2j8SIdsj=jK/ceb30T7S5[QMP,< + 8'E3Km<#e"8Z2=<&!O&X04!e#ta[ZTD*p4-R9#i-1IG,#-LObKZp-UF(^`)h!jkTdoM7mi;ajYe7?h"Fe6bZZ@n'F17Zt_T^`Ef(EY+IZ + 1gthSoRB5mK1Jss1nhNP_,j2.q+3iD8Us6$dpY_QUInL`8sHAKe?;LZZV+9,99d"9+?N72- + 4)s+9S#o3_66QC@NVt69pFC,e*T"Yjr.4](0'T[5p?dqndC]U#rMN?i+rO=!WnVk!]:?>#i + A;*'2_aG;3`(teK7[(,>qMh5nkP?THXG;1J,AN/jPIr^pR,Ah,HZ\<.S=S_4=R,iDik7Q +q 0 0 829 466 rectclip +% Fallback Image: x=291, y=1, w=245, h=50 res=300dpi size=642675 +[ 0.24 0 0 0.24 291 414.268497 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1025 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J3Vsg3$]7K#D>EP:q1$o*=mro@*U0p1+YAC!+S8"jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/m%JDUZZ=%WR4=1jRLW!YA=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&of'PL@t2dVg=VpoI(>.nOVg>1C$G!R^WIr2[MDKfeRWhS6"i.H,Ig[(s<55T!58(o&,#o>r+a.'*L$K_(AmpBLc1?&8?-UioiaGU2Z#jRp1Yhjk:XfgS6-n[ + "Eb6-l`=YNU'Km*udM.*T>LG?*InCOI"Ys[gFR:q]duaZZ;&6$;I8ZENV+joh'ie12d@9=i7@prL$iF-8"L#&).)4#uP=5#R/VeVWRd9USCG1F-Ag + XcnC>a;0ta`A2`29U'-2bi%K?3S/GAmSQ8ds@>aQY\[Mr^eX7 + n!BINQ)aRi=mH/$@\W_B+iOEd%EAV9G>NY[on/MB2^DV`>1W#=q7gm5"]MBKcEVt)a9::KH + '?TgXTias.]]V6!oGm[.c?*Z28l_%f0e(Ro4\&I`'jY0VfQKZT@G>s:=)^`S*T9rSGQqf1i + HRO(;FpRh5u:)iKh%EjH%'\*l1q-)VRhF_&;2Pr\-cK2a'r%L*Mc"(=7>SVmir!)@&WSS8=Ml$:,\W.aVD^oR + 4>;H@JJVct5)SkI*CSh6%0@Y1Ks9bW6>0R0+S6\+n4j1q/Ei + T+^3,M,lT6JAm[aNlG>CNj4f?*q*eUnL:W`>qB@-"<1^n\,+m2.H=T9Fa-ABgH)++X*ruP- + /M:!e%.n>jAUJsidNtK`'kk-'noS/M=H9k,XN@#\\W,iL>iCdm5/uB>uEf]bJ9kh:!VZW*O + ;o.?eWW<&>_iCEi8r$8Y&E9ZH/9]O:g,MV$:T?9=WDug25K@>:X#4@g%Jm>2LLjkp6i;(dl + H*20/SD/;G.#oKeQXPKruFII\Q;Dd?#,PV+6063=3C8E7b)ji,Gr<0;>rT\c%iP^,/mZ(Z" + 'kckHX_OIu\NPASf&GZPHMU&+RnWD#Ks4"'3`O6-X,G5lVh!;Unso=)2YLe$_0YEa'^.1l) + %=[l37%>LB00`uVq)2ZhqI$\<;Q*QYb.LhDeGR<,NJcalJ)k?UBmFU&B.)5I+Kh7L:C0(n' + =KD][p43Mn&0[%+qW,Q(de2PBDs1T;lptn1"12iHQ)qXBR%"\C:YP%t2p]P*ar!GB%4p`=k + FCS^k)hTh`ru6S,$"6LTKlE-l_`2ILiN,()`n82I(lJsb4?tTUptE\(WtaQuK8OYng;Z+rI + uTSANH)Zj`u*<5j32m/1$I7+_uClj9S\a"^qIgW-u7nfS3P$']ZM*b.-32_220mgb;j&aAT + ;jZZN/P!q1sN[;0/.H5%`%WP[%h:r\ZKLE6[D"3Tj8=SMReU)IhGaB?^+BF3q>A3Xf_9+f$ + [preJl^,=Q:nAj=:qO4$'V8II*nV1t"td_e=PO8V):[-jR96GlT66(b-a0WuSY5?2IC=0"c + 5X4ALW=mJra1PK_Uf/hqt&+Q)=2>4i;qZgI8]o0/O0`Q;8-rk*.S[.sDc.Y6DBl..T[J$3F + g=pCZD8!WlG6ZjX>CUY\*Qbkd2X0#/YB4[,e[p$[n(q9\H>ahi]_Al1h])$2n(mZO\*!_K> + R,k"%Lp5K56^!c084B%'>sTX&E2D7MK(g3`43i&j#g\.ES`^rptm7Tgl%875#'.E7aW;&*) + d;D<$R)^L(47F0SL("%Y9-@!IuT"Jpr(rq>gKX;IcGC?Ar(n%nR-L/?+.pS.C274:o@<5bm + :d1ZmWl*qHqFblmLO!&A4QqinU>BdFd@*)Yr:ceYP`O6jIn`OL + FXj5PbLTT(iD"`K3qcP8T@;\7m:nkrW8?s=Np;nuWf9cRT+7ju;;5-c79lnn0!P8r]iol=B + WW#RFo=qk>V!;:Y8,i8=5U\ulkeWbT8LG:ccoqc?&p!JWl`pU1]-OpRN2$RqH>CBrTuiE[$ + JK+>3fG%6-B69CpqN4!3:;DiQ/N*'Y''^!NVCbX/*<\,e=Aa!irK9MmU,.1qV_`]PopF&4! + nog:_Gs"DdASm'b)R^)GAL + b])@^Ct0"Hd`sQ1c0XH9&Fduhkg/bnccl=66)P8'%+*6:&?Q`[iM=n!'[hso&Zmi$X[LYn, + h*8r'!4pZD,SpS1tBF6ehK5:m;1@5HIn9Mf.Dalm=a+dN#Am$fZfT?O^R8?@c]=U(9qQhnR + iXmD&6&h(NFj>Z%3^RKbs97(bpjanVnD@M]$Ms)3L1%Z(W'*V&@[[)DS92D9a5sPSG@='d4 + J._!hb_Sd41gk?niN?t13#Nu`,!*O4D`J`e'spE!]U*ho)(dI7O:[L5B''d6,H4Ap'Pdfmq/LOCA/6`pI^PB& + tl5MK_WlLWm?p1Ej2W`Rea.]X\ub,WnT'3iqneE/;j#r$N2P,%/b%`r9G:RcdoB7ij+X+rY + "M&O6j5/oX-@Cs"F5W')qq^B.uto!>#[KX*F_]LGT!4%+0V7$L,.Zr'6`<>r#@'Z#).i;='M[L + %IF!oPCSUAbNWFQk9K*V=bMj&QrfKEN!F.8f@2+L2@4X;1h?'lL9!Y.I:]$LBM]8rh":NmT + $BZQ^iSR5-2XY2^_u_O6QC9X6T%WPqRas%RN%n1P(nnl"uljRH"d-)ph5'_(Zmo^_Qa:QfD + _P&D^D5bi@H:o[#6%nrg?LJ_tq.1^*iDf7'sGq%8lp[p*42))&9WiCrZq!tT`1:Jc0T))?u + <0G'sn#lP.b:Kj3/Lpi!O2ER\d4Oh>Z7k#41@2>#lRtn#s%i);0E!R9"LaBu0[LB?n.Phd( + :^[k!YZ_[n1?E"0`uJO"-YSF1H>S\[569#>I-n*o1oP];$J^m#4Qgp$m@NOeHei/2_^@QCe + 6RlHT2FJ33]@h0X"h;ZT1jlaV1-P:rNjLV[Ur$3mcC#&D545[*:KH%M7qg&FU-1,s8Ub4V7 + bC1XsgYf[5ft4maa`;'A,eks#qT5B/JDOZ*s$6Ip5b'Wl]s'G2'SNtmY>6'PG>1ehU@EG'H + 5B=;qqep1p%KkNB$BGQS@G)p=FO_K!(Bss4>p6ZrXk^i3EYS/-8'X.6nbdi?<"i + KHIRPCpqP2j%:>"\SYf?"lNVhUb!,K!?b@<+O7SkdLgC1'h\,%+r%LZ1*oTF"RXVh9QDXWd + PUDa^N"AG,S\oW1u.T-iD+LT:6JaT[-mJVp@=IGo($Qm&og`l78VE[obU=N2&,`3M02Er;: + Q[b5)h,Q5TGcLE5ZI)[Xf!*\9/aq.@:YSqeV#G + E)=-E/.Mc3^k!<;_J[l''u'ooaZ'erW+!Uq0d2pf.feQQap>*n%In37%Kdkga_>I-\%'2$# + \:Gl.m#.%Tbljm@r"ZD_S2Y[^eF*3$iGmT[^`R(/^[I8]$X#Uph3Hb-*e"&NX"(5rI3k.=7 + ()"iY7:EO2)R^_ro>_A".1anP@aPEDG[V19'4\I44u",koBV/i90Y-aLU6qj&O/N86Y3caA + iPc^R#r-7D/#<0C"Si1pmm:%Ru2HaMO:5t\?Uqd[>NVIM`A>,GeK%qZl_Sq']E4-5ir@qF9 + X00OeR^X!,`]Wj_X_VNhKU9i%:!6M!;\\$K.okJWL_-?V7cuOLY[Zp@st4qeu,7OgbDZG6^ + e8%89h6,C(k\<7()Z2'dAeSZ`3;<8O6+..J#=Eq_F;o_2.lBs4W/[]4D'l,FGUFuAP + ZaZo[de?hkrE(iX=E=cKFcCb`P?Bc0=]`d$\'ZrIf)k^$T!bV&[Bj'n4^2ksI`pt4E)hBhq + gXP227:[e/0eFo/YR6&?&V!Rp_:[YPC]"^Jaf@b>2UKKjc6q + 9P2YH(i4lH>M/2#d6/^B8!Of9/&WkT:Ycbs->7i$%rK4]-Qan22mb*bM-#kjU,FL9MDY]L, + 3Ks1bPW()]fh3Wuo("7@Pqlun[0)]g78R&W:pgl-(K6Xm)0OVTQ\pDnQY-!"06[/H\$\4pq + 2Y:*Km46DGZ)M*PujD5P:[T;:pA"X.po + ^]?h9r4Q3F'B$"25aKGmX>\4MAF^P+6)VA:\Z3+$/tA#B$8VcH*=(.CF>GGmKLW)ciqfS?F + A?)^d6M)6$MHGRW?`Q()da*`_0r.K?*nDaLmANr.iBVN828/(a23XJD^gBCI[<<5/:tR + I'KieZo[6U"C/Sf+;N*CkRKl9*C7iI>6iD7%8.\o+h8Jq\J]?F$-mGGE4kSD\M%rl0-g?MZ + ZdQ<[,3/I7\;b0::j"l4%jY*5DS&_ + *8TXoKtT%Dkl'sYH_q#o/6g:@<729!:kAR)k;Tc:WUQ0CEiZ(0\<1qSnjl&n0rP*mcF)_$t+4`Rbpla]1*3C + 1"G.F>A'd.]K+r>3l7?.&+*[Bb(cpJCO206P"n;=]pZfmhK,5uTdtG;VP5\W*";PIQdj\D* + .7*FVt&RFZSQn[kK@*H?CRFdr>TfA!Xa'nn>g]@a[C]JCGY43dC\R518\g$gr6O@>da\_>T + I^0S(ZZWDY4\Qobj^Q#X

sm%rdX>7^ + [_;qp&>!cs$-9$$E*bV"U'Cm@1[Z_)Arj9_VrlZKMEU(M++/$Ggp=c(lq/Hn_"&*74"&5?H + cCn394ER"9G/%Pg3u#Rn`&Z1O(!aX;\Qs+iHQ1DJI!jfn(^q)X",+M)*dC@R,8H3 + X@ik(dr^S?!b%:.i*1gm5SBX4'q=fO?mA&+o1tnKF^K1A\lS\P+K=bErHL80PpS53?]\W%$ + %nC6tLVi%8hL%8OYE@&jo6Lp+=!OU5lLj''-VnBag?s1A/r0I^a5ZZ8h#9N2a3sJN\Y&=$L + We`Lq71r=LO\Xbs*'uooDnp,-i)P;^p#p1"@6[?`<*m&C&W:la(`Et6I;ee%3;>IrcUZ5r& + kT1nFT7p7+"="FdpcX=V\o\;7d[bB2iF\TBB(^*(NlMG8qQAq>PKW&fcTL)c@B3FNIEHcto + ?FFJ1))181s$9#j?$F3@%`rc?8gr4OdX#-n/K9X?k+1`E\WfQ0d9@EN=,o9AK"cEk?b3_r6 + soQ!aIOoBAt1`Z`d:kji+ER?UK'h0)UZXqMG@) + 1TgYJ")0E[r0rdosS=7=!>%mgrK6!@GNkJeTP4F(#XS2=,lHiQoD_LD-n+4rbRr3T4!V5gF:t5\LTMU,LADi/F,Y+!8]Wn + ^3Zf@US9>CIfn`.A,9?s3&l1&O4bp$b*ol7,c`C&/O];=PZi#96'O*hY%0]2m&qWr5@mT>00SXOZ[\=,QNUX9F9_(+ROsM_8criOlPKMUYf;@W*ip_)P + KlhcsZ3Eis]7]@V/j?)uP#0/`%0-^+=5$k@V<-3V/>bkbaA.>Udn+$`<5(8-UpNR2i16.n% + ?To>+ud.UMa)07X/N6H%CC+dgMgGrqA#*1S&O[rY;7qlI.P-k'^e_7D]C9;ZG27C*L.?e4W + %Bc0YVUX@TKfX*Gah1RGmncK#I]/8-_:3Z:;;?5PWK&^SeFt:Tgp>_7O'\j"R]TV*`ETA8G + -+d0\QA)3@B>`GZW9-5=_gHAX];g:f)"').s5j7Q-R!C.e-e[aTrfZW(-tEC^XUpK-jtm_[ + C5kr2`nVZ'I5EN,'O>/< + p/i:J-J[aLfhepF0VIZD + h`7abu[-Y]rbQ;X_e9Jf[SCq;Nm[T[S'.CD9U'`+M(i_rgRlW+iDg`QW'oD;0'':^$9q + :Wb]>KeJbVmC>E=!23tGsHH03)I;Q-0Y.N:(o3H.fJZnb_^=\ImIgiS[-\Lo&!^J^L=\$_J + J0:34Q9/l!IB`U&?J>K"GA'0:9F4!-ar&=AdI)?M\nqrCJWN(EDYmFKGEIro]@,:sRq'0LS + [I@:IM$MqFhhEB/[uSkgqt0F%.6%^LDM9XnG%KSG_sn[f1I6Xql(2PA_9.nFsk6WX*P7h + dB8^=#aV#DCP\:RU+q+lhCe?5(b`CJq%_2Go@0UGCl?K4V-WH#Sj;k$9L.G5-Hh]X$VebL=op:QHR5kMBdor`:r=\dLiPenr\rFcs&3 + lU7\SGbVQSNR>1cl<4oHcr?.);J)\tR]j&o<51n$)JSV]O1Wi)mCMOoo;3JG7FQ,Z:K4G^n + nNfAq3qn&lsH=UIYZDJs&\H5DlNF/f@g%nA++i\^\<3_j8<8NnEY)>/k"&W#=-1F^OYYJ8, + )W05M,dLNMn/=&,Z=e=>GL?0Sc1F>m_5$ethLC=GLqGbC7H^m`lWPRBFu&%C + g8Xh*?(lJ3U^JiG!-_&=,m,SLXXjK:Q*30NDJ$Knq)kcV!)38>D_6L>;"*%qF83?%I_](A; + cPrVGi\R>h*E=B]GQnPM/,EsN8]:?)$4tmA9YS'57$_X^["Je@P3\!T3W^eb#9!G*FP4PT_ + H`9@n;6]RQYktMu>7s;C5qta13s&PPWtm#)Z*AY^]/(A;8n#>A\&8Xr->O_Z'=p93]L5C=- + IQeDlR&oi^bUhb7%5\h6S#'=`]R`aVjfBYl?p7-I_^&'c@tdHF"/mo(5Q_!:redX,8(`8 + \lhea#J-pNia;b).%"*o9"is55Kb.%hR)-QhLb1072fd;:S;=ss!l0JE8DAJ^lj>\gfLZG' + 5g5BC_gj>_PT`.#/bBUW'841PL-[nUN]W#ilkYV>MBW^l\BBi6bb#D%O"TIS3>7Mc^j%jhn + j3EL_@#rIN2Julpd.=C&F5`N<$j9ARlS@b\^LOFN%)W45e7&j6H]PLm'B%T7_2PRb?6SGe2 + &f4:;^#16XitT`&LE#6m[LH0iU4\S$=\`Z6eQq6hER[q].b75s'`d4-W)oX@54`7D%>E]CB + ^UPXrG$8j"WB`4IS5>:meHI)PWt]UR$18VXQ+LrC6d]Za<\EeQqd3B[G!`I-B&,k + mnXA]:E8iL/FcLmnkVC@:JCIX7/'J]l3;K3"AfY[cjNB(S-"rR#L*0<%0',uTEH89:V`I/& + .sYYAekf7:g=:I[0K7A#KXR_8EC&b]T912OZ1bt^sro[iY"NKPf[-]d2WcjH5Obg'1#1N;- + ]U,@co_PSN+P*Y-RCkX;e\G.)F]50,,J<,EpOQ+\HMC8pmSS1RKCjqBL'Lb.OgZjK0Z>-kX + @g3V4do'J)9b/J;m+_Xe$o8kDZd1/eRY0[.$fS2AEX;c?=$9>dmLPeFu143ROa9FD_3/NZ$ + n_m.\"1#odf>9Keq+dOo/<#TSsPJ+n]^Ut"E9Zt9h7*[/WG_Y!IbkoF:"3T6I$P(.9tlP'ATsJ@no\Smo#eJ5FLA8K->c.t>qCJO&T78S[/1/.t + fHj&&dE;.UP'N#+d6N)?#gao!("$llGC*A8,%dIQkN;j:/o+MoD&dQFMW<$f]*-IR;i;N.d + ,Up%YC.oOVC;UYk;l_GeR[8QoW95CEB/MaQ[\O0#j9` + e&rc\gr2Z4``*"Rb_<>L`(!]g7oW1*16X1eF2L]+/C]k8`77JXX\70W9[qkI1I6groM\J+< + 2`I99oW'$4PSXQe'6?+gg*u<'OAum?bNL/c8Ah*X^'Yok#La*4qqVam-1n=@C5o/:;p__`F + RQUe5jQhDG6soh?6&qLc#m>Qp]QTb%`p72=?Mi#8ed8%&]#0J=F?LXNJU6/(:]k + Nd>XY_SW6;7O&U3Y8+`7Q2A-l% + *):I3(.pIElgQfm?3!,]ei.A++8*GD.7Sa@f:ur+;cm43r3'i3$mH%(1TZ-;)^/nY + '=Ck3Xd*rJ+/b.3\o&\$NjkYYu<>.>iioURd=HX[r[>M+uqFTM&6*2(YgQ*uRU#4q,?j*=a + 'pM$"Q!ca"=_t+kFp>/#R7c9`YRUum]IMjISBE*EdbUNg7LqOj0jtAGg,umo5s*e+25N`"@ + :cC\mXJ/q(a\)Ur0%AE24Yj[MEoc?>pej%R + a_BAEsVA+@RabjFJO8*#u8C6#Q(-m]>!<>_Jb5J8b[B:#LX.Zp0q-AH'" + E!++u9")Ekt*uG$3kY),[I-"[7AWFH>m_3K9]tgdSAXnk>'7XT4&8Y%*XmPbj2>+*mp5TVl + =JfS1[%(Klk`2fWdACM"Ac0[cr^na`At>+s-3:t)!i%@Mfk(@Z[K]lgQsb&T + iop:6K8;Rr%M:i&5s>Ui>W881TJ?']39>8kNf>7r4cnO:",n$kUaSa9Z1UEYMnE>K1+*trWBJd,m + d"@riLGaR*%6#hpnjR?l'W!Dk+!WXko"ZT>3p;*EY^MFE'nfGSe-cgZ&NW"+$b!c*So"b9SV59KXpb&TFlE\E#b5CdcC%I>n\aYe`_ + e.8\E.+iFXaaJtW(F1H,Wbl_fi + ,747HN.-L.ZoUW's1E1GK`8 + T:%>7XKsos3(+;-Qd?7#NNoDC'F)9QaqpmpB/Na&q*R>"'sc<a(`QpeX$A]7\@[I36kf9A$9OF'k$a$5'F'VG.p6906-_NbC%X[I,E-Fn'-7Sc@#Z>:G?V + hE(GN;$NQfHl\JttDgAe::'(CJCRO^H=AoA>KDj(up`CZ(/a;h%h/Z#>A\WtEG:29'@Lmk8M=qoT-u=T'99-Sn@jPcmcXD+g2J?^JVoR:cCEq6-9uf::coT + @q8VF5.AqC['i2L:/]rQpV,LUbRQX + !/s)1jUb_i6+Y?@k[l`^$LHGa-q0ru.&m@qh[@__;rlYXAs`4'"kQU]fiLAu,In$gL3$Mf9 + j@5-%$dg$JB`8]\AQ3KPuOND'rm4dHs0(J\rK\!/#\UqbH!Z!:&6';H_8UEnB0m/o,70X>N% + Sa10Y*WQ+7SV]%n%!I&":Xe,!50sua"[^Qm4UFMQe4gfLdF;-#CmDqp/-u@8"Yi^"<8BQ0!u1RQ + :nRD$8Q+'g4],2/,-&lHg(0uu5f + gim@J(b['UM8H5Xg)VFQ\B=[/PJ"@PBi,`R82N+?p7tJjWBr?EPW1W6uaf4R + :VQHqj`u4oZlal5lYaenb`cl63DU5YZ-i)N)mCQucE,D),ft)(Aq:KWS)qOVon&JSDJ++Eo + "2UO?mfgXKr(6m1E*@\8EFiQ^Nf + H99:YM&W_Vet+Uh9l4CGCB44hjV7IL3%UR)@to%hHCV6Tg!K*c.;8trDf.o6)G%H>&$M^/HWAYcZJQk3 + M\N"%BCed*(Tpg/m^'eeT`_*N\b@E^0\0sH&E)+[/+baFoQ`c06Aj$a="+P8Nt\CO"\&%aFgeLW:_a*-7EdNeu+Dd2_GiPIu*K@GEsI:+)W#aEjSSloeiLpr/HZIA5]q + Ml5)*7MGcNckt,gW#Yu5f[PNu@*_/Z]aCHhjG'Fm,=GDb&>Ek1M@jSR;YCK,W=3A*c+]E$(?XHm + ]6/&:p6[e:%,koqKQ3#(`OP(Lj4p1gEmA+:\_;`"*>>*EeJ=mjWg)dS9Qhpn'FnLES=X,eZ\eeaM@lQaf'Y>OTt2mPq(@3^-FCmD^I=SX$n0RZ@C]G%d3?Jg\5IR + OkF^Oi!T8r\NSYOV\/!k/1Q,R5khQ3A&&*s4'^G-]IibX$:JB$;9dK=60E(s>fiG@;]&4kAW-)M0u^Z[(/;GA33hQ'S1EfMA-pSDfG*YB3?EG+RG>`YUD + RJShL/rN8O'-G!G.JrRF#[R6g.P+Re71=4G-RM"h&W:*H*>XCb1^'0C8C + 7/4ArarT%g#fcd][s;d*g.WPaU5o^U+'k:'5B%7G8THDA&DQKn+5:NbAofi;4QmHV)`GT)" + je_NSp2'lg["?TR`MU4fK]@sB?HFLTAiqTpNjfXZ9]Bbg7r#^WCYN6Z!fK0F>b+4=?egeX@ + p^LaV7a85>;eT0D/$$*d)t'0:"4"j + Uj*dof99,igjkI^>b"/\^=aI*il,ZY,V90;3("_F:0")r,"b4\iAP7/U?BYl+JL;N*cD;KC + F(*_7Y8XEXk7RGpaitgl%D;?;nuA="WO(^QnC3?-Kj71V^fF7gEE+!.Yh0<_D!EM`uiMH7) + -\KD2/E*gP':TMZ$30Rkbkmtu,9/F(\oX(,JDNB62[RGDAW@siC1natc+f^g7]9Skd82f5+ + E'.*Aji5G2&oUa9IIJ9$b5B=J=b7kU@d!WObg"p/2bn>ip8gg%5<>KN& + Dr1Er[+bt]"_DQgI;Hc3$ + kuI@=a&\^H + At!4h.E!m@J!Lq\e-%_F.1iFEY]KH.W7*kZ:.]ZB;sMq%u+1;0J]O3VS(Y[ZaVB*Jo]##K( + ?&8qeMD$Q/D#I&CWd";T8LBm=,#Y>L=T^E5+Qr[TJ$,P!r%ZLfeF:h?M$&)moJ7Jj-l5caJ + a//.fN)Js-E9o$6lYaBEl)+roese;3lAj$qnD[e-eB"fFSIXGs$/4fdp;p(pSu+L9%&E>cW + :-ncn1OdbD[=OF^1\u<&^M7+%*nC(*2AQ=c-IZ[k.pPf7\m:McS$uK%lgDJ7e?(4cuDg"Y= + >EC9$qJiEb^-t/KE1nB(aJXdYa]?mqC102Kt%_e'E(/NCN[4L3]fAeH_oFWmi7(OSNgZXj( + U`6<_sdTWYJqqIeO6MYPfGr;1>aql:-TcbS3cQ@R.(fuk`?n'9WRnCjp&rJe&\&*>UMlJ&7 + *K^HX1:\9F#^HE0ij\Ic\&aMacknh5*4[aIhppsG:`>Lc!:la1Ke`_&-j"t:)^5q*:;&9g?%*UFGnLVir0db-p1D7 + #4no6G1:Fsep1DC&So5R=mXT:4T:sb>&oSRhanQ^(=@.d-HorTM51K)ZkE761E(N4&gmrK< + T7'#j6e?On4b0q4_QE>"4)62R4%;?_ZL=Zs2)TUAI$MaJ_r;.uJ)mR-0dDE/.`j)_cfoaK1 + ERPp=d^&>n*RWYp1#W$Z4LB+lrfQW&1%^0u?N6/a[VKY$%G_#?HN;=Q3@KY+&/Me((BP3d\ + VljWD>rHI/L""FMfLBO:cOCh2'\$m,LdeQjVl;dh[V&sl*Sq+#sR8(;>T%X%h8X,huoT]mVfgu\!m5!KM&F1r&nOno(/rZ0-/=eFFTZV6YA8_2r[*p]@%kp\gn>[> + q%)jq3'bX@lbu$$eEuu&/,,B3V\l=3(Gnun1il9fP>;s2.lC+8 + (GU1.a*@=f4W#tgpWCG(c\Bt+G'_V`q(6(on!pP#V])[[)Y945:U&I"T)4<;3\eR'-jMf\b + 5ATP4(@A7NMqHX^9aapZk;=\NO\tMlMgD'[EZcLn+RofhVE2Rh9Z;]$[El>kf8,s`A!R"tPFUCDVA(6$-+USb\q^o1Y/Te$'1o/02`C^i + 8/Cn'3<`JF/:M,ij%W^Au%bF?khb08\9@eQ`<5S(Qa%X^R9b.,9&J%TXh+b-l&Wn/1Pijbs + )U1,.CJuAHP9UaJs"]I2c]Ldqd7j0i&J\/Fd1mYo'd"2k,8QJ?(B1*UYS>s"'jNL4M3Z1fcmM(oUHn + M'i35-b&hu\;j[&4J6*O!geaZuZ9"f1H,o.G/(U8DsEa*\]FHrnL"_131 + b!D$1?$5_T0N&hpPjoLh78-")DTQVE.k$bFGB,43RfE5\#AUp(JA`_5i(__G.;.d1r9(mC# + ;!T`WH"YRkK%>X.%du&j#AE]+TJi'0OVH!f#naQ)C/YaZ[\kj=feM'^c1*^_Yrum&:JatEC + _J[B<=ne$5>dV2VtSXV03;%O.;eeF0XD?&`3KrZZMDSFMf[n$Wd9D$8t.J!Ekajig2osQdgNe!$D=$^`)2W9/lg#o%S`+Q[hNf + JU*l1B<-krGXgM!.(C^-)l?s2BMs&mleRLUt",pm[N2mP2\md0k008hb4&=5WDq/ptg9]8I*a% + f4G>P\+&X:]'B++4"^Euld=VX/fI?C`[5ZQ,SGpQaV%?f-qP8BXN<$>SY4*k$ + oHA"jW(&O9lAZ_EQXk@?i)V_DUhl^/KY+"g^HLabgoGI[)#,"ouFFXHT"s/+!Q\AHV3tmq\ + !#*1&&o#sL'XN;_9:EDZJX\0r)_\O")k-XKZ8oIINo>Tc]potVK2g$aCo;orc(,eXSQ+K\f + M.pfUP1Pknad]RRI,_#q4B\A1:d48d7[D;p;0L8_ZUo"YKqVZeWO#["qkKlDlPK9`D` + 72clGkgqO&V7+q"d.2oi$cM%bC*DUpH)0g/2jV+$$o:V=ec(@+!?8FZ95S`Z9IZPUqi=jMi'[ne<)'.8N;onJrg1HU9^)?Who';2`P(%i^c2'j$MS[ + oTu\l2tIOaU/QRC`0aUDT]:F[`'l!PA(0U_p?r2-2c)];3taV)ZAhD1O_!,1q)g3`s\4RV@ + b.mu=`+Vb8gJLbCkMk?/c)KK/E!\F[MBeQQ$[_-3,?QY.Fd%Dj)2_O@BbOr[f?1uMQD5h`\ + m*(,!T^VlE#,OQ^#3ls_"c9ta8j%s+'S9*Omj5-;QhL4eJ^$jiaoA?,/I,P2;!3\on&`A5R + i7sDn@4C/A#:\WA)Id@%Sg2D4-*t.IbP]'6*4rPI_(GX6B6unc*6jtcd1?0*[2r,gN2qTN> + ID-(KN=LoP7;7d;9sJgC>Ai\o_XA$\^6Nn1A8IZRK(YrM)3s*'qH@Bm2M`0=h1pip,X2B4Y + ]kR-b@hi]0/HT32P,EPo5nnU`EAP43P;3]lAXL]p36]@\(4-buYRZNAiX5rH^(Nf2u8PrRH + n*f^$=S:ELGH^t#3r$YA#iUQBei+INaa3>6V,/#nDlWn]BS3?qg5[?@%E6!'C=cpOlk,Mb1 + (h0`;[]$nXSrSN)rhnMhW+&DQ::%b,WotOT$aZ2fk9Ed[L"-eHcPbb1]EAhf$V$7QdSpjTn39sJ%?b!8,Jk]#_TPDE7[3-*sZRGgs + J*_;Cp@U7\%lIoDQh]O@Z0&3[)61CkK$c8cl@Xl:7lo>Rp"b\rL`(0Wq&]G>B?PUPWHrt$Hd6(rP7ZjBJJ!n^&FWA + GA_4"CHncXXhaC]-f+\<;,ran_7Cm\3p&s8S1b*&9q/5#K_XY*7%m'eWA1Bb`Obd/#.1/.: + =dl'o6FOrIrZ\7L%::#oJ3_pEro0,rko^tIFZjct#:p_MZ6.%MH'gto@FW3kKPa*=^dPRdR + 92d[L2b[/7eN6T+obBVf<&H6t;cP\D=&[YWUOs-F.Tj8f5t<-7'2lm&0j)Z^dL[ep$LcC&W;rH\UXlmX)4oEGc`@ + n\$%-DXqu)g^a;!E4bYV_k6+)&]O9F@DBd%#A5@*+6Qe?bB"Z<1X7"Qmk)lWa8+93oUQl1e + +G(i;:Y;Ng&?*drJtC?)dS58FS?g(#&O%)n^64g#T=*1+QuE1Lj:Ch-?GpWL'GlTRrPbD%GWm%B*oY!'9`r%=jDg.66MgDYn9aAgkc8\nO0_2'2L@J4X\&>T3/%? + ,-h)2O_"E_nY[07*+)!F.PW'1,8j=H(6&T/1ng^2)c6AVNX2$f7l4Wp;0"Qb1#X0D[![p4, + _&A,'dZC-R$3f(Y`TP>74UqC,:$NROqKrM8EhQKA?:uXdUl0E\M#=@e=MkeISi]T8LPlAUQ + k>8<(a]$.qMDXQ&/`lK>;$(jg)N*Eogk*\Tg65n.h<'hI-Bb4=13#,%M*rN%nJ'1=m0NRLD + r)Ync-5(fL-u'4,NMBt2&mSDTc/9oDb,RlM8]*gm\=F>;?@4'=ouAWt)uZPhuO-`].<%/$2 + Jl1iXRo0T):3Mk(`"Ou.b#GB=EXYkjH6X&jfC^lrA2Mrp;W3^;W:"G:_8iYY]h&gR\!Pg[- + #>&/6N27"FPrUcJ95.rgF*,CG;`@!:>na)X[,ok;[p;d8SNh;#hP!$"K^but_5RqTV%@)M6 + u$ecWR!\beM8oQ*fJVqeR@jqZ<6kY&N+qhOI]E2c;2cBm)3D>GAV?W4>AheSn$.^iga)-VqN^kLphgXl30Fd-K.E2aViHja$VS + a1a3dlLf#bM7-LL0,4o-!,Lh%#Z1:+M)$(kC:#AP5`pl[ea"/kikIfEDo%q"+HS8Cd^)+NZ + ?L-j'DlrWFfB3!<]C15fec*=Mr@AD/=sO(r"9E!qI0/.Fc&U6eL941^+!BC$!9YPYnQ + 9d=)2BSHqL2'Hl7u4Ds-cFZ8'&p=JLbSNGSG7,I@k$"kR6EQ@Gib7;3UMl1X"e5Hc@+b7!K + gP<7q`SOVI[7DeL3QfapifTAL\bR7thVNFBuB;\h?P-]cD$ZL65@M$(5KcAoRp]PdoN$j_9 + (,VG;<'3\9LZF0hmEPj4l&/GST^)nW&6*T,A0%A@)i`Z`l!W)Y=C8)BhuHEUeZkpj4;.eR2 + MjMJ2dJpR:Z0g;^CL&(!ml`"ZZE39r(!#?LAX)$"a/m?7!CH*JF+Gk?IQ4h*&],:i)EL*m' + 7q"`*7qHM5Q\DZ`c<7YlDDl4ALN\[t_s:!;/PJL=E?B"fmC7CXNO7IQ7/"SOa_l;Wo!`tC0 + -D3dEAqjbN7T'W>c?1o,C]>SgZ9.*/$$E^N-X=eej!H9H$;5*hFVE6nDGi"_f,hkiLE8jE? + 6RIppUmr**8*hIPo/jk9&`3LQ?I.6-/O,B*pp@$*lZP@R'M9tc/5K3RIN8m[$cLJA>AN8utj6?"qnd\'[N95WNN/a!@AkL1P,:@%0)+66 + L@dOk./5_G[g[b)WDNoc.Ak)fgW?ih7IHHl.m,=K3oepnPhOJ5Ko'==sIAe%``Fp7uj(_f; + WNRO7]WrgT.]$1o'jjRI8%%MdpRfnuL9Wpj2lH<@CTSm-;EmLClZGSkJ"!E<=(h[:+?lt\] + #k2C>6"&6]nP'7>@#mpHt4B"a+QZ('3QPBBXT8-Ki#DGCp0fHp2i:J5@ic5NN'<;6pfWDZg + gGQ?P>@-ZF8MRgdQY>IfFQ)h.REUme_pTXu4=\>Lqs:\DqZUUOOnblaR;gU[*Q8lL25S-R: + L2^giNmF-Ak*#XEbC^n_:PZjB"9$Gajn^sik0j:B&]&A[j;LDX4u\-%78!kG:9Q2go>X;AE + m@tb=/_-()qE#&IVIi4Fh\ndm'nSGd8T#)+Q3\-mj1a8fdUdA$(\m(`Al=@JY*'/uu_P=*I + Z`9qH+XbI7Vpm?1j=e+!Y:(X!])*j'15)+.Zb&%O]3D[)CFq=-0LLu:]:6CLZb*iS`3W@,Z + Wk.*gGqY'R(NgB*?!;<,!1d\+QR]*m*7QeJIWmf4L;pQNO:p + e,64W@-H/(:Ode4!9DMK/NoHrls)rj>q@[;4MIe(_RY^,;Zi7H7VqUYnS-2V[c1lI\T.fuFcu%pO(V\GIl$p9!BM2e2\ + /#mRkjj#;no/k^RVj*`/)s/r/GY-AkS$LB_l!g5*c^cVaq/;(bA&]dq)aCGlRCk]rPLrb7+ + aF&ldP`&).MoaD,aQ/9p-oM.2,XJD9""]VOM.6RFPfXeKKlBro$?s@G-c0nG_@2mM>an&jS + B8^?ah/t:r0agkQqA!93g1UO];e3@X2=J06OU&c1bqE;YerQ-_b;qnUFJ%+2Ob[G6[LK/nr + 5_oO@-ZCZT=8]dl&1n5I5`&]DFXgWglI66o3=]6t2h-F2^P*a$cZ!1d"E(n`E6fWO-c`[%, + ")Zd0]J;'-3D[+!Gl3jZLU:q]Hq[3O9lK2eK?fu9u(7Dc`$d_@s*>.sLC29]FA*+?Q,e@#3 + aXFoWC*+hBnB?cUa7Z6S%R,5Qod6>q;2QLW5gMKFNf:l2^5<:14.rij"p-2a:5C7NCA,/'! + q4eXK2iN$kU,FsAk,s:r2r"BX]RMmTn<(A6Y2"Ug1.!s""\A!>aRo1%FS3t5pGY:W4N)-__eAA>W#-bLJ#^G;<0YkjK?+JYIf + `S1E<[C/%ciqZmf6E93XC(EQplk[lbY@4=`S5>em)V=PaX^TGX)uNB44jnq=eGlN;rh03ncdMGcld/P27gWCqr"PC?:H-Y7PeGs + F[s/^fZ%a\c)bT[o4Q(Dm*4$rXZYJm<,W8biD(Wh7i^Pa9D(eCji:7M[p%\7KQg1Z#=#Qma + .h\9\I_Nkm`3c?a6S6$'_44,+3ue8:IOk8>lIbls%i#nekKaW\1 + "GRSdRr-etTHM5&CfW(NPa;7e7(`&1EJ+#;'CdJY3"I'C5WOio$r6es%jod2ce[N+Z_]G.m + !SLVga7FKB!1`_n*gqLFc^GK4N9GC46eAFE0:V?j'bZr/[/5mWXdmZtk+)O%r1fmVe\Pp]) + 9\KM1`BVTE?+oi69e+%M'h(hqium_Z9l+B!,Nm=*8e#k,e?eJc`+r)qdS9+(^BEH6lYTW9E + 35D9c9([-4]KI,k:QX6:5\#UPj6#rld`B/\%Q%LNPifsIm=_;^a/J90dB^MKN>e.f%%oD`Q + R&EbhT>Fgr7QDe]utSceT>^'"D\1:e*ffQ-b$n#PHZ66=0tZ#$mIYJ=$XnO_Pk%R`[42=)8 + pZeU5unHJTB`d.QV9t!L6PY9sPoA:kWmT$K4%ubYUomh+0Nc0 + *6a_nO4#Bi/Ze5DGdX4=5,e)G`6`J8H4@@%i=l%9GQW!0Bfo7YVCWa<'R_6m#`:Ut#eAU&# + d##-M=q"@;*MP&YUJnMNg/U9nA(oE!VmV1`9ak@X-)unUX.[F#g='EK7BXm^@bV9$%99\tU + GQ7C[,kN-qUJj5eS6d5Q+oPc^f@5I8%$4KmDN#d`CKh,ec + ^Op.tf/oA).UB@br`n*\S1gH3P3`LR;KR(,Fo61Lim3j&S(u:,h2uIg0"SioVoS[Q5?`A?2s:uG#F9PpG$IA&M$akA;g>!1!IKr.f)VT#tQ> + )^hX*Y1sk6d7M$2=/jmCZSaja]f)e`cG\!@qJ]QC0O&>c/h\pqs_l8KZU+W$2-nN#?X-mV\1M.1/]cr&5&"%^BkBQTBF036#1kSBLH&Oles> + Q,QYFa9l,K=Zet6[82hdnq"3#<_hP6B%+mN'K#.S9'rT5j-jlGi#A>ZBfhQ(1iXD6;HC8H+ + 7r^PuYJP&Z$?B'U!4bQp;Kk4O/=E[d&k#g9$*m3T%jh=sV*?rD?o@WJLk$'lZ^XMH!Ci=cGh36FslMX`4,`.@\$oQ/h<_%PX&QImq/6]G+^hWQt_kSQ1g + [2^@q#Mr,:0\n)"!2k5Wb0IKiVJ!8n4bS<\VC"I:HXn6c:dFHqF8*.bi.E2H-?rSHh28SZP + OnE18Mp9!i0:L7A3l@nD'*jSm"l*;p@lW&[NAaO&NXBt-!Q'?)J/7qnK;WGft;Pr@ERG\!XfsUro]1VG(D;"du622T[%oC52=.a'lDd39r?h&2,%m.:#WHr<;(uRc5kFV*j)T#c: + 7&3Q+X6d^$`=eOE>`tSrHaQmo@BsJ<:B9Un,c=&QMPO&Rrq + e5oqtsc[oRXd>4oa,"6_KAST>Pp9D0*A"UIg*Ub)$`&o*`8nHsL0rsn>OB@+\2nA[uMpLNc + r;<9F9lTD'7AJo8f-h2r#EL++@p0,lK]#t"Wlc*5R2>"fJ-0si`EZL:\-GaLE.dT'(lqG'. + ?=rbOa'"_LEi&k2-Hb#f/CRZPi*S*EAoMS[EBR?C4_HNR8?6eqF\Lm]`Qm%IFfFbWf\m=nm + @QFJ/*(cb89<%[F5GdFmXM>#JD34]7^Z!/2M7_NKl0k.iT>mRreT=K+m?+$i]MG8Pj<5K_= + %#Xo^7h6"i\e/o3?_q0*Pj.f;nSjXS(71FamL.k50JVXErd.q^8ZG2mI]q-jKHL"Kg1q).J + 1N\B.hU!'gXALiOL6kN*e%E@I:K?Y]KC8G8B`r$7s5%q\/"YLid]qr_g97^@4*<,52G)LiQ + =A$XtKEq_\un[#]Ks6ui@.uV02j%M_e9Nt,c"B.T!"?8IC=9O.Ep]$HcXp(-<7s_e68Y/Cp;%Y2*j + &>Ac8?Qol8e]O#MX]B/b[k?Ome=?CsEkD-raE4EPM1DPnjA+EagVFN/#b@Yf5]Y8SIT$D!@ + =rerB9r$2%FW)FM\5.S+?ClKt51a;R-kcn?pQMY'^>?W]HgVs`M!O#?*dk,'-_8HDo3Q=[( + K76^lodD#VuLhOg[KE74oZ=baldjAI4nB'\(0s@Hp-@/5EG3oqu)!bNkb=ilT.iMUB.q;/! + T$Z%K$pu_2\VJFLR?_jU"88N%G@.kD@Rp_f4*c&&Zd$cO@a)N(XhVkCc>?740or(S24\@i- + 2j9PSdkn]CeN7qX)o+._roK--Z))?1o%nhcITa[[&+-QUWgS9E%qRDOHEZFmLp92a0R/5,/ + :__IeT'Zt@"Nj6*j23oX-DJSHd%`:K+1ecVIM_&[,,5#9Pe\u?FR + YUie?WXZ6KPB&h+j4=7Q:6mL+6OrpoHcFW;`Fh0El-\_ + /d3/7F:[scV;gZr@_$uoj)K7fCb\i?.ekAT5E8**NGJt%V#_H>'M#99MUc_>TMiahFib/5` + ceMkIFCcq<9s/f<"oI&)XSXHTp?#meWO`q<*-sDp@C[0Q3*H6Q^+ppHnA&6Os*X#O;siT?L + %-`LlL-s3ZK"*XL + l-A:Oge!):W&?S)CV#:\N,V`+f&OjT;)\(;[OG?Zp*3#laVIBV!Le4ISgXiS_qUH7.7dB.GlC>4,])dQ0%(k + eEJDs'KRP4tp"C_W1e+OA%u-0d\"LOg??8iaJgtSlRF0c?c9Nd?0k![ + =0NZH'MGBG_(FO>:g8n^pZXYj_,<-*+0ccql]2(&OaS5X`S:)V57k;;Fo'QUcnd2P>cD$"Fa$qPL.#:-aR/K\ + L#K9.i'Lp:/I+tWm;@WF2c>#nWKD*4%p(9U?bff(7Ro0JSgKqhjT8CQZWWKjckX%OCZf-Qr + qQEo@Q2S4q'V>RuDNS1s"L!dJ1)J2bQXZPshn[hk/mG48A;l8?lS%!_7?/*VJmk*b9 + pSRXj]A7e:L$cT5^"\1D?>J)i>=iP + iP:MgIWL:kd?RP6,QtM`=;RDihDAlNfe5F1^9.`jimMfVOdc+QfB%q?;o5l)K/n&$KD6:4$!9qAT`Ia-Rc:MFZOUGmiKC'tLK!:AA@,6TEF_ + t99*;Ci9.E(1)Lqkja%oU&1^Rs('V:_OYdln.60m!ZZ=O + Na=g4@0"Ps^.G(ER)M239R76--H#'4dQ.$1_=RWtfP#jSHKQP\"@d8j=iW9$b]?2/D>O0/( + os_d,:BB$@:[]g[n1?ScPKN*3m1fM7E83^nse(,r+'^7Af@op!f8q1.Ze)kehC'4SO#Y$*h + +U[:Q[d[EmtZYn$(1tQGT;Njjj%6ncJgJl\.`NJr@;_>C_`ONB$?Y^6tXQBu:f3T!)G[Ckr + p#aiZRpX/0h\G2L@7/\r+(TX=/`XLYORjN3c$A2&kPXM9o>OO4]mHm'O[eC`9%/UM,^Gk$Jhu=,sE%V6MIRaY=>T*Z_]OSiCL,\)u2i0h"X&;.$4f + NCIN9!*\pX;n[j4oD9jPEI/\NOd#UImTQQ=Kkfa-[kDetD4!?'A$5at]CXh+YA3pt%,B + ,/L%T_aO80_B.hQ + [/MabpfXC5)[nbld.t28,K%16^D!bKXY5&<@!:UA`nF1u70SJ$G'(f"ZoIuC!E0U^h%e==m + o!";aOJN:$lLCPM%^OIL[^7A:d?$VUbojDJ9%&aFP;#iR0N>Jt2[-0 + QmEf7j96T`=JHkU[\;a\\D.'m`ACceF#]]AV1%]G*&IX].$aK^S1&T[cS=$OS+Ar4_EYBU: + &L"Fp+jq5Ong#r?[L"(&\cAZ9!1K`;>7F*[%?Fj+njO%-lQic-,j1dF@uCjQ6R1#b&*d-On + H.WeP/U4`&?9KanJ%lSTb3?^-h/DWa=d?B/?)FU',$E)%Q=5^4AJ\o'Jc?Znt_#&9'$X]rK + 1&XO]_T[?N,ssXqJPdW1`dl?A*D:`"NDBb]ma[;"#:kiVE-iY*JdsO01\3mh,@Lk + `,;c\L2"E"O#>62=M'2Cg2=jO#'3-r3AN$l)2Y1U(TdgQ1`ju0l&E*d>,X3N[M"i_k-'6qC + 1//7A;%Bm2pToo?nG,&D8L>+P6aC=BnnTTcDT/jlq816LA\1#i + L;2)C>A&5t_K;pr_$o("GT#eO;_XB'/^n(=Wj\0E2im" + YF[$(c3I(1>r7SjXuW;Z4WEUoJYuTp0k9'lW&KkG?q("Bs>RWGj.?$4V%(#`JAj^,@T?M3 + N*iV49V:]Z.NR-F#=+m>RUmpNNe@..Ar1sU@;P>,'s:%f`=nD.MR.)WBh@ds6hPjM7Go1s: + g:U@W%<kMX2(>I#8O^P[!M343$/0o(;\Q=TiZ1l#B2u[(mbJI(Ch0](NHnm(q + 1B'(E+-W)02&R'd;-FG;4/?1NNSM'`lp-pHs$'KG=/JFMWVEf0a[^;B91uFdHNZG@bojKG: + n#'WLp/X.U70/.^^j(Z@l.iC.umIM=`!Gi=)c!S/(=Q$J^_H(dHJGG5o*U)1;>H6IMH_:O" + b[sIeXHU3"+(U\T5f6i-o'=Z/5(XILgfmS)#I6fn8@>#=pm9_g>I^ei)@dKS:"+N8u?PiZk + 3!"PRs+%T^'gQdC^eC-R8-D1EIo\FY1P]HU&DWQfK(^[>(*u!Y0hth+6's*S1TQ+.2bt2K` + 6em%1V%$oF&3,QAF*r?14]50=]&4*;3tAHc!FX_6,TrREuC:ZXhIP> + nKNbZLPT(6q0B(KHKLC+SCG0o&)5PA8P/CGP9E2Rb=t]c&N-@Sepa1B0aZ*CR?^7.l`Hdgp + ;Z'U/7J7Q%)IFfS^8B[lW'eQ231G4fcR^MfMKemW]`)&+""&fRHa+8Hq?)'>#%-l2p`)OW1 + @))%0sDj'oW)OWgS+oWC%_j$QZPdsC%\OVoNVj0QEQ.]CXJc6QjAX8`sQD%rWH#afHB6Pl! + (u[jp+oWA<@@1J1R+[Q=q2^qHL)%Q();1RL)5!?@>FG;e);23_+oW@Q=ds]KS!gsZ@98t%a + ;LDdJ=$k<3ROC7IpJr[?2<$$g"DrWl(31fSltQr5g0R#Lm[+r$,Dg9[L7bu-VenSop@n93' + 3gY5G;10U$P.>GY*S;ZOh:CKj$3AF28H5@40S5U\JO9BRBdGIDjH\%@4#LJ/=Gl3o,%%Y + 37B3X$'u-_acR\b6Q-gSLU5VkP-(&"3f7H7L>0;e-i\KsjVLg,hE!APfg:&oK3sAA*aVA#0 + rRV,\gRnO7(mLM)E_;eUMVYua/q\Yi;7M:;j0`HS6kqe;1:W/cVrEIk(Zh.i0'!LBpJ%/Hn + ;3EMN\crS%&[=uM>")Q]#71m4MFdnDq=fc^V>+3]>ct;_"eF+7YXjD=]g>au8m?)_`YF>-I + \KS0P2936rY^6VZq+o(Y%@*HCZ'ui$]Toc!-m/81QeO2.4#ND9REE-()>TiH)5kZ)6^q:S[ + +eNdqc0/U>F`<6\,1m(*7_PCH+)l@[li-aSE8$Yl-4VAc?\CbJKPI0NY6m[&DNPG]:5I_Xj + FWi9\LD5I1Npf)#.=+]0!IrpestU$3u,$4+j#00QX$)eS=mF/O31U\b+,ZXr3MhU)9\P`RpW;%FMj*k.Yitp+a0CB@&[CdX]UD-@FV0AA6_o_q]p_`/EgP;ij4.7_^5Sc9eMF(@ftTM/eo=lLP.T;q8O.Dmf2N + 9Z4:S60dYlb_661u!R(]K,Dsk'A0)3Lq[kfi'%@5?44)G + '=4/oiOle0^)tR+H-F$O[Jh];?\RRULIhD(E'?G[T9\KpMtt,Sq;7Q^_<;_E7[W/5)!8'A[ + QWaoIHR25TY>L0ho5LTXo%##U7]&lr@-r9/\Du\dp'kI5*NY85$CWiRbH$m + b6esaA/dqR%cFOcmWSC@lZrQKi'"MaO8g2jj(-m\a-denZ,VXc%C9Z#2:'qgHcMFhM7%GOI'm<.UTp\ + Ok2k-9+15Yqjo!uf/.!=>_Y9b^ + !lXB?>X;/BWiRRO#b)TIoHAm*/Eq4etN^c2`(J.a,c,=qAnP9h(-#nh`Uja<[KaqBr=B9gO + 5SLdE3bdLQq1ARIpb\-a?<_Th^bqr+j>hM%RPA-Ca+7k`]3R4tLmlaFpjRYR]F7fW#:ilRC + 5u?&W%n0>PAJ%F0FF>*!;KS0S8CI.,0hYG%jYot6<a&0!F&gh7D0oPie`D9h=>hIV@p!n;Me#t6 + )q[Jfo2MF%FB(XEY`*rRZ5KLOmcphH,R5GhuN8m%oo/%OV:==Q-4'pNemA" + 7gIQra:hL=kHJc.Q'4,W.7o9B;NRF+'N5jUaW[!#4J$Tmi7o"akBH;BCcJqq;B+(Oki:[B# + bkP51qoE^!Z>U)P54U7Z?:kb"J(IPF7%O*/a$t4fC78VMKU6Jhf`<0?'Ru3b#!tk+%3f=6MieW-77c)T,NP)LP(KVIJf + m,r'q"k=NHLeuQ?^(h'(]]jQ'b0e>:NcdX(=&kJ?ilsa^pY]QBK4Eb1UbaV9H)Qm(Qh!Bh$ + >n,bLH_nmaKOOO.0!b@pK)U9uGCe)lQbCMdBn26!b1)YDd#%@sd_k1)FqCuT=W[C`n^d+qp + dJ":Yg^GpuT4Bst$EDtE-&4[Z!>b([6N`:lrM>l\.i>ZkANR8B-&Euc'$["kcLH<:i:?[g' + G,5Z&]=2fYb'b3Y,tt;N,.C*L+^Q^!Pd3E_;"KU/cn=st]i+U29M?%OHaM>k)!Z)8$uqV]F + R[SiF$++X@AToWOOT_Si;9.?[=2`@r'+ifE)%EPJ-Rh0*9"@,i#E"a20A.L_o-cF2.JcSW: + `GThc`gDnq/g.$m4cn#9`X^5k'U6=*,Gh:p_ZjnC7/Oe*4t_pFPErblK#q%hhcE;*j"e.A:XQe`-=/6sYKZU= + e#M;4MT'.-&%:cOV*q(QD"X43GWuWg.+8hHdEcd<^NH-U!EY@-O4@HcA7>^)+sg9\s!P3Hh + .2VqY*VKhSIab,AonGuUE``2^AV!?CkBS7!XLJMhl]keHeP-ZFj@IWf%X$%4*.Ru(:r.eo4)T"O0NjtbLp)YAF08_Zm/[O#XI9SJ,i+GJ + Ha]?HZndM41?n&(#Q\$QM#-2ft$iXl+NFuE_H;i0R!-^gQNu^?`ijJ0E1j;D+altq6JQ?4p + IKnNs+C__87mT&"Qfk]O>+i3q0ltSlflW\@anZ-fGKP30DDs\ck@(9a5^h-.>]7@7+^^'6[ + >PgLG&?;Xr5La0,RedK7!?1(HT%=V^".MH9AT?;5aqb:#NdX-N7h53[f+/fDPkQ`;&nW:_X + :AkoT0F%&slRBchCDSM\tSQjCQQTSo,`<8!YBrr,E$g9!#T_(-rKDHc*=2+33FEN,7D(iI[RIl2',.K4f;0^T\A\[;ZiU@iu!ep@@%m!WfYp)_]9HXCb8_^J@fHX%>dDEXtjl+iZLuCAO`IF),T%mP/n]j&RkOe + iTS;31GqLcQidCCZk5V&)(J_%)q)6pN7\Wd=R_]?:fO1Dd%3SP^V4.eP)TH7kpWH%47179n + ]Ua`U?5q=M&36SmYoWnB1.PT10ZSAIq1Fdbj3oU'!nc(ZY2lb%]4!QdBUU-!p4ZciRZh(XV + F1<`Y\@\3@&7d.M]1"Pc'bG/o]A#n.,>1AG`4Q`%Oc.)`dNL0"`\3, + Bl(!`7r`cZni=NeODB-7-nO!ZtMpVBh!2Z7Z;*%S8/gH3!.GQ[^[4 + VL_lplAOdW25/Z^#)sY.2FZr*@^3f"VKQf(*l,R9-[u+V71ZCUAiBH=O)G>NCqZ&i*nMg@: + 5:Zl+.tku:"Yb#k\81i[H\jtVME&6EVojBHdh"au&\mZ3Yri%I)j?&89m/eVKEsGefHc%3(4dS + =!(4aq8-+5P!<\uIJo#6t4NL"66jF!$"LLDrLcGOh-3;a8JNI44&R#1E#lo2lJWLTKGV"GC + :]pbU_pCupI+uoaA^*ou9iU7MI-47`k0BL\:8I,LWD`o70EQlBJ`%N!ZgO8EE[FJ-:Fb^$9 + B4'_og`/Oa,G6hm.O56H5Gpp09#Kmb3V=?B@#B&a7JdjqgHO*JN+?r3$\&BqZY&=EQDYa89 + 5,7Hh];=M'o*V-bK8jb`'=%JB7'V3@'\@>(oNePs@\S8ONd131i3i#W\U8*W-5cN>P6fSgd + &:8^ep"m%1M:*@[eW.Bp\3)=T\''L>YN.MSaGFJcm,1=B@(3msqKX#2s1X>=t#Z^e211UJY + ?[30c%9-l5%FL/=_-,NTl6>F,!HG).>]TAX5bG2g=_n[Fr_(8O%9AD2<'JW8accs9MbTo%i + H7?aB`C#>G22"!6GtN[PC4[9A!Gt;*F[jl/8Q:[A\:R6rK"K4D7(`EEO&V(5"Y`BjYt0MG0*69!eV.@1m75*f8(GFbL2^%D83R+m.= + k;-!9I-[GpIpfEJ!*p!U*L93&l\L@"f!>:/&K!>jIXD6%_]Yecf1-4^Zf07tcsbK+29k%

\dT%q`"0.j*QdsqZD8*gZkH=O'ZrD\b#K[m%&^EH- + :+1M>:KPU3iZ)A2\'W.;QVId"Y@")/^iu0deX><7'ol36;o_L;aWts4Z1*G^*.m8[#I&P[5 + kV<02o3pe&9#F%;4/^1poJ3e/MFf/'K4(a/qWrf(o" + ^h"3C."R$*;A-FqG!a$Q$5pM"Q.KV;Q##J9)ja:.3oj:Ge?Ua]r?2(+!7"Wm'Jlj6!*K/8) + nQ<2MfU[Vk,kOT$L"LPM?lfD?XoR+;q-Wh"0;\YTed+J4N5u/&clJ.N-'-^g"^p,>RuNUZ9 + pB&q$"4d25IHj)`L&XP%644u3>@P-_u_q\(Kp,<=p,#N-su,`[G$>/R%52dPM`UIOn\3V`N&$6*.(Im&SlF3'lT0b2]e#J!.!%(+dE[F7OQ;bX]"A@Yqa85$%rduD;.S]/e3+2\K)e'5s629@t?b+b[i]VAO[*612 + 'c4jjU4[n]O@"/A_<.s0nK148H"&Rk<+5M(l?'X,NNSJ&5h-Z+M"9:4ZW!WNXA7YsK2?$^. + BKUnQ.#/XD"M3[TX2a55@ogrQJgEnJ"]>=+6N^FAhi;>VL1jq3B]abXB!,k_7JYiCqmY\n^ + ?'o0ec4e@oAEXPVA99l/ViLAEp"0p2kC93c&:MVNB[m\=Q>!G]CXB:p":S`:i2E;Ka?@:M- + 4VQ@6L!mdXL.Xbg>F2ft7@-%$BM>&]lmCg(#)o9qj&dUH,J"d>fAIS=;P;)e)4+i`3!^S.S + WkX1D:Ratonm-*LdhU,oE]8qjOBXtMS+0H]^;6.IHX2D`ML1r:!NX\8'%#)HCom6)XVBEgFn/A=PXUWM<)bN+&jgsaO_p0]em@2$_& + "gCZpkS7[0`e*D#!VTDsNDj,Rg?+ehAPG>m5#pqm4[hUp!8%YZQG%*o;ZR.5!4H7J4]?-dk + j,0@!H8[%r_O4=!X0]4K5PJke88OBD/RkCIVcFjqd7?gkCnk,r:465G"9PpB-uM>uVIAhmS6TT7O*]1DP"gp---05=7Ydj;^iVPcb@W+3h1;gZ7DZ5c3f + 5]NA]/:5<.o>PJDY@Ki7lRb?e76t]g`SAI(CNMTOm^T\(bm&Dk"BNQZL<2?dbQ7.HjYjsBE + CK4Ngh]F8k)NaN^:\KI0i]nK1o%"[GNWi!TOV:]P_E!)R!T\3FhZeoTFoF7 + 2;6s#ou]5Acopi:'P3AM3@CD*0DN+%]qe5)e$M9bG*"6c + J,MI>A>)fMm4snD]+>Uqc>2uPj[&*#Y/CIT*>:0_C[C"ART3H+QnD6!"e?E[@&E@OjnK]E) + Gl?)C^7cjfnQ`V>Pe$(V5"ip5bKMToAjP_E6L+i[ET\qDouYEI7U3DGlXi]Y5"V!",de+TK + tp94h`?9X0t_0NJiFeAY/'-`6#tJkEocNiQq)eDM>5to,KDS23obKhg[*C'"ls$@E!:be:i + _%DKP*PkpHa*)Tm@6DH3^2i-UKGP6ZhKcmS/4R?2erT8:$W&FJl0'-KFrH9[EH-mUo=']`M + ie;!6G.BBJOCpJc%]JC'nUd&qunG48p*L$p*dYq;E[#@"H%SF9C@@A\obr0-T0q">)`n#[A + QIZCBLA+/!1i\V+M:6P)VPDfpCn1L#MVZ_1M(&#ZPE-)rO+)d/4Saa+.>@oY=[rX@7FCSBp + >IP3*V7\=-39k.%X2VkQ=54s*WT2kPpZ''ZQE9i`:295!08*k:[&j9N,.=pIO!@Ui9 + mMoEL9"oeaT_I.>m4>nQ!K"0iY + (7^)R-"Y3LX4>G&B/&an^[ml[mXI[!dV(G^/O$#eW7C0=\rD(:@dS.$[_#8BkAe,g\2IRSB + *3hei:/c9r[/lBb3!_icF3?EU!i/sqV>3@9=!mNpL!?kc\&senbHHs:Ht8Vht=R/^A%Z>in + F5,>sD-"e^3!!rr91^r;`;tliqPsYSkai5mFn!F2ld'PKV_XPsR%R\8n3tN + Ls.B*otYp<$XS>MfH(.E4Wrc1GiP[\>rZ+7sq7kojq*ZsPbHQYiL$)&H^V]:aCTB?fY--Q/ka4n].ck^AB'+h'-tm[9a4kI2B3BPi.%6Y<96/9F0+kLR5:*% + 4WihYpHVO:70c/Ke02J7ch()H7T%5%\mnVPPp8lK`o2f)dhL=g:".*.2,G6W!Oks+L)Ku-e + J2(_W_#RJq%?26Ce$lTp8 + tN6>6X,jDAr6A^jhflh8OD>G:h0W(KV@'FW]bJenUO1IHPl^Qas,$+6KEmIU@<`DggOjV?_ + ^Fk*^(=iAa:AL72\V%u=ZWq5&lc7A_Mm@ee5.Bbk@WUQ=Ir#"2U)))7jg!8oOYNW6<9ZQ]#X5:Vddf623f"_r%!6oMf2?r"@)lOBf%K$YUV[VS$(a*l + p=jDW'>iZoHW]%mf6p#gYmb4OBI<*k5*q:P&(ja5RpXBOZKg">rF5h#ecjFFm>( + $UQQG#Xf@RKi$286YM?&+rHQ"O[Mo3nN@^5oNmM$U,n1Qi>j/jaH0lj6V,UQ,Xqp$PTqAJN + 6,*W-$6U7dgU(dl%Lra'\+2PH>S\ieN:g9m2ja;I8@u\5q$jaU*Lq@ONKFO..o)7e>/(&P! + )SieO:b6$E7"i[S/0m1p%LU[?HL1:b0M=.+Sg'=V@9:b*AX,V..r>b@Xj.[C\!@r3KZ!h/b + 2[gFh*(nQm`s?n[6+^/t6O?d'+59UV"u(hCm"Cb:K<(+@.hZPo:VFcWfi8N,6@.:he9Q*T" + @bF+hWB'eksZg#ud)BA-XZaH\fg#UU#N:gpr&ZZR(4ZLSoU)p'@;1a<`#3Y(OEa_cfK84F$ + gT:Y[%H4u_f:_A5"+m5l'uSEK1dZu]aqZ(=,m2jA6%BCj"!CDGK7I,3681A&iC"f@@'UB\h + (T&L+<:Ok\$dPjT_hC/:>6D-+<+GiqJE*EVkoR)U;9iEP'8# + ss"YLV@C=R-]5X>QTTk;/S#VWY]T0 + b(cZ`5$/U>JL(mjUe9`?M]nOM8(n[:O%,nQMLBmn__l.Ve+r!:/;#q"6G`<<0>Y.HBhVLmBqF=UQruigF#.W#GfF8.*0I*5g-4$9?TMjd/81FZC)ZZl5dm& + nX92(o*dnYbhYE^(]:0WS+k@DseXB\G$3ggfp+eDLL'G[q=m\]\+^;[G$Bc@Xm]l'_u-jr?36TH6YRZM*OL!Y#]^kcCUk + ]-K9j[4><"oc%hC7Fr4M[n(f^rhUb_)a%cd/MN=L6qagmN0k4BUfFQ+rB]%nFo?+uP\Y4PZ + *&S%bG340Q@7I#gp>pA.N[tZ!VqSqs?R8Zmm^K:0:-_(++YT//.Z4FHQKbU7l:N_d: + 41m!k,D;kGPesGd9]<',X?7c!nB/aIiX&%&Yo:[Qb9g4uWE-A\KX&O'?Q*F%CG"Z13g]X9c + t",O0pjf@aOqpcbh8hV-E^/;fhX*B`_l*c""_VuEc^@?_`Ht1^!(Q41OKt7nE7j2-JL_9MX + _N(5S+"G%YC@(3Y2cb:j!iul/9-(p)*lZ*Wn7R:=.](W&K-Ee>n4k + FN0Q3TCO)O?H8hC0rGRU7>#[f7d5TXd%R/EXWq$HiM/`J'Bckeu+#(TYUDr(J4RaJpjrD4" + ,0Gssh3s1R/")pU0cp\'K7g.13"UBY9B-qr-.0[ckh_8O!/bn5mC>mGXi6E%q%L!())[F`+ + Ia.TkDBe(ZMW1cmZ\pfM%Q(P14QD5;j9nlH80YIE9]XJt$NmWpE434C@(/mA[u5^EbCJa@J + j,3<\>tLt/p`;Wg?:&YkY@*#Nf^j9R-ACl]833GlcA?A`9NH`lGSpG0"/Hldd,!*923=*:Il2_JC^jj\5.u8s$,\a`:" + nhCf\NY.e9ZR(CY-s7GSc:I)h1pa38r'CkTm7MOBN%oab)lN1A$ATZRdSb)ELsN3N#uW:pL + CO)'M`j5o^BmUsj7VW/!.b&^#5m(>]LL&cb6#5 + V2m$?3%fN&]!cWcr2%W>:#G>-$!KAdUaaBu[;MPL]JLJs).1 + gtX162\pALN^q-dn_G0TdiHR0di).hJ6Y.oe*U-KMD2K.^"P:/;75C4l#ejtON'%VH2jg?0 + +*\;k\@/!UQS'b/OK0+4G/LlT'.lOK$H%dodtaJca*qF;$m][!Jf;\p"M#eglZlgl=eF!cn + To.n]<1Bc@G'/pQ&q\&>V_!^A!DXS78/5i`8N+PA((k*J + D^<VA#,8T`!-I)g5WoI5P4EJF+h:<=0@@WS:^Cmu)&9;H%/ZtenaX8RQ+,UFJe*\H9?5*`m!cuom!#bl''* + @G[gX9sb+i5"G/.OqR$%O#G_Nb,L?Tgn2,L+ri+U/!5*!),WCnMac!C3D9q*%am72/Hb1f$ + P>>=Fc`7`!\W@RVDK!;R4K<40tcK3,=$dN04OMsnTaY"'?%2.bTLj(JuhRV),\kqK`;!>L>!4u!o + %njZ5;#*nFb):A1N(d)bQa.[%5\4t1i+$ddn;P%5 + /NLaP4&WT;dhR]<5ZgF2Xt'30-O>ZGntIK`[P=4.;JH/G4SKk373@=.?-?-NB,<`)(_+8EL + f/4\N;OB)H4/qVJ/-^[2Y0T)m%@)dDF.LcmTS]5XL^5!+6ikJeU%L-kkhBEUuXiANqX>5B: + EFi0XcA"9a69!s,D:5oj23Af\^8'B&?g!2'L'6j'_QW@Zh?mEQGT&e7/=)Qd.Ji%Q*Y?4p9 + (>dbkHFsg`7.:Q=.@hp_-<\1A;C2V2\ZaN4m<^npQHV"uI8!JWsQ;oB@O(B)3HWE^0-iHVs + 0e2gf$+=l][&5F]X"4si/2XgeFL`s0^J".J9MiUg[)ia'b=tsb%?T6*o\WBmj%_B*:2?nG; + !h$InM!@=MR*afOT=[S"`2Jj:pV*;nIH3^kttG"1@ZJ^2'W`T4#?49mP@9!eMQ_G+#PSo2+ + ];UPs[FQ2\dl)nPa2@G8A-C*lS;Lr?USf[U&!R4FJ4L^>IM;I=-Gea[s`hB=E[3as( + nabD!T\i0"#P;-#Kt:9D'/XjO[9(Rr4EM;JZYsW*pAp2;5B'[1'ZF>H[7` + EC;/q_*ENDdO(F']\@lO-3o(Oj0G;VAS3EJn*F6n_Tacggb;fo@FFS1I4=&bZq[%$$I(,_> + NpLAAf_04;J>$3&@q'T@DB7%m*#bZ&=b-J'll6^_6T9k\(_HV:u%-9W[S-QiCrqGrKH%hQPMjTonR!Fm_fp-$UoPJb:2E1N%=9!i]) + +QY$JJ\ca=gOTWo:!/kAs`Id90Z##-"g=,2n!u=E2S=hItP1bKBmB-3(KR'f4e.@.50pMnscFG\F&T,_UM,>:gG!KM + ]EWG*egZSECdbkB[!9G[6t#WM"4nL3!6cphafVE=[;>:UkafR":tid86.Z1(b+mobQn[iA3 + Ha;HTUZfV_X"X&Bp.DjjGi=M#7CF#nr6Y="? + Fo2jO4)&GkH9U.Z-R[Y%OFaBle)Q@-s9<.ZUOn#u_Ohj0/d12#_(_TF,U`H>F^!P) + U(+71/Zqn[gTS0j/@;>U#1+M"`H5[p`s8gRSZKXg#=)Msn(uLO$k6UNa#m)<>a*:!+j%B`a + kWZ5>:=>KTRhGCZTBtLHV:]NK:BJ?bQBZESAtp2a+=+cJGWK$K6Mq#f + [f_'>@O#dT@_0H%RqOI7!+i7NVADAVVr%3(qZ!$lG_fO'B=XtJ^6H_Ws#f(T)i\ACW6,d** + `MY6*nm3+5-4b%O%tMY,IE+c=G&H)]G"0C_F5+]OZ,WJ,&4#",S>"l4#N4C6i;O,C*Z!TpW + ,3)Mc!t\8*6mK!"3Z-HbJ#b[ekOA[2<,HBb<@B"*7_H?G-q#tc-j.n]489o4_VHE9H<`f>B + c\ZrO:58\-nEnG,;^7Webi%\K)E>[_"4Mo:^RW\fE33ql5SN-;%Budo_Z.;_->*j,(H8]K/ + :,H:-2lXR;NM':i!=g/EpW8%nk,<1E%?)J*noAsD+eN5d)>fY^ + 0Bs)H+*E[`5"74cp"D29,(?]\_Q5`\$oaI_]@G + N(`RX$\s5?nBJB_0-?4qAD:oO?#s(eet>YZ@UrmkBGH$lc-4fl6UoXZ^LY=l(M>kYdh?Jg+2\::nK?;DZ.Dr + ^Jt4au#[9l9@or'"H[YM8Qqqm=+R8,8t\5S*bp!1%9nUBn]X2EEa)FO^)*-)5UdA)+KaYC1``NUidZLk%&TVa2M#3+rQO + `N?ZI7<.kR/i+tb/8huJ./-s)IP:T9Xe[FKb5=;CL]@T[e2Kk+;o9oijg[U + l(Bg,=)MBNfSeX)(t*eZWiE2W_\l>Vi>h2@END3[KTV&cQsD2eCfgh=m2pHhRB(dRV$bP_40U8DK'c3RIU3Z$/CKh:bYoB/:b]RbAroI(U\JolE"8qs$F=&Ol!*Ht\EUB + n.cg*;$O5]S-@dS8REBm5!`,mgK(RJPUXZSq;@h&CEeDpAs/37)3s*-7YU:2D"5E.mMIPJIQ#r$OT\V"6pZd!DC*m"NL.)':Zs + IF$=95DT19TK%1HL6;arkUn#$U`b;a1e!5!;%"P#1qD(M,=JK$'Vbe<'iB!&7gRs',Ik7E; + @Ho:lA?r<"U(M7lt9Q28MmT;--F:iXT_M(n95UUj>+^j$9Kd?H+je8 + ^T_c9nD+`?3J'VH""?98AVO!5K-IOi^94!\cYp`NY\kEe[!).2=8m1o6HMP#Y,"="7clYEd + PUIW5i1M)]f.j'5k+UeM5"c>[j9-U?jo1SpoXP*J(@= + @27nY9Ikpf[DTu\DpAC6\NG!Y7T,?^:ce8$D)\tZ232B[RFB[D.\Ls!rPaJ,3aYf5j)][.1-! + VSWRQ3$`B!1_d0ur7UQZF3^bk\Wkjd`H?EfPF&Q_#hjfZ0[\ggN< + k2DiGKlPr]JGE9=VA*OMi+]'WqgcDl8lOE_kc#386;r7=Gl5+qq):LZ0bjeS'5iL%7m\tHE!TQfGK7!906e0>oUAV9Dd + FFRS^Db^-CM&p8chb0'8,oJDh0Z=_k>8tJrEsas]WbET$J:taF=^^UeiM,\qm];$p!1+S-b + @W>k[gul`l$Hd-IY@GOg?rL(A\;8BPeT[D)SN)7J?'7>MZc,QEL4t@cU,d^,^a'29XeqIuN + g=Qb1"165H:`4Rdls&:qUXrt:sn8F?JV08NFXE##lK%thAFJpR#61QXk#pV:#2^gm\cF9oZ + u$,D3$5a2Vk_DsUh-SSaB3@gag".m&%T-cXj5r/kYPSC[0'fioA_/g7*3toFU&]#FuVc0WW + EV)E"1bCp1G_][S[ac\gV#]72m\[?M6k#J1cVhc3N?T-*i$Z[o>W8]4gYj'1b6Zto0]lWYV + ];&6o)*e6d@NPc_&\5N2'*1,L`]tdCi/c]j"=@,M*CRfJQRD]rK"6Zquda;8(Bj/ + "Ci3iJ`^+3Tbdpbb/+$Nn>X\7iO94p]A-+BoJ"uoG46gHY:m'b+)[aHh2qeiSMknIe3LR;K + R'FU\"o<&c!`fXfH@c9Oln,r_I!iptWpcjC1CV\(t^Y1oekqe)+>(jI]JF!;ug.MDW8qXW. + a!?#I[!]nFIE09T8(<"/"FnK,ImAAjQT9/S>TCT'P=T4&5no*j/"F\EQGI*h_0ctgS/AeAL + I%Gd6*h@u<#ce9UVY`K\kAne"I)4l*l9G+3i""3We7n$-9Bp/_L*e(_^S1,.k'I/]/2*$>V + 2p:[kdk84,TCt6b:/k]>HYa_c!$]H/Q#]^b^p/*`Adr.m,pV4=YEl*l+^Aj2X96X2G+_$X? + !1ZpM--^+2%K]^&/-CX>_9b:A6:X/"-3Rnp^a"iS=0[6./HN)%=p_2]aA`=q%.KZ's^`P(i + c[F3;g@p[,q?nDK@`LC=u/*9:3e\6@]5)RsdCgTX7@6VQ`/_fY)]C$m8A>;P6UUV-A0G$b^ + <(`qnt*h%/H:c+FuEupeJQF`;+8UMd=1mtX3Rn9]M_M]fjX=>X2Si\b[8c,m3iQoXk[n8 + Hd8jbA)Pblc,Vb2%fd<)=DCc9G:^n*RJb/?=J*3I,hl:U\31^pWVnaKEj5ggj[T0u!r"@Ki + G[_1D)`+2A2S'f0-]HAK52"XG6I)%mk_7c(/2.\Gb1(MMq=ZthF28iJD3+=nJ3+uUs.T>*8HbF5,6;^: + 3,_&+:`q@7Sa=dc8m]*8L4Pe8qZ%,!*BBLJ-l:,#)5B>,;i$Am9!AR:0!ieeYQL#[9V7a?' + ]$lPpGtC/G.9aTZCX_:X*CqCC*e=A^@sq<^co;9`7cR)peas8K@k5_Ke^ID8H_bVsO[[KlF + n-X3%+^6?NXHm0m&q.EU?pd.Eqcbn*J\[8@kqd5;Q-D1&sk1,D@JWPgVTgo*SFWP`ir6\QI + OboB*W5$2A;T!Y`>gp+42ai"b"dR(1d<(#)E7+--KdY#q?XXV2R.`AG>bN1:nbeMh1_*+>B + XAi%[_T'eu>"i.);bY5*Mr@TTcTX3G/JU:bV)lT'42o*Lbl#f$SC(:4le5mu`[sLhog5KD: + SMeo"$eBe_.M2;7oY2Dc-f;1j,Ab4?Ng7Z!h^]f`N7=TWY[fpK`F@?=9=_'kQ)M1!4Zm"GY + )+>?!H,k>NNWSot]>L_;#4.eaZ2Gm#r>fCK@MIcWd&CIE:!!a]=,ZG + Z56`!@Z+kHl&d(S&$,n2pT.bB+&SEmOS%G]R:U<5SPd62q?[2$:)lcQK1Y!Mgr&hW7+2!"EWmU?=UI84k[*)XY=PUAR>=qPH9/OU:/"-KA*(il[ + P:PaZ2(eR(*5JsVQT_H)fo/g&@CXgGW"8":g2R;g?lbA7B`M0Ybe_gKNj*4#W%(@%EBe'Pp?:^YUe"M%KWdKJK + T(=fM5je&6otl@GW'b"s;h?+(?Jf>WG=k80HL.cJ76q<\;@[e]Za>.GV?]@ZqM]bELKu-K8 + >af$slY!NOrpVm6Gl>rs2b1mqWIKjAP;?$)URL"S5P%TeA=4%7q%%=:G5\(AEN?2YJ:V%p= + :J4b5,46>NjI2Y_j3F6O(?@@PXL$7BQLao6X6i=8g,d]C+Z+S=-]\Opl%Iql"\jV8E7%CpC + /(Ica;60Y\;V`teKMT!kgQ$s*+XNRaeMLY"8i;Oj`rXgCS&hj;gKQoSg5ZIqP39K!$@<"i6n4SBN-\ + <*OOPJ^oR!OK0;*`bFn + 'Ic00&"-_<$P!NKWM"_0/?d!h1a,HGR^rj1[Y-VEId&hVINQ;+qV/?9G5[V1ZE%7bC\C4B:W%r^p&oZ.PSPhTeht]o9VR7&rb:N,DKcQ&oQ/o]8O7 + j]2$9AM%8KQ=$IBAYW=fghRbjQAi%k.rJsVkf/GHs8I!9U<@cXe0CrTJeFEG7l'[31\4IPmJ + /[.$QX,o3US6YmPa.oLHP1K4mn@pF4C3QLHt(clL#p]km$M7(@H(rdcj6j8WD6aATMR-Rq[ + T.mmJYIme*OPp=GiIC"7Q?meLAjV.pHgl5Z_e/jh_nK&06k?=*\0c:?%=LMDh66gk-#-;34 + =b4'8,E-"P=KLJ#rnSkM;jX1I0^C9Vfp.OfIE;m:>-F[-4>1o3GnPmM2\eX-&(YRTd4uQg, + /YJgIU#8CM>!BgnhuiN@l>d'*nePqIV5\9^;6F#?"dhpQ@5T[h-hCPbe=1X2X7\CO-FcNnB + BM0]k:^lO&6k4sKc!)XD`7A\Ie]7eGb9lga,A^X2j7l6oKfR"Va<,94)UCJegEZW7mHfN6$ + YDPGn67g+%,&uOkh@IVuK6^`g(%=RVsQGD_nUheTPH#9fkR7?-c;VhECN;L2WYK?4Trcr>+ + #r5Jh!D;e]n1BGh*UECU"k#R%AF5@ers6PM$2-!VG",D.Ve,0W/6P^BE6/giPCNIFm?809-[8% + f)GOF>&bo0:)T=Q@FO$@Bc$ + =V":\mV6mtoVADc02cLpX@nNUMfppUlgg\Ok1PsWS\AXl`-h$cb!fKH + TdO@o%9eqOAVn^td$Csef_5UHS*XZGcJX%u\Bd/@_4IO#*=+BInHM/rrf0crbB*!,s=t!T + 3BkHsT#bQ&_P*^Y-ce=0\3qJ,B40&!tgJnb]YAm*FSV<.C&Kt+"LOk[9'uD9m1-`_)N(Y&,;5@bA`t0AJ)[3K + WI.4,JgU3RkMtuBWND5-de#Sb>V*< + 2+r",ff./sN86Kh':eGNcP*,I43qulq*33T%/W?;'I?4bSNF>u?orVe0p_F6h*@B>FKjcrI*JWPj;:'-Opms!n@bAs\NT-hT4-,p + Ln^!>.L\]52jOm!uPi\<>>\dm'J^'TpL]1qU)S*k0F([MqGFY2#U&UeLH5;@4kf03LV&['Y + P]CL7H),q[R\$R[C>-t3B^9=,a0'4Sp;*sO)YVq\dt-_ + 3kReYkWGL7tR2G@[TEMM\dAG7(X^h5bt`Yd*s2(PK/?Mc/'60"ssBjf[HTpZJa(#Gf1^9Nr + 7tYq1++a>1u/2=Ik9$T`%0D&:FG1uD*VoJS:Vgt0!,Meq6/:'^.CrsfpBj+!(VQN,6H!'*P + M!ZaY,G##m-4,8)T,NI'q:Kd'KE(p,#6g/u,_>lgC9a2a()MF3H(/aR\8LW(bMP0eW7EE=Y + 6-)ZL;;+0e-^c,EjJ!EA5Wjd6PQlZ2'+LXO6&"h0*$s6]&`@=t#Je]6$t2d@,(r:?0b,X$= + K*%pS6:`sC+Mf`]bb/]>`;O/0ub'P)<3P,;Q=%6KWp=F='FTg$][8<2+G%7_V1&DE^!!YCp + _cNcB(p.6:_9>*olmLOV091Xk;qZUkFe6CT2DG)(<+m_XNa>mS5El3khE\oj@9LK?@4(.G, + ":PtXAAYne>!R(T\i$tYJT(-sNT.jJTKM3U`b>;(an0UOE1Rkn)`1"kp8BCd^N1g-A*>&-d + ";amWB7\nT^_cN=%=fr6(`n70k@;QIVER%V7gh/unSP@A@cIVXF)rlRB:'Q!RG8B1/.ub^t + 4GUI+,JeE8h6(IhG-+#4j3aupj*Fl)s`%p9=Fd#!AD9*b.V!!&)j!NWnjYOR(M=$T4[!54%P!>ldXna%u!-&SlA0$W)(2.u: + ;TX34(dTS_,BWX_"[5OW,>1D:FiCn-L&m!^X8L5CQNOVtd1.=agI;a!,]XV#AiS5B/ERf3B + \Q7QX8U2n/jJqa6CV!?b$`tf;jJ6/rR=nl!=a/tLY*NmEn]NA$/_L!4]i,"+9DODujrDF/b + fXKQlg9+("%L%(55i_Fr6qOO!Ln.AX\Ge,2s1q^D,ZmD2HeJ`k?R5Ac/H;of*R/oXS$P]id + =PG4@Wl,$k):#Eg+9;T&Q"83@R/L'/sf^%;i3>^9uQ2m/l#JgDfR7c + F0&q@U#soEOJOO?A]0;Z"pAWtXs+$u\"5m*tY2G_bfe@=NS9R8Q@2pXVYdEqlS5+ + E,C%I*-h\i&rM\)"s"c;$[.ca?LP[`67N#ao:U0Odjj%O:UEQ0q\f^Bj/*c+tn/LAJ`($&n + QN:X>]9;[bKU)6D*P\J]J17;UD5"^HnT0?NF<@9H]qO%2%->1QUU&F,RUiZ-M=3_]$3RtC+ + G0]NNcP;HJ<`gdTF7!\JRVo=m4a#TQ1+BGr-bNU,)-o(BQjs.1-0d>2RPj6i)-4l:YmSb,*qd<].kl2;^'o\QQ1lf]:([_BW2>8M%$95h/M`m + +6^6f$MSCUF&J\Fs7JG&1 + uXHs_VN^@1./hq/!FrnInYn0;5.g`QB#cg?@hO85?+k6aukhVsJLE%Wj(mPMiL2WLe;`.oh + 8Y"#).W'a+Sa3r0AWi9m?WNNW7Cs^FAVX+kUX"]d`9"6r8cH`I9m:4r=XRtohr + pEE?nVE4ncW5j]%^(if$`+-tlIt_ZBFc6&$8"^f+`nB:`:]4tcmn8I_\R$qo_+k]\Dt(UUXT,L!s3*%p/27,p$D-Ds!6dY.%QOP[0'!F>*AAOV&0R[L + ;Z]]s!mY!P$8!='2Zl`qUX!VpcpGf-7g,`OV3"Nq&6?o7>p/QoV[ea0:6PmbR!].Z#>V$!N + 4S;;/Bf`LlR2!(N7.$rN2j%tln%/_OIGO]d".:QX=X)4bki]jY(LE8$V5ILOVkEd^9+WW-D + _ml5\^I\#n4uE&V/dIX[X2e9G(,@.3u)D87s9#2`N]+)LMEFU`.CAMpe(GTg_n$NsieD8C.(_\[]:W5,naV_5(r@s=H.Am'+R/PF$rX\/j + D@JJ.W>]cZs%F^X-M&0q!9/qr*"WCJ;g9H?aZ&b6TSu_Ka#M&",<5*fj\04eck4K + ;/jc&CNdE_(a0'("TW$1?6ebjcuuWiYXK-4o00d,m[#bYQZ)X7C<_jU_Y^>QjVHL93-E+V" + u67M`fc%l>@["PYA];^oWjnDL5N?u[Zk^]`RZ"$t!D_(e?".7Vg,7$Ma^oQ<[2Gt7LMN3^r + ,&LS*n/sMT*o(,GYs%1Z?8ee*nmAEN4XY?!"q,FamaZV@sSD4gogiULj&$&C1.f3-t + 1JrU4l%m&P4MI"H1_WA9;3IB3:>dp-^p0RjZ)%Be'&g0,2Nb6'1N+58.faBI!3AGY'c=l,5 + 90/3;DHials0[6Po`OF*f"/+1SDcCoi(?I<%:HBDKL-+GronkGq>-B.V1^r?T`:;+g]K4r,*Q3gqW$Xe,e@4si9AA/?mL'R;_gQLM.['*&:Th9F%8sV8CFtkX..n]H>PB=?E09]&Y + __J,h9QM7a;^Z.]F%koJh6b]g:Vsh#K\k+4r7'mI'2)uCQSg[P2/Ekt#\\Sh(Q4Z;sW/tf/+.*L$klHk(I21EcioiNo/5>K0^P"S$dSQRh9d@if:dTK`F&LH + Ya?,\O)()0+9RKq9>Y@2Yg_#&2G63BJ)>UqWN6#%&"7L[).#a[Ua[kpc;3'fk,)*q./TV;^ + 7n,NGL!oPCE6!P$]B42U=@/#q2pQp.aRbHSDbcd&n97qFHO'4fE-(7/BCBgQ6!/4&,\+#[pE) + XZ+[C"AEd'##pFf]%1NoS1G5%G$i2-cFDQn\,&i<%',d=N2$[#/uG9KibJ.r"l#suF'H5(j + f"M]m`/g%oq'd2?@+I.0/1'2tM)PL+Bf_(B:foaq-Or5R.q'hITd<]>kHFL)+\$9CD[n-8, + I/-,C(WDWJaP+0pI+Blt0iaFJ>e;^t0pgo9<`:akD#0H'9Wt^N(1S/ipJ#SD:"b4q'1#Aq# + A8/E1s?,!0q3`6UM-u@B_WfK;7,=_%;E=*C"P:X3'2btc"a\jS2a#!(eXpr]Z+sUCc+:8'; + XcSWlOOK+8BJ1DNJ9MQLO-=Bo$_J# + +8\.5V0[U>0n,f7,!PtEC_&'!FNMKa?P1l2"qNn0BQqk5=Kcjn0^fQg^3NDi*@X18)>.Kl$ + ]9PFjX?+1^3EZ(.f<"QWAD(\!DkDK(DO($=XuUbDM/>s?]76,\J(]T5eoNZ+GF82hi#.kp2 + "]_9(uZ2-Jb9*'QmJ32.iZbcRo.,a2Pj:'4]2h/S2&W2:a'VBc%PWM!qeLCGZUHbVZ[[8Sh + mX+EW_o>]5CQ`T/lVC(k0WU*JdT:DlgEdpB)W4H(bt$M3ID%QP1W(UKFagES;d*!M?*uS"f + DqUG\U#ZkDnT545cuWPnK=,a>Na0FpFl@)7&/RTWlg1_%7)#q\U_5q3D6T(Df!8Xafj_#t2 + IYBThp!F_-*2^u@BS%njX^`36f4:/fN$pt$a$ilrS+Beq.o6pJ:?i&0N',.EI-`hX/_`GcS + >VUXH/X,c:_OO$Kg`r+gfjss8IX-'bflsTco9\2$`[QIN[VqO:qj?"Ba"@=Ao^B#PS$:SR7%&"C0_KD)F#.M1[K.-13H-99C4RF?jd1b#ui<#\\O4&p-D6/uM2CsAU(dCi]9ue_/W16j'EcfLnm' + MqX[/CA]0$3li8X6a#S:Cl$MFl?5hla:b-XH$&0TQR@-@ukS%u3n^*J3BA8Pj_HC6_HF(O1SZ@,sg,qINEDaXMbLq[!plDnq&"rY'ca#Whg)N-O"`_3^d+b&:nkmdt(NS=NUR`I1SQXQ45S4"M$Yp + #>.,hu"PQtT+e*U`e@;U/2m#r[I6=t0jV-1#RsCZu?-cWRJ^)6e`-(6cfX'O7DIH\*J'B&j!9*k)R;BQMD@]BS7J\bb+GZ5 + S!$&6=Ud27O3*u0W$TPoQ?PS=B*J0YaF'TPa2P;1PM=M1dc/>;9JD7*TM + QMZrel#=7>me9r)me)"@hsZp?p'1nV_dK_d>YZ@UNQ,JuGoPF%J2ON/T.WU>rR0HGB=#/I( + 5GJQF_S5orB@8Ugsp22>7Hn5BsHCYp=l.Xmr,nqDk">@$`f'%iGnX_N]/>RbNEoW*8X0VJ( + o + P^+G:R"hlRKR[F(@J!srW8!;r$2F^m3=^e4nRTXoLJ!sN+Bpn+oWFqX^7ZMbd+6JDkTP.L= + LSGakM>sAc>#(E^()g5HBPIa#W)oVRI]oj="aCLYJ;h4ZOI5,1%Y'nCB$OED>@Er-mEC1gV + 5_'>\u;WFQK,Y2jj>PADQ;nA"p%6aQ;mZKhc&`X[D-CX@8'(NJ2R4m.%HG?tN$i:m3eMrK!\+-Y`1?:U + @)#@VjQf;'cKV,og`0=(.qD%j_R2(nd6nN2cgbOPMf-dVI65A/Cc=<=(qO."m])$U^kA$UI`YM@hBX7.>%1U?FlJd=O5G + #i61=[![oI.RAcF&f)a4aHP%_1PSJ%)^RI]QdXqVLImU_6hl%sdopH[nJ*uNFP<8*$@5K&2 + *<8Z"V`/DJPd"Z6cF0^US?2!;JJp#7J/AP#4e=DC5t)",uU`&<,%>LR9D*Hi2kJIF&qSj?7 + \)*Y[bc/=]+j(8+iUa,Rf<.bA'BrR1Qjg;W1k2Q$,^kf-FFBfNqHUH7sOt^1>C[?nukT8bN + TA=@3@&N)!;+.8[u0;]1jZEK<`c5qCVVZXiXWQEmr(bh7=tB(GVa9D3h\=p2ZTP80k"foXZ + 0;c@Ee26+'=XOee_=_l95l'#`#+e(Q-)6L&Z[as&t?SW%*I9 + c$4db*BnX7W=YhO[^0mf."?;Q>Mp!7c<#]OA`D.KL&dL]kW2-[LO + LGB*bPq08/h_)^D:[$K6,SKlFmH=7hsl/.tuUd.K$DG^,Wc!bdC0)*#QjN=qsr:U4^>.E*" + s'pM["MQq"gW:_TQ@hkb;d?ZU=8C"6qChespc8_ko@3$*MZ]B:[A&l[l+/C06>&kX$8=9mr + /e[/KMipY'"k(#YUmGD321_\;T;lqS'N^*< + 4eL@iqB^ji@qI!4IFA/q_TXj12kC]R?KC=htFI.>3+5.Ul'!5[b3V%qf&C..&LBU+%tj,06 + !Y]"b;b#77;B@$i!94%:N9^kT0mQPOA^?0OP3<"sbGp?m>cd4c/]bWhGX^#eZjM,fAIU!8+ + N<:bb!QK2+5DR*-^_4H#@X5"Wka0.1;3Tp'MW:O/U$#6;_PUrC`L&HI?-\^23hHZc#ZC$dqF"YW?gA6[_.`lpZZ62D7us/Q$$N>[+:u#`k,3Mm;BhLn^1Ur3:@osh;5dW[THj:7=&OPZ^2W^?#P"L?[7\fn'b1mP<*Bb^(Gm7-7l + t-.W8&p"ZWJf24lkc.IhF`%7*f8^nClL!kb=66Eil`3;X>OS7X(baCXGNJ6iOiqoEfS+6&L + !M`MJk+5@\=je)SRoC44LK-)l/*7(AOIYlL!2bNh>K-$f:4Fa1$3H^"cDDH)j*[\lIVWTD=NscS]bEuB>//NhIo-q3kGqJrIp9nJGA + .cJ]o;gb@-e8(C]2b*[P?@eCqFPj/_8:XT,27sq%pI4b`:G)Bm8tARCFl0ijsV?;-eH?G.1 + 9%]CN.Kb3R0]mmcp*HeM`2%Iq0AdlJCOp0mFelS5U#o4biQs#!O^J@JA_`1/cAVZVaZPFAC + *S%^FKp'X$]:f)R_aj3[ + "#b]M]3qbt0133JMk;Ne5[ip= + 3/*j#=;R]Tm2e);9CNr&WGs<3OR:GkYMtJ`Mp:')VYESarK7QMFVm#.Y-CarucrWHl!h1l[N% + IVk/BOohaaBUFLsS^uSjO2I8c-+UoJA&`]sJJ38io@%$F<*X6o8/H.ssHA9Kao54tFgt_tq + *SgWH4i_7[Z`9)1.u1*FMFdDThK["#V_`59kaKMB.;d_u%:`=(G>,eZW-4%ng + I9DdY5**5%G=13uQ`LCU+<*I8R>eY#[[e"DiI*"hdas?:o2ah,hj)r*.AA8fIY;>`=EqRG( + p,CdU)9cm*o7/e&qF9NB7f*0U"PF!,ELQbH7l(:XK*FWSLE.q"8G\DQ=C)-r&ZD`O#9[4R` + #CD/.0:&"a5`ARPTW#[:]QR^!+VUR+>j5=N_TS4c0P[Yl73n-I=9GajcYR7%DUu##JS1:f-^Sm/g+OoKt + 2<8d(j1&d1fX29JTNTS,8:'W*"r_bhpS6bO1$b=P(V50CN&9$/!`V&;ZP66XQ?b65($;cFq + d7nY[04Zcb6UdXA*54O]XIoY32/7[.XP>\c!_D,"430@`*.79PakQpl)$*YX!f.tMZl9+?/qRp'#s<].a@-2M/k=q2YjG"XZNhGDL= + D*FW$k1:fF%\DIR$.E:W6=6c!49)<7U)9S`+ajH&52!l4 + (lpT75c;Da(]aZ$r*4*S@-VI]:;BsaUF4Sc%\l)*o;I(W7/A+-a]f%E?4nU;K$"1___7h0s + (8Kp69Nj?n0\J"tdj/86SMSKil-iStbg^.HjZ$2&Gg-[Y`dDHrjO9sspJ^0<3"4BXlMf%\r + 7!#[#P_q+WbXi_:e*Y(30\PV795;2?OP>UK,^D8W&qR1+0_\(K&od-pmT"1,_sHNKeO8C(_ + 4UIJ-Y:L*'I\dE,Gi08^CZ8Z@,65"]l`j#)&W\!72(ni%otH,-\2LgPs-P'uSg,`8"Y=>]% + Of@hc1OMM:7p"V#@r`<"Q/c;rFBetp_oS^C^8cIU0#/ + 8)^\SOT&U;r#nmaeIJsJ^b>4g+.j14PEt-V+nY#a%`L,jf'uBWa(CjLJV90huelF*rm.["_ + #EUR!=>k:kEp6N4#oXq&:XEJ: + ViJM[p.i%G.6+,Acc"\$JeAh@K;/n"B+@brctl\gP.NSt]UYUDg?^c5TE2FB(F>qS*B-95R + b3TH,\6=nYVCbf:%Did,$au']8NH7]k3*iY.?+kAde_Hi=(#A5Vf:Chf%H9LXil^qC=5A@! + *Il"E+!P"S924WXQ0LcE9JseD]p8!5AVj<+gr1hXfY&b-oo2WCN`+&5;VdDe$K+hZjihd`5 + ((FM/704K?rKa-fmPd9h>YXbcentr?kh#Abrj"BT&<:Z?t*]lI6leODAq%mi16[n8qti#h+ + 5f'S#sBa#eP^#-NX[IW#/!VLbE!h,DD4L"25u7$.@*OET*FFKD75(Xg(X*"pFN@B;FHiE)_ + P48d!]m!U(E:R$)+-,ZSFd)oNj<]sQO!(s!PBL.G8'(6N610PN/%kr+]=5*L^L:BVUY!_=J + cnH.NA4QF^Kj0kIfY+9:@SZ8i]l)'nZk+Q(&U=U_W_f^%P9W$lF'.\f+=*)@AC(fTUi+.eK + ;4Odq2E-=G1*=cA( + UZs]JC/.,gAjgBM4)06UA7aV4STrP285Y+5lHO^a%^&2pY@Q16AEAA>r3T?2kt>Vc`4iY&N + (mX',o$'_j^u'\#$5.HN8Gq@jgC.bp:t]Sp.SZL=Z0X?4C2huQ0'W+Ch@3bgs*S#3'RS89b + j.6[V8n$bfrp>=o?iM%aX'V#ZND_k6B@BX+k#q7`#(FRhg,7^qp0J-3=,/!!"5E^__\I6%a + "4"R*O/+B!q*9nWd8!tZm$b^HD4.f`[k"fP)]mbcMpj>nV&Ka8EE(63J2!,*K1JqtT6?p2= + ?&ft+IKXap5+?*OB,(Skbmd\`o"/GIuYk-#":7rR-BG/AD,D!oPLTk6A-Mm>cAbL$>p4$[t + LM%YV-NW]T3Tst,WPe4a1[m%'G4>?I?ZC$dE`V(9n:,@k+)Z%hU?!,7A0XJ2c.cuEghMTTX + /;k9)bXqH9\6H]f>^Fa(DYe$Iqo5MnV4>[ru,GU-s/sH=B?Lp(/s)A+(")0hY(#k'AgFghs + 8PXQ9N"uD=fF:>o)M?=XEh<:;b;9@ALpo?bXNecGst]s&4s"9a<@gXoZd""&m]ag%+uUje- + ZnqX\00D%pttVkn#7gN/g#F09S&'GU!`BV"(&"i*gImnu1;T7Js4JC#Won/6j\.fq[lK(p^ + GkV<41:',)cJtO?3W#B@'ME,L7K:mhgf&P&?)[?(?"Kbg,Vlbe&'*&R\JN+5#QlkMl.d/QC + iBTDWYXnrSFLfe8u#Qo + @6s+*HYD<N/srmB8321"(nb?(u_m5E;aK#_lBh!Xp]"bu&i^ji]c/G4K/PK + ;j,=mGE.6(QHh!Pk?n#A:I!mWX$6p,[a5s(3bYMQ]7@RHX>?]q57Ja4p4\I6M"FGYB.fn,I + m_l-Hs%T5&(C=q44"B@(e@P(JL8'CZ&2EbiQ,@N6UAdu + '-7>Y5po6Er4Of+P8qCo9N*Li1X/iTfegs[>hqBb':dr@c;V+743pjLq)hI%h#BRb'HI?Dc + nma^?P

3#YZA^ht"nkP;O"R=J"7:]2bd58rB4!+[2hP1Rl&YjFB(2(;-)4IF]hes1XMFF^EZWrHVdPed[IEf[U^0AD%`(4&">W`M#mBp6DRtg30m)CYMn7\9"+gV8$ + qR#PlV_gp\TSFPRa9b\Gq"Fk_,s=,lHh?H;laC0qe-o6oNJmK%cDL7Rjd*4Rmf33+P:fs%[P9+OS"/=f""dk3.h=L]usHLAGL/-fk'0FSi!`3C?;*6*3Yms<0%9Ws + 8sprr%bd'Xp!:Zf&KHX5jD/YAuqqMjsJhKr!6\DfKn.ur`h:32`d+7>cHlFQPmqXD.>71CB + \T4>DWIQtU+r4`-Grh%&@*(&.JJl"Q6?jmd%crD/Qe@b_; + $hf.5I4pY4N%%)44pI*RmfP7A#_ug$'Q+J*('Ra4:WH6,u!?`2ioj2YS6ne6;,,A]L@'VqJaT`BqA>9lL&me + 4&Ai)LngEL.uISV_U7*;>N1M.8k;_obDc<7f^BC`a1b8--i`sYJ)XZ`R>$$=^oj&DAIJZd3 + 7JD"-H`L)i$Zk,^VgRD\k+YCOA2_l0)aLp&A/b-M5dQl^36\B?r_JcqnA2`4IMfIh[F?'"A + ;B2Z1S^(WB:DZ&mB;;(LKn=[f-W!te;lt1[\@PKLls?V8[4jEXgUkLJ@GV$HTr)8X]Jkj`% + 0[J'Y;t`."(5p.j4:!Fh\f3_0$+?7Ucl,If%k\o + lZFE=glf.p7S(Wj(rP"=RCK>):9h>pa=_"qP\d5\>kV4C,t6=K19Tt"`dH+/P=E19FegjnE + HpTo9:)beoLnnkHKGt/C[IR#2OTgd0;EG.o0-kI-LlQI[+^ucrbc*["b>bhbjecA$\W3Uo!Qrh(-OU3%j4%EKQAmnj2d!BQ.#EO9Fe91Kb[(o-@3Q-.iqR1d+EN"iB.JK*Jph + bI1?LD_A$`j9gS7iW$7]7&i1qe%,kA=!$&hFiC.(WW$a%.bc@;nW;IN"TJ(u6J![u:+9Q,< + >D\m%U@Q$Wc^I'gBcS.goS/UY>$"C3I5HMpW2^HNi-f>O+/mT&5R0R.e_7'"AF1\/e_7Gag + )E7h25[^dg;;;'qb*sUQDrKeZX?;H>p$2FZ.RJ;gIRfQ]WLVH\!HQ;g^U#tfQG5u)d;8nbE + XH`jJ9@KH;VhSXgmJ/XPD%f=5Cu4GT%R1QTMogD]@/PGGN,m2o]=NSb4d_j)2UJl=fYnWPI + us"e.+HKG%G$JL:p3S0s + [GPJH$d2lkX?;2314P\DSXD^UK`]M4?O.\XpK(l667AWeGQXm@)q<<\=ajecsBk5>W;RUr$ + H=]!piI!$E7Wdt3!!=6]3ZiuH5jPWV.!:FT0nf%L"ot#%FoLH_;7:9*M&WL<,k[Qm5Z7s!H + k?C`/iX$%Q0S>j]m,Rfik/o\n_Pq>ptm7Ur/p^aiKu*iJcFlLVk&]"8954QA[$=TI+=\Z/* + jB=Y!$+%N#L8)HI$0g]qelaQCh0(T1`s:I`&U@+(DBiQ14K@NS4=M7dW9e[4W6Iq:f7nI3# + q?mWRObbboGd?a[H=0'KooYOGibh0Cr6oDI42WGcY>^#hKIhbu0)?X)sRGRsK*Qh64*Hi_* + '']%`l$G`X\S?*F@ajMPs/A_lPSd.+4N%T2^5f7J-/IXo*M?#9Q7WpC``6;&O7sO.u+JM\! + 19=l/%W'.fDWa]4kH99/mXh6nIXA/JHgZjtNf_@6Q9%d,ac;n5m\994VE\`DmkB4sKY4D&: + (BOt$&mm3BJ'L^1^M\#=S&.o-Y;%!pi%g[\`"<]Vo:GrpW*_0WW&-2 + .mDb1Sf9_Aokoe0qMYr`V]55)6I'?a)`;+ca"FV=3ofM?i/O+A%jOP+V_pl1g>%@[)eUlk, + 7P-:J3.5YOO[/,i\\kfK?DmnGZY5>2'qrqMd/_HBRDS30ohG;MT0@^uai;K_=hf%SiXoAjTfE#9 + MR9<3p!MO+\lLcas.%LCrU3C,#3=ECiXhSI0H^"kUqVRVo%ZXSHfL-#?"%5Y^1k?#XRnH8! + &K;d0:Hh5S0Sn"J@sfKQR\'((js2$a@'5n>q)#Yn76#*^PGn+W([h\c[I6!ci&"!#bl'&e" + c/#Q]YnJHXEl<(Ct3-m[LW@)NCG^&oK+!\HGFdT\9N[j;[9/Fq=C'%\_`gF`uc)50S5,^/c + frZ76n!E/V]!)`rm728`''eq9ScYco@7kkTE0i:9hEsj_M:G>XC1.A5;d9?;90%d@a(/Ki( + P#_O2i.Lb.Q-"2c6Zice06FjVr-u3 + )A\*$hA29q&E3(s)H9A9EoHudW"a-i\RJ4WRW%D&Ah*dSl,";dJ5lOr;oeG+:_*Ilq$4f&H + d%a+Y"?3kC!J,>p8BiGu.82&5q\K4!P/Z"nFSV-b0FtNBdhR#4@Z=PR@_6Sq>CA/VEX(WcJ + X?it+o,'nI&/BN,+4Qp7mn![/0c^pb_#6;K@aI?YHujPh7IO;-YM&A[31i-km'&F;5-7mJ* + rJ.r@kN!FH(%KYi3JO]0-YX_U!6"J7PT[X?#^&jrO!PtEC_&#MHT0a6e89C\#'W4RQX%O$' + %3VSTdd8[U3:k(1(h$kfO]aAZ-VX+V'/\;peCcQt',6\e:/oqMeBq%@eCYEafTBM81G\a32 + `#M<(:-4@n!+\f8Mnmh2".6NZVr_u^>&6b(t2m)1M",hB`(NB2Y!T0F*.?Kf2N?g)_^(fPD + 3s2MVo)_3=GR$F^$k]!rpUIiMR>MC!>S!h]>T,.FY[*OmcAnm2o.I_m@/>F2m;+!hb\_4Va + :cPL_Hd(<)Kp">f%_;Bk9+9*J8_"gtcs;DcG"366,'#-pN[N0QZV7mNc:'nQBjaTEnt<&`E(!+c[8dZX67?6fCQI@+l#S + S-Uh`"(24@P*c=%$EE#m8>*7*JN=?a:)*CFAQA>5FsCS,?Y,O5A_(F)(2%6t=%@g37Xu1C! + &8-9^@Pc=BLI"Oi2d8"btgdG:A!"LQBmQ[+&8,qC/jA2G+28CSJ&Ap1=Z1sNK#_p^GD'J264&A0p`d/mo*$&)E.=.Ps/sSGi#cUh_Tf?[7kHS%li + *=rs>kKNX]"O!BJ\CE_[\7okRV"X&h*^*f`0\n/T>%^E9k(WqDQ[Et(L]9= + !?)HD4pr,9#[iHVON[hZ$3Ogd"FrN8(VC"RAd)S<#XE8<5f=!`dLG06'gW`[Tp3dQTmmFIl + Tm]NJLh>*1_4!>bWqpmi-#AKBE`.("H*`lTL&`G/IoQE"CO1jp,*077q=*2#M&ee!;$\nF: + t8Ad1$h9p1o?698+2RKfDk^=BgKH?\N0@'B&j!8-BBpgLpWee04Gcpj,F1&chtD*bqsUm;'mT+G1r0<+)WfoNCcu + bO1dGo09-X8Dc:VfQMZ]m6T;.;<-JXJ[gO/,'305mEOB'OXqt,;TfQ!SEORS$o<14L2cm.D + *tTN9'>lpK8>"L.=Y^>WeXGUB+-[=&Fn-Nl261n@-X/>XG3VG=.S3kVB3\C;aS!^?=,D0GL + NrQQGr=9#3D59dO]]e\T\Ti2tb"2Q,e*k + =^9R2QIXBL3ieTkQ[*)aH+BKHRA6gnV5sI1X4kUq)Tf'abH2]/X]iuOK`2eH1/W^H#.iue< + e),Mmph=;7)12P5U%8c]OojUS41boRGFF%eX[WiZ!.j$q[JFp"gS_KSsZmn\aQ-7Tq=1ON& + ;?VPnLV^K:jPSD>Qe5[cO0n%[E;1h,r\73Z2j^*E1@bU+>&oF+L'p$CSF(D&\QCjBVG9LB'otJ]1HbMSTdOn)'q^^L>p6Q5W%;MEn53C + d=/d'?WD8.3\RB]`NHc=VQM6'sHEEN:H+t9(QhoI=HIKWus1d=k^Usfe*!sDc$c*en_-@CA + i0"=1@Rji__HQ8p5``e#FqVt>??tS8Ho\F>O\FLA`*4%rHQ0mC5>QYeZ8DNcS6Lnu0?+jBZ + R^H?q`Ba:(UNn\\[tFN)bZj9:K!=?NV;nMPqu?c?]Ma-CDcZ;4)5d>Di_;%UFE+MU+nZ0*M + _ACUbUBY)&732:Qa1M=.3:&l05&;Kp'e1VNM$`q)>2be%;&m]@5N4j"X`+Kg(H4PratLot? + XBG3HJJ^)E\i)sMU:mAd^DH+WgeHkX>e*RYrIdTmsCI3[120@JZ4d_.P76)54r)6Y<)!Za` + OI7qGEEYf`o7pn4W+>+_o^T]LO`Ed,H>Z%c\*Q5X417V.kr'A;MIu=+NfNIOGfXm(;oXHtA + T=f#X;c0nbT?"X?NAW)Vgk3!q'?IhC[Fj_^gL\Kt^Q21H[bRW;>BLn_WkZ/pi;8,bH\]dg. + AW%3Ob/.\*6"'\a`G,>F8B1/be#39qb)k`SQ6e34XsHckM1CR&3LWBC.+'E+4UH[#:"WdPE:#e+*[J:HAD1g&R@^k^^^(Tc1rE!eDWjlKQ[FT.KY1d)l + A8QJ#hd_YNW]oq`h@_?b?3'jm&J=l:'O]^9&0bZ&BRp;&jN-T"E$=X4gKR[,/pYhTa,f']$ + LRaV?o)Xlm-%!<6uCTMS,T-3X[-_I)]o6ol7tA4'gCkcA)>%:dDMmF^3?4!0NgUU!AURSbA + \[huE]CpI>$QqZ.q\Z9ah2/UX:9K2idI6aFOBIkV2)7<#35Nt#E%6C09Vpq^UQ37,g=KC"g + Q*pR"^=f]u/9WoBrNU"Bg+?@[6MB:9CYUOd/#)UrG\c0>Q^@SPGCREd0N_A]lMl[Xia;(X6 + WJ2eaQ,*Z>bb;fBcl3/V?W/[G:1/>M\=2B) + cQ6G^M+k&S8i?[!@&Nqa"`d?f:H1r'!m8\c0nHHG)MW>.;uMB7. + FSp1gr$q"t&?N7D/!s1cVMR/as`)-$dT-=d\l_,+Ya#=:+\-O<&#PUd//nC0$"6SRjc%n&, + T7h`=G?J,H07ESAqZM8(#h?-V74CNA!n&'X]_h)UrUdF&Wkh5>kH2+ + :SFe[8C_<`SgX@p2nJ[:`^m2atGoh3W-%G2j%i_hR0+q8-b'ps(URA%:YcNL?0J6uZ\)1&mEu)'P?2AVUIqQt2RbYNPe45KENq`5VeG&.0T^-X[;_7dL@V0I?rNd7FFJ*"N#L-j5'O + a>6IM^k3!n%4i>V##slsk=,PC:LrHeD`FAET@]XH%1$HWu)' + !:#0r:)RQ=A%jP\uKiZ(*U^EubNp$]bES4V8Hb+'KA.BZ,b'aAaX+eBAB;sF!d>`Rccfr%AC+r%ZSr]Z[V17404k<`90i>XR2RR\3!3,D&tWbmp6=g4gTk& + c84bUhGO5sL^J8I=kN`re,>j/`ck3=iB8QQoq)oSI8=SQ5%j6ZT@='q:c\PABGC`EPTGNTR + MeF>Yl,G+U_j/@5m/;IAYTehNBj377mU,sUf&8:KEE8W"A*b6fl&niY=t>Pp.G*k`HP8TALO*jn[t$9t"2fB4Pkr[PW.'g;f6OPhL + JYUl6Z2#C]@Vf*hE/Q!0, + S9sBd4X%:gF,T>d'bRK\ksANa/?$i,S\8276"q;CG:s`'=ZD=q9>8J`*-6&D@\gCpu)g.ommZ4jR7Fp61@;t%ef$bEYh+bGnVb\nje/(VP:2/1V??OnH# + T!8D/+'A0G-0=6ZWV&AB<66Q\(99r>3ikAY;OnEfGacu,SA``K^]`Y5X,>uJE*tT(=.P?%r + #jemKu6.+s-o8J`6MI[rBn9g_aO@D>hMbE8KI+R!WpRqLsb"DBt#7`tZHs;8"Gk/&g/L-=[ + M\<>YmAi"]S#n2;XqZ0"9kZ'M_,3n50)2/UG1-](neQpU,I:ZE2SUY!ZePq3,@C,Qlj'.XT + [CeeEDH&>;X8NFsu:2@/sH+@(JhVBuIT.W$WSS)F/T"4QC[:VJNkSmk_3]!/M`eN73X)W#6 + CS\FoqVY'W/(JaT`L>C!h!*"MVb]b,S6%o=C+H>G2*Y`c>5P^D*nRXnB5d1na0t_+iS?S^F + mg@.]m"AihL"^<'g[L#"q9SC5[RCHUGES?'qu^H>- + qBDlo7pZPYiU)>t<0D$cj5QdjbYgo9Mft)O/NVV'\PGtFT#*);k^k8_k6/A\ADEtP4/qaYF + )KInqZDUmmVea&`'>HgX^d0dh_)r@H6l!O;(;WZ\_.4BKF;Vii)3G22"L((?$m!!C3Wuf3] + OW.X82W^Z*g;rm!$D;s5["=IdDBtO_K4!br$a'_-pk8g6Gj+KJ/q3.*i`F,@V0k;$9*138F7e*8pYlF@#`1=S^n1G0 + U$7\ks,XBY3N/4mT3:-!u#ZYE@DF.:,.pUS^R5FbQVbPNkITF>'&ZhpGdK0d(/b.:0f^_5D + ;.n2RQ"MdD01o(?sHjHl*XYf)G[.*UsqbWju%Bc1!/u%\tqm=2H)+J]d`dDCP]MBu&:j7qLF9Fj+eMN]F1pH.3j9S3EgVBTm]'Nlf'2cb3%]*!A0HaIf3B@6: + .Y;`k0bf/Z,gFQ_cH%Q_biC.m6nZ^%01,:bbsU*l]SO7kf#'n5_c%Go`7_X'NA8pBaRhL1Z + EqX]42]R1X*d+@Pa&I`Mo6-rn=9(Z@LZJitH#s-f$cidafno),K[Q> + aF97+":I(;ob^X.*-d"K84N$N5:Ma'k"KIGSR'W+?Bal#ec6%X'Q\ + d[!erC3"XP#5gsT&b)c.ePAi2^Y78g'E?C3=?9uo:S\*B@V-s.WZ>o[\ueN+1^UbSZpkc35h,ro,e$b?" + ]Y_,eV0OH`LYP-SlsU8g'+,&'q\YUTYZ_5"mA^4!"Aq0F2?K/L#o8`or4/UA^Ru< + O#o-q;8Te?cDm!`VA2!D-M_o8;1qZ/cj4F]APO">Ki&DI#ZCi^u0=jf58B&[$&]L/Aq)Ac8 + @60HG.qR.`=G:gncd]Zq"B?JB?>"1Gq(lXZ*eSo[B)R7.&Gq7&=0_VGlRJ;tTb4"K>1>6.k + Kk5TSS$c&l;LWBaaQ=[T7C;!H.SZecF`^r0\]eX>8_Rm@5Eil17nea6hk/n3Ziis%0s(,'/ + B[f]/N!]:^DAi5@X2W$DOPP&4:QkEo*'nUBL!W[ZS&UYcS[U\pYhpt'M^-Q,O6+;P`A4?K% + IK*hh\P6B3>G\rBQ+UKo8tsl?jMc4B\gbm#X[,KrV``K;P35[1)jQ0eJIEA'^i + >mEdI%9!AS]%>iiDVSgrQirPL>dbF@?lZ-URpGM>%,=AcW,q8/?,b:6L=!=K((:Gp&DLm5NGhQjRTP%otj99:t!'TjW2=gjH`^;k<*TA+dj + %F91eFa".[X%29\a-\MCW16fOA]pG%h!No-(W>C0Adb0pG,T;TM^>g2k#;7o[&MlFV/"K + ]C?7l[Zs2NRYi8?]CFtgtr[2=W[980jlZE<_VK4gfjm2WRn&G7nf"^\!rbpZmn-8p^&MsZI + 7g#ss_G(bsk6:3J#,#8o"!5IGQp?:s2=1>Nkgp4m]/BUhGr?k%rM/2D@R7r*YN?BJRLb'@85%hroMU`k3>_)h' + uE>AFaW/eRgt&^Wu2Q("d,1B3h*B-8$J(SWW4:CWcpek0pDI2#l(!h=#,TlX`cQ*r4Yc<,] + 5FF2n:PcN3Xh54dr%oA;h)oi-Y#*7cM>;-\3oG^B!p#BXrN#KKAd^IdpV!!Fu<"+Li$%o)] + J:@SQb?j9,@*n#$Y(@aT;nQ+/,7i#/j(M)D?6aY_CQ3jU1)cr?\ER("J7=8ZYS8nDAEX8+F + NoFdO2/3SPDGCr;Aer>.p$,I8=GE\b*2W)'e7Lo2?#\!:%dnB2XRcYcXj8`].Fi%=o5d@Wo + 2bN:55&bf3+@m,@\.U>jCI_]:\5GgF<>o)a*m]sIeVHDGNXI`j;\9c"TUdid9FM=W,?im8, + .*-LKB;lTFu@F4G.#ro`pHJs,H[6Nd0.%oc)S,02as:;rj?oF\^F1a&Y/O^FJs/8FIDgQBq + '=)Ki?qn\^:Pf3Lsb0D4\ngm'Vf$SCoj=^`sm\Q:%k,r^!QKka+jEee=GM`U9o_<*?"^VX5 + rf55E\pMTGh`f>-h2\Roo@\hGFN<%>KK1&l(,k+:(DRKSF-MKDA=sJ-_tJ]>BWL_G#kAYFj + XdpW7.q!:EQV%Ik,E@`cfA%AmQP2jB'hi0]nV%G8f>of5p7*[,(rdk4Z1J?ak*J.X.JobE' + Dbhtd;=qYUTqJ#E2DfDOf^;ZnM!)o/+3(]k-OKL0*DTF<\T%tDN,;)k@P/Jj@8@o9bsFbLn + @87?O!,Uffj,JDj5Vu)UO/XOYX'02<.@lKiM>#O&.P']Eb:hS9-"V-M\UerR?H&n&OpBse.2n7f19+7PdN$:<+ + `0anlfX?(18nEI1i6q-YFCdcZ=:DCd13I;Pak.G%=7PUEF?:HZcY8p,gq5dlBdKG%BaH@FB + >t<=2O!L@[XH=l%V9G\`R^/0Y.Mi?cW@Z55e@@1Io$kNk9QMc'J!srUd5=WS>hj%ZZUEh5: + .P,V36q-#Bg#7HPkRC;`LR'[RPY`e6;YXd#t5ahAK=43k(.#5dWCa'9:?$<,>D)aq4CA[o^ + Q9cG(%l=^9R$eTkMiV-S.N==HD:s-hGE(F(?5K1"HJ]O8om>S + ?c1ukHfF@_/i#!dEK&IP1'n]q^:)eoGfT%,3iV9Q%Mdi'f22pR_`]]=Aq!LG1jB4*&PN8-$ + 9pFb-?U[Z0_o#k-s`9QJ8h7gN*J-YM5fQ]'cXe(VT926Qa(K/DgTTE/ + `g:cuBLf9?":Y>@uN.*.K6j2bi`kbIEQC1uYtbl]1-"%YmC83k[&Qe%7b)`pgX3B3amgY?, + ;f]oT5Vq"GVcr/hMar<`3E[O'mgNd?>N*YYIti + EER2Vnr/npW+ajtSQ1`7^LYXJ0&uX`(bsNdE,A@LeO'Sl4<[\(SQ-*k@]U_>[T8Km'geaF$e=Su=E)MoX + AtG-)/M9DnE(cZ\:"j]"?hPprZO[3M%S_e8VkbD,Xf/5'4QVgMGZPd`>GQhij;jGZ#2*]aL + )go"CI*\>^d*T:`=qa7qj]ZKEqk=d%c86N`"#Y$OH)"A&N1t+I!FgiN+\t)ej-UQ9e%g]%A + 8rLFU^b7S#N[+_5DND=UE2[g=W4$_qrannl!1de)Ej<1ks`!6@c6$K0\rW&$%Od(%+1j'6A + `E[Je03G0cmjfu[h:2X@-PE.BQ'Z"mH7-Y6GMX"e>gi]d&Afan+m,iZ%G10.^++s(O:^#I@ + #RLPp3P=lY/'(%5NTD=mRlR']cpSC9&j:>?p0PAcH`r$HUY!Z`h_XMEknP@*`0U:`Hh3n%@ + 4%iFnNif5qgZs"!%.?jSEPQJJEd2n5cQ<,e3+\#n5]jopb8Qfe/)&JNDD#-5mW`E.>3S\=4 + 5W1$PgT3c,8uYTtj\(8lP/:;Rf,ZFhSY7r^jigmqGqUa; + ^9p^YdG!9,FPSLfR80L0S_9?Hh0dSY:=/aILMq+n[+0BlD#V'dNAo]>iH[@6#e]ELF5DlA\ + l6$$cO6j:kEFepB\n,Bf>Bf@"T"b:Z@G.iba%(lp4"nB0Mnh*rhs](U$rPJVjm'BYJPpCYX + XVNqDh%orW,p(Oq,oR7tKo)/;3Z,QseQP9sB:-V\%,me[i(eYf7Ks=Bc8DS3)Jpp*%?kNt: + 5T7>\8r-cK2c(1N5.MXc'[)FC)d!`1u`CP,3[U!/!rg=;8T#!jGBgEHWfNK-R\2ZNm.1^4! + S2p@D(YZ:uY=:D#V\[qKX`Z=diGPbG/Z;8pemYlggR\cp>O"QO)Su2`OU1^kM9@3G&El*](^C[$j1`Drau5h)$8:LTX+S0DAcrAAQoD+GfLHW3I?c + u>fW4VoUN3IU/5I49q1`u.5X/j3V(c:Sb'<>djRiP>jXc+oa[eIDdr(Z9i4'c"6.k%Nq'j>AZ\$_l.5=d>kZ[nY + <.FM:7D:fG?otnqo6amcruP*eM4D007gr'%[FnE;me<&f@sCtD=+td]XP?AnfCTKH!_fXr2 + 5qQhIk,Z^HL_m#G(KNJ'h4]j=l8n@0saTKk->b,<^ebp0Q&eN=Th14iaZMJ2@H1Yrj7!%ib + :KCZ9?kMt$^)anZp3OF5nL-hBPIDq8!RB&B`5/A?,LS@[&0?bGIqUq#Qtp=/\n!RC + eu.YZY1B9Vgt-9Y"(^9V9Bt.^m+=iGmj^*]DY2VAQGBVkn4HI0,tcm0%YZL;VG4AdKmLULC + tl-`#B*6,OF7@qIg<3/#]PJ'Y_*5NM$X`Q#r%bRD;5ue4qOAV"+W#u6:LtEYS>Hkp5lfmWm + D`)^]j5H$GoC@i-kns]GRI(q!cr3:tk_ah[V8"q9PH(nETj(nDo37qQHjp&H&iXig$^Wc>I + %el"\O?_NulkF)T9Dn']QXHMrg1FYD\:/JOX.0Xkm=km\iHO7*#s])Ribl;.9Vm],QV"J(= + HlVJ#$cFUW#^B&5i!RMbOX31Q^dRqk;T^d9Y:bk]si1Z5p:9Ng5E($H485V'9fhVJL0*$Xo + >4@V[_,+VGOfVUp(>CN^gSL#=Ya$0t]c8_s*"g42;99gJboJO^*HBIIOjtJNVB00!'L:F]! + 0UrqPmi6U!`A_;j9H(@!?`fO*`<7MndQ[-7KI8d&:j$T@I+qCIh#<6%FQ[LDjLrs0b2VcqG + ?bDYGi&8<=eQ#&2=C7:1Fj_%1t<+2UN;dOW+o$d*Q^_&oN75YMeWJjV)9J'bE!?30bg + l`dr5;X&Q50;ZKlL6HVb:i#Jh0K],$=?m&a_gX_/F_Ynq3-(U8G&d=TuK$Q+aJfMR[]d`hp + RXlV6FSO&8YG\[bHLq)mS3PZ/H_p!ADn(0Co?$ZO&>W#;J"D0iJZ= + Ac*8$$k;,U!oPFL!Ytqq0H3qJ+B(9VW8P^ir"-2&!-:^e_A!,\5Qd]b'4Dn*dNpdA.a.B=% + eF.'5QZl_r<2QKqCSeI/ngM=2oe`<$Oo.a`:Lo!Ie)])tsI"-UPQ/+3 + Tnm0%9e"L48fd@2_L="V`%/h[G6''?%\Y/.BKgJ[Tso(b#I0ennM1"8!HPW(t:5st956e"[ + q1eU5K)B$@`&KlT.?ro2%7LGAj*cu*Pe87\=i"n4l!jm5B!Yu$3>FG-53("DLFQY<9R<3'(=oq0BTbWG + 3MT%-`;GAo>'WEr@!e(4Kbk:&.'):!7]7f!@d[:NChBianL=#G + 8)'n'.sS/[#e&n&?Y@"074Hm%YB!2133ZU%(rTirdn*7^tneZDsu7L4Nh#2tFSiB^%X_+D# + %>]mqhPas&,d7P+L,S.(;dR>FFLEs0&37/6s1qt>taX,HW-8`keF/:4C%49TT3u$L49d;`^ + 4Zgr]-o8b:/u[7+p:>.H:\'c6;O]h7%:_3Q;,M036'N6e/ZVlf*sT!W_G^,@[[BLe38S]K7CNZIh[_-8e5S<_.!>`dliY2Y@EX%).'B(2Ye\te%iGJ4V8bVCS1-W!enQi + Tr8ujgAo79P7ioTgE?@uZmp$H*"R9rTg&_6\(&L1Zq.8=i$:"Le:Fq%2GkrZ!t:@J!@;,(L + 96"3k@.5T@J17:sZSNX9O]Ccg'PN=9lh`e*g54Ns4Z$e.Q/M%Kh(p_7pD2SO[d0/$.n+77G + o%sVs%.MagB+G_0k[-Z\F_OmGH?Z33[[DHnWI"Cnb9M + >pUc?n_2J*-MqH.136R8*c&F-eN1j&`JKPI'L^(Y!Ic;%D3:%D?nUmJoA2Oe`VPT=)dbrK1@qSPfXOG@ + tYU"KJ%n1Br-K^WJ("$l*iU*B8p?O5eh$a"VTR%Gbl=&Q&<&=B2g7)(4D/[d>[nONEOl(3SBu!h4?X]Soc + _-+_VM(q@%nP134t-Jdrrs!XtM#*Vp)'RLPqPJ/d2V#eCnG_F]5"6kB@Z%0F/H#Q^DDpR=B>Q;\h?4;",EHZDiVgAHKE1LcM#Lp.E&DNLH(2APV]_o$OAV_pV/fY^K^6C + Hi:^l!IKC#'nID<^uPm]KEe)O!+p#PU(dWu+;"Gr+$;!Wr%5eJJ0*K&&=bbt!#`VmDur9I! + T:*eTSsMW#S-s/(BIC?"$O#6dBqOB`ba25>"-I"Z)!]cUq:!B3BqJoIW7D!qHYiar%D]gg+Z'c2qA=ce + '>_g/6#n(&A_9+^fQ\ID4EZj8H%M@fflXpK=>?&)`God:aR4>_I$H%kR:(E5KTtGKedooLH + 'Z()@Cqc7H^U4J\#A)nl^2N"3iRY5:;e-hqMZTuYF@.k'21(`C + YlN&I/T7-H`JbNN*pH + ON0+U@`@NY6i%&mq"$^QJ?lQ22Sg&4'8.?`EnZX8;k(q`hXg+2.Q$YS&)\'jnrXT?$l'-2` + 8AqS1AT^W6s.hu4cDrZC>&?f2P=n,GW>r.+hIJQ*`u"8]#$"G+Bt&9p53#n0?lTnrK&9iVg + \`Sp>cltSM@NQ7g!pt@_&Wffe]W;0,i9dQ"Hji=ngaW34-9[u\ePj3:8T#mnOl1-`f7A%sU + kF'RD),-kOC%WBJW>Kl[ZIHNf;E<3[Z!QC@p/f53t2/b[[o_WbGhh3CO3^ + $>39^L(n,9F_LFK#4OlpiKOE(Pk*3Z3ZtL9-]Kc*0jZS7UZt#b\"4"r;"AhUdSq))I6C9=/ + jbdp"LL)cXF36D0HqIf%YJ\hX:-n*X<4*`SkgHS:4NeRE4Q3q>B*):SlV*p%k5>[qE8+mrl + >ah[b4Ujmm]pI\/R"dHgORINHh%^ZY.`IK&,rYOD#ZrBILgrrG@2,Qe+U"[NPV'-klK"q8F + pj=cT>335%H!_BaFLS@%U(^6TU49g@%8-'AlnO-:j7<)jB(Idk-"W7XZTq`PKn\fJh7rdU: + +%Pl)K-__Fi+,g.u;o0h%b9 + ljk"2bimsj#KkUV!C*;o>L5`:NPUo5>UnT"$ob*,n!(:oL0E^;06@g7oAo4,=tdQ3"\dleA + 8Ca8[RS@:K-oi6W$g*+oZjgjTSO%eT@Wl=&npI@p)iTV/':9ou1uX=*D@_q*F81@+f-rQY#uH3.9q$%Ve,giJqpYkBRhiT4e!)K:ut)-B"X4qF6[FBmO>)& + 8C/oU0)/CSu\7DqSokDCN90tY8,dq\2]6?+:GMi]118WD0omL[r)"sj10&SVX&88?jK]E$s + V[T^DgD^\\W64-O[b^gd."?')Z@91^BP$+3AEUV^or2r5UVo&_fbp5A(c#105=s>jX5+^&=d^9hZs,\=lBIs+=#okq_/E)q0ltGkB[&^UZm)RVlT! + *r\u_$"WlTGmg7kO_sn7e+ZK!/^HoL)AnU"6pMboo~>Q +q 0 0 829 466 rectclip +% Fallback Image: x=581, y=1, w=247, h=50 res=300dpi size=645810 +[ 0.24 0 0 0.24 581 414.268497 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1030 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J3Vsg3$]7K#D>EP:q1$o*=mro@5#]rqCH?>"_0e'jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/m9;Oh/Bf.DHAorf'R[o?=;%DiUZmb^f`?n4=1jRLW!YA=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&t\!!.11>pAH*rR>/]>.nOW?*DmcoafT5e;Ld:DKHi2k4a;=i,EpG4sq/+a5h8d + >eXi0S^6M<"8t&(=;fBr>1:k;u`_ + 54U?]?gri!Pj`Ckl[&%8i-mV9b7XC"9A4I*s\P8\h/sfYj+[i,WS%`'1J$eTnX + *gRO^)*+7F?sP@E!5^@2m56To"%GVDF0@76WGe@qE)6;e$[gCQi<6Al"\'3E^)pj]R.J;Ak1cAX[4VY&U + #pV&jcOQ?i"4;&PfDMkXn=:6D7d>%X-uR>$bhW9Rr[TrDdqa^_<9,l*k2lh]bWYSJMX^U%a + R@:?KZHWJh/m6pC&[1f$t@C[-MY4?1CHg*K'Du9'lKV!aT/FiXKXmINEh*G"cP;,B5LX_f5?63uI5b>AFlcaVfFjaQ@G-ooWE%buL1+%QpYH.!i + :r"',\@cT5Ek/R?>l7pPpDEQknO>PQrJC'Vlf7\rnk.SqnEG5n.ar'RI^:U2pqXol;N.=VsXGI^SsgL`af%!0DYZ6+\ErO$j_CD0N(/l)Zq4)&V5N\$,F@h"[s[N'Q@ + @fN6"'ci8c&6d#&B$3)']f6mRI_Se],0=UX210nljW(dgi*%:R16LJZ\G6^W31iUfEl&Ioo + $=G7?],Y*\g-72-:SJ'q09o9NX.7I@6(#l#kMU?#g7C^#HUBFk]0n(iH=NMH@*,1AZW\d2T + h&BjWClVB,3QE!$*TOLU).JChMm4fT7EC](@[KK:nK],B#_*9!<&OnfO:sqL1BjG5%fjrj! + _X-9!'spd%=('eAH\]k;N-2[@RklC1oU)sUHqd265N]'"Q_WPWc.TK`a11[^o7h81.EOTjN8p + HRhK4fmgH.oUruTY/H-[r(4U6B*T-Mp8cdD_T7s+d(1]i5);c3f?ib0]]0t.!O1`S3P&5j/NhdW7!L%bY-p7*faV.oD21 + !Y[_n>E&,UWjXmNkCUJB]O985[O-,d>Q+DG*(S2i38j;K9@ESW^WFY9oF\sI)Mh93Gq`t6S + 0(Q._=42W2+i0H]=k@ + H0IcNA7jt(g-!ONN@>bZ09$F3=Kb8/;c[3aM>n8.jX2gO>$0R;&^Bb8G,&O7 + BE?ErKCh!qS'W6*H]_+P%?=s#"E4f:-i3]$6YaCM+fkSV>/b)S,#okH+G21Y.'pRL;FmnYe + &32Mf`V,_(e.D523%Nm\h&>3Sa41pjEQ+6,mqBtZU+2"mCSA,L$*4\Mb30XB2sW+bJ@<&=c + 7s4/+3=R/+d/2`a')B.OHlbQml`dY\T3s/mum=DNsG0&?(r+?AmRBs>>cQD1(DmN>*R2+g53:T>YB=a=e%>?dnD2j>H>bt6]XPoRh`K_DDr(6(pYQ#/g + ^AXO4q5>Q7g.dU+,03U)8l5n&`LAuLiH09`H]E>j'5fKELo83q"Grm*060O?:DuH-R\"\Vj + ;@`?-;tm/`n&!R<)\Ec2"7sk1o-kF2!(5a'cRoUBF9s&L\ZP(hO+@&/Tq+4oijTLPlUB`2p!>kRb*a.LJSZlkJOCm(m'sI@k^1M@gY4&#_sUG2\kH7f1$3\*RN:aBbqAl + O-G8%$;gN=UrsRdu4Qc04Q?@H*g#H^ent$NLb"l@r5H!TBo+V_\:hgm2k[1$X<(#)28hWVH + hl3AWY*)[I>tSfqZ1]6UR8Y2E9msu>Vj-23<"FCQrWPdd+>&^ + c_:-Z;r^7>/%slXoE:KrQ=;0Ig()#^Q-(]5L!Ye7>J-k.`g9N/c4n + 0LfY*4)"KUPciPN>@<52*'"fqY,X:W2:AAHD*#-8`[9H_4!FMa`a_[q.3bWVhiR+Z>>g.e=Wm@;oCAD6FRgL6Qec* + lC>Ep'TJ(U86%mXH&cI2F\q(ibNP;3;^#P8NhT)6oSInWt.KSJkA!)KDktZ*>40[2SSE)_n + lBn\#ns],Y\_*^PGn+`n?ip^\La$6Wd\i'IDuj=1-=*h_[;iSP_0[i]Vc+0CnJ_41cD"=aC + P"[H#u!;m@YPSh5m+//1l:>mBLhX0armV_%Pmd25JT-J7H,DhfVcN>FG%.KENnW+>/cPH'A + <1WJ'nu;((an6u61F?i0o;W*Dl2mZ_5poH!oP[4kdX\*R<26HGor:G/l;[ITP4kna.BdkCm + s+SFI]+oKW+"WZC6'kpT(qc7q.d#^c^q0m_Q0lAqNB"&lIRU;gAB=NY><$=9)U@m!3f8IYm + V&H0AQNPjSfN>ribJ`b-I'V[Cn?a:++in?U.)]#i=0$r!K+OO4K\\IFM8406g=bgR&@isq%6%a=;N + 0_itQrm3A#4g>A+D&!W0'%1b1&Zm\unKef&-PF["5HS3)/RK1=3>:pO5co;GO\XP'MV.H#6 + *XW0&R?/,B\P7Q(Cdtm0m=TLaW+8;6aSj@oJ]/p4[`787!)-kZr'5U= + N^C7[.IRZ+(e4EYC`$&&?VV!+l5h$im#j!gJWV[#d1crCM*u46+*d<1keX#Ur#0"FrrPi;* + smW#?'",#6renfo?c`?OQ&R2!mj[*\8uU7-!$9s4]h1.q'#HR/7J,q7G@mi_1>/LC&`-;Q> + eC'uC"4S2NpU,Fn4K_:C5.*>13:Lbi=,c3&.;R3+n&uee:Gq0Uhq%Z%D&"+s:TW:ka + qQe>Hc`[.CeLcinqm,#TDl]j60+^/ZX!n0'>0Jrq'70D"+"A@8JP>;>ilNJtF22h2dn6n42@_n>_2Y1bjPB + @+lI0_WY#]kU9F,+*YNRnIfp8e+YhZ"nNjCUUQbp:4uT + ]kf:=*T;G\iBRqSed3I@"tlPK6Bh]*j;M(`8[e#(ioV%XoE7)I.,YV,:<3eDIMZF5+3BX;" + B(0FV@Vp(K&Lc.5,<_Df8NlF&L/%.X-:808V2/P>fo>q)(Mj'h9fnUK#j54o!RX1qk + [6Nd"q^%K3!28m3"=#G&)"n5kW=:niFC;J8*MLo[G8YC"a-/81ERH]4'\/F,+``h)EjXXt1.tBJ;'aaV-'ptG'`3!ZrH.UEFW#" + gQW[.JHVj9c.!^1]C-@q!)g8V4G4M2J2(<`l0))b2GS73)19i?TV(;;#q6ItbElfl_:G!*` + H8Sh%ojKHCjSZE"HSh.#JZX&+sf*9kF?.ItA3$C5gn"Y+ZUUBomo9!Dt3FGko$MFoiB^GM/b + mJpn;>FUD6n1B@sCS(q#8Y/SOOfN`b8#R_>d`_5qstXLcBPg<$GmeZCmcXW$#tHJbj5RNJ5 + f'd76'!:U7:BE_k*"ZlQ!)Zc&t"WsFpYbWBE!f.#q=p9ID'>ZYrWRI'!2mR]dPPZqP3CRsi + ..?t)Z`0ph(GV1Z7?t7OQ5<$]q.H#$"grAl-[T-cFV+3@Is+_JQu\=V[rIf4FXdoAGIr9%2 + )?`44"FBhGhK(+o#&!uZ("gaS%6.WZ+%=u\&2;chgC]EI' + JF,%8K4a;$2g4;E + ^I6Mt655L7MX[T3KO,5ocWQf@\pn9tlV*TF.6.UZT%ATs\.B@^*XrO"8>bS;_V9/Y5aulZG + r2n.)X35mqbFGu`]YN>hS.qY0$\4HHTEkO*"t2=(&3S_iI,V;Wj?Pic+(G-YSCPknJKUU`' + k'oQ[gaN#.C*/.PMH[fpEe3I7C.1i:)>^n)b"h2=*(ZOQ2+ocM=[$;<@57pFZFT214>LUA% + $`"Qr8tnf59&"GF9>5pKN,kqh(=RK:7"B\J_H:ogsG@RI\^s + p;_n7jZ0O)Yj43k8.d=$2>g#rG'cO"Oi[N)BV&LfPV:5ZA]rqEbW]h)IVn73E + T_dhkIu*%hKe;`KoCSg'u;-=^j7q"*%1f0l(P`Rr9*>i>TCJV#C`a&1opgg8e`F4+//,kf, + s?@0C2sTm!CPTq_7La>q.k(eiB@I?"^[NMc:E[f+Q2)ZHiT/E+m3Vuu_rA+3OIf&S`47c[_8\^Hdpp^K4\-^u6e(qYGU7*[j>SEki + lNjhN_EY/T'9.WJ7ANV$hmbg0h>sGg'B"$M@=169qH_$?+iKmFiHf:Y3cDu^D5Vbeico`Z> + \C!;JZ-/@5>=Ti%4.MBUB?+a+Nl/Y.AI<*1_qb>KM!I&BIk?)LqEVX(llWOnd,sn6I/^tg! + Y.>U/\7Z9:=68PPprORSBZcH(qHc4_@J-MPpCWLI._4kgZ%Q/s/i(;ed$CPrbmAWmp=aR`0 + 0J?*.2/Q2;;Na^DGWm.OXPY17Brg[1=8FiNZe]3MNU>Ku$tID0(WGT6>a!JA9uE>VAM*uBd + RA+A4lDGMF?a^oN2I+DL^REjsdg)U?ZWiAQ'80_LHY?A!ZC\A\f?or&+gHYEOS_!7T^#*c' + *$,n,M8eLpq&C_'/2>E&F8?gEB2&_b5X&Lm#Q*WKgO".W&"`fZ_rL?6Hp-?LDq47KHi8F9G + K\t1H?T;VT=a.XrI;SCqt>M43o; + Mgi:ZHr;AlWB5tmSd*sNDG0n`E'QDILJ_N\cVZdf-SWpfCJ";>m2bqhdnc![#eJ1YQe?qTn + DU_Zl<.CH==k%=hopoYtgJ"8IIH!T%!eTYDl2B@Bk)^kTFAD%_uRVZ+bH-p8pp6i(5S + G'@qCYLbhXFHbkotbS%-EY%'SC]pPJ"3C4f65u:Fk19`=/!`]S;*1+Hs]t"pO=)/q8\"n=: + POK?oU0eK]-S>`+S&=IN=5QVKODH*&sq7AtSkL3K<(0!LSA,)&>f+jJH6RPaiUt;K%snra2 + &T)4##JjuKueS"&*aOFNbQp48k"3XLgrS%:M?UZj*`O/ADXI[Y-kVuETolC,/e@VWL-\$#% + o)?+L>4!)AH(g@d&D/EKcrbWK6G2]pu-i(&UDS*XM]PaSFq7djfmf7j,"nr8=n=2EA?D$Ap + (WQ!8EI#QE7_^Cq+6KhPb]@%82p<(Cs$CJm#,uWmoY!I`e9,?=rb?ShIpL\BrHDpiG28jh> + 9EgqH/85/!GgP:7\>?JGf?BUBmDmLQ0'3&&eX@Nk\:q"HDn,"m3=gS2NRb"0>`jJ?QrU=r9 + 99hQR>",E1A;1Dr@9:%3%P/1ge#`r-s4Jqu"5'q#DN/!J$7A"$e7H!FSS+JP\6/5kl,u@!! + $5&5EU!=>9t%e/*aVA>"kN1*=>g-Nked'-.&T$:E;n+-6s5Er8I*60]S'+u'o[r_ + &>@$4)Gq2a7&/EM,(s#:d9Bgam- + 7L(*,_BW&dT&3IFP`RPWcLsmmS>4+n=ZTjJWu$5;^dnLUm-b#Q&8^=WMM;I<-^LpIBSV*&UcLqU^-^+n/9M0Xd)=YV#',@-UEK%1!!6;#LiFK;1PHq2"b:#_ + 8As>0@Z\fmNRJT-\FB,"Dk)DhK>oRKP6)RTV4P!]T,AWO.DDh.+fGc[uCnkjS;Wcch>?c2e + FOdl[Wc6g!bC(l;Cj_@GOF1!mN3QFBBWf*ZpNiK-@R]nYTKj/^qe + uq/]]^Qs?@s?6/ZTNGrnfDphH*9[#]g<,o?F^OiQ.>67dPOIpd3F$,OH)M&=("Uhl*-5NpC + jIWr/(%;K=3ZJ=SP$iCitoH(/E%U>;9\?eX*UcB$OQV4_S[!UU1f"d99aOC$eot/;*S+$B[ + :i6U27b^bQs8F''$Vd-nH*f&'^/JT,%n/K]P`j'.F\#_<7)(Ll$J`<_rTDM?Prf2a2iS$<[ + e[G*r$C@=U4[XdBF&A4-V$-#@d),T+'6kQTq`r"^8A"kZ;aQ!(eS*l?bmJBF/p-9mN!?`XE + L66L$!m&m'Jl;f79^/N58S$L$7td@qEe7'FBh"d1$AcTW.7P`->:QljPEliu>0AHm/eq'k< + LC0r9@\#9em$%`D([B$\u,,VS1DW,c?u:uk=k96F1 + H]p"ppStAFuIWh217Xqj:L0s.FH8C,44YeJr<\JZ2 + O>77is(-`&j>QF9L!g2p=Nd>c=>;bCt'S)$'%c2GGfI5+Q5,tDtOFj&:7PuF4HU8gtD;S"o + q-AgTKUU@op8?Z>8\NYSI;VP;h6* + /3EAL]25-RRp!0XeZ7GOB5VSb]sr7Rj4kT;EGf(0\bB.eh4(S1U@!?WC\ + 1?HhIt(]U!Pipp^`K![..DZX%g+e&$qR'9>t_#:\;(`Sf4=hK/h[t7e_IeRIdj#2bBWJ5k_ + s__1NIisb%"Cf^+mM$?TqnI3L"j)GO#2!HikLq'SMSGod8U"'UC"n#f1OR8P)K.KI!jB_K' + Fi8]a2NnCLU'=E[YPq,c-HUop"HF\C_>Bo;d%\!Mua(ZRVhN3U%8`tK],A#st2EW"6gR0On + r-*NGO5"E^fX!c1TU:kT5<:H;[.qP6UQIoeWT/hadjugiJF$=6]=f4/SWYM$ddlI#ff/ccV + mmZim*,1itIAoDLo?!'gA^5P,"B$j+3a"$fDXKj[=OQjJ:f#.R4fNbAh*#%QFVp?\XNGYgR=ntS!"]i_O,VQ)FDIkn!PQik73_1O,)`Op!+VrU`'oMZV01DoqM4JU(UCf`aJ2n + \H:RG*N]"$A^k5!:Z^.18Bt"4ab%Y:3sL]lKJsA(\/AA3I#*1ooE%&u!#)9tg^)2#5>/,>k + In;_"R(*EgoV@og!Q==XHW25G7VZWeqJF>_h?2lX35/]/Cdh^DZJGo4>ZuB"puO^M@Q + (lhIPd"@rq'a;b@S,_8K;KLe4Z82Q&l9SUVnLIdA[8=YIE890Eo9S+B+!Gi=-GQl1`?:bP7 + aW-Hqn9.A_)+dM^NK>-2^`/%ODO(Zi_46r[<1:>((J2!)6'Rj1S=_e%*(gS,3>A2J$^.8(P + G:Mib#o?D4-l!k,pT-Gb*r7'j-q:bSP%$j_Q5:B,6LR9/[/2,Y[fGlHnghDZpQ@AYd:9lgb + fcjX"`gQPtSEZo,'PW5,K;a_u)d3KL3mDBX.HG]>V]WA4K%(8?uB=]DE + sDZ?)A=_SjnRVi/J^"OAji;)8&IVs0./4B/6u1rX7S4M*O.N**$;=u3P=4VV-LlTW;B5.s? + k1_W5G*8`SWfLA6]``#GcUYI=%B?j)f^%4u^r+\D7il8<&[6/:qjDDp.k=5X<`uF9NXE-8: + lpk"@^>sIOqsPTEGq67J2FMo#;STt>B0Ths^O'ZbqeONjM+F`GceOCRjS_mA!)NOo:\=cc% + 0Fls"&M\p:ft0.E)_Jj1BOKU!"].P=P!OA$jDn*8M(EKkhub@&-6>S:pARS82C>jI#!h"ao + !)DXI[JH(f>\td:F4g"-6Vi2`;Tj>Rdi:9N"?3/uXi$X2])rbDbH\t%?9:84bQAa,'=K5 + E_SdG44-[[qZpdpj,=H=]7"T?qS4P(R92*8tbfn>UlRbg.:pU!rVpi^U6chbpK=n9e9j37W + I+P]hfYt6q)D'UU/DMZOh&gF,S54!Jh1:f[Yoddh<8$hffm!PlKq(+B!XA&LUPm1&b"4 + p7h=']n!D08p&G#s*;=,hN-KmQmkJd&)DK?+=KE!=W3ec7RY"]^.Non-*-PD(0P6M8K4+@7hB;Cj*A6m]%=,g)Z(YOe8 + NUR[KPY#:1L\cnZu'q[N<0=jQX0T2bgCpc(;[bP4,7d^82`fbObIkL:D)]Yu(;nWc + ]1H)J*BZ($YXB]We*P&NUrL$kZqPfle0?;>1FLDQ_:2c$7,6'M9h86K]I1)6e?%-KZrU/JP + Z6Gh`Ig.hPu,.'`#8G8W9mMX]0FhN?HqP=4`+:MI9@Lffee*94geM6F5>,7:!R9`2"b3*AR + I\YCt9o_<\m`Fqr_!akf4u^277*f>k,5]\2F5#7uc@2[.W#+DP%pC^JoV_%<8hF`AY,F=2j + <'Xle6Kk?a/3?Cg99Xm'0"l<_0k?H`E9W`b=L_Mt$_H_+,f + T@/o*d]K08,G\Kh/Q1W,__eg+`MQ:]2C12jg4VFa;m4B`X%V51bO!!tjZ6T;5[+@W>7kLP, + XgBJCg0QGgLJ!&F@oM#9JW0LbhPtJXh2g]][WFk9eF+91f_dp9[.-Dc#t^>!f/u;`6G$9>\ + %*i4C29I1f\:R`XQ"_I,HH63)uNo'l?/dpGPR+^I@-`!lE">j2_,[$6$ojjm6PAmH$f9APQ9;?<:] + ==ClAPjaL?'J9M1d:P',rJS"CSG[4nZp#ZT!LTY)Oqg@%0&-2J9#,#THKob-qqqpf>At=l_ + mV?Eh!TqXhdI%*6rWQ1.VVl!_i-o]SNL<+Zr^t(uk?:2TbpZ`]/R[V4d_.3c*MUFt0%r`Ig + 0:RMNMk`@W5F=g_j2+mWDMQ=3&,Ni;kpa(m5s:N5pfl#`%/CWr'f*>+kp8D<$]U6"h8#97M + l"m`:bRG42su08jtfs@]M>"D+ufl_.)e-9nUb\Xt[O11/5,]>^]T3kT)NZAqSQU).B;HiO4KYp4 + q,BM)!gLkcI'Y"NP3"aPnd=kk69$<[CGj-Id+U9_O\)es,0kQ.t^Wg]f=oNPZRARQUS=9qI + k[?,jQ@aI:W8@qEet$CGQI7d;e`A#%f=[,bKk4Na`>JZK?#TO+;]TJ(5GL+B$SkB-I_QF1ik1-m8e'W51WU!Df'q+YC.S]U + o7oO-1/AlB1<5+G<]sCSaHM/DAINUodoMo/^$oNBABt6D5PL@2XZJ7g89Ji[(U@t9No\#e" + 5u@EuS<^Ls.dI>9$#9%C9`]5h$7N7"1-MDY:XHO475*19Qd>HS06.qO!8UkuCC$7fI+U:/p + BURAIf8XP(8t"(Nq'6G<,JJ"m6mNF + 2Y%ED@(1(XAWrm9&DmHL(2i8%W3SdB=k#4OF"arP8+72Kg.17A")Z,uX + !b"Ck5?E*m;6Y+fEcbf3Ec,]1Qe/3I/Zp" + fJ^d)7>K85(4fj.LMA"(DsMZjP5`dkdD(qF]cHoc+*i$Ji(hs<(>8;ngk\2Wo0p9d5i%FLJ05l>8 + ]`k\Sc6[7[iL.rY/FER*1?=Y+`#6_1=YF'@t>bB$_;EcYM=chAqfnTqk)mgVAX"j); + 2^YFf"2Br`h__cA:pDuC@ONB=7).t"#/E1n+Q3WZe4Y"],g&&K=ouK^\j[A0 + ;$b4o6VFJ`o"Ues)%]5^JD>lK`>n7H(WJnn>oFX/TrT.L-i'e`a0F]Q>XsqhW?O + mRq5@N>&lb&@TB;PY-d"2tV'hS7['47:o$RGN(6\%!02*#CB/1RkN6=%Ojcn@TRPSL2J!pP + "jZ6.XXQ3JZ1YJB`:T+#[=3qWr"$t9;EW0TJ'NGf8Q*a7,B2URSraqF[XY3=G'WG//;oWB0 + Aedi778d*-/Q#Hsutn%GMZDDl$2'oSaCg^(Z + "P/hcKLHJ)FE=$%Q%:!6cpC(g\)7iRiT&KoNU1s,,gg^+kT,=Pc&uj4O>sNLa2h3KiA^5#; + oG_(!lR-Khr!J*%W7@XF\LQ>`7*);]M-k;iNeTP)sbrd5c5/rucA)IA]+krO9]UZd#qKrX@ + *VQZNP]&%/(k%D,4YTN`Sm!*>n2TrGo>>EkY*W$o7HYqrKkIR;_VXAYH45&r5DnF]dG+\[C + #JkAL57D^SHr9.JECZWCa.6l=kK3;-V_--`LK?B`Sm:GCcZBCf4jkBW2c,!q-V%1fFa\19G + b"_6BA%H`q;L$$4i,D"GK5Jci-,DU#N?d'2iND4r`T_d+oZd-M>BcC1lERapTT\T8MWi#Uh + O2OQDKG1aI.Fta3^A)ar5r-IEFUPBs4Y*aX6##AQ=pD)96:=i"jR5e=;" + ksljYV%nA#N9,%2UfN%o9V`>IRTA>%nG1@T\8_=L'.@3MJ#`gkTuK2cY1@BUXtc52t+A8TT + U]8"\^iuD0CE=PZ*3G1V?*Ctj*`pqZ:VfF%'ZbgqGA4o'UVC3L!t?PU;+rZ5ekFCht9iie\nS,=O#P4@aL(N+:j$7^;n3UVq89,U,GkW + @ND^DF8P=1`0%A+Y;o:IfbQUP9==XJ]di)aj=d:`(>fiCt5%dS-Rl'8\e='<:Ce] + 2_25]HfS$R!N:)Ul-k5=>2;jq`%H.=rgAE[>ccn:"&;*:_^:J(c + ,^u-.66]>mu7V>'AjU>o]4fhKrX[K+A';EuWN)<&k6YPLdCc$RF[MI7]gKc(AXhp;]MH&E2 + ;gtRfns?i[&E.?"1NBQMS5XVSjsf>WVp8t$\]TNfW["3ZmZsYJHBdY7I8+k74kmS]D!pS`_ + 5bfZi4MA6nfBAg+Oto%4P-7tSr;+"cWTUsqU.p8A)495IITK;`qeLBkpIbM]KY'Aq[+kDYK + tJj5=b-BTB#_tef:n0,/jQ*4cM%)WWSRa,[K>-$A$1mbJ>D*ArJ8XK#T8K_;cnVbg[cBY`- + q4Qr.VW<%3Nu`YQGoD/A1!^4teL*Q+7lEfO_ELhBa3`+-2oi^r-dViE1Dpn\^7WUYknAGjE + 6\::ZW?U3"u"3,T/)N^*r]@";%`q\VNY9\6Z;<8P5f_6K8]RWKb%RVK%Jro%noo\f&-:nLj + Q"5i?9/:*S8Z=d"0Ht7,3$(qp),?bL1osK9\ld,^AM$kn'iL4W>g()FL8G_,<&5/0cKmoSk + ADQ`=I0_2HrZ$.Agt^/Y8:[g@>s`*29&bQ)In/;NcF2@a4ThlO5ZBS6U4QUZjqWeXHYQIAG + jR&5:A7k&!ZWQoE!l0#7ViBFk\2_U"OmLd<[[;ZR$L5\W8ODm+%IAmTlVFQT.ljjP-Y./C) + b_4?*31hPmi9cd^Lc;jS]i*0i[c%U,Pe%2D`G*D(B2*iD(/3X4;u + %Uh[2XMBDAuhqD1:T$0[eZB?.Eec`OWH9nfF=RJ + T(:Y7u6[bi^CKQENW@@4kou!guu.f@b&qcpJs!;S$.#1A_i=o<]47o,oks8G!rg7N*a4TNO + \&$j4D:;h`2RqdO"_(#s(XbaXEH#Z<^+;LR;Hpc%2\i#aC1T"nS6).b"Y6Q<6^F@8&,(e>= + 4PddCN8R>6jnmQg$m2lLiDV:5:5;OMSCaqAPsh\rH!^J$#[g8(]li/QCQWR&_:bXh6^.Vf`GVe6`'/] + fOM/lX$V4Uke!Dn!.+bI;pUAd^j3aeZJ/o\:.dk_0ql,p<(+ff[_t#=O!8?2KT>\5G)[-/Z + WP9q2^/OMf!=c:C24?U"iJ>E^K)#rf(OSh(@:[#^EgGNjilfhFNI4#`.@io!ls5n;e^]m+0B6(@b0=U(ItM%jMYPhTg*K&uO=Q00'\<=S(nQcOWGY%0EElFO4cOoaslrC5 + 2CrMY5,Xp$F+B$DR64CA//mX"SSDd;nu:R!D^VGq't7sDjhu0k5""OqF^ + =,D/gIai7G".fC?#r:U^/D'-!"Jf`KCqCEi\YRf*\kJoB-m_[-4#XFj6Sr[UZq:[&ok:>AY + 9s)IK,W+Q(qDMHLBh9ZLan.,OT#!*;2\>tZ6&0SN&,m'!u+jLGt#f+%>'T>jJN'Jlct:#;9a8f8#4>ORY]T!pAI"61k-0I#&;^['p?.H\:[W]=, + nUG[RgG+2QVaLB&>ol:5n.k(%C"M8&tLL-T.3a>.[$tbEjI7D[4;m!/(2$\'"D(1`YQBC!o + P=)nGm!j""f(B/^X/C%%[q!k:K.E$p_H5OY$()jUu1E&MjA!1B;'O"#,iP0ZkC+:C/[q(D> + %LDGN,F&N96e-DjR+1=&5XECU_F$Pa1J'e`QD%379"+Vl_@(.W8BO)39`E2u\p(N4]qd=1j + &K(ru]prudgZ'+q1Q6e+o)5;75&YSu[E[XY*q[3ED%=k[`$QEa[3R>QmK#&EW-N)%A>F[q[;8(;`QoQs+CLIbC&82QAuP`\ + MdP+&(r'0u["J-H!olC`l(*Rq8F1pUW-M+`@&9@Tm?!+lF#?ln:^Fce=3OY8Af5ql9m0XTX + io\WA_.&9c6:<_fScSkB74&;%p'G!PF9`jsn3A\g.d4H55&RA7(;`)g/(2Y^&'3A(/DYGl- + e+%]^$H2]OJG;X1plfUjYE:asR5\tjq@e>ricdA%VQZ2Df?^`P0=`#XR^$kI3\?UXEQ6]%b + -M]*Z]Wc9Z.T$h_&a&q[8H(oLeRgs\Z%ft/O\T@PVPP + kQi"!S#e5`dg]PRuf\'*L'M]MM6GnjEdW!$#FXL"F7<3k@$"HENR0h(l.*ddhFDD5JDb5+68QJ&IQ9Ab:6"F"hAiULPG;eM?1![&Zg!Q#dC!AX;+9fA%FeE8PCj(Xb:Bu[\tG,%g> + d8'9,:GX4k;^5)C4YjDC1?T0E.WJ:):Je5n;(l'/Dd?5B-W0>H;DE#GO`<8/1=S*k(Xm'J' + 5\Sa[eY&9),kl]m:0-JD#11+r"'=:d$%"70H + 9(?3opb#k:5V2SIC#?SGlM'JfrOmRacH^U-45F@0Ghc!]lG-M0ur[M]@VGmO*e7?EiaN%IL + W/Ojs<_o4hhQ(M=Gh&%E]n + Ael*^65-f3V&"r+2N_aA2Jj4pK@K."W]Ort6%jJCC:*GEo-K)k[njC2CXY(9;`AN[B/B8-' + pd;`m3RN[Ro/cU(:N7$[c]OalI9lf;l?Tnp@gKT=`:]?q9sb29'oV[$TO%?f9;7U.gL&i]5 + `tFOiRp[rESZ$$UIr>nd`m&heK),@.Yo"HFuudQp2R=!U[9j6PZLlfOc[Qf + $9]2KiYNqQrn;C^Rpm%Cfk7)\7ne0=.k[[;3OX0[bDR_-\qJKpN8-FB:!M\8Q3#]Dbl69(n + 2BVH&:1Hep+"eGc:J]s,.[*)`*h=Xc$63*jP=7E`'q>eTcYQI/S2?*HBn;R,\*fO,fKh=a9 + C.FST;sLkM[Y[g"WS%d=fb34EErsFpGEO87nge!6)j5/+6$J,P(QBZij`re + O6C%#'nkj],V3KD4PkVF0=0k:q$[^6OHkLJ9Q5c-6bpXM-QPM)\nrZD)Q'_YR)4e$: + &$^HpZ'ui?gEWu8-aEok(Vqd9oXk/\^^lnrS$(r]>=2'k$j.kt,WY=`eutV^fq*OnSss)pg + LGk?pg%\RrG6CHL6W0g\d<3g(Go1DFug@91KA<%2)Vre(GC+O\;P8"Nqdj`HS + n6e9&ZP63MXumMH:+A\jqYS8Yjh#V!WqqZ_+(=_V'1UVAC[C$T"ZH1HW!uNT2O1n`-O"7uk + 01=g.R&o@K%%g:%aQ^tAZ03LF3KHDRX,acY;^GM+,`dPF0hh*JN1iqgcB=ZAl + m.s]juOLj+kPW,`=Y9ke$Y&\5b#t(lh(U#d<-b%9be6?CH@@('O.2Fd2)_9!6--1E^aIJ=4 + gloglQcU58a]t:S5hl_IP=trgb/(j9)a9l[EQa^n!!-IB+\=GYCUH1ObhH^;)dCQle'4@`K + fmHj>Ao5+qmTVD'TEFF)?\;h#;doiT\GBu(jCbH(V+BCDHiWV2X-r?V4s*+U=q>$316Obr[ + #1fI$Y(e#'Y.F.;X539@o!7DH6uFL0H36Z^ + &.Vnh>MhAd1/eZh4D0%(CIL@(@Jk1U>^(U$H^C3,Qe19>h=Jc/TAQ44hnAdAh?2[W#N,Lg + R,R0E5sth51'4hu$GIV*7R/&E-3[^+9E:ps!1Pa+43e9'c.pXiI*q"]6aaO>cFh?.)fatEk + I3@acIcXQ2TsEnK?pY=\<1(,eK$L?h4^Ee\[=$"r=r77-b>R+U@geI0op8*SJdtMdho!q2\ + "5fOc(E[e*@IV359Ic_pm#SN4i53cdRkWD0'd3V]']m;ocYAIFd,'W-rfeR/'pP=caU>fQd + _Sg`P2iQ)]*G!rB%9ZjWp;,@,[SP.JGh*Nd6UbJ[4[`1=NYdSdg$(WBIV-/UAY**mo>dB9n + kY4PSp?/;]PD.7_4&BG(=8bJAu/*[5*F + 626tM!8n>+iN=ol=6!ogj1$]$s.c,k8'Sb=D153Nhk,UPS,&A^[pE@4h!_`-oeYD!L>Hic> + m'7a.5`+PTsZ>*;d[*N2i-91]"K8*Ss?'V.I";DUZ):>DSP+*pJ_V5#&Zc$Ni60][g%Z"@W + >KHV6q+u`Zn@46Y69>ON8\BL;\gB-BpaMnom0%=YQ2dAr_&@rQR9kc6l=h`;.YCTg.>78gP + C68#84LF@pC)roBre)H@]X;QX)B1gfZ3ja+OGBNK"f4"[$&ib-t^G,o2NST),!DDt)_Kq!; + !hChrRAbl>`^Yl=U2I6Uui5FU/m&0hA+-+EQff8o1Ik'=!Z6/K#k'K1^c`uBph'SUaVdjF` + D;B*CrnLuLG'bg<`JM+\2YT;Rqe_,'i:&^1p(nVGg>hb74LPbJT%`E"gFNU0=0L3AG1Z\Al + XR]e?U*9d5;/5E^%-K^]pcZ!^RA['*8s]C& + Vj5d#9+S(@Zu.C-rQ;VHY/3j?4!jnI]OI7#%4&Bgi3!rb4f@CfqPiEF`Tn`f[P*BU2N6FWA + G7q4Lj1ndL'm8>^rH+3=_7PpI1.q4B$,&q$ET8b/jO/'B=9S6!gRm("bkk%RsQ9TpI>0bnb + 'en.WTC&V]Z;hX6lc?$AY4AM*imleuQjSqG@1[PE4:m;\&6reNrO=F^/!jom:(JQ5+mXf + JY%:@p(]AQXj6bdlT:FBph&ol^HsFh`0-,L + G^9+6T?OJ"EQ\o6oLtjo9g<2spp$*V[hfHJo]A%u+LPD@Cl$(m4iSig?;fN+*$H.?0qOmQZ + _B3;,e&A9cBOP[sSj-\l9C?g`qNdnV*Em'VH#">Gs/bOA:;*kQ>5lLFlfX,OJ*d&-dC!hh51@!44-&0BDO[?DJ"@V! + ot9a?p@<6rkC3TMn6@=HEc>8Tho5,`q:ou[r/W#CEi3ora7C:c14Pk-_:il"]amEVFJiihc + LhET!q)1=/G5):LKW@'QAW#+c8BP?A,bF<3;Og(>9&C2QN%-9&0)ig`P + AE6_]A3N#1]*@PNB%b*F!-[HdBPJVSU&a5#US897FL3sMWm68SA,p+B-'2R"]0U0M&RbQW$#-9Z`\[%+utsUpABOX%l>[\MYaFbb()^JknuV8LW[WU6 + Q@W<6BJd.qN7pQ#alG9.=:#jg;F^'%1&YH$:lPjM.&1"B>nN=@eUfFCi!A$96jGbBIm3Kt^GC + p>AGnLN(b>`]0u$9'LDREg4Pbdl08KgtSEAY'n^H[nQ#A6`5(P[FMlZB2MruY]j(ngsiV85 + i(r7GQ^%b]L!n6lEeJTQ"B%h_FjE7kpq'*Ya7L^B3@fifI'[qm?BQF:S/LtI"aVl'`t3-+) + Q3PAOZA0$M[dHU\'$>^;.M#j^AP(!e:IB!Pj + -+OSPjD9^fnj,*'q'qjIFM5I'!s$Or"bCV>#J^Gg@>Zf%0SL8O_>$(t@6>iiE/kaO"8HGbS + .1#Z"2-cC8-'4S%?MXbjA`Yc!T#4So2VR4MO)!5Ml]QcX1n/o^ghIR!j*[ + 9;HrlGi:,u-Jm^Ls;qar*Dm:L'Z[17hLh%)4.M]XL)q9`<'"#(Y&]"$p*h8tfCbK[@Qu6aL + nEU5b?s;*$9uW5EMJP\uK.m.fK&,pK/7Tu-"P&PHAO/#H'FRT!iVUM7t9dLMG5uaC + nRUW*Tb*c2nGmUKSL#M;0,"*oma3m%2h7n4_)0LbWM7adochHI/,h9aNC&-oYc3f + %S2H$n[%U[W'Y5>?Gr`iT*&/;$u1KX*nqr4iFjCg*^-PP(:n"bIR@.k92Gq(N-<]iWsEWh0 + ZUKO\OYkf7GqO39PR^\iu0VWqJs9"!K=a-GFLJQ!\=sbAYuc^Tp]r?i4Jnq5uI7guk2K[6O + l1GR_`""`4`hE[J?38*=/&SogQlde\/[l5_g^o_t];HDaj0C8?!1_)_Z`:^Q>YIV,73H.c7 + YWU1^[DZT>g#7&?Agp1]3Z-41+(kh<-?`XKUN.kBG+`"b68/_q_R?LM7WsVIkesL_6+2=h`@oR3Fj5e/,; + %N\;T/m(^-F9F- + 1ag/Y-UQ<26KB]7ln-*H0Z+#J_Yc1,'JgG24Tc6D_P(iK4?'l/30tj0J]'I4fM)L97jo$(_ + rJVGUVT$n5c<)G45<0@,'63U7(`Xe6t=(Oj2<_u`Pdpl4DW!9;6)g3atnCe`8f%IZqn$G4Z + qI_.0DfR16S.0dA^&3`F>,r;am4G7k)!pWRFDOM:AO6>dUQc`T']^nt`:-@(k#c4p+Y3X&D + 1pX/ard[:Em+/**:jZSe2XT"F2:>Nqaf?,k='T/sR'oBlt4Xu75.^B2.lZXk[,Ah5b05=>& + c,AB'"pFM5<^QQaSUM`!^I)"/05ML1moDg$JF=g/e^aoQ&6[(e"GeeBH87W#tEZZmgLnZ@C + \+8HBgOMQ\JjBJ",-2Bd=t[WALT[\85uKMTCQ#^FmiuWd0-hGh6]j[HOfs-R08#LmX-UU9Q + bjsG68D3O4J0M:TL2,@QCKBkZ=Un[U.Pijb#G7\'W9)aW(M01b+u#Z[&GA`G6RJ3J9V7u<& + T78Y_IP1_ie\f'XPqA!!)Ku!8)^TJ[@h&J]$hE1/'u%>ebSh/U'9P-INB1@KX:#p"_oP-7jCTD78[H)2=]"(<-@)iVf54ZD-1Ls!D + keAW[[Y+4R>P9Uppbnpo/>RX$dL*GKr&5%5^[fbq$ZdmVJOh#/5W"-GHj3B"#LAV@aEr<.q + gK_%,!aA13!fB%bRUA2KLkE^U;AKJ/hAW6l7o=(o*?&\GM?'ZLjp9*%u"Qaf + 7\LZlchh+VP!L67aA'o9r:iWmOM@d8^t)1piU&UsWHcd;9Xr[2MoU)UZMEdFB;cbo`bN4bF + :p91U^61pX!//Q,QTKRcID.Y1854iD0PZNRti[(fMO^/EK4bG?Z+D(*>]_Si4i9LU^!Kepq + WainTZb^7RXMp>7S;G>!T1^$e%h)iSkdm\W5`JU,'UIWQd5cOB\1pgOWle>-U?d4747O"hO + 1K-b2gHHn<.eQE7"'<3?Z,W;KT$6,aj69;YiegrI5/cNrg:Tp"^;heg//31r;eA6VlGmqb7 + uM-&S9h9#Cqm;ncOF3^jRK>To$9AFcU&8[&Dsrrp.."D:OIjJU^/o\LVTY>^qBT\`;VA'%P + BLa"TUoBW!.]B"BPbHW3jeTP + $uG?>T'fHl(#/V2P3J,i#bfN8=0D26a)J(U$BfVO("Xbk]fq"M81Tc>tI/l)^J46&bD4f6BEee5"g#rQ+BF"T_#7$W< + *=Tg.NA+8,W5N6ug8-?([4S;q0&"c-dfdNMjNuBIekI[HgZ:abSO6+W6p8cFe68eU2+PY<8Nn,+.cj;mI7pu%9hkDO^3rd7]] + $lRlF#GG50I>647,NQ<44UP[QF#O]iU-)G#aX>5?-uLZ3F=Hq4O8Ha2@fa*.?B@Ik;[Meg4 + hI*G&#WpFVA;87cI1UO5kNC6)!F2uNZr`GM>o9nX';ffeWW[Hl\gm,$:[QM<+b?m"((nCaZ + .b$C3;`K]6pRpT1fhdu@cD=ue/ptpf$b"sjFL`sr48DhsjQQSfUoX'7D2Zm3^gAAimI4/Jl + XjZkpg9%GaFp?l*#hnDDdZ#tDA[,/b9>$7j.#Z8Vof(h1[TdN1]^/SGS5cIMc93aAS97!.< + 7+q#5[3V@bsat/#r3$6;u$jj$j_E*WV/55 + eJe,/?s^0P$CL)koL&aJC"!rhNdA4k!S3!7-%B;b!#3#"o2MS8E>C+?T%0,7S2L79OsG_jQ91tp.]* + &:?;dTL9O[2GQ@dU!5iIcj^nE-(`-b:2W[1)#4Q@8c4"*f8RO_fROtLHQ3G2*#MQdCAd2`+ + ra'!-`5J5TgC:CA,Y[.Y%pMFu>>rdFL([1^\6FX2>FRq;A62J0(L`!F7C:Ouo[MXugKYG"i + S31gMr.t6W`4mTc+\MZChGVBa?F2]7\rl><[LQeiu]gi>jUQWrR"VNdTn_3@bYO$=odRbp> + ZPUcKND@]j=J;I5_"P_#j1e(sp'?3CDkV;Y4.H/:@`Xi-#\Y!/u[MIZWNch6KS5!n + =BSE\Uql/h4,q`5$r(p@r"iGlW+2T/d(4\>\7ckgE]Kt/N%.*2IR)1B"V""/o=;%DIHPV + k5H/R?5Q,&).tHk@BI<-<*n'K_Imt!kCpieQ,RCD8nZ!'i\Y>_CK]j!:K7L&>h)^8,]?r]. + :!AE^@^UP<\0loL>>U[XBjE@N-S1T1!]GtkfiR&_u8UpIq]#cknNgNPBPMgiT%X6hCWF*D# + 7q#CMB'r??:[3Z6sA+T'k1smcNromI0=hnEQ>0'P=)rY+tG4o^?7J<=XRs1umSUK5FpEh&PVA>Oj2r[n`+;? + 97LTPfR.jA);aO2JScL5hp4S.,kl2LJLp@r_16LQdqmmfV2QU>jb5kIi>oN + +NSA1.](@tcTDJ;=FtBFa[MIrs/FJO`JQBF4a,#m\OpS500.1WT!?#AZ+Mn7MoD7on'9!!0+)L/]5Y3kdr)m=hS,rV^[V-IgQo\1[C^64]KKSA<#3ICG')4pUg_jmm%,9b[i=< + 2H?%Engijgh`m8;Z:2LH`^6U-OhUC"-.5hrp@_X:-LF?/WLN93VmCt2Ijk23"/mYd-mKE)o + o5\>YOLI\=F"lZh532<94O9WXl$WD=p?H/n3Nb"rO:OGEhi%9MN4TTaoQNb!p7V$u(j%Ekq + 'S]-s-%^]PPJ:Eq-m>+O=c&p["^6%VMK$`['YHN0`q9kZY$c@4p#[@V=6EDlShpgJ"t`L=` + meLFgn!A>r6]KZZ*[Yp%E.EVlT=U@I+M6nJ8ufpLN64'N>#HG&G<44r\[(eomQkCnDX>]\7 + PQAG4k'p?p4=f5Pa6b3]\dT(d;H4Dsmh-"<]Mm;qfAIjk&P.E?8ik`"lq`\DMZh6uJ&eb:` + +'k'?,JDKhuj,ATb2;pKKjmui5?9N#jO,(f[Lt9n/j<'99T8e8Sf_pqMpudli\!>+.ONUF^ + rP+Zsa5?jii;N-'rW_ot=A]2`["G"4VMI]jFL6bk=q[mXHA+0NpZqY[>l#Q0qH'J8O.q*dn + 1u^sbVuEppKVjj:&:`$nC=r?#&HuA'1F_)E=QmF?`u:V'-+I!HQBfnE.C1/Xp=j0#mQ!ti9 + g:j1"d%m5M^2?PYrkDN&o^u.,8Pss)gfe:!!O$7jXB\RrSg>VHY^'\m&)NSB>P)F>dLtRG@ + (Q5AX*Y/rCYQcUM]bh"7%@.[`tG;Q;?aSQ5[OC=VW-MUcjle:6*:2@C1`VdZpbXUk.PS]K6 + f>V5[hUQM;NES^(BMq.'cGe^cX2tmtKs+SALYo'om*ua.Y\8jSFC]2/9bN6&=bdMN-T$T^W + %)!F?8#:^EfijqQX')WEZ?85\MuC>pg[1CJXlFbOhIGr_/`%QarnV((q)j[9rOBi0F?0:@U + N).9lh-n;^:S6;g[FmL*]DaZrn?si4T:WWTA&#nr*SiW#LD`.qg@#qYN'-dru^C>I/q'%'F + =c+YX/8(_(]C9#+(EV,&>&7M6(!Tn@UOc6W!#h9oq_u!>384/56es&PQ1>`K=tB(WI&GAJr + LMXDJ`2ia@4bR>"Uc+%NU6KcnI""H=SGngonLa\lr=-cqO)SLDXDS?455'!/Dib>R];0?\D + F]eN1o*6g7`'-u*ac#ZRQ$%_#ND\a>SaYbtP'6Nn*c]cTM4jVUe!C>'(/HnL?oAQT5gXDA:Hi3E'$S + EK_/F][,tIpeo3SUk9Q[Q2rKfk6kFBA&T1SS;7CSZI!:[__-D>DN%=Cg6W+_/\73/]2YbQU + T/@h3VF'F^>l$j*0=fh6VIbf?L^gJY,$Ul85?OF+m8>Vl/9b(71S2P68dI^B/=[B<#65(\42FHN"e_q?KCFa + 069%[G@72Ap:nAE=QrsgUF>8KRc58o>AIW(d2h@[X080*#3+;rq+61or!-jH^K0WlRlk+n) + BUH)7K#h=-rI2^FXVFn%qHjVQH,\=q(Y>HkMp[B-`L+l`,-5L9Of`CQ23AQIKM>QB8Vb>gk + erkC(QV7)4gE!o+5$48A8Ig"ZFLCdUdH^T%),0:)-@>6,>$/U=U`QHD^oK5,6T\lo_iinRb6\L"B:]$&+chdu\8Qphb*\5JqRMNl + 5&1G-SUk8NC9PE9g&<4%tS1?U"#FtJ`!^!5"1kiVc>FXAinR5\=9%)hCR%aE`HK24SR,J,i + <[+\;SCi0^"`HY>Jhn4`+o3PDsH3[F6*L[C[cKX/3-]3X"A'4OA<=qDjJabijo0o9;r1bY@ + pHHM&qqn+XUe"M_;Fgo91Y57V?gE\jX`6\7fN@<>j0a](&ba47`\ba^%"XL`:.!$i%jI + E8N04V'c,j0Pc_SHJP>Y^^['l;Km6m_W;OULka\Y=Xr2Lp'[b;=*d4Ajs+dT7=>;nWl05^e + KQn@g#2tmJ1>VsL31F^7%M4Wa>Q-h.H<408`XQI]sflOpFRaR]ak]a*JNb+qq)Am&jPSFA5TXJ>^e[.H]il'An3mk]oL81F8ga>*/Jl8\B + *pi64,)o&,(=If%T5gc8auelJ"tq8$W5J0"^WRq,7mjaHs;L6=N/6((Dl+:?&?Nq3#uK>*9 + 57eN^aSY*I.l5j<91*nee7G3@nS!XIGBm(.u_05Z6Z*W\AJ-=WLuT;?1YE!GR(o'#F?cMt' + 0&F#^oK)4*4=m+\#M=M>IEa#W<+eh;*3]G&2d4_K0*Shp(acXQW7kIgXE"R^Zb3[7(,h&3! + [?BNXh7!X_7791n@RF<'lX(g:O:Z0bhe(Sk;C->:jF[i1W>+\g[SQW#dG-TT/cWFn4r%c5^ + ('6`a)[1Y<&;0tu7bN"g%l)?u/5F(J4e5t3(=:bMpM*dSX^:_@eR(C1_!T4F>:_,EY + gt=FCmGeWCj,$u<^V%$:#0@H`L#FZ2FcBA,0f2Gq3'hQi1; + AQ%@Qm;af+GK^9U9[$FPO#-]XV#VNa-Ha;c*Qg0%Wao+JNK`gU>($]1Q)+*On%DfP;nDQ" + 5Khq+PU\&g858lnDnF\,XG_$?6+g'M(&Cp1mjF<.ql.rtTGOFJKIuR5!hn/?,(B+!DNDBr@ + a8"+!hC$;;Y8Q=gYE8,Qh*CNc$=;AkD/i[oDXZpqCuQJZNW!!ieU!`nX_90G + H_tE]@VBqSaF,+ru&\_h0[Ilmmm/t'qY`ir%Eor7:4.36Smg$c"X8eYL31g:ZoQ"5lpWpeG + ;coP=asKL,sZbo6d>ILgWRG@?u!r[^d6NQo-:/\; + ""UB$AZ!3d%8mD,rC`iuZ=gs>QI.iR550&]:kEkH:5kGMSK&B*dJhKK@c2"$;\$gaJ*JtGlpl,Shp/-?ApS + 9dr+^ojc49G6Y@+R2WnEk38J$C-`mcs4Xo+J4r'(5c50* + :X*3:P*K'p"qD2[Me(d#CF-3([jK\8@ + T%(TI$\_+-P2$BM2;M@5m\#f.O!+E8XAHlR^u=gjfkVNn>RjP]*g.'k4#o2.EHXe=UY\dkN + g_.K09Xk\=R`R2R:-_8b2,$N5'0,%SQmT/-c[3n=^KMlKf.WcEs;U\>$&P0'Y;*C,ljrpd% + S1Ub:+.NnC08@`@1\#&q02B^ + \D"Wp5(GBD;do@3fIl4K`(ibN0nVJ,ZQPkq)*4'WCo5u%kN>dT\)Uimh1O1rYM)[;N3-1-? + Z]R7hPrYjj*C4i^L]A.0T'f^HO/r&#qec:OIZ]WTaD94$:cfXZC)'jRjo+W0T`l9CWe2abm[?r1N%n:0[= + kRd_:goT(9teC\KC*T+6LBl>-\WVl_QcQom.d_3"X[8hht/^nYlXm#_kGo)TG^3i*E1&lc[ + 71rV1+aYBsa1".-u0+fte7hcP0:%;(:FQF7`5r8*q1c%Vo.US_*;=E$oa9ag@+]6^Z;RJ5WoefXt3E%ds;ft5C?qpg^1e,:<<8g,C,](Z^$ + 6J0SC,-qo[:MGkfiGBP4]BB`eTF!^GcgXXg_(9$b7@Wph*+^Y5'-#koBd``3?lm+,>IO^&4 + o+75+U9]E[utGlL?AghmmBp,-Y\DbZp,r16FkT8\dh + dioME-IPe.E7]9WIdlbQ%@_a\``$oDkc/(hQ"gIGmskj[*d-[cH2K'BSd^TuA)1m&:9X"Eb + E/hm/TPbNNSVP&J#I1[%qfR6I`a:EsiUZ%heIKB%5-?EusgJ4&&X(9s5L2&PhoJ + M)1>ToCV9Bd:5"1Bf(lN1t,:pP>b9k=&4gK6=O+1DhE;>iWmH=->pEA + A,F[j=3.)Zgb6EptS^/_[LonQF?3Ai66f[Ad>HpKFp)"ZR=NkZlHOb,:5 + 5n>nD?g<%2+0I4$Ip?4`(V2m\.q`%.qhj;:,,sdcTu8%mhSrn:J+heQD57\s%kLWC@bqN2%_a4]5BT&D + AZ5g7#Pl;"`2gkK_So.3+7PdAVR^)LA5J<"X"Idp^H5"!Vh0N=G0/ZbR=K`E#"@OG:PR-o[kaa[V!%MMJ!Its3BoETN[eBfrqfSQX(SJ>W\,1T3)BH>4\[NJ%=W + "trH7P`_S"g^jUNk1.Ap^<):Kn[l>?XO$H:rD8\TShUV-*1PpM5gn@$_LAO4&#Rf7)c03CD + I+GaF7T;MV1"9.7[#H#?oMg5ACc"0!u`P*`:#[Lk"_'pDZ=Hd]Zmf>?cU[=#QrI59?S(-AU + >Q^*[.XP%$a'W"EWc)mCLXj5/Wdf5Xi"#+OYJ9cX31s;8M+g9DIR2"JF=!aS_!N]/WQ44gb-XaW-qp4JU&HTut$E;;anGKiti09H7KD0 + tWU1)i`'^*Og9[*rCT!][aM3ca]@-\G?0!H^.`YaZgWeUK+.M25e$Qft,m)NYT+:Giu]cBt + OgfNuD<:PUF,qF^;[$O8IFo3c;JELI<*A-s74t\q%A/>:qW)`r0tjbh;5j0 + hYl?h4mh-4n:Eoc#9[BsKL]$nqAn]mbWZW_SDg+\h;Q$$*o,3FhB0tsc/F*dMu;Xt>F2n73 + 0@B#\f*NR;u^sr8L-&Y"apEIR,t-t;'hbBV/((IXsMIqduTi>]c(\7:-YWs/!.5u^2uql)s + u+9UVARNe,#'R3g%p@IJ;Wr6G.1kXAi9h>$18N5CPr%42h0k=NHfNQ + km*K?B@PF#2OJ2r$iQjL`/0kWQC`P&5ar*`=3"dtlrRF[W[*-9mQW>B`UgmC=3=@;V+LU,0 + /3WV2X_+G:_EY*H- + HkY?3L-b)AJOUMc6kJ<`&V0-\+T`*]-4[i0&E)1K5oL4U]Ft;;%&$3'@+lFu1_9]'(1A[7J + Wpliq;*B6hLBJ$J&U<[ao"BHr-uuIi6DZpMr6i#"FrlC1[X->-fXt$Z+_[Ght,iWp&DOf+: + f8UrWNFU_-mn"<#QV\L:Q6)#E28(&W8aq&Vp9ui9$$>UTsV/L.NML3Fd)bKV@9SQTsT<$"K + -,1j@3B'3Wep`rDW+VmH47#(kj,;4Z3o1U`WCE5159qMdh<U[ThR)gJ,iNF+:d(=,X\2pO.0k0DBYH$3'2*iNTh>4)54_FQ9Y;pHrR>Pf`$. + %Yhtr2k&#Vh"eVnJZklu3V9nB"'PWcen_\)rHhUMZu^)n&,.SQFhHQ!GRdcGl\E?,\N"ts4 + %kpbEcX8=G)Q=>e*?Teh.DF@[c?fLge@JBLWAEFH.h%=98h6WpA8!rpCG=-o(mbPO7rnBp\ + OkRL0O`0d>n]'`p>u'+6MgI&k(+u:DF + h'.K:/Pd;3-,(m/oS)5$'HBcP.t*-iVFEU85r(e=D0,"U-5NaTr6=g]TukP;Dk8hup5.S;# + bYq6n-bd`.l"n]P`bS(]5C_s#FY;AsP9\*NE1O6/`c=csS?-]dhm6".?eG2Ck'njh).r+jfLI\BDJ<)< + `P,k=]RDjUKj*M3",k=('T>!=EZ!K@i4;>&q?>2FdH^.epX9BRDj$@C>+uYraqF]e[]PO[a + A/qgh(-:EhDK&+d]8#"LD>Tpb\o.DRo=FC;`qd;d]E\DPDql]B_XMq3'?"m<59%R]gh._NE]g6Cb + AqtR%Q4]!%m,K$61B,]UHKePM=elm:XpX[=rl4;]n./VSjuBPg32;UDr#V#kAfC'IK+ZPG^ + ^Hei;!:!OlL^NB8tO:?AG0;FhLSOe0BAk3/jZT+1RL2WDCBur+UZ%SL4@tF7akf)M.X2WY1 + Pkrb>o8V^gu(nuI6iakEs6p]+BR.;46N'XO+h&6iO>E%[5/]9e]O?m(S\Jn(aq_.+,_9L4^ + oY]^LBo,)&t&XJ*)I*W\j7g_sa.l?Caedcb3CgiI82R(aN6UQ>5:%UM,OS%QQA!D(E&^Z>h + 8g>@$OEQZF90:NPo:BWgGu%C<4bq,QT4kHW@aXuk-iF!5oE]uB'OcqK2$QNl(5*o1&jlq-% + S$(L*)Q.]6th8e+p:aa;-G]8Lr)A$GlWl#&jJoB'd:Y?OWm\$9Tb/l/#?r&Pu=Vb)_t8Ca` + PaojO.C0$(Wuro[o[tKVOc'l;K'u:nNKJI?eOV5cAs9U/XD.;=`7.WC_'*V950(de-A8.=e + B48u5MP[UUf'UoYn8[@)p">q:c)Y*J:eF3\o=/>\`@ernBa.q3EKFfMq6'&Hj9Lh=qDbrT+ + HoGh>m98V4T(P5aR"CQ0VbusR#>6$2W@!"Jan=DK@:lV#`sp;Ha%H6T'KMXbBW`KTe=U@J2sL__`&kfk#BFDF4J[Q#G3&KE$sF + cT(k#9m\i+)m)p_MI"gUb"5DYeidIBcdj2R2V;8FHM^V8M*M5[[1OYAAsCb2<+EsSL#%-:% + Fi?PSe,>W/qhSN#_;&=cC*4<"41Ka76lS;'Qd=*MU(!k^33U8mCd/ + k4s.[=-Ni:B6>6%J!<AhPl!I@>F-O3-Gl5GU5PHnS"#IkSZfUWYLB)<"/!I=!BLmf&<&D+6`!.CVB_k60 + Oq&Ve#BR6(qR$]3l;C\dgeG!Xt^VS0e>ON,8@aG[')7D + $1EV7XH'g0Ql<,F%^q`9S_^1f+p5B"UJ[VL2;tWbOCIa?5W%]V@)AZS4Mb0YGl[4Q1@NMaW + 1pT)d(0`U$q5L`h8RRY:#!SAXGEX6(9uj$'t(Q1+iDoOo!G.fq+=LTk6A,\!8`A$_tA`lXX + U\4f%f&&%=B"X$*`Ci;g:p.&'S7r3?=I!*JgG=RmB8"=orXF(gXE(/t^"@thoFFYp;EX:.n + 5Z@Zt,Ql+UKg7JKa@q=0P_1n.AtI3OaK'D6`!JuY$TECX^snDeN"/Df%l_qf_#t@Zq>e>o' + .qFo3$X'6Z\6/JR6d8N6)5I&8e*h\k!@do+\ReIX,\?Lla[-BNQU_438Z_OnKJ@+6?AGZMY + 4/h.O&sb8usQfX.2f+/kXnP0B2ql!ro#/Tu/)V,?(A\4>f/mVE0)`3eOD!P!?jR$H'3P3m/ + #/6p3U,-p&*HUmi$n_\U0/:.uV7Z&?(;)n7W54N)$G]^6g>%`S?=M/EZ79_B,X"V^k=XOk<7C%%TLaRcK)TI`i5DTcJ=C6a&5KCmZ`nN#`l$/!! + @g+83!\bY>XQ1-.)?EYHW61+?TuKgt[a##&Rm:Zk5Xkb3$NQ[QJ9ceOkY$R#hG5-T:9=%_, + kK>5oM8`b:Mf[LI.g'DHd3U;K]#FT@a%BM#1)(U^XHjlr.<",!D%Tn:b;^Ke;2HOMTD9-a: + /"R4H)40N?9.#:o_gMPmfq4HH?hJ5n&o8i2o9k&P5mSXfI&qBXZ$/h`+RQ6&<tOo*C9jg_A+.Q&fZh+Ud@JG0-)8j4X:MjW&]7)dV/,BZ^JA2X=pZX3TMg8oIro + GTL0_P3[?N)3]B]1V8HiP3cbMS4Lij45d7!kOjlJ9XLt?&5,<8(6i4+".hiWg^sh$S,m6TZ + 41<@*;bU]37"!%nKZHQga\8MQ49HhHnmP5:8PIkd4?Uq:FAfE7d7kld4H.b.I+5g?E^cJBZ + Qrbfn[)#Lg!3;m2D>3>P-kC>pmG[W.Eq&^=C)-o*O5h/?@(E#VJWKd]ZKXU)^r#LIJ<^h!IA%k9S:\JC1+ + iR*J#faJhIX,BR2P9Eqj;<=&q`Kih:+j)ql_H\H\Ca[Y@QW_8+_S%ANjXNUP02fKW=sPh+1 + SNMkV,Ii#93nF9$Tf7oVPl7% + D?:ZqN5+:#!!"bgRnJH=a`_cc%ubeD7o>lcEBLBA=qP>WX[j/'aW\`n_+[1;1bqi&fNge0i + F8LWEtZ0HgIq(1SsX"B[U7mF.Nq86o3hQhW,NmPoc*GQn.E?3Wc&r^:'.7-$O#A8XkIIRW= + S\PDVt4m6%&5Z$>OFl$,K?L?k]"m<=:l/`rPjX'-P5#Y9a:u:>1j_dgEekK6mo%5K$fL1eX + 9b#2_e_ao#aZYHi"c>lWMqGEP=Oe0aE5,8'Rc[lnhu/\j*?3,o!f]=966./j.N75LP1bL7= + dB=k9lnU8L>ube@$d[h7fb>d0KY$78$/U3)B + `@:U9\[-On"!7+TV2`L>@4c:qpP0qYef'S8jr!Ji-*!#j-2*LYP"JnQ5,RZW/''EJ1CjJFs + ^C+0I[&K?@pfTJaPH#2FqA%8Sa?t + CS&.RQFq"9]^*4R4YR+(kZVN:=KLXdhHf:14auKt"m&3Olu'gZHj*j4G@bqn/>_h%*mA2eb + r:Rih`ZY7$[^c;?/KO+hgSqbSb_CfQKB_7f\QJmI?(g\AmatTPLdNgJc(`8_81S)0U?YQfVS"QThO!tZm$0K+Yn;1qAHeg\as-sMAG>_Jb5K@hm[O;rgj + '0`Chc)Q3mm^M6!S3RrKFfsA]@R/;D$=lLn]@#NBO:o3AVC[_KW$tb( + b,S(**s`i:-s`^7^8KWZG.6&"rL3h=<7L-?DmQGh);ugI_G?5]lTf*F/M$>Y<6m?@LV2o/b_FpF[?BSf6(j^Z!/:DgG2` + /O+0;)X)5]k_6J6bXl#s]kFdrBZ7UCG)iFhODmB5@]'@`rf]0g#&.ELgc#M`Ah\2,0hmgYW + A">iO;$[GpB4,R2>D.pW(Z1?%"P,%`dg:+0IlVA860@U1;%TD^bk*n6E8;TKa:/#LbAb>Q: + ?[5"ElW_:GA=-0tu:^"jfuh=:r78MfQ$Vo=(W=c_pBdW;<-bHEX;oZV>N(5sSEd_&#u94o"\-'3M\PF]B1 + DDI^e\=5W1D=VRFS[%?[:>j^][dJ$u*:>mA@,,,V@gE)%BMGW%sq:$2Ve1J.=-[fGoI + QQut]DmB1T)mHF"$BY:g\-eT7g8-QL/e_M + #1,E`I@%"b91#"9"UeJO(Dt=@=2F#)5HQYdedE+VTXM6=B:r!]:pW7q9sAk]AEjO@`sA+P9 + t/N]TY'ZK[$?@]FXUe)g..r_5JG8Om7[4O/X9U.a^W3+Q>O4f1_Sl]fpDV+7OS>7/l1KJXI + Af44lC3#YjMWmp7`HSq\DCU@:tBj_hY7DRV^8k4*f1heuT^jo5L8_I,(M4?aPnMoB1G + 3;dAU*KeKTo%>S6#Wrq2=0p;nU8:3VGI]Q!D(_G[mrIYM]$5E=@C+SO?5mb!s)5JiD`>uOm + fS'Uic+\J/k6_Aa5qAdmX<\QiR7"5HJLd"4."^hGBobYkC[ifnniPVD5X"-W:kQ9g7O+fgu + En^rkD7m7`A'b\(<)$p;]$NpP\>Q;;1rj,)j/WpF[pHDH>U[M&^qppa8Ci@)cBY&FNJQ + CrcB)7N2J*sP.[l<)\S+=/PClpcRk[L]YQ?sF"G]pSE@2p2B:1fi.%9:XQ;KN"[ + 4jUIqrBQfmm07UXB-e'URRd1q7FAJR*Ch#)AL;EF[(60URM="*9iI;P`+P_8%7a8pPrYC4e + L))5<1GgcRX^hbNFuBpQ(aUK=!n/q?D$B]QXAB'"bgETk#).gfeu9aAskPOSo<[3BGMuprP01!#R\nY`]6:OTB:-!` + UTgF>K-.qLEqA;V.VC(Er=6CAsIbQ5L1HWq[P!/iMo#E!5g4Z+e"*^)U#Ffain!'+p_isnB + C[E-O;l%J>0#Khh\->!mm6[(o8U3aq;OQ^&\"9._91*>Z3H@U#$liO=BIUZGW&_klqFp^h: + 6U!hb>hA/#/"[IEm>TB#cs?a'"ss'N82r;Wtfs$.D%!8m\XJ5@-c5^4"F?t9OmL2>/LP'CY + 7)=]lQ[L4.D1jU;'\iaqZn/^Q,#iQIDKJ8u16AS>C@:UR8#F+mI\70b,`&3/9h#/>IEt6OU + "Gb&.+,%;LJaH7&Y=JHSWqCY3V?7R6W@i"(R>":;q+3#"A(l)!UN + 2[sFUmTYqj'k`BWF[R"'X3p6U0gSCJMAKboU8]6Ce\Wc32ZKOSl<"M:C77j8t_&*/qqN^Q]00qZTWZZp%#mFUAp\+$&#O;%5q=8%\(ZoMm7! + 7`E:XO@uQ"]1257[)+`CkPJ*;=)5\nGe31'r2T8(R6Y:/ZZA^^#UtdBQG,*=4]sjG!86cTME*sR#_'2/(*)hf:Y#:WI2^7DKU + RH7!L7C:cFQm'j%08^>4pE^<4%8Y1JT*TE&(-Uk6Wd@poVdiO5R%5.n?N[l*5t(`T])C`V\ + $\YX;=_H)hQmM,b^;J"?OD2.b.aMorNb&YnA%,74>7UJ._n00K-mpP-,?S^XS/B@-eV^YVm + Q>=GmVtCh8O0egeaCgEU4pPRW3ZglK9BD['XW)2#09cpIUu$EYR*['6^_>'j6W/K?IO0[A< + Rb;X'0CcmJLp=r$6K+HrS#Ii)%i'_@7&l>Z(M/bNd74=jMikd7Wf/>6+=LsMFUgq!X!piBi + drsboh+@u?3$jCAQ`hZWNq],.VQ8/fA/p37agsheHfU_C7#SdZ$:^tZ(o!gjQ',lUE_2*B- + `#T%PS0#Zb%qcmAN["opc2Vg)./'Igp:+M?!tieQ\O(5\lu.CCJ4?.Luo\s`63aD9RKbp(P + XMJF$Y1`)61JaWrj;^C2GZ[FZ3q9j.%ofNarnha6-YSaNU)t1>J,;kF4?#m.2UNgnd6h>ln + _#?@k$C[6`Nk$)*SMLGfJgR-:prUFir32b_algZ9[)[$e`i9/&j"@udDXb<*8gD!,NUU)Au + RdMY>Y,=AEr(SZ)pe8X7[6<8.P6-Lqah%EpBDN6!F&6fG`o;'Q[A]9,+)n1.=Bs-/)Cb + L(!Y]_A2f\6dECgE3%/AA=g!mjmR" + 8coCV6iDZIr&=.>AJ(MVVBjLE*BTU[c$l@[JQO6>`'cJka@rf^[bS@Mnta0tu/XEZ6:F\JHOPQ>8WFUG0hH + k:+`YWKm%_K.']F1(1*?E?#][+?96B(Jt>,k'\.->mX@'kl%:@eQVFj4E + -YS8D$ZB^sICd5tC@V-e/"I0C4*oZh==Lh>[ro3q3Hhpo1iVrRIli552h90^:89Wi_GX.(>\*`P`:%5Y`Pdmjk!Xk1L&10f*#2hmUi6D\ + g$a/H(ZJhr/iU.o%c7$>%9&e,O]YqbKMq$e45eXk;kloc$."GDC@:l[l5noC'VM!n-li=m(X,FqTO]8U?p,Y8ii`N*mK`j'#t/1:FNBl/Fs;A'C'2UEC/! + R!2jhU1BR[jkFf-gd&g0b.5?SQJ6W5M4s*(Pe?_Ce@H\P:7KL:?!@oi/!*TN1KILZ\(oR)S + _)_fd>QjM1H9_NC^t&lJ_`6+F3>42/1QOOpRlQ\)3HLrUe"e+kb0eJ,g/84t&_S1G]-"Ao* + XcF!&lW49K4=1V3W!dlVRT+Rfg@0G=JNl9\;eheS9;;Bk#f.c?Nii)2-/;DBChY9 + T,p5nHI0oG6Gea$IYX]Wt4l&lCuOrf?VP^!90_DG);j3g%oUj\U!Zc6N00(e/-Sh]G'[s$#B9X*cecEq%.mPZ^O,D4kOq]&K3IoTdWh-BO$E@)3fQA$WPB)2I\ + eZG9"Md,%05FJ$"r(JK%C;`hF]VRC>>S=&=4Q[^@GgL<(035r4ikik#>1`'NGEZJEP$Q.#D.7P\1<2)X-g + 5@M5)!DOW*GT'@r+$WMrjOM+37OUQ>s/#fXBcH-MN`pF1c%B7S^%c8iegIk5.99S>?0<#;5\&'ZXCTh#bQ1Gh_Z.L0L1_%$O^,Q[@L2;f-ai;H^-ZZ"FlE# + ,]t2ZI_t$ZMFCE+bYA.koZX5!UuPEYY'FpEs*^h,tJNEu-KN;q5$;.3M\:4&?2J25)0093S + f"\;g/c(KWhf>)G2N!q7uico31,a@Ge]>g'X:E^:r@7gh^p`h/TC!s:?R.nAP:TfmrZZ(d0D$0fgR-*9])g-t/Fjp522dor-B0_f"iK_T18CLiu8U&Z+?*I7 + P;H%g^0-tDm?IUo@&n;-oB4#[r?d`q%('NdoZ[3LqkE:O-2naSoNI5hb.]9$E-AnE55J49X7`YQBDMRh93=sKc/YG/TUU=qDu)E*&SZE2]fNT: + >(\Cgja`JloSUtSb,>$%Nr8YsiCFF%GW'B`P>(QOh^!Vl)mq"7j9pKP&?5Z0;p3<968"XD\ + ,GDP.=)%h*.hZ\9C?<<%kR3O$0Q[*eE-C:JqFoQcH#:Ga7PP,B2:lSlt@:<6RPftXIWFn]g + PC3"F@[GU0(YL9YX*rKWQ?VGVe0Pbak.Y^FiQ(0"ITe= + eUEBVbMC^9pdA(o*9n+Ob6RGS&&O(b09GPa+RU)=J + A)g=?(W@PTBn4fmkKrO\o]pTmcq&t#@n#FRuWu3Rk<(UI9]mA?n@$V8'Gt"gf8Zaqj.1'dT + /30j)H*0/5Y*=OW)XNl;B'UB.%!chaGO;U-8[(8]Qdht))25<]NFhu'%i/dgHQQkqSS'j)9 + _?f>q5^hV)hPW*&iAW&q72-$RENVOZrJ[*gIn'&V_X[d1AeN3EKqE!TEB]q!de\-@B.kDq@ + hfd%8/A\TPaqfKc$-8/tqhQiCjVeOpA7\$UBdYSHKY^e^<-RdMoO@4Wi*X/(0t@._E9l)o' + $HBtTquMnA$!=M_\_e^WDJ=a`euh,i)(5JF^EFW9g9[BI>nCT"aj^[qWD.)!E?CY.>LW7B4 + JG1$Kr1?YUch + #o?FOr<:VGVtK&4&gS)NZm\^fo#em%[qQ*\5moBk@SM`QL+[?U[Nic%=l";R``=nP"k.o5 + ZUj>WQMp2$rUFEHpTRVnJ$rG:/8p6(q-e4"@!>_SQ5_]AcXjIem8ht9^=dJ745>noV:t6Z]j5M7gNEASJ\Y[^ + pffn&&R34UWc]5V:7IBDe$>AmY"c`(be%qAQY(S0s3Ko90#mmK:SuWVoig05Xh=\dCplDg[ + b#N)DXOuo^#&SalJ:s>/^?!ie]-\@3DI3u3RuDsoX^+_T38SSm5fSWcI8Jms6t9G5!Lumb1 + Q61SK?*UTAT*Q^YJiRYLDn7qDH$+pG;UE^X*"]s$2>r%0Sqoq9!GLET;!^Ad6dZ<-BYuPR`tQE>q8R):EGSbo7OJe>J&ZK0BcBPs_4;LbLD4uPa:\NBJ"/8 + >N?ZI73LM)5LBO.r+6NZk.8#bnX"54P;?&4@P2*Z'))C9kDp4CW]ec&]q7SX[P?cj#R.FSD + DU+HNrbe+P`:Z7=F7HbLTC`9j5gXU-M*XuCjUc:VP[T;rRrj?18CDUJKR]Ij`AL?`\qKG(o^mFJ=n*CP%:R!N_ki + #q4(LIH"i^J+8LY&R-&\!=.*\*DK'3#M8F(skW/p'TBN4umOsFm2O:&)Kc(JrB\FXO$%_#P + RB.drnB*4q3qfJU,c?3D:hr-H,R4@=rh[&<]"ZFj*9G:!OI?>-((C>24k7]e[FkCcguQ:s46&:Irh%Q3Id6]kISs>YP^FVn+Q/KO#c9O&,c#?j4=<-Jb2,riWUE + K(k%J'k_6bJ?d(CGeaCPgSZpb9u![:%-OVK'nqdT"R$X!V:J@ai1T$Do'iT3U:?g:k7?mTa; + t>?5aqc][R)l#^,)oL*V+O4b/u2J%DcVq"ajergDLD(VTt-!(H1>mm'BY#X4(=*j:@ZA!Ts2>&D"irEJg&VE]a'#U)P$^J + q5M9@e"`hg!ZQOJ;'Z5,pqg1)OB9X0p?8VkWZX#4Ng,_@i8=#3B=r$PZkWXjlb,?X;d + P_jH-Yu^][r?K?:A4HL/>`*>I8SaOo.720^^#.X+lgU1dnk\3[?"uR$Ak#::]ma + kpC)?'pF@&:3U;%?*ri4PO77';2%>C2%LR(Efj5hWCo^Zi285KSrU(u4=\GEXDbDO^C'*h8 + -oR+Dt:f..k3tYeYOLVXp8.rAk>RA(q>nXS&,ePG@g0RR<9gSNkp#h750E)iNt+gODG9?+9ofp]-V"!4t= + KAElUSVO(>M?-%kV_WC0D]!&+Y@ZIf9&"KO!4W^9(l!.YMpM_=)-JdB0M(E6hr8S/m-ST<" + tcM?3WB>!o0F3]9]RKGcf*=8D22M0,XpsTg%AB2U3%A=BBB!1,;?=VAiaoIrF`bn$nJZGf + 1GS&Y`"CM&/W.VAQ!@m]9L$quZ7SR(4%$p#1jRMXnc#bo.gk.>)0>%`-di9]9,;L,lR3_bue"0DSb+fJBke>@/F^D*?'t12YD+Ei5(T/aoJ(88bJ#5AQVCN^OfkQR8Wb[OHHWmo7CARJ%onflUT* + =k&GMDX_"bYEm+6>^=JR`jI_[*`0#N5a_h"Q;"g8#\'DmHuK(C3 + Jo=0/TUM+MJCA*o;Mm&>(,J/sqfi]&bO0?tt\J"s',"4dpANRg#,U)7b&e0T2?p + qjJ>5$a@75V)t8&o:EMp=1T=a3\qI:6PVXRO>9Hu3dI('M]%qp)`k%#]Wcqk+ARS,.KUG,1 + K2*,">Htf'JTZ&*s%aTL*0@2CWs^2/& + W\u`%43m1do8M7Dq;X`)K5pA4&K>ZN["#*L#6k]@o[%<1U'^7.\nkKM]Vj;8%?T72/Y-,LO + 1E9tdSu7)HJlFBH6*hSl5&4qh-a)rig!j>GEJ[I`N&Wl1uD@@u$52Cn&N9C"KFbb.>k52$3 + !P0lJqd`/'P[`iNKPOkndDRU$l^L@qu"8GA"q);Q!TtC0('6labH*07.5Q#`.F9/l#![[:._-<)=CBD>)" + 0bab_5VC?)jmGD:-m*e\[d('o+!'6T#kGB\e>Xd,aat[,c.joZ!q%VEKm=?.7/rm-<)KF87 + r)S7"RrM!K8a*7&6<&8cu:P_hkk.=E4SL9LO0E6[L-%HSto?(4e"k"$e;e."2@s<(a5_b;; + a_7'b!0Zcc=Vb@J**>e%t!\MNkL\njur6f#EQ9Z6#+`GAOBnAiL)_8Sf@bV[0e9A)uA.sOb7E!B/X'D_7?H(n!`UMqPCgfMT@]YMu2DesYW:[g:Ai=\T9lt#;MeXB1 + C;1/I[b;:p>](]0of!"m^N=J1M&*k#h7p68a&Dq35pa;&^_h"7eGot^J:\F^ETRuk0"Og#PraO?Ba>"Ha+QUqs9gb`Vha? + !l[kQ@EgXW^7<]t7+K:]m)F/![X?`B")?)S"O4>@^H0rjgi#pPg!0BJc3/,^)u7c/i'50@^ + WUpu>Q7H6s3?";:5nG3V!9/1JkjMp:>:Fsoi0!@n@3N3cQiq/SFo/.LM3RG + 9\\U8gs=4;RD*KH5'8op/N_G*A4Bif[V\64"7FEuBg3$[`l1;>_@Sq+77QPVc1C\3bt('iW + M-^k[sB)pZ62YS9WgfY\'0+Vh+tNY:sQ%"a40KHl7ba2K[0aT^nk+:KS*9XoLMpkcXQb$Zk + 6NlM:L+5[&?`4I7N"ME`A!>2f20rN + X3DUXuE_45RQ^SoQjL?"4"#H8)7+aosLtL[`BHYgPk`.ReT/-3`Vu1>KT01`P*)2q,Q2a:U + H&8*#FpQQ9gargdM1Y`;hU"!S1NX>`ECU7;0Oab + 6="m'RO%2%PGul@/BK$8,NEkG6)Peu)eb&aL7:=4(@H + $mtiWPG(&TuZ(*o]%i:ck<)V.iMdC<<:Wgt!n#S$N;Y:]rW`ile@=XjJOnC5#JS!/d!#E1_ + YJ,VsBIJ^>263%0@a)Zp?n!Xg.Q83DM1."iEoK"+/M==f,%2Zho-K9.bN[[_'GSE]1,l1UZ + _#+gGA+b?j5ZXtscNRT@nfAlXhj'%N(DN'Gi-NW]T#K73a%R=d[VWuI0C>Q,Z#-[>.Y;?/D + Z;sjZA\Ac(FA\?*=F\dnok@So`?!k-ANT'!:kGn + iuQ"0!sI9^rE(ZZ[hD966^J='AggqJB-jsX]1.#!(d_KLZIF/e0LE2frLKe7>s4`kB$0*55 + tKSVYk#[de^5Y@,3#Ol;Y90eq3V(uYY4@Z.q2PsdrVl7V6W5ak8&0h4k(Pf5(KdO%i-P185 + Z1/nInL#"?2\b*aZS?SKjNbL*?G1,u)=*NG_,lm>TD? + 2/5:;s\)lAR1knMTf#]=C9lLCqZS0eYD6GcGn_em1.M)og[Yi@-p-e@!tXa?*)Pqf37Up@( + f=tL>'-8ZIKu%mFDhQMP'8e;DfCN,Fr+gaj<1[:8=5I*` + ?IDY'CX5(BDET]05]8X:`)`*O#Rq5(/br7$'k:#G6f&,:JE + j_V!#$YoteE,"/g0ZMB(=F@L"YJ9)D+?Cs]QE:(G%Of6@'-VLSdb"^l"6^o:;.6C7c!4`hc + 4"@F7]h3%D$F7]4#U=B20u(BH3s"Md7Li&Ur'-M>o4l:s,j?O:^B@&Q"mV=K_ + 'OC8:u*eB+]_\]S/0sQE;D0Es7r&;Lj8&BXT_u*l[Lu3)!::)*%G]sA"GAhY&Dfoi6c2?2= + pF";bkKET?-?9@6bdSa2qS-hXhJtftnjZO1Xn^-g-KS8)5-KNRQ\+e2b@R9hrULP!)W! + pp.[m2fX%/P#CaWpNOeF%gb%p=JGCGkhk0q&U"4]7B]?;Qei_k11,(?>e;3Qb^A'eX&Si7/ + !gt^#>#?DKhZ>L)g$h8)`p16+6.[`Q!#.,$]g(D\.FMhsDg*7LKHNZrU"j>UgU%BImN\(n= + 2I(Ka)c/I9N`3j4%>e_kBp&-4/C8nCo2A@7Gj6U08(act4d10)UI9ZbI4<<&Re-hJ<'%ZNf + N;/%MSW=X,&W)RL30G"O+D_Ae-8t'%/Q!^<=/(PXcNm2%-R``QpQ+!EN3uG'm>=I!-F1Ht? + i7iDJu[7MJc9Z?25Oqheu".6>o\"KI".YUI=Yg/2B + @cdS47XPAQ^oorc$O\$(GS]G*T%Bg;aMfBA7&&]5O>)!LIc\G885@grB/SJ)+1;f66!T:"a + e2pQ<.9?H,;1DdQT-pO;"s4njLH\0_W6S#/(UKjcrI*JU&<.K>4`Ga9e:R$aI#DIY$M4-#\ + @q6uIHGnru7(e=D0Pi[0k=dBmb5lZ[Co4Aa[$N09X1%EmG2QVBgS(UYD[6*:> + OPeYpV-,$R;@BB>UKcnnHH*&`>X,$V8j@-+!PE>I&[C%+G`W$K-BH&4GKPC\h2=)>q.gqUT + "#UOef0)r0NN0Y@M7C4ed)R-]Zo>DjEei"5idoO9bl;AT8JHG@!q.0aY]-K"%mH;nSk\rd( + 6^qHh@D`\H>U=m^NaYIqWS.Tu7N'V\u])>n:>=qUqX8T[])+030RqL?!Gr-r)Bq#B7,!IrH + '!'iLQ!FS:uJP[Bk^mGuAi,fPhLP533d\;#J690Zhi>SHo>`0`s+bU,m&gE]:MBPA6KeT)5 + 6J,EKiE?YC&C(g(=@EH,]K(U`kocbbHB9UOn_j8p*iu=Ql]K"F6%8PuOc+>9 + DC+JQ6I'E[ui^+k%pC0#a[i'k:/ON\VqFaehEOR7s<:1A5!0fqU< + k;atH,afNeM_a0bd%AL4!N7+U+9kdi&_fK^t + l+FKL!8luBIW(5i_pDI$6/6/VER$kl:tI-`SD0NT%X*]?+tH+'_kqQ/U"-`[84fJZ.s;EHR + laeI:^QALJO0>m"^45b`/&LD0L>u1@%InFXY1s]^pu`UMYO/tfVd7.g-*T#!%FV,W"au[(. + %RJ"ZTHe@,drH!)7.3TtfEm7am?LCCmZZi53UQei>DTZP8H.O%7Q$93Pb16#5AYT7>/ + .VMLMsS8+isib$qI%MBKZ5>aa(*REPMf$%1a[8qRZ,u)Zg"eRk1`49fM6Ep=j&g<7N#qsZ? + 5'Igt6TKA=cMt[l2@rgbD4oT@g9X:auF&W$@&::*tWj\eRT&[1@=2A^4f6$UieG&9O.>e)X + Cc7`cAc,ZK6)!")1inqAH8'WLDpVYm6>-f;T_(F%d2FZ,B\a>soOa34cf2P!O8sR, + eWR+^DbX=c4_(ktK<]SM;HT;dW&7#)e1c5DC>1(qCpZO7nG+<^RlP,h]LfIB1+?BiI22GZV + bHVmea,i?CZ>T'N+qS1m+B](UMeJ>ji=m_-9kb;]cbjjd#e[tl>B`>Fn\>UP. + ,:AadG0$f$'_M15>C==\gNN@ddWmmT7R\COEViop516?LV1t_:Z(5iDU\M9Y:fiR7Q)K3%" + oM>TLfG[M#'.GE,E6QcJ8.:n6=%%,o=Zf-c:p`)Fs,cWTjrkIYgSZhAk-l2omW2DN49-A8o + ,ER41(R%c3JMKE*[ON=k#FJO5CA=f=W.0W_noTe1ZGB3`h#db5t1h-jN$)ET#)$5=Y0g-O$ + ?#tAQc8n`fB"D?DHV<5_AW3_uONJ8(*gS)pF:3XSZn$fj@-miu_0A`\iI".NnQm0SGb5r7r + -+:nH<35nNFn5oI[p!Xhc8s640#sfU%BCech^pGX1[&`LH5<]mK/+T# + 15&ULMrZNGEtEGhH:VfmHoR)XZ(c[sM3=7sg)42V#j/T2+Ns=TNTQ+5+5TD-u^rO1MLmX3&KElTee + a/jgrC,"lZ0^2)&l&M@W:8#_3VWsn!V'i&Q5m.XSNWn?7"7IKp_;#jC509nGndO;,C6%_$U + jt)QUb:U5MKK9CllX'0'd2?@+I)=];!^Fk)PL+Bd$G3GY78f"%PZiOE8M/0jUTZ%%e0-%,: + `8CRe"^7&2,a1:H&i`3qQ3AoT9^tE>9(j'%N*QoarH4cW^rV>h6)dp2+dBLn,3_CpZuW['< + $O06l-qI#G1[faX8Bc()5%TXOYe*] + Lh6#VJ"h>MOaScY#hTbW)D=HG>>lH>QreYIL:[l*eo"Q^Ms09M0Z,j^M5iM2:i[uRH%P5H# + (ul/J!XZUC7[fj(cCFAVjR-"M/4Q4i8H]N4`D1*cnd'1':<%6N`irR=OBeLb0'J7AkcB@Zc + Ar1p2!JXQ#Af]KWpD%k@`gBTlKW-nE6>(Dq@%nP%tbE1!Ak?!T^:&"$=bcg-d*#*nI$`s$Z + ]jYP%ltlndHY9%)Pa@THXEQTG53Y,uS5%Eg8)ZD(1X?.FOn>Eht7kUFP9F&'F8N&M"L!,:i + 9j&FgbQOWQ-;bq*B!&otlA:KI&Wgt#=W/ho49A=V7k4P0*g[#mp5AB>P3HJ#K#:@VAnYujf + l?MP&+NU^]uEG[G6UDGMl\T9%Nc`l2'/@nb6O`.ng%G)IH0c>$tPNb:Llc.f)d,+;l2(;fg + D>=*'k27u&rmA=8O7`HJpu(QV*3L4\F*O*-f"jg`RVj\tn/!,p]f>L+3A;g%loR5)[3'?B+ + 0Jfilr,#:B-:#A`Nfij&5^Ac,]k_VaH1A_&i(^BT#m-V-4k]NbbfafQ3 + VqNW8Vn4_##Uua:U\m$^+hoZDR%;XUEVV-O8BR:obt=GS!-e$M&iM&rH]uOq]*M.hhkKF>Z + FkWC,/q6hEf81eC(L9J4c#WiGQl;)&^5>q=@T//$T)P05EkCI[@W7W`-1CFLk+](QLeLs\4 + 8OZ+oVSJ3oBpN$'DYtm%?O\,"rpeqPSO^,?d&G\t?(2^#(P8a8g+PUT((NFjsF"4`jK_pbP + 1NABl#q[#jb>%<8r:T,X1)U8d*lj@P#)n0YZd + g8hFZn8p#F"*2;rp3'XU%H1"TUdiC5s9"C(g/2'gRW\i*ATi)%3el7,qoG8&nd'BJY:F&V/ + dIZmGgH'EKA3'7gd_sbFk'd*dp;/a4APP>dc3kTI9uFG7TK+OP/0X$d774 + <)XH9VP?qWQB4pgO:fD8HZZ>#D"W\'p2sr+ln + _CtSSAHm;!Y.A.FYPnA2!"8#;m8,?;lJgi53>`I`H=s!Oq1Pj'a;!N4ALSS'?q_9.m5MK6CB.T^E5$IK2 + "m!C.1riH[YQh-H?bF>lL-G<^2Y72>B,:(p!,FQkXVgq>Uc)R&%2<;.>,!Dh&1:c/Gd'amj!rg:)*Bb1Rk:_,N>&t2fEB + @SAE;<['1m,Xf>*n"?+/6E(s=bF&d5rkSDW0Zd.dci?HL)l*K+LJjL4&G, + XD,&@:P<8&-jP%&>g=!;2Kh@0@%Boa3M`$^Io$M^a\$PWHBpK_2m5l@Z[4JLgNH)6fdRaMJUAmY1GmL,YJEr"d(?_o + >8612F%mQSik9?627@^Xa_?5?JMrbsJ!^)K'6Sg?'0!-&i`HRcfFF@YT/a9Q;OAB_euaN^Y + u@#6tBKf*e&Ou7sA3EMSZ=3=C2BG + 1dT;Q/ch=X-6iD(UPQ!HBK>EHnuJ3/W%7Nm8K)3oNmH^u0HF#4GV#!.e;c5j+($pS$:aN#E + e4=sKbf/Y]0]N(RCl+[iAuojLtkFu\*LFBsndhi:l;NS*KlEA++EKqnKPf3%i>pQ$")Q!u` + Y@SN&aGFr?jM0_sW0_lM#=W3;W]%\)IH]P.h[S5I5c'(%aPB>E?ZTERgaVg?#P]"]u6^_mVK,L54; + mQUo3E6rtKD*VR2U_jGfs18+aA?ZXGZ+i*kF@k&SBdma!C<86IXl[5Uc=oa)C-4e:_B$IL[ + nlFpk<`JM/gbmE>r,=(F4M3?4h_RTe?#2_+YE5hkM)=\2l:Q>Cj;2NOZT_\:\f'5TBfboG! + uP%@Ud88<]mr?]M^#],FVDgUK`-Zb,o?]%;RZH`7[FMl)m?VUq$FECm&igeF1EVq*GG2kuL + 7Z)EnrA(luj<^Mj--VSa!WRan.3AiTQ3>hUnPS_lN2J&q)b@[_J:ASSS3K!sMpmo"oja!8uhj9<8"0cZ4i\[Z%Q!q< + Z<2_rp + k)o$+3L(E'-!`>]?.X+MnE0@]Gn9+I21 + /@8^Z;#GT&rr]dU5!c_e>']p`k2RWX]rif#YuVu,]2nR8A7QbP2COnM)?48hiR9/:_-WVd( + &0o9J*2laDQWqY,][-,::e\YHdIXEZ_p[9&d!5B?E*'2K5[Z.DV"IY_;BmG%+>YfoU;WGTuKt.<'4q=4E7UYV'+DR+^BGb1WNMj8[:3dS/dZsgm+D'rYaHJ#MV>_1.6UW2 + (4?B(-roMnt[-B41%,`[=#g!H3(IeqCJ/ZGWa:>>gfLCIM3k(b((&l="Y,9ljS:;j?31tIN + RWW9B;[=jW]Q^pBp@T1(g\(8CHnR]a],VJ`cY:d@DBBIWahe+\2JWk7):@^2(C8B+t5"O8V + #N\!b^WiMVkCOPNF*&!+HVX-&rMeJn7G(Uqr(]>+r\^f`DCGK8:I&!PI*_-4*U1Im\#I:h; + dX;(=bep0.lb8\)Ps*QoG]%+_;gGdnX!;oKVc7t[\(WB[dgNrU/rO\IW.Bj\ZghH:p)[gIf + c`nI8h,RV!(=f]!J"+p7aOj>R:j%J?ll#_hh]//_r1QUL?"_0p!D7^&FSf+8ODH&0MMX4'i'nJ\[`$Lf9 + `U%"H0h&D!pH-K^3)ifTQX;?%Sqa!9"M"G*HkP.>,oQhuq=/=!T=o+r[hB/4/Ck3bgQNd!X + 9cR8)e%1U?EUPObj&J*`DifrC@uacrPMjBf.5Djj"eXSE*4acCIOq`B"#3$ + pD<^TQ#jOm0%ZT1I?S`dh_3=Lp%J]CA7;_Plolp&a2YmK\Eo5C,f[o:S%4L/?2iVgkB$Id# + *$.2cKV^IGPc;PgL9C?qX'$`AD:NWgKT*Oj)qq@mPjO9%h8Nk?u^GhDFZ:a^Z+jqYP-!uVp + \pp]+'e8CS\Z3^Y8YT%thg5n9LK%O&Q"Y]CpTb^AjC"FmSu'+73F=lghUc^ZMTq!5Bhu#_/ + ;)/.i!#-+j`mRZMn@J!srU-OR_PZnc?]Yad]_TB;@tBblg68J/nG6)7Z%&QtkE`TR&k=[k- + 9AK"&#JRX,L1#-mm8*,DJ+IWoLT-BQ`q4Ap(;G%0F5&?\T=H/<\8BDJEWS*4## + LSJOn7ST-3>%WtrBmAbhWFFSQ+ff&J`ZqPM7b/BYSN?bfYAAOiafuijaoA_b>>EeZD7bPU\ + K(moe[k1<[e%-Oql]^FDl>SrK(*G<$c*hH:Cr;T]Or&$,08(RaWhdo5Kb>^p(PJ([Ee,>9%Q:?K3L'1G%_*nM8?*b0^(R;cE?H^LLs0rU$@tgGbb"G2YD&a7&o + 9?fR@M*;BMU:5RMOLMd27PI>_/;<3:`V";Cs**"fROo+9jVj\@/6`b[FldJPemi]We.6D2sIP=f + YZ_QQqsl=`NOTa!fTKL7"qPTNhd'q#$sUaC,E1mQekglQd`tOckl&91^Qe-"P5dP>ZR!8bk + :nAFuU\&rom_=]$U]JDM&.;FIKLckjRJ>&,^->;\;meKr[=C(g5H9D4u3-1aQmN=o_XH&j[ + `1r5TCqI[r#T[[Q.\"s9YfUFGHRo=I92`B[Mk60h8:#^4QH-kDPo]pH+&];og#i]K\ + O)^saaLOn44l"9YEs./]3S-=U$!X*FPSBRtC(T[o;+,Qp[FdQ2A5jS[2/fkU^C#F)hiYU$[1 + W?W8rds7qu)$m5jD>D&QDN`fK7V]#/I_mHKrWDK$[C(+*70Y:]qe(Fd(]0etcM]p>lLBuVK6c7d!m8l&&(r@-C[W0O3*>`)BZ<@P2 + /-U\u=943PbJT:$273nj>iup+,^_?i$ilmB_O_I>?-Qj]uQWFdUe_9-$CaFM8[gBuh$IKt# + ^p"HZC$7L0OHja%h1*>oDFkM-U-d7;)WOaRFMuF=$ptm&#*1UW6njhmW$bpa+] + f;4BadM.2G9qoTpe#8l5/OX4YOY[I$m"@81d)S>Q*]d*p%e:NFuObD+$1e5ts61DNO+O7K2 + Je"h+/4]BUE-EH;*\"$5O?I">CYX*LjId8>-5h2a=%='MN*c4kjYrY]G]?gNcUYkHAsgDb?RD=+mCpD + 2`CRtGd"4jPHDaHR3^4Y6coEXctMg;%p9HSGoaJ+%7N^DHKjDuKXMGOGWJ2uA!->Qqh0:'K + UGc\Go@*Q2SC&E8nS$;S.jL,dY;So-%gcUmec0_lC#SGB@Vm49E\rZ3E^m'TSB!Eqb`'R=qD^2OBg-AG>>Xj//($X(>P@:Q93Vl]hj] + 5AH1Fl:_#RI?ucU3h`#mQ>W_CO1-04+]bq??1*u-4BVpcnfi!O+H!YYF???^CA(ILUZ!(BEBcYP3M'iVIcIms-Pa%Y`o,AMKU\ + SNNgV/*)hnGX9BSjOgtaV3AQ%D3e04`'KKt;0l*/eM]'(#S1'=,0uKP1],dig&fpP'3ZFbA + $l'/6,J$7=.8-O!F;Z.SWBmSb6+G1FZ`C[\/\9X0629061a*saYmpQ,69+&43WV<82QDq?W + l-;IN%#m5]!J.nTZau0d]tt"&Z\jFTejMA&f3YT__b9(&R.^J\JDhn81Z#F&bAVr4%ls(9T + jp=U121pK24VV:c/o("lN#UlgBBj.=s%rXW)-_LgYoB1'2tL^?L6WbNk@Eg`o2B7%-/"W`Q + mF2lu(5Y/t)Q?kIqTmXhYeJ]N7o85m7?l:#M_7:]K=FBCu%=[fDo`OoAC`*3?M1P2t$J/hr + %cuKCbrBCqs^T:s1/+c&qI6V7T5C@+@KD@u?>edXV^U*i-b[VS2rj\0l0RNl1WP,gQG/,YM + 38fKU99;.1O[*lF0cYF'Mj-`#J;J#m3HTWh.%J)r(e)$QHnhME0`1Z0W,b$Qu#kU + :MSF.3g)H$lBBdVQ"YTZ@!TPRjkqE*BM%/WSl>_gTIjB0=q3?WZ0WoL]X_#1OeZ,u8B46/$+o!Z156,O9FaudJ6K"JoV`5>MD_dOG#HcO$^afJ.X_lP + u5]KJC"ik4#dJTqh.qsK#3&HLa#Kb-VR+FOGZ;hVYCK.CPaMEHE&"TV(CJfl&8>Y-kB8tt* + :`NaF$dR;%4?`f/a4d^BrkY?\j'EJU0Jp8AgX&B0Y>g0:b9Y=Y)bf`]0bY6g<9^L,"j6HdI + -@u@k'#8C>;"$k)eje/)5;[/qSCpABC.=,19q5rmSCc$@CX$tY7bP)em,:G3E6^"S^a*s1b + iL42FMM.)#aBO'BuiPml8_NE\QjKs]R36)In/RLZ(-A1lju22M`L]fZ1Nad9SUAgLX0YL:O + =BYWg6GJa2+OLcb,7Rb+-f1dQurUchrp*"HpV>qa)b08Q6qO;_t'tQn"Vo4*h+CPUX57S3j + Qo1X)IQe1NQL09DDP]F6PP1oR6]2T`2WfN).&6\u^ofjGq + (7eBe=@8K@jo%$W59"=d[\+9\$-CGjId.*8Kkah8&m`18%,%&!6c:un>%MRQ266:2[&ZpbT + p3keHK'N%e#]SfET]be'3(m:.&!%7oUpg_^`Dh8N=JRomcj'$t(,6>Rr`S`7DcoD9n97u_H + "Xlcni)g,LC'@F'[>70RGX_e8/->.s!:UBsUNZd$+T],2;Oo;=jLrNT/=8:L;pen!niYA(: + JCB,uNCmEG\6Gf?0I9(jc*g+&i`+^l7hrg![,b4-_Gr/La'DY>PuTgjp/W`#:N^2=V:AUOqD*,j150C;bH'3ZC-'m + \_?BtH"0p31DN8:Hf*eum$as+fRS]4u:ol.`]=^_q]ZKgUZi(B-,nIX/HVkN*ZrAkZ1cYOu\k/l9L + U">a4U1VCtUAJS^uA/fI;g5.CEkqUlW@ + ZgW:\Df$/'<*T"+YZb&nth@u@!SI!T4RmO-XhGg',:$1M>!@p:*_WsJ0*U0VaHV.:c4@6Yd + e&C2)2QRpKd:Ijc*`4J"o$N_T?V)a)>oQYYDhU/<"+C%-MC:Aj$AU"\$L["u&r,E$#+UmX^`>JJ?&P*Y72kC1a&QAc?LXY&*77D+2NR"OKCXfKEjbJq + 7SLJNak48[[N3*Tq:IiPlgFVKc=bG%[ru:(AB)m7uXM^srgNi[f/T]i/0*Jm;>&ibg)PI!N + N1.Fjps@^?p%`[kTJS7:Q2iq2in"GL>#@Rt?G@l'rk:"DkKf>u4h\u^qPP:Y:d+#<6=A%g\ + p1U9SV1>VjTgIS[>N`)1\T + fHp,r:9@Bb<7k4E`OTW5^0,4h8cZX5J;*`NM+)"+SPm0%jsFEm?>X(21hG]Rjn)fs85k,M, + 6?9]hma1%86lATDW^*!B&:IB0#[*?`GBG3,nNTLQnT@*$LF]jm(%^u(^h+nV2M=(CKh&62BPo9Ej-XM7M/FnMgtq + *f*\Q7IN84u'eeX:O*fn-ZM,0,]h.rq7[Pl4XPN.AMcgEgXA\nl.nc$RL#9Rss,'"bMEY1].1q],f,4]DbEdBe]63T&eGYj]2"-kgLXAJ`BO4O(1 + )0DQBW%^,6P9#Wm4$A1cjQSjZX + dr9>=nf7s<3QD@Tc!6S>9E%Xk(7IK7/WRqETfG7>'N>Z]-@VY<(E4jDJW&oVK!USDJ;=0eQ + \Uao%W*hj/]@7 + :5IF-XioW%D%9lPigd@U_V7gerP=9]=2KEr->cjP#CnZE]6Kb-Ne;/g]a7/"O_G0s]@jq)_+K6IFQ + pD#dmLb/[4mb**t#2!"1Q[/MdH[P"'r:eQcn'QOZ0$=f8-bDnYE + j'=QX^6_cVHlFQU>YgRn00AFBf`%errD>P[fXmmg\#cGG8TeoQ8rXj5"Xc7e_$?EVb#A#Qj + Pqf7?41E**cVG5m7`+`Q(cl5:4M73YH.]&iqANCP45f]qHT,&0[pchT,cD^q&1-$&2G9l2V + QbbgSIRatt0SA3cVWVlR+6D?<8D'b_KP0p,T?gSSn5,1rRM&!l1?M)R&\,80mMK.5:)?4A9,4C[ + V%=abNE2J,n\A29`osMfNgtE=bgS,j1/1\0f/1a9I,-d,;V^Li:9rsN43pj4[3`pM4T.Oda#o&B+ + 7qE%CXD]NcULd]>p2\c1jpZBRaaJQF405/2bp";jZcO?#;N8Te?pSs<7M^h:KF+N.B-D"22 + ,UFes3p\C";n(b?e9#g&?"n#Rgp\TSFPRa!STUR5]i\#&?#At>h:>%XXKC:^tUX(s$l\#u3(k.Z>7-3hGpCjRe^Z + ^W9?`O9Vrq6@AG4+VgkDT9]^]P[o@p4;[e^AksA#X8TI+NT-V`1b8E(lG)%\h]?;8G=Np)Q + >brbbb6k>*Yu?qJ4HUI2[F)oZBBpU8[?LOJf)#@]HGO8t)KWX.?i1Y]i_Qd'BdEqUaZ-bGs + d:0-31IjHZe+:A_GX3ti=IO86Xr'#RhQ,D"/'SaO5\a!0(Y,!['I9$=O_MTVMdDYT=1gQn8O# + `#OD+Fq?t770fM3+b[(['1/'`$K4*!6NKFp"&nUoiM&5b_u+"3Qb4b8Y`)ED'gX5BKb.]#H + quI74iDT1&n5BBM:!q'`7Uatif\D>M?5=s2$X[9r*f9s:r\biW0J!uRRNPaVc54=,QF/bKY + C;@_j#ECLg0_2 + kI*X,b_e=*3U9qq'e0\[$RE\1#9bt,ng8eVMc$F06VTh$R95^6D[CA.Bo/PL;j;SR4^J:*q + iER=V+YG4^34BnjaKpqJA:f/k3V;H=_ji%erQ[G*E9N/G"Xk'*ePMEf;Oqq>Fq2dL]16o/W + 0pmA1gSoQ7t=fWfl3_4)NYaJo17_(G8[-ZJ'rXA8h!al'm-=>N7qSo.@_lAa<8fd+[.!c:f + .K@fEM>K&H`P//HgTKd0.j=,t$Ve)$6BF;ei..C8EkheP<0q>-V,neQo45I]c]Y]i]%rA$" + 4;]h6$tP;tG0h\:d:fWrCQ22s2+K(PC?(JeNq(]P7u6m\o>RL,o!2eX?'>'GuZB_itn"TH&f0\` + @]b;jh-PRDbIWb0A4V;QnoLd?P\VV->$^t/#F1*R2Dr:9l#H#13LfR$;hE__r,/;6#7kO:l#TIa=[V + 2YRuH57L&q'*WlC='ES7m%099M#QW'`K^aF"6\nnP@UM>';1'\mqrN4ua!YO?S"TBXK[$FU5I5GL=i0FnWGZ3lGoaSd8V*sF6Z=V-Ij(U^`8&d%jDLHHEoa@I\aj3)h$rHnfF?DrN$@Y-S$OU+G5[^cJm)J@CV] + Bjke0m[KO3nncEE!4R+!F=?T[Ynn&H0X=7_AY#/eI7joXSq&oY5V&NH[A-,I)LFn2%E4*bB + -SetJI\I:9Kq5,1N(k'^$P$pVdjTNO4QPEudKgR8;0VU]P)c9j[ddbj-a*@<=j6B:ebLT&% + h-2/tS4c`qj2GZ_42qW;sjY_i9g!eN + Z/;>"S[U`SJ#-N0^B**Y][./nXAWIGon3je*i=Kra%-l90!g]G00@,L,7OXTBPZ+d^M0;G(+#T@6=)E9)H + BAk'WSb>dqW=="j-U&QIi6(RVZmIgfIrHHr`=b + lL(rap@fQol!)@:HN'*_mE-"crTIL6KLIfAh9ILC*`JPLTY)8psAHNF,kpL`YN,gJnGj3ML + Ub8UANj-U)>fd=^lYb_dNp-_Mm01jH"qYDNn7r5)D$H)?#>fIHODtNPG6dVa#Qr3k@?M>_- + isY94Ueh*0*&1AaU//]!$gn=i1:6.&G)qhnP;(7%j)1`^5FRhMD2jScRfRDrprt7fW_UOn? + WSM6OjW!g!JeYDc#.!mSpAlgQ/]c^_K(kB?pWJa4NRZdh*o6:B\ke + >o.73iGY2<$F@IKrJ`[L\!^A=u?-A(.k%p/i*C.2#Y] + \r_>T:/.QPm88h@WSAt?c&+om^>&`n9s68`eO9BSX$H$L-k3s"V%W^a69'>[bkUI:cY)NM, + Ati*[G7Zh"O?Vpu6L#qT*DD)+MG46DhZYQkVN/1m1$]*aKc^oo+#34+;>VGtruZ7`+4:<8& + dKQ-$R$(7WF5LJ_$)"/SF?N$,"(c!?k`Vbi#=!h%FEi=YdY't(,SETUZ:cH2M^.dk,E + US)NKj'0QO]0g%WLq0dV;bno_!1t&$KCQ$ZOkkIh6O:&C5J2:O8/n)Yahrh92&XNTUpmOP[ + %='+^QG0fpl0rq_WW'QHV#caPFN]]!IC^+[?:0?4gnlJg.7(9cC,d;o3p#;-52(TG;)ObE_ + rmhj>M)!iIHbU13KlP[h9)@?-VCdS4i=W6^R)_=oBO=)d';d-t2H*s@F(7+lD`5Nj$fIBWkI_`)Ij,=k& + bCa(_(6/us!=sP,B1$uPFD)@9dGlt3`DdE1-I\>]fB#t3AP"hlUpf4ArX*5%aS!"'?[`rG2 + !XV&6%0lO)ZRgLFCO`.5U-"&t$5Si@4>g'!e>/EA]5&9bW`n'BVFl/'Sc(ZMTR3/2:'SEEO + ir`@UK$/MGJ>;/F#9ha7uu/hTEgEIBJ1m2.R70.-oVEK_8[Po5N5k#2KbZ)%I-q_7G^!OEL + Aa$t1@\=ld4`\*<-OiJG_/u&oG":'H/iL^6F70JR'1f-sO;<7t&EAk2!8ee0^1o3gOWCk[H + 9,+j-oXdaq]h=U(4FuE-!C3SRp8FWf"g`gfd%DA3FoXnt#!c>q;n4tgQVS=a3q?3FF/M&)[ + 83!W:U@;fFRr>04Us5S4;)2Xo>`skCHU$c_'U]0idf(\B + p/B)=5]T,@k=0!O2>LU%qd:0=?rd/5Q31O'#@PFt'#4-5rosI7:4[sS,T[[f[28_0\.n'uT3_ + !/7Eu-cV=$(I^5BE/W@%_$6!('.#A/gk6@M0dtQWea + O]F(=(-8mS?Kem8+De#)-G;jMc=*o.XH"@Us;0RdX2I022?Y3>J[P]LGY@T*/#C6Q9-rtUl + F5p3<`Fq'lBLsj@p59Q,O(gAIBkt1XbIlaI$#2Aqi0(]QpY/b+)(^nQCNLc[Q%5:2_(C`$/ + _,b]nTe7i79O^trtIl;f%3aGTX(!st.eM + B,p*>FKQ;:dW>j[j?nEY)9nQT]FM?\R-BFBE11(hn_,CPNaFLKJDKKFeu/3"STn@R,KeFP% + YeNWM2'53#<:U!+D:\TiqZG*o1Nf6<,(I>[cKM`A1i[tAjkUPocMA[d^_Q^*NNms1,GB%NY + R#mGWt3G5h)N_>:W2mZIb\D?nlhKQdKkt-8>o4b?*C%YqFp8(fQ\SAFaA[gOTG-cWqbeCNt + 6?hmJ22?[%h1)lV/p>QkR9>`AR?uf1N&f(+\B0\HUJa,D4RKI<'eO_iF,e^-NY) + k`G(!=%*IDkp&iEBsYEMsePE5,*2r1LuI`jQ67*Wh[2a=!WX2@U + #I"\24f-b;q]L#R&cdGhln%!SU'7d'>:!,%jlShGh!n.=-HO-q6Cn:NOf,F>:Vl2WM$g1T^ + RAjqA!\4)38g5O1(<'[[Ek8"erO5i)pS=GnYV:(M`VcIQB\[)'bqWK0m+!V"Vd(=8`&b!Gr + N:J1VUuic(a2?\X':uu`7/Rg>(^'`LKqEKE12n[/9]j&t!R_QPjf=h(9^)Q7L>O'Q8`X1RVHbem\RbPTmT>1]E&0U6IQ8P0,*O374=$%R;Q^mbPs25_MTNa&C2m($CeW-UTZa9*5>@W*r*Kg57UcM#T)G=m(Mgq&7=W;j@0nH1F)3c(uD3_#`[B"1+Da + h/XVcd4n>'i`)pKSZ>K/Wg`Fj3FfO##C61KP*,1HS/Ia)S\`V4!/s\W+>Xl+T:OcQ)])r;4 + fFrQh#6cs,!=_.41/$35S]M-p_l_8F[TrN2cCdRUqNqWJs9XCdtTq@eQWoMr&(bW44Y8#_qQWY1YT=L#K4'!HM]X#3YPJ:]>`C:Zs"Qk;uak,:WD-f + `uA)1F:lQmHcOeY[VOiNHYpil.EL7dCg36%A/bq[ffRtdMl%9Bs1=@9p>M8*#Km09h6u^^&/WO,CR-MODnO'$*6Y:oo?A'ZWB7eH8j.it + h3^:GQrm@g;7Q#5fWUUFEY[[sm.rN@!.]e-t7gUI&4H\(Os_mjtXm9)s7rn@E7Qec + t/h;7H9gSO??!<6c8?r0=W)@X!&[%U=;>W*M,KuBE78@/8hZA_8J0t?g@@fMN\UbXqc_F5k + d8u2slC;'VmQqXA53L[Nh3+l!aO7mV&I6aFOBT1JOrf'IDdV4TM76-i6V_]f;Vu&6O=KC"g + Q+HX'_-O:slrI=:T#YQO>-/RPFgg7F\QeKG)l;EYH+Ml$(X(A'!IqpmYa?C_%h4B6@ds0J< + ]_1[a^fF[%(8qbQ-#l1J69B"qEPf5.l74grs^[,#TnH^f"GKJ + rmMpmbYL+,EeU5P^i+G<_eKl#arD?:SA,>q-M[mUiYH^Y\I)r*E_@T?61!j!'d[qrrGBh,PLdr05M?s_/OK8#+VQars;#RS1>"7nC0$"6SRjc$lX) + \6up5.1e/6JVtnFH7?LhZ(7J**A9(/%ZtC@JZ-7*+)DPKF*hVU8I3H[0,`a0A-Q/U$8Wm>J + -qWuoSL,=GH2fBf'"kP'960[/B1rsN`A#N"&]6jQE*'@HhBc + ]c235#;If"[Z$B0tq3@oL0K_d<'LM7[Z)5,]D+qZ2SZ><6[+-;j?gc:=L!RO%`N-m7gP%of + Z;R(tDVkDUoPf?@SH!/SeSAe\u`<)hoYS?e@nb8 + VjQV&/oH7&kW0fW+kcE/M5(?f/"n3/mZ*;P8MWr9,`,9fQ)6!F?CL/YVQQF,l0/gU + *sbcQa/&4Ia4mllC+a1D6<6_hJPDujg]2T[d1SDS(@&AmlT\3^Ml:'#Jb;K(CS0#r)X?IEB + J.Ua)X:t-cbejQRo\6`5)B=F072#cl-1'Vp]\#1R_Qk4SMkV!%o.N=qpK(*sQo%Vf)iah9U + '55k#kErc_ZbH/a$s2hq"ChGcf'G(*eh$cMR:Vs/N4J$0Z81$?"dqm"bf+FGqDOWs_<,cN3 + j&tdW)IBr=t0E(K$k;*%C>grUG;h+Q?J#@ZF@O7*(sFEm9=H/f9SUTa_dCeQVJ(DSrlk% + i7"_L^T@h0"'.?=*!k<$ecm[=I?f=df3[u!JA?L%2Dta&7L]4LTsC[`7VC5@gmf@1"JP0L8 + u_5nYMPW\Che\>;hKMei>NA#gQSr4-'gOT>(B0/;N0+'`c#_!XDijK"T-Go-S#h0S_Gr/"Hm[`"nEM`f-L9cZnmnjA!1m2eL:kS!/GB + :)@b)hJD>#';aeRB;g\oZe7eY@I?;Sk<2\ci^b_jn^"\eqPWrOI;d7&co9mMhm;rKn)ZSpk + P7I&%1c6R('23a&Og)*Kn8MU#>qEG7o['hU^CiFW[^S2eKE_$oVGST3($>I9eV1C%:N0mND + 3iH2d+Ym)T.GX%r(Uk>LDp^*N7GGXa=? + gn@aUm5<]3kq=aY.JEQ1$qM!@;Em!>fD"eo'Y!?eGML2!HSW&6RLe`::jOj.UCfJt*$/2W` + WdTTARP,TfTeW6'*6=&8+-"UnPQa2odb?:;pB'f2ZZkdi3g&"3PD'c*eG,A5q;!o1_H.W,* + `,/^=QYgTGU)H9,h-0EQ,K'k3SPu-=+j%]CB-cNJ(E2AiH^-WEFi1c]WchZ.h"E+Sn9`X93rBRqSIq[UNUX2bt_+ + 6FTE%NH6:jSDD8mR&uKIE]Sd[=$I?VVc5c)5O<%7?DW%k7c)>;Zd#<(5J;Q[D?'GbufelD# + &[h,D<.pF. + f=HVug8JF:sfHe1<.<[=\Eb3",gq-uY'!2fs/LcpN"-9jGq'EqJt8dek1MLIa8!I6Q1l(FBB/`,1mRBM;O^3@Qr?Wnh^?mYWCfBkh#S,WTKN?`Bcp7<-F(pEnKcSKLJjG`\_e7("#8a(t\sYW6I8=F@_ + i3AF6F%Bd`ah"RC#G:hqsZ^Gk5B?YY!2s/oYRe+75cT0T?kT%MgKCEZON\D44"2C[GY*h&c + =&/m'8#%&#IL\Z>e=,*$;>%%_E:i"Ze$h9=G!Pf)Obm+IbCd0D;4IliUL4pAs0R8k + 17-(]j@gdHZf;UfKKN9-[uFj]JUDV([#CT[3L2[IK\A,(MZiA&VB_i/Fs]?D;b)4\"REmgm + $q=iLSNp."WEuAf:ZL,3^U9Y%@:Z"fbT*602LNWm^GeIVr;ZG=T@s'*GO,5-\+kG + ;jo'[ip\pfC]8=o4)L@e2K]E-EC]UZB:I$`9N\Ed%YVqV:Z9%=Z`@,]?@iQ;(Aut(9^BFW&"+U!es"73OU0M[PPi[;Z/^fB"VmZY + R:K91a0N$b-4hiQ#"UPdr%;,?G,hEYbQ3O&SJR.StXW_0`0BZCXon@f.g4V.XTi-ZeI3mgd + 8]7u-7)V^9bY,n(:;l3h`8"1cODT\r8H<-%!(6qi!$9*eImnG\7=o' + hE.7p7>/a;>J04mt0NeQ)%N_,J`WQW<$k,TN@E(-47J%&$8p7U@[[tt[]6`j1,?VRZ'>(nm + T4;.gg%`C8KKK&*4;2-hZbdgn*'?$q_:nlSoQA"Qa2/M3a+=c"6d%uo&40=>4PY=X;[L$B8&Z0=7.d+o'VNSoTLq&7aul + E@7&A%*VTMT1S7$`!3)]jF'S-ru!(\+KmA&^' + 19747mF08/HABV4m7ViSIN+blF&ME;jbQJSQ&q3V(6]/7r<0Fs`3mMdm5&<@':l7hR6,WUb`!X6#PE7Q:8BJ3$cN + HZf9CYU77nRm#8qcf^<:nLIoM9T%c[>H4K_`rYqTk%!""5RL**!Ik6-o2,:]fASK`TNXiIU + Jhf4]1flae@j&u%`X@o7[ps4*##Lq'[/#u9MEAojMeb + %KbI/$7h]?%/BQ,+F>cg89Y16oCWAPhOZ\mNa..Fl7!d)cbtT3'bq%,WN.=6je\\Y7a>DMi + 6e+_*UdF_.2ln`)K6tktI7UFq^"\'.eD\+=NjpX%c9pX;>irP"ge\d7"d>g%R + u&`mJlHd=F@&N#]8`)bdX[HtmR/Vp + =G0)"/FgYho!qakfYpG7eO`m)N7dNWf_(?Z"d.I9P0/;c"TUl>5Q_!;X@gQJfnE2SP%BC`R + XgBeeJLp^0KrLD][`:>eW$6PSY0.,Tir^R7WkN)Q!;sR>d\sp[=qr;j1A/GWk64aef;Y&AJ + RRq^fEiAd?PVNAK03?6e74?;<5u!m;#T&,>A"\Y][I@FOE\(Dm6$ef+c_9,_c?nZuJY,]&K2\;+e#Vd[9fuA'/'4o + L;sa#'"S,M)`_#ie=XB29L"Y:Ig>.bCh5/n4[G9U"h2MDPKnr->i>g,86N/a_E3lJAr6Y.\(I'rKO:C/H:V_*qa8[:%_5@9VL]ei[nTr + ,Gb8Lg(dRgF]^$A[B5OC"lnHK0kp''3p.58\k9kD(+bh-="F\$(e>U + h++7#=h,.o#DW2uJWiKec#41Y;!SgqA5*U-.'H5"!m;_sKKe9fkq"6!/1Y53]3%P"OP7PZ/ + @g?1m#_(#Ipk+Q=^mi\i(A/g]LH-99Ft"oqI#BG(=<;1_On`LZ+"db9=+0%CdB!([4s5Zrd + mVNI_E=me*YFme:$m82(c@tgN3*N*D;Bj-NYh`a0Qob64gp>bhJ<[rdVmJ:1\qTP6%YJeT" + h&OIHrudPMgLMJ?XW^C:,/RW'WJ.rlA\;DnESuL#9lck6UkIMIa+1Z;b(K\-(.gql0YeMip+K&ME9h$+bQ'C9/8dYYV(=H,OISfY3HSm?Ldk(Y0oN[JG9C$ + )j2Rh`.tR+KchN>W"rAM%HfEeB1=K9;ItX'G(HN7eJJRBGR"G/?&Cg,@ldcgq:cBI2EjQ:eJqrXBj45D?i82 + M]cc'Y"%U&9D- + 3<24&4SjI8UMoR[nkgRLamBfaI8(OP.koQ#.[K>A>P[r()sjQscK#0d` + >`VOLY1<3^>3,/(uG_gg:EhUfbM=55a`6\K203('N#l;ZurN'5fWW'7=Fjt)q+_SKuqX>9I + +<")7]m^2:D[;R\hmj@\q2YCuXp$[(tNCmQ35Uuma*7Ync?9RQ=^AYE0?Z5YK!&ekJa:!a4 + ?jR=\lsBm3DZoQB"n6Z5E.qTXf#^cAamcYQA,aY^$u]L5MJV?mm.IVWVN'=_7VsIOA;1*=D + ?8h$P?-ht6?_!hB%^_f-X5[\AIO4C7bk[g_7,`e,(?%[h8J7>i8ngqi=8s\gbr`6O(GGC>e + ]CR(?jQ9l+Vm[::=SGMsK1Gm6iSp@ErFUcL<+!nMsqMmq4KK7lBaj203[oF*BV8Kob\a/cn + ijn$q>.pHA!.5.YE*F8u=5'iBLc$%S+N"jj/d=%2Us7Wte&jb[-dFLg%"3C"5!*aS^Y[0ss(/-EB9kdl[>LlU)k8^s<*Og3\EV9f= + `HUko%TeY/nil$R^(GBkaPkY'_pFU-FL.!(%X;4MT2hmpqsc:f*p4X:;Mt8>5(k[m:GY_B'g'sog.^U$c7Kp6=\p2AU>1V7<&\>lS[ + /[UJn&4RUbK^@-)S?ZU9Eo(Sc$pTLq#=1%j;[!R#)qgZ&5d!CEh;1^JA!IoNKDnUp]^A&MZ + ZrC1\&&J"p8:X65K]kte^P2HP=Enb?pj<\O?cN"V9),c/Co<692i[au:2IsHGmBI/LCj,+P + P$jJ>qhOhD2t_Yjp&B2QLHo + ,JfGain&pMt)mrVMF)[59GT(+9DuWKL0*D\1e4?!Ueth_H;ukk:OfmB + obsAnWE6-$C:1XlEjEuhfGmpB-&0ZSJ%0;1X%fbJc*Z7G2_1`Em;onpR?j>sLA'Q?i*Z6f* + X`83A`3)[[i<#e)gf>;n$kIho5TZfj)R-WSnB^B`8l"ZSM$]9\'XI6R!l-tDn"TkpXibi)f*X[J,- + c/+()F`m.^CjNKs\Hl1XuPs'M&kg\Pa$n*.(X5Xk5BCd??8%g9_13I:uQk.DcB684oV?:$? + ^Y73j\ao6≦W'76r>L+CDThF;\U>9XA]p(V9EEeR^&)m)Aa!%Eb\*al2Mepjh-3hRZ\AB + J!srUOX3RS>b*41AZV"C8gjG2.8'09Be;u4Pd`;=V4%!0N#[;4N?c+.cIf+#?3g_V9UEnqNpeT5#_:"sgHf8\a7h=n&5HX<&PomGY + ``Vk5Bp[/'5i)hT6KA`+d%><]-?6+`)L9XcLKHSVmL1;(C%LV6Nc9-U;)%K)tjI%VpPN;0>4\rj#Oi\Hj`BZ[2 + ^\lfl7IN'%jXIpDVmI4HCWDEfMLuk.+aC0]kF!S"n!"%E%$mPn(b&+.X'5EF5OW7Bq5I)g`0mD&f"LoRZJ + OE]:r]85b,/o?$Q6WV+DS[';:`'(=go9OFUAqomN*fDY_Vm*qU?U]F6TncC3#!2(#g#uIWB + 71Ai0"4Vu9;Q=Grk&*rq*,!Gp=:&9&j/`fsV;d;CR*fY + &*]"!AZtNL9q%Wu8u44U0icF,]0$e(SS1N`!eW7gSK%j1KMq%5"Y=AuQk_"Fk3M%B[]P]ci + :qA8G-iZPM]Ig8'@MP9iM2^k/4uY + a>Mkf-]%RHEE>URZfH]M104P&6:>H;:cO4DOd='5Z(Ud]Qjp$P.;Wr4. + ]4H=7K:;TRHmbB)#QYphY`Q?dl-_&S8PVu+I8o$!:NARVSG&C>Qq-P\T!P"H`>$Ds?2_OJb + T6&SblFIjbl)#ocFUkS.3shHpVM*F-8*Xm7BsR1Se\QJY6?#%t00(XPaILj6kK^!(M^N#dH + WNsd]ufM$h^-sJ'CL9Sg%q!f"7j)rK(;`rbsJU$qOs7YLHni97%cpU^/-kL5mfDK'^J(1O!@56Dr]`/N1I!'g`26^VR0D!npe@[gTm"`27;diRRTM37?%8W]5.]k"R + Z?fYNjYZX`=2S?/#A>P-#(,ejRHQcn*"J:p%:K$s9sqG+%VOP,WAQ9of6:CNDJ7XM2m+F`S2Vd7:0X3amX*Eo%pJ6nl.pKb_ + r0$#gJV=tdFK5[HB=.U1"4)^TGe%Dd9Z"QBE"cfmYO#"ja9q!WQCU:Hla.$:#/8Z/Ck#p<)9=]H5X`f*#3VT+Y$'h0#a$DmAf0d/E + '(,4l+WVX&QgU!;l4(o:WH4[VJTLs + 6+ZHf/L8Clae[O'OC@+:eLZ%AM8Nk-HDo]Q4Rpc^#A\rM"'7K`d#6=ik1&+nif:[H!`#^r1 + BDJeJN[mED.I,-rd2Cm>Q[)9KE&@oCZFnehWpdlbGHaoSd/]!.YV)_r,OTD"E>Lc[G82#7L + ;aj,!4&SahU;(&;*2ZuLSd`jj@Bb>MCo=NnCDrl6/BR>t/,CG..t=5hDl]9n%O6(IsEGI93 + t4T=2Xkd1"HR-plEs'6#+%rXk_cJt\lRC<.`R-Dnd\?H7eXXK_MW-U(tdq>]H9g7m$Y']TN + e?6a%MnXVBcL!.%m;6>"Y26i$D$2KTm`XVXn4\D8JF@CRfL7(T$hh?[ORTG1o+*baY\==2B + *U,-#>fCFE.%tqJHq9j#OmKS/\i4^4qOAj"FrW.Ai'h`TH92jortD&YcM@9K*s"ch(Y@/!+ + H+^:`.#X'MjDkYC?u,LCGGhq%&AmYE"_2TaiRQil_K$%'KG%3;SY!qQ.?j0=#LL[h'caLf$fK7!QjEgDRt3G[/uB.eBZ5>%9D#]>6Bf/mEG7)'j83nf80;*fs4O%ItqlDikN*R'5tKim + @';VV;V3'b>5d&(4#\%*8=$!>()Z&BcpC&'#8R>nO=[r@oF8Y%mo4.$r9;'//AZEB9L@mjK + j`'MnMg&PCH4!>Q%fl3iXfM8p@VW>5uI(/6pW9gKQ^7i*G+!j]?rA-5don^U0!7Naq-=[#g!<T[!r(ef"Yf,1,.'Ot/Dr@5[Xf2 + :=8UE9/#pk]I3H*"OO3DWa*rRj43c8iCYqIJe0` + H*ArmD'XAQ%2G&!SJ%b"M;&f(Wr`_sT!G`8Ek'Wi][^oHe^O=2:>7 + >)LqQRF"B*c%OA-UM.g7Z$/hMi2DZj%?ru2U\6?<_moIWCo01*$#6Zb1QZnk)A3CFQD>ljd + >5uJEu$k<86"9:A;+rCJ??lIO+1N[>#Y?2OHE2m:n7D]f@J.r#LoF7J5*MK8mZY;pb25o3("WE[(SI%5<2VK3KdeJNaECHCH=4j3b+d0/GV?qi + ob3-4)mC_&+L>i^D(N"..L(5&Q:gTO$,0+4_pBIZ"1FCB2L,<(=49B%`&rYGh!7&IFTc2Oa + u!Fm/s9X"7_4"#R;qYPj[$4f@&O[;52]"*?)*b0%<>Z8,3Kd0A\#^'g[tY)Ns>NliC1RJQFa^=*B:2[Y\\kLM8Z+Bi^@1D4L3k9G@L-%< + [G0@M]D\@r!rkCNd5ED<`4Z.;U+CCj)m'9POQXYR/+*(r9Mm'uaE9iER(h>h8$g[F$ojfl& + ^GYk?gEoWqj<\R';N37eEl1r1J\od-udc#rtm'[\7c7pq?k@*pOg<9#@*NrH-a-qeJS&r07 + \N>0qp\2=47Q8MK>3u.HPA/,\+[1d(lk@CX"AG%5Mn2!th2eBBS5SZ9reN?V=7mPTp)7&4# + eobVlO[9Uh6<9S$[[DF7SU&h^H5EE(\#F]JVgC6W*?fM<@13TQZJ>L*Cn_aR2p<9Yd;-5AI + ;S*cpY6&KiI$A8"b9>NJK>?[kt%%u>Sm1=[EEHss*ubl%[QT9p"+,*o->+M3"Yml=<1&Bl[ + gW;?Pk4c + D#_rEXl`7fKF8@&TZMnchL.S;LsN,?ZX]4F;)\j[n2<59e"TtA!IFEDR!_4O`hAB;=_,Mpk + 'C5.oQ!9G>2X[l5,:9JQ*JS)04Jh(3>@u;2WqOGu"['36RieadkE;SQX@2H/5%%iLQCu03* + B:_)r9Zi!Vf9"+VsnGlo,4ndLri*Vp)'H4?P7jrKm)%9aWcq>W9M8.#?W#QZgq+Sm'dP"8_ + %Onq)5GR&c15tX*KUik:?;"\cF4-TM@PVg.,N6d`rpQWs+-8urtFp0p0>toTQ:&/?^&N+Vd + l'E's.!?n!165nS?CSKV@l]5X\VZ'oN@L<:(:Hei\X>Y/G:U4qTK%]XhY*b.HTZ! + i]u\e24E+SC;dZ=&g#]+U(\=p4ms%RA/EJQrjs)BAjI6omJ&q(&7##dAS1UmWjXg._?h;mn + Y-E)VUF>$7)3_kn4bPko#dec0Xa3-+k:Ed0m93G5kq)4& + .TXiP-FgL%p:a6$cB<+)"5;arUH)3aXMa/s\GNA!+3O`VQT2`06S.Jc(Q@5:7otNQ`YC$)[ + >Pl0^rP1Mr^iaT<1%m@9Td_OO/STIn^Q)qH2"epKX3]kn+MFk7a[ + f6pjY?mYpt*X6"U"X'*:5^3[PV?pYj>9O7^T[XT?!Y3%%&E*6q5nFQX<"d"\gHE%#?+J_g^ + "bc_gjR#oS]0BtZ4S,+h-hnk?/+2+h;1Y*T8.i&A_B+IOjEL//#ZGj'D9:IQc`*6/p`iiuUeOr_*rU$f\m!jW7 + F`^00Rs=6:Z*&q3dASnI2Z$0H\W&q4j6SpTZ"Fm)#7k[&Js^4[&HBB]FEklE*pSt5+0U$7$ + $l%&sP5pbg1YNjuLl[H8-^9d/)\a(C_&0Y'?I`q)Lc0X8[lJBqU^<.!.hsMX4mN1QZ?JlG; + n*`I-m%FCIJQ*7'qsY[,n376KIgcqirU9b"nK/G[5p,V]+7\53np_VKJK>?:*V1@+o058,I + m*B(*SVZ'n_LD]s%E"C^L-]N<"YerpHNg5hg>*IFn_M^pc + jQF5BHV9)>PA5&\j\.^Q'$p(_7^^qHp#oJ$&R^Z21*"'gWTFs1/$a]DMW%r#a&XJF39lck1 + cRrEmn`?dJ].e,<\>'gWT6J*[*+mJfE@rda85TDTf;PG5&\"G+Bd:cM,[k7!+i@Bg)c2%Xe + .7:;6g9PQ-rO%3]X(qb\;,a1d\&m7o"<@sM0j`;;]b$d)q9kP,2eD>C0SknP!)=7ob7%mIl + 'REatR7X:(l.$]+:7kOc,!?f'dmthf&90*Z#?D`T(t6*1g4bDT1cQ\c=@K];a?HFo*2Nf5J4Ums" + [N*uC![jn!cr*hQN;g$SN\rZ<5Kcft-"IK(C]fC/P6s$,uU!s(Z+,RX]>n3don6#^?R#=7j + 4,7-3#4=X%AkI`S46ZD*J%n#ji6P2_'dC0c!EC4+A6?-O?(IdkI@i7aUUh^o=n\fJh7rdU: + +%Pl)K-UoQ"D7S:LkIAEmO=,"$ob*,n!(!7pQa$;.:7Z0i'W[,=tdTV(52:oY + iU\;fq+_:K-oi6T,')+RN)F[7(U+179Sc2BR2M[j.Ip_sT"+a82.:\!+U_p.d"W1emH"$K&,DflSVCRR6pr5+L@rK"Z,%KYn + 6;M-(N_4`\p_HenAU.leHtDHS@U!GTY%g#[iq@C=k@)AV''Jd],G>.l-97i7qF6[FBmOB\V + eQ!^Op#NG(9.Xn)Sf^^#^YF6Y76s)Sjc;k;3eIb-XL#bAV^T~>Q +q 0 0 829 466 rectclip +% Fallback Image: x=1, y=139, w=245, h=50 res=300dpi size=642675 +[ 0.24 0 0 0.24 1 276.268497 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1025 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J3Vsg3$]7K#D>EP:q1$o*=mro@*Z9V@Ot!-![Br9jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/m.nOW?*Dme)*9W,e/>aBDKfeXi0S]M!+93jFL=;fBr>1jb6_Y]X+[f0Q6PIBM9K + s91iWNC!71j'8+\$IQ3>o`O$salCna'p5#U0+s+\.\HD'-hS*0hekOkbd@a`;o`+%R$?R4& + =RUp;1;Eh'0`bB"*"1!C:V`@aj#m("tZ'4fQLc4b$S2$`+$k<$sfW/3Z563Q[9:G]BR5u9l + &pHh^K9_h$\oOT%3:;QTF7aH1I$m,^@,pc8_oYi[]e("mn;:G1i78m0N4\d&R1t>f1e[8eA + =]Ri]?W`'uSRYsWuqkd)Vs#AkBL=Bc0p);f]1:rnXP@TaRYjn + /]i&RY;=An>jj]!o6h-4_ZbT.3YDOfa%+\P*G7glp)SWof63^GCYAF;cZ.e7rU4mdGFebfi + :rlKNT'[309PAl?>l%jq4"Lee>nbW(@Y-6r_5ZPEVLC(0X"3X6MZ+(N + rK&+T3RT_'@Zn9B8r85)3U/1?23dLS[\nI)%@s+Ts`H,E0h4s@!eL;VjX_nl?`U'*V#0`E5 + VG_SAU4@^Fpr-LiARhHuf:*&j[.?@T3(!$+B*RSIN`Mg<`a.VfAnV/d<.*+OVA7LTRTRW"^ + di5&ql]*^DH,h?!)b3a)*`;&_a/JboA(6#M;@5`$n_uST1l.7a-:88g)^ECVNfkXbGReF:- + )@W/P(T&pO7D*T,lF6u"-QGP6/O,f+RU4Zr`)S4="@j4ig#\JG=s+6/VaW3*O=g5bGfb?#6 + =cEP6PI>.>TqF'Z_;Y.V94]ei0IJd$aJpB+,gcrhEfq&l"GgAD.2CH05*:r*0NH+t_BDhb%'F\afn]a@54U.S^q=3C8ECXC#(2@OHT+gs)FrJ.X9,/m[3KK + mK,O0UZ1aUB+3AVSa&ZGU3\&,SA"XR2Xe6N5;76*k80`MaTjr+oQK>AGg0]sX0ICYMKu5K: + 4G[he8agTg`8L#'MR2ZhrBnTAiS5h3C=."@4RF)u5IcFOfK>S$A(F`+nD])pjT?,NBP/&jQaI + uMT\1Bgf:a#N*"A))?jn_QRPfaAp54H5JD7RY@$+!p!Ca)5HhIOjUa9881dEV0'_DdoosNA)L0^(\A + a,R#@>k6L4/7o6 + *om0H64LQ92YQ+b&;P9mXj%2s6mm[BY^F>*B=m,E3SmTiH,LPL%sPr[hbb>@7#7POidUud; + ,SsaLohKQpBcb_DDT:M*>r?g&WMpjMFVP9*@&YsO.:)P800o:q##Z)I#^WdW>n$G4Hef(C/ + "3O0dd3ZT]T.0;Y+$ec"2R3k>Jloo@[n3V8U+3;YQnZ:5q!*:J$t&->E,*V:4S:l^HX%DU# + /se`Qm7*E/H1:5RTSkAOb!;qJ>6sN`n< + tW=9oESdpnX;9;GC74b;7/suFFF\[=<32aJcT[ZSf;EVog*&T7keI%pIC:.(6.7Pt + ptta%O4I@Cea)NqEVPa;T+8j<;7C)M7?k+X0$O7*7;..(T + X;I/e(VuFCd,hA)]$WagP]ueD=tU?DI1c37e$e0D8[5V>K\?f`"remf-M+rN>8Fs& + YZ\?D47rbM9eSkl>M]^+8Ui9D&<9pqQ;8^b?VA9G7IY)Zjgs_Jh'D0JLB15/^8U`3;ABlqG + C&7)eLt`c+VkXDZL(1=NI-"gD,5n5ffJ>m=,6#&nD`Y]0l/FU%>Z#;CE.n9kQrHO+SA#`s` + GY`T4\PmGa)#r%hTE1I=e]H6$j'VUs0JJ&TgmL3L/#iKk.^b>hAX9()s&?sB.W'K!SeI?KK + (@^)_:uEf'$ilq-6_JrA5n4IBU`%=NgJ,34E;^>"`7sefgdjpHkX=5YcJ;#lKrAPmBBGa9t0QI25dBn#8[JfV"SR'E`\Y*SZamdEb&+--FEl9+Q3%.K; + fUb<@WEY,*D(oD3qna@g&mjP+F0(V\oW@F5*dP1;(7eA?)o]j$[Y?%_X>4rJ3p+L,*lK#-( + <;,VJpM0d':Oo[1K_WlLZ-SZ?EcA?\R'U_*qA'.^.Y,0Bg#'[(J'[rpTQo'#+D3f.9SL!3>DcO:_K>(;FnB^ebrbE$YPs.)F+R0! + $]./24u'3A523">E%nbYZ:-"U&d>`f+aA'+kQB'adkU0f'\%o,TSB)_uIX16X">ZS=6,2)< + W@1G_*KF$2?95r&k2kZd%igMn + N$jj_<$%YV]+[-r5g5drj&/1TFmDfdEUC_*-3j$R;;m?pogC_]h&bEZknKg:LmlYSC'%&Pn + NVMDV"V^;*MK(u@Zf'd0'bmU^iUKg)aO`X)gD,P/("$;&EG$':"W'p,5`pBS.4C?VKbuFEk + &H9CEKNRBPsT2!6EYl+mYMi7MO.2@)R6N7Z+B31Rd+n.lES;rm^AUf],\r@lqY;9Y0@uIe= + TLh*_tXdY2\l3m\+ObU+uT7Nq2Z0!q7rM8?rUGZ4S5M'_/@G+k3$[e=cB^V':sV,0r=q8sl + WC[3Li!,LSiY%ofX1`iV)>,c]ZH04uSLE@((!9oB5Mnms/4jS8$Rq(copZ?H&lHRGjg:U5a + (:UMUs[/5p!.O=(+R,qfo$"0aii')ohR8Ha!)``6k0o1_'*79;?l=*8pmS@["/@(m0j@p2H9LG=%F + ]6On`90r4%GXI/F@6h.]T8Ynh3YsbS/*,33YFAkVjb8goH7MKDSmae$&CEA^XTCM:idITlX + JOgK.UQgM/Z3[7`:Iu0/V6Um,dMj@:UEU4!DR(cbZ7ei`1J;aHWio,i[)"";aX!2Z,h13f& + m4ku?#ae0:%*^0Eb_lOkt^ef-Vp3,Z@-dNr?s0mqXjpe2%'62%s2q,r:6NZ$^";eR/RgF.h + sK`1:XtS0efHFKl"pSW?sXa=-SH[!/M\&!EX`I%@"HoK/pom9l98/39X%Np^Gj$WkkNW@\p&/X[T2t:/ + iom^ET)V=ll2ukAb+p%`e%NsU;_5\D'H!aSO3gKYpec@hZ%q(bR4&+B@'>G/<)*Cq?K&4u#VmBY@o\;a4,\5B]O4(.i'19k=oSOLA4Uem+@R?Y23*jqJh%eo)EaGDLEgk + NQn'(m.1/LM0_J)_$n;0tD!nW1,Vqe + WG9UU5kp'THt5'hOXV+]P"M;BI)`nOej!oC"n^R/q7Bs%GEZ,]q4K[*MH"CJAhR9t)![eDd + a=C9a*O:=SZOdWKUfq"g==:\=lf=$nDo&X7K8PdMHpn(VuQ;R3oXf9\Y4E(V4%.WRQ7@9q5p,HX.+C> + S!g[R\\V?5^(s!a&"puN?n_Xi+=Q/LSY;+8p\CC4Kj@"@5p7'i5S,>o6p`%B&`FWLJ_o-@+ + eX'%TXFrqnIo;V'\q=o4.%a2'?(T?G7rUY4IA=i[OF@c1uNO54t*IbqE\WF7#^nDA2.;BoD + aPg9gEr=LDF"Qe.4i%).la$B!I=^2KKa\M&D>'Lt7&C<&I%QR:rakBbl`o2O0/eXf6K9*4/ + e*R(&!bV&e9hN)mD/EU-+q_2T5L+1B8Z[a/eMj,S5HD><>c=RV;+,EI$:O0UR&oXSni]rlP + ],ZN$-MV9$&'lb*.pGj7tq&loB'..uMpt7/Eq(GN&0i)h>F*TiD)*`3Z74D;0Pa)u*fhj5C + >>dIcFqlSZfjhBJCq_oF/JUK%=*]#cG>tXU[B(*fH%RaHAA_Q,0=>;Wq3/9FCrVYJH9&[Jq + 89][^USWE\@[C'qh_j&NO_-=\U0mX>G8U/PTM'/'Z!$G!7h=H8-?_bf&-`Bqn[a_o:B1-4/ + 1@I@#)E>L]sGR"^tFH!D4GbmY84jTY'O(\e*Z.'4A=#^QBOEg)m$b,D2F8K@V9B&N=1*ffd + t\5$C<`PO64F6,Ie6L)_c\>#c?H;l.!AjE(tMfQX3;HPi-$V\k,c;3sW'I7"p(BJHK"<'@1 + B[A_KbMH6-ZfWV:*Va]=7C=p::(:?Waa/_%@7omNdoRDjH_2\<1NY^AmQ57@ + @A(H5i^1qk)CXUX_M1eU:`R?")9sA(CNhf"GeKop?i;Q5(uJ%mY4?^eQ;.A_:FrfXo>Zj&d + &0PQG-X-m2>N@8IYh7N\>lKq%P(Qm9@O[@mEogU\2p[M+9Q2r&Qq^q:&Lo@&@08#'<,UYXa + [)FK3WmA.6t_R'2q1]bL9#K*uQ_tIG'>XXdaB;X0%V_2mSH?[>*T8<2liN];Z;6RuAT>m*a + WB>)f)OkJ$ZcAB4Mr$V<(rOEuZ"r($aP+2*hGHg+Q]9dkaggFU4JU&jA>ab(DVEbfoWO>0j + %FY&Y*fYk?@e4L?d-qq9g$me[,&ukiD(:RrAg6n5FApHcn\5b-ru(AL*E_9:&jZ!lTMu&'JcT4<'43oU+$_F:q=J(\oUlaQ65lMJ__//UHR4ESoJ]VOD.Qr(4iPEMmu(iETNB3ePEjLPa`AMA/R[(onW;R71KIC/N=;R + ]#3BX%&[LCUA+)%VE'H"U'CeR]o=V+r(&t[3/gDX\@q>[MHlS[E3li"#ng?gfAc)0a/]\PH3 + DuKtLK>Z_%\6Oct+figl^Y6@"O$f_Qk]N>bNmms*\*eIP^%_CuD<(3_4Phs!X4Yknqk*7%V + oRukqKu=^p5eD2qYh7+e,X0t:a,gO(,.QW"2QZG@0XR1HlQdakPQ\7_N;,[=!-RH2\*`enQ + QJk7W_36)5UdA'Lbkm + A1KUF8\nslI)9(K?q.a)kN\LgI\"MZZadj2gdb]>Vi1ei$RgGM^SAD]CB1Sitk:34)64AU% + Jq`N3rjT.SB1aNAo:fKjk7*tR+)b*]s>pDj;<.u\m;R1$7AX+Q](-2SKkV*!9FX9[Z<:r[Y + ;@E/+moLV(Kk"7dFerkXuu11HXBkI\^b*o<[CaU=S4W^!s#+;loH2#C!qL'-`s:]g + 6T.QCg2(r]5:d'jhGe?2^Q:]>%a\ZFWVDofRM`/L"gdBGCA\&?J!0^I3.R6s*e>,mJh`F3! + nVp@)otnK-&ZJi.%?NFs2Y#32Ra@ilCl\MrRpb2iXB\c9Q^:=X!cj*P"(*PN=f5k]ULiIU% + AoH%'IqSU%qiH06(%Ap#3YQ@GRk)A\$@*a1ajJ"A#WQ_s#9G+s5OqHg/#TBCs.XQp*L\Z[% + mm_PtEVto5UCtgSfEbr?jhmVq8q:ouX4.bQFDC64i]cF`1pj\j4%fa?6*#As*S-SlC`,T$X + (2r_*H.g:0BDL+sEkK.=b]9TL3R8^Nmm)An7d!:ioURp1D4fue=4[&8EOFA@7o*jR+6Pqrg + im&MGMF,Xs+5=e*Z=ruRf57.j7tko#'6I:JddC-StZjSqWO(fN;I;B3f^"#!_.2U^D^2F,t + W\oomJ:3EVCiQJpa8il3`UL.)u[Yr2Jt%R&ZEB!)uSG""QfRY7:7:=J/N+/MK#g(;[Xg+H^ + euL#*0'P5b_ZnUlIK'Z?C!)Iq_V!A7`$)_pi&,#Zo^2 + NmN3D#HuY/t6TBiJ,951*F,m%h3b).T7,te#cH5'.0i[T_Lp20N3U7T'E;.?CmX:iBofnS' + ru`0*JE'6%6#j:9+J]u6,Dm+#87FY(GjmN9oQrt3N^"^ir/6\RK17U1Atqb3NE!cZOc!I7a + LN<:A;$//nlA=RBdk@he/d + nATTep;"e>&e0 + NOkRe[d?goX*&>*\t6CbJDm"=6Z;B9_mnE^iuF8EWKf'[<;/1J>k%'#[8G>_(Je(;9;ik+] + !"1#B38HA@WkN?ZTH3Pn-;BsGa5]!4M'JASo`:%Y>shK\#f9oX4ed5oSu)d`:un;ja=8)P` + j)<9M)pUIBUS\^IRGbat8f+<`T0&Ei3MVquVDFEWNt=_#iP:%"[N_dh`iQC.lJZ0G%p5(e\ + 7FN'nnD(=^CA@k2Fjlmd]c\C+UFA81H;)IMX;m-m + Qe5hH^Jh+g8dhX.tm60X`o0sS=):.^95d@=H7cYG7,E`SOBLY,)F>S\]iZGpkRQmNl#p?$! + 0TbiQ"%t`g/?i*W_0@=3@/M$uE,H?+(fL`L>VUs%eeZ/jB+En54c"(^,K';C&LXj+/]>_.# + XI6\@eE7q0p';mP:mkbHuY0Hke+rd6Q]eEU'#pZ%24>$9+U.?2l@AJa]UPL:!(VjV_%/)=] + TXir38>MgB=+YEdLT:'0_dgrLaR#19l]A6)Z(;A@]"!d,nP#.%;r'\^d6JRP.-cRmh"Yp5n + J65dZu7K;>VV6W/ZIfX+X1d8\5pB_KtNl(pFYqGNqEXY/M$-C^s&D8kc9P2iQXPc>43;UoV + \k\A(@X&X2D])?%(lQ'K:me^2=[tCh)/TIo]._.\ll9XaTG9O/!^":[h7]ZO3YZ;+K^3^Ff + G*Rri]2lte!eFlh`KYi_p&B=uTHW/[hA[g8HZ?/Tn5)0RDEh*UmS_4 + h3?;XqY+H?Z4nkW.qbs*1Jp1%rP7T\ZlA%h"-fL@S[ZKd5(&W#cG[[p"_`-B(3dGfb[GDjh + 2XuBgumhJ)adI^/;W>a#"q[DnL?_EU#aF`']%8%KX@)';&ZX'o=Y06g.el'6J,KdJ?p9l(' + &sFM=BrHA>Ga?!!utI]:mZ[olL9]LE-&0Ef1G5aUCRZO2`?Nk8A.m>Cs@p@XXN]-P)q@-AS + iq6-$gN_[]EpV(1ppY^(fl0t*>/C,4D4KY5A/4A8A.cOUi`$)qY_("gA[%I3]fhj$>o3F`B2MTo1W*Vrnhhd_l1/$Uq'*an + m$XAW`l7FlE+4]C?qn$)St(W9#Ids7^fF[$0#afHX#g4FP(`gL2:eTpGPt-YWoS5m.\n:[[ + ZR9YJ'a]HQfiaPP]d^O8GX]p$%C(7st_SCuG2nI`PBZR[3IQ- + !:N9c!GqA;17f!m-\Xtt_`9'!-a+>laX7e\B66[g?oIZO$UqY\IeN\)j;p + JPcn!YO=QX#ME!8`3]WT:OMdNjgAS5G5U6F46CXUO.2f?OXub>i3n,cKbV$cf01-.%6Q@bs + /29LK1.+/]6VKC*6oRJ9/dG3YK]"3Yn/=BVA,m$$"AD!@Cd1;a[KNZt6dPhN_,Pq)6j*cZK + @kVXWIlQs.E%.\7$GA@H>u?C9KX:h1:+k\9>e'Y`Nni--bIDH_L9Bbb-O-ZR)4Kg_`(#0O? + DhgR;:-IUX6?c>VjjY4[VKJ;aE5k?p/j(WCtpa.n6!HRC%T<4k!!9j%DQ<9WAIq.MLgA3i+ + \9jY[sR5%@M@XD]T$E%9mKSN567S*N8==WP-A26sTsb-s7hGUo7k59jW%3k[aE^,ARO5BCF + n1;b1qq)5#F8/fN,_>:prKKnbG5Q]4i,_n<@Lb09_^dE:SGe(;@$UB>iT^(1&qV!Ns$a%;$ + 5hgdu$K*l^iM!$!5oSfUCPB8t'Jt=C5U!Rd60$dZh.S;>[Y6U@@Fg + \`\Z=<6tJ3%geJ76c5N4mKPkUN*@SJ6'-P]Asg=3!u#7p<5_lWm#- + n4/>ta.$I$.q-K9A&m$45@VI;FFQN-BZLP#80FHo.r<9oDBIA^$DPaW#^]m#t!tPY"d48S&At1mg7='WuI#_.9"V]GX;bSZ^b?6, + N+WbE,=0OhplJO3<`j;j9TfQ:bDY\V-jlX-lS3r40/(8usEp"KXqcXNKV6dTj7ZA@PNS-rP + IB;KXHeN@NAI.oOVC;V`kOkrXVr;$8ih!5Jq8),u6*1^E\29D(2ABL:0r1DN.m!Iu!kphU4 + fGYFqs9XR&Pj@W+J5Z?K(7/Y9J"3/RMHF!cD4Hrf-SOZ+Q1rX5f9h\Y`<*?lp>.j]hRB5$s + `7[:9g-t[39uPec7,9:khC3R(4g\eG'2(f:iu?b37\o+jA)kSYCrYL6`ql`2UiF,4=3f"U2 + 4ChbgKVtqGJ,+aWq@iD%+5iJ5G;>3)gR="IV5t&]&:Q8^*\W`#; + KON_s>[;/@8q)Y;)t)G3+83El1)e!Ip?+'B$q0>shf*%95cS^KTkdS$7]jXjC'QW]<8=e1o + 4XdN>"RT[=p=j;f/Bn=@_(nUD5I2Jp8Xk)5#.%_pfF' + ;sg^:%B`+T6<_>pZJ8=,4"jQ;aAc]t<.%Zi/P4HW9!`VB>FeHUS6N>5]KJ("..'jE9\V^Hg + f6-u4d/=S17-O%i)OUQ7W[HD]P,"g=u>N>m%2tGW1V-/Ucm)o6eX`I4a"9d'OJm!O(l7=\L9, + Cb"@L%H,MAB%Cj3c>R=ODs$J,B-)&*/o]u:Qip5E!p19P0L"ZK'^($Rlcgq;h*`g^5a3DQl^h$,sl,jjX@mRMFal5i1)'_0 + at6!\@ph1YH%Y!hQ'gHU\Pa;&URK58fHi(-N@?62bcp.WgALu)*flQ,-';g<(@"_A^#=12& + Z`!ka*hE^01j_aFq*=_+i%bN'S&!#SN8fM#`TJ*8NHBI;kT#3G8mI-=&\>9f=+F%oZ)jI@74^,='`_bsulkrsK6j., + GU2P*[pT2+N3^?](bo^K9akN!"ZC/rm3:.+;hg)hZ?c])etd`,SSDrda6lK!Z.%SU1e9jU3 + iCEs#Cr/9l0kj)8IhQF_Qsm1.e9Ib&R)1NQ>Io3X=qB/OaK3-0R0,sYZ;O + ?BAt"7?jkB;#HFjqJ$W5QIuR;htMI-r^?e:i;moDLY,nDas/I/T-nHBMK^,(5CRV9N6c7-p + 6!YY4(S\3N.u.D^=!(/rnqsO_GS.n458hmAF>)hsYYLAPo/1e+X9DtIo9!iP(;E4& + usn1L:r^7R9[TN47M2;\Yi?.J45o1qRXj>GTN7G5G"%9CcRA8\EnV[tc;XGTGdC>Aep/HP3\qQESe=ar0FidQerju;&3C&!pS)iL+^]@E`U(b + LF1oM`R0i'=Oq#!Q2Sj;B(Fek\YdV`T$Hp6(YNF]o8bhU09UTFOnhe:Gup5U?^H%TSH\d^K + D5Y$:^#7-huHg8$TC:Ci-K\D(&AG(!#Sc@85f(]8];K9qHla=Dkph@KCl>4>?=M^Fgk>2:% + [j#BWd6PHdL:)n\$;K@]*0S%okc7@IJ(domW-"rfoY&`O)d(:-P%%pAScu?<,Q9E"-/gdoT + ;`d.TSEG"lII7\Y'BnS1Q#p5R[)O(uF96sZIIpE>@EXhQHB:!Ncj:G?oC + DDR4>3:urdD\$i_ALO*sK\+.>d&=,44XKDbV:&\1fDR + n.:oRH0Nlm>ikut/Nla>>KrT@UofW:Bc'<2!%[9G5+gM<\n&aZume8&81RQF"?-YD.Im]mVBj=g^41e8Te`tpCpMR`UYh:uo + )eWdCFT6MCpRCRE0KI9j+7O(g#=8uiV[():/1 + P[--B\:J6]gq%&&[Odrc,l=1eSLN&M-t&`Na^fE>e;*B,O1$M4GVV7gsG[+6Ns"+362?Kcc + _G1&c,_nl1f!R;tumE(Vm2^1ADWXKO-I#5OH5bHg++DbIDN_IQ?H3P@3f`]V-s)maYEJ"*; + ljZ6.XWW$cPZcr/,:QrnW5Ysr+"%"*lfnVrB1dr-HQrJ))D,Kqj,>"7ZN@o'H1qBo"+8"qb + :K.P?Io"S%+oZ:W[8g+31;d(+4[;9.kk.PnD=*D9buE#P&XaFBXi[EWeupJ&k8h7%[+G8`oDs*M=jD5^B:8aIT]`m-JYHg]/s2pn=uMeEl+12lW9P:mT;q0 + [[]-=n>0a[[CYIX[Y3[)K`Nnhl50:0JgJ6G0mFt\#[dH5+jgslL^?fr'+no)eFiXMjf + _/UCC"i::^N&''?&9NWoj.TKdf;QL2G4ZSC/s=@Sk;J6H/r#Y)H035bsWj8:mBNM85sEa@C8#UnD0RtKQ(@ + n^,\\1*#sU#%aK@KLW=!L*HS_ONr2=.-^#_rBTRS8@I8olaE/L@U4(@eofmG$rf2("(6C(Q + 5_pil7Mrpod$/*.-sAGNKK=k8$uJBE]bAF#;Muco/rlm4)b6--ND[dVO/V^mUcAR?;BXpiE + ZHL'#o&W">#\-m4Y3Go,a]5jPtb.^1aa!oSZ2lS5`DB:o\5OWYK'X<`8lu.tr1$Q1W$t$N4I.Q&r9%ABe9#6#4^[WjUF=eQ^4OCb@ + `R\//E+>t_/@%6;;+<(3_Ip)a)b_&Y@3@N%np)51nN6A(17:1,_J + AR8!iqbef#gp0Pt_D%(%K4j?"@,=L>K'V\Q-N)=mY7B#;cUU4p";Aqhlkkug'<18tJHXR[!/.783cI5,$LC8g2+aI + s]^OjupkmnfG-jX0mBBF1Pqf.$a[+*-#0c2"KCFU.66eu,NCg61#.b + P=.ocu9Q)utM!n]k"D%:!e3]ZohkoD1(G-0`VLiF:$>SGKtU/XiMpDPL9R&_!3_>Ljb%d6@ + B(Ds64+[?aFF>UhU,L=JnsQjUf7.k-<0- + 9T;9'/!ii`KuJ5MUn5@]kgDc]8Y>mg7)#dG%6BI]CFVFSMQgb03QrBnfn@0llLIK.I0/F_\ + Je"nVq)q5Et@Q$8"Xt[4o7:_\cm"@B%/QQI+f4PG)kh]t:at!oRe3\\hShf>r]kBY-_s%f51t&c4tR8mGY]:OVhDlpNe(\4-231pS<'D,& + a%FOchKsaW + D9=SO1SNh.ZrL`]i/'ptqb&ZBfbLAD>jp_4HoEKg7h@mg%?P2;q=,l`65K7sZ*TVUGPOdR' + b^!POjo!\V&)im*krOJ#r?17pg,IN-QXUdpB;0Vo#Tn]XniR:\X=&=kXD2Zr=#CN%e=QL#H + bE3Uo[h(4Ii\+FpW]f*T!\=Ig%oKFV69fp]mmI$lkZ5$@ULdHqp6Eqf`Lt[p/]GDgHs-O4# + aRUXeXW8]5>E0;uH#hhM%GV(k8+"\S\PK3A&a9Xk:+bg,j6\\1Q]s/`sSQlJDlE50t@]X." + r`rnm,H\ul>A$aPa%&rl4h"0NQ&CW&Y4mf\XrieuBL"Ff@ri/k)uOUaG4jJJ&77_])5^M,+ + Wli.2`!_oh"^i!IS$kWb:9'3*Pk7EZj/pP6,.[H*D__@[KCcId,OMgESl"itZm\7ZO%lH3>%Ag&[<,ad3B89-P>l!&lC4nOY7AF1D>m*'@A_4_>G0Z;@?lq#HqBW!/ + VFVr=tAn(!;Qa+^GffIKX%H"8`4@):+ + cIYFt^VlIRbjYK-"6!l"O$[b'gRfMTB8Z,2'1`7Y[BZ>Gt\YKeld2'%b3Zbno41#fR"\?tY + ;rkiR-0D+%8AKR!2[a4r.O9FCd#`+QNh@L>D&/O,YT\g;'hiK*K%K*e1W/N>:""k)dB\1"N`*X1f]>k4#OYi]/nZP.MGWf$_=3I^i + O7oeG6s3XkYRr9M00/A5q.a\NrU*d*Tf^`skK&S? + C*GcI!+UkmhNh%aNq&nFZ-DlL(nSb-(Il&IC*d!ABK;'Ce-u'*Bp%#;>ZV0N/E7P7KBH'/> + [_1"-pno,BFu->_Z3(u\-mTMu&6Ap$B_1XTPd#AP9C7KW0S1P\X)5S=)@TG[nH)09.$Od-a + QN>N7g2R@<[Oc:55I2T;8(QXh"%:jmHCH.HVqI6KDmJccTH + &_,M"1>5U[*V7#!n`8F8inJD9*ma-o&+I!/aZYdd4j.f"BRI9e#i4^*!:g]2K@(i8OTDMK\ + ^2OY0I\2`"!WmZ]&YM1#hM@?2Zq7<,HJifMp3M(-Nsu2itO-MXuQ[TAL24[-*-U"EaY'YAI + #MU^efRCd"]@f(?X%mk:'O^15?0RI$W'0_^md6Nf+7$K.65V8&]O!9M[.@TUF*)$dc[mZFM + L0AIPYY/4mH)N-c"Qg79dLaE$#%P2=<"nLdZ69?'%(m&^gSo'`gg0^THXEc:3<-F:r0V<'15V^]ht%#FQ#h+!B,>qSN##E + )7*m5PArM0F&R3BeFRkYeL`ZkRlD(8<6]%ofn,WMfk5]:B\+Atf'D!A!lN*.cMNtq@mJ + -fs]`PJg5a2\h&hsKO2$:ce";"!He09qf.3qkB]rU$t&lD"k4[Y&Gj@f"hnm_oH8OUpR719 + Ia%V8(LGin?37IZL1:n&l70.@&n7hg(pCd0AoP=P7sl,A9GFGEDoNp=!6/$?92%_4,C_(9D + J8oiB + SSFIcC8Q5si;mOQT:`.$SFTP\fP + A0#P&?.C=F/eiu\ZZrn/7.k[rR"eA5$Bk8U6%/*lkEm82-fe$1*As1t^nF(!*F_?rP0$a$\ + bdC-s-P4M8&JhL$(8ldCYCi3$K@i&7(d!C15D3-3KU>Pg@0#p8c?,a=L"=8C@HiM36\^jG" + HdF?@F?tJr$^.N;O'[hG_'cKjbT:ZLt:u@G_9p779$IW3&OPmQN_>qV`>P(<0)Q()m=B6=T!>$"a[pK:\MYs-Fq>?>FHoD; + c,!I#?'5h4*\l`VN,JDh<",EP7Np#M."P)kmq,e':7E*Zgdja)#3HF + _J9nSo:TMbgg%WtAD"NJmr==*H'uQRu:j0j$*\NA9Aie&?Csj]OCcFEO!7Gimf6l8_u8O#< + t%Z3tg8`HEYr>F>Rtcl[H,#XhoiK`@g(QX`%kp/3S"]R4%dSf+n3sJM + q-.tVJY]G.d1,fqNd1U-#!XgS:J])7d3V8*C.bCB>O/?F9*2Id47SP'nTRRS5hNoc_>)Xd77TN6N7paF=q/$u0l[QA95I*^&Be'4E1c + .ppqr7fL:kKZj;&jY^j2&su:Qd;.OG(?dC'ce'_66"rbV + %Y")kV8&[5r`JH[qfhm,8k@(sfQ/$.a2CHO+pTM"[@oR`p&trg`V^fufRESkCq1T5;M1;s8 + ah&Y;'$n]QccUEBRN/G&9UP6#kKA)jB=,S#(aYZ9cOfoecFi2u*9lNJ5NH-DjIT0.IT,=a< + GCFg)*q2mRI*h$Flo"-\,hZR%1?%LC_c)Vdi4S#gR$rkdi(f^6@!Y08]W`],`iq)E + 5:J)*JMSO[]@9!T@2N."[2qH3]#iRQO?4BF\sF3VH*?,'_#f)pZW>QES`Iis"1fGO + G:FgiNA&U(aNfbk1NGn8Su+d#_7"'d9@Q\jFbZbnZ`_f/Aa?+%T+\U"*?H;!Wmq*B?*afTN + -XkhC,n:N*4_WI(eh9pd^/pkVONFKG67lbGCnsRPrKAUd)hn4q>glLApp%OQgi3lY-Yg\+d + '7b,P/UK"jXHC[!EO/ab9E$T64u6jk6fXb$je&aO2tjp3AcA:Q&,KiuRnp"_>kI<^`iiBe5alHBXL(l2TI,Ti:"/1TC1k_f'dd + p?JWmWXnqpEJ%KXEf+&<%L;[QWXpc@3foX:Z&b3nr!Yk'K1^d5Q.#7p_>.W73aJ + m]C*5>HcYehsR&4gM=98D0Klgmf!.KD")[s/gh[df\l>20D@[]T?_6E\rM6/SZU3(\\!:Aq + jFMBnG#+ApFB1ekk"Y!N`MdAhsQ!)4o\0M(;5RjLB'N"r[DWb]>>W1JGe + #rbYANCtU`a1M#Q`:6Vs^CmYmSofUQA_1+\r-H,t$(WZI^6dX=O(1*Wq]c0Jhe:Dm8;MMk: + 9HWE%G>2##h)e=I<=ME4e5[ssC6peHFLi%G(5rl[AXWO2g30TtCDKsIZtS3[nc!2;2[.0>giid-F580kctAX,rS)PPf7 + i?g+6I_3Hf!Aj>C0_3Fo?h4k%RtL@$huCJJEE)(1puE@8qa8pl6MS@^r9gM;pS(22l$ip,e + @GR2/#Lj>eP_P@Za,=-R9alYH;TOk.e%b3qgOsCjS(`Ztu44caE.%B&C(84)X5BN1u,_!,jsM + pV(RXcrIk']Fk1\E4e+?`,Ptt(Vat&E!aT(`b^TsUe'Z$NUnR=1lW["O$1i6#]^I(+Ped\e + 9&B":XsU$B4'3_-pKrDG.jekKl_[o1#8UPYC;]1$!LQm,tXG`S9ZR8Q?fC"C4r7YIZf)SqP + lc]WN%#kZE,P4o!Ya&Wuo?dI&eaB[Ol90l1Q`ZEqmS(rW^rM3NM0@K@ + @A12)XU#;cXN"WDGcC<_\J2@r:1%\mqG:!(&##5SatZ],2b/0?lkK]\g=XE@:N-TahGmiBJ + KGu#\=4=@9L*iNb"+gLT%a6`aHaEW3]+p1(ar<4=2Lk1Y@7mZYk$H0;(L/A:!M?nnL75Rhu + ij(.nkc#K>ZtPF2^u((6nhDCXLD`u&7N9H!2'g,4)cDW391`_U-%rlN;Yjot8YS?pGkdc'n + o.?E"Zj^=q]So!LJT4,5Y.^]TbLIe;)1OU.1%DuPcU3LnmP->q)G::ntI&NJQ"%0=Xd?kXW + "4b<@_'h.ok*%U`?j%[R+/EJ9 + O:RVWYIXePLlL.?Cb,C`@@uQJtlCRh-PH9KWMm(aB:#Dj=J3boff;1qb)ca03-!eWNc&SpN7!b27X3RKm]o`Mf4$_[7(42#"lGATVELf][ia(Ik>(RX6-*U8HR*1k:m*k2 + 8HftXP`>E911j5%eNn[2$DYB$SlANYjcJ<=P,oN9Bg_U(6l[>pgnBf= + c5%:2BJ9eV\$#AWAKs1ns\n-'egN72WjT:`Uf920^6hM-tYE3pu<<'S9^.bp,^83pE8MoKo + pC&8'#s',@mmE5OA2'3hI!&RKOa.,[mg?g#lt-qDB9^Q8np + 4Lc4pDR;sJX&F/F0,g#kEF]]DtrM&FRL"lRmi#ubq%aEa%ARlol/UM@]udKM)f16[4:7nZBL:C;J8Gt'Fp+W]3rbB%C?siGcYaWSM=DJ=Z[C(rN2EhD94!=doSX"h3 + "qFe,M$T,QS[8s'AAXg'B]36oL'O$pD*(r3&$<$jBB]@;fS'l[os]aU.c'MSHP5=]kDC[;F + Wro$EGd)C7JAPigpk4]Olb'';3EfmA=Iadp"SC2H\"\I#p1f7I8#uro3R[MH_a+":fk[4nM + qqijK8/SEb\8)krjN-&u:bTT)pML;h=%:$c,`\>%$c"KK4.bQag%.XZ:lc-4V$5Eunn$rca + 0DoZtIh#&:==;i5R)!QHpIDU(@Y3HDT)b.U:pFZuML3gWk4*FGpkDRn>Emp#,6LTkqc^Y*r + MB'c(YHAk-Z"i-31bcWp)pH6>`_RFaW/i5SCi7:Nc2M03,q`a3C$(`s1L@H/HRK__@IWa+<=,g3d[:Od:p + &eH"4_O4X*"0#tk?[DUGm-(uOZ49ebA:9BZ1]ot3ZBU66CjZ\No&-igJ67iXfJl'c*H)#qe + o\CEO`.&kI6qK%;qfkE8n%gMa,5BmlQrNDL>s*5NXe@^%Z[pH8i>83NhX/UU`(^$j4"+o=. + si3+DTkGs3KY#c)8CPZmnt;+A'rN%_L7Pfo(Ho)L'S<&k5543>5<`&#$)`!6E&S:CZ..D5^d3/:%CN`4'"9>AB^a,)mp4 + D[2DEQl'g_lL6(T&bM/hRgX$o.X8?/0(1ippSqp.D=d#+%8*r)lpg9Wq0:: + 1/34`f2fKN!fP;ld2#]sLh@gYB2W!^52Z/3I*q$X996i?34#`T"tQ6XUB*bJk+Q^2ago6g: + ]Qm'^XD^:Me5F6sKRm\-p+Xb:H('&#Q#E0*_4^Hi7Io&$oTp:"9#[sRp0Mu#Z5q`huF\&pJ + @g?QH=Im=,;8##GsX91F=";=Zu5Z@qXo(BeEp:uh\'H;opZ\pKCMnnA45k7Z0qZ+G@&igm5 + 0L8nhKT!ZN#=3,36$o"Glkqgi$pgp(\a1:j'HO''TeSp/-GpS8'UQC((d]uu(i-$9j=ipmS + &s0MalJs`oUX,%T#q20aqUSm%&:rjUe3J>Rt,j?0N*GR$NQDMS)@aJZo"9q36KaN)q['$5V + g8faU)cO9-cYg,dYD\[)=f/9161Dm'=Q'Yfi8#`-]]rKLrO[X$j$^WnU'4dn1\Q9:kIu7/X + p&;=m"V;8k#n[2[c+.Jh"f<4-2KTBO=s!W^3(bf-pG7Di?D'$!H$il1Lr5"sD+`7)G9kFj/ + 32H46W@aRBWBg12C^>d\FFPFP5gpZ!S2Z.Bn]P\:EEh[Ah7hH7j@cBm-j@;8ma$SrJWouCZ + r2ubNa+ETko'JrK!Os+s,KtPF6Lr1X"g:jZa9qZ4)i6h]K1JB>0>ic&dS77:s%%NnYU'(dU + _Ch]PX/66_.*beHF1,j>Mc!CQLAC9:O$rKcdr9[Sd + gK95Pm$)PPR^\AP\Y4JP9DFLsZR]YpVZ1jm$cZE?U>^e>G;9I&&ogega>0[+(Hb]DFU:r,S + 2b"S>K7?'4ER*E%7c9;>87GQ6a"OXXSjZZXPbsZ$L6g1WLf"SND7WW7_K5+,fC-MLH:"sR# + ,],-T#tF:?7g&Fe%-'Qnpb"CE:1[1pgiX5$G#"6L:9*X#PjC$`!B;&#Y6=;R3q>70IR%_"5 + ]p8?KRtkAo[VEKaAhn0K)X)R:Vd/G3>H#>/!DYL'[P9fV3Ht\m/Xt^!LCt3\\k">Z_,(`Ps + Oc&_@%.+KUB>V$;%W>;(Zh?-$.I.F&u(X=&jFqATua3Gh7(`=+tpg@!ID3:k4'#J6]5W/Ur + OEJ>hs9=="b&a=@=R-],1PdUa`:oWqFpZI1mmdNsc#'o:Lq6s+4kdGmV_Ppe,8]Y*>U;S5[ + [Pq?-,^s_:>;Y?@)"4gBj;bVMJdlUMZ`AL)"ge>?99Zu9dgfhqe"\egQ+G'pK_TGg,@(p*o + e+;d,F(O'Yf2N[:<%I=[RMeSYCK+O,c/AX]"g?F@YbF9Q&B.:4$K02spU>eM\!),5F\phH; + 8:XZ\*O(>]i%9Z4!nl<%&,Y[i1b_*F,%+XHH38Iun"_#K+@o'FoaK9"!bE + Y)!bdi*;]N\N8IWo.:%hgFX#^S,hUZ>]UeZ$4M08M.7C8Nfec@Fr@HW&!7L41?m;sO6e2W6 + R%Jb*;_6!g7*.l=gXB\822.Wp*M9aA3Q9OC7I0!loq]<,e66AF`\g#_rM_u_W]#\(<'&OKO + rUpKgo[)^e9'S4/Ds_OoX?ui7hk"(-+-_4q:+sg>C,L5'^3i3r_Bk"gW]$7?+rhWn_R + Een/]'K#K&Y>!5eQdF>kc]YS7])S.epj"Pg]8:ZVO\j;9SPOBi!O>ntdg]`c\fD)r + XYcuj8'M\o`Jf[h/0?DZm@fJ.u&+^WjB"ZSjOIR7S"6Y)KLjJGK/Y"@]_9]X2fRtu$;TM#8 + -!Iu/m1?]AVp/@J0EFhrHj^bLpE#&ISO=G[51c:&"['ALoq.7iWhi;#Sem?"KqAq6c=S;T! + 1qY<7Oi4[,bOq+(o7N9P36hX%fI>O-Y7/p).&r)F#8^7bhm`eZrW>9;7\FgJ$^5AELY0,2 + t_.Y+pGG/\B&p-EcJY\1PG179JQFn]16V8/9"WVCj-[0=]:]@g%P;'*$aF!F2$n@n"Q#<;e + L_2V)b)0V&2RK+$K4)<7f]A$TW2c-sd/eB);d437Thjn=p^L!hkjcHp0VV??u,X) + haN<4,q%hDM + YH2qrji!i`/o]Z[^9B/El0_@Z9d?!BB)AP#"!"@bgW0XS-@5Ks03+#Ot]I!+#gO>m0(R"UI + JJW'g`T1'<%[!g#*fLa.Mm,m"?$![&M8:`f_B-3>uls5`\h($V24Q$DCdbIU)`llg0C=<[;]l)-Xf$D%"u*3k=4%V.pg]MG3t<7@2dDEIX4_#gfg6#e2H/QNC;t/i8L/CBH5mYKC`:Z#O" + S8DH0A0d1kRI/SKk_DN&`+S9GT0Mf,t^&t\I1[8pu/ma[%@mfnnSVX\-6$#_D$@dV-*9j\, + j@>.f_"DBrN5V%Fo)Zaml!+5s17jE4D]RWNTJ1qc?0G";a,6@!lK/bE9?q1=GD[!pP@su[5 + NG3,FDt0!%aZg^=2Ph4eVTF!GA+adX2F2?^WcETGnr\k$06E]7Y5#2[nu%LmViLQXO80TQG + pJs;0N3MK$@n)S@#!(eQI>DKe&A2)k:N@bgsEIZU[pb*P170`Ng*I)1of*@+VQ]h.c[!9@:pt(W,L[4rKm/E(5rl=GUfC#P-3 + eHiK?J&`e2=6*B;[!U[4O#=#f/bC:So[A0J#6Fr%`*XuU.^iJIt.In\:?]t:\U(`$Nu)K@1 + Yq&1*#k>`$+XfCW4[#Maeo\R,i-CkDVqQ1+=IHi?4?+SC4BH_H/@U\Ra/:I*uj2Ra*7?8]: + /V5+k-rc8l[QLIJ"9h9IJb17(^b]4mN[Mh\96G1EWN(BngXm&ntkp)=Bg^LhXm/Pl+FHJ*2 + Eg[7hFmBZ&"J%DelX35P\Is#,&2jW;4roq;Z0_gsb*uaCajBq>0c;,sTb@Y#hN1P)Ks)[?[ + 3X3<^9]sjh1_"*pRouoFlh0XNDUH_;g,W>trRURa^#1aAS0orraiK#%n!SsN=Nm=lq8"ak2D44`Gnj[(Rp.);]/ + %Pq6q4\AfPY.7kreHD4!ih[33P2'Sp/<;F.]:8Vd,I?qJe"T.%h#r2V?;'""roRg&Gp02&V + K\6dBll)\2+A;L4)L&k*O2pN(qjZ1VMXR/*q'?p,4cl$j*0?]Xm'L-@X*Sj?^#b16Oie'kCMLB-3]"[(d8[e@@0e/L#L[E+c51QXsK + !8R(bG:j-[hLNT9g%6':52/jki`R79o8jdBF\Q"CnOA!c=\Y%aBfGd3W5OVdBJ4D5W)i!0: + U@IA7dLd%&W+@lF#Vk&-WU3Z$MY,/2aR`;J2SnDZ+YA:`K`-Q4R+oelc)_T[Im<_U]\*_q1 + gUZ_kVWGDX)nu!QloXXO^[#,7\ZP@k&dP?(kZ4"'nS"7/MB[G*3fS0MRQ-J+Bb*%bo8.rZQ + Lj9kA^Mi=f1__-$4;6Ropo9NUNQu:S_:"MVeiJZ$HI11XG\0JYq7IjL#D_VO,USo$b)A50+ + 6s5_0QLl"N:.q?`_='hTrIn1EPj8n$Bb:510:f:X%:qLHB&!rJeF)p\iujJD^?co8!4QY$7 + "Ns,M=$FGD$iJ4El35Z>2oNlg@57uZu0W#U-pEUH"e9_tA+pjS:gq*6WN!MC\O#LGdC61Bj + =+VZ8WiBeGLk^F9k'M/\[1b_=2\f)dB>)a]B2Fe&3*^eG+OAZ-LLe/=-UmR,R;OgTj!C.b0K*Y9ZK#C38_u&F?3 + ??WgToVg2<_(\A+4YV^<`0B1.sVQ+A2;#(1+clb=WF]t@`ir117hKd9RBb#h:sD(T>;[$O\I?-AX]0BMiBQa4sMJ\WoU6$1 + 5-+`Jt9L1CfK6H)(Pl5 + hE1IXnI&VFGY]fbU\NimP>H=eqH(U.uSK:7F8S(9'T\$X`X!Ee[lW8m9SJQGOFSN]@Vf.?9?C`_oQ6n + 0n]P`fYT+l&=f@Kf(r/kV`X'QpmolKDU#fkf]PF!SqouF1>mFcY(,'>%\XJRWC088.`FtIC.T4g;KfZ0?JuDY+Rb>aG")Y0J + \WILKhWr^OYO3.A]NYm:lq>[0Oag]I + 8Z`mN%$QrZ$!(FHg5C5"o66%)dC[N]OgM7(EI8`K&B)j!lpFn[0FY*3J5KDHpH:o?G>o$^q + oI?t-[_N6(qc-.?UMceg<;kYJ6.e3'd/\jflH-N9l,I(`Ib%8hX9Ni?W>X]Bbalg%]^Y@e: + $S)D#&K=TSrB6ZY4OIghe*Nq\/D$:?[L5&//k.p2M%:O'*NNkAt^-mr.V"D#j97@:V%M=.> + !Oscd4.F67h]/0Z7]MJp]?3X9/ZeW2[?(%0gZt>OkOn5TfG=R!8M.]d4\)FC9@m`[c'c(i? + `E`?ce3YPA*s5m\G"eBhL!"cm_/q>P,%Y\$BQS:^i,5M + n=VIddlMp\rLsi3Ubni>M%SpgFZLs/pi3O5*=:V\c$H@F@232?!&.&#);VM:#X#`U6G#/l: + Y"JB'9Ea%fs]DIUZ,Su%H^k3S&i%$9Y0DZ[(jHtmo(m%gQgW<96)kq8:$/srVS^?OpSl3tV + 2Ni:1EXNaMGJa0i4NkR]Pm`nSElu6pG:C$nN<&@ki3,i\YK[FBNpMO\(.X12 + mnWLHXWk%gk?6M9j?pb;^Y0`_i*"V@p@&Fg&=&If=,)%gj.]0N_0!-n%M$4jrCr=La[&' + XsTVS;N&'CWaO&&T*$722r)']tkC&N7L'1ArlYiX@U@lo&pF.fN0oriANel5J]doYjW$jDf + _2U:1tW:+JprjXb0ImT.Ac9#pIlH,B:Vm"D5),3MR5VQP6e9U[d,0^*I6W%^=lDNP%hPlso + SbGq$7n4nKBaNT[J"d!!60OY/Zh$/bVc>(MgOEBj3f*A6FXqC`\cJ#G">gP0_#jV:1,\nQ@ + m-bB"$4S!DXX+=;.'c7F(ZCMl5qE'aM%!9F,O7]*NEH\W>4UnNerM`\9lUWeFUh&i-0ZK'% + >>Da)\GtaAha"e&H*r]Gp\]&-LuX6Z@OEEKda6Z..W3D&r^?e64b9jn\L)!733[+(DX"6'l + d1qnN,N(6S#E5/(%'AnOEFm*rN=LrA(E'Z!T)&AGu%4(=2'[:[H#T:B)q,(\7&Dchqsf@2P + (0)"7g+n-l*iR4ti\0.plEXK9SWbf`bMbE:6<-i4p + G02#P&$(1(*FW'o6("HR^f3L8e?1m#[2;;GL6j%rj!c: + I55]-e2@7%=q6IN&*P-AY`a^D1:6diq^((EWS"_"PI7.a2B2>Z;`2e&uM_pi0K/i,tZG2q$ + ;j7;c[FE]BiUG&m0UND&)'SdQF'1gNA8Iuh4;5Z.1,6Dp/kL$UkK0`+n0*@HDBCe%b'0?>< + 5j?e7"VK#hE0-Ec#1!b<7s/&WD2pS:,M,UZYprreMf&Z#`A.JNo^R'K/dh<30acS' + 9>.KrAei!3M#ckSAFG:5;I,KcEoR*0#t;i=J4pJC@W$?[(-XXY%D*"TBi!PfNQ5kYWe[N + ,C%=^eFO(nr,IVf2;3E>K.D2[sb,@>hT6E`g.uQSj_G1Q8dn7fF3SjU;P)>E9UT9Dg?PjGfaZDM*Z@=%?\15OSL&^fcuXT-'GefP4SM + ^T]-@(8/$P&&_F!k;Q5'\Y@'<2@F8/(2ns`A]9O7=h>'X.3CA4:54(O2pS(oO-Q"`RNPU\M*PG8lIl-`"8iEd5]I)HDbVc_[>3Y3GV]Gt!.e + h3U-GIQ;[+H$_0QlmOg_QrJ3qR>c?6rtu5_Ug"uC(^Xcr:3f^1RPP`]'Up#oX.&4`RlDWbX + JY3IItH7'ITSCr=>ub4eXY8V*SJnp:gZpRf:DI!*t$779^SFXD4^CeL,'!0;@T8(&YJZHm^ + 3-nE1Kh8H_A[[Tf!T%i=$2]aUBu6!C:$)@0<8d\YAu5MHdoSppHnWZ)!g[V#/Q9Gg21leYU + u.VAa0GH>C6'U_[CE(1Am,@(Zgh-3=^@:E"denkE:ipC22o%D%R#\H-l=\TYQRX;\dQDcs) + 7Q=([W_YbRp4=.lb2oAl\_u)Z\+jLlI1t7*;*2[sq,A]kH=:Hu(0hGI^8_.d+PbWg"\B2:hJ + m?)K3%G*c:J6%5*9s#,qc`9&VmZD3*Z.C)3SPU@WLaZ'RQ\]+A`2:+OrRX4H`CH(L)s5&U"dkO?=DBS0,Y^RDYF,N1-` + a4e(/16/$ahg:acFS[I2;OX#AlZbRmh5UD`OH'DQeid + S-A:\"KK\LA*pf6H=pBQi\CUs$B=D*AeB85osY1RkPJ:$eQg&PnS#jXWjmPd,LQkqKl8&r[3Y=0qb:oXQHLT&[B. + Mad[A@Pm3SRjG<2BImN_qMdh</h[fq`FEp*r0BDnOilbU + fpp'#0:g@#Z^Lb-LIn/fB_;.,7^#g^0pB[D%@5=S[G,QlWcTu1=fL:sGT,E<$\Q@WEHrO!= + !*dNk&^@S*cFiJeEM+&*IcUE\_6EEqq`D"O2'fHH+]_u=/Xa8pNlf;hhTP+;':k-+.pqWSk + OX*#;c@#^ec37!PlbR&HtGhA.iuMd"13^_GH1=$UV)9.!Zg\r&5,'ORtmO6u`&s&Ob"l:DH + ;QA5O.3EI:t-7WFYs$:aNtE=BlBj/TkCLeUb"$iY.j+> + `Yq)C.,dAuFP4ZXG9F#RWJ'>5]bqr.9<7Gn]o6K.Oc0&p-4!3X"_`U^nbkF=tVeNt0:e,(' + 6QbqP&9#rq@G+)se7$pH+7tgI8^aub/oK+%g"/rh<;f!u+6QWo;Ugsh;c4QIq9rAEeSSYpR + oO<=FOo!YrcB):eTkHP[KV,%)\pn[@@u^GM.G9D]pWLQp2-L6)V1>UF'=mCX_\oa[CC9^f) + bkkR[4c2ESS#hb%]X4?.=@oQX/QoRTH'FEaG*GhIte:q8[p@fBrQN?apuoK&Ghf`+S$g?58 + 3'k7O8"ikS/EA?J"-^L:R'rY>C*Wi^PX/ + d>"Z$>S1P-qFK@c?H&*eNkF08=69lEDf9kkP%(f=Ld)ro_,d1p)+7`I.'rGO^*2&)6XQs(C + so$6;ORocG#MCgpi_kl@qg1kSo2+_<5C&@9rFe/[d`7kL\_\(9o0P#=kma` + YiT=(Ie+bKB)Z8?*c%WF2H,/.'4)o-)agX3Cd,#_ + RI[uJlG;kY5.n#bVJeQYg$h6P.*.7W,F7mZf.KDnN':b_KFG[R!77'REUNCCDR*b"s9dkiX + FtUG!]-hJnW6n=`1i_A6nuV=Qh'70`OlI3cT(7e>.kXUXk<0IL9HLl'0CNKT^pk%Pl.%elL + LKc"B@(OUjZTM`ihKIVO\mT.8#^VXVH!D_<,-8KWYJF2/SD;QRNKH2I1VRlMhj)3a$b + $e'9d6,Z>%H0fkW/Rm*^!6&R$`a4$a\Bnq;ro6ISCm05=u[Wf8pE:9#%"W2"`\<70)r7sf. + rA(nJSlNTVUH\!=U?,MdT9&iIMWjZeCgQIJL91c*:\lWat?0fISaqaMkAK(JS(Nk:$X2-;g + KWejoQ&K$pEI(ai,+"Y)?*_Z40A0hmQrh\+b[u+GVHZF&N=':)X8t-rSCM?&@BIeB]%@r(@ + [ki)#<'$F3O"b0DYR7ScFMgoB8#a`JT*$g(a'.d#T]OT7XmEG.RZ!g'QXs'c-U276>/dq,u + d1[;"n'd8\Gq7cFqt0U6.rd'd(MNB-TN$OR_Qka=Hn_Pu?&l9V.e30=9<4eXF%71=fM_o`# + I^MM7BPc=4tBO#(BQd?udkB?b_"2J2mCS9&Rhc5G(f$5!=Xo(f[+_DFX4A=3s+/qZEsqR&W + CoiuT6MhUia7j6%NUGQ#6YBh,%==A6`/1uLY#ti.S3uF:MHqCUZ9$1mnT4R#9S$Vg8=%0/W + B]AAges;oXlZgfmj'AmKZ,YJ_q!/tQb-SR((-L@-OKYH(a1-.27;Vc=Eb'U@\_C.Vh&W`1Y + ,%W"dYi$LR7hb#1o]8h,4TPo)T=X7Ya0*LG>!A>aDYAQQ`RXjbR',S=jHoZ48/ZtQA^9*Mb + TY-%g([J%5m;1$_-oTE8_3KKdS2`SN(DC2pm+;!U)?Dg$Q``@)s)Q.0[ZfP_D,<-&W&\/&g + q!csnFpb\1lQU)<$L]':fk?(O:$oPr\KXGH>E$c7-WEg$'BR7d$sc,pa@.@57\e6^0ocFm: + >F_pDjEq^V5q1l%oriat#cX?*fMm@MTc.[LtC[E.K[ZJ,EQWD)=A + SAKRV6q6$eg[_F)R\t*4Wk36ke]k\7>:/5u>D")BDU>o&,3?ocR3d$bfj-[S:2SZK\* + h-1*1M_&Y`6gTpI4,.40NhN>LgA4f(InshYVbFBtkP74l=lZrSRFE^pl<,_#N!iE43g)>=\ + SLo95$/;i2Os%'[p6S-TN'TF!]ar<9,n"=oV>6^"uGbILQ;W%*HTS!c"3RQ7.43PulBl.A# + MN4J6t3Wl'A3ftHcUG>cD$Rt2K;l2j-E<_LKa:%2:gJd*<%L,Q[,Xlmo0P8q^be-l]Mp[L9Jf*f1d`4t4 + G1C8ln5rW:;"J@/8E6ggJY]];`.4q2,YnkqcC1?cFlSJ]i$OI>XTf(f:tE5,@b%3UL66s@( + %$12DX,@r(J@Li"f9e4sO_d@qiB@CHb[4Y(T_;H;14+lb8L.5-o73qs;&?mA4<8Y;09G$M? + ?AnZR!/7I,??q3o&rA2jEq0Ci^>MZH&:CH+^o0M64:A8!rLrih<;0Ur-IZiNY<^o)^.L3Qo + h!Q533?%jO/\MFqu$PpOQ(FVCh3J)rMb(mde*%7#@1#ne&_]UANT1@Us5sqA%4;FG5(,B?` + _0`#XF""l!R6e/01<=nK:fIZ"=bN_\"Md6U5XPX(:BU)CKU=h'&4D.i/]e8MKV1C/(f,eZ2 + $I)MK9Rj=0Qi*-:;W5D%d7tfaA#4(5D:dXK6U(Bcnbf43!/;4")ojq\3,Bl;?RgpKnr5$Y\ + &S37#.^M"WElHr,=t:RNE)`"kZQSE)M?[>_Jb5Kd]7iE(^;K1'Dp,KKqGIgp[@;8->558bb + (F&4$D.\V%t:KfDMVBO:o7XhpPa8t]%9=A]2\=+jsBZbSH>n1b&*2?M5pK8;/`^cb:hV?R# + p"9aPaXG=[J_Ftr1qh6^kq\K>%st0G6K+4EEkW<8^,+ + i587G:+S+Be2L+GjYZ$Y6`fH<]?Wde<(#*V]aX3p!mX1Jm3:OB6o$DYA4.,b76P_EX`E(?4 + 52XpgHE_g7'u\Oo40a_iAP`:`:IZV4T%VcE5#gu2V[Fa>4"6Fl:#a]^;U119kL1!l35Y-O#0S6MQJ]JFRi!Y&`!WX5ZNN`V;:]u@5(kG4?!rs_Q + Qq)e=G_QK#c"jV>OAl?nU4V;HJZoo?J-LO0)OrJ]JHu\bYU>"s)TtZ,#7t'l\a`;1+A1/7% + U>KVeNa5H6A&)cK"*$+ck_0T%ffu@!(Ar=79T[V*J&GKK4$227#cgiE.srX<&IR6^ifnF:5 + &[5"'@%)+<5q\%fjZO!"].XW!!)S!eK8H!C/^Jn0`qB9*=P-ehkq/4`a\!A(O_&='^#"2.; + 0FfhJ6bc*L2?`%NqMh7NUF:$goc-%J`/[o`6S:.9TKI-&tr,u/8;=DU*%HH3qYl6:'1_s:> + k%?*/tm_)6@:CQ_g*KW[)>s4]Kfd]&$SG#,%Pp=fR/c>E.'McuVR4^o)`"R"YF + mH]g7>X8HYnFJNsbHh9iR](]c<%<8`*-/927Y;*\,Z=(oR.(g@-Q:<;?H!a$PnGRN?Pkma: + $!>l_p#BFL"]p-Vi(aJaIKp$QEs9?Q^ee_L-skPKL/#@7i9]OVj + aYo8&D-=_@3-(j'2-t>9\7Q*=2k]pHd&l3k[Bl7=Ik:9oKKA!.aH:t]fT?7/FV6_>$1tscS + Y5r,kU(ip!7M&=^0b$bkP=fiRs_s[ZfbtN;Z0sSZ[[<`DflA"TcShES;pa>!mAX<*R^[>W7 + WJj)(_RZ'tW+-`D4:XlX^uZTQ_$H`)b)Oht`Q.X#d]LR$74bELae!m6>dGnG + `QX?bpB:7WK1IMC^&VO.@1B(5J:,H?e_&enoZT!S"#)4Tc=+`n$<2alJuEu"*30RGiOcMW_ + C0m<=B(#':BVLq!pCt^:_<]iB]iO;;pZ6"^c5\_<9@#r!14"AD7G(s[ZM',W>"oqJ-[L?QoHJs;`=A^JT6;I/>cN@B/[N[TJOZeq!fePQs>Y66\-UbT(if$U + bK_mq^C>#a?`?qW$jSiWL<&Wj1Y/[XtjT7l*1/LFr@n&=ZB!b"X2c,f"CMl-keiHXMrFL4O3_fmnKS940F)/r + >VnGQ?Y0!1r"Pa:J?u$Hj8LN\51[k&.drA-Ds"eaOi%:^HJQm3$oq!@SJ4^e*s3i/F7SB>n + *=$^@h`%l4B\j7ZVXp4CJC^:F + U=oD\)X4\A$f_*`k,a+4f4dQ\5p?)#/E]uC?."aA3RnA:;d>2Ba'Snjq)<^[J4QiJ&%:jm% + YBXjh9aHR-`RrjV!Gf".`[T<:(aSm44mO#$4*.e]99YD/6#SPqP[?fo@W(!3OUdLJSGQ))2 + j^"@iBLDJOqC0EPo1FUi)W."$b/;[l4Z\G#-mNdfQ@%/"gN.L";P9UY@`o&n5e%7^2I0K"I + ,\972odpO0OrS[/=BluW>p7H*nVe^Is`paIs#XsO<4biH$%V]9jE6aO6[rcSmF_M)(G>EDV + 0+:T^QM1UocAn8L-P9+G')14?nP+s1-&pmgT(-UInUoJ'2]:m;of^;A/^C3$0-3oLVrFZUE:l`UJeCE\LSSi$#<>k5Q6@sdfDZh)nJ\SJ%BgD`qXFa^j!f\U4g-(I<-deFYW7F@6^P6Ql + #/Vc&Q(5ti$TI"CC8[Ul!^FN#og^u"9?9EG23JR<#9\,]`dU`cRNN0nL:Le6F1gj@%Lqk+[ + ZG[K?Yf+!GCFh9D.a;Xt(aW++dhtUQ3a?S9h@IHEiN8T(hBIj3=*W2K-JY3_Y*aSK[eU-b% + I:q+/DqHinDK$G%p@inHs*AWRNcuRdGDG'+5Bm);GCCLPpQ+5$BANO2LOHoK@BY*'c5b-CM + rEiK6lOEs%\@Z`+a>h5EL*=p7iLp$)rb].@Xb36KpV?/W8at[:X>#0f-FM[<+>Nncke%_-^ + %smP=;=D^A^!G"#^):JN-eo3'2^*$\,Z(E7fP!+VSe"TCH$r=tpt,`T+)qlZtn=J%)5:Be5 + iNa-+UbZK[#?]#L.4-?E>-EJjde:FC5;!tolGDGZgEAfL]X8Ysk7Pf=FfU=uTs?gX^"?F*b + 95MI^ron'qeZ8PeWlTL[8fO)=\.t@fCj`A"=DV'5tJ>,P`[?l*lrVHVD?"#G3>V73!eQh,( + \BK+JEKYgZ13m]=?#Q"Q[55A%Rb@_7dHIs+Ffpd'e'tb4]%*SSV6S_?[Ym;7iM4!@5M@V>p + B,o]J$]tR?9]Bkb@iFd]^BS6T?_)MqH\!B?aS-\5M=lLs*MP`ms,f-DaXJlXS$p'Ic"&,j7 + ?l1^9@6>X7?&arr6R"0L(0)O<2&"co_mC_,+AS#-O^,S04FN3DBH;Ye0]>_U-de%E$QZ5n6 + :Zr'q=:L65;%!s"]3#=[$f@2Q.u:dbd5V7AAp$t=I8)*l/$$oc+f*).WqZ93sU$/tYp+%an + .Td['o-jTjp&epQQp,YPBb-3o+-P3-hP,h,A-Q\=RE(Bj=TiAeXS^fhABAe@H`bL>8> + 50NQqq*7a*%1,>Ie.iQ^d54!$7a[HK(J)s'Pp&`Ie?(Gt;cLFh9[f7)4]'V8*C3S!7tH1e8 + d:E6^RaO2s&[c/Kk4H=Q(cB'f-buj?Ir3UH!d-Xc%^`#[QTFf=Op@.Am*\jTk<":IAL]4QG + '24g=FfZD;3Y/_/rE/F?X:)Dh-8HSSS`[ZrIEe7fu8 + PNa*%iD@)m-mL*-21g2Q$GQ6V7GfWHoI@JJ7)N?+D[5_%hi&\jK>a(@u/AGI\*D]edm?^G@ + BSn)a#q6APAB1MdJRqK:j\Y*V+7h9KhpI + HBLK-@1&'T&Pj)Bss3B\/?)UY]0>P,'NF;s:&!RUVV\?E.l/V^_YUT6n^1L[_8-;NRf(b&> + Y-.Z1g7Xo6E-\?Z$+([/5Tb>R!#Qd?^V3A)F^$3;=B1lU+>JAq`/NG&r69M(:r`^IlW;^V^ + ,bS;oZRLat<'1/^f0Ig%^1;sf[! + ['H+`L,0k!Ji*+,LBkV1cCBAl>]G]MD6M9QBPf`I?2H-.Y&*d*eQ:oOO8b9VDR;#PRr09k4-lmpPr%c5bLQ=0E:J)Kj.-1<&PZ!rJ8pODdARqa)d]QZofjQGnPdQf4\Pt + &SSuWTHpQs(W?n5750\:us=UV6?9QeH4f/1`WZT_tq$(WnbUsiJ)VI_BjLqDg/nX)*1HHm7 + 5^_e'PSBM:hYaO"WfPKq`lnsYr(,%e;_\?U>Kos]LnB,D(/"b&5R*Z`Oc<7gaTTg%\d$BSV + g)USB9d5A"k*jc_UK+&5:5cOR[mo*CA#%;6[*f`I8D@ouDF9u-[u+/@g[ffhe@([QC1Q:;U + PNoSh8d&B,3$fH=?!H`)i@I,k=3"BAi;29_,63k=?04s.IT7^0j%RK\`#Yf[kKZ;Hi6>0:)@<'2m`r[+ + uaCoY^U!nbZDB5:8I1N%Yi&kujoYcC@E2$e4\>S.-#Va[AjL*7C`Q0A.fXcb_Upj,eGZb>@ + #(^Z?h*r2K)3;Uu.M(C,a#Ys9ji=^)G%;@&`$]["Sp;D&1"J8(VoDH'bc<-fel=ugQ;s + SOmXqjW'$mM"?"iUPM.RYSd\scYbg^6b;5V"Wkkend/Y\Ce%-=!n4Xm4elBf!f@,C.;Lo.= + 65Z=\(:UD@4W&4dR<'$.=SL^IXg-2@sqOHqY`DCci>_G3;2-+JSSZE>IN]b0C=PfT\e>0QQ + 4`+.kT%._*Dgo3\9k=$k/*O/IVS9dBgAE%@K\+2#_`qoK@E%_W$mAe3^VSh`:36\kG$)`:G + U$g.SXb;oCSOdCi"1bKB2=fE + g-ql[pXhdfh#SK:7)j0^dCXsN8q7G`GAe4N0Y`V;IQPPos!?`RVi^K+:WrQ@0nuO]*1Zfic + sd`0NBJU6=4j"l@Z4OVnU<+`f[&(-5>CB^\"`LSkKj$qI_Vdb$MW#Q,#8&u4[(),T(t!eAC + $N)EgP%Cq-,=[H4?/XpXPtP?))h:+[l=k)#N!Y2?"TFffpc)c1I@pB.W@@om%j9p@@$h%,#.pQK%R@c""[ + (c%A@nKW!MC2;TWSSEEY7J7c?-:i:[REU^&#I"O912/%Xr#]b+g$mf_R2JM%HUb"b] + :#f(B_i<0SiXqqHe)aR^A0q:eNg)%Rg'`fcoi5XtG&cghu]:al_^fq]F1EOs0*t_]unb!tY + pE.f[+;+Dl;>ron3(_5:a3kT-O)6&!Eh"du+qJA\$tT&e.eW&*,*F`+E]3W34!HEup?Y!;; + AAk>b4i2:@;Lgm%u2Hc1-s)O$.,A(:o:X>XPG:4h2nks + k]!utjU::!$o#R-NU[G%qMDH'57!hs5q3D-9Y$gV#9[=g!?)HPp:uMC'd7*"_!VEb=9PT&' + Z#G9@07Vtp(DoB$u;;o+X@Pj_B,D,N_+(NiC4GfljQac8'ZnuTTf],RS+8!:m#'Md7iRC:( + X)J$R)#STb8H@P`rBW*'nQg]`'>XMT.<: + ^\*Y]b9%NrCcj9W<&[(]EC6^2R\;uoKGGt2t'8/-PE<..;qX!#?/+VI9jE^<9[*@(3K,"7, + .Z8G6p2bL/G,LiJ\$+V5/]-K4u,d288%t>lYKOPILaY_A9c]H8s@j[Gt-M6=8<.^(HY*E:(=!\2#>#FdA:TeC&X0D/g/-abXi5;:GRY4,(=:f=LD=Ki?@=9mqC6J + !uDYSYb==O'A@$^q6L8-AsV'd4q]@hdX9K\E@V2%YeT'3qnX71$"f$8o]UPA8n]>?oG-2bR + imPBu)!GWJcTL_GM*Mhp8hPdo)g'g]8Ci?[n`ro&B*)$)u6Z0E$3":_"RN9JgqY,i\\MA2; + ,'g\o9@37mNro?=o*5Oc*c;#&2^^6Za8e1h#Zi+>I"@2Ri"LH_h+k.<:3FWBe6BMfem\Yt] + >T86&>#^8EUr6"n3We4d8&.Lp?nN3<*"L9\*T;G\i4]MJIZO+/AbL7]$9KPnsRj9hsD%sHr<4-S/dquBTCpr?q'Yu&\lGs[]D\?&DdT9-u>>8q!-5 + >1+CnFH:k9ge::6atcDm<(NK);j.U9]aTdZ[KB"Y$=n.I?4on(0%54&M't;=EUE-QO_;G>a + Lp/6+8QchgY(do3IU/WD>]M3sun`'nJ&@TA$fpX[B&M90Sd]5C[*N#($(2W' + IIfo,XO9DGoS;G>/2a)=r'DqO)h(BjPX#:?tKT=58)7Vbc#[8-H5q\8]O=!&itrGm#_Eg6l + +DpNK-)Pkms.Z.AMn)&4I.s,u.FRSe!'eIa1b&itS;f*m6[q0`GmBJNc[9J`VIBC7BFiAfHcWokd$T6=]/`U-CI1-qh318f5Fm?J]#7VhXAk:JTi% + bA(`c9rJIC + ZMYPqDf[i]>d>QT$?MML>=NbS#TrQhl`Ab/E0>(!8d?_F + 'p%p:Xp0fG=U<5LO)eA:OV4'YQSD>-UJVggcIjRdq's_b7k9p"d*faQ%2!_c4TG.\dQ'_rP + sGEG9pH8-/rI9+P4(%a@5!mS0A0t?C=r]sCG9mLH!,>G2jns?*I8TW"kA6OOCah!CO5`X(S + OS9T_S#[$ike`#iKk1CZ5c]WYQa7+2Og1\[d,:1C6]CS1pL^q8[q*j-\?rp'G-JednFp + $@%lI9:`7WiLZ?tIiuUS04tFa"hoXTjaVr:D3:0HZdE:,9O/`T29Y + &?(*NjMZU3c3ggrB8>bKT]9c9?htHN-iF--_UUr`n9Br8Y%d'pG0TcO,f!'fiR$A!FH8Pl1 + ]j'i32>m*n0/?8(<1lYgmijT"fGdU2*^cfLl$hg`6\:e2Q4f%tnn@;r/U4rk5h[pj>Y>Z'#gNBc5naQ. + Y/+-B*"9t98Q9)j"TV/iPGZ*c'K\n6"dPPrk]S5,t<\b,RAfUPd:1cP<'Kh.uHl#bN?17q< + W;jE,6Bs6,i1.$(C@_H<$ + +Qp]D!+&G/0u6noBG&k&u]:D?4t'K?e[+R$Hc#\9=p1`8_:*djBPJc9@!OlUp3a79h<=eFi + 8M^-dI`\K8l?A2-VMmp`A^Li[*:g1< + "s$b3/c9L:CdKA=;#osAK=-?;q6jr=r2jrqc$W>!"K)2VR]f!eohd>@TJd8NFJB2JL1O$!' + PI<6q'*E2q[i3Megu]e['-t(A5.TgU-bt3=GP>:bi@Z*Q<2,2fVe'J`(CkBEc4#5(+:P4kj + p_\)ftXP]]e9H:5Tb=q]`eIMI?@G]k$^@@3?&M;gLl08j="XuD;LR+=?\A,-m'P%6R*8!@L + E7TYf2LOjtP@'KI&@@'L[g2Q3;(3Q2h\_5f2kU6@\U$,Z,O.Vqg*gCk_RWb$rBtB=sWG9#A + YG`K)+oeZ[RbkZ\C\nm\Z>;TTa0TVE7b>!6]2mamlIqim\Sf!umCI&$2YWs7\gDiskBmJRS + *)m]&&WBL0,MNr)8@/7nf2Z&amuAP/;&!l0/eIS][@bG+rq"9Mk\:j9@BR`i\lrF*HFW2,o + r.Sg*OGUM[2V"pVrkmGbT00a+S)'B2u + J^>r,5q"Gk+UE=FiURd,kZT+&;0X@=fP>(r-7)'VGQC+O0*MC70niJN-H%>-)2X%k,K,]\; + Gp5J#sL&=`31%VZ;sVTAMGM'bo9S4DbVH>2FfUZS7AjGNlg]P-`Hg4`bKs`j)eC[6oY0U=s + 4.ockWk;7*rc/LFHJRhJh + >9mO3+B&qj=dtVbc3Pd^'']kl!SolT-?HckO1`%c!`Afd[4^7N*BbT?O2i7=ckOWtl'D8H8 + `ns'q,mS;sP(?H?s$J1D%A+>(I8m0kjt%6mU=C$qFKci'dn*`Ojhaf9 + ^:k`X`bDsOukEsm>61tG80,-]@VU08cusZY8!dsQ]b<_2+;eO3TiO\_c'mBfQtkS[6:hVpY + QR/Wpbf9e[O90$p3l3onE1l,:re/.@>S1jNuJQ8R#R#D`"[,f2jb0!ME/1JX,@&)sADalfl + Qr&6cn9Lc5O2j<'#YJe?YF9*lb-m"G\SD8NefIZMa?50+'Kfr/fuTsQhe#Y#l/,Uj;l1dW, + ?nfWk,+P*2/fX$Ut@qin840AYCX:)#hSHV?sp*i3h#1?=Ij\\%h?@--^)O:3)k7`DT@Hs@ + `q+H3O;O?c\E)"+'L(BBl-7'[5P2e`b&-8FV9)sZm2!6Qk&Ac6;#4S + o2\8*H&!IuS9,X!Z&r\W^MW*kIU"#0rbJIR!t94:LD15-9pk"IR^MKT4Z"X&F,]]YOO/8*@ + V-V*BK-_kddOja)M9k4s$.D-WjQ\fe^,q^S-o:u@&CAars + )h3;lsLm1a:Qk$ftLs;tDQ;aG(W^r9W/rJ9HX=5oWI3D,%:D/QUR&++.B(_/^qicnP]OPP& + f98-a[lPReq-HjmC@(q57j\P]ZpDgh!mDMmYAW]WNY3&gEp,Qn4'Wn+;F@YF5:\q4l'5Uh: + 7@Kiu-"3PZd\0D%&g'4KU"EXm:'*k/Up)l]kU*kcROsYK!;mBE + i$!ZU(8`3'u&3k2 + l)d-5&4U#h;aGGAV$^%8f/cnijaZPp2ba:r6RE:\^ac)_2Umrd5-lJ/9"iDn/9I + doL/&O1q3p`Wa1bM-00?RNRVrTU*6RTc-*QeejW4Y!dlo*#92FE/#ZP1#>UG=VfZ+KWk9/A + K=)`^Pj5YGWS96&mrj?pS+UVp$k`+)-*Hq1ie7Dt@g7&rej!j'>`dDXg8[*l08jAnX3q/?Mr$Vg=s0;=`Ok+n)d]UE?Tpkb+Z*QN6g)H.A^+`M7RUfGMB,0% + mC'B8/OX;BUh`Gu?-V0\LM10$BK1V=.@+`F`sh"(YSVjE&;Dl8YnH+VS9Hp!GJF$&^^TP3` + -`fZ"-_p[!6Rp1=?hEl.`nrX#J:fekQ3$6++]Lg-Ko1J:e%R[7"]G1cRE@WW&oVOMQeY&:\ + :0BUP/`[9*!lX!1].J)VICB&'t'4,eu8a+%++)Fd.EaJiIZfG@7g + >I;)>`)[%U>rU-cMgb(UX(MLEWeVGVa[d>XhmS>&kT*P6qTW8t(uEf\B833s$t;Db[H]X"k + cZa42L_u%;=$cC/6T1ad/]PsXPr45eU]0omnW`qkage#GS^Rjdb]bF-^/Aed,_k1I91uqLU + UXpU#`uj)9.tl#-ZW:#Rh,ECY^$Jocr**,7>WsF\%eS2C)s,'@aMnqE5)i(WI+Kq@BIs>R7 + VN1KHIKV#C!FNF:"<%aj\SFF>mFJ@KigPA:bG:;KMFs:QmtFTO=l-0I0O+>7qsp3`->%QGr + O19:;d1Yc!\$f5)-:B"G5>35YqS8%[b?7r4Ao^r + WMHr>o/fTT`6tKNd^6O&74l]`ajTOg*fVjAN4q,K1.8H=dlfM9:qoq=eBCWG]kgS0N)H;/a + A]ang9S<=g:Tk$h:)8Xbj441X[5_j>qABM/8[gM6g$?r7nmK)<0UqCSCk9?frZi"[_-;'j) + %d2^"!FY#.;2`Y\Jm=I0PL3"^"9Ii_KTJ![`n.>]F#"YSI6fE.u)%UJq-#X^+G[3CfV*8$- + 2peF"RV9EW?l-6iM@:k"-q<7i@1K.&.")I`pG=Tka>K*Wh+$=X"V$36(:Q7*CHn-lFpMd?" + -R9YBA+EE1G9Y$F4Uc9_&n/>U]D2!4+-7*neQ/^ZNJDT,B?L@4a"&q:NQ3dERK8mIjo;O4!k`?hGRF-31rJYgP4\;FIfH:lIcu"c47ddX*uPN + LT"LmQ`.ee[je4M@c:#549H>rHUJ&Pce)=]O;fkgaJbmi[`nHEOb_ + oG!`6(Mags!E59[uC%jfi2-iSl!De25TBm6o2\k$(a*gKWlGCh$G%c+^i%XUVj"Xh2ik]Zn + \tiq2*WrP2DMJ(tep>RG_1W*G*c@YJ2b";!Q7BGuCmB3]mHOX57&Qt_7^8QEuYj7Q_RX^GZ + RHX-aV0%_s]11DM$7Ge_J"?8:@dUEC,8qZ,*am6hu+[)<30GDLLt+R? + bU11L&/rWpYWu(Ao7M/?=_]*oOd3Ji"D5V7KTnRPmFMqWI>%L>WsU=QsbW9WR_DE*XbW3/A + -p11`\53[@fpB&PMI_1j$Ol4AB6Y2-O8'@=6$E/B0i%)GGQ@4f,3uAZW:NXgOH:g?[YG%*r + B76JE^'>;Xf.>h%8@-cR^g`]4(EKC!'bmS0fX@c^AF"u#!H1#)[^j"<1K-8]GfSDt_s#Jau + +QsKpR6r2/[05)%cFur]?D[(/Z_#;MJL-Vo=6)2Zk'@kJDnN_Z.9I:lBK&de\qJ1d=6A'Sn + :jQ7Ln7@;Iihdhe")ol)!)!F011_2>joJ-laA[i7uaA@UR.j5A,&@-2iF"e]MiU#@4diAICS,LZ&3(;31 + ig3/.g,rs\;;-KiP[8I(iP.TqT+)&A2kft?Z`N`l1Nmp0tgI,aDhY=UC4lOkm9a,r!j9hM='/G3TE:u597t;@mGUZk\4HeN@K[N)K">)jQqFKR=n2K!"o + q@##.imZFp7))lPMRb=pLJ[D/;/VFmFZ31j7o-a`:N^:9F`ag"'dN/6Q-r]TB1<,E,+N: + g.7>7amo:n,AdrCU%GCH&muDhF(4Osa'$M-h4XLn^hLa3le_u2#n.(1VQA7=^"/$\UbdNC- + oZc+^>2'J47Ej0R7O6_P-0YcY9i1V(mO;@hPiB((>FeIB[+f;L0(DrTite*J^7FeT(WlGen + [BEV^DVa?)u*b[G\;edQs0foAVGA<4:5SekbJJ\n&Y[ag?W: + GmnC8WO?ri?>!=bO*781-u_:grC)@Ro&q-'t/*'&%-R8EKnn"i*h'QV*LXFG8(9aWN3ehiI + =^!I6&mO[)cs';4_g6R4X*"[GO`ma@q=S9lW[HT+Up5I1/V[?Q-t(!VF(j>_#n@iKRW*=rqD3PY^cG/nUD)>s"IXV&6 + ak**I+eX"CO7m+=;[7)eHegHEe*8:b)T?rqUEWec:4VqQol7m=tJZ;?*l@4%Yb(:@%;!!GDm=E + 6eHFZqgW3bYW*%,(C>l4Wf[N*`]!5d!`(luJKJC7V#QrUrG[*E[(QFDKT+(TLB%NNI$%p(,Hc'i2PNVXbh7^:5D5 + Pg*0&>tDF3ED2H@6jL5I]22p3ZdG+/d$DoDAK9+2?G%o)HId"8r58O;n]15nGR[gE$P"5k- + %qOb?s]k@)+4_XOoe%Du$/1Cq@#"@EclOVCG$7+"[tG/R6drte-:Ch4EQ0r/W/`j*I#*642 + sI2tijm"6D$;CmYLaDst6,tS_SB,JVYHrUj&P+8Wg7$.U8%SK^d\::&]oYE7U1E=XpbnDAQ + 1Rk]Rg3([3N8/oR;f(b\:<)AZ4WRJ^rB8L@m-&<5F=G[*;!7cj7F;=i'h99$KdB7;P_oB]) + `55a9N+pX3DZ+Y]g?g?FU?Am<5uKJF4F9%='!leI9(,aBDKeUT1#XH?30]%=EYqVP + T,Z=LLAXA=TNfM.]u"H2_heeto6X>5$([Cg2)1UM8KG*Q)lhpD(ha>k^h1FBrPT`+RoXL#7 + .3Q^Pd)?.['aI%TR2n8Of)mK7uI==skuR>:USK]./V)29#R<[)KlGcC[\R970ZN+5uX3,b3 + E^-du(k4V0+A>VgpQ(-H->EYBLJc9QL3MpGXR+\'ODWc<0rb`R`re$kW=t-sjBDP)XJ"(aR + S#.$KL88\O3i89QC?PE#XD<.5\7"W,!O]1KL\\5`CthG*[-`UiJ$X"hEW0ckqkiL_DYqP\] + Gh%BkJ%r5#6]OFjlZrfE4eCLCDUX`)96a0IjA<>gnCjm)SF*E>bC/^3R;cZr$Z19+XA'1([D7_`#12E:cek@*e$k@dscq2>7$*M"//#L4:g!V + ^h@!]@>$(a0YSDl=D86'S2l>Nl5W1$I1Kci4GZi4!?fl5oFR0"95uO:7)Zfp;S#k$&P=N!, + bl+&KNUldD'm6OR2=lD@#NEe.n,i-'k3\1NDVkd`n8pudn47IEQ1%01ut/?S9U)$hEE$76mfI_PC1HTiil04KN)i[2eK!>tS+;YSJ + 4WPtMH`/B*`%elp-u'$Y,BA\0PFjLJLD2>Ij2`KC\,FhGmPHo@;3S@mpAg;SicIQmYVh7+'/ENU%=-=;u*tJi2].6/*CcC^mP%2Q@bP."cPX:"d4hVYN[8r>BQ,&](uG4Q7dlOJGWCa + LQ%Zj]q,@F>U,H;V+D'*Aa[:Y-sc\#Am2kXZ:TclCL*#K(%P(^j&i`TT@Kp/;:U"&-4a5"( + 240i6fCG+J9;WkJm5PoA-G^?1CqC%9MFE?m1eoLC1:%*!5M)X];O4H?s;RI + (q$oS=JYS+aX>@%jPpDCc33ecL^A,m'UYuWF9#r(]_X9%U>0@Y'$OYpZban1:*d`bkHM.c2 + "T!UD-d99aGC$cq?[+;8,>.\$h+u@Ub6Nu#\(nGO(Fh!DhmPJ.t<>hOLA[kTH0\fdNEUF"P + \7n:e>KGE7D9^Ifl#&Q&/$3qm7!H7?jZ?btnK73&K"NiN6V+i6,*]0j]dcoO8R(!sjONd"Z + >TM2)so$gC:'4GRSlb'=b#O*]p7$d!\GB$/L@^QQVudZ/]iEnjrP+?X0uK@=cU5c`6+L8DJ + 1_O4#GT;f#0@bD)#b;a/9"b`D_b\@bK,OnV$1um+WF7M'ZsnpSQKVQRNh3k'$ + &1OVB`FRGc6>(rO4+OV$D#N>(8)mo + Wko4?gBMuB&B2\fio8(ltr<"G)"5p>eSt)mN'R/>FOb]4ib8(]Q;t7k3Lj5olfB_hn32(6/EIiH#H$.iA[c,hR+q^G_DA]GL$ + "UKhm,8`1>pImet:TqQDR`+dI9f=C=6$[$H^Y_7ZrU%Rn.UV=-.*l\8hV3JiIc`5l;Dmg[M + *UX.RV+N.2]Mdf8_M>B[jS8=B)c=tbVk@69e\BUXA0!.8khcXYVD][s:KogW`=if84,^*5C + P;6`HU01*HG20+TS*_3`<,XA_Z(uq>8:ss"!0LOM;I''W_=`3";qNNN65IdT-f&60fc]+[eFP"d#"PrJ.Y1+T*)YW*1)@Qp + lIt'H;YkZaA!037^:kbn1NkY^Mk[Z$Q.[I#$gfrZVa'&'O2(?`gb#^X`3'Ds_u1ZhOI^i;: + S7D4q>M\$t5OhVYMVIa.-SpHJ3J5K!;H?g7OKT(r9!m0iosr;@n^hp;Ma9>'2@`rP?4:p:S + k:`,=4)$0'Ui_/-kc7&gql;]Go""W`Q:-QFQ7)0:%jN^BYWG(A\<9:1s_/P\+^ + 2#2&T[(QN:q32MRg\B.m7u<.N2[5c1s6Bkb=Y+i&D528`:&Ic + ,)%hiG0pro!SJb'gr1h1s95=M:pq![(rK$T-YOMP;k8Ws)*&5$^0t/*!bdrq1t[)Xf1l` + dD1&pXj=ZO:T1$`;)U%aLe&`6'^g%*ml#0'LJ+'^j9<%BRFBme\K:rpT:Yn>t.Mo!n3Dp![&H`EGtpY$ilqiH(]6_^eP4ZJd0[J#U%h. + i0"Tn_?qFj%3Veb^t&l:'Fhjf&`D^Xo6!!UG;ljZ2tM_^dtp%,^'J+_"fLZ&;4]U?S:3,b) + 5q0b8)Z2oRl^/C4#<]pF0Rfj[6(_1qba.MKI$L.f0&NP)`-"aXiVOpa$%o1*)Y$FPNY[0eN + WHN!--m:mK5\YjZi/B!E&APDB0ff-Qf]-!f8\HEWhSamfQ-I"->Yh%S&je!kf0Ms + #cGp9Ipk)qL^dPg8D39Pp]tfST'iaCi:Z"jX1[Z$;>j + qE3Dg6M$m)#-qY!uOM<4/.*?RnDc"h1ZC_S"\LK;gmr7:U'Y&6\!tML@%hcK)kA9[Ik]a>N + ndP+$<7rjDdnSg$/]%3"_-RCTD?]=Z/V#]2nKdb4oH=`ColUBjZP,'7$W-F"0^Ct3TTq3$k + R$0r#.)$.2&T93k;'BV0Im8?X`2b<63?`Y#Ntjj+b14l!/,nW+2O6X/QNA>^'XCsZ1".uXb + @Sa1:8p:H8R'NoliMUGusZe=>bG-Fb(Bn`]3S5*RW>=F3mX&f04>N=nC/"PNuCPX'51c>;P + _4:^94h$U>dV$nQRlQ=uER,2bA+89TAd2FYV?LOk`]3]`Aka>SY:aZ11*0cWta7+;>\ZdL/A + *4[N*s?Et;4p%)NG*gX/m_,NPYYZG3kSR@U8##n5a(T?N$Ep+3C@E'I"&:l-.,t>1Fl-`pd + FEGNAPmQ##$5A*XP_PgYHH8!8aL4da:q9/Xdq+c9AaHC.0[iY'rFWFC.W!3VnE2;Um-rX>% + k;]Pp4[1EeUd"=cZYFF[[U"BTnTqJK=f$+K[nL"i*eJ_(ZDk^Fk&+<\8`%2[k!k&TZn>EF^5Q)!QpE.?=2hNu78 + F3;A6[1,ua%FdG*m=$;Y@c#gE`OZ=#9G>B4s$'FniFh&cYE!onE@?U$`G&@t:Q-jd#+'i#7 + ;qH-Nf6L[t2aU8$?C-Ej1bk9)O$dKVGuaeH8U2N2"[m6HH?;peY'EGk6XaqV@JPUH2C!RoQ + "#cAHr,pi(,^L3H=tej-PeoRGMPg!i9XP,AFbLe1n/'6-V>.[S1`cMP+cKH]k4]k.^jDKOQ + e8rH"YbE/(T=`QlSmU!A([C&=>&$\2"gZ0GYhu)$*\)B1=NMq/`0UVi!O3\jb#H@GZOr4^O=V[(1XfAe&Wg^>qGm.E_ + .ip]*Fu@6(=ZRc'e->I9&LZNkLmi711!>-o^C>QgDiGnI0H'Ir + N^$G%H0VbtAQ>J5WKZEj8:5h.O%(BD<:PEkq4RNLoj)Js+NRFQ&k&s%X_F%=k\UlD$2Vo+G + ]PTsXnSQqnA>q@Fdp$HMG:!VdNu;5h'f2Sfk4J@'cu3We4e]E8I=[4\ZQRN(3r&uqP.-g3M + I"r4ri%[SPZF!c?[#s4Sr65k'\,L<:*WP)K!_)r%?>S4f"*Po.B6-EdU#BP%Kn#@6QW]IVh + >WB1GYE=sl_gFnQB^N]"9p1Xt5MF#oFi`M[cc,]C!ZKT2g=onuh]R2="':&=9Dt,p6a#iOY + ;_TIH"Q0,rBrYb_De=5FqL(JFCYNL:JtR8e0RZ=`Bk0h:ePa*FRRHToMrYt + 5h@8U7jqp/daXoh&Fi8t!j3H-adG"V)@S[[)-Go!a0"D7>bQSrio[p5QR_Q+3;"Z&"q:%2,V41'GLQJ%;4u_^^JWF#;]OT)8Q:]^oE#E7ue<-M]-G + lG7.H,kJ2?$^r)5_h0Hi-IDPl3f=cafBR-ReL)O;G!?/_iqRa],ho)$Hg)NW:4fR.`X`]&_ + `7ur-e\g.Hjc`qV>eFQiqUaN1gB-/KGIMq>HKLIBn#j3*Gg[VhE)0iPqj+mWH4i$UFq8nhS + AUkj@D&F`=1PTJ+!l7Y7b]AtS@N-2d@)&mZZ:34QenAtd3oLL[%,aTelR>]_^Sa/[?pd9HX + UO@AP7=%9?0V:MJ'CJCS"<:crPpR'\Aro.;=6Nd5h0Q6!ta"MA5(q^5+BD#Q\_.8Sd([:h) + m05q3D$1)F@M&&!Z6JT;BRp4FO8(I:!m!U(A/8%sCLZpNCI-gCrPACXt;4+M&L5Y$f6AqXH + .g`<$GsB<;f9CF6Xk\!#)Wc3#-f+NR?Nle;:/bMgq')C$6s]IRSb]L\rKl(Mi&_UpI + LDOUXl1)4@b8ZZRk4qq+1!u%BQcTIRm#c:IC(rrj"-O.>hM^%_6kaK.?p`BI-K_%!tG6,cV + WlfI/2mRa3Y^g\#euDNursZ%>)@RC#4(5GVBmPOgtPQH0?p_TG.DJN$&Z)C9e!GJ\b_d\4e + KB$>(c[nG%r/*naU?5S9C5DE/_u8(%Vu"AVN$P=6c1=\PDe/XB&kEuB)B@5FBIB+mkdhKLu + O7+t7dA:BA(U+hdE)>s?!4f;3Bo\#]s9T#TFlO%_g^7ei3;d:h@[:76G@jdA'lA_ssD6M(a + +[D$Qko!'`N)o7U^7Z,EK5S.g3or5F_d>,7\^2l?#Jf#3poKKO'9pUMEuBAV>3r?W$s+e_Vo6@qi?VdYDt;.B%a>1>K8i9ChVN + /^-^9aYP%%,fMUb#*de"*0Y3t2juNjk]+ns_>cUC7YLS%*-:KJ59*.'2!$?%;oe^M:XiAR7o>[s%8_"D!)DVA-brb`R][BXN%2W;/T>1WYcE$+5u^N/+a*RT + !KG>6b2(e=D0G[=I2kBWFRNUUdl=5j-pQl#GsJ)E:0Z3]:bV5`b#q4q203o79ICZ^akJ!rG7ag(3/QH-A$4'pWjRK\`/\ + F(<%l=^qPIdt%*@=V82/q>&K!MALo>Jb@#58&SkaAJGHn`YiMSK?oJ8caH-(B1@mia@5foB + i#Yd<#Ohl=(HT^]Vh0`8LJKFIe1RM3T>Gj411[hdXP=6kJ&+W.B1e&,0BT&pG/1s*g^qld' + /*'he-!jWgiG$g7;e@i374B]lYhHN+4?nGE5%bOU&\+4Kh0?Ur`BID:Qjq(6HaDrio]S.u- + UE)690TC\M$&)77O:_j'R=:fm';>,Pp1'PJBi;k&`'7r_Ss1rd[s'X=PK4b>m61c(M&@)t& + OGsjH'L)o_;Cb2_Z54_7@gl5)1.J5))^H,HOQ",;8FZ:!_t9?.;QN5XnJ*Q$"M5:U6=ii12 + C:i1f+_/B;bcdYE[OD\Ss.MYU`&Fb_.Fo#Qr/2Ff=;VR0luOckVgD96q.-YCYa0%i + M$f4sdoCY=$VFnWO\<2l-6%Tb>5@)=4R$=QUgMT^/l/m,pA@rh7&Z3c'&V@+JW;\bP@G$d" + -M_ShFnn0bhT[.566CNlGUJ5oo2;30,C,9_+NB+`'g:LX\NIe7@-GnI!;eBm+Wcq4kg>+-? + F.SBRHT,o(%Y9Pn,N&cU&*FWn-jtlP%QVVWEgd@:(`f6%C_2/j/Grk%o:e[P&V7_N6V,R<$ + @s+Qd6a9B6L[aF6u]7Cb!8mXGZb>PPQ1s8a;!k:'.6-I9aD)27\40I-]^``4^*,'8=S>0eH + VIAC6;>m-_RSaf.80D\D,9dY2mOHd+"+V + <_IYXa\FG@k61^$,2?ML9T?R/f9cgQ]h<-?hR&FYS>$D',d)l$kUXNUGSDq4e/l!+OKVBft + r^uDD_8G2k$Q#CGUih@J7AqB4GMkPJ+G$)='E>Z[0TCr+CofQ4H9X3?oXp"9<'F5b).A+`n + WT&CT&FJoF#qJ,lg1C8FpN;re8Cr#4#A?jVAg8UQ'\(q8jG8a8!&#Css`$aD6)EhhS*6+AO + X>'Eu(D*Fc'"#XVp:JiPnA'PtD3bNitShmg#c^F5dB<;3,9ShdGib#:B\3jV"FaC5P2$FuR + lWGa/ENM8t95.ZdV)6bidQ0`dBZfkDFTm/G(&q3!3)'a&bt4YZYs#3(T>"O+Loc>[Y1B>[dVbuq:!Yc\k5ZZ^;q>WmmU/g6c&QP&4p@>Z#4.;jC + <]AhdW&&FV`aIlK!(-Um7d60)G1T!$V%:$J3%jo!_uJ=:])_1*c0]Re#NSiX[PO&)g)r:DXHn>^F&ChT&5m0InEAarbm#X^)Rsh3(d="ZMZ[ + e!\`j<\:X`7$j@MY!jF25\B8EngPmPLO1>(L5eE1Eb76elPFBCS0`lEbbEk51'>Z2e_)n.& + 9,=dW'`er%0q]).g5*_u%Kf'L=ki:ctN!6ok=\j30np"2mRQ`%nsC,LOFKdQKD+[6-Xd6e3 + e.:([$K7/Fj8PLuspg8q@0[E0[Oj5%-.+[,,:&sM4lM(lX_iBAc(^,dc.;eNgRffjas^5QG + `U,8+/i5R9b^>&(q9CKl'oKQ9!^K^FsC.g2_DEjA(5GN]]1/YO_p*E(X + -"20SP][fVKJ0?03+N*#ErS+H"gAsN8%N0FH=XgM$*\K35beX_Z]Vd;$cjco\N:9dno;JHY + X'1M_(p6S9,1!eR]!^/0mJ/%E*%QL!e;L>\^2)-X.c%n*l5Y9JKPGQ_NuY>!/BE6"IqKoLr + @0o"%_^D!mi1njI$BX14k<*_LRT^P*ro@L9[rbak30g(dS7?TE#'N_kH9%qO`]lU^Cop-fh + ;MKWN>O3m-fk9(]8Yr37]!eg[(O'T8^!( + -+PY>_]M2'<^r5S6]j"Q$KMWh>;(Q`)[Brh#7*-&\gpQcT/DO;b![G-)=[f)r'16Q;`YqbF0i::Z6PPQWZBM+=n0a2EBQCBd;pN^sTM8/r%/1CV>m%`hlcc[ + 1tU>S9&R[n*;/3OF]uJ7o*W8'5L"Jq,#d&qN(nKK;*2W=k@KTJckU,YJRO#rit,J.sGO"u3 + ?-"qXVXGZ?fO#_6jtb&!b$TRR/J<2jm):sb&N+<(>tVTO62_]!tk/?g-'X.JDe;4Oo=`>d4 + L*`:`8;:McQW(;Q_l%B=("*"jq,YUVE[mU).bFH;pN@7\q-do+G;RIjb/LAhE9h[Dt4]KDL + %*$7#a@.Z%`AKJGXNShpbu8E9biI&-45rsA.8:deX`Rj_>gogG5##SS+Go@Ru0/3McPnZ^a%k7"^"C8n^SEBJ% + RDJk7NqN/4fn,9i`+FbSaKdJY,F84%r*/@$e%K.RN9#XF#IVS\2FZg#u3;5K\0D?"TUeh@H@T914oGB94]]fAAHCu/6M9i + f_m:&"clloUX4fGE1fHCV;cHc9>rP3"c-q*54r'_hV)h&_A + jt3U2MF?>9g;fe5W7-D`ZoD$+])-"mRC^U,NElLbON=skf/lYQ9+>$_X0hAHHn$g7e;$],] + M9*k.fGCgO2r6rDGl^>.@R3cFiO(I9+Ch!_:06T:Q8 + g&7/A9Qqad7TSgJ;Y^r/ + UJJE@N%/Al\N2un.fenlK9p+==O^Df+=Y.$h/b1bnnQh4i5C]< + 6Hi;S^V]+`JllHXeF4OU9/Od>!u^]FC8&C7T9c\g4_l51h9QT%THAIg=0b0h6+LM87=)A>7 + ><+9sRM?9MR06>>e8O>4]MJ:cK1e,uj,!<9c^_!lorr>LWf$l&ng=MFZ*,:E@9KFi)HeJQt + SP>ZJd(9ug%_q-pF]gmuU/X;*+h(PVWYKH-l1kQ-I;@YH[S:aOOd0[YQ`4M"`83iHZp-q)q + 9-%W98JKl@m0LDa-l\]X_"B[]k4a1DqR=NbY.<&DmE')4dZ@jfSh9?`u*T`kmk2*n_hKe3m + VFtFe9k/Ln4h[@n*B$U7hc#6$i(VJGXBXUndSn^%g.achSYf,(W(i8N,CU*UXP6pkB& + NmK59sq$,P4I.'NCqEgEbf`4QG0k(sZ9,iM>WiD7F#n\[Xe$5Rk>6o7k;WJlQmri[)@-BL2 + fG0F&.qKe:,+P^d.J>[hK1VU`XG1C^lgpL9]789QNJ%:d4A'\p;:L\-Q,TG7JIVLd)1JORr + :ZQI:Ced%jVKcDShq\fK/(rG];fEb$#TJ3Z&PQ8LH*3;/Uct!;Cfg1'Q;%'jaa;4k4QNN=$ + Jn1F-i+3'Z."MRg$ao'CI&So + uG8/C]$h:VSPZl^h&B7lgDJbmU'b\?fhi+Aek9PSmpUVA)IJhg!40^joX68B.Y[\;k23HUZ + 9TNUS?U]7K*X+h?udkhr)`3g8*Hc-J;@/BBM\>7\4bW"Q3K!93X"aQL5RPV+tHbHs:^V5Md + CCoDi=]]BC4:iWb#pSQK>Q=TLJn!9>ecO>$a'Fp9XP@SVec2"YIooitg_ecq/U@W\jjOB+6 + ]<^k24;\t*P2qEE4KP3?tZl26h57L\fJ*Go$RsZ2jHd'_QEoh(1Y['U2=/n+=m + cC@NmFGS"m2F5;nY.3-t4g%uZl.Yu'mG8PdSP#]GZYF^6uXQsO7BqWsBlkoefVM!e]WL"QI + Ln+X@/qRj_p'kiT=Cq^R6qOJ@E@:g+_Q!`@2hLB@_Fugb&3SRrE+FnejI+odY8N2BZlQn#p + V1Eda*IF:I9su"d&MhT0NnWKr:5o$EXsMl:EeB:-2;D4pjXYu0.;(:?ZMaYbZ80*NW.BZb0 + rObo'QR"rW@!*0j5(67CAk24ZmR6CNEt>>%Kq\j)+CZ%8E]0B-046M]Z1+9Y_LiWa89Y8)^"jg!5kR$tg$f0O)L*07O!u_1O%=ks8"t3A3#"IG;d + W%(PCD89d-Pb%dHC\DlpWqcAkMZ==iI3;m#9[4R_NtDH7KadQGV=r!4o9JZKm8[DI_Y2nO6 + HT#MYOsLm"^R:O"9?O/D73YXM9j5cHX=X0]mj>g(r(1*n"Z/dpS6'k0c&2PsO,brc>94L]d + UkbCMr%E*,LK^<1rBiNUCF7Nfg-`>e>\UO"^6A3\hrf_g%],EFS?YP.=O;^dncV%VqW_IYB + M[huGS]\7?eDDh]YeNMuU'9m.U>"%bH9)YY(K\i(tO,46uTeIG)V1qh1fKLRI&/47;[b]c!T7?#/k\P[U*7`:I%`ODE"S70OMHYl"jL!$/kM)@']D(!;[=]TJ + [=CnF-e0&"%LR8FU'#/+?^;1;A?ed9[YcR0htYHm6OOt<:rgZe?p;k<7M^h:KH"GF]hes1X="DFk@*ck(Ctc>RZ#3cHmD3 + _r7bsh[*B,k'Ense[f6#i8IGK/(\"2K(?<=9!Hem9Gq/G6#(&rgAf"IVd=r8k(KAfi[Z%c] + -7N7.lpl686ijcQ!Mdn*J2iaF'k!+o;)'1nU@lZ9iP8r7V=F]FXG$8r*\TVVRAspAMSE@AM + b%`$omXLjB)BOU,kN8@sUn#"cQ@GEn7\?Rs>*c+-C3m=hX6Si-SZWrYp:u1Dq\HhXCg/X7Z + u'#-rc&hQDVqp@)kQ0&RG2WIEGmqL_nL&DC&W)@*#A^nnD#YuAoT48'QA')H4kjN>`'WGU0 + OuNMXsBe4H:[lqErW*#r0)XFNJ/lR&6EU=I1C@H?7`:M?dggW2_>8go4T,[]:mkqP^Ns4s, + L;/:KY!jK,CXR'nTiEolMfX-2taYJ.UC_6?,\[I#\tq\AV+*ucKI14_DObZFiF4q.S3(tpV + ^jiKd>Ia,@kgAan)ptiKX"QVf]Nr[I!"ZapLkaa[IF^S)'40Uj8*YH+^n;5> + Z1imkjq&W>Rg>)Q-!+cUE/R"5"9<1_K.(+DJm^c!Kg:TMlK0'/6/F95pl,b,GS't\/pgQf2 + cHKi@#>LH"FtkQ4lEg_Japr:"O'>9US(m!Y[[GQF>$;/JR(_Z8/Hk[5DuGH6AD/>'qrFC&H + Tik!,0[Se+/$J:u+I:Gik`bA9X*]jZ+bLjo?W:KobdC5Y.:T,31kGO]"508GOPWA>Gf!6]$'_MCK!5Fa.]$VVQ*RPo/`lW&WQA2?D^9bp@Ck+aSubf,sU[W+3neD"XmIA0I@N6p9hj_7FUF>W-Q + esahMCBK^%:BnD`-_k1Cc)n@]an%^N1NdRkrOPf0o\LuB")k=G^UT%*6JO^3EC5H$39M/Ia + ?:8YFBQgbQS(_^eJt?EEf^o(7-GX9V^6GTQFaA6lUg-BFp//EdhcohS_Z.PY4SYT(OYaFm6 + #W3+L8YSePP+Lc?aUQ]00'ip*s;&0XWm[^*g_l%#L$#L.ob(R0I8SW%8(g-om#:]Ih(l\:8 + GThH#hfE"3!RJ4C=;*;\atcEms?n3R@#_8%Y@fP:iQ-j'H7$!,EkE>p+38O;t;IN]m4KP1M + gag=PWK8kOI^_l\K&@<(u>"t2=I1d'aO=A/1NXk#M,(kshAeHX`ZjLnM#LLk0N9D)[?pJ*+ + T`HL&(r6kP":TiH,Ae,_?akjh?3t$:E=CK5$=OWq$rKT9d3M6s+cn)T4.)jSXjfqik!@&2] + kc,\3fnQGa^.WhHLF;i]iVf=?Mii3Dl)s;f?X8nd&7Ml`.PAr'RL;obqN]m"i-:i?H]m-K( + )eM_)OX`@1400A^1OJGZt8$R>Wh44N3_IAn=[cTZFAb,@rZ0'?t]/MXaZsS!^p.:"dFCk8` + NPbr6V;QD9K'AFIRKp3>X9lI]a^qO\WHJE3-rh&G/RDq#tb:u%m6W-o9#c;KTuMDdjXj-Co + (FH*i_5tJ=*-`%`Ao;1ASJ[BFjB?'40fRoaI*\e:X"f]W#'Tuh;3eYS>S_0Mi4;D; + _-su0')TZ)KR`^0@C(g8Gltmbg`+=d_@)$I&/Kn712+;MJ\o*]fW-;o0(SF7A$pr7F6bC6Y + L5";]`r;WXjETHQ==+WjPmUQ"sqSE'X;[#WN@rUb.J?*$3+%V\0*,=HDL5>P1^3ePIOVNh' + W671b=iiWqKpi09MC*k:'ujU;9j/jGF#+L,m7`n:6HbE.#^$\9]IrWCl65q%Bq6""=4;%c( + t:oKKZ?54Wn(',c9i8>'[YR)Fc"[GO?,Lh`j^?'poM^XmJYB!>;&.Ln)#iVKli>,K+3"^*b + #k0I)WB(E?1^HYWhiHDYTiMaJjShM%#U!daA7Q1rq#AGUg&W<,5f=!P3!-lBOC:6^^sW_N^ + 'X3]:[V?>W6W%NA'i+tU!XS;#?H_dKbXt4GEGBSF9$Ir + gc"^5KXP@sbi-):Zo4i(jmlJcJ6DqtRcgO?qU=Su*;UDNWonTh+kIAosV],^PdKdqgXX;J5 + .g@D.^Yh$V+A)aO1^1g\#68!3n^[j$@Kca"i'9?3^`YcO*?6AU#&k>!@>ML5bh*HSfP;\E% + MB'jF9XJj4m:l0i)g,T`!O52&)bY=Tf*F3c4u5P(82o_+aOdZr"hXK%uX$ilR3FNMN&aa%A + 9j1/,(&tU>F;8!YE4;Z9f(`)^.HH"2HSlOAHQ4kR8VT&qKZnTh#bG%MCOY)1b6h!H8ZZpF" + n:"VPt[M_-*F9*Z#*"[p)dJ:su/EnPu%#68'A+O1>@>RjXo&'seY0E`"sW"hT::8&(2i><& + u((K35N.?C-MjOi:KEgKD#k4";DDM[=;[KD7$1eY@OJIf=_(h,s1/_gAF!EH(0/D!41:&l) + 5s>h;e@C*O#(U1oY+Y%+oaUE^2'n:ndp!<'>r?\52,NfRP>*s\V%=4ClELW[bY[U;'^KR<& + [&k\YorCQ,0)D`SPtipOYIL5hT/PNmI<1(;l[qJQTG]DTQ@Q`Y4A8@:@G2&EG?d^MB_P0>4 + C"i(/\W4;/FI]DXm7=nhGBcO"D0qJFa+[VXHJnOc&bXNu9T:)0?*$Y>0BUUY*?.]sWq7VS0u!ApF$gWs99IF#+j_$9RiiD3Z&9VFC[t/lXpDgc6W@+k11rC2;A12F%+JE + LK@U*[=Y7Pu.o#agP;#7QZk^D$i]n/H[#-Z4=p0@"?'X^T@\P(Gi9GGKC92OX6Lbmrn*MKPZVDU:'9/a$-2H*I2FQlQMk>)fX:GhM&[,Xmmr + AP;j2jFV,E=4_NZT'ak`Ql(mN+G!a*pdko3DRs^d4E?N1HKM6aKqu9C%FD@6>e?D4,fj$;o + 8!9#8K&<'m%\9O^?522\n%ZbV'nNd;[>&DXqdk51+]$oCAcBpD@X`5O@o*0p'_]KinK.cr: + H?%ojNI(Kq>S0XMo@&[r^d.3$+'p?aG&e[ltiEufh_(SO#7iLCXbJGeeg7#N2LKHM!Pi3RR + >?:\Eld'sT/_?r0`+[?OD!JhD_#!oV>N:9m0Lqd:rB,AU9=3G5/H"WJY:%NE:p46&+KObjT;&'gZdtpr.Mc(4Im + $=2?<@Ks$/FA]WC9smT'8=Pd%P6D%Fbg1/M1?KYTaD + 8Eb?Xd%F-K+7e^XP=+Xms:"F,Q@pJZBk((lP3f.XmrK_CEL?kK's7@.VCf5"nsmrc.OMklF + ;!Q5bI/k)+WN:RJ=GDLl1NJcgZGpS#f3:hH@qiDZpOBDICR0('=NnG].Me)M^J///Ol5Rb# + IHdANX?V,7dutToI$lj>dq=k)0+YPFBD:ALR4!G\&Q(LQBfGF.fCaV?Kk\$nC)?nb7^mnJ2 + ,cIAmW]=oogP4t8V*2DJ^_Yk&QtLR-TtI:<]8JDG1hL<90PjEDD?E%pa,'fQdfh:(o8\\kM26T6+0<-eo:6g'`'X(1A6oq!Fecf&>O,)$)u6Un*I#]goU + =&tnY$?j>E?5"h(<#3"b_DK8KSjGUP41E&AX)"E^?p/8F1W*'A(H5W[8Ml@VO1DS_S,k_FG + Sl27a':4-1GMSFbV3Dn)PO(5l]":a![sT+0_jNr+fh6bIcVX-BI_+-[qV.:G?YO=n3E),kf + E%;%T'`pAC=P>JM>Mhp[?pPV45#==\/uGu)hM#9Jp1@^[dRPB#=f&igXnJG'rj::4kON1'*%A@0<77ot=iq!eE`N5hJmoE/E:k1HKbY]A9FI25gZjWB:^- + S/9#rWPSKO_@BKsAL\3Q0&45F:LL1/TTQ>25E_JYXH_PB>YD.\:TI_t`8":mHEY7jdAjiFP + k0C,PnfR3Dlu3C`d)%[\,>VSn#k-7Y?2*Gr/5>J5fm44CUH`bYu-mX%tZkXJs\!l[:\rSSN + [pbK:/QR)6&8W.rMo&==8C#Q%u^!]_$P;cs'&fHV?'%AXrSF6.Zp.(E8-6B=9hZ[\9\HI/C + A8lnr4[(;W)-JB[umCQZCE^F887i#E6"^'XnP(-sNWTj=>'S[k(K\i2jIGFTY(C_HgB$p^Y + b4Vg\E-A,]@'Yt+cqIuh^,ZKj4"5kp9\>TM&/cp(?;B+PBHf]Q^jqpBP%UcdQ[J0a4p9O`* + "B:[eS,e9@"M[&);&g%+]Fn#1,MO1cefD*OIC7h"+q?lH$&'iH*QeV8c;.gY?\-iJ_G/$5h + o5jd`S/GQS6+S&5D&Nk&uM7&W(Fd;N_C"buNg7IY!T)q_o)r&,KpE+VQT6^04No;*5M1kR)s,%oUNY4R>;P>-C5k1s>qq`@l`* + MQFhrAJ"cbm9Ag[bI[O+o>2A+kH*/2!J"@B8OXoD%nWs*.;gp!/IOY9OQ'` + PZj#Mk_r]+e(!8qG'i(ura((gXeZCgWrCbd"CLb`DD7f6FnWjAG&%RJu$ + +b,$l(>8gEoDTnu=p>Zp*31J3+PIBKaD*X`??gYE.)N)G.dbAlDD)$`5W4&^K=8Q]a=00t' + W-qrl&Peg8tgBD7+ckCg:k%/#CX?EkJgKGLu^tH%3Wf0N_R)=a'h`@^.o*E + pFI%CLe?o9VUYJe]_X33P=)+1SAJr[#b/,R;]%S2)RG8qb:q2f^(M#-hBoCe5rK1kX13B\5 + +7WL#Gji49tTKine\l!QV(ciet/.%/Ai+,!06BPqP%gRB/:W'IP3?gh21l!fo:fI$%4]56:/Or`>2+ + :SFe[8C_<`SgX@p2nJ[:`^p[Dc$EO[GZ:jpm[hs'ctV0Ekuh!!$+8"K2(@CK^@jTkR].3Nm + )M(;7%l>B]\IE*Kd\^N!Mf--pEI(4F\Rh3W99G[7e%R?hrEkD(4WT'.'kK;*%*2MIhC3UW,PBh'58W#tkbV5G4MQE5)l)V2*pCFAqrB + C#F)_6Igg-I-7>qanc7mAKct[-el^ib%TTWPc@:41=j%R@A['E,Zd0$,@\]1(D/F."<"hEJ + U&6acBIO1.=1K'Z)7t#[lR76Z + ?6=@9=b-i-Yt7i%P?>fZ9WN0=.ti_U&>?a#L-VUN!Z3m%TYqOs!)*Y!(`WVA=:6MWF:3XZ7gC0 + L0q9C,*.msq&n1-J$\:)\VDO[-nneRZJPJ"T.#AUGn2#hB[M&_@6jFjq(BRuH#f,f1!qGI] + !e`IOanPbi+m[M_^T6Ns>$^6p[D@n + de-!+1uPJ$5kf:MH@`VUYmD/ZN#P^S*PQ + CQ6j?!V[h^fE=*m*7E7C)0]ZK4)KMuhb;k_Nk1o9qZfKZb\i3=J>rb%q:9^FV2dYjQEDLc' + g"b=@E:6E\[js*0iOffKnQmHYGjd>u]XrpibL=nM%n+9>\#Nlne(B"Z4mn_N_YFW5ehfqi3]@8ZkR/\'`^A-Rnja;cIn@mpPr84dY1^/m;^\@U*?n + n<*0CB%[Vjdbsr"fpKF@!bG4sV35P7nJ((5bsR%%o#J($Mb4`#*t9;/AUV0chX[(nVOPCfu + X8DC`]Y`u&;K?]B&k2TD-A*FkUbN\U5ha8#:,%Ma6 + ;%EfHsTi:m"QLa]9bIN6YjrDL1o(>Jq=^%Z`I+H9:I8d.+^8KMMXdsO./$%@cY-Zj!c`8'u + cl)jJ[*8n\lZI*J3[VDFb3N@jG)`]-]1BCo.:BNjKWoVD_q9;PVd(7'EAf_r=Z76eCo``L( + 0ll&]iaER7A+__^=$dU6YZ5$VR91(f+`FAY=b+q[Z9Y+gMMc8Xld[5`NeG=qh_XcD9'1_5e + Yt/WH<$+cSA^9n%L0mFnY4VI5c0?DEGl9hh1U7S#5f6gXEcQAad=ads5:^omSj.p"m1dI:+ + )kJbSjE_)O4Vi:r+SGW$7!\6^NOZg/DZl3.?HNt6K9%eN:D?Sa*?bhj*ZWCE\9Z + !q)9V]*0ZY.ITO[;B.rA`W=jlSTHh"U9V_dRSH3VQZBd`W)e0`3B&(q%ZVFZ"\Vm:r?C.(k + DboVg8#,`@Mjlc1D9c;)X_/($Sa*sscIH\nZ]+P&Zg0^"ZR],>*i1_]/-JRf8m8L^e8@EY:Y4;[P + $Rn]>$m=7?*[_tSX + &l2:Rjo)Bf`r;V9>s*s)/Z*>C(q)oDM?iW"cLKjtm_jgh9#-rb;K,?2."0954X#`WV\soWZ + F/_FS%`%%E1IMe[_kKE'&h6h]]&XPdbHEu9(95884!Qo#1Hn+"5eBu7X%XM?41MXb7D#$X] + _b%]g\AC$o[)9Yq;o#J5_6iN*4>Fad6W+nN<33FG[;*u,ZHkCe0!-*HM5sjnb1):me\ + (+?]^j]%eY8cOU+oOSsp-)^B[tEkQUAHm]kc_V`70KX[-KE])t/W*#Lb") + S8BW4'T,)C`U-8K"Ih2Zt(?$;nT0k]rV&U_Ghe^$mmh'-G1R"2$>ALe&F_[bXh@X4FiY9j= + `Wk0K#@T8OLb)A2D,%c\d?_6ZZ\L.q61Ze&/M'/d^UA'5&A.5.un>aoUhH$emSemmJAb4mM + AU6f5'$EPunf7"Et/PN5--Wl2PC+7)]oe5"0HI(ltk`?Tjl'X>.nZGfHD\3ksC<*)_&Ldd5 + YU;UTcP[mF_AP9D@bIkF0`4a@D^<=a8bN-H3oYAZr_6M@5L*0?D\DslFA'B%/9RqVEbfRYF + 0gLtAbe]:Z%*chLLrV'ma)#on'[TPod*3f1a*cC\jEXGCHHg9Wa2Acab,`SDIE2!7_6i"=$ + FDW@K#f+8_=WC2PT\6q]XuoiaEtZmPJA_;MU$4Y_Mm^"e&9,)N\scecGYje)d#kqb<2cO8O + Cln,VqRDQUtrE6Yn^-<&m`_hT;i[8\8QO`1>5I]JbG84e/1(/).uTkt3e.6o:`'oVK]I!j: + nmb(Tu-3qC4jX%L:l2iDq!gZV8ql,s3+72tQs;sm:7#tmCf^B4kRHrmQ4o0nn]9GJ8c/JV, + O("%.>d=i?5Po%i=(t#0!dBstAXCaqBOoaYdb`*a"FWn&@"+W?5#8h'2N7Z+FY"`1Lbj9RK + 4O[3(d_QqP9iWUd9d\NZrH&%'S\q(^!96c: + /sE`gknF>:VqdD2&tk^UasOObK0mK6KIQ+/FQ.en@Zu18RsZq>aMl.9#HUV4Zo2l9_lf[4Y + A:W]oqHbXSkrUU-ous6p1QCA49+;iAW=l8t#QQFU1UQ#tiru7.dWN$gPnCJAj)M\0P'.S@- + *@&^,#(`K[m[eJqGgB%CO+em2t?F_Om1C"B\,f#;B)eSE/9Gm4gVJ>`a(F!q3GEmbDSQ$.an!6dUW[4eLXduH.TGTf>W'P2(mL?I`J8:fEEs-/DEIL);$e%c-io=*B-^ji7N,n + e!5a73O_r!^;u9s6=Wbhm-)q%.Tl:@<$KK+o>afm/\I=2*;r?%og^mI84,h4cTOk5%/%:Rp + 9le=<7F"4"4bokjjaL+St&"dX\ujNs0g\<G\sde\'(u6%_]YZ@E + \F&:WC\FA`Of#!445*Q\%Fb[spLX$OD86q'CD:tu^Le_66['`(g[-(BdhUqbk<#dDf.a6Qra4*c(_Y'0c4RkcY + 1;)@IcSNpD9hO*@9A-uP]hJ4CbWO#.qpc&8YqJJK-g#h?@-G#%2TXi._HoNX+L.$K.ch\F + =Nm\4j[_-jBDg6!b5E=WHeX7?c5$Pg9-Qfje8nFRlP + NR&tf$ac&2NS'kW[hG9-%c^gZRV4Ym&nAGJ/g+DmL7J"GdoUfF^NE,\jKim'"WmH,ehlKRV + eZE3'Y1;oHht0lEo7S;g!?uC_>>+P74q(i-$0:eTjT\Ik(/O$5:us$&jYg*".8'a;-3h_Zk!_J.>hiJ6.Ef8E%CK@Jf[ubJfW@;B]o:V"hH8he@^[c3Zn+cI/QjpthF-ekr%Qu@@$/`0[e]pX + g-*L/$YXL^[?k'F&ZrZ8-9j:q'AR:aV@d9hklF*eLL8eqMB'+Mmh'Qga7VZ]cCLS;CB%2KC7W.3dE + +3F!CP=psDK=$VNoHnm[3g%Uc?@;=j'q.q[?Qkb#(#I7QX5D&Cdrk[VLMRaS7iA&4^ChH/i + mZ7Tjtfjcgu!A/j;m-=6.T7d5I + P0mbR9]hWaakmH3>Dp>G?ciU/R:#N0.s5T'Sf&E,;cJe"RDO^\PBl@@8*@Ki8S3BBHJ)e'`a6>PSfTiWNe50ur)FNU6iP:1e("Wm8V(9)3g1@G^SD_Es94;Vp9d2BBt>rMFm-.KcZZgX + 5Bc3F,S0"'N#3o-jNCQ0e+(AII`Krb[Ahm?RbB + p.OeZ%I%t;nqSQ8="!]KAG0YKp313DDe\p!Br'r`PLouCJ26i]/U$W1pAbj#'.Ked_TJ-pWK"G^?&HU*(7s0_,*-j&X=h'YA1-jLN;/-MGa.O[mGdbe0(b2.[BBet[.7kL1 + AM-n8WT$1G>I7A:#glWGui'k#FB8A<8'4)H(Cc$50nMoMXb<+8uJ9J2$ilpO7h,!72HqX0; + Nap@ui2'G7/GKK11EW(\fa%>[@q9c,-QaVKanj"\%@C-*D + r86QVY)idQ=P"K95W>g\cs-HD*o,4@LD).V@RF;Jtl?PUd3+m=\r=QokU=(I^RUn+B8nLq8 + /fKY"X&jf$X#3p`B"Hp(fPB&""9#sVZW\pRRq8bXag54kpFE$;]l:U>R2;paHM;m&hq7[>) + g;[FJ)RXl<]27I1IBs + _:7[k4`=pN6FOYW*UT%hFZ=Cp3URY"pYKf]@38lgCBFZ./+4Q`p+tDnFfnGF+lH)dgeOnit + jO^K7bnmJ&Q*?E^!Ekl5Dqs$)4`oDE@RR@k*.k<6m\X%':McT;\577@K'rZZ%QI$Q;N$EBZBSarb*'%gj:)4dKuRK7rfl1 + ,+n"FM'Y_>I%)LOnoUWPQ,i]k.a%=hYq-Wq[(9mCo*PD.Q'3321/-.gRT7RQACc6uP@W]5' + iUppAko\Nml\p[h#0B4'C>ldcr;S'6dTpL%7O,.>ocD_'P/R\dWCHt9@D>7.o"J,jY]f_'` + CHr<0[nc;:O,j;,u-=D,jY;Q$m4Ges1r+=k<8JEF%0rm;lXdQ3D\EfTmP;@9F?tP@N9HD4, + %iG)`&,=r*[2C>0N#ZrIm[OR`DP5R7C1gm9V;F52@1e:6;@D;BEm(QIO%hNq[.H\5DEd='o + &3",kVA[,']S>[J:Jmd5+&V]0S"Q1D*f4NTl6Dp6LouDhkXc+IUSIZX=/D`3@TfV*BrZ3Cl<:3"T + 'H^fZD/g_)O7M*])nS$WbTDYZg>ioaK[;.C:^l+>Dh1G/aJG`\aIYdoY9jR%fC6!Hik2$Z! + 8drag)mR,gJ1cCH'9j/H7$;[#IFHeoM:-1t=JggMYbf%BFa2oR/6;j>`]'NoE'iCRjX4%`` + ,GG%SY$gH6M"^@OKNkBunbrX7Q@^+Z[Kr'+!,"6I;s@B0RJ?AoO'6/JfEl9cW:0C?YRpUq- + VE?&!K`=Z'QBgt<\BD':W\)$cVZ3TJ5rfT[1,KZS%V>\OLPc?Ofg00"uJM08j#lt]-#u'h? + 2Sdi!S(!h\>:X"d6!,Q>i1([:ctWtT\31(*"<<(kIM'F68I=?4.>4q.'LKl@$Xn]AL/>H"6 + PsSIiNtV#&FLL96+SU8Qk9A7/Ug)fbQB%Shhb"W-:HLV]`kK7!&74n78NshJC#,R6U]Q_-= + 7SKoO2?OJVUP><>jI`BoLH>1bsm-(o&?rNXS4C1RRPM=fXfK9`@44&gkuH_aS8K)5RrMRj$ + L=Lh\M>7Q!1G9cD,;P#=`OPZ]KgjMPGC9pDUl\NJRkqeVKkXKf5=k:,LKGa1_J]bc:ihb40 + XQ3IW33("89S5d%=/g\Lsq0Zg[RA+`N%F)GI>iqRBT6+![h`>C@DI#W1RR]8N3p0mm?*)^@ + 7m-:QWOhQSlbIb+#l("SNdZtnq`n)^k<.f2oA5\XHK[tH]iVRqT&Lia:ONj"\bu7NqCY6:% + J;pgF[;0(omUi4,>cmQP2G7tU/XS^V4&s%+>RDW&2FD9UtnPiJoXU_nf:*O91e@o.AYInPZ + #(u950)2VGt9tdi)ppl*WOK[-jG_b&pKp"[AqbqNR%/e + $8L\E&5";q_.+YIe>a!?d`^uX=-RAqYCsnG[a*jjmFuBjGA@r3L>0#$]e_#\9df + W%kUOeimAm:ssP,e>,uDo;WJO: + I/qX8JEbjW('-5*8e_M1Mm"6bt`("^p+'KCE1Z_:VBd@6?,qE5iU0=C2NrS2lAooJ.N+/6% + =rO:NhJ<=,%oIq%-.Mt)Dj*k9E.@pDqEK%Np,A,8Dk)W?b;X_OuK%j>Iefo`QdA$,P[+'2# + 1?*\WNB6E5a=4mnFj;ibbfl"T-4M>0,V2(4Erl#Rc2.L\f:6UB0:UZZYAZVS7ZP'"W=krCW + gYJRL;T@-XD#J*[eZ+u0\mO1uK]R;Da3dXoAD;s+DWZ,)RMQ3$*-Y\-VS1$>=!m'(l^iDM" + k&pE(@#1>eN7Zk!P>O0#$pV%Gc/I86jS6j^*h++haQjZPSX#Ml4^AYQ1UcrlMT%&pI4Q4); + .u-!,UEs"(7*5_n/\UD+m=)[`6kP>@277S[l6'p>Oc/j"o',@=C]J67jd_1JWI*,$-<#t,fT\L_>&\.arF3.jXT^A36#(CeaS] + '*4a+H7UNBiN_!92`_^3X?HjIma(EQKA@$/lZFnI[k<<_G03sL2pH@bM-kM1,O-bF>GkU)uG1Ch-nUX(DBMch)VC(hOs4*VbqV?cLLQkWOk*,GeGWBX + .I9*mTRp,4r_g542NW%1_KFsg(A_@"nSFP5Jg8\Mgk-GaJ.;c"/X^5j3p0oU_$%bL\(>dAbHXCU$7&RTgnJ"P#0_<8.VEN + p$)i+>uM=LQeanXaD-N"sVn32MTY?YM1ENcLb1=t4?*0W()c:s\:9oI6NZS]e743R5>0]ne + ?;KI6L>C"biQ+ZuWkMCi*S1d;1>pH,g$Y%KKB'IKRR-bg$0F[6K,jQ!U\5'Iml\`4^2EM.] + e#Se`l^[=@`k#rc53^RB:=QmD$It0jT!1aOX^MA#lFcM#]d-F%le'g+I%^f)ThoFlC[1Dh, + 6ok5U9".DC]#]EW5mo9Y?OU/1V#k4/e0UW7:SI.'?$?2[GOJN;gYYKaQ&O>IYA\ + e%Oc4:K)p@-04TYCp)^g+js!!,1oT`k3FT&S,X+`EJlOGWA%M8EX/?8PbWoK3Eb0R + HTnI4J3]&Z^&r"$O>8M9qEaD?p_r@o`!O4LV?h%MI$Q&P3tn*&r;JH$/Ls)*_%D$^0B?C3@ + 3!%slYULL\$%+5[uFR:RCn/.V>\H38S!_Kog$,5b?^q((4e8R>KE'"0dh#o<1eZ_Z;cK>?@ + klje#mj\2=meI&]HLM=%fL1gN/+2oS_$>s_nZ[DobQ<@A-GZjf*H8>*OKT\ph&\<**c^-[O + l[XhnK.5n*msb[JSl)coO$M_%40.&Z4%h<&IbrZp8hjITOT/9#[EJ(!'sEo_%dKbpC;3u,B + >XdZ83`r33I6#,]5OdYHG(T[h/+.-*\+0Y"94c:Accbjfoql%+c,m,0hnoc:mOOYMca-JGs + G2Zr'*OnO1^n8C141s,N>P8Vn2/"Ldi.dI*m@Y,`>cC=;Wl!RZ)#n0]$mFVkV/!tg@Ul`TV + (f`UZq"@o?VnXB[Go*$^Zma,(L&7#JI#4KsG#-kW+EP1f2_V"Ct*0UZudE`(-Sq7R)+0kP^ + 1Dqq=*ATDi134ToP9huj0f&>X1=J47iIDS-!!,=r!:h==?p-@*eMT)u2/d@PF'Jg2@5OVX, + %3kP+Uo\QE?iqR,SP;ZZZsV*8Iu11,kHih&m%'\57pdubL0f2N6:6[BbDuI&oM1W7b&.BFV + ?NXcLQL(&qq`t^b;eckYXgr1X.@eq^J`Nl*?u_Nh[@FXZeMERW>dFEk)ubn92+P!jh\R:bN + Z'bg`jW"4GX`X]6687i@KG"OWhZn5@iBVUlIL)XVeS/)`.='cW6Q*"[4d;[3Q6$@oGo^erJ + bNMSG;2`!9f7Q=m>i=6BZW$9]?2fu#Xe>l2[V)U5%33l6iE`Gd&$BZ!dqpUrFZ=;%@AL=e7&l)^[&(;n)Y + `uK'4)m/s0fp'>M#Ph_k]=u_YPAn&;ZH8g.I[R=ZDB%4%mDG55&kF'URKXfo'=d!!gEQ31] + ]iSbag=ZeJ&BCEL-#&h_#@I]W92IZ)JY(X[9:sIF5GUOh!.;356j@6h/>>PX_Ft9/08"7eC + Id'mj$TGuuqP=B0oSFcBM2O][m5VL`+eT^#n"$T:6T>%)VY[#8,Y()Ad8>$qRn:t?39n.!m + B>]YR3FKo*CY;\;G>a2]je_Nm)O\@Ql9QfR'Z^S-SANCOW3],QSZ`Q9pY>,RC:2f!,Zai0I + mjPG*:JCbRnrl3/!77uN:emETd\-ZK&2t==(6B=_0"!3B@k4UB;KBSj1]8k[K2X?r5a=>I1 + >&0t(=i$qel>9oOB5ELoi3h#B6@BK<&D>2Q:HbXW;sZ&0TO(Q)?N/a:d(C>8G=C:Q-Dq0l#/ + KIDR?rm[GKrN_-T&(?F7g\eBL#(o/]nF.MJcZ('-J)"XeI$@$V;c;nl*97[CC&.2;Y.<[0a + c-NFkf.P@?'%]"2DB0K1.A'o3Yn/h]\[^f3_\[G3snVL0Y"W6k@/Q<<6Q=?Gn7(b>u/s^Jq + X`ZsrG45&XB@(L/%>e+B4YTaQnV"k^kS"5%FRka&C:Ca#GJ.cN]6kF0HoK/pGKkM:cr04"' + d3Pc^oM<1e.@u.Cq&Y"[_rg1iG8LW#U"@56*;rNrE]d:DR^!k=:9%l%<)"VJ6)\Vq0&#Q4EaPkG#^*e>4$N[eTZL/Pt+-fUFpX50!luEde-V,QS59*H-sA + oVu:p;'VcG%0[\-H(6^Isc[<4NNB#UNGhn?]iJkA-NVMUqX;i<*NYK:C$%RMB7EZ*)nUA"P + >6,V'\GTT^O)^0f&\-%lg(QoUQP'J@:\KW7X*KrDbO`q1sYnr#!cK,WYK,?IQQPj/HeE[$,nV"DF@@&oKQre@Ys'et3@ZafJ95TEK&Toc@;2^'(d;sI(7>M[-!nAlGo# + j9u22Mp,5cN^RmbLhXAVp+pG])u-CXhftXa)`Un2HbnEP%.;K8^HKHB^j3prs(G[>p[:Nd9 + lLg$>5Tm@QE/T6]mt\b&[Kaqiqk#@Lj@q$pY;gS$V4Tm"Qk=ri)KA-'I_!F]@LJKL&C0;BF + CP1hji0FHm<&(JgDrYB=pNLNO[T2dqL3H\aGpR>JnNbG0mfFbqt;M0Z9B[t/.(ZS#9qS'YAaq + 863OB:P?rfQWmE`1^4Oa,F0RfpAh4(t"tDs0"KRYBg)MReihN:rQ_W;(UUs*:)F:b,7pCWX9=VsZ@6+%\"[ + 6-st]LBd-\$_`+UtJ&AqGX5:=14G9Pq+5IG9rNcB6(V4.!pFlfMSQ-o=/ELF8%nt;qIAf9= + ^LP\^_Lq97N>\?"?YgR81SSDTPfPT,?*;]CZWH1_Z>s^RJ;YME8ek3m4$+n@cE[7^dWq2k' + 6HdCE1'iDH"hPXVRN)rF/(-Hq(nE& + VSLq*s('-Z5&jZX1iVFsm9uNQ!e3-CQGr_BPHNQ@9NF=E'5]9IB!?XkjlLARUbfo)htO31A6]%0NsgU2jL]0LYVdW+-@+ + kZi*W`r.!I?.0"GtQ5+gODAia>F=],4e'E!AR4)I[k`]h?j]60%r5?Li^VZ1FsW&(RNuB=Cqkk11-r>^JS=HfpiEfGS\-&,jT$8*I+L;# + ZU1HB@FJS[pWC;AkV^&US\,X/_\n]5:+V]=lsa3E+_Jg^`B1q7"$1DsV_L0,UnZr7bl^iSF + :DhD@O$GfegPcd,qrrdObD^@hK_l2L_Us$-qfS9qe&E(RghtZPu6RA"/jDsYsc>PG.c"F?Lq::E&Usj#M)I6++PG + Qj>p%obNrOo>em5E1JVWke,her3.h?tE>`,>+U&,ndDP20Vg\btlN:SlKW0jjm.cXXB=mW\ + OZ:CiGqipsk=IP4KjL[=anrP&km`J*OP[_Dd.s5WkJ@)$XU[/e!]":At4#Q,)8_%9Qb"rHc + &+DenkU&Jjp&A0Q$6VuaH%E$QWHOE52]L`bYnLRLC78[Ks'ueR/XBhIgP\D#TZ,CNc7ZlB+ + *lhloJKPaVq5Y]1dO^G*8>X7lJ!uO:TdRu+\/0eI3D52Km + b[M)>)068eokOrb#1F*CWk*Kl'7XRFPSHJ!m-E?WbkoQ"jorFa\76f-gB^>T&5qHXPJA'oQ + O'p)T?,=]RtXB3DZsR;#gtSYKq'p9h5AgNLQHD-OIQ^N!K0-,XcYG;[ik?$AHdF^kk&-pn$` + %64dbGd%]VPC0VHFC/]CO3S1\1Pma_m!rmWpd91XlgY#=\/U(g6K@N00D^7WLsZ@o,jYO`iYpcq4R/n=L;e"Ip.&[+Ag3HY\c74\+ + 0*fUD_jOAZgt2KJ=3A4(3rNN,IVk1JGt>B4V`c+BB5od3o'HZ*d.^`'aKi#F#;E5pU1)i#: + \>^WPl-Pagp\R=NZX.#>lD!N,I5qWZ-UHJ,?diQ&MHtF%jQt't/b\(10SV+;;Nm;8ZpN2$7 + PIX#_q$Ob;0_&c'+[g5qD`kBto9"^dH3iAK4ZUfJ&c:0N:N[tspU^-,)s3`+D=tW0f_aQ@@ + n[ET1quH4rb)pb:@IHH^UOLK<%J!!W\ssY3["Qh]b>uSK6:hf;aW"fp\Z>ibm@^n)p^k+ + Mpn,NruOPd(#U8q:?YuY#Sri:WWY5l/M<$QW+!oR$G#CtSk`hE'de[@`okl4m[m"B2<)S0! + jf-8s"BT`ul^I[T0,ch]HY``2-8Z=^Z,h/EjP(g]o7h0@*<2c(*lVCeik'-:^gpK[*&Pg#RKk%W(QHSs0gfm&hU&3P`13%"R^H1r(p:5b5,- + X5,J'=N-jq;4<,/G5VO7tJ++1B^mG?.i&hI#E(15Npbr@alktHFQ4_!$6OR9*^!g#ls0+XAQJ/ic,-Z8)>bs=LT)Ur*Vu`jtd9&f+QU&)?TtABFBAjAd_I1>cW<@aA#^ + J='UlMnl9t=E[rKgqYOYUN0/!5:Ff"AZ!g=CMN`=)ce-o8Qu$>qPa5%^fm>;$-$,9V;T`-F + q.lL;%A^@+L!jUZJSM&/HZ_6XAEW\oD!$$J[e^co>Fc>omG5LGG5^I6XW_\^(6c9bVKCV(f + (O48n'dK>-VmC5Ff2VBT:2$#?+8scBFWjK1`#XYHd+lj:$J@u/r7;p,0m?tI8&2r+!B`QLh-lE.82=n-!sdl`'s+('9%6+G72h)W7>W9(eCXP@hs + ?6]5qbhj&!uLe!juqI@_b`XE@RO + ekCpecu^'kd.Z#G9jWp8ZsCGoY89^a5+qL(Q,S'<=m>1+Xeq]Gd%/9=nS0(14o5jr]/lE\: + ]4:o4&t7in+qb3p]7KM7I-[?>5'K6nH<6k]n4D$qbc\NYdQ,/fc,9UD@[39Hq0+ar\qf@mh + t0`Q]^j16d'a>0BIT"2')i":^QXj)M.*,"Zu:QJ=fX^rDkEjHdgSms'ZcM5I1_]e,]Aq9cL + kc/-qp@)i:7$+%K_d66&(AE;u!iIQ%l7R6QXA4tlOfc<'%/C[6!#/]]^@>i_<1`u7-9A5/Y + -EI>)L38Rr1XQ6SIRE4XU?>at(5<7%JUHWb>=#)f?Yg>0bU,k(t&8S)D_JD&>""j[HXqRce + 49cC%WjJdL5`;a*l`M<"krT7;3u[Cr/.OO(m]Q\C_#ZYO'=P(p=VNBs.!\6CU'A00*^bs4cD*_*&8DiA&CBOJWqcA/)>^e*.a1f";!SG87_sl;?uF-4u>Dd6:8 + &+5<,.q5*S>sN']'7l6Mkc39j,Mg[.CEm_jJA3BQaq'Cn.fo;oheXNR!G>PCPNcbG(Z`=&3 + T)UuO7;a8b15NAIp);@Cb=#*K(67,\3Wbk[C]YNbSS:`::o8pTX/#lUAMd]rVBG + =$\#a+SZWN-\!aHUjLNS!*.h99J`oE&W&9:m;hW?QOFu]6"I;VaQOD#'^3i7=PD[:7NPnc]Ur5 + a5%%[mg83Z[=7%<\V99dB,o0gJS3ICEZKD)jO:eH*1XZQBim&E:fT>aGg79\B["/:m)=@J^ + U\n^+k]EFg*>r8?`]!Pg0P35:K@6hDL^s$;d]FID1AjGsp`f(ImU_9P'9gti>$Rg7*>"Lnq:*8+(:Z2ggJ!U(LXi$nk%Zis9!"A + !&,Lf"BW@g/fVL9U1n>ae6Cmn'&M\hjH!>d22IDCu@'VYmd3UpWioX=e5S5btkGoO:I + P@n&fNbHqc&]2Zds[D<8c`iHRrGVpN/,A3"LU5j,s"sF3uO-5!&W_e&.\cn('-B,JX@4(8/_MJ'`oL!!OY#ghm%-?WCJPeV.C^h-nkd@9,Nse]hT)-"L%^B3&k_!SA;jKk]+LCK>k!epnc + ASS`s)DUt(fSUB&VHrp8p + =6qkt-%X\PJH;$f=@Ien?%lBtDcf7F=ET@#9o<&DMcc7'=KRD\NIk9j*8'qF=E#6^jcP-@P + 1_FJf^4r,8/qYaP?E$lfd3%h?'8?ERot00=cJs)]p@@-P6$*(g%Crj*LhVnTE3$,g.e6o+< + juHVV23O>'6WE?)RdHVV8GVg4cY4]rhCLXBPS*gBFcb%C01T)d9YUgF][75Seqo)d@0ggNC + #1S[ht,\D+^.>K+3Yc+[E<^K]7(>OB5\rP;_j_H]7?geG[k\2#^IEC9(b$;X + '>g:FHh99-cc!9Y_>lE&Tp!R+TdbPqm>trgS?.7VHeQm=K?%(C3NS7ZlgKgB)h6#ILcC(:^ + %)JK%:;7SKKsfi>rQ,-pKr/paKS_&R]->UdKs'Pa*?k*3F*Wf_%"(4:q?q + pl5?3XeT#@Mud@#bP$SdJl.$Xg[W@*T3hh@-k@LaPj*[5&G,@Wh^@SS4Y?7',u0nbAnD-WB*GGO#HhcPG%tWlCC';aLS/#:fKnZb2?I7pk72$8*cbmGF([[YB23S^??T + t!G@uAL?bd!2Sl14/HUrC+B?k;UhL9,%SE3:aL$!phL4TLKK)u718C.oZ^3JjZLM2-mBT@p + _Sr.3aMeKh`B-(VZ(_-T/NroQ +q 0 0 829 466 rectclip +% Fallback Image: x=291, y=277, w=245, h=50 res=300dpi size=642675 +[ 0.24 0 0 0.24 291 138.268497 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1025 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J,g]g3$]7K#D=.l:\\HY*=mro@So+\<\5,H7Ug;'iq>KWO:,SqM'SB]-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/m9He`k&B%^k=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&t\ctIN)4XQLiP61#@>."::hljh3G$@,*2Mu,%5Bg=;k4T_eXi0S^6MAHlT"cNAdI3#WIS$0)cgBNcIXb_>k + P+oS^^pnX!PjdJ%0OEX9GF9)ctN@_S?Rq1$,VM(Q79]['IrjInEBTWJrpHX&]Be_9bYJX9N + ;hHO_e;>`Y"/@'7eK/^DiV=bVpQV$?2=RT$e$l^`66#cQg';3tqk?JQPqf#m)f9!B.8*RGALp_X'Yuq);ro#e;3aQb5>tMqTfZ7R[95q"?b> + Qn-BE>&!h<)6bprR5oEo-lhH?5IJ?GMYaP.TKg,`U6N.pW5Hg?PitHJDc+C"bY\8R8cpi=C + rA(iYYfEM.2.p-]8^A_)[F:36jQZj7s0I<>.cT1QrJt7ST',R9ioajI(QVR:_.M1RJh$G"J + 5<=k7Y`BA*i37d+D\0qo=B7[9_\RV&'`BtB%kVNU"C?\Yq`@qg;,C1CAS4MYa%Vb_6[s + 2\J)%2A@C7%m(6sDZL283k">(=cKJ7@Ud"UhDnH#Q^lj@*NnGIhda`<)4 + -ccp2-S*TYqrbE/G24W(RF>^ib>TW's%O>72Tr/e3uK)2P5',%8,EL4Z?lLH'PiU$P"";$98 + l.!ZDX9Lsue'NIQBS/c!S:_=:3.Rb2V']Qc*M\0hY7G+p_ioFL*d=sDG36`;86tM?4Si'!i + fc/sVEK/Ag3(Gsq*Fm$0O&=asa-cZEj7In`Z49,j8Hg8J7#-n&)Fd8c)^=?bOcg>P8'+m[- + )@?'P>Z!f8VoSWjSYR5&rog]=[OY",cHsUVH-\eLJ<@bU_%7dq;!&aO&>' + W2-tYoQl^foP[Q2_D + GI[eAk<%C,mnD=t]RpB5>;qhdFP4inf+TPWDqRr^3)b8V6il4/0=FbfbT)Y[k2h5e)07^2n + 4f7rhP/''Sd($HXhWqHtnau(hpn@eS9pC04MIE*Z-*.pe3hn/c80D5Db,Q)Q>4:?]o0[RV& + -un1h'KChls2#UG#iRls1U*/^**?TfiPCsI`ss303.hfP+dbL7"t.ALUD43M;2^260ZCqWA + In]QNNoWFa$V3nA&5j>Z1,gVj*Mq[9S\a"5"!F:G9u=tr\m;):Wb[b.1bsY0lj^)b5#[#$r + ":"`nk%G,Q)D;4NX"t/n`C\#_.i8fpf)Q8f>t:lLpsg0AVcE#a=fOp + 1YTMn(=R/[n/bQig)Of\lO7B)![J%DfqZgI9?&Pu-QGsh0d!tDqS$P"rd2$6iB^Is`[5P7p + g7)bhmEN!p"c336)iJ\'4j5H9(?cY5X*"d>r\O:lnD3u[=)S!%1Uk*o+6,=]n%JV52t6hbU + 9fDi:'\U[&?FbP/7rjpQn!mk&)o]=M/ciN`V?&CinS=.EP=TVGm>>M>^qn,*_FKL2_!h#YE + X(QmI*G%ZSj35=OfW0;WdIPLKA@;!He+Wc^ + M-e@N\#lTJUWFl'[\qVP`Ih2AL\50_c-7qD!mT=+Bafu(jl_A"4gkK*TBV/oC>G"4b0.U<=:c'8qCAFM4lU\L60_'e*qbrH1=&r + 7/$:Pk'd*[$1$dsZ(SHFL/>H#mHC1QU-@k&d@YhW.1a7j'TS@)c](PrAM5+Ses_!?F\Y,Uc + h[5Al;Sk-;2:u:q_^"%e=*$(lBP>KF_C8FMS3(U8*BgCS+]ldb?@Dsn?'IN]2q5a>O07PYJ + 9M>==XCOn,0Fe"L0WdZ&fC!b:'Yh0`c&C!NS?alThgr,dad&ZqFU)lWCQQ?la2E[Ynh.:dD + LJFra5."KR9ecqq7a;Rid$"fn6*l_Y[6@_1m.]O6GndQ`t$R'O&+d*Q`Z6:C^,pC\c+u1YmE\/Lk0dC]l,HaoaJS\O]V@DIh#s_;i!&c<` + PQ2i4-)&4&mia#%*\-4:#%?13jG#6R+DM+2Ae;c_&0[P]6'1_Qm.AUrSl1sKD + c65r/$F.Il6rBfHD?OMUf,G38kgljNhs&6q(J96MIu]WGqM^DqQ + fCb0=i??WY57-9!9p%b,c-'X)H^no+(aLeM0\OY*0k)o,Rc[pG-[^Y`J__0FIkr(BG + 1/Z4I`OX"pu=2BB"EZh$ktn0nF#0X]A2",gYHbA.6"6T.Sa"RCb"l]hWM<<^R@2iqR]PCC+ + TEsD5j]F]D%Z]Ml)JHugC^6>#m$h$+ao*OoT^aoKn9Fj39*=Aki$W+)9E4lY'3t(Pr`3@2h + XAqD;d)Jo7`c0lGd,`6/k74i_%kNW&m$/*foZ<&m&/4o_0aeP4%*S/Pb`N\Rd2N\f,gI)@c + Oh`Ad5'&?,89\td'5qPN>U&[*7;#g6a9E%D)B]r?IU]8(6(s&d;p)nFVhDef$/P1nUT5rLt + IiF)(gsgEM",NV&F3S8$0HCXhPfPYo@,Eh%YbdoT:,1j%4+T8ZhM&mK4J/U>N65*O4P]ETH + +6lQ_QM,Lj7soY!pT]1d=_9U*Li[*_S>eP+KG9f1U!FPT\KiD+LP::0+XeEKa!0H8t]&tpo + g+Nb!UFp9&r!.Y8&cR])>qb^mUSs!WR!ji,r,X\T[4oqodi-SBHS-C*D"lU(*iAU7UqSp!72"'k&VRB_M#1/2)-HarrIirf\E&WpMR&qQJ6H1s+6a#q_h4V6"(:t9/6ffg0c5#uMaYjFX$nIO" + F&6&MLPQPSR'$M%#&bH^\'Gj&W*7#0j-n!>8@oE(+i)nD,Wk4 + >AS:t(R/2Ad=Z9mB5Ma-)%E'*%AQEfTc-mVgGFI"oS#h8NGip&*#+YX;9Kshmi@Xgi"W52n + `Jep[neq#:DY$I]Z'Cf^3q*17Fa``)ioEC.0$]B!>5o@:$;("YgjV#"iC"UZDPV$WL1M + Uo&6^0fN(:;Oi7uh[cC^j7%p;o`HX)-A37PTI2EJm;PgDNt3OKkJ3[cq[oIi?_:89l4oP`H + d:,tXF&*7UUDpBO<92/qsm9(]G_/fSo#7 + 5=`>@SDA+0#%7%3%?oS5IQ9AWc*EeAF`$u_ZPV$C+EcHT/1%(V*RJ^RfYeHkg^MQ$$06d%n(#/[^9-=5,>dh + PZ_%IPUGBP4r,Rmk;)HTssnQ\,TZNh_B8dXgeU@0CaSA.tKK2JQb2m[F[50,6=/\?3SI%8h^Ta&7Z`dm.V`S"+A95 + @F(k2q'Kk3/FAXJNAG`u`5EN=7u4u;M^3l+$?X$:j6BFM;MppFdW$I![QMsT+0(rL&iaV+B + pC&$)8<%j5SWta4F6khqIG.DE."`..NTj%Q0p>ZH8sqb5e?O!R6^IO`QQ'/:qcU?h?+*sG[X2l"]5b9KFgh0k[l\lEp + GO+ePSG?`&to"+@!DlR<[aUk\Y3$6q2k+Kg)<&O'-""\>He;NZiHT*-pT`'iNKmkf,1p_u$>h8ONq?@#=PZVpq + UnMRmZV6agSqI,%7H"=L]4?BBr + S?Tr_?`P*QX&t^hR%"SQV5uYSB9WA>p4KIZ$*@=AXhnlcDD8_RWo!o9pr*O$a9?5t);gS0Q3JtIejh`L-qq;h`4>!8\+lRRKU-(LlQj(D&$,fic + `4-Id'/RTbgO^DIZW6&)dndt'AMouj"'@F5Q2BZ&5@?9aKRA&u]RO?nF/OD@37F"DglGof0 + B[L9W4\Z^\s%nQQXjEZWZI/Bd(*^kX$!nObAbonS@q)bV5r7>B0=8W'DH.Pc`Gd,buTV%QA + ]GCfBdb$cY]mu9I$Y?^ljgAB4iZ`kGTC]+X%4V+e<'gn)79IiK)>.%i7V4"U'Cm@1IN-.H:b[6_VeBZ?4WP@7h7oO + H5>clUn%D*#HdpfW9/Bt4AWRG[k3ZBaLL!&Fs]/D9CLIL1JHu$LPp\YL'"bLMSgt7!L,8A\ + HA";3\F-W^(b%7lhI+(kCS9!d4RKeuSaeR;K1tlQ"rPttilSZorB$&*#ff5DgrPn/E]AGER + >R)&G_o-0%gcU$],F-D`5MR7XO$f_dk]NC9NjF.us6b*C^&J`n^#X,-)r?<:rOqOjq`W/QB + ?@dXq`ii:Do_28qgY)i/,oUb&/5%P5gVg+"2RM'[UI"VHlWJYVA(G.Ro$1@$c=;"0+OIJr& + SY]#Q.()`/ud[?c93S<.CAL>[\G8d@=rf7Y\a*J"#oWGoar%jED`8nf3O:R`4'C,fpZuNa5 + 6)JZC/eV9/`e9%'fE/JeZfW@gilm(Q]IZPf_tb\!8ZJ'BcSeM^.`XOfG#;k2uR+6JgV4O4\ + gB08u0N<)]<=MDue297W + <7L&E=F\l/I9frUAR/YHRC0#^>a7hTR9itFm=/PH2C3oa=EZicA6F(URq14GAYEa?2Pm + #rVCCteU5]5CjHh8=/9dm/DF^4ZHdro$kFlRZ%GD5:,?FRoBI9tOeo7#S@p&fhQGQ + %u$@+Ze:KjcrQ&Vje>IN+)PGa9k9iqTB*Mdna/2eedNW@ft666HO7J7&R[Q">5Q>F/ps3!M + Y1BD-(ik5!# + (jp>YLE">VkE5eeEnmiOc#Y7n/^Bscs#PLK>nQ"ET%u>&e+Gk::3W>nY@#rU*LC;\Fre-Zh + "PIWk]Z^6s*`gMB;+#8ptdZaJ"D*Dk&pKKJ">eLB:7<3qT"r-m2tMRe*_S>n%\B9#X?Ia+b + )fsM4uE5g5CCCni2^0.3Iib!HOj!CNJG/b+"5up&%V?ta7_S:U^8)=)?W7bE^$/,!//^`K0gtcD5!@/LSRfjO_BRc8 + G#Z8,a'cL8DP40M(jk8m$gN0$;Vp=4Hh'(99b8uCF&^NQ'.S=Q&/p"-,HUJjcX%GP1;?]<8 + O+@dW1\,9ff2*m%5U;HQ#KP@]2^"U1R;ZR>OF(9_BI&`e;c>B<[Y>s#!MGYeK2=$06"is + tTiZ^(8m1reV1VO0$//c(Boiq( + P^8fSi#^Cd@#5HTjE\=Kq+t;o6>s7IU/ZGo;Hse/E&BAf\>l0#>YC6"ohif\>-R$,gR,%,D + 9t<._,oG3Vf])EZT>,5l=ptmmgI.+)U&I6naLolkhcc,_n;FtIl$i)*EW0l9/Akbju"+V/OSj6ru!oQMIJ]14e'jG + 'H@%8'cjaIZT\3*oP9Hc"7RM"<6ol+>>_hH)G(;g4hEabB!L?;5O_jG][mZF/u0bDj'6WQ2 + k3Np&Lg*I\_T+b[e;i53'.ReEJ(#mWh]odF(7ME6K@iT,DnY#MD\D2FlM=Za/p,Y]glQ.;) + "ckD:"NVGe+J^J+_2)hq@.Zf1O\Cj*.(9R.oOB4Y[Mq^LE?AW]N@6(UUIhtY<8r"t.ck\S9 + /=@eV@6k#jg&QdEop]+)1=1neAP-bofpZZJ=ok@bK+E:BM@ZbV'_Y"RaZ"f.J^WU16S].$F + 4]t3R'U*d^boj;_M!1_*g'h?d)Z>11kpSRWCB%bo*]DB&)@71R;t)H,hsYI-mMHY'\J&i)k + fnlj1Db(T.$DPlaYl98qs^d@*!aB_kOa[#cccRN"Tsi0k;!+PnYL,,8,>4fEK-UT&AoW)Yu + XWTq3uWmu79lMY!i>n(4aQ'/Jbg='3j?#=b2p"NOqH!Dl3`32&cjD9/@@('"^HEUYmD#4S7 + G*Ro32D]+[EZ)<%M%?mrFEeCg^AR^=o!;A*qcV1kBHQoAJ31-0^do!K8d25GIR0na#kWKYKs5ZU_@e + d6@8&,(jJEfXrDaiZgcgfFqF8EOQ;L87_()]pX\3]K>%7]SN9/&:`jk'WU%F9hHONbB*J3cVAB^fA;:$EO7G"lo*nT.nI2F_fckuMK^5gZ-+=[O;U!s#X;1*4Pn + `gU]e?qqJr;*7j.aG/r[B,JPq_p$I+5-QN;g'`KG:bn$/OJ"'C=^1Hf8@1g+KF-\Wn]Q0%k[ + *uT`]n?.MT\n^*-^85oYV>K'#%U)`XST\@NcA$]M7#N@r\?\GMA^>T5h615dV0$?3;h9GN[o]LcBl"A_0#:VA;U73#/n/5X;J;FNuP78-`N>ZX?n=@ + I%.`I(f)=Cq^R6ifgiJD8s0hu^moQiua17N>l,.@^R[A,oS?7Hpt,#Ts0CEP7@_XlXNeu]cHQ469aoHE"Og'be'0Gn9jH4CCf1M:@KdT*JtO>,a8j4gXooa + #7PrsrlB?pThuGUm9u3-G']T;j4bLh)LU#"B!Q.C-k0AYE7dTBbN9Wn!k=5Af`uK(oluP%@ + br)#5[@?SA]^n>_f09]:5:#qQXF!/oHo=@bX8IC;U\Mn%J3TN?G94P*IEtZ(UYbao?6^tAC,8\8A]M$_^'5YD$"1ZP + W\]b^0`)F?%d(dWP>_1M'rjW$kr*=_BW3BNL/.-*u`+[RA-09qi$3r;O',rsJSb,_. + ,]:bq\.8&DG0NpiJCSjGi#g42A\s+tur3SgX9iE#i;_&I'GpUp53E'1o;fk,`4?Zl]3_S9i + V>NYY`'V)3_D3E%]@B/?>JERc6;3&)<')Tcb:CfBFNhWTeIB)i"hRb4]N_MNgV(:)mClg'^ + "a(3>SY%Jctil!8-)O!!9ICPBJj%?RYUQ;gmd(;:fSBm`gK@4JP[&8fP6COAQA\=ehti\/^ + doemFF\n>morKD"#[YF@TVNaP;up4dL%Wo)jo;oRFu[.fn02JN$kh,.+t_ldY'-T7'9/D + .p;Nr3c)'!i=oHA0>fZB]"Bn!,8!RaY-BRW;e.r4V)R,934iItbMsSk[(kS2@Trt&;r]*$p + )dKi8)$3N>>.VoS[MaG,Vn/u`9"hpD8P:\_IANQ@[8EOSh"9Q/7PJ.@_OG5R!FDt;$)h&!P + Q^F=>o2&b%"(6Kq:1t&-HDj+bC7J#C(=s7FkaMdGrL#A(B(!%QiKI5[AfiqHN1/XoS'dZJY0E!:oUaIROie(Q\NHTo + $^O*gPi+Bq?pl8<6MsuH#2,RR=f!N-%'6[#UKQ7X=mRbKje,N.GZ*k,g,#c`mA2N@VF,J^i + Dd>@Pr@T=(U8u;g=/>[lp0:l4PK`s41]%Y)^N@C->8D_9PI4#/gJE-2Qc7Wf0O( + f(VkoPq#-BW!pP\E=HBf;8VOFDp\<.ghrJo3?3:crI+2/11,L$kN47\=0=0EUGiLWF`]r]f + [2eXUeHC0f;':-`P]4fVo<(#CML>huA]A"Xd1V + UL#MVD/VBZ%]#e4inJ7d4fUkt^niDYA!tp$[q8DjL0:T)3H/r"bYm"2HYl*91NW6gtSVE#G + 4$QNKC,#NI3oK:"X+Ni6oGVf7Bgn;doqSuM"?fN*b:A3R,1c"cP*gXaBJa/6*,%S824hs3( + 0T!`TL`jI?tFRb:e^Wcab>0?/r"($;ucq'JL>BSf.dU'o[:p.S@QiUFO?q=c;=4FpH0td893nC^-)Tk['4G3s@;?Xj1qVrekkkUmff53am%lV&@S6t + JT#jrj*jZu6Vomj@UD2+?`kqK5R1oiD"`?WVAg=ES7ZFamQeO'3rn=+jsBJ1q%T-!JQt;K` + QPBuk;f-k:Da15$3'"5aL;D[CT:$MK5_C534.*:RJkWp7>0A4>6`A_^SAD/h#aEBW4*VFn: + 0(/MZOhCJHgAkm?C\+-n'l^W[pU&4B?FR-jIaK=doimdN?^ZCM$C_VtO7U!U@&+!#EEc_j9 + 4bmBKKt)8^hfgSBV_$5TqUjR@o(OhFk%N0!ctsL>m3a^PeKSBjeDOCek6;0.IW@cI3n_P8i + 3mbR*o+pCh/22NmI[aJXe\mqpB@&9dYOjE'nQLD+GF'^IAECI;c + ZB,G1:):/:XpQU?m+*u9qF8*\daGgDuk90Nl$2=`L\&X)3V%,HY-$W34$\*m=Y/\Rr.5!au + CmFbW08jY[qKUM\?cS1/rm,X!P@+M8o/6X[Y;GiV,4f`WEHn"&$O&A<#6M^a%YmQNe*;&VrH>qes4cscHen],LW/hQq[,RWX> + 'OcDj02j_pIa8HTlE]rd,[u/5HpJ&R8q;FA11Vq=":WJ.c5YhV6rcc_R2%)duk9UL**V=IkG8t8o,t_'WXY=`mPgS;]*OPK% + E1c<178d*-/P9t(k@IXo$bZ*#\I#J"-^LKq`7,.K47R>#(E\Bps=]AEc.@U48Z + dOh>4>g=EU4lGc$%J"+%Sa0BH82T<$B]2m9r(oRAh\8D,'k[JEKZ+7IcHdRRtRsXT'^MjAm + rPBLY2\:J.7Lp6REI;L"CmZ3'-cpD!\[#T54OLFZF+s/`Cig[66d_76%o-p54^%c"Q^r&%f + 6.DpXBkOj[qjalL"CP*R_@m5J"!4[L#R?+08+nL84T&"Tr;=lkG$&$W/USog-=r"^=l&AHc + ]`(n>8D_>b(`6_Jl(b+&[W^.,T?/pmLD[kbfmp\u5G+MVn32/+R>tC4pO@j)[,)P'S9.^$pf+q"eaooK)ZA-<_.A3YU;&-:* + f2*-8b*"OH>H(j:^LX>th#oJ@*U*7tr_<qa?iuCrh7?5J5,,El`PL>tL9/6AqV3JJPdfb@u; + B7QjW=pK),&i[d^.9VS,bQm>:eH8Ka0%J[f=Ao*,/7jdGVBX8f91i_H7B\Jtbfj86L1i1C^q&U%l!8),GKl + E6]bd.5S'0c?0?M0m=>],IZ^ld[O\^^&b,=8KA^gUF1J.e + qW$=97N:!;q(4O[M4Ul(a3f#WDRaig]5LKNi4CT2J74k[H9V!]8@] + Gf='i)Q,*n`o/S+Oto&OQWB@Ss^:o`,;poi]QrA+X889I$N:rB)(\eja@9ro01$=qk')A.1 + b(.fFk.^$EO,TDu'IL'#t!G7?*"E3YpBF-"LhgOL!G4bLp)SB'e'=ZSm9X=pmK,D'bsaer% + s\>>o8'.(9gTkQ:f]#n^Vh<49^@gdDNmDXHB#\%ip?>=`QMK5bXc(EP6J\@HY>c-2)i2X"E + CV&(?Ke[Y./'#mJgG2l1H7K_B5`p:R',F!/[dGjdg<1];W@T'nf#5BLFjWNV>@D9j\7OG)U + S>;+(:=oG,-`$D^Ur%UY8gaa5eKH>[>bmVLAlPbO=Y&#jY#Qs3=t8kqXeitGQ\tWGX;jX4e + h"Y)fUZY+8WpIN@b[R)cUf'cEmE + 1'PrFS)9.>LDcd!=l;gef-S9BU,'_T[KqLqaNm6dhtO%; + Fe`WE(*e're:J'j&>13)8eS`pURIF8j>596JkH>WVoon3P:[@G^IUrubfFjfb+4Z+uciC=e + Roco_,`o + ELK]j-"JhV6Y/j7Vr((X4SiC[6F0Abs0S^.I5h/V)=L"AgQZG[Pe[K/r"e_;+u^i@.U$j^& + _dd_]2HV-u!&Afp9cq[q%';))*X.E+9Q(#n>aN-aR6`j9"lp)J:;k5IW94eElmN/KaoAF8d + 5b9aDO00JF+:1MsHI-!BehO0.Ib:M[*jgl0nZQcT)#)/<\,ZpTO949X#2VVQ!71L)5]-eABHEl$:5c^C"A% + Hg*D]:a"am+Tnr/"->5]&2+*j*?oM?,17,-0LFdb + _$&%=7kdje%R@][6Nu]J"tiUr&8^H)D[00D^G'uKNae/HB*aN6PAYEW/p.IUG/<[7$#YE]: + 5LAqUF;5$ktn>CN&m;!6L!p4`)+%4DPkBNVX"PQ`U:+$E6=U\cKksWa)#j'l+%8-g0bE;aH + 5K(OQadpPq@r^m]cKT%&!'JW64B,n*IS<%g6OH^C`$7&T#W&Y8!_7)Y*>D&s4TfY:@if1D; + DsV/J9`XSSe+5gW@roT)]\Y>3Kk++;5CE*LAN^*Yml_u0>.2g#9GVY3RQ_F':V$=T/ooO3g6a. + F0dpV[6#&244Ccm+nup@:^LNS##YL`@*Li3q`TSG*4+K'1ZLUu!!+2R!LOD)@':;2ln^@GP7DIEd3Ic + aIY^pHl!noI5k77EAb$''K`$muAJ.$_n`__A,?-#G*6STcUs.V@;M+p'&*q7lOUA6mr8Etao7r,[1L?"T^N*.mg@8p!hp#OnW=s + adgU%I.\nj5o$\I2EAoW;>r"&t'C'@0%n?228G&p-'[7J9o/cQEPkE9Lp+o,1((PS + G>trjrjP5ZQ-8e^G_(5?sJm?-Bc'F<:YV5=/B88+\;WF>!h41F4k;jY1Y9%U17O77:*H#Y? + Tpe5#0-29+^U\Np,o\\Lo_RB?[Cf9X.9r%8@8I9id;E0c<^8i1gl0CPk2-l9S1f + 7e6-i'6>6Js. + b.KSBlAT!<0M:(X_B_/o2Tj\qFuN#Dm*2=XZ6Hl)_ng)Fd#qZ-ZW9=E]5XlQSEO41NSmAF4 + W9k'oX&>;A#ju2tUPRuhUF*N + ]sG17[AE(pL;Q:Mb\6BXu/"JH$"qF^BA-$QP#<'quh7j^eWk*/^KQ5M&N*=5ak7M)%;<+f[ + ID(-5X.8H?]QfBOEI:O*]lEl79N*AiU[q.Hr/!QA0<1*#oSOs4)8l4DWEmgL6f+.B*mRApD + CnG@Gk@aAO0(/mUP5.g,'_&K_&^pt['+5Fk-#-:1:A"*p[][quqX_jB15uSS1GWo[4MP>sOIf/%honXje9@5 + sI0qc=]h'lQ+!6Z_u`N0qqMf[Gehf8UX#NEFqq(uL?UfG("b%G,%u=S$[9F<.mf!r2dZ?r. + !A8fj\Y:TcQ@f8j/qEKPoYGF*rF)'>#CKp"G8GdJ?3ni:tU2gL\UH*CLuf:Y^`:IB?*#1?L + Z'L'8lg3-";t07^A"Cp,@En4+:mpA2:!1p.:ZQ9n/Ci + J(0`6fE5]dH"Ha[Ai-P?'WhkRDh6Oa95si&d/Rb@U8GiOe;B;SjJVE/XmRW54YF/PINVqQ + 5M+M%kEI6MN!!)%]Wf_1)NbGg]fs5BOdk6[>_CQUZYP5;AFfdW)0.g-csOa=a$))GSo1R#; + 9H.bJMc:*t?q38JEOB):(9S@+&b:()2t!`I*U`@Md=T<[R%=/U_isl$)$PGND>X + SLPM>$lP@7\+_C3qahV(AX&[7E8?u2bq.K[m'uB#L_,J#J;8%>1(q\E;ThZYUj1\qr^:WeMTh6CmPUk + 9mMouL)WtB@?^SgpS1?%F6@7D;HKG7Z-_7UZ%g`sZU-c,^2_86$e@8j*E%E"\D_q[2gr&_h + :8&SY*`$3bR+bCCN6j$gM5%nm=+n`iYPWUW$)4HOK@0dD%p>_U^65lmLHEjOi]uXH:aI9fD + :eRg4PsROT?TCJ\;GF9X2+(HIPre#R2n,(!JL4D0-F^mrFB9iXEL0?@7PLC"eh)<91U-AO_ + qPnTP^?lYep) + O>=rhJ6,GKF>DL*h-plj>+27oE5DmO;<42VpHS_5$KgS:Os]q<'k(YX'RV=u.hS57mF\ae^ + Rg\o%G^I-ga7Ir`:oUeqOIoH!s<&&CU*ja2p+(?7_BAVTIp=bD[@?MHT?f7>6i]M_L$btYM + LZ-")iVjq@J!8lbrjV?? + >3o>i)e^qA/'NBfC?-c--nc)cZWBY>;;SPadW)iks>C3BtrS%pEU3hdXsl[0Xu1dUAo5O@W + ;)C(=c`:[eo*I:1?R'+"T!B#AO#BX8f\ + \/aeD\&oVV:AOT05uC3;#&;3(Zoen]E.iu5=5DV2%Op>Zq5BX?E/bY)fd[r/^jgMAsJg?Ws + o0X#Egeai`h^bB]Z7b/LafjI&7=Nk)/ZG4CiZ7kpk9.uTg?\6rmP(nH@7ED?)G$np6o:4i + mIbRn9DT024oZLdId:mm1ktshsU8(g']"#gZjPQQ[g18oD\eU0GFLQNC(bdB,/c]'PA/b6l + cKJkP:QI6?'"T$USE02%CLcgcU(SKiCDO`).dU'1FW,.0EoC0MI]ZY3K"28<\RpD&C%fG3Lf>?7(Zepj9HD;09jf7 + iC5?FPpRG$VT!l$_Heq<*.`(\u[6i2<.QKAbBJeUQP5Q5tc'pjOK1ig@"4MW5PYicH:;jun + abq%c;dX<"md[Ab,Nq:Vrf)ZIXYS,3-.G&80qrcO^bB&0o_qdI+Im\aCTF`.KC + rcN.+meOV9`npO-SNJP8J"7q<'?0KHB-).6-iYF5nm%m8CiXpL0RoVjcTTRBr=;A=oNa+2C + EM^u;q:PH@B7q%#<-5J+GQe2g`4#=$f3$InfEO1.!d?%,08)jNVNjs8*2'g&km:k$!4!lVB + 3$FR\kqn[[a8[n%\ + QW#[iTB#;GWi`0g<:@uQog18\Xi)>GUm9aRILk*XR8docoM8[F'SaOqMRCi0^P]/WW_i52g + V2,6``3CaqL6SkBXTs<-/VeGFfZbgpI/3c7Z"S7TtfQrTmkrpgIq4CURIF$==<=qY!+B/i& + :m)EK#XJ\=Lq&/c\f2NlJIfaX*>qgmB2S2e8#0[X'V^ab8b&N47l]TN@n_'+Okr5kR/%dJo + QuYhp-;//"X.CbLQIZC6V*U_,33j'OqKZDaS?Z$6jRRfa9Vi=Z+DA`'VCTsFAGLh14VW/%0 + <5oDRq.\\tsH3Q4'C,9/Xr(="<-2;W:t6jh/&];J1HARMk%&:[F\\6!aes7JASeoC1F_P3BZ$96k2UN^X9iOq2X + 8DiYfLiCWc_RS<%j4n^BEn&(N\Z0?+*Oj<8Ao<*6o"khBAFREGj7j*HohN2B!6 + Y^\Z+p8eCO]n\R?;&CcUlqgm&l2R8=YZug?EcSUEEQW,5^W_=nieGDqC?P\I8@5NPp3LmT7 + L:O"dSpK8+9_5lhi@.!4fln!+$cb(BRu4"b;[H@dIrjZ.;Cc=Z[N'XK@fiG$<,C:_RGWkR+ + ZiL-_Z!oCqggFZ"2Pfj"Q4YBK5bE5_0AHT@4WRTn?s(L(fLlPH + oH\unB2OhfG0!3!.TUo-^6Te:CjO/#]Y#65GB8d#O/"C=-UnK'.R-X=c\V`jMJtO'-U%3M( + s#BaN5);*DF\E\n6CLRM.]29jFX4EjG_71NmW>S@LTA(13U-KoIgdfZ8+jlX_lcQ"ISlbpT + r;T5[\D;noaP]*#)CSu,hT'5CXDIkNTd7u8+^pIUojqn\4nDEFLHr2Nm\.+e54T"92S#@?`m](IQP`2n;#md,@eo4r6fI/?%(m2G/pX1$R4qc^ZK?+7:Z$idGXL; + FD*q=us&+oNL\ocP2_"9W%8$,[`\M%03%%V=5k(LeE@UVD#&nff_#KFaOB\6/Yo1PWY:pEH + r#30T5oo(nI.N"H/_3P5]o6]aN*Oh&C>^pFS:U^t95&A[WE_#tB:'GA5M'>[W]6"C\GBc_k + n+T]2r0fUgI0i3Yt*._'0[kLGXGX]5#.)HW9\tgO^)lpR2,eEn"3s6lW9sc_d[t\]/ceT3[bBS4B\Mn@m2;.5J2n>1 + `JeEU5Q[qN2Z&2][Dj'o?]lm8dA;BWDcL/F%rX'e26`b.Dsb*ZC[USfX3`nWZ0["Hr%H!=Z + tP;^$4LUOlJ=Bi2XCq2=dlX!j=dDj9^FD2DVV>8ai]j>`(UUT0qkWZg+uom&SrHI"02dJ4IP/a:s6I%"lVHKLdqQaCL"GFG2a%Bu[ph + !"].7!!:6WE6-NJPiu>mo5ap4%tO.sLNBCLS0>b=-c1h-_E/dT>aN6Hh%'oIhQnE'jc%_]ucL$bXVL1qW$u8n0ftBp_".36IIGZ`D$N`2g5lW4[/(_qW8FRm + JO!5au"F6lSV61X=037(j!t]bErCZdQd68K>@m4_%=,]@Y!Efel/m9B4d?ZWKX(=fQi*76= + a;r)H0pX[Qse^*o:*&aISkm64cZ2G@>oe*"H%o0r:Z2Oj%`]50qeCqf02`_9sQ'Ch_?o>[< + Nc"$Bs0G"6:>Qi,72uIU/gOVTUE\@Oq3';8t)C7%1FcY;Ma)lPcROuRMH+:F@8%aVuCkP== + ON7!L:42?PFR.5JlqXqScK+BT3%W'bpKb, + hU8UcQ.e`l#rQb3M>3m'&kL5Z(KH4Np+(g6"cb8Z=h\[(JoPKXir#\kBD&[47`jb>YfJ:d"Fh=_9=`8#e\(s45n/#`kFRVWtT!WA6[9ZbDK!;`U^O_j6OFl@8-jkX7ToA4OT. + _E(rH,N"jf:D)]prFFp/0J`(VS!)lUmJB&%K3$X#!)Bus2!_5ocU2o/:+>lZ:"ilMqa.b1] + >E%'bnPka(cARHQSFTXtn^Q)\eD3gJj\OD!:t4l0eHJiN&j$<5`^?=(JM^91)>bMb>;=dEqesqge'1R";iR7!ZPY9E$&0j*c^GeOe,Cu\h'*Mb<"*jZI,MMG6U+Q0 + ^h#sdgAS>3'sf.s-`tR5"Kf,;83#@VE + s*=DK?PmP]IVaT.olgb67$-rojV/(@o'MGckU.h/Hf+%T@SsoiWb!r2`a!FB;HTZitY@rHM1EZ-+_F90F+n`>M'TI[kQ6]et>R4WPu`f$Wm@fK? + j\UK0ed^X=m7dRpGq]YgbOMTf>E9Poa:R]tH)j\iK;.n)$JD(\`ZPK"$W`[*B7)?!]a_:)? + QTGYD$rARlmo\WMF!%L]rkWo<0E*@AUcF?+sacP[$$qgdIa\?,\mYTj2ZL>_Fk/Q#,6UG[.q#d);m3aDo1_o + 6`1I'<$m))_KRkQ.H)QUXE6985[?W9K>n=AF3>X!inX*i/ub[9$s@8eGuhYie\fqnB&p.pi + MT7@2A"9:AN"E@B+s3q2/<#"7te-"E("Ooj"YE_SOrM\"Z$K]]!?qYS!?4:E%%-A%.jp#;W + (1Ur'A*-g&i0YJbBG.d.A`f+O"\rgA0JS1TIG@,h83TdE$]B?efGgo%Wdu>*LHI@c + q\H5;2*A50^"_Bi[ck9c,rCK,t`$u>fJ-fj#LB<.7:Opb14mK^+@lQ'A%[cZqc@F20n3kf$ + TK/d@G_r7G:i@8DU]/@r=65*T"`jhq22KA&X45gqqo:-J5/P=LI;*1rnI-R,4Bpj@t`aeWM + hN8%u"7hW/+i$GKYZA7#f9`aa"]2=ons:hj%-O7lES+@CdX4hptqc/r$fD>_m0rSR$H32NQ + 5[Tm2mA8;cIAa=DKrakC:Ant/LlUJ>KP&,EGSZtCgM1!G/i*>`,%V\>YZBf?6J5J#Nqohgn&lUc#--j$g6Qdd+r\C,c]HSJ + nR!D@`8u943HRU(2/B-.0Ik$Fu$$KI9hb)BZ?!@>e.eDgt3iL=>W`S7ZgeR4%of&krW*<4c + /E(5NunE7=LTa/6Uho>0O*"jB_*4CsChap1`WR51T)SFOAQ)nDqjbN"2W`6tq2p%tNCHJHk + shYS(lWquKn4n#lY.hY_p$&b$;*n%S_h2YmFs%fk!L!2'Gp3!3o)."G(uL3Q]$SY58DEpBe + (?cAgPGXLSOG3,9lOHs*Y'ROU*\8bFH&j,dP5.U2i\oEcKnV8%QQQ'qB)tDGF#In7I-j]lm + "u$5o!23/CO!B0F,d77aJQNWQ[__+eSnpV?eaCLpB.N=u/6gtR@+Zf)O(tpQ(\-B&G\;a*GCt-YLj4E*pnrtZ:M>>QEtmh9GiNiJO@B@1`cD*Ao + (OVMP`t[iPk*.cm#R?UG'3.(b4:90Gnk(,SpKudcLXI3nV5#P_qcJ(jk9gO7,r + OAKSOBA`Ih''f0pi#Lm!:Q"InKJ3XbGaIRl8*BZVkC/KUI[B@!V?r!=_%a92G0QQm)Ep&eU:R,t0Q2bduk3W;$=8Ve/+`e`po.=bL3.O%3mhc`O#3UWU?8 + ..f1*$==3MI*6N(tr>GWB4FVgNMIs,P/?OsqWi7jaB\S8\4&Z<&WF9AEFeS%<5!S'B_ApMB + 8E4J,G_NX,HeM+45ekjA3u1"f=#5M^2APu=M[X?,+D.c"jLs)hB/2?r9+bj)Lf/.K)cVV,j + qg[4C/D:-gU]Kb3]S(7*(ID%Nm3&a;5c\?5Mh"[CG.iF:]@^7V&SlPdPC?>%IN7N-oe:6ZR + 3XljiVkMSMFRDk-8cIf&Cc2)1Maq-_EUGb]pDZJF^>A>9mO!DoMo=Xr>YOQ-1RA&[o/]#=D + c*j#/TeN+UOWm,T@+XVtme0)eL'I%n!T!6uA;]q6^jRnM,\#JKR2Y(WI&GAJr + LMWMWf3a8)rH7oA>k*lg_9Jq'6['T4/+`T2k%)Y.F'CDUX]Td`ShS?-!g;SfId)RA=BJ"!\ + @__;lR=]@tJ6&8MZc')u"2LqGK#\oY.Z2SGQPJ$KL:?@oCB[>02s$2lKr\eJRR1(/VR\:Q9CLDfp5'tA_HcoTkW4]XeT[cQGotBgQp[/@Y/!4)`QQ7WQnb4

kq(k*NukE4;bkBYJSH09JXJ"W? + Xrec!bqG*$Hko+0!VOhF4J#R0DSud6Q>/C6@CRWP^Xm>iO_6N?20#Oc]]&(k]D0oU8Z>KIs + iOSA\QE.X_S'ISjT:*\9LdMhLc''o0LjFHXQsAKLa/ + t+%,OI:Z;;,8-K)<VSN8+ULM8,7W#43C>33&Y]PYLs\UV77a["1J!I_dCM>.nLYh- + jFfE(1f6gN/h/.!W0NOI=\CJd/Z&Z\Q]i\'9Oh%,&(.=-`'A;N5R7@?(loW`W)a2>nNBBAp + b\3DGu/5o4t)6^Ua-#&O@i>_.6.a4Pe*EJ8odB0og^7W.]AM&W_'45AL5laYu[VX9rjP"al + NUOPY@rOaq]`BYE1]04k@%Qr^MiYZmj2F[l$Oo9:hkQJ^MkC0LhT`WHA>` + ee3j&l\U$5Fu5'/dSagT?,O9&:@Ol;kC?GZJP16a7H(nRZL],W?)L,AYrskm:[.K'/enf*^ + !cKGh](N.&"9@ZLXou#H'51'^8`&0k%?0[R5D,$XhUe)ra4bq2NRpmRs5Gs&M&%RKk[#[o1RYXd4?_RNHOch&iDd*"GHOm6XR*[]1l)93i?NKO9YbM(r46^b.4Kru-LI=c#uW!PgA + mZk9qOJ04tRKtX7Al2!M4[0:X$<.9nV"\@H@%s?C7M35(q:WX>CGL^5gP*06VhFT]/Y<'7_ + pTA+7ae6!e7Dhi_ZM35Am<`cjotNHBrEn=TJ+)4p^Xrno?p"mLE(0uH=<@_)S/Hq`aK?.2W + &im?;ulF'lPnt5K2Ckd.(,P[]W`I%ShM"+=RB>e@\$]&45&mQE-D\@\m.p7jm&CrgW=F.-r(QK\IZNKk)P%<9ht-rNE!is;3"5p + s7;pUE*k9#\,]o]eFuq'h]@WE+?Qen%YSrP<_IN%C@7gA^#Q>M(fO#&gD33=Ff#bhA.h?dA + S$Qe+?6Za\I$8]I[#m8q76=>/D-k>l].GUNgLcdhVU:?glJ,UWW%:?(J%2-Z?o((/(IEdu\ + WQ1?W(?cZY-`hC&fU,XMmp=ZEgi.-I8`^?-$c<2jcc_$:Z2-k^[Q"(^,pHT?VUX:\.8l(>T + \]>Sf>QAG7igS:P\IH-,mBl(ZSb5NaHa5$%W4L"rrY'"/'N@EVCp&F:T@3ghY+'V_Mo\UDW + T-;2^pQ2!7ueRU^5HbU5%rBO%a831a-2kY2HL=U9W,#<1,em1^c_.Z7i]T9EP];PU%K.T/3 + 8Q6!@]bC,D9dd4^;WMaHTR@9W\C3Q*&NbkXS\tWg5NSWkhF73*c_3fWAT(CWtcd0MGg<&CIASH[;ZcX8fh-77f&^qKH9^0VSVsEcFYPsQt_FRI2]gOF%R_Hc,'Gk9XK + oq-ohTT,L0+bPV-5\XjJg4!DBjt=C[.^*A7;/g/imLndQHCr:DjtWf\'`p(gd).[]C/fD*! + 5%lIjch:F*B8"r)Z[:DQVQfGA<_]MKMa6*U.dO$EW,(lL[[0GJACCq;ZNHJc1*!i6V,t/is + &/o`/q?!3d%8O;.cb(BH3-!Uq*20H1(4+C/CE$ilrVlnAER<"M=g%aL*r9Kq[A5j1eZkO;= + 2c@"emD!-mc"f_"q:4WUe@g'gW"HJ<&&44GHL?WZ'aKD21-T^OQVWt'QUJ@u6m^DFV]^);" + ljU,A7rmsOG3m@\m-M\!-]m_sb3eJ;bqA)%LTIFJi9patQA]UQq=/Dt9rf(IW4,'1+/, + PI^h\eK(pD*V%f(&`"K_koOj3*`;GKZ1;"jL(f+A(rY&E6%DVpJKV%D"EG(WWW=8^ + ;[/=p+gN77ng,I-.%ek'#8.[md!!I6Ij;1Fl-_i=$7,t@89E!0lN^8nCnSpeWsPl/UrR0V9 + Y.'8=t"?oV=)kA9Zmg&c?\Efbt;[\C,@*o.^mLD)=ki0!psEW[mulAF],'VH(FsO[Dl"8KcPOER'O_q'"DSHTapR5+\]r56n&61 + serYs@rGf'RdBqcR`0/C9][=J60C^+RoerLD*7&sP'5\h4GrKem2Nr\NZ\(4.KfHDq3:hnc + 1Q7\eoGI[)!T4dq;`"IS-3?FV+/_7H;mcj.0]eH7kY`cP1+PICaZONV#0_-51,D"UfQ%Tel + %^o;0S7C:7`dIQFL"[K0TdZ%<9o]O$.,@m0#]0#B-tj9$L2%I0%B9M\p:Np-h,#%/DA@OlD + SO[Hc#"qnBi[9npN(b6gk0(nEA4P&J#Be%o41H;Qd+K!7A*]&4B2&Q*iqNI!W)QmaNs>Y4G"We9mKB.?NLV;!0;5k=!\c71u^k9bd.0rt$[/&7^ + ,X:GApcCL=Bf?gTdB9(b\hq(5GhoW:h#mo&h!jY!ZF'@QCDEq]4TqarGb(b^"c0oR#ZAr0A + epf+X:'Y,r!8GPnfg%<5:<6\+dd5&.8(?%^oi9O+U?3/NL+024UPjFJ'n@I-D)UhNpYME"G + >8rh6:j!3e'g#?3)c:Ia;7;g+2)/ihR;01,C=]A!p9eZo\SDtnQD#3,+MBgE$kM:h4ou0p; + qjB6<`+k8OP<.LhL2E%>u9imZDV"!W2q]K?BVX$eaPOuoJo + laddK1IOW-eGd1S>H&sRkYe:ePo)&#u1@IikY<[C>"77kET8e5uPp+mEs"_4kEMA$A.1D&5 + :B\AM`(A:ZC(0<8W>C<]'*T<"nK]E-!BE/(=(-sZ3(3^YGo7!aEr>'r[XlY%NhRjR*Bb@r6 + CXk=d[M;*l(h#lPJQ*5TC(_Z['VPdA_);gSc4V8`$m?/kTXTOr[LqHY$fN]K+NbE"$k<;]' + #=>^@8[]I%MX)QC!D3D(;#+,Bnga]+2O6X+i(R\F:t8A'hHadJGK]l4:C*X%=keu@-e:f64 + b9j(',<`TrJhn*$6>a'\I2:phbpk\ViG\Mg2sEiK+`S$T_^H(8X;[l5=G=F!1-WD6,s=puP + N,kr^lkNc8os(uB5?pPjqSNE5nLd",:/UMs0"lI5?DYaI_-;a9r@b5!2-] + G;]DEP0\@%H9LiP8/;qbQ5W"dkcra&eo[5OQ70'uT45`9@f`u0F/NN64P6oF[5LMLpUWu') + #9@;Ho/rmFO*Z>?6A]h#68!4(YTZK0G%=II[T_$p[(aT9cD3_)YSkuOgf#/]25QSBhQul_8 + 6t=/VjDd%R@H/J9D+S:PKJ^!oP=)i-kkq<"7ck#sgff!7i*??kRJRUH+&sWS4&FXrF5<&@aH + R*Wa85R/q5ngT.UfO&kb>q!kG1n#WtV4ZN2;20"]0qJf]JXnMcYGmd`>L*E#t=O+78R2L!$ + !Kn1db?#\,YcV)LFXLSM_r%JGd3)@_3o_bqYJ'VQ-MJAqq(:K;EnP'CW<)Y:bCL4V@dJHLYRn[70&RX%*o=&pO>?tXIME[SjGZ9<(FGEE!D0YMVRI+^ + tFsdu;J[bH]\2grQ>Dj6*Z((-C\Kmf;CLJ#!K7_q+ + i7(a1^I5& + K9h#R@#3"['WF>h/(dcfptKl+WkUl&X#\JGT>4#iLQ$I0o4_e'@gCj;tRF5.OYLaZ?oq/I6/[2Su-]2o-Fgib81oL3ie=.V&q+F(tc':+ + MFL4Ec]/>l.3:Uau75nS>Wlp_>-^Rj)f6M_p]2R>UFV310GHXHD'ReX)h:XM9/j!j:Q]GSl + PX@$>g39(=;ZB?pZ++`NgN9dV]8m^UYM7$heB9VeehjT^iL6U)[TX''pM!LuIOEe&#N[+9 + Y!7ZiL>_6/`BB<%\R[LEVXgQhrL;'8jaQd?qji0-,r`a+GdNN_'F^VGH`\oQ!q`Q:p=\)0F + #01Rh*JKK's;Gme,[d'J"o%Y[?A9L<.VD"a8%F08G@B^/h[fq`FEp*r0BDnG"*GZ + [FlG1dUdp@>l"=.K12V]\hj2B5,VPaf4q`4h*]<4s4V=u]7&8nH+;aLp\WeP%FqoH]X[m22 + _U<;j`>_frg,^GdH5D[*$3]BZeXq`p0JgGO(5!WB`/iNL54@[p:MC%muPBJp%j+9DI7VPiV + Ic5ciO.G^eaLB!C4\Q&HtGh@GGjpn8ofY_D%c5$(OhF0R#l\_:oI;V[/l-up5*`ApZA[/n?^dhNb@;Ujn*`b8@0>uap*k7O;8e + hs"0>#sd.B3^s,ahqH:VJ6$KfJXb(@MtIHODn?'@K(3`G'0Kkg,>Y=AKUWRZ"r)@q3,W!f' + U2$S(@,BA6a=Ac#d6'nW&*g[rpYRhBk,;HCMCWnpd-`/ba_OQhCJu(mj(cJY2)]#]5rR7N$ + p=\:M(s+6M-%MW1#3/7@J)fsG.F7&:7p[bI?YPGq6-)qfkt,&A\"54Rc`f/XBk! + LhK>E&-.[X@)S-E7J"!4ZO/2"YO"8[WN>CIR;@B=VC=u(jD*&c.L?@U + U06k0QJ&_c4)Wu+RK\`7]'c&rk#cP+iLX-_j)[#^+bU]^M&F9HraqFc;RFF>@#&;*6W1r?4A?V74G*EP&M'h9E`9Ei6\5M%/g0F@jqKG`IrPt + ;$Gh4U`.;q^qp@Q9o$Si?_BW'4RgU%P"5!2lhIBm`Sbi5d;Pq`cFK%0E2`PNK0"\(CA&^PC + M4H!lcoJnL24S_,Y#i6gD + 6OCf!=\31++"=&QgILAJe.gf[;.tQlrp)_-7$r=5c56oj?6Z`VE:`MP^W%eFmj;/?RWY1,M + %148s1D3OT,")^*-R)ZcM%NSO7OZ,(,4o]3@a&%H#lZJ%keN9X/7V6X.4$8Wd$*K\l:R2D/ + OoB6E?gJ\H0NJb%Quq(l/3/ZoM870!jPsaoJWW,s9$,a + Z.qUTFQnr>g4D0?:B@GD&Zds^cVoqN$NA[S/p6O1IX&G[NY#OgHfI(&\YLDQHL=R.16Y[0!Z&:f0_W-Io"i\5VVaO>BkX + 8:g?IVd2(T4\!-06DumVHmEHa^1p838I6fAPh#YpGP4g(ksKu$/>\S5&lYi9&?[<_^ + K=,1==d,h/LA+E=\DBq98RjGV<#CY.tM`b=cnMAC?(^tb7cP.T/5W!!h,<]KsG8G&=@, + 9?!/u=!uHPk5(Mc]:L*Z-W?O(nQb5rMR#H's$$.^=U8.d1%8UY_S,&PBXQR41NH^Sq-G6 + >F((?o+OXVmlbC>k"A2dPacGN5OZ]U,7K8$q3Dmp@#q8ah\rqr8kRV^&q2Q:Ki3-RH<&RT' + -m:+Ah"b]"$pP`!=Wi-Z7i-"O0E=?]h4YSSh,+.\s&`RV"ME7%SlB=h + UisE,Q-K[2*\DnOg\]$R3%R+FPZ?P@ihiV']8tPi*IC(njP]DHVTXdq#AUa087C-E=FRV'i + 0TVT7c=5#S(66fiqVUm^DfFCLQ"N]kjZJiQcW^B6##M;![:bu,H4sNJ2bBul)/?8'TE8/oG + RHe[*i>Z;roo3!VR?U!e'B+4CdR=K*?V!j6@;"PY;u)Ei*Zkb"'FkR8+M)Kb4^<@kBt%+nURe"9 + $1Sa8q,Pq,Dl9Z/Aq(Iqp0<.Mq]GNGjMsA?d=j3eD$3]+m&9'sW+dQrC)RHn[V9)nPld3rtDS)K\%k+K':f4 + &U>Z41;Fn\$BatZ@"_^'=tfV.k"#C]@)E;TLskESqDPFJoedk)_s32_`U:D)tPr";`>bU3' + <-l.KCZCfr*Tip3s+n!`AD'nUgq-/AO]@*7RUlY>\D&mBF0J/!<EPHP;5F_/EA9^>Vqo>/Y8#uVJF8r_4gUEu-&A/(bqgC_%j%(-n + \-rTpI"W^1$+Mf!&j(b+>DGJl25=st,CqI4qXLaGN?c(K02rTEoFo&>OJGH7,\)uj`0:s_P + U*=K3FnI:K)e3T!m-j33Q)uC>#qk<,!#N,3YMonq.[]U%ROs--6oBB&U-9_&soCF-B'NKHP + \$@(s8>M_\3t:$o0mQ1GS5QM_m<'4!I%>YrcI)11WGXMn,b`4>NEi42a=Oo=Y:*4h^uh"6_ + Om`'^jK[^.O!6t2;%pd;]!8N^f]Z^a\$P<:BEIYLZuK&@pUlsPYeYFc(+J\W%P-mJc.(P7G + cJZp\W3$$6$)Zi8F!JJVs-k:d;$,ubL71@ZT4I!B5PsLV!,f9RAA=5HJRP6Rf\\oUNgmp- + XS[o6$_F"[Q!t36`DF/Fn0a&oI6n5d,VP\/]b(MAo`2YlGLmX[7_]1G\\86m&XregMb4Du- + Cp>@`37JqS4+suQPdO.o.Dst2-tPmekVn+O-`Q<'KCCclagXq(76EXgbPO/NI)*[XPKY8m4 + G,^``5K(5m7`@u!&uEZ&0$:Q3J.raN>acuMR\ol8AuUZ]F`0K3&p"C3;eoTAL"C\,frH>_5/MFXfOh#R6f(H\[3D]qjg%US + hP:=_CVur/O>?1bA + %KO"a"qXV`-3.BW!E_eki$JK_/d,QBlO(/'RiiJt:_[%dr`LP0Xc83-rK#"5lPWW[0\cOeg]X`X\oCZi"b9Klgbm#KrPcr#`i$er_l_$[8qGJ + j.bqF%gq\/h3*%'`!\*>KKn)g%"MN=@dF@R3`,KA>!PmMZ_aL?Pl.f4\2V]\kc8)FcGg"L`cpU+"_WR8kE^F,^3^3K2Zgc?S/<+EGS[!tPK + 0g>i2m&h-4F;u&@WDak\^nqG(nlbKL_#XkcG8$Q'>t,&)h#C@1 + ^u(ZFHm^pTfUPo>gg+tjD#5m=&f7AW!"+NXhZ@XJp=1`'Vk(ttf58T$)iD65K*R6f[_;QLs + 3`NcuH/`SuJ8c:A)7&&Fa1L#d8>GDklV7]`E458UT>P'UJV)pEW5T&<>L]0SM,I1iB2IoNJ + 1/5d+S?p^,\m1afjJ99GW)J@G4/qLd#NSBC6B0LCNa3u1.3;G;1#9d=;R9ej@KmrMKGY!qE!@cUoDii@"Tdmirb + @rECZih&?PH`%->[ONScUkkJTu?ln4tqBDMHLBY;mdB(Pp?lhtnS\e3cJU + )j(`i^n)X,It[pF[M/dndlE9L@!Dk7KK%Z'SUMeJXgDgmU@bB?Cj!GLS[kk"nrob(DfO^YC + 2kr'LF4rh'Z*QViYaF+rc%[ab[*+CH0@Qp9k_XuhnVW)_BC&XdR7\ShC)+eG8aaBK8irRm% + 0@I4t;T>\G,$In*,;>[qlEd8?AMQk93,7Qcj.cgRT(Ej.#2!"1PZ)H+Z>L + `S$'&jC->l"rjW>bq#LHub4B$>u:YqZ-WTRGccVj3Qn9*VoFmiT.s'BjKT``@iJr]JAY(^M$a[Mfre3kf3\s9at63NTfbr@==Le<>0=_RW*Ei/oM#X + cO&:f$K:ZN^t#-om[EY<*q9H)ib&k!1.,N(P4:]7ECi`2[3kEX;!Lb'l=DGL#,g2a+_)T5d + dJ4:7ips-%WW&]@sb*m.)i1^G$$=:t + t6:\F4;hsLH_(J-)*fJNq)=/4"hfGfBL&rRm=7Xn8Q+&[7f]Ce23ohBr/=l%bN.p?:CudBJ + (f*tC'H+b;j1\"(0\`pG\7jsm\]c7)WMWk_B(L2oH\GC-#ri/puEig_pm[;a?aYtANs8kd_Yl6>mIke]?Up`%6Q6D\(Nqif$g + I82I[eUV^\&*LLB?XYI+,c)Zj-@a`kbhL!T7?"rN>*=4q0:o9FV'?cm1=Q__A_\%`=D+2\< + mS'L<>Ld4A--78Z0K'ZH2^>o:PcS7*=*;62TANIoY"-&BDUrcalq%#/trZ4)d=8J59n-HPA + 'T.$?&KVif_ZFSpE9/=Gn0$BFU[kQ"GJ^5dhZR!dPbnF@.2U*V.b;^U8Pj(0Qe"#kfcP,+, + 3DG]:h*97c%*(]Y'K!crbS$/>7`dK7R7Kot,q1!8Z7Re(dYDh^J('YT4&=@+%7s>8?@=PD.[tB[Cp9ZSYlWq?6,=(J:H2E'msrP<\`9<=]R\QFdpGMSS21"NN$W][Y + :l,gGZUmDHhkSrcMH^'uP%Hf*Sp.+6QAIG$TOjeV_a8+(uh\Xi7IG`Z@ctm&+('B + ,[k<9'&@-50>L7J$N++p$7OoBE2Gd],\R^fLYF(;`Urc4Hq%S)XlR6F;bRX#BEPoo09=.!R + ]Q=lYVRAOnhRQ8dpET_W@H_6`EmY5`F\iKb0)>[*pV.lBD`+NdFQD/l!3gQtM)7nD/E9hiD + XC`R;#0!laHS3HPm!DUG[Va(\^1D1$QK,OD##=X"S,:%8^)71O]ta&!#6hp\r!*^LnN9tRD + OB3t+3/CARCH^ur3%csEsCT[bO`C*rb_8?(IDR&]fERNFZg%aJ"'_^?e9H,)?n`9S[o?eTd + X.cWZnCQ16&)$%u?8m*]d*XV7p,>RAUA&F6KNZ$@n`sl8PrAW+nqj8.OZ=HJa=IL`V#HWqX + lCX(sB@Z7Lo@i3ocRHK)FeMmpQZY=mmZrid^\B#"K[@M,m<']Krg^QPN"^p"Fl,3VarKPq" + O/.t#LiWTnrJoKYA[iXQH;M?fX%H,?P%3\Om&&FJ[+LE^^LO'Spj2?3clnsBlXS*_!@K6pq + o7kUq$q%>!!5K-I)'Xa86hlmh"W2h#Ym?/D&KDgbF>o*)<\-YKZPDKL?&Oho/]JX@(o%4DN + 7!qJ7S)$>&^id+B_`c"B]Q$iS97_%5t<4Y(mEH;I8NT`5Y(&=TO]6sONJ&#aB9*!jAKWP;E + d=e#m26EUj8!C7SG)[kpFp0F&Qh=;5spk-rgn\Pffh(b*?G=Q,9tpMWnFt[>?Hc*48nL0kA + I]QS)==d[/DTD(UTu0NRJt>O.Sc/kM;;f1>15$bg:,jm'M@UskdUaac%d[(k6=h-eGVE%q[ + ;_jH&i*:.!R0q$P3R,mY*PHg@X=ld^CD"V@u]pQ-_oZ`fA3uj4YJ2A$u5h\'uBam+V1tiYn + k[RV./V8d9qBX^,ULp7FV7"^:gI>DL*H%*i]f1]>i#C^*E0Qp0d[(g1S83t%1qh#A-T:+dU + R?2Qh9\2TFR#jcW-+kPfMiYtmA_D=W5K*r]:jaALh'<^%fP?K=,'6e1WjtR0UGe&?qd/Uc` + >"GlFg9+p$3Je(ZK[-!+6-7.WH(0iD_(fpX3.#BkMk5Wk9+ah#*(mEdmNbrKmrqs'QFnJ<= + [TJl"Q6CMdhe@i.uI8Cnbi4Cj7%`XTnlfIoYV3X*nn'ZBlWlgl%/4DIq#%f.,uhgS8iXQ.8;X[2u+= + 4!udn'WH<;b]g[ChX7A28LQ7QUJs">4DIH4D1qDaRadJ'eBh\>u^#luXRhK[UYP%]MQn + 70(1Q@4N*+:m1`\h7)\WBSnk<)m7-(`>kR:I3/c>k3/B8m!=1b&rtkY"aGD-&,CN25'=SIq + TKKsWac[79`IDkhE"]4MC?R-?1NE4MCTAsukkl#e_X'Zuln3cO9RUS2dOp!r5l;@4mEUR(rDip\pGG,>A,g)@T_G%,"rYP] + gH;(BPlDCNn$NJ>BMj_ObYj9q/(1eZE4BH!WY'&$mA!JTIAJ._F>^L.bZf"G`pmPheqnBOh + Q&%qqp/oJ8a9H^\ALL]+MdE?^pS00=CFMET`-a45kOi?PVRj?]"fr4<))*$*8bV@p`YlO-Ap.kl82(rK8o%eL`*1T$0V?9"FM9fuA&nO>( + !dtVmc*+M"6VDQ=V??YAS[?%6"5\;h7IWcV/Om!9GBbVAj1d%8]np=@'buq,Z?KIICbnm-X + H+2oc5t>W*IAEG5PidGtR]0RCc1Jn6GaC:YC!39sm\;mW\qcoKSOLm?+G>$'&"tIrL*Ms]W + T_)bnap^[dPZMG4ZtH#[)kWJU'N^YHI\rbLe.hZcW#7S]rO"sY'r7p\8)`?XS&a*J(j2)R! + j5t!e" + N?Ta_T"\1W6[;A]IK5cp^_%03R2:'go^#6<*3!:U?s&/P@D)Z_Vsi8aHi%S5PT.ZTNS^8Wg + G/&_UN1uf')";!d#9A\^5390K`NikkRbO4H\>/.%/j\,eW%TpOW?KLTc=0*>G8S[D3,j.pC + kA2j8WJq1DKYu/sk`1J;/rJmE8Asjp$3gLpahD17?Kt3r@Z5,oCi5g3Yj6+Ol`>R$l1"%5L + <.T%TME_QDUQYRg7]#5U!0CVd,o-+`6/Rtm]Q'Im)&4/p@$-nDm'01:BYXq]^Vq)c-[0L$> + f6+k,.Eene!(E#?^P5qXX)+cq9IFC7:"h%.juAoP1HQmn0X!:91D1da)$B+h/L+Tk8bDcD`@fA+52R0E>0?+#9kSRg3)Q7It\8"(`DS0KgYD2WJg4"L9U'Y + !W8P<0NDKjUtJr;HCSN>#nI8-4COm$l7ueGioQSk4!jJ7BFAm10`+e_ifUW:mC1cUC#;:$7 + o#mAdi(K>7.U<.LFE/Ei0;HE9f'!.gbGK0Z_=3b%Acnm&nLto#D3`SnYFemHPb\E9@_VlOP + ]CbHUdp:ACV8,tEGj*+F#dXK + )SJI)XP.'T#UZt55<[s"rrA\pTKWI!8hUFh!TDNF!D,.I=Vil$(NX(&a6QZ]r + AM`,(lsX-1NrS4P`4O^#=)\!Z^MXn-f;j(*J25J6!]lJ.r`'0se6>1V + #_`$TmQ.4>I^R-h8.K8.d_02.8la5hN34/.Va&=qtCK27H\q\Q`(G>SY"Y6a%ZN_'0Ft>ne + eUOsNB#,*MR??5+Dd:ci!*+'mFNiO.9E:g%'\0]^_B-&G\-NaZ=[5/C'86u[?bdoroDs<'"aeeo?M#Ok2;_`C4^T#EI.cbAAhM)./#YM<,mJT5=]r?8$\s!==3>JKe#!88 + ?@F0'Tn$6>tK3J%@!DqDV%K1+(k/DU(Z3Ql7GX]o+/4=0!rAGp4dP9`phQ?&T[m@E?(lP+) + /sgc`o&s.Z$oFMBNbVpMt87<6L:;Tct + !sg'UA@HIo#OQ[Ush:,\A`'GY^>&*aY_^'WjJH<4p42fW'PNe(2o@:31 + @+Gc6Ebg/&Dh!!0-WF(nfE6YI.=ed7kk6jO\.o?)V^]Ur$J6Kd<^hUZ\Uq7aK(`%lG#_*,= + NHjm/2gtZQq$JOC8$g@B+1hp:@3b$Gb^X5BCb@"(hV#"eX%-J'g$EpkDPTkMouEc(G:[J!"?ZCXe@ + ohi74!XeW<.K3('hg6#%&(U][_chB=?Yi5_!?oR&XoP/FJaa7GB%oq^W>'qlFbP'ESFYhRfOIS,BVc@9SeCpU;&PtS"eQR$T6?tBkA,n$1S=cTM& + HVlgNU,Wr!``'TpV[McZ=ut,E!HfMc#!WObKP8q/V[EUPPoVTR6rYgSK4[W`[`Jj$/O@+rU + JU&R[GCD*e7VA;WpWW[$c*a*X^'Z9nD2HocTsHaYllbSL:9Os,&__"t1,C_B#)WVbMe>Q_d + 2e%j`D^uSXuK0VrUs*cL"XSa:']#g3U\QsNt,2=ce]Jl*\-*53YXY_u=HJ=`EmpR)JXu7/+ + RdJ(^F^d%_@[ppVHMYR+rIHpEA"*Xi(.!NIRB\cCA@ieL3N!:2X0OA"ZB3VBap1N58Xn)eS + 2PAWG'>Q$WbkdbZu!it3S@(7dh(/K9QL$%=m=.o!Jd.MBs/gt)e6(] + aY:&V0?miMCMKqm]b-;])]::P!N>."7jq\SD5@4.fKaU5=68![r^)J3!cP$[4\r0)N3r5q3 + D-kgV3.!MPSh#^sMp$b^'k]sY2\#CF0_):JYdGW1Do,R3'J6I]\%rpTI0I7r#EHe3[of:@D + ;*#:@.)[8DlPFOe:4=SsR2;,W&g"iBdrKGrs>M0qF6bH!mM-I+U'1>uA`Hpk*>42`Co*F0t + k^7*33"YS5oo4?:nA'l2IKW>2SQRgJ)^NdoT$@mj9*J$=S9!]F61''WO!7nF"69h+qhMp + pT:XXt.eqT`I6QK2nh-/X*?7K?C#9[Bh+?BS^-AXq($,D2XT""-:_=#&+m[iu2*Jc?hNQZY1,.SZrKlr + \)KCa5^9Gj-8/NhEU'_AY5#H7@=m%pNp\ + bK14V'H_RHQGHd?qe/HbL),g(5G.B.H4"MPqd=$O;AP@??:e/ftOY7,h'GN*[qMMah3=\Fa + N%*K\0*@.7h2;(7c&jc\;$ST:0OTT',#FP_!tPmrFV7oJ_7,,%NjE]Q&nO[Oqea+Ghk5$Yi + rTBS1YjW>0KlXkCYE;<6l]p2gJ&,N3 + Qjbk`%u%r2nQ=RA58fR'A9+)#[S&??biSG!gua)FL-qF<=/fUZN'`$+>["gYZX!NSCNb%VV + Vc7$g@!aC/aL#u_;B5,*)7:dOiQQJhb3mHc0 + =?d]c2sg]A^$II.cnJ?eO3/rI+^i>7K_7I"k9T^bcc24R66)41%76L2q8Yp$P[,4tRM9$QERL)Vrc&hJ`AL?k39osjoek + M]IR21dZDd$2DRUb(Wq7CItQJK,^>B^CcESRJf\747f:!n)hVW!AQhA:J:D;GKMLO*;D^%6 + [lAejQ[Rog.)J"&n;$\RBi7M]g-#Q>ZtiRil3?fCdZZ#L$j*5Dl7sD + m8Q^J27Aa:Ujk4?[J!rBpD49VKQ><%[3UV?GRK\_(U$,Z,Jll_*;4jZKL\[NCl2$F6WG8;U + >XOLFk!YOI@n%cu+3:*49Y*@E)Laiu"@2Db>PK* + sQAWLf*U;V\ZVL,&)&gO'e`H8cX)Dd7;00fL@JJ^JtEo + !'lg3!:cD;+^^f5oem*VXd8pV$lfiHV<-7ag?,6WF)2YJX`22D9E!q"ejfd.R9"a0t\(cQB + mkHnQk`nbc.C]Bt"fkL0&"^NZXPH7tc^kHXXh.3.B%CcN@E"!Gt-p&0<4?\b!SC#rRY@] + ?)RLLT^/Qp7ZQON^qZaPiH=t<$U,=5eFfne1ISB#@'\n&47[ceY$?u/Q!Ji8C)-,[f8PTpl + <#WI_4*XppYY'nNHJ6E7j#qQMuR!oI;;)`--QZ:Fee;h230 + i5ucT@;;GkeqDD]"[cs'%\]+NM,>0?6n#GVU@WNg1)!Iqhn/N+n&=NTpT7"/A*]s%Wg-J.= + %[1<0;[1PNg&/@EZn+?l-=:%i4*CkYq-3YR6)N!a$&4L_1(tCSl<< + lDZWpjF;6)ZqJ@-ene&<="A3lq]%6\O20s%UnE_:T="Pf=6X2/+c + ;,d3V:b2'Wd!]M+2TIOZgO)&:[rO,Jn4a[PF5XmE@(N:pj@/9oheelpD,$W9%+3:.bfoqu' + Bp=2\/#9[)go!T/Tfe9sO,1lDdOr!1MYcE$pfM`HHf)eZ:26-8A7$4=e`LACRIa$a[ + V(:[-o@S9_T5>H=ttS%7/XT5A3u$DZ[jfM(7m.1H-r-24c8R,T/]fVi.gkW7(@]^p_6;T@) + XNafZ=j&LD=7!=b3hq/&g/](h5[8NH(7B0beKd"BV!dl-oBU:I^M^%#9sZKepgG-CJDGOco + 9Tl)*,JpsY2$O0Z/VIZNoh<#iFK.QdmY&.57!SB1ijG%[g46E=_.,uuMPDl^tmYJL!&=ECh + rXJo2MeqdJ$;Z&5''u%.Y#]HD8I3"@.2\"@.&O"&N5NcTnFmiYi7DA.$(tA[""&iD7]/sgYLW&Bj + Kt5G^$6rQM6Z89>1.J-2m.sD(VXB4p`u7ps*XaCA]sbV11IHoaZOp-H[,7ic3!T`q+kID!& + 5.G)2n)6+7bPOu%brc4f9'?@05Q1Wi8n*,WS];i&.WmUu$.PU"7X$6F!&3U\T`rk-cof8%]FX2NT6!t:lgEUjFm](nDWU@`;Md + Im2XfII-%U4c7i2D%ZmI5POlrgpE!Mq)Q2Ii5?FY?76r3*I7mm8gOGNr$cE>+^&OX_k2l3p + t\UDi&?i%D'%+-Y%P4.<*q@S0.JD:J(YV.n%2I)<097IBU!Nrp\5LT4@Bi&&7o>2rac<4B[ + `+e3?Ei[iaX,>^mjX2*Zlir]f<8DU$eFJ)gRDo67t_m_C%8^bYGhb472J"g;-[V<`Z8H=F( + @@lQ3D\3#A@C!d^&`7sVe`%nJ<2cGa>lX%N-gh[ + K9icq'U]MKKlBT!Z+5R=7Y@^;LIb\Rg`XFl,s"^G_K7-AKI0$M-c27u%3gfA8tW^GW&lf?\ + s'-]"0]<%X[=?g__Buc'JuG7Ypml&6Z]1f6aaO!/j!l1W^F>7_?6fS4cp\q]@9'O@][K]3' + 6^k`[)A3[X4Hc/>Zh)-Kne\r[6*rGhZocDe6d$YL]qQ1aPY4S1j%:jZ;8!?24W@+\.`\ + :4e#\I(XK,2]Cfme"=eeXC2-X+`'O8>pgpN>J^$=.]oO+Hsi1qPaXfGeSCZ?[[JA+rk/ehr + cP@UgHkCrKeV$LdhOJ%?4?&/>p`p@S4n7r3WDT<`:#P`&S*-=\1>fU)mL9,PgHum'(H,$-P + a0Y\6_u7@BID>,t[tK76o6N@&J[`OE2q*/i@e@HXKs,QJYChGBS-qTgKJ0oL"&`etlO&7U- + NXV*!&K[&XH8Al*^NW9!jEl"aF+7['Z-X__3r,*XHoA]*#oY56@=q6!,r+/9$%Xg"h7,+dS + P])=p24(au#MY4=J??V92c7b"S^lF/>8ojhfIu6[UK._N;ZCfL$%N9'iam]J@(PQ:"WU9(: + V.gTeo6[@b)kZJ7I`K=B$f[h+HPbH_:=j17VJ'grJE]A#7oeA/D\6VJIf9Jf1+@m.Im82Km + 07&*$uS4\uL7(e`+bd:,5o11D'c(\Ff`>d.)6d[a.dNZk0XGkVnRP70Xe[73a7AEh`o2\#L + =1tZi/Ml4jUgQ'm@7DMP2<88m"D,;7@D5]S5%J@kI,r##C;--4L6u)[9Q8#Tl,P(7a#%p6` + ->&,G<_I0a.rH#XF/(#rC?L/5PtQ'"9Q'FopMrb%2AdAJ`aZ(qFaf)!n76;P-h9X$Ynhb/>q^bd,Y2ZdL,*;DfWG4Wr0 + PXO6[@bB18"l2JT-\gfPK]9>"nap-&D]Z]q5bS3B+"Mr;4_8Jnl]JN+%;oR3`(>:>f9r-N,e6bqlF%tCn7 + O)2.6XLZ,?qqAE<:PUd1hY*$kg!"@:1IlVAEV6sl;!T07kAc&9^Tl[=iZ3`cM[.W,k4,&o# + LCp8.>/^,$@]XKH4Yk5R'@TXp;cP`oRMo+V\kK4dbdBLE`6CEo:fGfY]^\YDo[,MF< + 6ZrC9gmN+lQH]-6d"^"9Oi"@q]++]Lg#FJp'("eBq$QF3 + s;2':aja+b&g'_;+"b92$&;HZW[\X-=d2dpI`S"RWm9\')hY$5#Y_+X(=4?FA"`U3CLhI4M4GC;b+e78^lbHU5c-'>sMSi6/&SHg="-n6!r,?f@4<`e9 + "u#1u$bYh*(\m>ARcnrO>NFqs9^#^j_I5h$%EH*"1*F2M0Z\>t&NM&3'a<-)D$88S.=#!s?' + dcj>4s3RCTRkSSkDAqA#>!AJa&!)R"sMr#V[?8_3Z`T.mOjZ5im$NOpq:e,&`L*Aqd6L8jrO<[p12gXrT29P; + P%O=]dr"N!bS5m;XH*\"28/H$t+34&cJ6+]LBIi7prK?g'#I',fo.G2.5O?i:8"'L(NO4c= + %p6i@E7.MCQS3a',UG`GQJT?*:9YfM.((c(5lLQ5nD5?ta$#7MW?JSP3799Wb-d[M;TH/Ps + t;C"qHB@XV2r"i&T(F(lr:cH[ukQ!<8/nQ"*1Bm%M4*QdQ1p+I.hj$#n3UZQ:'9]:,m\kEQDWc(\SDD\l__;[S"a7$]jE%qk&$;njn.[=r8q%r\rL:e7`0$p` + R4@A+5Qp!3hg!\BOD#FAsNJNHSel["D5LY=ffGOmK[A,_IjY4k9Za!Add<%2s4C3kA3i9]? + A[9bibi8kI%5,2Vd#WWl89[]hX)PAZapNYMl6V<,:5s_/3C<=m0_1@I6]rreY(rIam54@Q` + Z094Qse;W;k\hj435208miV_SHU>'Rkh:n + fcm`iCXk3m3V:%&97eQouZ,]ha!oCXD6Bj:Gnrg)RJaA5R!@j[XF(!Gj\A+.&8l/jqGN]?; + 63U.RnC+C-T84/#e:kc,F"4%qP0,5HC#`-2Z!h^]f_e+HZ%tN#I"gC[,h[gY1&TQ0MJ^>26 + 580RJVX#q6K\/Hj5"UBg/V7E^!>#]j^LVNVH1Bd;GHZ=:Z.SU1YI\Bim%+1I'Y*K?Jaulon + kXo/Ac2*FoQ#]-d[,ISSnR8WQ?'pWCu,9h_Q.:p048@\o*u#+*XQ!1`Uc6b]Os**(=prq2e + R=oB9!O?SBYo[4(0ql@7GZdh*GoP5A1Pc9m8m@,M;W@KC23^mT(?M>tT!`Le[:[kbdY)01p + ,s90Qm"mbPQuerJ9CC[VsVFUc4P7eCgd`c#3amrk(.*r+;%q9O6\^h0-<[7j>l2Xk + RcX,Ed?`0W8"<(+A(bp6XN-?\@G$p9FE%Z`=+#\%]J@KNOPb**7O7pt^p?-dYE#=-c&GHtT + Mcn8Vi%]hT8V3$I!P-b^fGB*'_>M@)IWf/pkKt'T7H]\JbWT)GYd5qod=Pl- + [jSorPs`\Y:FFu/*^@pToLOKsgT5W5)@Hdf_4`OUit`LBL;W"F`[! + ;i'S!RNZ)GhU4!/=[UbS/LOoF'tas?PD%%^YVQcrh(Q3>KK1n.6E/,>_+G5lAS-o;,#&rbI + M!l7-`"ZDoIW)RM32O_TAs(a7Q$``$*X'DjCg8g\XP/h.=/hJaMV5i9d[k7!6-Df/Ws(SU= + (TYgKr6nj+0K9=79*3OF@<"8c<]-\Z_W2Uq/>N0FqUp0l2._-P%:G89`k]8dirgu][e8['Qc8+XH2V'AB;=;Kp\TC]5F7HY%gMcX<>R^YY54DRl',/+U54R&_%%;C$\E!<>Y) + SiN$SbHO9@'(M4sEO"Mk9#/3^r@$nW(.E?ia?K0Ze'rKu[A$P>e3N*uOt0jD4>#bfIN*Q6@ + lAKM),(crZuU7d5\8!3_7,fu2@RjH2)N1h]BnuSZ<8s6ip/Ba3p]e.Fum(kF#;_5gBb`bH= + k`Fh4[3@':=Ge5nk?5HISVI(44\mlhrBARAjTRlqF903?;!2C'7F;fd'h;,cI3*RYoV):X; + \Z>k9@J"!_b8JAD*q1M'eMjQ<0]UH<7R7&>ufGJ%6I_AQ)uiGdSu3K86;)$*CABm-&"V:F4 + BS";T!D4fug\Sg0qotbY'="V:+gUBK!F<*;Ogje.hL"L + 6K(ST<)?Pfj;H_9IeZ[Ug+p&Ta&==LM(i9*QCKqINl%u4Sj^*AK)pm*Ff@`GhqN?$U54FcD + %!NV>nLGhYdA;;V4J)sgF=-htdH)Hs?B.dtfS=.B9BclLbH(S`[re6r43[+KBkdj6EV!04\ + O/EtDL89;C:_-eXIMb'WNU*Kq>P@!`sJ6?m/&]g[I$HjRBc/B#337dk5gp\meaH_^ + $eGdP^i/W#62L.L/.if)uMG(`Gn@5rblf3LEFV.-m?6T+D;QGbTU6V&;)/i$6dfkh't+;,D + dLAO`cL8[ZpIl>$]gY*MQ`-G6u$+T(e*2=]KQJ)aOD*a00SBno;,J+'6p/@8I$jso"otmgW$9_u%3e*dK@(7.!ru"#+oF-R7oBQlL + kQKLE-Vj87OZ7.P8Bs`?]7j:1.DPo(um_iN=ig&7Kb1Tn-(K@YG;U'I0=fT\KFP3,lfpYXg\B\7C4(pF0TMAO:d75ETBlgDQ2&;fGkB-6.c#Kg\fM=!^;jUQ9J6C5t<;(J!9N$Znd2d + Q2D$7d32]Ffqju3tbOdacj-0:>Oag_2#'eWid31,5\DCm5&mEpqJcUrEm[F23kg&BHA#'BjnqQF"WAu<#rRarp@S5J2\Td,*iK@9%>[?SGH1*ST](`AoaiQhqjV?=1E + g^_#=\C4t_V(4&Y5Ve9cFRrt[[)ic>0EEF/g^c/QaeK!9NE6ffP;"c9OA-83M+7D"ONSN)fi%BCmB]5>bs^EYPP9K + $.J(F%<<;,`MMh"H`C[%Sm\Z;]T2VbMJZ@E6XM-a(@l-R;d1nhns: + '``T9F%66YMkST6h]fqC@j0\@_fnqU(:d:JM\Fc4h;OUT$2$<]lUXee_E69EGf((\b@00gt + Sjf>o;o?Jedk5;M+:Bc0K^orTN,tVg\$iotU7a!*?eeJo?JDHoIVd^%.Kd?V+6IYOo$5]Ll + bBR1`_iJ`;NBjDY=LFn-1"c[d^kT# + 9)D%/bd=sKp)5WuFA5*UP#?FOX)"m=eqFb'/Cj'4l:+@BGG%Y8^Km_<%:2MVQ$97+3/"MNd2ItnPGi=H.N:))ZK%$^0$i_C] + WC\Znj#m'HJ_!X,_DE?he'UULB_9PZi:/8ZB/EKP^jf,->$ohd8B]a,#`6:iD0NdLPaMghV + SW$ffXCha?LrS]ICdu:^7`q@+PfKqoa=`pT&;Y8-V$QFW#q@A1Co%H<2$rWNUT-ZgC.Sc16 + HcB&,;.9<:q4`.aNVlNmE0&.nAoe`jEHB0m\5^XWdaCHoWfX;mu!(mnEB@3U+Jm:HS#0cd. + hsK'$u[VI#$"JXVC=X)QZ^aXH,"H@oEu>fRI`m&h!b/WoDLt/2tAQpkT^`iPlYN[^?5R!bb + +>0@]q2?p>"tp11B?T^E4r!u:9D!!jbKj?,g@bY%sJ%C%[e@)U\p8k&fF#@M#oKO!>OTSG# + l"VH2U80"pC*3\:D"m@TTnYm5MXVl,^)R6OdEO?ajZQ%sLrIg!-s6o1P&_N._ + oc='8Hf_\Ae7r(k&gju4QfOMkL)c^/7bmZe&/LBp_6l40]5:8I0t,meC\lS + =\ZjR4+1SKJINjU + 6).dOH--kN?#E31OKb:(ZaF]+CiE5O,VALYjR%.)j\;PpAaD(>&2.mg?gYj3hKpFJq1&$HJ + CoSCl7`-eqRd8:7Omj>+j&q2UqdPBrB;X4rfh_-QE05(e2BWsJ%ru/]H$p^Vt5cP*CFZ:)^ + NO'CFo,-p#geNio5i#[I^7lV`[:>k:N0:LZqeqYCq%`$%["3:oOV + ?j5E(SNGleJU>gdo*S.;Y;nFeN$Pc3B3g93:j[&F0dqC9f]P^3g6C?2-Eq%>u^Ji3H[noC[ + W@'ofQ5o!NZ;aZ3MLneKD"R4gWf%1)F'!ioJN25'-$Rlo--V@g)Iu5I*3OY&_K]XD#`)#$& + &roF4)!:?\p(kjhe@e]>fG,7&B=-8[;$CmdX_-HXb#6aCt&bcsQ4XXHlQm#K)/F>r?T.A'P@PIBa0_\*nb:KmsHd^;4o$p$B1nrhkX%l#\/ + dn5p'0%O"N9&iPhGtV!h0?]e%JOU4ZFYZ2`)Za+T'g2D:%S2i<9gN(tJEot8@no<`&>7npT + u%Rr/@RpD'gWd:TTTOWLA)Dj)qKIjjFRKA4"kPrj0W>4]Dd'luJpFZbq9+ArYN$kHH<&n' + 7J"'(C*cokN*0g%F:m$YpfFEU8,uU&_6M1s"96:QWQ3 + [0mL,`+dG2J%AAVR^7J@=T+2X:B&'6+fZDjLiMQNN&Q!8tABM)Pj,pjm>s]2mN&MC$Fg1XC + )>$!6`KEnIc_l%`C84'=<\=a*4W[mXd`q*/b6N@o2qpJV84='KWX>EV\"\EO5PG?e3`>dPS + !e^8S;M0FoD-Iau!'KXB:koF,_Z,$Cf.CXeIC;LRD_6Dr:D)/V9$]Y3\Tqc^I1l;K98./e,`a_fd$+ + oNYT1(G@;d4_iHVma7@2H-e&D=.Q5HOKsH][/$(VP269(6fln@;PaQdJ@UeU#=u!;tNu5o:6@6&ZJn#u@3/S>)Ga@,;9p]S,KHW- + 3,!_.41B&XjTIh8M7EB]\N>e]8AVcfokq + ?$UKTII4/n#s-_\/W])/n0]=(l=0>%+XM@^Z:43(E:AY^5/M],$[]@h6D;2`"aF-Gc235ot + XA%qG,N;#n.dQ)FgDfo'd,YVXECh9NP_2e(dG.NH6pMe3BNl`)aGI[kB&p(%t[mVdJOWk<] + $;6MeMlRY`P#PYdQ3DS\_-k]fXE(AD)*+fZVMbJQXd18'fh\b+a*b,iI.GUdbn"=/(RA8!I + Ho&hQfXPMl",)+YaKif3IW(/RMD,uIuVE@1rf8-a12`,[_qdEBYe;3&^*T#f!kN8eHE?Hj9 + o@`)]$4'?q(6K0=G$.KQDjk#S7)g/-98>(a26c!O`a%-%Wi?[p6nq*=99I%Cpm!d(L+>j=e + ']]=Jn>]KIP_];q[FWOn]U]0.s^]fE_G.ree4hp>"QF)hi$7PF4)@ + ;Na&A$D5LP1VNn-qptK!h=ho:CC4$JNe/)A9Kj:#@_7UkNr$.:aQX1X^OKb]8A+1=f`h/cg7\3 + d-B!$$Q+',W3PTVR>?i/#&-R:L*pigT_R@>9@*)$*MN.s2V9fGCl&d@Q'>IYm(@LZmTQki> + =__mfrEE!F!#e=@tBh*(&(l-k*ddpBG$SupK?6dg>'ln0a"bQFX=;:Sn$eXPb"i-I3NjKP!pL`Y,p":7?O1gmZ)$+s7#H>D4b'Ka/Y.qk[ + YmTp@Ok`51]!miA`Pf@#c'^:91hc=2bJh+uh0RV9>\/:<_oC7ZhKnG,D%!NB@.G6//_^"m) + Z@aZ.Gu2.9)%erIL1Kf%Lnm=Lmo,@k2Oed(,D"\ + WqE+G'gD`q<`/k(9A:OfS>7A2%7$p!nR;r8X9effs$*@[?BbE(p`L+@@]BS7J\bb+GZ5 + S!$&6=Ud27O3*u0W$U60]3*8"Ep8)t:#'TPF@)iYs-VW=UU.[E`hdhuuI@5O@\AM]*@l?PC + >]&sb6r"p''8oIMim^eBQTiJ'lWg.n\B";$I(%Sk4t8P6dY0a&O[uAP7C"..K46Rd9KX]7F?\"(WK= + /NdmP'ZuI&*1!,Coa2[Is+Wok*M]t^?/<;)K-K0e*8^_>PD9:4rUg_Eo_7(r<;NZYcTB=Y: + 0[(1BK.p!=eBp,qk/i04:"*q93?':BffIEBZ2SQ7VSR[D:Xe\15oU!oog5,>4VE75jlI7a! + FSG0P#?Cu.Z^$0JMM_DV9Lqoe!0M`>Z=5hWDV0q/P]O"[99J0e`Zb*>?3VPAQi-sR;5pV;l;NDjg>;?@SC;2:6 + L$2b@:rh8XTrGou0g>C'W-XmU\,V.K@$,D+`'JPr9\dOV(`c4Z(EGX`O$T-cQl('J%5#E*' + OfYf3\VGU9Hj7lY@LLbZC/oDF-R.;qg3A*[cA;F>7#D'D!C6+nHaYfCH)*(.=f\%0F)K + MNSD-OG\N*GkPL8l1oTQ3t-EnW_/<3*hf1ig>X!HPp;[+-L/2i#%NkX0"&*&\8GlHCqG9e! + WZUG=@E^H?FRUY6pTLWmPqEh"T]u:FA;3d7bPb!Hs[Q,EVt#*`q"M4.EQVCa"P\XgugK=o6 + g:"cua[89@7k;-VoHV,*q4,"p&3*]Io,*mFlaJZJXIiMar*!pWH?bi-9\MMrAp;-]d@;X*8 + jP/\SWggA5TWBBs_7(UCn3C`=G8Z$uGa`A9'W2=g\3qaL/UD$s)429h3BpjN[?chb+fa7K3 + g5N@P!*5ds??ieG3B0l\@;mLPl!Si6]IKgi:aEgWM'0PHfpq^VtHk4WuK88Ob+6Yt?&0rJd + :mD/M@D[Y*8eXN^;AULT@"oh^,X`3o&Yb)#M:#3P7>T52,8=aLL@RGsE>9UkplQ0J;Ed[/+ + t"YCTbLCf<(`!b.j^FgQ\O4'Mi!TM-Bugn@g$170pO^fFRl":_L#'MnN]S:#+uY[KMcJ56n + %.:U_K#C;Q=/".L?*QeG?))MPX?EF^61+>.0QNejh7$+KnfA^/taU9.Y4AZ^,(HP=nYH/S2 + I@cV..[<\qjke%_MZX6AeIP9ga*sj.q@.'ar1,54H"AI@KGsR"_N+BD:9>a1PU$*h#QIN_H[`Ti%mR/#@h9`T<(3c<AdY*W%d@7teYgG + Yp8nnffo<)]AqjX&Dri=6@3*#[d5M-0]?kn8S:`@4'=:o"MFc1f`D?rNV.qB0D)1q?m%`,Y + ;J.MUJ5^%;ag%6'fiES!hW'2&l]29!qZo!O,knKkV>ARaE1q&MM'gtH4DGfal89#[tUq!"\ + DA5u@[h^KjbE3Q>BG%66ZNcEc7.+K1V;+P&/ndYcVTsiZL1%'-'W;WF^bAOhi6p6HlZlulUg/aAp(IJj1B9DaOj/`j9cEC0T/2 + *7[Fr@6fWHDD6!,=`OnLP!5!NIaLaN2$D04M#)aMj7$%f#"mjZ4POZ=>u:R73k + U&@<(jd)a=SAVSPT655"_2pB3j&cp?54pE^;b^$fb;`t/RF:NfI=r0M=I0ZKg2N#m#/sM," + dbI,2+TqCn^t$SZm-;hbds>o4<2Z?fFVRa&Pt\Ju%8j%Fet&jfZ.mg\Y?1C^@Fe53ZW9]E> + .)iCX];i;E2\ + h[42K8`MlU)"<;@_NKOqpD:iG)41X59/R*8;%tDu4s%GSEtGr"e;2=L#_Ph91 + _c1?`]P_fY[:E4XH!5"=br`abfgj-P3W.*2(;U\3f38s8mS[8eEH4nIcmf)$uN,pd*B1V]: + J!(k_AJ>ECE^rqM*8tLTd0QaC]r>&SqAaM)G;ZC[b6Ome]4$1DH8I%N$3iOf4#_:j,&0\57 + k1pm##&pCN/>stJ^aoW@^,XMcQ>^:$T\bH3]b@'['ET>Y!%96Q@KN#fNBpMgEMrp,+Tr\b^ + ;_@2OLl.AoHAip>:tKj0a/^#)1oNCtCI8.dk$eaM5s + kGZS+_9Ng3\9sB[p*s9@s4t)GINeI&@0kQ$Itimf:h5QXd[q; + '7VUVhSEqP/c6Fu67FN*.C`:9XuhBSb3-Upk`_J"32/74ZKs4!+qujY39iQ7qX?gUOKTmkY + 7m)kdI]G=,jH-$uFK3IaDc%( + ^3`Qo!Zg)`Tu"+hPgY0d$Z&-n'Zd(PC@cL(I32!-&1@9;n4:)F,KskR5""\3"3Z"]N^Ce$q + R2a+o1Q"TTMCBG.qu,D!oPLTk6A,=R4Z-3=$/#nS<^^_a[T2MYC@"R3r3]01l[9nWd8!tZl + 3(_-PWFT@4&!*hsG]>qRAKJO<8Rq\:hJ80d77f^%*!.&bgi"uJ[.mqO)7XTGKjEJY/GfQ>$8$%).UjXr_3A[Bn#M=)+j&s,HYJ1b?[+"KXKS;V[g + V1;e^_L1/MF^EnT#?3$b?@+t>KH$K^LLSA$hur5@)%p%[OdY$6i=AkA?4Jb2JD&7"I + Ta@C!!BnTMu%P_T+oGDP3d2/>I0c]V.TZbs8=O5f='S<":j/&%@K/2lmS,,C@6^g;,i)b.? + CQ1>S[/J&.$#3$]D%4<$7X/O@j@6/a]2S0'AZ+JWb^0%5e)PU++5N^?ts6>DLsH]l7(PGBu + -_P('lCAL9>84)@*0A0mHZ:u*>0tGa8b^"(dqZS9F26^mU6ZWROKK1`f3\\IK)u=rnI!qOK + DhX9FJCk50P][fUI=MkGJN+4=?luWJ.^:riVsmmnO?A6\97u_#E(,DP:9d,l"$e;e- + m+#%'`am4)g[L^[*ch1BW+qg + h*`:0'dG5laI.*oYFG?J..kC0]d]l>!k/^eadXD94p0d-*54b0.b3hZQ,G46KImrAR4m$X9O'9^bV@=p1Y[#@PM[3 + #"#t'Zs3aBs`4?H7G4AkpWL?M`mRkIWeE\\p)1_g7c"RFY.MT5itJU>%\a;4k6:B^hd"NW_ + .20.=%UBD\s!l0Rf>\:Y!G#q8F`8(.i4apL9Mo4Y&dKPG.AVebDM:eqe=M9So`JY\aXFT;9 + #*<4.'e]u,,hXaqd_`08[49j8.TpVR=`N_07#Y8+/^jTe=W#%J"e@kp1.`KB^RP+($Xp*L2 + d.dm>!)bmNB'q=R^T:"5Y\0f*!JT=W`/hg%pTEEbs@Eg$WK!e/JP&2<2V[/7p@OD+l0M)U_ + FEBX?g)-<6VPqh+kI;:K7S1/lbcl1Pf$n[FIuOY.[k9I90F5\kB?ugc7mT[(Or+6-Jq-06P + ?:"j'dZ_Fugs>eJ#+6NBep:Y''seoRM8PVp?oUG/6S-)GE=?#<:;F\NU1`^g`?7>>*<8uBe + ?cDr"Sc&&dR;U>[f43/:j8.dqR>WpFaJhS3NV<%[W2M/FaLqS?eb\(&!MV'Yn7,+[B:`[-T + jkZ:4J=m1'#[.A=NSc=I?Tm[]2>#cpoj;S04jXPU10k'N;3]V$;XKo[b+I@P-%Ke;c4NY(WQFNk^i + gDqbs:";F?mOCTgY_V:)QZ?VV<:D/V7"gK'i_d">CB4**P"-Y.*E@;b9MaA.PKQ3[)) + ^Tg\U7r`PE6,m`V)\>X_eMY[,GhLdHMqoMK_Z/EAc9j)a9T1d=%*Di + h#M,b6>s-Emb7'3mVcN3XSAjJaYS^^7656PC^")Ef/Tp+LOYTI+hEBr2\/'W<%gP\D11]bQ + YSD^96I/1TJl!OE^l%!+F^CUD?,C_p>jYi'&S17YRLfg\fN3`kpLIk0jgJE*Y$GVQ?00@<1 + <=bG(f@2-8d%+&[JaJrPL==M?l!E_r?u9+roRO&5;m%abqE5V"s6;>dk1pJ:'n\D$V + >31;g\OJ?4P\$&2pi5;p5BQh5W#l'O&18B=i4D26NW"(VnJHgC:EsQ5`eN'%Dp^<1;Pc*Zl + Ra+A2&F/e,AN.th@p]1Xr7ia[f(F]D(MMbB(k(?LC2$ejPuS7G+*/ + 7:]I\\.erD%>Q?9Pgd-tBO:o6+_l?P]/EMmK?-l"H0UftlDT9?J0Jc8ZS<.XCA+lH+CmG.hDU!!(c=Yo;m/8Zf/Y + >7dP0#=rk8"DaV4uFV%9c5D5Rn]2a2]+c*1WIZ!jBWBI" + 2>L)erC*T>irb01V\";b%oLghRf>HAe'7D.V[^2E?cgZn$4Ktu5ZmGb4Y'L%[h`[tmW/_jA + ZKo/+T"Md#uQX_Vij#tTG:7=NRc.5qq>kU-29:sdg2,(HNj0Dg&&Udq[U'>:2Lu=>/7_slt + @g)9f!Itga#.iH!W3U$E)E,*A&;hD(j8pjEJIi9@\8VV=9!ViK"n5=L-OlOs!mW2NRGSl/2 + H*KA&"5-_Lb#[ip`FLE@fjFj!S]SQT+Q*N-$!nFj)hcf,.mM/+Pro.T#7VEd?kVhR:7CHl1k[FJT6%4UDNOe%,m-(SteKhKhp5JG + C[Ne`p@[?RoVNBRb>"ctK_^^=Y=>!%a7" + ->*dAuBhZJH;\a!>oO)84K'"*2V7-j8U0g[i^F!:]pbU`!:`.*:j.q9nV@[nt1kbB.3Q4N; + 2RqGj`?MJ2r5`FYoR[k3"[K:H%eTQ?-E!o1ta!I#[L]2YjLno8fC'qR:OK$$3fedq3Z/^3" + Ga%F^rE+=3oPQU0eeCc:,_e)oH>ZHHmuE_8!cBAB:7"rPKpim_Rk8qn;Q<2qn%[<11=o_#^ + R%CkC5M!i5Y_8fL<]X2fe<::7(if9)g/0ipso_#Fomq29-3dc[S?-DTLl"+5jXMQuXR.&=g + p,8B(]k-HQcfs;Xbc;"C`R6@j(gJH+2Vs)3Qm2hLE8*2eCe9nHrsg_df^#e@g*fI + VV6*V\G817KWm&*s09h>%kX^\HqC=XoCb4UM*A$3@G + O8Cf/cY9gkmFI#__W9ggh&l&XNT0DPBjL219E#.oK5redphVJZa+hRmZ'`Oc.jF#qj`4LJ' + 0ZA`c/u@#r:oO[puVpmWI1Np@!LKi:2&Zh@D\le*HWjgPfK`_r("\2]/.T^YA8XD>j + L11nUtNm7ZjBi)+Q$dG93C=Z$.;21&r'mJH,h8,frqhR3p%qR:&]1k+QcDb-Ih? + >K\eTW@^c@q5.@i',8>=bf6&d0MO>hrb5(_FP9nn1RY("::"YHJ!r1;pH;<+Pk@9[1_g:GR + K\Z[6rBGABk(QRJ+nbcs`-".KJL1*VAMWIuGOplqmM%V`TB9@jDj4JU2)[3c` + H=N0"/oRECqAoTdkEY0;KBSW3YZ?nh+J9m.pF_N,f:Ym90T!XQUf7qp(*Bbs>$f=Tm(4=`[ + -\f=caN4FG1j@m>E7sXDVLcO]'gTpotKlVrqX?Jg^a_-nCqnu'ni$_RUVRo3bQO2>aeu`Un + #"dbbQ611!T=Fl6M/W>n9J`FM/bjdrV'e5LsosG@AU6]q8linnHO!h"M-'KANPAGCZRc8lp + bGpr]&5jJtIJ[]fMM(S7o`.r*W"qOY:am&k-M\@o,*'*7R+N'eZ,I*?NUb;,$kfClL%"$?; + W%4asFrh#rer3eiE?iD#ldJo]2Ec0eY_19Q)".Z][!oQr&3RBqp*Aaj@:>?RG:gE@Xn[s-C + ,TT$<[L^4gfR2X$nq>'5r5+ZmIuj.(Kua])TRjP(+efI$aA!%0Lie:]j?5kLr=Hm]7YoGS7 + 7FI%'r'2c2A*MYM9,3-72V^4&ctCWd8he+Ujj/7Zs8MV<\KAnr\\1"A;kMo18RjJQ/c=S98 + RsS-+(i8dj0'1;;F,W`8I3Hic^C)i]nmE"J=0PKhue)7EC`/,1M:*;G(+`.ENj4Pk`R(Wfg + %*V,8T#e='EQb"sT'D(*D=[2G-r;`Ih^.cjX3?d-knMEqFc6PEZ-+G:2S)0FmY?krf/4Z#fBr*V_cGl@&>&d=%eN + Kfergj_5@8Q3:7U6dWh[]eF^=)l)RT,XR;[EEHE]QD`*J;OAUG + 5`nNIU!@UDLaXfo#&#<5a*"Itqr""[[eZ,]< + ?fbcFINpIR7:iPsXPKa@DA06K&ee>nk<.9nUs$5/,(YXNU%,niVLN)>^_h`\qU'*hO8=J=% + RrjR_@PL?\r>\6OI2()Q=@dWW/&g_](ukI%N;#Ll>o;:]/tuF-.=UV0W9BZ0[!>A[^@3L@r + uYbYI([St6RZ(J,*Zo&;HLP&WDtGoeEup-P#b5U47oQ6jnt>5M-hIu-c + P#0&#*`#L2c]#6Ak7;@0@JM[h8uJ=<[j?KFIri49beY,6NI&>Cj"n$@s25"p&fl"6m*)`5$ + [-U?G4)_9+@Jc3U4Jq]K::IupR1l5*oK=:!:=+TkPCS7*##"!CDG5sQOp@!iB6:e1gV`_lp + _!#^BmU_&*eH43)-;i"i<2$Hg9+/:5`j;`rFft.6U'EWR-=jSUP@&";8KKt:)B0O/??j1A: + &P^%sQ2mj5"@-oFK"t#E_0B`&_&Q&P@)ak8k(fKNK7?KGSFO[m_;ehk#WqAQXeQTRcC + Ah^77VJS01o2K%0U<[=m[K'$>EdEnX.C:\Ualh$[Wdc6nUNu*QAFVO267[<.u + GGhNK]A;'!Fn&@Ra\?,8l$dQqr0+Vd:uD0QJM8@I^q.RrCC]I-qk%lrS_q&'T'P81_Sa[Qj + #m(n"l?m6I.3^L_g%d]iqu<*nQn/qGkJ!\_m!V%#p]o[?n&9bd6-&FIqO'4/[G=%aRm)#e9 + Z-eZi(]Bg"T0Fq\Oai4eC6ugL-mqWiNUWj3=WoLYnD'c1(HZA).Z>G&Hc6>.9c5hODi=Dh" + S[lbCu!>P>`pRem&5k6%qH'#D@I'4Nq$N;U^HLKOm/qY6>>K4fOgruNomi>nWka6qAqpfY] + *9JJgc::7-2!)$k2YR3bC>_nDJk"lg_.sg@$Ici5;k>3MgNdUuPRcl8rk\rBBW8+tg4R#ME + l&3,HNgu/*TY,5A]W9EEkmms:$GDO1;,4d<%_h9`DSE1@P:k"mY^5\3dT0$H#Ys7I:E9_e, + 7*ds#nWF0:n%bFSmTeBV(VoWOJ*!-Ws]KB$W*r5OL#>![gUR"e\Ja%pK7nfY)#X^_rBmNhO_Xj?.'lH + ,Za"]6:[[8(nGc$[s,i&sMd5?T#3AKj)+6Ca%U_]O)!5U\!R>HBYTcTa.d(>s\P@P9&\A-b + ^B:Fd"4!!fn]JI`cNLDBNp-3@:87$6XQn!Z*_Y6/M%^)R^lgBjlu7-30$PK^!=0)7#]A@]& + B,%J%j9:6$ka@"&fDie;[Q4f@+$,@OrYap)^>tG,;Lns;EH+d5&Y8o'dbO6`RL5>!bGdQ&" + r.tcX?GXq%$<^-8=BgnEP'mB-n74-:]R4dTY@E!ia%n&8uIuViMDZ'.FM3p^_og%u>QE-EN + 8ih6#WKa2\ihT+d9\'?B8M$L[cn:DAfk'WV4a-j';ZcMVPn(%9F.O^s%3eq/t$d<+D + $0c0EOj2:TO&W!hCAJibq[IXTXO:<()RYWAM)=)J.8=(6TDR`Ui)\=G;CR#j>SHG'2kjc\5 + YXD.Sd-6Wcl3,ubX2c7m=HJVNlO[T(Z0maZo"NIj#>=^pNm@sY!prkj+KjNG&j7I\IlGi/2 + m\9!%f%:tisM!,3)b_qPF";YPr]SJ3]!Bh7eR]#5S0e@"`q+`@"JVkD^[EZ&+Y&XnoZ;mG< + HVc4]BTgPL?QPR;u70tSD;L;h*U2u0e4`<%X\eV.h]*#c_h + HK*]*B$I^6j*caso)bi$lLr00)uZI_Iidk1*(/l?G;0B!^(:D])UZkQQJ;1UB]p9dk/s3=7 + k/kkA"?Q!`a)3/#kO;0L1BXcG4!YZd[;^VXQ.g%R^*"/SFYWfX[6B3if*:lT>F%ErCY3N5m + "gR5J;cs;\>`oKX+%[n'PA(9.D$TV3^f8oEPH-a/h`>l>:"89"'<=+W^H".6:K6XUPj(unp + J:GY:_a21+Tr5SY$uq;;+=SkTF)_`[Pinp-jlA)Ed^i`F$6k]4k2Ji9fLWtLNgd[*28_'u_j&bH4^Eht7m&LtrT6#WG_eU%(V+VYV9IgZ!&El@k'b^/p'f?X#!1!GqUn#066qZ;2Ht?"@;R?!*""noQ^HoIi9`"M=cK?aJ2;OG=`C + W8PY*u;`RG!TT2i?1bmL@FKFaqkqr%@lS)`4PeQIfD)O9b^?%LOV+jYOd7mP\9p9ctPlkLH + .q9:t@ob4W'^h/:e&qL]r'dkCE02tll;tW.`F + ZjApO&S#i;RK(oF\P'M;%W-*u@M*FGX;2hRP7c!ti4QO>c=Qm(1Y-8i?96ERb3aYoU+,2%Wn`Q3eV5h+SFMlr.J(Pg$m + c*g:]om;mCUQ;(pLDg'`RAQO)"(-HT&6$F,jG?9EAQ\Th;Km=Jl=i%rjJLr/0-m%!7!B6le + @%%A:K2iP@B%+_32MG+2H[HTXg)%GMQ>o)i[bA3sI8fHG06_,nPA/QRH1n(8:!Y:(CD)r:< + qI%=0:c1(lYFqX/$pHd;SrVcXPf*^W'&M[?%8/E-El[o7^UPZ#22FbrGMMLX7n/GL)70?\49cWTm//iBHaGV)>Wm + =Q2E>;l3m.nouN]j(h]E-92Tf*u!7I9e@I1"R@8G[DBa@n>+31A<;*QS1q^1GdWALT[+fcr + #TIq/fG;2)e0Mn`t:[!71`M!R&p + KpY=Pb.YfM*DjN:\QYn`8G53f + :8#j+PKUT/9=AMGS;le<>85ZFh8?&$miJ)L:"#Q?bg=9O)G;JtRXnfP&[J_q]!D>GlLq<&F + Z0e_tdBAiQP1CS/p*"TUK:Z&sZMQ)gHS*.*571O1+j'UfCQnR3T2qH_hPCC,n!]>D]l?De + iK=J/LX=g2X[WF_lj+PnYeHql$a2Xd.N[J[o9hqn$`lQMt'VrjMEu)0pkcql4aNKNX"6>M4 + \QD8#b-)Aa4(Hg9cTaW"F.EdRSm)6JZTfpc1$^mK\U;baE^M6>H)9@.=fo3n1NqjilLMA@F + o>Vh8M=4J9jeQc8-/R?:a=i?k4e\,VH?#.u]D8`.A)'M`W7P87[*S;?ANT!+SL5l`DapZ7R + a$#d0Sr9RS0IT^UO-;.GUn;\mht"8hn0]+]KGAiDEH.o\^*@bKJNe3pBdlQ>H<,0d!/X=\X + RU9mrC*8L7,$C&]L4.6,3l9^fZ"lRcD3UZb\O%S;t"n]f?:r+&#]M9UmK6gafTl8Y.Qf2gn + KR*dcK;J(C3A@=;HnI:MJIO6AckkMDKg(pk&%*"KuK$i['V62hpSXoZ8BARHV]^`"8:+[98 + !Ug9VHhTP8WceBMgm+n/tGMUBQH/'C5\/sh0IE""Y?\uCS`uD% + bG[6TQ*FW>?2$`"iSWD'c5m)5>j3nJ&#rjZ[/8.q- + TWfJ#iEQY9:W>#68(DiJ%0cX0]Uhr'l\Z5mS!MhV5c`gKrG/T%!YpfDZlHrHssdTBko0n(^ + !W\3B;0*3JZR!7HA-+AbTJ7LofF_BEjmCd&]_LI@YP8@2,H'oY,?n+bsi4!0SUA1Zs:RDgS + >5MS#.CpI`Uou:A19@r999Rf:0MD!O]eW8;/B9P@1IThNc*LC*2GNe-j#TpK[-'NrZc)T$: + UA:F5#;pc"%#h+2$HBEZqV'";7'mV7FF#8,r>[2GT$Q\1t=N[;mc3WF-e0&+\C1k,u7-52) + SB526D(-=-_<_?s!%:I:?;9R@I0;2#Wb:"SQj6BHQr5VeMQB[@( + i05k$Z?T4\b5J"4Nd_f=I5--pX<2_EX&SG+&ZJ)2!,hg/TfH*;JM#:fUc&q(t(/L*)))bEZAB--tJ*$Kd + @@:`1U!;UhL%JY + A>Xk;NpIHIa4F[WQru"VeOS7\0tRELo%'iWg'M5)q(XtX"(j`^FP3D]X+\e1g'tf1sJq*E,,O[7rSde@"+r@+dFiRr@(@9tP + `.3,TC\h`4FrL?,p@O,iEb^/M5s4&5+MnG4dCVXbY/9@rMo51AB^^E3"8n3=Z$rr9c`1_]L + Hn4Y#!6=4gf$N<8Fi=Gplp[8d6d54c978\.+7OYee,@kiqdHh*.kkQI!BGco*E_M&+3FT"& + .0SS7`IdS[$phB\#@PatK91Zq65W9"iA,CNb$f,'[61Bf*$p87`Y_'&:CSJ322;b"*CFLc% + Un]cLX?;K6o]Z*!q8hfn?HR2W0jt:FAE,27O9Uj:*kQt.V!I0O%#p1(0IqN@^1WG0_Qe1@j + G\rEMbN]W6lG!`+"_RDE1/8K.f(,Ya:r'+%4`V))q)AF`_Yp?(.7UNVq,.Tj1%B\l@L>&XX + f_73PY03g#bEK34jQZje;99YW^'-tIh[l.]mF.k?F7VFhQ9^/*NTplP"fK/%jBq:ZqG-cnX + S%6Q,h@%,i#6!O$M:cfS1Wc3N:$%q6kk'oMp,o\ClofF6pr6L'%r.P3i_eL%(k\rTd)WI2jjl'roDE;0l)3&]BKZL_7?=l48%5XOOU:V(.C + )NG?G9(8P1c'1B9tA")k7bG?#4:8/pO>6mF2`D<`9'^HS6: + a>Ft1KOL"#s0a\C"$*>a\`8E;q#*6kA:^"hmE$c\8Dr>C,Z_Y/lnm;\ + u!PT/QcC#h:&seAnXmp.Z#c;T.rB2%KZ:CC`@.H,[CX3>kD7f + ;X6o(B3QqnMaumGK5^JDVCPI/"n>+Mjn+n6H^`(diF1>UN!Sh&bDYds5B%7u+'%+/Lfl&O? + [K^N(qmi#*2Yn,.0^i\XPA[2s/oY2Y2rQcFN+&NnMI@oIaguF14Zb\^npq\IkGZ + ?@T<8!N+Q[$Y\V+&[_LK:.c#,C@l)>P\lqXgKS:EAQ?:hg1O)m4%\K)3''&7KITiTaW + 0j.:]NlD>k&.!>F5"gR3aHO'r&2-U3ppk[]mY03[u[&K%,G77Og:m%nCbNfGs'?F4`A=64E + Kfe4e8m*[cRV+PT+T"T38]`1[h%kcZIQQBnW,('n<`'Gi>/O]jOSV`#Ij)@QlZgr\qgl*,j + m8YESP.#7KX.GDCS'WUi<_cMG:cli!HdpAX\)$pciqo!"h`GsCI6j4Er)J(_u_^kiU#'DZ` + 7l!1dnpZKs"7]6%`)t]G\?1!+%I- + F3+udp>?ca!V2J]T^F-l/&j0F>&!aN`u4pP@q(oBV'k&_SPNN)Lf@M!%8O7W$?5Z4>FNYKd + ^_W]n,pe9A)7.#edl0[sQpCMNC!dOuKZL4pua%)h'5Wn%S,T6j,`m'L+u25b[N-5)E"JS3k + .66UZ#c`.UTJPXiZ^8>._OKh+[`+9B"\)?:1AL29]$6rAVj;Rom:J<1%lG\`42!C_)1`Ic+ + Egf_DY>-i$I`I%YSdQY69"%DgV\mtt#g]k?:WBc*7^mdVTX/dg/B#)"-3p[9E'"YG9CHZn+,1CGrM88:tb4o@'WOeAEJ`[h/_UF>TqD28,M=4em26bW + )Qe$q%#s4oCP^b7MB,I^(.j8VB`.PY0&nR)uDg`'d"XbXKX\T#mLp8\C_="JNs8U:;m%!Pf + Md,P+>DVMDg^JD@*6=?2!n0?*Qgb/@OWe6U5PXNKV-b4Mp+FC)a6ZFX!<`XH:k@a2nL[`1R + a\tVed@a[NK]"6Z+7[+BPX%5<'Cdum[`nYK+'2:Q1E(:jD6$.1g1?:r))D6,T]B%&LdEXV. + H-EW;8%]tbbJd!+`\msK_E9*A)YLbEJipDm6C`]^X=HF:f#W4j_Wu'V"4T[Mo!\`X0LOHrA + 2[0@$9bG4aV'T;[$&][3[l&52nOFY>CB(Z50>.E8i5_VPj$DgkS>_>ajcnWXQe$;mS-3<'CPE-"9/=Q2'X13rrm0[*ceST-K`'1BZ6T + R[bQ>1qPS4Bh"#aZu\`,Za$m%^Fd)2`oOt\5>(!2&+"e*#VF + bk!??n9T42>Rd.qW=hG[?_?4%0'(<[]njhta8,RnY%+7IXAAWrma?'+;9Id^IL9JfQ2S&.p + P(5\PfuU6L+#JpD/<)>_N_&^8c2B?A$f"K%h8qb9:/!c6'UBkp4t1mLdtI6iPk%bL36COR6 + qluHSNBd34k''be,;J[!i:[^@g+7nJFEuX==r$5pXF/3LZM'XKh8gW*eCg=L\1_fq4(7r!) + N8_eHJaq*<^5Q#;#5OZlKHLN+Yt[NR5>N:rL$d,\p'?%`&jo]0m:OlkE + ;B_a/"Oa"/')<>IG\(Ir4Ld>DL4S9Za"bHB)09[+U%h&aVl,>nrt4I]hO*@"**btUiD4QFa + S]dG_>fKCN1;P]1;$Ncq6e]l"_aQ%?GE=C;'MUNQ-^"SNB1O;EqPg38dc=JoOm$H3:L=?Zf + e/a`N7d6QP5mau&1KVA"^*NufgSEa:g[@9SHQGj"8,Sm,u#8B: + t50-fsL_2V4$#u;cQ8Ycs*D@I9,&IAB%>H\u]PpXUA.IC/E)E9B?[Rbn&A4gt!JXbUZH43o + Nb+`PILTeniC]''I;`)*@c=;65're&'\[c.:[r9_6;&"3=@TE5H1+br]Lg43^Z+k1$fi@+!TIF2W + Q%36GJ5F/pXe".1lI=@SeJ,4DVj'F.enMZr + EiRk&_a8?>.h[P/Zi&6^tS0e4.TtLSS).GZ[_<\9(R/qAC(\QBf%O;A/ + SE/9\#?LBZ,[P_CR2kZ48Yeu]m!D73i#*F!L.@%AB5rOK*q$pmr<8 + 0)a/mCfEW^/c=()'q"'Xm[\@:adj + ']$k/Jb1enaF)D!'kuk4WcI!'(,5-:uQfB1=K9;/-6JG0/)oK6T1^U]f_D2s7>'kI,d$`U2 + ]gIbG9]g)bdpFnB2;JuoeRbH:NrE0B96%:>Cs;"d=gAc,Cd9"m$Ls@!*IN4W[g]DTr?'j`Jo%'[kSZ]\rKec!;[T)ZfIe`6p4IY&P7.cB*N8=eu\0!XD7I2lMdsP#-mJ-('V3>"4U+s$^H=`[ + c^u+QC2Ckn/gnjQ0VNP"di*hVKq&.n)qF"kLRF]hSE]n^?d_MA%onAc@JW3JlRgpR`/Ik"^ + >LYL$3fDX9>Y_U/2/7OEBrg\8SWd3@J_p!!Ki897UDeL+VZQp;?4T + Snoahn4(6qLB6mU"RJkQj4;4hQH*?-K4Fn6Cn?>k*F1qubiD3"BU'-0(=-1(ct&F@m3[ij> + fKtSa4>UNAErDirWOWtfLN'PSPUD/U>(%CKnDHgRTHj[n+p + o>k=Q]'\=cbqC8'`gcB*^IVXR6Hdbjp>abc-gk5tmVR>hIk+U_A6b/[.+^'".sYl`JhZ@@i + TDoRpG(sJ2tNA[GDA0PO*C>bHM==apRchsg[Mrt7"UJLoC%4qM.cr\K#u@e!\b`%i,KZr#] + rR-oOj!TLPbYiM0,(VGfNKsNe,Q*U[Y?sE5mQQrl]Eob[`nnGsIg<%K!NI&nn`#hr`Gg4(2e6dm,MUfpBuP2fFqJU:d-9t(l2Ka + K2^iD_.9F86=3kb_#B*0D)Vh61gp_=dIro:`FmerGGIN!Ea12#E\bikHHcN_pcb%[D6\Abu + 8^s\hfQMWiJUB1d#=$.WYJnW11kP3Ig+S5/P!sW)b4qmXGcnu4T8[m$NHDo@n>*U>rM9)QV + >2Lfm(Phek>En8Q1[1>oh2`9?^TR'AC41T40MfNrW6d9^%pG'p!/`s"`*Ll?[P+#l;`5Wdp?co+[bU=,Hft + 2$htd0$qYuoZJ#E2DfDMV8;?Rhe)aH)2";VFOKL0(n0FI@js+j'P;)k@P/Jj@8@d?#71)1m + m6'18nKDim>P=d+kR:NI+.[;jOdjHEhD)7\VB/=h_p3AE6cmV]d%2+FbUf$AUC1S`]5Fp;: + eLH8JHSCc:-2!_gDS95X=(P)D23H!mZp3>h93nRNDmrtg[l\(deTPrh03o$R?1kDDn;MY(@=OP^<020iLJRM2Mo^ + tJ!RIpGN;AJjn<+j`=AOolEk&k*8<]-F[cecgP2fS4*A?Wd"Vrao:+AX6 + >!p5,2N>'?11CYI@aX)6T,jgB!](B)3j>aGoCEST/?d<47=D:Ni9Q[Rt?hNrr`H/A:tnU9: + rmIPhbQj*G=i0YPpJRKBJ'8,7GDAe5gG`Ef$@Mk[gMW5PN22gL>c8gsGfa^*+j;B:5P3!QF + 9TS*c$3DJ5l4Q2B#hF^'kaEtV5A;#/P_+E6.5F;Pb8-nWl>$a>W + p:"h[&[t'k-qM[)Qq&ZCmqfWZ)^MicEci,>3NLL)h-kZYPY@%^i(0m'ZVqthYk0n%'(_uE- + s/qc1)6%'[cGf0,W5[%@8?mnr8!AbI9UOG1nq$,D4>6c;-jioPTFXmCP!c;:W:=o?5auSVt + _c]j7Ddg:kn*Fkj`Mc[F[EYRcEiplI.dD8BOH+U>kJC`*WW&BlR86iO9g'?%W2Yn]^TA7b5 + L57D6F7rd%(+oN#ieKRRlCm4V8Eeia(b4;XCqo`?jZS.cR/BIk?;Jgq<%5oRn%%NtWHV[Hi + 5=!a+TV-mj;$n=5_,V>Je;P8t&8are\3-X^!i&^eM,V8k>7&h`X]>Ne>:Wf\Y1:`NL(M10_ + c;C"egeds9.abWen!$6r'=qW$6V_rN=fVf7#=b^-,c1='B4+2M6Tf(77a6i@`2?+;-GS@R( + F-noO3[jN\ACS`>PErB3lf2;$_+^!ru7mBgDqa&'4+bUVqG=6rlA[^l]`!PZKC"J]69DONP8"cs!aH?KQ'hjb8I9aB3]P + `EO*j1R^XX*V8Jdh:24)qa$Ag""MZt"P%$InU?\UCWL67mm^h"\ljjc;RSM^S41"0/k-Mfe + Y5nVtf8V`97,`DE13,LdUkU6adK=#MbLbKC_\gd + 4)h3n<[M1?D0rNL-Ol.!2(kLJEbTE-^lTY+EE3g1hf->2&uk!r#C>]L%;eh"^W`;*[<>q + o;lEscq'lL)&g9k":OJibA:^@'S!ma-nBcU3V4kA?C*li,;"I79=Oq7j@`B]_$;Gt5#Be*Q + Vm=;Ck#/V,>1>?V-So-G=::D*4-u=Tu063Z,8t.7[*&O[\Xr^Y(b<7DXr3it1jiYblG33X- + \sP+)e]_)ekb^J^R<'\*9\XD@/]4i^ZV:*fLO!g!]\9kk#lm)7N.Ol2k'>Q-kK\:GL*p3VS + ZcMf:A;;AkG.%.Zf1I=H1aC?"7)TrN@)D4#=.K)l(Joqr(u_86na6)U3Fn?^oB#Z@"%jGOA + ZEOH:"HB'H!XU%[5>>8?&S!.D\ic=2OfD9sB5gV\J,2XJM1X@J*m+phpUMg:`9Gjr_NDLD" + &)9o/='-,g_)'k5rsMt(C5`Rr3L7DZ&T`-<+#?p_Gf!#:R7:-l#X%jFMp[unJ/8]RWU]7;% + e,=DoVYZX`g3]5F+s/\Qk3hAF7:NuIF[B=[apl.inSFDf@R]6B"uhHR + UQ=decAo,\pEB2K.=q'cYF3US&RI?_?X';5D#(s>/P1[@j + &T'@*N41FbhK76"Uj@a2S!OXYBoOi,mUo[J[m.#Y4/^]nFRXW9?12(%c)RrDk+A3J=Pcbb5 + bFV4T`*.5laQu!2>;BjSaQ=$FS2n;p;k!`$BY$d8)`qP-h1D6`^3oD9CQ1:VDpeX/)#(+ee + T"U3+mU6h=]s?hk9UJJ'rWb9Bc=5WV%=9e,%s%lb-r7hZFEbLcF93G^0W-rpPO;hg@nI2o, + KD&)<8>p`"i>rb_R?OSn"(`OO7Aj.(bbrC[gGEu3KiR@BH>grl.9#;+2dCR>uGAbl/"kqr= + q3qK!"cZA-8kP>!?gA>1'F>l\h;^0u?s8;,@*k.0(dLN)$LP"fj_!:;DS?4d:XZj<+^q)*i + e?6mC&1kGQ'fIc), + /?;C?nrTcBGAll,h%#tF+#>*JN'h)&Mo7iZ;hLCOX:Y_r3C!TDpG\MeL'f<-VrPGP'n^QF! + qVa3:_gfM2`4POXSHf.5I)%P*eB%>dt/pl>)5+DQPD3P:I1_e."VmNC60T]dsbJ!q+GePPk + THcP]Gi)/qk/;4uNT&MXq7o'h[ + Vml<2Q6U_\h1_a"[1g*9>>t-YT7W`'.P\!=]4<3=#'0ppL5Z9Na%LMc\+#Jg3oSlE#/P.a2 + +[?OrTf*KJs!;`?8ooJSPCkgcM^=Uq9.'6j11NuKR7o&43UUN-'[8[WffHE_3u$=+PI2)7K + [=at'[QM&&QJeZblC\m4YtYRZeH7YWgm#J(CQKY;sP6`mlrGF/.mRe0JOE?D_0.A/ITT%$d + fj;ks5oV"P#L90(hNKlTtq7"nX/2P4o0$qa0`ag+[LdNMqNZ#f3dmgJgMV1e.hViC)-:7eM + $reU9q&HWV#4=>bq;FcBL'O]]#SW8WUI_$1!L/-8e$+MbHiU%_a_O'=KK'NM[eN:/gT3DHZ':%Eou[,jCA + kr^D_4EU#=Zd3(Ppihmr!;##C0GPAk[!@oG(NgAm;1P5c?p<4Ved9BZ%D..3sTG_V>^]4?u"+W-D6!=rph/> + 12DcP\5[eXk.cV)jn?B^^&FN#TUTjN!XEDgpdo[a!7"\k9h'D1<5Me-L + V5u4Y_@^Q5YZEI)b[m$*<50ZSp:b,Dn)_>i/G'6Ql<`*EC/HeT25jR09nYXq',4Ca<)\KJE + ;7TZr^LrOuB@Je=d!'/&4'$;9.0]c;edLE=*^Qqm(AE@QrIGacJLno^FmFVQ$X + WTS3RK7][hiD4f!KAM?k.;me#uKs$Vg@Frq6q4=A3iom;mFkF3Pe9.t(lH@q*sjFS7Le[oD + \E4"IN_5?:2=f4AKs2aB2(mIjP(0fhrpEO\#L(dA\c- + VbEc[MLTMp(ID3aL="sl1[O[fO67FMhY)AL6L^83,`r,FVjdZ;7C%GPQ'rOK2Qo3Lln)WGB + h^VP>dj8;qRRaDlDbH8>qeokB@2EK"RH7(CL%RGEl`hiF)g8.Q%0l2bRWS + ;.E^H!Z5t-<^2A+@2NY!"$)F1/RMD4s*$&5GB2G^\YO`nW7VuiArp:O(3%$6\Q':@6Q*[<< + &[hG+*nKH6h:sZHL7>.nZKIpY57gp](j7aGrP"Y[D` + -XkiRn%Fe9r%#3[1pf[S6[L"rO(m^or?(n&"eB"kdK<-blNgWLPc/Xt@Y]err+(S? + VfYJ(R#^,N/^R)rE2[D3dT^g$uf]_Bb-q9T!Ccoh[X@HJCm6kKFQ$Xb:^mK::U,LR`6dX3F + j]dt?NmF*3,dr^qL<13g&bR6\377C.nh+bi$;%`_)E;`q8=[0n%92-6\K2gCP]P2CJANP9Z + [_-aJh0_9;CU[DK\!UI1m\`41A[lB6(>=D>H]uR>K'ZWqLT@b:qO9_fQ+FQ%]'IhFS$J_f2 + _H&]GW'3I0*0;/ad]l^*'28ggY'jeAZ_8ZB_g0L36>m*$da;&hgP1T^%:.[q;<:li$bTG@< + `Hu%LUa\!W4aJ!.Y=o):JL1iaO)Ir[\/#1#\i9J)Hh&TR[j;;A3L;eGN,'Z]Wde;'qf9?TV + ??';^q[+3%T)V-S/->$l=+Ha`it[lf([SVJZ-MdfqJQPjqE)iHmbPu.=8FguW4*N?#ARCYM + [AGrnf4L'^qT2T%76%$H&R%9]>CY\g-X;@"3I+@&CfRqo(#CG3O0-t;:e]s`OcMHSZIdQ'" + n_g#-mp>VSIf8`Ln/ENU&g5U0J;aLo&IcB1!!!]&TcH&Pi#Nu47D;?gIT+e0qgc1SSH)o.LZ&1GX6<_:O)pdn0IuA + ^3S:t&$=/do"G+Bt&9q.m)$u&7iR&Kq:*14 + -6f-$e,)l_B*t3iG)%Zj@-'Lp^&t)_#JLt_3ht%_Mc=o/*:$31XeF%TFT24Yb)AN^872+)1 + FIH2^:^$n$$,f^;PpV6224/=V'0jiJ5b + CuF.^>CTr*!QoQKu%3%R!]Q&l\ofCZ5D3jK,Koqj`;;eelt,Walf04%2OFbU!i/sc1Y*9>;GOh)J/NOeu=,Zb5Zf+6JgV50nqZ!C7W'%1,36F?0>6d1fk$7T"l)'hFcMSL[8Rk( + .#9;cM"#:DsCj0i6@7&cQFm2*FW7eQ$%M5$dYDHli-_f"7R"j(ah[meVc>h)i3JJQ7Ii%pms:"@p$`0O(5^) + 3K[]^iKR:X7&]l9$3D%e#(^N$A!nVCNbV=$kOrqikBZ3:T4e!)K:s@_+UOnERRUqdBjaIC" + ,]E9L/3r-!R>;uMdkSGCHCJ6Y:V99os&&)=-O/Ed:%&\53#lF]i&RrA+Qd\sLFVOM4N"GK4&5N2nFOsF_"KoS-V)WLSO; + K9F`7H]!;6;3pApb&6l"I"Y`p:;pKci!%RWDbIgs>9#E'560g&k[H#\>#)&j13ETos_oRlH + *ncY4&HZDom-cs+.^F;e'Up]ee5=Db0)RD!:pIoX-dJ85UUsD*I5K)eKs)Zuqs!UtLT\p+( + ;%O=05]n!A'YFd(MHN]iof3.T.\qZRU'iI$?NsMFZ5Mr]%kEH$Y>tcMKSo#(6?S1nG!fV&s&PY[0V7O + giOXMdW-`"Z\18;E;P'S7_IA^)h`1Cs^.6PO;Un[5&[pu*3&P]Mkq + 6"$pe8L%jA#/$aEAOb.1YjX_bI)>4K"eC?DQjW%q4ceQb>LZ8l0ma:%CJi\GBPol + [>tRI\t0Nr84jVId,6>^VodEPOnsNM@K]dp^on:C^Yg@8dHm=32l&hVLn*MO$Td0XOY!/KC + G0=X8#Ms+]KglPdP/Ppp1]LPZXf)`gThPD,+fI4/We`SQ"6n:[/ZYW0IR]e2iReC.1dooer + XGl?#4E5c5J$J3o'bg_U#'2$JLM!ruI%=@u^J#Qm,o7p=;DCK5/PK_T63n=.U + SdL3lf-4HSY.#B)K*JO!]'18F),Y9.gA=I+n[Pgi^krE87e=^\WL/LW(9.6W9WO%b^<(cli + BOu7>Q?JP4\Ect1jj^cIEo?08q1!3cgr"4lemV6b9/56Rag[&ZAfll$lm@Xs\n%4dom604>p[8ggI!:m(hKeg]kO\.9e-bq7)@Lq+,m + ]!b.KNn!%0<+Y$)1$YMI635J7u>0K!]S$;B-7Ki8l8\WrA2[C/]dd/M;aa_PgmhT8%)m)T* + D1%q46ZLG9Pd`-@%M@XLn-E@rc\(pardggNm3JA;j$/5Z[m3$1LQE!nr_*eP#%!c%COaFtl + VA8u1DCMleeR5ZKoOdD;!<`?/H(Fq9gRK:6$,m5A>+ToO((4qWp%=sI.#LMN[6u[_n6?G_U + Yt?>6Jp`BRL>a736^$@adg1r>DDQ:5.&(cUI),R + AMglQd4U::?h^5W&44Ae=)FsC+W;^F\\jaRFl+dm9uR)OJ15$doku+6*<)lZI_.p?YaM*Y] + \g>f7PbZl^a*A[QpJ;gFuarm?+c!DU[;9(f,!cYUjt3g=l\HC[L)K\2P2\gma(>mQJE7G>@ + H>!cr>KmG#63:p8^0ct735n\'.tpYMW_Hgg@n^Gl/p'uFWrE*a'k\46qc>W7?*rY`X60Wc! + 51Q$0i.P!p-(?9dLO)a#Ba#OqSj?g(DN6Y/?37t=oI%QX\_(EeiO"f^$f:$c0B?WeO3p2,O + Sb(ejcoLT[k^<_:oFY0P3_9D:Jubd@ncU67O%ehjX/U5N"=1S"bS<9VY4^)RfR!j5li!0R# + p-Y2"[J.eSZfKqY1;8gGB<1Xa@Gs\n(e1UVsaNT]nl+Vi'fk\n9tb^G]B*"]Ea>a+udO:OW + !4BGc(#JS&kDD-0>@aOVFN@ApN8uk<.q!oE(7GH2g\)L1$hLrj1/OhdPjLQ`_$S-e4$6DsI + ;1[2FRWgjAL:n,=->pn%ZKHp<\^IRhTW?T7'Ds-`LCGL?8Z:[,EpjVt + sr#_!kIUCLu5?I%HJ#E!hs5!U&DtErMe+nK#nbrL>pA^cJrkI4f5R[WX)um])!C/IC5Wf38 + 9EmaM#=)N!^ha9AS-_H^#f+([5f=$qbRQnn%m\V:L%%bUC?@BgR%t_MA:\S05\M.m + gm"6Hh#Jgb(q[01,Rj6OY\:*&9l41IF8]6VK@)>W+rc2a_sP6]=#mS2s$=4%$YC6d.\\gce + *l5=>?66ju@L*(!"(+N-6C6qeiBCdpg/7mqTkLFc"!bXqlb911=)7*J@ngeL9(:IP+W71<$ + ^*)]3V;aifJ78-]M>ZO:0=%.L=7>tA6>"5#%V"-KO.+6?Ua1,7L3*c(a[0M!QI6* + 7SIA_>\6HAB1@8^7Z;%NS8(NpCIYsQ7a,^=ghoUJ=b3)^"N59bKQ9NZF%8?77Gr;Y>]Bnb6 + \5_VJ/iJtS9d],HUk_r8'HBOgjVc[In0Ee8.:&?*.gPF(]e&'LIcf"#>BsWr/ + (QgMZB!#81]`kgl=qlO%B218IU_Q*0NlEP=[m$8P,b/7$[m2QNVDt#/lp#KUPK6Rn#/]8]Y + C5fL/c(,0Vb2?86f8*iQler!4j(9=9#u>$gmcIQY=e_ + J69&c((fAI)3D2=Ub=E#a>c>h^[nD+Y98pDSS@V@p]1]fL9?EQOFM7e"^B6U6"LPuL<5M*1 + I7j^],Ck;p~>Q +q 0 0 829 466 rectclip +% Fallback Image: x=581, y=415, w=247, h=50 res=300dpi size=645810 +[ 0.24 0 0 0.24 581 0.268497 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1030 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J,g]g3$]7K#D>Br:q$9ZrssoR@So+\<\5,H7Uo<*jCQXZ:WQ1,0_CYE-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/mfD.mIdf8]jPe]4/4G;H,L,25TF@-*KS9PE`kN%="Tc + _Aoh+fk'&t\ctIN)4XQLiVpoI(<`#s+\$&eJ?Frq)H6j$.t&i,E.B/g986,NNV8 + >eXi0S^6MAH`Y"/@)9.f?D&^M,G^ga"1$[bI`%haSLape4I3ZgMWJ;dW12$h=ZiGGt#_F1L-4, + 8i&4-rO;WP;>bS'il&5,qE6ShFM/CcQ$F%jP+9@J"E2U7Lsm4neEpdI + #u0LD;:Gra:AArV:0NkG5^6:>h:jdH/3\HeU-.s&3G5FQc8cMhVdTS&R\]eds^0+p(_aY\8 + DP,@$lBhC1)8R)2[>ic7t7[3$pZrj7qIUOlKRk5Ek-\:-k.U3>ac:jl$tbR,rUr;4D,>i%^ + X4H(JH:jdH[%SnOQ6NLoAQ/oR+k3^/J!l2_GS$g(Wc3NHmUG.!n+HBtP5ll*=OWpH/q_lmn + r50p:r],3F6D*%[X\F,hekIYWD^@f/-S&YE?n,)PS_=1\_"2jVu7h*E,S6m;UEZB!Ba79_TScLhP&<7t@UG0bDOM + i"<;NsTNHsINMM3qioLAA>Ba,'.*j5bO%3"#a01^S^8jEa'N)Eo9k&KgL/$'.%r"inOsK.p + PWO!4N.@/M(!&<(kMpe(T10`N3@P^L:08;jHGJ^:n4^ku`+61oQ6caH^@K?ELK=2p + k8;@dd5kH(Z$73War]K1Bo,Vg7.E*^7'uu^,M=G8)7EE,+a,oPE&W/9^1&a:*6t_K6U,>Ef + i]6$]E/ioUGXhem3MWBpNq)6q%fsg;a*`Au1'0`O8HBuiI#*nPr-&W2)'bqnO-,'P8]aia, + p^##*M\?`8]`n=AC08!,Q)^W.8)*90]/+XW-1eT<'&%gs-=;?=N`GM2eA5`QK.mc98S!K#3 + Dm#UI4s!%/MtSW9k\%K0#.C;8Ys1#gFI`VNoT0=\HbBru;`mRFo`h-!RXn=jrC.aV-tEkY]i/Zo:9"C;Vc?$p(s;. + `kd^-"sDJDRA!2r\hb:Dltt7\Q86S?&/4/Y4_(mN#MS#`moOB,Q)A:$OL7C/VgQ^rl*O;Z" + "!R^*!&`\EUD/H + tp[fYRD>J[:WXoSJ?K&5Hk98@LWpsU#1-KX$`R;t`ud(2S:khSQ(1khdX]/O3d?#aI@`f8l + g2hLQ-/&?LcNTe&Cr+HHWfmBo>n%F=iq;5&AI0Or"+8=prhlHN.`HD'n,rA*D?n!; + ,6\VJRO^(8&Ae-3#7A*oKuR^?_^#(k$EQ7CYjNUO=GIMu$r)QJ4se/bY8(O%;iC0X0ZD\b) + IkmHN3UA[a#MrsA))?jn_QRPfaAp54H5JD7RY@$+!p!fE,;->1S+V`&.Gh4!qaD9a$F3G2UWSb&_'cIiVU%'`\k`Sd:+!jkK,;*5 + u?OH'h19KNG$jqe)4;[>YD)$5=T!eZUq + K,6U;[/qq+q,t+-A(kVs:Np*i6LTpg2)[g'?4bc.D%`j0I-3NVe'sjQD%YAXl$>im&[G]'W + KQ)ie65WW.2Zt\e:sKnq=D&,;`&lce"s64rO1XC0nVNa\-Ue<5'On6@M\/uC`Rr!C,:>Qj&/378O;1QUrOXC&q?'9ktT:]7'#EI\! + >,3CWkh8\`QfX?h7EYc/p"dR+!s*9eLiNH3Drk4Uu:&_9pZ.#>(I"T;G@th:(P?oBK9+WR# + kee`Z#plVn@Hp"CA:kZnhf<)SYJ=u4j1[3=p:Jrikr`S7@\.&GbFQM47o9fpFoB"aXK5^0Ha + 8/jMkC$R(F)Hf_q_qUE?/)pambQn#=-E1S>LWCdO6OA#Y54+cl2.[?pqP_3rnoHsJM97l_4 + U0"i1).olm0GA//bYr_b^cf0G)QL'[KSn!AGGnE#f!6+TcU)!jEsPcm]%c2$5Nm!t[TtE'F + H/5m2-q"L(5rn4.R:h?];$8=^AAU + CqG<&u`*oD>t8$r/@m?i(aR9khP,,(63(niJo&qkkpI$-BN4LO_uf2mP^5*@cQ]'(5Xg-Y" + "W#9][^!(TC[$Y#L_4@$\oW(o_bHc>]/-L7#,ikH9F"l)*[)K]oDHkip%M:7CEbSE_Q\)l` + 2hY,%]8Yb0&A*3':;cG6-2(87nNU2et`DU.V:etc1*mHR=cb"hBMkb[QLmj`)pl?McTHLNQ + dn4sD\Wg_ZO_/aZ=(&#GdOZ+p[XWs)`;)t"+W93@,_XRg_f")(]jIT#R_&[b7.:o2iuKIYhIc + Li7\j#g.e=POR1\`j#0/T + g%2Qh/3o?ToP01tmO`?`sT,qiT1E`TL:>-fl#-1>mK[,E?0D,+kT:9TXPnp'scI/1($\''b + ,nqedcN@>72qb++AEh-&NV#58-]W]PmEj5M'df&her\D(aMuP==o`%_a/C47p1=H4s0*$qk + /[H6J'i@s'W>(R,@FmTDhQLLtF:(sZtbEK_9I0,a2 + 16YWfa6^AN!IuPRV3r''7U\( + ibMsPTaZLEg[Up)9Jcp(4@*EK1"-WBSk4YoLhT\P8lPs):k2orB$*lQ: + *\f!9DTEKXr.f-)L8,pu[^En'\VO.T4u""8@VPd6KLf0q-t7f2ir;3t,X(KW+%._bX_;\_# + c;TEsDb:N=VOtDL))d5c6F'!.6fe/LCn*Q/j!Uc2*b+IAQXR$Q=6^OMl:_=k"]\Y0pS^mR>I.YWMu + (oOeZS(3Jr!86hm]$Gd/3T[2]lZMptk%G+TcL`9]C]7Wtn'dH-?IduAV,8%@@s[_[t`kA6: + d+OlMBR.3b8oki:>nbE-%<3:&K#:!@\9]:o&4q8p8n*ZeP-1S + K7.HdE+He1[IU.,a'tiVgJ7/MuG[&3K9.TElrr'u+/;%KP9(6+WK\"^SbFf_Q&mq@UP'&V5 + 6EJX,\tp`G)k-%^Fl?hu@+mE_Oe1ktie45)h[%I%#(cH(FQ>7?8KrJ$!B3Le)(nms5V*hMiWk,@tGd!#`OfOME7<-@^ + e6T.VV+)pVN1/'636u@meZF^1NNr4"G/[!Ukt&8/Y4<+,3qmKp)^79=DKMq`>k)_F1N-J,Y + fcErnj/C"'_OO*d((7('[nA//[BKSZFMU^[-&dH2bt'1PL=l#150dr78SCmF:sj\O1`*t(F + g;.[;r=64(!,s>q;CU/+;qnAmVQMF[N\uT8:T'LY(Ns$'EIGm7)oq`7M*-XWqNn24G + DZ0iMCs\+Og1q;MaVrh7/X%,hN=lObj2^2*B'gSZ/;Z!Z)puJCRiR`gs<,[LWs#c8Dt!^_Kdi5?$(]Gt@mR/Q]o+2?\o%H$'bhmM^b[I8[\"oWfprOaT;gb%JE0S[(n&Ni'^+ + 7\=`(ZIPEUg>Gl%_)>\"U'Cm>u13Z-5R#D3akkjPY*A(Ge,*HLqI=ZnT2[En`Ted59uB%V" + 0k*U#>$pbFS52PPsA!RSBZcGPeqk4.abGe/*1`G=8.>h6]dK+i@!O4)(kL"X9i1Wn$"p`r0 + MY>-:*GDa\so#?NPae"FZ/.FS&O[aHYm)-m[Oabd+Np-E;46=/[:) + N\hU@8Md?>Fb%7lhI(NP\k0@VG\b<1M7cr.`hsOOBVcTnLC0GrnPETJ:Bg;=GNP=S#d=8\# + g^X8eTtfTsq#$KUM4_uRo/YU7G>"mnl*sm"rl8f\g[?p?mLKP\_rG>'I-qM@S\E6rqgJJ4L + Wu%OqR?I+n&lmRhYoacli-pOkP;^k5gV%l@mI8$]*?)`iJN-%0UsEr6KTO@%%D4k2IW6?gc + l@g&J9^.7)m_R'd)ScK,-o'CgMU\-3Rs"`f[W??cQ%&YoUm*q643s1)tul81!O"CWS>:PpR + <1<1n$L;PF)*(NQmf/'B]QFY:l"XLr;7-iD"?b`am3>T`+(f/Cm;EGma17\o9/)J-2R4ASo + "pHKG"eGs=1#*fHt+6Jh!7*u]GB1,V:N?Lk8k)!YA;Uhg$J!pP#2bqhdoeooO + 1TORD-Jp[Z#a5BE!gaB_k2^V.k6fS#9usL81__HDZn"T14"09C$('\eaV + i*lFQL4!oH`D#*"6*=!p2Pm*d-WW0JG>E7mYDS*pQ]l*`(o=f/?pB/.uB;Xe-T0+Rm`:7L? + 'uod!_<8i/>`U8UEkJC>Gout93R.0/#=$P+4Qf7^o\C\XeFb2?rd/e$IpL]M#Cg%5T"VmZJ + +ju:h;7nhYC5?$kFpYiGht*7jW91iM<=W*EX1a"hP(DD+D=@/L3mTS,OZh*qB1dqB\1CgI- + D8;Q).&bg6\[nRUc+?8^H[k-m-_Xg*.+d&XV7_0NWfPkY^.C"Y + ?i7a[1J8V(mj>\k.iLFSCE[!frM2+8B*C7VciecAMkB8?raKRX"**YP'U+[Rd]R8%i@m29o + 1EV8GIaAL=5Qc957cY4&:dPXXOk)Q:F=eP1e9`-jFV#_W8K^kIgI0I%(*^+`N00'W7EFIY, + BRg``lsT9nl6p1BeYZIA=H047Sbo2Skll*.3gUt\qmk&2c\^9/ + 4kj&eq\@gq&9ZBeE@AM^`[:?gFnkEPM.PJVZB3U]+D`;?0*p3b3^q*6>#!MGYeK2=$06"is + tTifL2Gnmcr4Vsd`#Fj1gDdDXiGHgg4D0mGpL$q2l3r+UIK-[l?p/ + lOi94+1ZZ%V,W5=b/@hoksH`@B%r)_h="PQeCT*X+fb(0TR@B7`RV[m;e:"j<\[K2?['Qfp + \Bi@H1]Agk]f)fZ5oe1l\jr31.E\8)C%QENA7[=7`aG;n!%iYn_"1EEI0g7,+o,:hH`3%#oga`F.cjQ2Ro16iNqPZ + KAH4Kk#j``pVhG:_kR\X5/G@Eb.#1M/2cR<),9buqM6B)L5q#10Lc+UB!lX8IJ/.KS4sU/^L8Sq,CHcbSj8Ba[u-[.^ZQ>,u!8/VG2%QHIp"HZ'4LP + UX(Gp>6`lIrF`eW4;?ZjI85rF#[1F\^s=7>r.ah,(80+[r1X(HbXdn(#cffJhUMk5^iGCmC + eXjpVttJ*3YlSjd4Qro)&1*DtJRpnd7cUN"7-FP`NY$735#WK7iZ[6Q6YZ$DdJb-`&.0_fD + G(o]3oGHBbX-ZHElY*)S*`L1+?SK16nB2OjF?]tk + +p3@%MPM@JdgGP@TflJXoq&C'HLO5eI3g!;IahDUO\;SDf=TJ"?f-4aNUF1!SZ/'(VS+%)Tc.TQF?[O%p[]aG?d#mF4^*>o5$@rYr5%"YTL63Cu(ILaY + (s>O2#F3Z=1/BIbD"ps)"D"D_0(M[h8d%.aSP"F?iG(2d4ZQWmnh1f!gMHXcn((qg5"\'L9 + gr\(VV"g(toii!g;:\/OBe0\2rY'I!)`%H?N0f\\Z'kRJd^\;Q3RROLP6.RD/E0(A:i"-"r + >o.MRIY<)9c)YK062*!Ge3E#],ZmRk[r&Kr>&E5u0$_!kI!]e.$6JJ(ulNXp$+=,.)0UniA + UC36cV8Go(3dOdeRh6<%o37*JCo`(reV:W2Tr73#/n/5X;J;FNuP78-_u#]U$H97 + dEf!F+6H6*PkV8&`[&`S=cgl)+`%?i[!27B*VF&0M#KEea(r4tGI')rX85hmBb'7Vla%,\o + S"jY[_4[2\9mCi;0mkUOd?Wq5OH.^?_*m40`126:sO>OTUXj>H55/#'[7%!nMgoY4%/8(tc + !l1)N>oX8i;X@S[9RJ=P>a@Ju'$5a2fojTWr40t.RLcu&Lp/jlN4rV(rGSB0Op`NZnY391-.4e0[$:RCp + uc?1;df_Yn8f[K<%&?3\``Y]&I$`dhfpnYe))i4#=7K`'V[0+$4R4Z4o_S_n%q*8%Z[P7#N + EJKM'3_bXH?l`E^\^gs8[DcqNfQbl#j_/CI6TdEPA69iOoDk[aog7"]G1W:0i-+gV:CgdUk + k:"qu=1uA:di(^J]RijhGYW<$IHCZ;?cIDuR58PAEiJ3SVX8/l55/;($.rAMRmMA-"@T + VO-NE%K37/F3HiqfAkZN'Lp52f!W1^p_>\%S7':8L&US.t0bLIgk+t_#tB',SNHn*C3Ae_. + /28FIY=<):=A!8a7]`ZlQZp*m-!n6561J9Hm)41H@"a06$p5KI4j3WQM1K9"_7Jga]NIS'( + m9b7gJj.gJE[99MrBYbRs\4LVh2%P[LQ99d"6I(M*XX@')%Z"'oZl`W\1(dmRsbT&ml/$qu + 3[EM6Y9Ph^:AB25ZhGm9M)L,[jBEhIb;45c9!,r-rE)_NGbKdM$:9@htV-d6Y?e+N0e + f8R&j^Ud_kg'%-em.`GD":)$o!P<.56UYo$Fn-PpW'B18&Y53._eccA\W'Z^QigtNH5D:s/ + ^$i[_.=&eH8?i!=F5YaI:FGYcg;kMoa,%d&`6mMg6n6J0>@3/[Z*SiY>;n&%DHJd3P%*2&% + Z*R(,j+=Ig6fFIYqNlH/&P8bF#p'cY!f*Dtmn;>ZI'"X-bh,pS"%b)@\m])5]YX2:?p;K>, + k`@/:GSPl7:;S4]fN3gs^[6laGV$&YS%(J?XUf/cU_ik`pbr&F2W%i.g9DlTSP8V:@)oNIQ + ]3VR-4!2CECR'ggZ7O'r"[PP?^!nP\gKdP'btD8h[p+j#gRYiYV7Bg4]N`(_>ID1Lbg6S;> + 6M_^!H8_&M4PUk2,uVc<[6a4.o + TJKACb:35-d!UbuKmcdn#WIHe^n$"t'&'IWI%`u];$u/OfRniKV&$4^pfOi + d;2pO;]o?.TT[CXG?e^R_/>L'&+(,HK3?==:8HI6j-6o%[3H^]LKdKrn-eYVkO[GYeS?^6W + 0?Z/F90jh2lnYfQTs3'$0^YXa,W@cd1ddh39@YqarAQeAX%W[X>0T,s2*>(7`B/c;'o!btC + EqRkZncF29XR5lp*3eu,MVW3@U:?iDC"4j-=V]W@ZDpIS5om9@tcTj!b`]]R@OGG0O/KM7G + qk>-j\is0*/9q!LRq`!4^i*c!u=K>lHchoPA#sd9S$cj4nS/7.=5gf%+lI7n)'+7RLIWGK5 + p;f#7>6[;J3.qH`]'h?2,0D1c+jdSm9$$ + a(2!*N4rJe(eb%:kWTp'-*GH]L>gZHiT>sFre + bBbb%SE$>@*;':6XpCoPZqdD`7<-:A4-_pMZ)eDk?]W:f.?,2CRjOgmC`Qm[N\@WJ?60!@I + qXn1B7-.gO^Q*NS=;?DMOi#B5^!l6`CtXR>-?Rl8)<= + 5T*5&C(:;[EraaR>CNF:]Y"r,9k!0)phLu/?Q;kXh^/0Wo5]4A=m0m=^$G`fJN+IV-:%C7A + KktAACj'2XL1*qaRb4."m(J>f/K(9pc<0K4AsFD!V31-k+@82ffgZ(s>pJ`RQY-FLi-o]`V + 'otGF]m#Fb9hDqI4T77H0u+p=sUT?]+8(?I7p^Wg2?%-:?O?DJaSMtZ)#2j[Y.i8KsB`&4+ + C"6grEjjZL7W]<$H>A*CK:]qXm?YDl6V.:KDUf=*9R)oq,?/TpCCo7g$7(..(-U\6/G(!-S + [ELX0E2(LpqD14p,l#Nl9UF^oBW#]QDLp7-!h[qAJ0W7FpfnE-b?mFTpBFm@085mm!7F + *HX5L5?0uRNk8*AM_H/?`=$IQe]le>&!G)JG=+(3_.ragh*qB3oAR8Ehh]h0:=_o`;=<.oZ + (FNC-laq]Asf-aA"O[!P$FHT/B[9h^8n9*=A]J4KdmL]r.=a,fClFpKf!mOn + Z51BSbL.d(Sp.F%RA`-.J\HoGj1^MN1jD8)ocE9@aU"S6gOoCjPHC@(\0s)dQ:865BZLp)$ + `YB]-B[GEOEpU0'HhRLX>IW=5VANt_2c0Ep+^:.n`=7:fDp;-;omrD]cA\=5tI1^&Po^%!] + jpQgh?=Is!(\6?!CrQ8Arlrimr9X?TfacDT09bcUCia0PH"WI\G<(.:EB0?RS + F+iW=mnSu:I+On&UG7fB?oR*IXVP(iVq>X5p0rf4=-s4Bs"#sfR,_&dosZKYt+[`.iOAK,I + -s,H).d*aVlpK6iEabPtNEDfD@jD0q$7_P8/c1FO-K'+7/ZJre!J0:Y!ebFWQ`;V(66ok&j + "ds(8;]Q"mF-W2m&P=d@!9WW-D3fW:Bc25\Q4Zs,+_gP*B'DRs(j[l_"[nU%n*Dcc8drrmt + j?ng4q4r4fh`FEmIr+8;:Oih5*fdt-E1RH@!F.)s_LHVBYB_:"a7[$iMe`q:GrH?4cZe0+Q + >@#8+Fgg.EGItQ_#D'eMfiX*\(aoq#!- + 'Q9O.55?_pIa8HTmQ(rt7L-/)q;^;+t+*Sagf0HH#E:ru+AIFDDj;Z.+%=a2an-J&i6:K-A + =a.G5k9-_uoNag,X3J"C\>V^Y6$]VK[C-i)jU9=!WkFN]j[jYBNMq;QKj'3*F$>('dZNoeA9m7;hk\Y + G176A6W)?G!WI7B'ie14S+TK^G#gJgrcO^\X`%EF("q/Uf!Y0sJ"9U0e8AAh22btt7o?^ES + Mk>WB3H(%[:`[@9s8<�Hm^gU?4WE17X1`cPHu4jIi8k<7@=?$C/>CEM^sja(6WWN1=%-T + 1XITB@Z&J)E9j"b2tc2@sn:peh$2)H^l!LLM*7UP^/V*\31YprT4fj/3q_NgrRas,lBQ-;B + _mfe-ZVJQQjZQ[DB9D=XR'aeA>SWTo94W2_#4V1)$GTDGQPWjDW(+54;*<`$g'8"S3$pdEA"lS/RK91Nm65Vcii<1EZ:mMe^GZ>'05eI`GoE0VO$@8D + >d0FXGI)D/i%cQJ3LX>O#`"3@hiN,I.Ti`at&bnL$_AnERIO?;'/qkLF;i8sp/4E[I(#mXA + M@k7J+iI'1d2FT*BVI=QEU8VD=trKd]c*Y&HB-DFFH4H$,G[m'*`?H>O4!VJ,p].KU`rg#a + _5srknKL1@V&5R5"A0)i0u!dDbcI$4"#^NTB,![:[/41."r0[e7so+d[!e1ALf7[U7%:hD, + @4n9.\U=[8d=:)ShBc`XmjNfhQ^_1"8J?D<9T.L81.K@>g*A=gmoPZI9HO^k-' + +Jb5&tcA8c.k&n%&3X17)ImS+hC#kcItn!G'@V!ei>6bLr7aAurt@Zds'GfP1tL!'_PM_'m7 + s_\b"j>e8=#lp][4G-8t'\sIgV`^EH;@n^LIf9I1Be860Y>]l'N]cX%SjWbc4,u;!shKo8LTf[W);!3(`24"5)Rs=pMc,mc6-NrI9\K5fFl-VQTI- + k=JcR:KXlTQ]/q4EU]/2A+G5FG4KAQ0_(ZK`@Q1F"p(e5J1\/Cd1&`>kXPFf;\d5FgsW=jG + N^Y\0D$g*pNGVA-V0[mobXl,/"K'^`bNRXF-F'9/s@&u7OVP3/cU:NR)+da^K<n%JJ9?V,Zrf?^CdfOs/P$6>#/<]$f/ + -i![JrlosOf%JepV6kECd+[5MC[GE4[eA;.gH0n&D@O:hG:)A*gP^/k>=p=I(i977^?%^J; + 7uZ#hpa+J'(KTtnS%6E(h[SNA5D!i29mPc#'Laa^p2QJ[Ha + r-\^,ON&,\uoln'$?t\*oPPPdc1=D?].M56Q$Afd8C`RZlOa#YGLSKeU4V_\09bWE_S:PjG + Zn>om3Z8Tc\t@^MKSX/J]c>r".)b?6gR)In/3Nb5$>Ri([89rrt,E[PC'Gp7=5M:IcUc;*c + UbIC>CjZ[^c&Rr*3IcZ;EQKSNYTY:2TAdT9?r$;iIFAFdAG);^d@o2^FK?.\AlIIcA.;X;( + b\.METW+-!VRdSj;p'A]gcg_ig-a@MO&]*VAsTFM@np#o?0q4WU%[#dgf?""=`mGNH%,b#4 + TC,:^*RfGe^n#QQb-T'@4'TEO'`Q-V-rut51k#uUOAUJZ/<"3fmeGLfj15]^F&.A];P7LT@ + *N85OJDV]Cp^RjnsV+nG_=kqu:$n"@O)@$LROM+:?S]2kU\RSCc^e(5j8\HQO]7uNh$PO%FdiFRu*RWIZn_5)sk8mL + d*[;LD_3PfY[L"('!J@0QncBnr#6>GELZ,oi!#m(Yr]/&S!5Xg87lpka.0@pJ"&M?0#f?bC + [j*Rq,4!,JZ9ogT:?%9fNQsHdMqJ:)0ldj'M<'1d7lDe!2n4@d?.P002U'b#H[XO,SfaB#%$RdOO`?+R63jJ'5 + e$D+L[lX,m4&`gSPJ94Y_`X.!4O_<#iURiE9F!gf"S*Bj!>.Ln%6fE_+4KW`1\3d4SeFC<& + i*$D&k<(f6R1(g,I@t+oF3&;#3&HR5Z14f0NSEu=Q@[i"u9BCPVXX_D$Mh1#B![4%UTJ/@) + 2D-#aA+5&;-"mk,1Bb#7OXN3)J&!q2)Tn-ls@Er>EH'+o4"U(JBi0d+`I'2=GFna,K + pm-c\?/C/Ab'CRd(ml<02D)6#>1T_H.;-PkS>r7[R2%aIY9fVj/E7>P+e8]a-F(Y0eOZ'Iu + 2XOp3.cE=GC9I4jf+FDGWq?,WT_`_eY_re`c%S.iY;pU/3RP;s,-`*9W3Xmu4L"*!NB7$lWSqL'/csXA"3rm^'g/M5VsKd&k + +"k00p9e>gs?OQ,d,d*sD..]i6:(#iu6?k7;^Tdi6kFc77*d6>%k#R8_5T + @!jSL?I>/IS(kKog@ZnToELIo:9Ucqc^e4,O!]f"Dd&5QPTLHq + -IiYBpI;FPgffSrne?4p1CF=P!>m6_G#HHo/1F?71$5:6jA?o1F\=2I]J$XiZA-n.JZ1g&q + *>m]"j@Ph_,Eh)AJY@@u6.TSDV\+8g#>7;sE81q.tb],0"G@^ro/<7bBQCnjc12@NnEZY)!R)`muLr#&E)1KEj'D=Fo%N8c?D0M[t\"-b!-[Ck(GrLkL(Q"=lQ]mX9P#^CG3C1j.+TOj=5cWW.)[rYQLo6IehY3'od\Sf + .i83oP_ki.NI[^D0m9*_/F*sKlP%oZC@OSc9aNU6\PLUOLG]/j2T^`V,rE"=LcC!d2%si1CJi6^M(a!hEI + D`4.uH':2d/G'X_AY14S_p.Mg#D*OedB7^p(>h)V5jHe":XqATCYaO1K7jRWFNNFf-.%VqQ + Kr)L88bJ#I[F1L^%r5u7gEA-Gp++[?qc>,.2&L_cG[O?`dY!>Z7B7="\n?.h<&\LFTua/iI + fH()FIRa[Lif6V^=_2t[9pYEK-hLDdq0>Dp*LHNVQnYA(GND=1 + 0#[=Y8"#bp[QAuLTh&3SJ6Aj&3"%9ZPq;F`/N-see>/Wq`9m=HmWNT?oX?Ij1n[r4BFb7Y3 + 'h5a#D@Bc9lgl@3U]]V*OhI2C3X)@RK&"6BhrtgoJXH\(;koR'QS='osfQM%6#'I4&MB+DE + *n>2X-(.T2OB)D`U0CH:*S]I8d^Nq:\Zs\@n_YVi4KTEHoYeZ_"GsdrI7@VV6sC)Mu44NPC + tS^Us6Tg]P>3#J^0D[R%hITe6Bh*!(-S$@EqqBRV%.-3YA>5@AX>!,`mX"%'T2PW+$/)Tf' + C8&SY*XP2'b(S=$a=*hI7jBCK&eb,2l4g*?8Y;a4^NamI*s*WLDI8YuONdjN"hm!SSR(SGY + 3KLG5Q*KnY=.:LcejISX\UZ!*J9s)@p^OQt[Bn)8Zl-W0oV[:&9b1>T[0A3q=j`d>]k?jG0 + DXP"[*LG98MJn;T"%]3\ad.i.8G5Lfg*aJC/# + )pIUJH5Sa-fj&^Jd&dDqiCHgX*/$eSs9rRf7)B@^@(eids&ST3 + 6:F4Zk4'4CSDqQ:Y5LVbH_&fm$9oq35>RVLR/Nk@N$*'oed9uNLFg6L9l)*u5&e]")MPoO) + 4.04c6l!J=.?e;nt*,g3EEsR*gk,C/W[M=3K(f=G&=W1B?ubh+Ml):7hl#L/`DS\&==;Z^S/k:j?.0`:Ao&%;EM9')EH8N]i-EaZ5nkIiIXLSR9,0He2TIm%2PTIadZ?ea4F0mG?Uc#U(95;@"CY"`s + AA*qLL_q:MIT*T$er5U/L$q@=_7PeRa`IhUR"VqU#Ig:na"E_l42\a^;8g\7c(11T*fbCMC+\@?8/rBDdp#.IE2[;1VR;)qU+ + c5)8Yi_O7\_]JZHbZ6BZMH>.3co'-tpc"%0_[h>qhVhAr?tKfq16o/)T^hjSF3f=&3LGl7F + %Uc;rA-FK/jLR]NGeW\lhhY-1u?<\&+^@]I+LYLmZW4#MoCA[L.XSJt9GSEVLW*],R%/%I*KeERnbHFU>:Go`AojB!p2<0dctN&T>n3JuW7,L( + R_I5'7n\^[&%6nW[.!7Zjt?)TN*Yk8M + C'g".=e-_,`2aAO23*Q\PNPpRUD>b*/]13B=;8s6QYJ"A#UZRs?ujM*>h-Tn^ibX_Kf1!L@ + lbr*bDK\8?XD&l+d-i)Lg;O!_& + 8t6.^fhGD0h)eB$o\DoY<0\'HD]eX@Kn]+ + 6Q5'@p'R1P(0#,c'rA!eqKuC)]!:mC7jZPX_lf^]tJA)pAMsI)TVZuF'TSFdV@S3C!CkY-i + (o"??`O9HX:DbmL6_pmi)h<9EZ->$a+[rh?5dKcG_sItihuUjBB&^30o]Z)G!1 + p,3;u3]<1Csq7'o"q[UHICZZtgZl=?Ue:3s"*o"0kgP4EjDMS%q]'gTK + K>$`Nf(m(Qk/i1jE1A!X_&*#F(2RtqC&VobgkR2snpH"8bFdq31!T;piH`[i#/=,)HSS,bbu1'2[[-M@hlt`;$-Y + U/Y2^0k)EH=La3^nhGqKc7h^EEu@id4cKad2'c;VOjZiuNX7m0,o!SKA#`U(.<[j$"M1"2n + mkGYV)V"&h'!#TF-,YG7433TT-SN.f'e^q6[%C8a[88=J8?#T/Vj + ,Vt=";m*WtgSs3-K8P\%,I[a6rP4W\IBXgle; + k1k^GH)1u+]XS;Nhj`Y]V@FK]32*ZQ*-F2\c;pJr7kch@jZdiE"jtgI9'Y[tWcdt\fXk=Sm + cnl(GHUFd4=>[Ka>I$C%jZrn_CA"2CXRMSFm?YAYY!^)/:107R9$f8pg4n!I,t:;'M$h^?Z + YacjYciu;QTfRa]a15RM%q+$"3*WK!?(,`7TDO@r,_;Z$&UW=XtY+m'W+b(/iC[HT,PQje& + m1G^#)94p=3-%Yo#-93Si>CMe$6[MG_TK2?.gL[mWfEJYFKCO$_***Kc`0&]6tPnd+Y`4.` + MiS9oNF(4\-[kWX%7S(jJ;:7Y?#nlVp]='iM7Ga%R2EAg)aW7.`H&n;g1L@TjR]ggf^'UB! + -^bd!,`Id\&lD%gAJ]hqCn$X2@fXX87]3IGTp5tR;p)XUX6"r?Q;ZCC@3pP[A_%ZaP5\f^$ + &L@51>'MQBl6VPaba(=cs9bMG"jjU2:p,+S%-IVY_g<7Z*`S#\E7k;C"#-kK^N?VdihM$of + cMZ7qOb.J$YRKP:/!gBN-FUjZ4I4.!ni2e4luT[_5,n]c(^tJrjS&f1;km,c&qd]k4iZi,a + #oVG8>&;eHs-S>LM.2"An)[l[FXo;&<;ZD5"O@A-tj[!%]th,Of[UM?jKWl-(AYV]iUAgr@@4-.kfha(!AmUFge^0(5H,[X`LZo5LM%H8p"n]oiCP?K + hV:Gd;=O1,A_SX7=5Kf,qbor_0:"#:K!TU/H4P!oU%VK!8l@4uF#P\-!=>!n_Tod=Y1Y;KqG>>0+(EMfll`45YfSnZG7 + c`V_kGs)=/1aPcA%d-8oWi8uJ`fM>>:XN1e0j(Ic@FMWN:Y(Lk>;Z^Fe;od&q_up&CUU:I) + S=g@%:uVuma0Ye#A8EKJako?`VCStja;!7npYN#q;4^+I6X@sI[,O-6QfT:64C,"r`\dP4C/0D,IUS`qa0k;K>Eu11SDF.6>6=[""Tn'DbF1S][3m/YN + q2Mu^H5Rt[],H)Fj0H9I-7B4Q-p+gT"1G.2Ub]#/]9=hYWf&sF0Y>H3]@jR'ifc/a\=9P74 + ;*o.F?dDp5cL6l.Ds0D17l.2a>K,(W7GM.ejBomu@)_J@`WTa?'Oo + 'MA&_JX%=h6,pbb9$k5e7jL[8cj+rMPSI":R#`e`c2pfe'2J%ocI^[gVRPC,k_FJ:;_\#a[ + 9giZ,/E^+YkU'q%B''m%0JNt`;,@c?Z%"G%9L-TMH5_1W)P4k)4MR^S4'8-cRe-s.R%A]0d + 8>/F;$NMI%&]iH7\O-Iq/!`a/#/Ogc3KdOUX..4!)DK)K6-Ba(e$V + F!\AVR.YHl$u*BTU78Y]"9!"Wuj[8Up:l"Vn4.qEdkVUJ%IftZ?.kEZRI:r2*6IL)N'D>F> + unq0>j85_lAe;A@'1rP-\*'_tuC*4L_ss\ATmZ4E?u\6U!n:af6<&bMh!iK?U]-^qAX`1iu + C(!TQSt`5Lt6Wq4\.!0FlklpVi"`A?*rEUYK3h(/B[25Fd&ACF7@A^*o + tbuE9=ACo(2fZeNk9n_3E[+`dA_2$ZRKfB]qn-m>#jopFs`jkF?,id/Ujal&(J\(/;YoLf[ + Eit@G:8E*Z[!9ltlltf\:5f.tFRR!`m)Rk4^cR37<.Vhoo>iKU\oU\*kT0ceU3i".$/>B%1.P^7k_Ru^3b9B"^VjU4410_+XUF8aGL!e@!9%1;^M_$rcY=" + 8R6^8k%,We*c[):t4bA4sfK>]R@`Pa8h6oiK(M`sO55YLFd9D_*)oYEqiW4_Fd4Sl-Ig9&S + (`h%NO#iY&;$1#g)f#92`X)-"P/BpUtorG`,X2J,@jO$Dk=k"/?c)!bV,t#r%6rZ$3e7D(u + F[]8`7b!DfeK7fFJu,@0j%Eq=CiMqboTaR/%_=T6[LIJ]WndWZGX@%1IWWHALeV*8D%DY2N^FlY&e_GU+m!hen"P+=0ehm)$4G + 6L`L,ci$c7",8]oqjT`5L]PXX:;.4p(,mf&6g1ei?f/q2A'@e'&cS!iDp`Q@ + (_.01kND8K + KVa_RKah$Vb>G-N<^-?P`o*=q!IHHGQ$8c!C'2<V]j^X@#_MjW(Gb-1Dhm;nL!n=OY"A::J-j5==eHCSNjeITk0c>@474.JURQ:puTi9P93aHuHL,`LXDY8NV"R$'oE)-Z,G6[Pl"oq1%J>`fhrh/)u5jBV + `QS/j*"aNEmV6PI/;'nf2*T_%-8LIP2=:VFhQEAdslX\RA-l),2>c^fimlQiAdLD+-#.<+. + o4rK/?WO`!Nk3mO`+>aTCXr7/UHND[/*5O.C_XWeEi)uns/M;Udi?9`-H=M"MR:+=g#`KAr + 5n<3fB/[Le'-,"`XHJ5^>S*=m=s3`[lX-#flPJ$TAU*-O9as9"8!*ikGLXg(IIaa6hFGFL9 + O[2Im"3Zie3@[FJhP!Y=8(Z56-1(%%q]rcqT4u8H[%""$eAgGTVL8%KN^>oY4H![o`2u%tM + _.o]JC1YgeCM:4sW^K&9nL(c6C>9nk?.LgK?6$8uS#kMgsQDHCHDAg;Iun9.T;m\Yt]]u\b + 7m4TfIp>C%2%8EPCC%`VDh"LS0mZl@pr*s%kl%Ns6I;R=_!o7bq8LSA/-s_#u(3!Ocj8TP0 + Q$-I$U;@EO=,l08rSg.=VUrj%=4Tq!DFr5n9$1BkEJ5;#S1r?qSPd[.n\U%H/ar5s+l=@aC + Mm0N:%_Cj\T0h\AY"tFX2]1-]&hD_Eh'i6XdPc[HqTubE%t('QBn])IqV]'l\5i`^YR_70qVZLMi(f + !A8V>Y^"&LNMJ04lk%Y2%qqc)X,Qj8.[ZMNIpN2RHl&.V1p)usbjLUn@YhoC8NaN!EXJN+< + dZL@Lj@o_]+mPZ4g:M9*6B4>dmr9ie:+3aZ..rN>IkjNBVjt:iW^5d\_*5q]W[Zo@'qr:Ip + Bm0I%"uf?neNluVl+T35msB9!fa`4Sh8aur=+B&h%Te+Sh@?@/"J0T\L$F81lBQgCI=M$PX + E^=0poT2*^9?Vd)eB/SGkP!4##a=h+*%#tAQ1_d2Rn$%=BPh%E`BPULk%B"n+OWp#mQ!tiD + #$lrN?Zc_kIO[FA]4WN'H%>\C>OX*K;\I1,c4jWk!W$jYDZl[;c.\9BJ#:P29V*C/--Pp6s + ]\5+^*`Mcq,9V*q)ng.P%<17(_3/F"n^NCp6N=]R%I)U3:$creod2@C7J@:ll&m.R@S4^W' + 8gTD=BFj^36]&#(6Q#5CScG/7>WlP)L$\ZDR#r$SlM2CbN6&J][h9_T?_++ + g)VN$9qp-OFZOqO[ooobluh&7L7*Uj(Ge48Ej_RnG`K=tB()V%M;"G + 4XaQe8H7oXIK*c^7gJ"C:XJK[Q>*/u/a&gW_eSWX,EH-,W6rd%/AUo,]c'"kV&99RN2/]su + -V_K6V,gA'g'.hTgbu8H31/$)T_)]^1XKsFa`:(950sp)IicXP/9=0K<&s#]`tW)$ + 7a`!!,t8q'62sR*oX-DJdo=`@9@N'\c38c,<+d);Ah"^CS>ZS[MgnAm*])_.Hk`AZKU=p9h2EgNMi7DHl<"eo+ep--(&^#? + eZY?$B<$J"9'nY%!_T4Af"--i)2-+6PqrIc'nn!J-dG`VY9q\2jP0i5[j]L#MfM+c2`O[MO + iAfZ#atSAq]lO(5^!6un`Or^i;a\NWu,jOmtbQ5ldJ"USo9t%\p(>VJ``BS3#'7AnnpY_6Ig3q7pS.gIBMrTB>s7[dEs8 + iOPH?:>FBUepVccV_*5>$I!.#+AJUaZXpjJXNiP-+V]MR' + kmX(oeus2u9i/E9%tQFI?.bEN;c-0>CJZMamL";!7V+po0.)$S6(P(Q;R&Y^+&#N54tK8L]c54Aq<.7oS&j7VF)ad/,Cla_NLF>Fc_s!%m#B7Bjip9;C + d1@6`fMi,ai"1I4/J-p[=[C,+$U`R[&*!d_N%oc96fGm6"L%od:uj:Le6a08pj%lsA8;U_2 + PQF0Gt88O*[ha$+62p?8kJD!-Y2G2Ubmpj&hZPmfbGa-@UG4Rl9WK*s!?_-Jraqh6:Z_A-e + +TI;::J%.@El-,s%Sr'!5"\"\>VedIpQiX]VV=CG")T\6'+;?7Y8fYuVj\=UQrq/I@BBk!I + L"Me-]VV8G+@"OUL*RVGPShRf+pnQlUFH:nq<^),5q?rN&8c;kXr=A''2Cc.$oegSI=gDOL + b`K3kr7`oA].%a2HQI",kbo)^)TM43o1^8?S=mJMM9c]ucg)SQiK2SF<;62Y+\C[[s@;O2R + Z-g#4g?A]QDH)V*dT5g.gWP"a:'JJ^f*pZo;i#HH6CKn.)Wg5$QD[JuSiPt!l5_HBFUkjS3 + qm2_h5*Yc0(Pl$[s%7Ho>IXs-E91PPk(D[&0c$XN4gu4=.[ZF,gU>r^":%l?P,)r7.'S0B@ + KbYHgW;iaJ?RPk?Wt5=0GEarn#d&=^V(1/L@L.fN8^ei,O(J7Ks/ao+M:$8WC#F4NPN(/K& + %ERBlO$bZVP@B5IgBZrVY'g)EUtm4lY:'6s-jX&E_0*'pufSg)nl4?(6@Pn?Z8f^/UaS+f-]Wb]:?) + -r=O"hM87K[n'M[i-mpZoWs$o3SplnB + M?]_'_)'@2TPtcMWX>R&_$pGqO@t*1]6\$S%c/8q,+.q*4'f3M:3QSktBZ6/"5*Tl`2Kd/# + *=g:h9qq6#W5qE7>iK5/\2IZ[NC8P0rAH#HFJQVtKoi9?4UVUWpXe!DiOSF25TmY0$;-V^A + l;lQ_@]1P/"P>Z.2^!eV9W9M?XLo#AKb*sK*`Nn[#mb1ta:\+]OS\ + bh;(DEZ7TG=LZL>G$'+McYr_hX0/hK%XYL!H!$Pee;K]1[f2ZO"Nogi*5ArDa"M_KQqMFnR + Q8$f\SX`KPCK@j?A?,jXG-PiD@#[o&%(0a.sI<^Dr.-?RtGhq&_'"gk,NFq(WsErW2%](Q; + a5+bHOd3E(/]#"l/YW7Q)j@RnTkB9D`1WQpZ/>([ap/LHR]=Lf'".!ua?-F!$K[@jXRCiVqbnNd-[4Z.Hftrb^3^VRW^QHiAV7pTb=Q!F7KVff.R8[AtU&>-Sd8^!4<"D^![$HrT\mJ*0\t,_3I + [p+-$0(4lD,rM5bp,9?kLL)@WufA/e_7k.lRLAc?T[?#)X=lJ!gTs)_tDXmik.'a\`Ft&Gb;GLc_/'-i_3p!MHjic6 + 9OAt=<X"=4]Ccp$U;EjNGTUAJM`GAN=cDnkMAIGK?l]/XD_73e[Egs2tlqDq;N/5PKMX=)tSd^k*WT`a]k + jD-AAM38jk$[WD[%OSomuI>X/BDPlb4&*MC9J&jNt1\P3:X*6ndOmu/F]J#<6qg#V=-84XT + \7Y+OJLud0b3lXWGm7*;+5Nor=8M&QJI=U=8RGe-`."066e?G1?r6eKR-N$HB0PGU:DMerP + urX_u`?in%r/q9PeelLOe/q"j!Wfdd',9pdMpW3FU^r(k8bCD-QG(u$^lgM85X0HV9R,93p + 2*:_,k&^;=Nfc>(f\M*`fci"mP#67F@!0Q"9c1qB;pm*TO*Dun[1'J_OC]tqJ"))]\dM.]! + h]\q=!^Iq2!Vd\obiZ2_#4=>.O@]Xq;'V/u"n#;6ctO`17ZYnf#7b@];D/8H8I*O`#OZpE: + l>U"BaCEW#jpOed$0kLNl,mc$1=2g/tE?4Lr?Hl`D3ANdZdsr[0t9h$gG,M/9))Y_$o69?S + $[W0\0[<\IInc@a(#G9UNs8aIdG>b-:IJDY!6-r#Ch!bRDNE0aTIEW@k:%U9>(\.Q$[ShXT + %lDNQ3?D^P%Y0$iu?o8ts&cTMs)1A#DtVt!7Kk=ZN6-P[.p'lRQ]%6X?FDU^uZ(2'$NYBq& + tDL0Q0pe>*3X^`19OVglWq.J.nd?(O&Bc1cC)6JdGOeAp1QM\pt)X@tS/Yq*]bo4kZfFQJCHe\;$K.]dnlJ%p5p\9B,j@<7;Fs,q"[K@d5k%W9#s0FG0aDdq-*.\F11)@^JL + 7UX6LlTI&oQneOXF%P$'KcEZ@`JjM+"Y,l4T-c'NGN7>q)q^>g-rP0Z+<`B^=W9(J_jtnC! + hI[UDqY/'O,,%bf%1b3e]c/CMo5aoO)5qTFombOX`<;X"at%\f-T021+4N8G5n*qA\CcI3! + DFN#r`aR^tbo8Nu08oCPH4Yb1t:%KT)/L*$1.hsWip,)9fWh@i+X\Z8ee<<5"Pm$j(^DBUL + XHQR!c"?b@K,A@^(l-c>YFa@r./Hk[fQ2):caN^jV#/QA;r[0`9,?6fN8"^`3A[!"PLVfR= + ZmL*Np_" + Pb7pEtAp5iN^7`uMn+N>,pD!]&QD>^i#^TJ[2&-\tMU,.@=])Af;AC9O;3+V[Ph7XhfT3-2& + %-7m]!-%F<"jr_F\&dB]\=/u**%o"(@uHeO)L?WDmoacpB3@]DRO\/DofarMhtN`#?V^pET + jht[f^Ud-ZYu<*heB(?jm%.#_sQVF5`%!.'ab"OEW;ako[Eh)1]618:>]ml+2df1AbB: + 9q6*aX91a,2.fg:^G?4pltf7cRnLI5ca-SfmQ0V(U3pm=s7?kcAT('Q-&$Up5H.El@3/8[k + ")e#)G)Y@r<2BrZ+b=B\-8/9<"0X38E1s\ll5;ADW'K"c^FQI2b.qo?NdrR<@8uEEtT,(J%e?_SYYCACTLG_kcC + T?!%b2$kI@o>qaq@dVr2U*,*di%g;D3[08[bgotj]$;YZ."bOcdcqM/TR0_Lq&S'pm$ZfR> + E=hE3!,\pmaE)'Xi3'+I=*h(plEUM2HPEMqFC$^fCSoEXXqO&pVeei#Dd6qZrO8FVs%6e]j + +u?$<\a>WD7s:hu*$q*B*#G4j7C2:Y#pHGMEgGP!In2iEf?2.!QL?KGffAP].`pc0NMsCQ>nVR'epJNJ[F*D(a1XD)_soJBWkEfZS+*/0kkki("53h'`P-j" + );mMC::<]3)+1NJMl-9I2H*b.ckJ]l\6URqK4H.&Y.MKD`=!:)O#Yg&&UAH[r-BSgG\M#=I + Y6F)52?sN#Ek5\g*p$TUXT1U.\rZDSeL,R>X&5!!F&=TVb"#$4A#!&to"=+^>X3rJS6AU&& + ,PZe7L\linAE%%;-j+GpFIIs7:dQ!"9h39cM72icUOO1LAs>*4!^LT.b*WVHq"ktdE6h2s] + 8??_7,2a@X@8"%TdKI2Q6!2Dku4 + AUVe#/Mr_EQ:#2K8P/L3QZ37s\+]Tj6pt/Ci[_4R2M)J?]))Wq!RIsK=q/)+VTC + k@&ENu1(I2eGG\adKr=G`n:WMUW:U3j/V]c_oP:!26j. + 7M'Eq(_YTJ5S2HB*-,[7u_>9tjGC@TLUXb<"FST@l'P?QPg__ + rH^T!l^hRtWd + j$]tCW)]]iLESh + oi?9O16e\rO_qZI\r&;L+\(eFmW-tr+4?oi)H)dF[GVh.o)R6=H>K&:S`DehR**[]+Q,YXT + `d5`6gi$SjE9d2&@1o8u2nR-()os_XPgZh>egg&N(s,f7a]W,qR:jXniNPG>I[P8]ekToSH + Ha%,Z+5K[;Ybi'M\7#FDEBA:=;&rl1R[hNZ`cgBGU&[%+jKrEG2#imr9Lbh?`L).g>0m*5;`[O^"]*moCm1Y?T01ban39m`1>;<>YXm>fA6I4h8NO9G75=? + 3I.1_$<\s,4DT7g26@QshtkqRqV0qXM65ad)eJ!dg>tEl:Ii'WQ'O"ed`"bIQ[QN98MKg/H + Nf`T%=Zg'Yr5Mldd7Vgaa`3mZ2R4/>ePZ7-^b21JFP/6FNr%A24=4[RR]L4SnH<[7eJV%+: + K(`C'=`k^mck[r$E3eL-sS^\;OrCfS[XLch>:h2]@]0@o$>[OeO^#*$d`cre'Nf8I?)poqD + L=Xk*]+:^+Z8i!^Q-UY#Z/'r#5l,YHSN?5gPjK9b7F5c79Z`JFIXs7,0-k*G+1k#K6-t-3o + +,'B^.JOb@'Z7F@o@)THd'JenQ_r)"P+n'Td+R]#u@F3(XLN?h)9;;u[gZ@&2k8og/s.*D+ + VSp;>(r3n<%P4ZXG9QL'S0hg%AcS\EQ!S&gIa->OC:%P1@3mMfRo/ob]AC3V9F8Yo#:ibX! + /C=#H'.c_\9cGEMkCk'N+6S4"8lF0@025A%jY9FK[&$$3e5[V,"bBPT;Z.MG,$oI5BDl1>S + 5l+pG?0K9EEs[LBu=7Yea7ip==7h!?ZVNIJ"9Xud<",&8[icsa%<^TR&/&--f)=j; + ][kP<@H@T\XIn:03c%L5nuqS9^]@J"1,*Dk,u + .ep;A8J + c>T+42VGM*Vtn$7)obA-]'c&rno#B.q7F@Zg[>&NDnHt"_XQaa\74tj?PUS#k2D08nm%%ND + =U0LLWaaDcSs/hAf!&GRp@+.KCn@_%#:P4@N()Gkb9nm,Mf(UgE`a:9T^W,i`MJC?34T'V] + I%(SgTC#P+ehS,*h=?.XITKH7H\0X*X'aVQG2%#PRLbmO`$6rQ#J?uaL^J2lBR:4GE;FW//ODd?Et"FYD)j+=:5+:9H%7lQ41 + X_;\hNGHX8fe9qG\#[#'=WoshGHRaY + ;PMXJsQ&OMq0Bn&aiV=]_A$+i-\=b[aam3qO]cqIAQW35<%[%N*H"*-:4n#__D-"bQNGm*7 + >a?d1Kh&CTUUF,?DtE>HF,(MO#/e + Uj?Yj#1ZBe8/*AH=c6<0%9^pFgXPr>GuN,OT23\=N[f4kqgB!mbq1YV_1Cb9a@[8tl.W4Pc + AD(Kb8;;O@gqg>dn@aH\>$SE2/f4Gt^68GIhg'gk-7/PVdjel_H()e9I*lJL(2?R&&Be4/1 + 7OXhD6qG02s1V1S4sK71mXT[)Kb:[=EU[j.H?4g"s]^\g3h4lofjC:F.a!-JN&Go6"iW8E) + \F-;"l,$.&3&JY$R"5S:KfD'ckA[ER`DFNpEYf&>Z\+Mc-(<2@0RcG?Ir$dhdF+SeX(/Nte + ,5MKqt]h*?8Y"mG)pLA!)QgWbe'2a/^Y=URUWI+:?geL/gG+*MsZ3VKO=HFKOU2.(j1:9eJ + YbG&c7"uPfrb1S?iU\'h$BoKfNhKeYLi`hDX_6[;:"dZe:YfB.+.)Z1aH\fR$UEa%Lc`/ + e6UFEUMgkIDI"\-K;Qsli9RV?$%?$]91]C8acC)FtV:2]@V5ohAu-20-$mSl]+qNIK=b+I' + MUmc9M=PKoiSt)p&eH-.4Oh8Y(<+G1<^ + qOrglnl#p2UPg?O3;KJ2]769e"q_C;9URE6h7a5)DmM5`[S6lbIWRX&.^7=dpHdu2GjpnM; + p[=e4-"2;[(9tY\'j7ne[IK^3`p7Odj8>7(nt&@oq*-I5[0EP5?=V+BD@h6oc^m'6[o[>U0 + C!s-Wq;TFr]$R&_l>Q'7np*R]1*qsBfI@%;6Do#*$8)ns`WU@4'fXII0D;5Eg[s + &;$O;j,D%e7ZOFl)>n41BB=D;;B32Y,^kB6rCU5JD%6ik1G@It%:Br+01#Q=&0ehViIbV8` + (n]JfX6'8S=+Mm)`/YT*`lK8[7_NNc!A\FOJSRuo]=$TTF=3APZR9FNWGG,JQ20a!e.q\6T + 1'Y15m\[r\5H+U""S["Y'6#3,JaiHLeM6ZTr_5j@[diuA4OMT\LZ;[^-CT9G&+Zdc3]&P=^ + q^oDN0-30EZO=(1A$`d/['NB!ZV[\aEhp$g\LM:"Za2^)\u;spWf.'9.a,R`'Jq<(r]D,eT + F.tlHc7*p7_NV!4_-W;>WbBT4N"fp6gR!(qR&H?:qZ-[22fg=4&j,P:H^L4]gP:VoLHhN8I + ghiK[;utOT#?<2$5Yt4hKLO)f%Ui-9agq4oA5:M)2k.;oL:f/Z_dfHfi%PkXD!(XqP_3oBJ + cej0ouo5.g0W'CCN)FKV,%[tF07)gT1SHTsjc5>uqgg@;q'EjuMQ03a"BUOi^!r'/O3(WuT + ho7k$f@_`t(0GJRTA-B+tBs:Nr391:ZHNufo#f)o:5c^*abEY?AQ9aaJ0e@f/7"?4M&j\>g + 3Qr3uie+f=)nHH41#*Y:_kTN&c^bH?.,'_JgEaW$-67rq\tRfg\_FtG.imCi*UBncJ9;&=< + JCKMKO@g`J3JYU63ETGK?,nQ0K9.?7Y@p$"NWh1GZ]Wl5_aMaKW'8&GdJpqkJE^r_R(eab; + QtiV958g6Lq'2`%\gWWsf8Y"s?hK-r:%3=TjODKc!,YJ4]pZ1B]Zr!pE.,TJ6Sr5(cC,"CO + :qJ5cYjH3Q'%Wrt>n4%-+o7Dk8l7"iGf:iuro:(XWp"^jb$QqmhR1k^ZG!sh6r==f,=3.gW + nK?uCXLdqQOB8&$.#JagQ/4bh8GQn.E"t37M(g-u\4,"M;")(:/YVCej2?M5qK9.a>com"D + `rh]B"Y`B#TOENoeBBohLYu_I5]?mo8qlb."8Ghe`5mR%8V=6%"@tHQh!Y="gOg>=KD7;-= + @@e'A;(:V+iNK\RokI0AHbVB"b90ua@ZQe<.&3 + jU9P.k@THP6KTP"`R.0W%W`.)hEMHJ<1#==93p`!AW9AB4@I"J<&r + /S&]=8#KNLMB5V%El)?EqIJ;=G1`E`M+'EJJ"K&A!#E']9KAVEfKL?M`mSFD,\+9AT^J?a9YSm_8D*]9L1!l37/qkC,([fIJD^fc-isAfEr^#oJ`mr%83?tS-s5Ch + LPT>l%1?;Mo`n&""UIAG==o+q*e@u;!:U*k0ETl,!<\uIJo.1!PmOU!$JH-#d),QSeTni&% + ^1QsgC:4<4g$;t%traPO3gN&BLB5p>1$jC`;L^j;CYP_]hu2NdECQS]boM2=PKAh5)`^o'd + ?mW,Xq+?>[-RC>O@\m-cS$d2iM:qU[uK?n6F`A2rnYU/8D@W0>FnKLIc8\'), + 8lW*PaE!/(U`7h5*7X88Jrh2Xn0*A();n6ni)Tu3Q3Z?(*8lPV?uR)nTG[uRAIH1LaV1dJ. + !5_J)6a'nI\G&O + 6a)TEp1[0ER(D"SHt$7)>ot3SOfg!YsUDYSMg3/7R0]inc=tmP&,40k.qB"n5rW'1; + #-\<(_!+%r-[ASO'."V^D"\;"5LjRB0Zju4Jik@,+r\Gc15lfQt3]J_@UB + 'm?;lXX6(]B7%7>UFMT]cfh^l+W_/;GG0FYud:2-ge(f9+U^K1>HrLdi?I_C!lOm + P%JQ,S]hj[Q!3*).M(Y#N'ht.gXNW?Xb:,4W&j=Pkh#]k_;c/@n;@/X&[-t%`tU]PcDJ3a$ + C\eF'_#mBHX",7[TDAkTk)ch>=eq:U=OEh$i<.l!^!-e]#KI>.e@t6 + %_]YJA#0'K2@*+>>Pd$=pE1\%_HH[]T6p("PuLtIS/u-rIY(sJd,gDp0OhD?gYE4AeNC$-I + ]"rA$"g'mLJ8hDVbt[,)%F>KWFCn5&+=Tkq2SE70[Vl(2`4=DeBRGmd=q),p-b`l0aPLB3c + _EG'<%tGGIKDkEX,F[X-`3Hr?P`kM0Z^lt=(NJ5Y4Mde%>=1i2ADD$DNgh-=j]@f5G\kh_>srB=PfF4:#NBdH$biN-!:Pee+(<)lBl:7!gJQt_]!l(aq606fJH0Eg:e/E`.;!JIYD0]*d.$B\+n@9<,$lm+>."LK4Td>mo4f+nJ.ltmtu4fMGQ-:c*$M64_Er!a$Q491nTo_sQ=@Oac1=G2RH-9^W + h>DD+k/a%l:umJq5';)hMXn4W425OO;1D\lV9#3qqClt4jUFl-PDpKs(an6@#Mosmh1:@e( + Do5@\%p1[WoLA#P,Bq_pFXkM2ZX_e!k:`&k\L4f + ?.cq`:*2/Fte@aT@c`?t4n8,6?F8FIMb'"9euHb[A + o5J@THa&+g""\#(h95sQu4a&d,Fi>0p^L$U=c$[j_DT?2K-d(m"A+(dVI4F7MG@j`Jp3(r: + B8suUS7,[%?uMZRY2=)^QYtUG/Ol%WG96eY.shHuX+cr"GT>iP"B34Gsn + N0/!'c3?o9DJhZ%1;k*'CdWbFMacKE[1"S.72U!RJ;H8R%-9:^aPi7V[27-@gPW>$]E`?lo + B#&708"bsu>#D3%MjMCi!CSb%aW^KBs//,;EDV'.B\mU1M>:Gln"oeaT_I.>m3QmP$QllgV + GDq8H,pi]p5`'R(QOfS'B5ngdofAe1OOYi5(4)A_)[ceURe\[SP!.*(1YZ3ThfZBaDH/s+8 + 0;7aBrY%BmRHZ/^#a<89m'o0LEYRVq0d^9A!u*/3nK5FS:a8anofE:cTdcDlh,UQ56#95h1 + ?&h!^7J7d8HZf]A/J3b5adtHijF8E!bJ+5tChd"ru!3+F$3APTpLOd&\r/6Vs2s#",;A57k + `[*'k1.0^MPa`A(Up(<'-m=Vo(TN+j+ud@eE9CQY_V:kikS23jj2,,G$hLD)[K]b?jPJ#spcSO8- + 4\rE;eo)Z&%0o0qPW]Fmcu`.:7T%5%eO<@mJ[nBNa&OW`;?kr8J"8FX5>P5n!OY^&VbKrde + Af%T>JCg_&q?G6]k(@ek-\c**7`SaJ(Ji5X'f%"21o?[k:*B?feu9aAt5`hO(_\iM>hO!#9 + fpYRR1,2*k5eW<)+Ou&YF'u[jB:@S47YAG$T<+rc$\K$(H1r\#&J>S0hh-IN00t!.ULC?3P + pgGV0PGR7I5kL1,=E+CsGuOhZ_lfX<>A#[uA#iL&_@2Lh3N/k(q6GqMp2(mu.@Poo/FZs`)9(5&<4@"96USE:\kJ"$W8S6kJ-@9?2U@"\!NZ3r?!BbeRd + &;MEbQH4mKaMIk6Q_`:?#0_DEi4?50)0SW5WOq?+\]knSWiG2i7RT,,IcJ;H$SW-+'a/$r? + BX@Bh(is)RTI%dA@Cq%F&.iVX/uL(#Kh-`R)13V-cqBIDd;Ve^,?EUY$;TLh\"8A!m$BBom + P^VhK+^Yb2[^&++)8D%[4cppml&cjfn1E_!o!c0:V6PhRBcTqd.%Hm.\FJD + U5-mF<%;R8&7Qi[* + RQC17[;:bkB$5)CX*f + Z!bPEUHdePRMP2.4_Z0c=5RMJV''^,rn2#1sWcUDGSnTO.65\^06@a\=4GsbQ#gV^_d$`?= + 8diFe:C;ePaqeW]3j1M"8rQU\4S00%e"Ur5N7&%j8?!mYUrU=@4smT2&tBP]n1p@p\.JN[1 + ]sW)esns,!rse1s$u_DS%qXM:f7HQ6#WWr5dZeLTS4tscqXsK)PEH&'!P'6EB(f`71#Y5-\ + YQK5H"9_TrTFk;)$956R=*`iRMdCnF9AnqVr/s)QJVRrZf@H?P=0C\ZaCQ.)f2G@,e7j=j_ + )#fU0Jkf%#b=CNXQQQ9uRW;Wh?:?$JjCnRggXRpV*A4PCYA_1+A3 + fdpOokF"*8GVFn/V@3q`*]k8na:YZgMd;C5(BIt7#](o+Ma&p@s^lT'-\d:KMpbW/Lph7k% + c$"o9dM?=P[`T]m$oA#Va[An/`#hi@l>!B2aC&BhrZES5g;d-3ZYoahbp".g/[iKdeSu3HthgS5YK*Z" + >:1<)r#,Hs;FUPoP*=58n#oFc3NM4ND^=S^Nh_9.FgJ8eij%,jK*,cp^@$)*XbU%%WW'VGk + XZ)?f/jBi8Z)8u\5-V^37Z"nO$<*(aWrNpdPf!d+-cbf8,NhCYH%MnNO`W%4,F,ZGNj"?"M"@'k8#eNf6VI+bT)*AE.6cNRhR` + 7k!]'m008Flh](G)c^WTNN8/L^-l03)/S]AOgbb*?sBTbDktpu\8lah8iqnp`lPs+&su]1Z + )TF-fJ52L:s+;^3WfAE:B:"sq[)3a*$sJ']>L"BMA')t*2+tb7?dsJn/XcE/cuZ9W%;H]c[ + 3luVS,a2EGr,WqILCfXg6Um)#eH4u;r#Es>+W]tTnGWf<7TVH*&p + ?b:NPVqgk"8^!AD[eLc5R<'BHK))=_Aq\-m@*lj:q9(bJm-@b0^3\*Z_`bmuf+#(jDMPIi+ + BLEl?%!taY5Y%^`3-FR'ul>m.dFis)\;<-^7)UB]CbSEMDg2I&:iMureEZLj4'nMSa+XRg%n-oA^#G>f3Jm + $BA?kj",:B&01dnA1l.fDLsVG9Fr>l$I@W\T[F0tN"j->)@u6W@>#=]r#MJY'>]nI5n9Gn^ + ^38L"sW^AXh#PSJ.r'f++]Xk%Sd0]6nW+E#U)ABi7/6%%h%>Y0@7-A:pL[A1(QXol%!a3E/ + FZ]&I@lhm&XG)+Qs4_N8XaR$UBdYORruU<=U0K3H3[d!GW5ind[pn$'$F"+WLtW^?gHT$%h + Y'^u,ScQ9Pip#G>N0+Qa&^RM*F*%"W:/5kGo04:m(<']BiG!>ZN'3Y6:a%m_ZTi5Z1TN52T + :!$Xr@@"\aS?O9bU'MlY6P8UYoC+MTa$s$;.$;e5&^s5`pb4GV2A+h9oR08PMat.5SIt9S!GNW/%.0:O+sa- + 2e>FEE(EeLh+R\bm8.A2>"X1&,n'W@Q;C*NoSK`)-9bK3%XNsU]3.k*kX0P)(rjJ#fA'm551%+p^`,SCGbh7U + A57(.Z]oq:R`j$D(BC+/'k/(;Sok_a"4)n;Ir\4$$F(e(h%6a?kh%)!n+B#MY/%a[3G>;./,ZM."TGX2(nXJ8GFs/pm!ih$U%G + ZV^g;i@$O:6P-n:!E@^ddqTlEHZH2p+bkpd_/MFnP;V:o4E7o9i(_q5m8W==T%MXI.%lKDs + ;[\&<#r+<&0m00Ep[q^4Z!-o$)M)cI!K\&#cilns&jYppiVFbV@Kca"/8c4:+SlOIkCV7GK + ,*d+6F[ + +@MA*f2DAL]Xirs6$,A^K5a0s,IQHtOmlYT.dlh9(nf\qmYpQ*T]DM+iYOnDDVnS_JHOV\[ + ))&I5c#B8?F)4MFuO&W[r5-)Bm3I-;GV4U3JL_HL=A(7NA#t8%90[!4Opp3.]E/h92U"rU + K'=XK7:_j\X;KA6R+a;6T<(^jN^G1SRa5F7 + J2QtQXd\:2]&f"+!]YH1P6D*r(U(A"8bk*G:CiY9)^?4`Z5G\t] + -ih:;AaI"F7MhC0/"Uco$>J3Q.Jft(K0mL.S[@a!?c5 + 4s._V.?G$rHQoTfo5@=*fUj>\0r$ENERMCC\(#^M"6c0GAA#j*oE->Gsp>g/n&\l6$49D88 + g1tRtV@l!L3da#KCc-X9n-)'F"-F[b/cS8TCd/Le#F`&TBk';*'s;qFX=>,J-\Bgbl_/^2m$b^1*uq)P_540 + i!TPT))1!D4"K+rK9X(#8i94TWo8$dbFTd!ZNDiGKCD(X\po'qH/h4X&6Z/('n.dbQ;"6&l + _s=4<^'e%I^U>t*6@9@Q+#e7!4D@;FDA;q)gNeU`e4+_h`TA_+P/f%jjO*HW"s1(i2]ML/g + \P(.[m2jcHQfb];`iQ$1iH^Pb9ZT4FP\!ac!6+KmqRW>`6ePUYiTdugtHSWRO\\0QkZ^b+; + dR@7CG>&3+aY`(mAp,@1YKD?\/NbC$VU]#ZN*8hZ&#&AG + F88ce7,Zg43tle]PF^;oR]am-Ofpd8r_au:--e]#.H##Jk>EH4;U1#g^H>RN0h7_RV^YC\Ngi'fOq + 8Cq!d^WT7]G]7$LC=gs!8O.4]ij[Y4555WRGYLH^3-RU(;\'.TgmccN"LU&$s + X2>>S$h&28NHXU`:=4]Ha4W.H@-&c;q^<:\JP/%'4ZW<']AD,QSL67,j>2Zqt-W&3nj*W8r + 7d+-B*)OpNt*:F25tj\jbc9HFY$biY*kZ=*<9Q:beR\b*I_4ePAOMe'U?$3Zhrl'#Xd^q2n + 3;!:n_SQ\)C?`2l1$>k.k)3*/rp0/i79[Zlb!'hY9K"hfu[1u6e2.H*"]ilZJ[s;5r)\opXZib<_oVCj)7?6 + `kU(f0ut!?(Tf#_;B5,*)7:dOiQR'\&%"<;g1$$.MNEfF,R0Qlh5[ck9!pChXGdf?=+58al + r7VglMJJ]A)fMI"%[u\'OO9r-nZQhol#u5S*Jh!^L]]"U%)oqon]KY[egJ4\jKRm_Y"=[W] + 7oao;g@V[PQr_pJTW&O`l1_?GKU;kk*sZ#j=i.BuDXJ"4N[CE(:@`--0iL\uK@a3k*G+W>@ + uN,m"[,aBYo8,:7S8eQNH.%2o=XDuDfLmB3>Z"\Ye'5.-ZB2!@taFB^9.Df]A1Mk`':+$@9 + 1sUjZm5uKR!LGrFPMH%#)s1#t5urGG6)7[oQ + "8#9DA\Wb9i@VGh+^o"7NGc:)g-G7@9$=,[9M61q6XP]FmXH]==2.?J(SorM.)!DJ/oK$Jf + &GDTB<\0Bj-gnWdgJVh6UtQSe!QE866EWa+?R:!n)gji-#)R[8HaGi!#Fj*R5]c1Z + XUA[VA=$DKAjAu9"P=qdO*JRX+t-'fL*iFi-9MID6j@>X"]\c-7%)"'@bj;AQ%OI>3R3Kol + a:0)rhBJ*YY+D!'M'2[Z`02GC^>t + V0r%aVZ.SB^tZ@C%Qjp:1p',8hN9iFa;2u^.Jh`T3UfCM4VZiY_S$6B3rAJYAHC6MN[78(G + (/H\u!h,B2V:^Y02WaN-6Fect + %9B4[LC88G/g7=+@I!#\X0*Tue^u`\Hq%GI-$h5@nIoA@ra6-7=c#ECiBRfQ)<.G_0aP;.= + IU(b$t_4K[QbX4/8&L0SWaUS=N[%JDe9F*RE%$a0uX-I7KVdGLW;1*abp=W&B%WV)#DYaR# + _aI=tC"h'd6i7`tRGJ4NFuP?3]Vu&oh0cU;2?i!F1nNUZ>pB?pFpHW\"nCr&%(J>hp;8a2/)Ei(0didK`;*aY]Brl6o^(:lL[\UOYX0pGbJ8/Yk8lC]YOSKl4cPr3K&hW0#ea@`>I-L@uP^j1.FaT@r- + !o0uC`H8E;9+kfX]K:^@p0U*9\Q>FDVRh]EBh2\c + S4O+1n_A#j`RMoY2]3;7KE(&W4%1'=15"/]T0ck'pXJR)*A4aLK\_ + @^uPMF=su/](\\]?'7E/SH,Q$k`U3R=l2+j/D[uQWqeI4 + IPMbDrg]/"1EU8+oXPE@Oepu-Nt^J!02#@#/UmgKY?!RKkPWGHrN]0@C3J0;Y[4)<+5T33] + i0sXWi6Dg66!WZM.-\7h]%/'S-G6Klm0TQD6/Z6S^HMJt8TO_@Z! + 5i50jJa>*[-&Ve9li\#^8gu)^Lde"F,6PVLFu?\^QrbdAlb.OYbRV&4%Sl8_l&W&,2Rh?9# + @uZO@d%icp:kh>oBeWaI.ju6gD?mbhX.YWn#kJhGN3EYc(dD)p<_Udf)bM0@)'VoC&1&C)+Z1[,CbdS(/g_PU%*gg``UuT.@b + 9?l(C)X7n0"7=>>lB"[7RH%jFtj=d#dWa;=M((P@fW$F5^Ob.1AeeVJ[bU+KWNOWQ7mD=;b[2ZLPd^B + iM(Ur*[VqV'E9=ll)n+n!8l#.Y4olm#$7pG-^n%:$%GP2lIr!J;[4?()C(d>k4LD" + R]9uHKq,/oe5(B66*!ME1D`kU\s_%&Vg@%3<>1Ea4o=L&m2.N9Tt6^PSd'KLsT!-oCaQp(U + fS#V[BWiM^@_k/sl/\2k)1COf*6_hI/;M7S%Qmb7Q?nOKu2_1[>\uIJ01HR,@2(SR-3s%@Q + )T%:aF? + Wn,5""AE13Yo,iP]6b7!(l9F@8:A6U[UX^19mMX62[1G/WG)`9RMf>W>.c"ZNdT0Tq)CU4k + Q(%C6-,UOhZbQ332m8\cTCbrA`2!#4ig!li*las1f!a^_7h + N>+ubE"KEu=5[e-;C^6)ID2#fu.ZnM^ZRWc-+@dZK^u9MnoFE_[0L6(%"fPpb-q>+&AHad" + /!PCJN#+"F34#+m1UJ(3qS>]:*2Am$_:WZ0QqEgo!=/+*R\W*'gK4o(^BBcc#qO4iQr*uKM + [;(U"cu>I>_^=I5)AP^&SOC]0Ik0%c3Ljh"Xp/R:tGaC)*,&eaH4u*TIth4Q,nWMVAqN`*2 + '.`3D&9l_m&@B`1kCRT1W?P6ie)l'LK__gI/(47$Oa4lpsau7`sLV`(Wb3)filu9M1f:7+> + #U6r-!sDm1X.^8.GqJmBFf;`DRG\)^OQqg_+=LdcCU`Jdo^jB8*k>/+TQYrdar6]"K7PJtdpu9O(pZFC7dh(sIqJ394Sh9ZoEb@n'XkPnX5BlCUPUe=c + 'pLO_=uM?j0>3$)?]>JqtS4C>gcif2Z5-U^27#&5c9+BskVW;<;SUFTtjX`_^p3:lT>C-#h + H>EOH#Z&R\cG1M>*f!Qud4]r_gRPn_V7H-m(I=-\*``>)I,F&@((;0r;PAJrZc),\>\fJOV + kD&j)<&ci7KJQNTe/Vhud=;0>HNEFGbf_(9X"cc]]Ok?4ffWC#33"f?K)S(Qp;fP)1"ocP=)mR9ho^B@TP'+R5Q-;n+r^G5EkfO'=u; + 8pMSd\OfZZh[eP5"5@+7LP1'lBn5e0[-6rdio)s0i=q9PJAFG;f'i[I*tKl3Ra6S9h=)n!& + aoW4\#l07iS'MeNfIR0$V&ae29WaEINVD`AKpsK4li(,i_1%93e0MgpDO[h9!7;R:F:q@7b?MoLd&kt*:,E5M`#(:8L)%?Nr0?nDt*4X9' + #?!u7l(#N=qg=ELYJKPGQ^_OG/klC,9?FB%S`Tl=)le^1[hW=4_h=0:onQuIih_ju^7Ije^ + %Y6#=!6q/orGbYQgnljlhj+)j-2_(Oi7B[Hf0^rSoaT<&IpaRHb,tiuc$Ni!"Q3joBMen'458j\>P-T6dk/->FBgCaX!L0q + 7E&h<70(u_&SO[eW%q>Kb-Qc_>f+1jSPI6/)pX_qh$qco!0.4b)1b$b'`pq^>Vcgs;$7Fg: + p*iLQ,1fF]NrME5>5oG + .:gM('Ok8uZ;Vf4>=M%cF]6rf[KLY>R]=&>s[R/& + 7nPlAQW-Ja:",D<rTM9 + #n'k!K[fGXKq:H2PE&:>S%M!1oOG?qV@46&..+[>XHg,.0l%]Z2I[B$5S+,P^C#1*)#+]3e + TONB`l2E,H*\1tPUZsJ.l;"3cphO@,VZmp8Vu<-?I0h7>bge5fQROBR2p&-1kCK]kcaP^R,Gbs@?6,>@:MrQddL?Yp + f]m3kubW>:3K+)QKcXd.VWhVRXCoo*NWO_7C@l:TW)(de5iM:c6ii'@rhXDWc(%9QF0!&aF + n;FBU6%8Q>6i]cf1)s?Blcn=mK?M!VeAbFqm:nIB?C!_gY%IcRV[se?s-NS.gL0uq>Ds%)o + #AnZ$DHHjSG/nBAXW$\9FmrH9E,Q!NIgD$H$/o5!E6ilFT+:+p%ri*jnJ3D?2\L*p'^?JLE + GlV4LDT5O)/(4Z%K2p7JD]+0[F8D8'45t?!70P0BFOkNi + 8/\e%%%?`BJ'?d4Pi#$%9DQmf4P#Dd<4s.FpDY[^L?nu@fF8;G!OT$=6]_Q]QS4,%:sX)T* + 3QNFPslei#ct4ht[5,q=c94@&Ii85PB90olk'J=?qBtgX#X/&dJSk!l7-p"?%82LdY]L2Ob + "hs)g7F&ZEok4WTVBit<_=M:3f95NMC71MToBFNc(0q4jXhs-lb1o9n\-G1`<]*>Bt*Sr6* + g\_?\E>q275MVQ/?)aZ<\2c/8.uQmIG&c@l + :\Pepq0p'8n5pJ?4o^3^ON4cqAT`I$9g@DagljNDs);PU3K2O'Q^H+/1cdSmI?gYXl*VGf"]tnm@MF^[bNgMqlQ&(HN.mO\3(67)hH"Hnef;"d*ir[Lp>KEL\kqDea)n>X=i9+[G + :6F]F5kUOgX?D5MI_"=0pNGpbl4QYFir9f_PLJ@1[oUr.^_r!Dn97jl-2S(N;JM"@5juXBe + *\KH>W3Y_4M6(GNcaC;VBtE=,(qUdG`dk+Q]+`:5Z]'q/gK>(=N]N*KQ+&YZ*@)F^T"&-J! + jr+HgUOoB$[8,8f[aO3J;,ftdl@3\A7EW0Umnt`-ab4<8">4\8TeV+"20FU^o@eAY6Z%tdj))JBi'>t\>N'r=i5md@i2@)MrRhL4:D: + Y@Nnti`9I8E)u2ZrP\"!8=dPWr5%8\IH*1_Jk*a4uG3=/8s.3m!n_.sFqBgulko)aNUM6/l + S#,B'NhNH:qQ?ZqC7;lEXQu=p]qF^1#0!9Jg!7jJm2=J\Q0k&Ih3[N>@M"1[Lc4UQTm77hM + PB>:3PV?Qi^pt_S17UXV5^2!`?ot9HC]'rH?c/44K=@9W="NPc)M+cVPt[&[mI-r.c0fO/5 + &K4[WRLckL]CM^LY!\Mr/3`pC%:K_m%?Tm2KsKh!oh(43RI!?^'CE^!CccrP"?)nh6'WrEnT + T@Am-4K90=E6-)Y&+[dl:@"K=bW-o1#<(Mh-=t5.qV%]UL@#L/922"N]J"r6OO&u,*'r"DG + UffltOYN^MGaagi5toD;+t!)DSROp0:FZr^Yf2(2idutj$nua\P + 9S%LjB5ZDX]E<(k7hCT=Ak8=J&M-#_*hCD$a&\P7bHN9b\0TR@)alQ=JijQaK?.1ehb;_r? + !9^I9rtc2D6B@+J_DVO\-470BZ(35WjR[;C4X_Q58>EFHhte/ksGJ3@En[Tp)`iO0TVX/Hn + ,*D=,>D9"*7&ja1]S/WI!C8SZi1qd#O$mTiK=F"cY9^hH^#>"_Fq1$+YAQ-I0Wb1>Y0>FTi + *'24)k`q8..%+umONPcBq&E>,?Mk!2[aV3h^3Jnc@SMR+ccI.U5*7LH.PKhKN=%G$91EprF + V:Cm0>$WNYYb20PE]\"E@,9SJ0M_J!%.KPoSdOt3AmB;fQ]UfP_p!Ce5h8(GWb`-@+a\_h08raM34CO>O(*l;;%&%uEX[u5:V-%jLl[iAsYk9hS=Fh + %C4@P)Ug:eqfoI?U[n;PeI/4XjA'k4k0YtLB8<]IiBlrr_Z.r)oC,sWLR!)S[kDU)D8rX_A + g/$d<`__%Rt/B"ZcZ50I]g=X4b-G>JJeum^V#oag#%CNU0m8QYjfd:R,_7,aPWJ`J``50&`qX)KW][lMqf9Z//nm+/sFDcKND::uJ9f]%sa&q/dZ + 4r3kVZf!c/D\'9*QI!i_FpdXkJ1#ub"fu546Mju.P?+L\i^ur7]=q2(>o?WP0ZA9AHKb13p2D/=tFm4Q;dI@nslG)/_i]kCUH&sLjqZ>2YHgh!I!p;)k^D.Tj/`OD!#7p##7K5[R&Q + e0t&?L1s-Z*kp,\Peu]hYYd.mmZHQf2hIuCW!RA^#HYnD6.T6[C2cT$c]u5T_u0P-X1!_c.DJ76Po$=KDOoH1'3-U>-*f-]Q0`\;]ue"XZTH^W%,j`$lNtrHeV!EOCrbD:h,J2n + @Wu06$gEW7lFog"[YWid;i-n$aCRq-NB.r82G(^4kq0qmYj=46=,P^@]/M'7F6$q=i"4kkn + NnJPN9W_1+o^St%V13YAFiYa"rlPOF@eQXe"5)mc4*hi57^56't?U$b6MmRG1@9VX4:UQ?0 + I/m--rB]]_HV%.%Vc=Y-//0F;!3Ik_="/^KaI`X3qVbEQ_BCtE1KW$&u(XA8fa9EJI;X(+q + bGq)V%\uI0XK&AJ#;3ChC:Jpl\jld7c2^W+/EVYB@*['EcRKdP`fM*bB!!9NiK+@FK3Wm#HkS;c + G'W]LkR!S85P?lE0AODQL/@/c`I37cJYkpV^1?l,qeQljV\L2P7:@o%],F#nJI2r``_fS&] + k\4`Hrp>=pmTW#E[soEen.ck13F@=Ya\=;OJ+i>7Zc!oC?ZHCM[@YE=c$;.YXi5`W$M_ZRb + U;/2FTbd3*?nBG@.#&ouKj0ejBI8D3N#'Ce(8EoepWOY-.o/]M!0'%[jQ=YK6G'g^@*o*6# + tk!kECh_B*J&U,t5b.rce1,4CZ-Tq?IN8]iSiN\L'YN!EN`;^;s/odQE1J$Peq\nlc2'WVN + ,8D/;])_2.`_jW3EQ9'(cD6TNV'j(\n^r4AE?4O+"-MgMnaR]"N.0'/3A=.L0M`fISB?pqY + jr.EEXr@LUBZ[r+\4)_XN]u#+O+pNc)SS(iM?f9b6W")%$EC4UmiU75-?I4$0pb#`7"'O;$ + sU2mc=5$`<>l)P#nrU/4JqQjddbU"C/6WX?l>sL0c//0k'd\. + )1#t&a4&-`'B1c7[t(Ft3u/(>(t'MV#\@r0/%B!1jL^:mk7X]8-rK + ide`er"#(8= + >G27Zq6]j"LQ>ID@Qou11Da^"MZ>rBl47H$Y"pF2>'>?"U0[LZfT).DX-7OB!BFH]7]/A9Pc@l/=QKa0%2&lR]Z + e,,Z(*YXTqo[kuVi,YOhBfQ'bYdR9p)F?WJ:Me-K3a%rf8*Uqs$4!pGH3<'Ksb=?L@t6Q=nWYV\"@uUWI"7`?hPp?')uL.0.g3hHo=?> + G!UMb`4^B.I5X`0(V+)m=+0dG^=8UgZO[iFCGD,nIjZI6#;#fHHV9,ZiAA1s[Y]OCL+Hc3. + lNFI'0f2$0]Nq:Ji9.;FPe#N=+^.CU%3k]9N<&(mc?:uCX[,rdrB2h!Gq;\D,goK5][uH&T + -!R;9.it%Z+(^R;WfjD_n+l6-hT8qW\jrE%\+OE,Qoo%0r.oEAP]B(E5L2+*'&tEfcQ,mb$ + pY2cfKGmWE^#"\V--1I5q,FT_Yd=0>Gf>$72!N2;5d`;bc('G)D4$HOP(F$L-W:X9h(K&P;--e=2-DTc]e+F + PS_RGOGAcXaa7mQT+kn(]e:E#CuHcQk/$nTrES[' + kn=HG$#n^;b\0rT/BZEK,!;b0sV'e2PNK7)n*,!_hLGB*QE+8)@]l4Xk+_mimKL:R6=`G'#<>$g%RMa=0T-G-u4ge + aReRsc$SVl()F1AVZ;H6ePF`pIsG*8hcUS3bE,Z'lR=a8DY10^O`3M]<.'7fog#hW'e4-Fo + /PM/PfqG!kK:F_b;N>W,%b8$-CSWR)Xel`no\K@aGM5`P+PLm*H'$i-giFP'TH^RT=]!j8Z + 6`^#QX>WMJR`Uc(;69RnWqfkaq-7jk@f&/u8r"FR)0(.lF\>X]_(gds;_OMArA.T5JJjG\f + oVk-Q^O3g)Rc=!eu>**-(;BIjB8gAeFA2''fN4#S6!Z3F*4#*;)d1WZoD#%TOJ'i9:bn=;7 + YjM%Yd/,D4_^=LB4Uq[5";;X,%B%Lft%>[7R%dONj/Wle%W![iKK-+c7PAEj]V%pG5jsUEn + GSMfMm@76ccbI>u[;GRG%,>+S[h;H[1)W\C]ScL[n@=;O7Q%uAoN)o]FcmAFG5Vn/S8bD3# + W(PEN&9Wn(0AR8^r/O/oo:XL:]b>U\DJ,F#he8D:DP + %GS!cMT*XSKq#K@SsJP$R&^RMhg:O0d0S>h23f/J>ASQeNZJV`Y81US+]aa*-YY)9.Is) + .Z([TdHa;l*Dm4.lCmE*.s9H48%Z&W&7jDn@In$sq"4o-aO=u!Sa22`aA59$P$401 + [Df[_#"0q79f>J=6%_nFkE5M[B>#kATVfFe2H$lefR`[fI]^!_99eLN5cgVLWF8<;O!=e#< + =99#o^RAml6f0)XU73egD[:4mg,Q&@CHl^+J/&M7I_`T[,I?W?TPT]lag,W5n3*Vch5B_4Z17:kRt=GjC;q)2 + ]>X:[B)GYYZR*T!IGEUdh-=T[Hu&AfgTZ+`)08$_]RSe+T7XZmZ.t2n=f*s,-4<)1#mM4]J + M(0UA`1'b`n3dt?59f7bKBY,[""@aJ^PDq(1]^lSd4;1?88%c2$Ej)[P@sFiNM<"1HO=MjV + eGLCdVB?EC@]BS7J\b`hnW9 + [M%>R=1&K$`uG*Y>/+RtgUDD<,j7q<5-'RDYN#D6K+R8cW#\P5V*P6mJ6B/,iLk0o\GS3@j + VcfIkY8iCHRS5H1`H;u:N$AMA,&P0;RWCr\<"C^#DDAjZ-"ferQ%?@Q%Z!"/oHW?r\-BqR] + 1>.)Ml"dlb0)(jCmdY\<9gN0K4i6/EU0mF,lrEc-f2a-cQ-9LU)2ZMqo%9:4nN2l8gTpYqc + =9G?dcg>WSOTeYXgEk9!DN1&BVeG_>*^6dRV=a)%q)j*Zn+FNS\53#(s$12NE<; + \-4:62H:gb%B!A0b5Bp]'trcB)5ZnVs+0]Y/'6Vu18$UdKQ8J<3/4@Kt'&Q,_G7E7N!)+F( + k@h]j/\c-Y?&_qLM8+G!-+NS1PKd+U$&^"i1nl2#'ag-SkC)Z4HAI,4GE"[YjArO2ObEDM7 + ELINqaYD#G7*]cikPWpic'*803#U1]k]'AHTu0!\Vtn4o:W!I>-VEVp0LH?uq7pR6k4t93; + :KG)8Q$o2P=AI"XY,*me?QYs;fqt$:fMQL]OTE53P/8@BDK,KQjd& + oO_]p9AtChnA'h3OOQ:'R7?WsF3'[r/k_Z0;P%XT`=CsRbA:L$CSoL6bq;-F1kF)BBSMs^'FM30.fQM\+$4_[GT0eMGp\tJNnG!Pg%J)kus3^L*Y^^b/QfX`!&:Ru^9+h1'\+:> + aENEdl5PP)7&]!R[B`T9jGRT_-;Fl@q741@q*'$KW9H%VB.lnBMR$.@d9duMA6.Fct;j8J$ + C"D' + o:An1NI^L8!hI^K'V\u*4ur]=W"Hp3O*K`ua0e6I714HT]Sg6tqorjP + 9Q93`Yt8Zn1.-cFZ3Y,g&NMQp,,"9l34S) + 3*S8M[O),0KYLrdr(e(3/1)YnhQ8*,"FfUZ<9%QX+-N$>tQ=XpoBo9_Pbq7C&B2&Tf8fN0) + `d:1bN?V=Yq,8d^]WIJOENT_i][r'@`GH80GaGtBg*Xc1Rr\M_9H!4N/u)jde%f^UTcEKOf + ImC%Eq`lX[<>a`gV+J1pYTU.2r)@1^A%8rc<)c.YP%[Z;mFjcHj>7AXpVt311!b5ofg.kU_ + :WZOPt9l8#Yhp5]doriVG'\BVm#KfS^C%9MBT-c6(#IEu/8d>t?%h-H;:9P)ZBf2Q9+&6Bn + S^denXol'qUJn-5@I.WYWr<",]C0dP7@LJ_HV;lL=#V,YJ=;kW5OKGeg2_FR(d+['(+#GlXK'U + $!.7$^gR/D?'dRhg>/=#Nd/lQp:BG`m9Y.&]8D5^\r8pY,t'BmMfo^eB^Ys>CgN=1g/gm!C + F>u1[[W;#B2>_C5H$FZJ(0Vh;qc(ck%$r_Oj&;WH2/r3op2tng=f:XG&shRr1^]F)H,@?@< + -6sb.5s;kY!,c[F]l\i$<)[/1oDg)t)-k)cPV*5BO-CjR3In:% + RXqTcj#?2`mZGR!lb46,`-Er3Tk(B43'\+G.<`Ig-A%M<.I4EA)Oat]5/ik1c\34o5O'+d/ + $!^IE\^cRIhlifW4!C,=p3FhutA!f!p$90S'tW-U.emT\#\/R^h$0NX(Rq`]A$cs9[4?j>' + gk/HHFq%`\Z=IM]=KJD80F"*_i^ADWJ"BrQ]niCj`7C*1 + _;7sYS?,eF'5D3PofQIG_eA%A4"(3oL`+_51LdnD+5[47X`,.ar!5M!?7f70ZCi.29#!__b + 0q8Ni9PM;4$V,#V7sk^sY[AQ2S&"(qO_Z2^aoApBS3\s(5sq?=C_aN4(-5EI6%FI$J89l>= + JmkY_6Yd\`/-8Y*BLhS62Vfcct3sTAd@gE#%56R8\B_$5(ebQ]/h'+9=;lH-_`]31ZU#IK0 + _Sh/3C(O"i+$G4+o;O^;Bb;]E:^;+uM_K2A\GJ[*5=587!jZ`gK6(eAk7Q`f'o6+*:d^Wbk2k)9)Nh9V?sV&\,h+ + btS>i9e5K5crU@.q1r]=K$Yu4XE?d]Bh#Fn7i^;S/DS8khG)e_:""R44-Rpbi\A(Qa*cpm9 + Qs!bBuc_RZ6LPZ':pYcIQ@k0\g2g3C_]JdK0aU4TU^[2#\ch7L7lJ?ZK-hX]_+=uM_cT\:O + F9UiC&E.)M)k*Rtr2&[024,.frNj9kXMul"%F8T?7 + Ks]I]b`ZV*D\%P4]X]OWpS9$U%fW%A_t9!c`6_c7sjNPjaId0A1t6rMr99@lg + @fU>dK2AA_G_dbjR_(R(N4qu'HoZ9fY^6a5^brMuU.r8*FJ-]#g9ONdh'Ap,s2J[-C,Ao + deHgET\7u'HO$3PKbt'qBEasR'S-N!LT"Yc*KXEkZWP#Q=OijBgB#bs:fCYj-VZN=%6.M5G + Z1$7e#f7r9qkS/qLt;,K=qNkYS_q5j]'a_7\*.9bsXeFLX^IM@(%-c:^&Wt=:\ + o:gFr0o:2R:W)LA)UTGQ3 + 6DG((]-E&)c:\@+5NfC6dP$^BOC83Rpu8bu2A(;o(%c8-N#DCkC2Z(Y+M]LF$*_7O-Hbnu? + Nt\s2m(`PiugMoa.NPPrPJSh3u"O,$hM"l,Xg&-RMK",A-Fgtg8tA11Kc.*71icnm5Qj_kC + nD2'/^!]V:R+Ga@IcA_tROW#-P%4K[U?9KYql(0C+S=9TR8 + "bG'qgBCl8=@BL'jW2ub*lNbg4fsPL:%o&Bpb/cK$]86JPYVVOGi4"3dZ`M8UANVo;1R:,! + 0`(gi%]hT>@5ZWMM;(B/m0Q%SkJbYAm*Pi3HsCV#e1'C(OcU$L2CP*AITbE!rs`Zci=Y1V'e<9*F#B]\bpq+mN> + `ij:/l97UAhF0)ea(0'Xb+d<]6IY"bc/(QG + hGVt-Q;$.KBSDaI2P*]-7Hprb;+,;"`-i`ak#=LRdH+K?ASZI$31skXoA<+ihd1'`E3D>kDj%Y*5NJQVVt>F^ + +:Xh8OW]j'&2pY(bBCZM2"3d8cCqU;o2h;5s.n/A3CHB:.*X!7G"&g`JSHmNmZ`R>!E3qEF>a8E + rlU9AVEYs!K\#BJ2)Y#+U?(uKf7nHrZ-'/["6^'IM1J#;pQS47\F]N/.OVLsH64:76hSD/VORoh94`5&K1@mH6]U%mY4WZua + hd@><`S#3"teCi1it@CCdB[N9mDP!?l + X_6!-QEJK*@?gtM,u7W^:Or:]M1&-M?eU.Z,-XOPhBV@'%ZI6mo4F1n7`\>inmh%;B$dbZT`\`ajcmC^&ei:OV=Tlf6 + M=6LeN\HsE@WliJC(j0+0*4u-"):=0G.2;1D;a]-iOljPE15;.mgDbVEq__JG>A]mA)`2E8 + G@J^E@'r'D\JSQ+R%Q!e;CQK$Yg`=A + I(RI0Mr8Koee,\17V/*.UQE$BLQcGEK)$P53.^VmhoV^QnhDf(j;9s%PsNCp!El."F@18RdGe7t5W$ILFFY"$S,qX&s18KpCT + %@]5LcY9gkmFJ=jrR0E1>qU>*`SU92^0Y`e]ClI1K=a&0=D(AR'o0eS47l8"@2H2SH_C#'H1r-0Su6JS1q#t$UYK\2%(:`nc!JpnJ">&`+E7A'H@] + a=VR8ELSG\L5kO_7m#-B3diTB;&YA6@rT:(RMlK61+V-i'?8g6T.ICYFJ^rb#H$ot, + .sj__VM):paeAsJ/MeTP5BD;\FMQ][b_)4.btWo$,;A//8W_0/H^Wk,fB)siUR2MrM.Kfo<]Z/UG"m[A^fV4/MaCRNJadM*^hKN95XSH)BNR:1B2k\:.MWBA0MnX6Wrt\ZDg"?D=T4Gj`8Nl[(P$T + Glp#+0_N/?IQFO$#ol"$G1KO]^K(Y\l4^.Ic[p*>Rq.On9d/&_sp=?BLiXLJ-3lSr10JjK0 + [UUbdC^)=OT*d#=-]/4T@juoShNYbB//>:"FFbG@Ad?>i08@X\G9ofCrRsGhG?Oi[iopY!7 + a*3IX2OjZ>*(RH=7j$bbd#*h1OJHJYj0pA7Xp\GH*K_tEN6L\plH(*3GV!^T%U+U>iuCaBH + UOR+e"_k4"oE5^-Wq%?,0V>rjU"H!EYarg*e082*Gq@150#GBjM#iPbE``f-.8L5]LLbt:C + P"m(Y<*p%W:aLb@Ba__8%fs-."A&=I(dbGMUNZ@!;`\VenD-(%'^D;6id6I&jZu9L#nYTYL + PX=n,)p#SUJuSd;G'eZ.9/:'Plt)clBtO=R'@DAFBqnOI9FQ)C9D%j^Pp>.?`XumZ.:\K=` + M,c/Cg?KL76Op94g:<_4J0'%"2CHQ9[t8rk'%pKjDmI6n_GA^0D$kDeu,c\/(ao>+C^p&mU + p2Ahb;CjHae+@RiAV+XSK)3?Q-i]Gf%XL6c^YZ=W]*uI + .q`D[S]#6W3!mJbk="s3oA4giP,'3f3*'2_L4BYX"NEqKcPS:ZgJEbAkFIO7FgO>f;&UBAk + _0_F:6jP[\e@P+F1A!jmZ3BL>tBtEfQZq/?],STF5B>:Gpcpc4PF7d:E.f0<7U?KF4>Q9iU + &htG+ZJ.ni80;ql%i&req*.3DHjHIJ#`.58p?qhm;s(n)aM_lEZ=U*!(SO)?R]oPW=Ip[fM + 6;GEp2m!!,&'"2Qf;K>?%S!gn0?iG^u8d)$e*-s@''Caau%iX5c@+ooCm!m\Ve-%m]H)aar + LK/dJ2P?^(B$.@$FR"#WFpt1Bp%aZW6OJ:`2s+sT-]u9Ru'V_n,MTVYFWee+*>]L!i8-oSGf8PPn&hV&E(@rs;X+Hd7\nYO(n + @k^K`fmaonW^YmhCdpSP6:#Z$?2.Q-t3M!#'C6Vp_h%YM@O+F8;E=R-, + ]5`&u"et#iP,#KKsMjU%gI#:t-7JBNGK`:a(IA>R<[j#dF'UN)"0D^97?A]K!)3?ArH?R`s + Q*PC"2-)nS)e7OlG7@Wc.=Kok#In]+cS+6-%[Otp0)#f,@7XT58`a&Z2M,+M=]f6ZlKhHcm + ZIZ*>,/XYb)A^";FeksZ)R]'L6bB^g`dQHV_:(8SjoVRsfRTiIlj3OT%ig]-JW-YGdeYfeW + 6p::^;(>%pI!RpSMt(/8C2<4beO$7M.]oT5X854!C&XiQ]r9ie@FWh[&BVUQ9.d=*h4*$IG0fLlO(bX<]L>:XKritA`.0p^Y8Gh + 3l%"DtYg_W"'Do,Uuia]j+`k==j5+Kg^Qh93KXD]>pom`PKjljG-T9NoqcIS%M&Q;0>3S&! + /]lYY7_IlLm-0uYhJ#f,b-E?6Aonb3/947/F(hCZbAcO&NSg@K8943&=!4Iin@OglB``;+$ + t,2!orEh6dbq'S$LP2K:q1N'!.R"ld]r"AW:TGg/t=em&[2s.i>Qh+X$bl'a]FUZ[IbqD`S + ]ipEXm^:J!SL!ONN8\q"lWPY!^85!"j(If19^0$'V]$5Y4`(_(];7fl2&Ab^I3A=iJCi4Q; + $-!.U4i0iSUU6=:XtoCW@I":XBn+iq#QWn_I,gohHU$bd(0qaT!bj"fdp8XE[jJ[]8'F!c? + b`KT<.ZQZhkDEm!iS\p.S67HT,U(qdJp[VnJ##=tmrbEONS-^"^!o@I"56\@>h+5\.1NbD8 + (,8pF*pAb`Vr[&'7%Kc]M"D)IEDiQ=JjuhaDnH'$Rjq&++O;e0R/=RfM'O_P_+bKmBINRLa + "@\sd5X#:1\HNE"bDqtU&6T1^SE_e;lAX0qY-/#uC?oh="\ueWOE`_EK*W$<#XPW=bDbf3o + ^-Jkm>V,5bllCripLQ/d4Xe5DXd+6Y!6?fmuI=oC7+KB0^ssqnEi2&$FcE"1tnZO(c&D + 6P>jr0qD#/FUME$E@g\dAFt+4!.^O$']JFS&&n)"1#IO&4Hs;Y?$&Zipg1.n95LN:r'rP(A + 32O)VM/UD!pCg5BROmt09^MRS^7NC_dXc+r*FR(1J6NpE#-NRYNO?JY(S]#bN?O_UETgh&4 + u!odJbVAB4$.[gmrD[^V&$bI2Z9c$ZaB7W#QT[hA$0cL^Jk-:NqDq>p_ + PU=`kREtOG#.4L%UE'R1!cbsqOgQ47b*G;s=o]!QJaFN*5SI#c@j/&nKguB0FU'UA#-nIZM%j7#I+OUl[&d&:gis2`ulk"d8:kp6T`oh/V#j76T1-5fJ5!2U#$00T`DX.X>jR3Nq$P + )arP$/iCqXX/OXX1p1dX&uP`:$Xr-.'%GoCiWR!:["f-F.D7%hsN!Gt + rmrK-qGqH4J/a]_9Eoe!2N@pC_84._W1A9L3=Lf#Gh)(Gokm%#AT,),U(:+Meo+JZV(H:q\ + 9QO,j`[YHc6VF;a1BnXO;g`)ui"Y,c%H+eulH_fd+p9#F!l5EWTXP";ZqGMO9[rer$)'5r_ + _SUp!:sf@1M]fRIlA)!;*r/ZPl">-_bpf6*W^d*!!WEg"Bkfk;cAfWJ3?iMTEnqO#;a@Z;o + KjL*BWkm2A<]&PIX]B[oO5@<`>t[1S[)(b>aaJ4FsU?W9DP=PunB-4q^.F1.J/ok6dF?=YQ + sB:p3*KT0I;(-8647Yf.$9Y1(WY-SH8`P'K?9^F5Ab6(tt?(nfpu@'`c(0i;C + <#,#3)-"9AQ[MX*L&(EkL_1O5cYk4l<7.RrY,l\Hu=%1GC['gc?0d="KJOZrnpPkk:0f_a8 + D([P1'GQ06["aS4M?FHZ8F)@u;Z@K\"Y`j\q^po%:V.InXM'k':?"IF-65u2HST^E2AI9Le + Abkfb@cQU*FX1+T[jIcNgXUN?7F_35S*q`*!\cK&n(G4EPG9PBhqc^B(iY[eGa2?")C^8:k + nL:f"rd,'5;X,.1--sK$>G#6?O0[Sk`sl_2oN8>SEc8"TV+k72u?%((T9P++^Yh + p$kDTV*jbIT]*:cc#F:l$"-d?/$r;alU*3ONFA!j@?ScrQ6B^:DMpD&[^Ig`<.*Ft7kUimq + ,d;D$<:UP72U-.h%i\p[$ogXT_hbmA]_]"`M$rT7r%*tCGt)c`<=YKAa; + 2TG%q*<_5Ulgi-#H*MBjdS>!,<4a$4YtX(h#p.3$LUi-\L&-LE^#>`a`g%FNIS'D:Zm(2UR + 49j&*=;M-+t83*ASe+9@k(*uLno&3q!*Y%&V+&E*M-[;U#*UN0EH!$GGJJ34;!Z3`g;&qK_L!DA$4i\ri:T@u59q@I-rJ"J?/Ldo:ADG3$*Bd!?-,b!.G.qCQ;NUEjm?SfZB,!NN!cFWB-*>9Xgt3 + T8ksdW`l,V=R(.@m%s-t#nGWj0UGmdr&ct?5'KGWD%e3U%s8a)Fh6046l2uD's7eo5t[g>8 + )Xk:+a.!7>BqZA&pnhE'mGl_P?#eiR6Rq/4@c2l8;)^8iFiotljKI["<]hG!%n>',Rf4)$) + (rE+VGk?XA8S*?TDn/ZID'7CW8[nBC2>C!]oXgb + ")\3#:@4,UFZUCu#&RJ5YR:OS[O4+=`D\k(:@(\=,5^RMjAJ(qFc!F`UP^)?g]]J3L#M"?. + :>Fa)LJJlojZ#->,hkb\[V7YoBJa]6T@M-EBR%TV-i(CrAB(H!^!l-,h@!DlX7LDX(%gIOt + qr,8Yjh]^s_Ajr>r%#Wh/]+#1`F24I5h]]k0uOuKMP-`_KqVfFaACTgW&gn?HATVpQc9r5W + LRU[HCr1S@uR`/D*a]VfjOL:L!%eZr/T-_)+Y$@m,g?Hc0"' + Z]6Y>bt7[Crd:_6?q[Y`p<8q*D7GIBb1RZ"Pa,_:0?2C_dsNec6Y:'d4%s+d + <[,%NWB6_7dbASibu(5MU]Rj5*/aSk%kkq;EH#Di'k(U".(^n.bk2@j(ISi6QHHNQ5@H`Hm + ;u*dboNl,HtKU9 + iY!0bGqX)HBB3Q@b%29K!gB!KX_fsnVGWa0nq\2SMn'+BY8SC"RLcS-lr^pV0E'FIs^B\^$ + \+'D.dsi1_=0L*-:"27)nk'GB^FRg):IM=3Hr@YFQ4_Zs>#-P + 1eJ@J(Sp#fW0>QP_7X8Q._dNJ)-g2*MhKJIheNVJ=X#+Qq; + 7tDT;VPCV>D@8jC,\:5q,^mA5WDar*Ea-qJmWb(&\<0cp03*@E(F@YPoT(ra(fPr,;SkIK' + 5(ru^t#hrj5*!9\.4i(ura((ge*6(sC-,?c0$F2T8B(GnH2s,=tX&_PTYbgh\DUbXqeQ;Jo + m8`ahjU]+OQQcs'*3OHqJj_;pdF/e7"I6M[Hk\p;JOekOCdOA/LANlGRB[Nht8t1jE=b#!Q + F`rIV^>lHMlkWe=rP[$h>-0]pFgg5RZs9,&9=+T7EkL6^9\[P^_goR1n;\8qist"cinsO4C + G-clE+C_L/;&A8s3,)]3P>'.PFBk-oQjU9HUFa\Z8HsZp9S,/A*N](h6,T6[bS-sHe.WVJ% + ]_S?i3)@'91,p0fZhck<#'ckcit3^8G29fBm]_?,uhX]AW/QHn6?=RC28O5MI_"B=lpeq0Z + Bss(C*Os(m+uJ#2c8!#lIH"TXmeq6'ld&3LU/Q]OV$bY8ciYrl"u(D1B\C.6O/@2TjNKPYBn&^gSlQI4cA*lmg`VHV$'/;YSnk*1cMa[N"r + -lQ!2V'Qmiq4fH5'"kP%Q;YB.BZq,%rb*'',gA*go22/_9Ts;FJ!onEjZ&OsXR9`H`d(iLc + \?Q?5Tf-.>2+ + :SFe[8C_<`SgX@p2nJ[:`^p[Dd>q=1-;&?<@s8K47r+22,RC[S;fo=ghn6A_K%bV.a%U[B" + +IQIW0UgG\0.Dd53fa)G:M(!C](pJoI]>rQWPG@!4^kBGdlQ0im(pXSka?PiD0J)@Yqp+Zn + >"qQAq3*$iK'6nNsLgi?0-]/:6[Mb$6-%S6P,P^g[OqcA[uD[Q0()M + AXI(T[VMCFRFZ;ITc"n/&F'31G$gk/H6F21g + G)na#.+c@ELI_WP$pU%Ei_HF05a3hfK?,O4&,fH^iN,mZX[8]015?fK5tbaRSu-'@QLH4i_ + a;5*(JJq7dsameoA2j9Oa20H:L;6',HG#f6M,UZt.lj>$!gE\_8c1hlS + P0u)?5I8UU5+FZ(>OX*N5[&F,nZ14@cpM+'J2$O:;5ba>T)&>#?eaOrd%Kq&%VWCK" + ur7%gr^N_p_T?]u+/99iX>fc*"cD,Q(r(095GAMg6$`SOR\a]RG3F*s8_r51K!#2n8uKlI; + s`QPSKj'78P1sSYA\Ts!INMDu2%WpF9"+rC8&]gCNQ>!jTW,s%jf6.C:CqB@Om2`b!G=Sdl + +#e`=Sg!p#%iOB>WRubCC&3+r.X(DHiN2F)u!T7.":3;3rdPUNe@'(f.n7i@t\3U@6C6)8W`Xo+oTh + T!52Ms?RS5[";:MJ?CVj.1S<#UTi.Q'SlobO?&\s63/9i%aR6Dmg%WDphJJdmj2/Hr_r(i& + \N+4Z#WeiL2S7W>q8OqKZDB\sqBKRSuElF9nVj]uY]G9OB.[`Bm,>-%09OVS-i8=:,9lnj` + :c!J@\)(JLZUmc)M5c;`T`7^sZQYgnX;&4FRgoU$aPu`W?\#E]kXXpfd'%FqEBlbl]4M5Zo + .+:ip,&1^/QF@(;brMbg11jaa\-E2d9f]+/VQAEmZYnkFH+NC?o\c6\NcSJdb4*a!rOWReG + ?q,54\DHrl + 2FAhV-U`tm\rPRKi;+fqbU?fS`4O'&'2a;F#t"b<6YM*6:/'s3PKXI12H`HJDP5:L.^o38N + .I*#Cg/D(=8@&t\]Brs`W=FA+^ls,aZP#B?R*8)YiUn=t+Jl14#7qZBrT9X]_]k=l[/8[Ir + 0_3K)>.k4"D0;s92T%sf4W%o#[CM[5l--]_.<&m0c^J$JjP4INlLa`&e.Zfi:&CJ@FYgP=P + EFeKGDC>_4RKaV13hX^l`Fg4qUZ7$o-,(PHKYM7^&&"eloXC8?[_XAoLAf,48VITFW9$@HB + 2o"d=G\:,/JAb#%6^mTOidanQZl(aR0FQq2`3ec5O4.bi;(N4LYVWloR><8qDY + e/AuH/8F9Gcra16]Z:p,"pBMY?U)$f=PKUe9=8BFnL3-i?_j"-[uc3cV(JQ!:Khkr-7SB:YRJMcN"Tr`< + _f?SlRP0NgCrF\q%.'p8[S!)/F>GSV&V6)PO1b5J_91-:Zf=J"O-1e2pH-*YVbI!_q5f9fQ + F0N7r%^b3E2"S)FHaPqN;D*m;2*u?";cT_fFfrp#41]XJdojk:4;5RQo3XfCp,3;t0 + 8miRULQ2KLchGD_F%q6_Z.9u-D2iK_LdiQ1Mea=.^3^:.78$<8e8U&eL?]&^'$.(*&0Vc1; + b@n_]1MN1c7Wg28aMo_h9p8PW-LJ@>4bV"9[kT5RKm:H,K79_uOYm&-50Dk^XX?L;75\HhG + ^jKOlOiOhEXY&!m^p7IPj0('4%Ej1hjc7&]V-< + /o21JoFqg?\NsI9_/'q(MXERM)CL^<*i\Bi](3\[o>/nn7tQ<.o>JKQpcNn54G!>kP@ZDrb + :'G1mgWh^HcP)6e4UVEV)fEO9dj\5F6KBdC,`0F?MSl6J_i0OF[#E)eNQCaO8Hb'q%$ + /LEP0"pd!Ups-9Ka4P5"Ti]8V)^"XI71f4;G)$+\\_!.9:^r]r42]s1R(t[HiQTs[QkD9^`q2fA-8Zm"\%b^91,aL&C3V)g9br + /Xiq0m>R\Vf9=kmP5kJc];m]$+[4Yst2(@;"j1*,,DnD_&209ZmMXNX^`lC@Oa'6>LC@jgW + J36-ia.pXEXF%U%,+l4ea6>"h;ZB()c,,Ek82C@h9*E,LDD7#$8:toU)qDWEim&a?"is:QoJT0TMnH>m`Io_aq + UL@,k0,*mn,_6ajd1V'W&bfq*c^(b);b3U\gMOr2d!$/AtUce?7-X4[lGVb7k%I_uqn2ZUO + Nh`WW\G;VFrX$*CjM]&^Ck@V!ZhB=fGtXubU_Wo_Ffmi@q6`nOG(KPs.e_oteo_1ZkeUib! + mlT>5s7r,rbX2E0#)U-_NOgNC^>U$A`ah4Zd4N)cp.KN'aNm17N2U*Mg>h:n+LGo>g;M<^W + ]\:XO.aBn%:-C.+eLT)0/CNo;de,mYUrGF)Zba1L + [ms\r:45XVJ2I'glb#Z+d7S:kBA7(C6:$,G'u[)?XK/.]qXCeOD9`lo'uKYbJt;4Bl8ldY>Do+%PE7W2tKn,A=r2,*Q8a2S=[e>L>uPf0[uBdYlV + a,q;hF0ir3I=*8c>KmD9uHInF/=/C8qeU4if97t;@afC0s@!,Qt37op$fL(Jq,asDu8H_1F + $&q8Mm6b!@MV+b`fU[u9>t!?,6r[^kc\S@BSWV7#8Pd:*Z^j`Roh8?'YJm"dmOd-PRB"Dq!NVU6X![:I3V%)k^e^.`ZZ;0?D8`F + V?i?H7m:be,$i9k^/)Bsp0>dHi,Q$Aqm>-"_;$obgdT(bU;(RXiHe!2+N?YhiLJR+Sg + +=N/bF"&p)<=deo_-[moh]Vc&1V5MmoT1WD7l88oXnY!fq+#eKenT`m[:i(3c!T#.?fc*2 + r?/&WJJ0&B0I=9f4G;-ff%3&4i,uclKSiiC%q+N2AG/j]##XI\;WSZmAL:@;<&Y"(:4sTR: + ;iG8E)#:l>*tCAji!VLLg>^":1=,@@I63dhI5Bnf4G4sjo-@SrZPRL7q+Ahh8H@"h#-"%-: + NY:7?s3)**_8GR:<9q@_J7?blu9q[c6\G`_J5!q9Dun]AkD9=u,/Foit[*'%Q,(' + kX@5X>D*elg8]rae9Y"Y\gA.6]!"WU_t+jB@BN9!p4*ER,-@F]&W8B]RBC7k=>fPZ=4_*RjA+d"c%Y + V$'6>0suBQY?HXO#s,d6,'lD$Go&L.G3*EG4G[6G7F#^';iB:!KNLl'BudNnNZgfkmnGD78 + 6e`ng$pgOila!_-Gq#QTT+ip$a.R#kir<'u0URY%1Z!]AZr[d!([lKX]$DPlaZ#%d);2!/\ + XlL(fip"^PkY[UVhe@jbcm^#mW]OpT>7GbIUY.$5_/S?ZtV@3djqafS@FkR94CX..X/a__e + 2*iR8bRT!d*N+sQ3Pfl"lq2J&/9BO`F]!S^[GF-qSj@1pZ#%&r8&]45e0Aer,iiIZgO!XJ` + ^o#E.G#PnD,%\-##&lOOtaW,mFN=pk8a$T*V&kEEX*ZV%j.M7=bM#]mN,KJcnt<*jDo6t"+ + M`@WItDY6NcI_DXQn0cRnS*0(RF]EmF)5Y,s_(BQ";ZD^P$n2`:%kTkbu&mq#7#*\"Gh[Fq + E.92;uX[8^q4kum?\n+3aK`E;HV=n!cPkF8m>B#$\qVdkVLlo$n.U_r:o`c/'Sn@jLX2M:@ + e)K>8"nGa'DjNh3%''Q4>i!r9I>AE5bdpPUb>K0o<.l[^2\5>qfCWl96KH:hR_6\G%2`TF>![T56C0Dn* + ie)n5WfKIg9WL8`*SUltdV_cH'tMa`-$\BN@INQH\k8;K451D$@C^[RbOR(?jXWnV:66='> + 'jO_o7Ip)d[ff:;["@X6k'H1.C%(SJHtI"jUZ!OrmG0.E8J=Sd5TqLOJ,kGe#GRXj<)J@K] + UJG0C(F*dS!qY,#Np8`NFZM-N9G>EC'2#5J':@hcJGE98'?T7FZi6V(`GK+PQ:+L*u4C2'/ + 9Ffktdru:eVTRHRkrWf0Gs5AD;iVkdqTjT[.O< + XO/!!;?HqN'j-TK%Cuk(2/'JV\].ppk`IYPUPjIeW@3#P%fnFB!JUmg$8t;`=Mjo'6grCDJ + 8VIlMe`..;'JhC`a@hYLk,4\K155MPOl'G:WSJ%))4W(\e*$&?!a0fXhd]Z5]F)mQ@%r\km + X0r*/Dm#k'fJ%$9#R9Zap0:,@7P*qe[CGThQ-O_s2DL"F;cfe=GlO&OLD,I8hMH)*X8>inr + 2*-BgZG/pF2+o)3ob[?l+X* + X`:0CG)hNXA1AHe\u&Rl^XPHX-CfhQOieeD`aK8B9rA<)cKD-J$de\SR/@"YLbV^\t,G7rm + KKf2X=)ooqf=YmFHY4qaZF]iplg0/h\rScKILH$QbX5Vq(,j(*,$bC&ZHGpcId@+-iJZoCp + elVu&R-me6P.^>GDAlh>n8[8,b&S,Gh`c6rO)og:LCE:G-jE2Nb&c@>)8 + +ir)*OX8*>Cm?A>P'j)Sb*%F[.E_4_Yq=-sm'A@gP6AQQb`a$k0hi<4dkf6ICtUblF,LmrAD-^3Xh0iV^($1-V<44AU@oV#R4_<*.ANO + MX(4E#l>*b/K:7=)[ZuTA/iVmiO2[-s8gm9&+EErd&^NCN?A_hLc(N&2VhFA0LJ)8ermsO# + Zh>Mb0(ZCp4?tU5_KA_O\_h$ck^&Am)9NS"SHW&;-[El5Y^+LmERMk_T4sNp+SG8sF%9>%3f_$&51D-CV*9S + >W@?LnJO@*k7saC200AWG7TsZY^&D]WH%.lEmg6$R4!/XkTl/],M-dV"^NbHp&j'_'[[)$irl=)fRN,]kBY'#cKkfh(Vm>fB"G@u$ebu]NTcUcAF + F(sYs8!JHLq>bfe0`k*L%"XL>!.V>Ss3A124(j65Sl<@KcPbt1kHt&VZgN3:an7l3"TP,nq + LVN^$UEo5$(f%*;3rR4c3bS@<&c:1V_\7p9XhWNA'b"i*7LHK%&T^j[XWaZlsuF?MYsF6Gd7T + iXkM>s#02DT1,;='k:MS%3b2p#3NlNpp@J`HcAjkC"Rk=,A%X"b;jYJH5E?&$,(eDoMOe8d + I0/gZ51r$?m0#1dP&4Dh7C9$!NS)Q;\20Jgn,8_AH&T6jtYBl2c,8% + mER8WQD:5_u(WT)($;],H'LCl^WOdV.dZ8gQ?Ij`g+,eW + NC:CFG,eo\8VH=[Fg7>-b1aGmEGaiuM76OYhHTl0UX*K+4rR,j>\u'5V\J:(QTF_PgXE((< + hRrGIo:#%9;qMAWRVoj;sN1=X0lQCelp*t,pC8[KX`-,5Z[oa5W,ac5/G!\%q7g^*^0V%ak + *Of!e;Fj-%o;6_E1O#D,$k7G/i.g>?VupXhBFmcJcZfL8&,DbD7l"l)?C9'LdsV(0G2=`Oj + Eq7P%tKGK)V^om3n3gi\[GdlslO%t[4J8thilU>@.UVPQJBde&JJ30aig')Or+g^c6s:H%!FcNu6eM9eZ`:n4L\9+!=IhFpuCE3.%F@eJ":p$MP_XD + [a9^rNWu$uQ2\XpV:CK#l*mp[anIroo> + q4cZ2`E!kktk0jkQlHZX:n2g_LE>J(e(=@CD0ma^VI2F\i(ib$BZ%X%;!#sm-&thPn5V31N + p(%b3"gP$FV%OP`Y^p0e)?fKD+93"hrXQ/R*,dOJn:(\IJK6&9#]\$.W.PA\i<\^q#u4D(C8J9*]#/MF"JH:4!-/K+&Vh*`Y.qOTcc!g1i`f#n@9[Doee/F$o/Ii1)S(dd.`\GX + j;H1&DHnUislFcOY;aS;EGB6/IsM,,V\rPZ],M,XUn:6b`Z#fd1X]b^Es=G&^k@jdVR4Tda + &:rcYqP9Yr'f_Kfg__.#VpV;NB.&OQeJs4`CK'%\0#dN@HUjdkPI)m5Y2fd4DWs5;W*de,; + LPs$*jk5]d/pZi`Sc#^cZ#'*/"5+<1E&r(c106=KIPZLL9R0G;km09Nq$^n(hIJf]KK+nI3 + PY;J'L;).]C1,C@JZ/nC$k;)Sp7Rc8l<,E^GpG9%M1[q@cn?4Z<,rDj5p'bRVe;YV)DVt"# + i%ndbdq#RWeMaQD%F];U:)tQ3,9q0miff!>k0#`FUH$OZ3>,+J;H$9\Rh65I3\k1B1T8Spb + <"f1k&DhFYq5ioG]Y]grXscE:4P<^B[G1dd1>l<;q2TRgF9G#4t`T[;s>clXX_g6;KN@GPS + $g$0iI[%;Umd+2*4`Z4]Er3'HmMe?jm%.#soa7^4:MN[9Z-7@9+Nfbpj<+e!%O]^_q7e8PFoQ==,rE;!I8.l'^oSGErPG&7<^p_c@o%cG&-oJooGOp6KrBQ[fu[YtgLO_=;1!am2R!H8a(/7(2h=; + IlNG-+lSIp#`b=YshqQG#)<4\'E`1_?]kFFL6293W1]`jN-iZWV/1a[oqR+gdkHE[+i;,rX + <%iDXeio6B7$)#5t:b0]ZOb6]FmM\!uC9Ti6aCrn]&q[jHF?krmj('lYG%8Kj(:6K[,FRKk + Y*F]`[@So3rQ7i10#'Q:hkn!Rhd[KF/6Tu)fA8\;BkFWNL3G+V#B(pL][s`J0JTmH7GP?l; + 2i2e?JF4R?#[hHP[;&IpFa>>6*VPJ=\#^6%Yo?\BfqANYJCQ>c#A]TeCA+]D'oQj/`I/QP_ + m[<_Zu&t^`2U+j1cEZrd'7FC#tVMZ+SObO[cKC>_]eJo8iH95F)Pu^f+&PM,>>".CAgf5kA + _3RED%$dPg(Kj*jQL.jR]'Q1trV$j)Ztk:3I:lZaW^H6RT?34;UWh[/fE/nRFB%4#INtQ9T + O(#Bj&-G.(%A-_k5hNJKNOLj+u;R"^@+ObidtLtJ,P/']!J4:m$*!rscqhuF%MYTB1>HJ#c + dThC=3nc7=D#G>l;+i5*A2gmTnI'Z28THRhK\)KZ>`eo=8rY+$9!Q-obJq=&3rpcVV,cFgu8g#DI=U>DI>ALU6@K8#;'PQ\jC\R5p\')4 + d1@T:*6`RX(\qG&e;u!Y""gnI`6Ui1ZY!n!lALS9`0lq9/n#d>G^^!PfTf!?Mesj,MaPNZ$ + rM\`jEeh+&@Rh,iQ=(1A=h&@mm'h-7DrON?a<2X8A+b?Xc6OkCb<("Z`ql&qA + ;CNq"d(aSqmm?:@s(t*\!B$;Q9CSZ,H0:LG0sl3pN*ArYH(m(\iZn&)m-\ + uYI_?N]4>.g6')f>#Rs&'!>SCfdBjNJdB,L\]k%Bg>MKKgiN;#gWIiK$&*O*hdS3XL9s%C&]AOII0/r%2.bbHb:p,!.6JHS!:+'=0UuE + #UfggH>;@$\CH-EB,t0\N@1P99]k0V%]IIg/jNZ&Z**LVAMlP)f(_aEL%M]:NdtKRWk(PIl + t5NQVs"2=aO"KJRhrHLROL-R"%=>ZcbG@b?MBt]Xc5`\'-tJbYaH;i?Sq!mn]a8K0&!#5]IV`$#]*iHq+0ch4+T3^ + hr%#Xq)+gt9U%eidfbq]YpTj]`Y.,8,MhHf!^5C8i*Z^R:)6,X&fh#"h@ + 7iG&^^Lfd/=X:]G57R*TB1%O4CA*do"Q)'1j#=_XP[r>9e$s,LcDse3$#@q'3h'-e.rB%eg + DX))@B2?aQ1ObZdr/0=R0nD1fE%E1'Qa[l($B9?7!`VRTQj14RsRH-#Q5\36=[(i-Y5?PRg + ,Qp%RN4.4h`MhiD$l[@+F]ufTs\a076lorPWd?G9:`!O53S"9BU!6OX(_B/[EmXF@>?L.q] + na3.A#,#9+@;6K"-fD]PiI;1'IgsdF&`M5`Y[&3@?Q?)1Ku+la_GaCMSi>+Y&(?p[J=J^^H + 7<-+9Zd/hJWkN4)EG/%&SVQFQs+R#+\) + *I<)i&R,Tum!m4oM?WS>Y*?YM3&'b9"Lr^;p*,bPM7Hq`hXg+2'mi^_U?.%,doe[80dcqot + c]h]q=PTABJ-"&O5N=54hV.=p3bS'EK0b61Q--``)iSc[nHUY]s+o)$l!aiUGKQ:*gX + 2`T!#9X#^P,ECZf:fbAm*+d,FXdME=4=qYke` + U"LS + r_3p%obQc*kLe,OL%gb_;dFU&,o'LPKW-1=8]4ZB;a:0[Q6R3])/tmCj. + u>jmQQrfCJaprbkStH0P%1rVms\rN*X-ID3IGfB'*FqENX37 + RWq/kbd*Sl\PU)4`P,kP:WKb + qiVT2GN=J=F\UPSE2e,o + 4fm;`*Sj:K)B64&An!'h_BQod*I6h=&i7S>?KGKQ"sQ(opp/.=&m4`?WXZ>H!seJ*J7 + g'p,/%CflffPAQcHqT4qHZV5%eZG."Ym>B[]lD-PTQ^N!L;-,FY-Fr?ZEOTN=W*IrYW"oqt + RV<`J7pVl`NLmR)BIinFi"+aj)-4>H6pdPpN@eF6L[DUjXH + .I`JYZ85b,/mn*)J_^e@m]X'6go)uZ[bYq3_1JRIbj#6mXYNn<"e*$@LkG7C#hY4?i`%dlN+`6B+9W=/&3L]EBL + WM`\6[dg15h940/I:R(aAldN/ZaP1T#3>RKG^4OfgVV@ZJin4%bNm0r;8`nC-FH;C]q"WlO + 8)=k_PIOH"CdQUq\?,ZA38NL[hnfc]"9I%6 + @Dq#f,F8JliZei&2q1p+d;\OQ34#&t;gGEDC5u4(0@[T*1U69I^\m!WY1aL#AgA"fS(,6f` + ,#W5hkI'iU'V8XIGAdoIsJDiM$%Y>s6.*,?74NjY"Fa3)dNCJ98\Oo@X(b2FLS7<:)e2FY& + Pq]jiOCi*'=\C^O)ARr]'Y]=m#2jllXV&Z1JB%g/*Ripa-PcHe7_9(A?Te*:oR6*$IcQ4=Q + BP$S+Qr]IIbY(duAjQPa1F_)4oG_UPe%N2N$JS9h[;KU!/%:QdD`moINS^/RHIJ9)cARfGY + #ObYc\ct]S"Q-]X6mKZ^3s:2kl'qtH(X@'J^R3-8FI99]Q],$?@0qCD_::g(UE^?gYmMJQ0 + uAQ`qnRSm=&ujT,PSJ7u./h'gYY?"-jB!05r_!790eoQo>7Xr#p_qBXGX,P7(cG0YOH!$jp + Dg+c3H<+,:]TA@5=9@WGHe.b6)g>9k7WXpo] + g=)="*$j;To\M^eI*2+[Sc;Us%K#?5V&o-2BMf9FmaNA]=.r;&HZLYuVb<4-ojg;S)q6JAY + (IMltjeeD'T%.Ipp?ZX%Y-p,F50WbF1o?5\[e!?)>Ih!s-KVs1o7#W2$-,&JPipJYVKQZqX + HdnU=jr^,Gs8%GF_AD?T+gZl-[55K-j9\&QetkOX^?PMNjI:Tf3)m+Bs%'Z\_!;1?Yd?)YB + C,ffMINR_Qe,-"5PC4@%\&3]JHo[!"%A1mqWFsZ%TR:gY83@S.D1+,<$/Igm^rX00iPEJJ` + ,0Ti=mFnHLoV&k^,cB,5E&n\%MC1#JA9Iq($D^giXAi0`T(*_D3Q+FKd9')29Y"I] + =h6_0JrPiBMJE(Lm.Tfh0-M\C4Yuu"/8drB_3EBRoH&1Bj?=@GWV45_T[l2FA75$HdWTTHU + 4Be4WSu,*`RD9#;rqBk#mZrqran4Kp0N"JB/`3=Y#_3?DP5\ + pfn:^@'uT4l1D";ZC3iU`Occ[#OVkZEB+oR[J"!=r&h]i+L?@-ed6!72M`TSsD4`)[1q(,H + ,eV&c=!kjT+6^O?[mpe:8tr-f$5r`9?FS0R2S4G1P]D\)\u^POiY/!=FCH>ODgml&sr`uC' + ^b,0BFjbZ*83Bg]4R=?m\mTZ*\`b*IXcH](CD4C@@EccTTdlIU3*IFHMPG1)p"!-,]Xf:3b + .bcdHh)&9r3hkR\$ji'Ei]=pA3n+U"o<,!,YTGD-3ueh[UJSQXlFT\e0&'=:C4MrB_81K8] + He@\KGhUb)F;"T6-L7`g`/W@*C9$762=k2:f<%h0EQbOJaaP.!"Aq/e\ + ,&b_T4,lfP&.fd6dab6OY[d(jEkJdf^3i6VK#J=cED8!e;C%J?T@]?k\).++^p>,*3iIW$R + $@4oui]K"rc:0J*?j6:p>/e-W`QV<#geGbp;FSN27?gZf&2Qd$@n$T3 + 7Jp]ua="qPB#\@m`^hXMCh^m?C.>jO7Z;0'XDUMGD?=oh`rIY\'Q6JmE5t(\`nYA3]PpAOF + @R$i7lW#NBJW/KHH2h*8'H?Ngj@*.FN=<&a5hS;YU]c_1Is'La>AIh'RWGNKh,a-aGbdC5V + 'otK7[?daNTL^0Io]4Nl]_28E>e%oTDbd.Y:S!a[D33>a!2+PK?GtaM^86KU9f^PtARfJkV + ]VUm(qVSOrIEJukM7[%PP]UB<2"!^rhV<1lhdTh4%8b$:`6r2*)fWCi,N!^*8Ne>u8=.E^/ + Db1s6okVGj?ZV(9C9.[Hn4L*'t[7`'T9$f"[>e3RQ.Ebg;b:Li\Xjj\BXU:4 + 2;$FQuV[lHYpc:<`'"h!<8Pm8!kLm/oZqpY:P@aEBH;]'+p# + b5Jd<;XJ/3^''S-f+JB/(JA')W"!uK/)!gNonAHkLB#>fgS6MPoSQqYl*'@=Dpd*3"rh%!9 + K%oE39;%4+7*>24$'2^n,;,%d&>o$:S(K#St;2lGjSJkA-)c=9g;9^+Yh&]G\+&VtZ;@OdI + *?nB0!!$"E"&1'Gh'PsX-W5@@4SXBRQsEUg.akg/d`j"7h(DUm02ha&;[kH[*AUPF1K-Fn; + b],J>rGVu2cG,a;iNe9SN9]O%"W7X"J%[\D*CjW5?%MG4RdIB(e7OR)Upo3<)#e\>t.e17o + Xn-?/@A0Rd"rB3'Fo4SX&"Qr6t`/_7QkJ@/i + eh/69\DcZgUQ +Q +showpage +%%Trailer +count op_count sub {pop} repeat +countdictstack dict_count sub {end} repeat +cairo_eps_state restore +%%EOF diff --git a/doc/handbook/EPS/car-propertynames.eps b/doc/handbook/EPS/car-propertynames.eps new file mode 100644 index 00000000000..e8a3b9ef540 --- /dev/null +++ b/doc/handbook/EPS/car-propertynames.eps @@ -0,0 +1,11676 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: cairo 1.8.10 (http://cairographics.org) +%%CreationDate: Mon Nov 8 17:54:11 2010 +%%Pages: 1 +%%BoundingBox: 0 0 408 407 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%EndComments +%%BeginProlog +/cairo_eps_state save def +/dict_count countdictstack def +/op_count count 1 sub def +userdict begin +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/B { fill stroke } bind def +/B* { eofill stroke } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/pdfmark where { pop globaldict /?pdfmark /exec load put } + { globaldict begin /?pdfmark /pop load def /pdfmark + /cleartomark load def end } ifelse +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +%%EndProlog +%%Page: 1 1 +%%BeginPageSetup +%%PageBoundingBox: 0 0 408 407 +%%EndPageSetup +q +0 g +2.568678 w +0 J +0 j +[] 0.0 d +4 M q 1 0 0 -1 0 406.886261 cm +1.285 1.285 m 407.242 1.285 l 407.242 50.348 l 1.285 50.348 l 1.285 +1.285 l h +1.285 1.285 m S Q +2.552087 w +q 1 0 0 -1 0 406.886261 cm +1.277 72.527 m 404.613 72.527 l 404.613 121.273 l 1.277 121.273 l 1.277 +72.527 l h +1.277 72.527 m S Q +q 1 0 0 -1 0 406.886261 cm +1.277 143.609 m 404.613 143.609 l 404.613 192.359 l 1.277 192.359 l +1.277 143.609 l h +1.277 143.609 m S Q +q 1 0 0 -1 0 406.886261 cm +1.277 214.695 m 404.613 214.695 l 404.613 263.441 l 1.277 263.441 l +1.277 214.695 l h +1.277 214.695 m S Q +q 1 0 0 -1 0 406.886261 cm +1.277 285.777 m 404.613 285.777 l 404.613 334.527 l 1.277 334.527 l +1.277 285.777 l h +1.277 285.777 m S Q +q 1 0 0 -1 0 406.886261 cm +1.277 356.863 m 404.613 356.863 l 404.613 405.609 l 1.277 405.609 l +1.277 356.863 l h +1.277 356.863 m S Q +Q q +q 0 0 409 407 rectclip +% Fallback Image: x=1, y=1, w=407, h=50 res=300dpi size=1063392 +[ 0.24 0 0 0.24 1 355.726261 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1696 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J,g]g3$]7K#D>EP:q1$o*=mro@So+\<\5,H7Uo<*jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/mioWjnAY&^gM+`4=1jRLW!YA=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&t\ctIN)4XQLiVpoI(>.nOW?*DmsG$@,,f58"PDKfeXi0S^6MAH=;fBoLrSBh/,h + s:(;^I++H42=Z<#66um1_g).O;o2C_9e,`4-"8".geU>Ho2>Ld-NIt_f/R.&]i`XtU3)8u'qju:db$ibKoh""HF^jpG5LL,P3rhdpVW'9f%a_3R./kgc/#LaR2"2e)^6b:6Y>S51#M + Gj$QZX)"'=ZoOSh+d8T]Y8^XoI+%t%H\CrnPo\Cc#$b<($0#r^?+57DFB]tYgNsG)`RAqd([^[@h`(f2V#,#RnhZkN2+,G[r.ZnkI^%)%eI@$421U%Mut3k_!l^erPJ(E-PaH+"!7cgEI:$da)ZQW + /]RAZV_u]hgoilcMrPB;d.D`&5!+\Y(K8h+?#NOnp&/N7eVh+rCY4R$LN5KMrU4UZGTDinh + KSqV_;feBmuQQ?io%T6q>PT%j8*)cU[.atc`QPl@drjSqd+cgnb_2k^[h9=f@0Ejs(JblNr + OUhpjHM1e,"P9TCa[mK>73\T9Jhqn.#B48,WHr6jF4\?nK1()AZ_.&V&/"#!hB("5>m]J?fqN=M#H'\5S]8k;iWT82b+/@*2B`9$=VdS7sO&5j6V>XEXG!$3=R-!\5n"*ItH;=k74QjBMT+gX@:YG>(kN)]9e-:M$3Xn?Q70TAA*edGE)+3Un&Y[] + HLf$2b7;0XSUJtQZd@*'ckk-*o[#U5G8ju>".&0r>Z1Qd],L9rXrm#!g*Q + &u5XO#E\X$o`h,>.Yp3B]gJbV+FfG5P>eJu:T@DaW`9D!4U8e&WtfBNe[iFqCB[tE6e8?i6 + 2t)g7:iT$T!FEGcWc1,E37%3\^s^Y>q.pH*lZd$). + W#UOg2>iaB;.^ASf>PZW:8P=3CPUXTa0\2?(,:qb:d?9l.4M>!Rl6,Vq6i;$9lq*&=/gnV*T+b\Ms,4.:/]L0UE + t6u>NNoWFa&qLG@q!MA#Q?s/3`"OI-:l6Gr,N:[R?Opo:Wc%`FV"DLQ*RPob?8I-N\ + %f/a8TDh3HTB**6L6^5%u,!KQLh=gRC!BE?#k*3KO+(Sb&N;cIqE"kKNigoBq.TK:>,>6+r + u!5l]4-.n&U7(,n`3s3_fY8Ei8hV0%OK*/"8cC!AT7FU"Je@r-.GrCNur/uR6'VcF>u[Ae: + tXrLWg=q.THXbZ])*'37gl`GH9p%H*=)Xh*qrJEK<#0,=X,@qDUYHi>#cIrAMB5Kr<[C3mN + )T-o=D9]Q!G6ZsZj&^,962i`2mcWCfUNLJ%W&Ft\n(p.DH#I!Q]EXu&ha@\e"=lgcN8o);XVm%"@s + .L\SmXILErMh[AD_2hpiIr*kW2)<8AJO7HUIaG`EN5Q&!Xo)<:IH+tsc`_c^-*fJC(0pCh7 + 5Gn)Ur*ao$>O+FkaRu.G@=(d\0R^^H+9(*'5Ns!!U\0]1-!F?NbrspgW((g?RiEm,C![$B+(%DSAd0gNU'bN/5'qnkSnJr5S* + tg:W&oes-JQO#a0b[D?'6-/KYqd&&(,9M=nS + K'sCDUu1(QihVZ%3_]Kbr-n(bppcEK(hjOVr/")6oGEd?tmBV&D(f)A0(iEN^:cYo@\j)mQ + ^gn[TVO]c8cK*-'4C;:-Agdi@o.*O49Z+X1qK,-iL=dO?C22'Xk:,8*-aE^(eS5pUJ>,dKc_njt,? + 9dMPt-$!9;;IL[+)A.OrVnhMp$[eoDEiF)([dngSdX\?5@^YN5.&t:hCKW)!OKM5OZr3F#n + t*4'n?ZmcZ]_@=nu4,pXXd;S.r.3r1;PFQ`3a;W.>q%P933\Q>:@pT/ZQ-*14H\>W4-N+\. + Wj8l\`2moe/)q\BC3ll_cEeWk+B#0IsfK1Cl2jaY@SL]SkArldEK<,ec&]^.[`3ZS'#A1r$ + @V^[(1$$k"mJ5er=,_Fu%.o1[%q0/ZEd268kslpeoE<5gY,`=-@u/71=,FN83;2m[bL9T7& + 6KfCl?aXih$%&#[4Urk6sb7gF,Z^T*:Us'LJbb;u"9[M%#cHYsjc:mIam-Qn9^s>s!cYWhJ + m/oL'#10Ii4fo^OXV0(L.6_h75#"Ame+#Wh3n8(E5I:.7m:/C5@+OrUeu`Q:/UKc8JO*"jf + Su4`m@)^fUI&gpg9`EbmD@W-2+'2[gseIBF?nZaf\Xm97'oMA:%I2$p>@c0i!p,`mLJ0X(I + i9\iJoJKFE'e<,i^L8Zm0uTpnXaJ/P_kkt[?(e?% + n0VWm`_l;Y@\'Ys%a`9WH-lu^KGY0\%=fhHA>mPN_eFPQ=AjR3TY:9OH_FR\f2'i/`XnrFq + imjCpB3qH>X:iA,]FV+4''_QBDp%9-M]G!Qq&JbffFq54j-"1s3&huTaQ6B],2\e&A@^[ + ^"Z!.t:2'qft-8mkop,uqE4(NM#AC676``o54ed(\*he + k@=NA.J$]eRGQE89)C9\OL7p_:X,"Z>lD2N<`pFA:[_ + >fs&W3?=)\1B'ml,ZIooMf=ETUeooWC&70G;7"EgCA=]#=pVZkFcQ*m^un9:0PKj1sm>PW, + @:mD@?F^6lN$;e-9Fl-JmKjE6l%$94Mq2\jiq[W;pR?dd_;$WOJ"V>hb@2Z0ZYq9CPZXHl\ + 'G_p_eh7b`_dc_'@lC0Rp7IlkURaEuSU'%^=lGp^j.6#-T"4*Wq<+A1m@RPOT6^C-\cJFl" + eYVrTK3CPq@0,Z$__kYTpc^i[Z,$bC_%=$CH=$pEpIML_4u2bE-aYqf$U9KeL].:DcYbkqG + !d:mA5!hV2I()2YX$d@rJqmE&pF,f.D$SQ#g,sF"= + Y>ZQ!uOfrB*DWjbI3QVDT`[54.RX/m%Z'2ttKB<5o(G1t2!>1%P8C;k76\8GHo1oTb1GRn=X1LNKkXgV2m]LV.kc9D + FR(pm&NPSP6E;m5Up/o\YP/n\07Tn/Laj34hsjoo+7n:`.5Lf[IVh8\fT.;3_qpput\('oq + &e;Qk]IGkY.l,5JbO_RT\H3C^a09j0osr.(B3Ud@>WHj3)0K2pH-(3ZQhc9Up#2:kOihh:%G@/; + H(,=iTpcjsbIOfaq5]u&[ENp,S(YECp+;e>eRH)4("pj+)B[6c*R9&kcCF"A]^a;Fp!2@gc + TM*NI/VDT!RQ5od(KV0h$MI,("#/^d2a7TI36kM+W8uZ,p;4pqMV:81q4HRURHNWg3(n+D: + E?&UmBK,)J.G^;:Tu&eX]^j]j]IW8^uI0D#HpWG6t0*a4(!4e%.?aG-fXYLq+FbWHsbKP7; + RRQ)V)-WZ%k:;^$6GV5k7aX#Eo0pJ/L?=ke!dX7p,Yo2D`WBm05c_n+5R3n<%IhlYVgY'o+ + bg?m`UOND=6hRArqgA-%8oZI%3hm9HBpTq+$'BGsu%RK:"g3<+1GaO + lq(ZK\Mp*pfG%2V7N.o\uo8L(i)^4[C`qkLR<@C)o:f$abZ=>LsE3;'ZoZ#RHo*k^"B*o>N + :V_`P268^DOZ!=Mp*0rU"jWmCWHrR+K.1j\`PJ_0cp4\F3>N+-4&^O-N5Pf`dJ0"iGTr_lq + 7GIh@h`7IbM7`0i"Cq(Ip/eH>ghdKfnRJ$m[q5MR[KppR3Rt$FB*7&'ha8'^Fc*fRZI3n_Q + ,Pln`qOaV\^TmY8[JJdkq`h^iJ%bbE_>Jetr4g5KhoYfrebq_cr>bjcU/BA8.<5CPEI[@q2 + \4NR6.]C`s/NLM%p-[J"U'Cm@1[Z_)@6.n_qqW]N'6Pd%2p%iWuTs(&HHSM6IELSk]3:co8,_TEHG3bTfGFFron:0???eS*],-I + G5M`i>N^G+Xa?6d(QY'_cs3Bg!RN2$0f("7SFS]iDY>q]S\t0i=Nl,G9g26O9S^-?Rs)d^R + qsF[uroi5/\?>6q/^_`'^YJaiO$fbol?2r@B5@OTh!Oi`*WHfOb3\\E]Y8ksVpaYtH_:GdQ2cKbLr;FC7"9/7h:`Kgl^lUY_FMRBK`h9_@HX9d.Kl^Xok(0dH=Oo'bAJsX@s(5EbD4bNG2JJ[s+ + 83+ICYLc/[r3AND6Ip6#-H!d)OEV8AmH3Us*!5mA`A3-AtA+2hNtYAHs[(TUjq:2rVU4F2u + X,7i2?C7JmhbV(PcEGL*;Bk(lg"oR0J*nN8kc+3b.20q>OllfbR/AjLIR9P%@/ZN+V1^IU/ + =QAgs*sk./3CS7d,uCgn8prdUU))A\7*BEf?=J*B"#P[Tt@?D4:^3h_8+C<-.'A8=6_[B=< + -%_&[7q]PNYMs5oX/hjs!-*g`m2(J"M/m$Bon\m'NQq>ORpT-)!VI, + @5*oDM2TC%M;\BlEeGAYEu7rl(N"r?)'cq>:V%q#CS_!e>q&!Pi@O/KckW(*^tP^t9_2TS* + u1:cMPb+o_`(bSYt/Fq-h.r[YsH+bX=;=@*j#$)'a;'uIV5_Ur3'TnG4V:q0P`iVsAGbW(A + Sp)ZSP==XXS6&9Lf,"-[8&;=5e+7Us1`5nJ`@Y@Di6E#CQ=K`Hn>[!&J2D2tY]bk5^@uKlC + 0h,iu)<4\1NKKqo7_%=p@t\o`;:[_IGj-7o[!@^pmQX4@!?Yh\IumPD6:cA,,%NNfOV09:8 + D,.2,a=@QE^iYmGqC@RXIGCdE_T-Y5ptb.Ss5Cm;T_!o.V2#KPk)7_9'KJ/jcm$9P/:Er3H + $K\`4M@6p0>f:Gq^Q:_3gDr@n\>60kO,"R8Z"U9_k#3AouIi'2E=;=g9h2e>X%S0PLLn_)Q + 'Aj.)=!F&#!]*](acLSPK-]IXIo?=72Eh67QN;9*0m%[AIMa0lp7OLUdK+j+R9ZVc + B,%RpEd/"bN8VIhP'N0cb=s$)0eI8,C(5.d\!C5i`+f$p^P>A2U575(S.l5=odeVc^.?S6SZS7a4+j_AEm$XWCC9=eDir1%38BI7Wd3"R^#lX\[XQ;c + EC6sHuA6V_Q8LsZQ+;XbSUr*BN.`cEZ=ie^h<56eH]8dHduK[.g0`m9i`+pHU,&_c + U+MJh0e(Yt-_XTh?G\m#i5R#_L5Cq_3h')Ci(LVU$Hn>)fWs27/1q)5IC^C2H:"8SK6t,cb + 78&ung;M,N%RFL7&q:jFd*TkE)mG6GWi2Q"PjD!7&r + p68J/EDA(PH*]BjhV$YQDf9Kp_7+UuhqVG&Ec-NfDOJ*qkS5OISZIq==SM?uTd[(]/5t`Tm.A + Zm%O`Gb>8E^d0jcjkWd*?&daJB[7P[]RBIV[4hPoJn6\rZIRGKRHd6DV8XUN0h5aLYFnVAs + NP;aT55Mm$'W4]7s%hQ]lc03bCJ(U"]O_,C.:P,]6pW:k\b<9hl6m<)m@G5Api%FntVFe5n + /6]p5@?#K+r`9^PXs':[+GsYCMdrPe\"QSoe'JpeSosH`;#Z;jJQ4CE + rK(b:]E:9>q_mo<&N;(ME:.(>Jk[R(Y_o5O];;Vd<_'pmCj[->:]U6qCekcrKlgIXIJpk0D + G%PQk=9:A\bgjdcSA=VZsAa!&0',0UiG(UC3F3%%%\<(d&!W$jRV`M(p4g^mU)tj9m\@(7> + 2b0nGP(l'9'])mQZ90u9Hdg_-m8+IYA75uSsGBrW)A(c"si6(95W>:DkM.'g0/ZE#OmlReT + I01$%#)oFD#,;U<<1:'Qm6"-)07Ef(,`*Ysg?cDA>7N>i+/7?I[@R`aq7SID`>\?N*BLZfO`dDJ[@u. + XrCduQd\@\]Zl\r@7E!mnO7iRJd_iZr1F?2CY\P&X:Cj%gSTK2M43K'J!X.75BRn(DD3S\o + dP^,dsGfOm"8/mc59;FU1V+3>=3;Rm2HuP!SLcVVo.GTcZj/kkaX/YrhN'jbhlD1FjO?3iU + WdD+QX0a@_[Rj)mNH;T["I?ZuhE/B<%L]s3i9/4n[_)1&49NG_'V>m$_b(9MX4tTUA>;86a + N@,n4K?A^9?XDoaK2/]OLrh2Wm*[Gd`T_Z4[[^^<3JGpfLlMk4d<6(MV#PZgWN4Eb?Nf)le + Xf%i4]

S0U)R^+a1XXe3GMm(_d--lik1O&3tmT5a2V\6PZk)(-p9 + 05hd-\K,MK.&]#,"1#s"n//?Gm'Z"i6Z9/u06*.ne)R=0!Rl'B!4<5A?+3r%]1ApQ,'/;g1 + ,XON0_HFr9>UHe1.8N7V6E<17\^a$q4[Rs"6M!E/!T(^-6UO!76Sh3L<&QRd2P,jdT$01$\ + aIA?nO'o_2!dC_q>)rh#XfC66o><>711tf"[lQud+h.!eIP';?QgsnTj1:0j($ImA^O1I[R + =!$Kbm0>C=.u?7]^?%XD-(q)H"Hm;9^.Zh&fME+AqMKdJY4d/L"(U+js(0;H4u?>piNe-rO + V*;LL"BN@H2QCW=mV31)[XQokbHD^hcC38AOF@X?'OF]1tdMKF#?Ck=&\J4G;R3IH9SXF8R + 1Iab\ee&6dcSN`u82b4ZL$8dB7/;-qj1K7&&\CF&>I5*dYMH#DAaN[=%A?W[Nf"HKK/QB!u1YPJa[7`mA94H^MF`U3@F? + UR#bIr'O<4tJ,GY@tL9D$$&m(ojs_b;LR9LA-=Z(g@U*/.,+Y$dH??fj#%^l'0HoQ-Ku1:,Lr + r1upr%jM>Zpg"[G`]]e&HlIlQMZD?h^g`K;Hmoi;t*$k#?9I.q0o3.)#cUFiiSG"bOp28pLZn^;1h9XhE6Td3P%0*\o!`1^6['^$(X?8?":Jbh'6hEVcgd&886JK^OgYET?@5'bWpP@8TJ4+gb'ND&"5t;I#1t`C7pHOt/t,/(1iM;Q\5Lc-G,#cWq",>oh8I/^ + [i]dTo#d>trmU7FtgNfA1;r?(KYSXk;'5/CNA1-QOhfppec^i)FPK86n!KZ]NX96W78UhH` + (hNTX7=kXV1,?CHj[I6V&OkMBc9hJG.,9S5l/OPV4ZhA,T\H`9Q\.']nO\1 + r`::)';eYBta+lnUMg06:-(/$tno"oM?_MtS%FS4A4XA=6=c_aSrmB8kcqrLPWAR,5+I/mi + ,['Z[;>@U@EeHMRV\=[L0gRLL_[U1mT]gO\FjrFlm/\ksZ#[AYdgac`:7Dm("'BY:pk+#9' + PnpfV(hUo?>eSis^2E*3gLg_/B+Arp*cks0FD!4Jk<=$$DK]ZAFm"d/B9mdTSpP+PceZk%k + M>"EQ1IK4o%kBU?6quH^$f8oPhsuF?KLN[NU(8,LM2^&W:kr%hMY>[KO3r.hZB)#'9CT_iC + Jm*kOt'SVI7WG][dS8R`]%AWn*-14[-##%'JPXCQZSQiEW\ID/`Pt(fquk97@d + PSTN&Uj'gHM9@Dt]r4_/*H/^(7@QWpXeg2saIQn#`^D2:CI`#Wl`([8TfN'P*G/kB$/o6P$ + =JdcS^+sN;!a+7.5Xa15?'%d@csq1O@tg.WSD;e.%W+e'c39V2%R+$k'$a8*)F:9*_qEmSDpQpG9`>AM!YJL/[2-1$)uOZq[jfKs + OSr[H3q^miG\O/I(=l53;/%i;h:@&)K;r5\Ds7= + JB_CBK/r]!UPA%-;?JqGhSqqBpM/g9KEScVb(Dq/eWT`?-hC\9<%Y2-DM!6M`kr&0TDNs%# + NbN)"Bn%U>X\eT&RRpPL4*=Kd_mrc@R-EqLU#\- + 'CCup_nDU3/02WK4L^o#RTe-t#R4'@;$=dD8=L-Y-t5u(HIc?A#\k!SP/U!CC1pDukC/FPn + Xj4\Q2DH9L">kGmn:U?q^AHXRPVgCc-5ikG=6R;YKgI:4?2;:%X_hR]"%CY#>"m*p(mn6P_ + Y4^oZjoE)k'$^N72>UraD_?!E"s5Z-NdVo%WMH5Gu'`G#FB^CQhbQeH + (ULMYF,.#RX)rCH-`(-hk,2E.e+;edao-&>`!Gh0lfBqE[H@o5GrN+NH("#EM"f"/019nH7-brBCH'[ + W9lW>I)!bcOl[66O_r>df]1^IFbMQ+IVS-'D_/&dCfB_m!?GU)tno\f$FarCnQ%\/.r9So; + @dYlAh2:mks6Rd6(Iqn9jtt>$@s0eAD$?+tXd*$ae+S9)fm_KO0%YS5QhIT6p:`dI(>E!Sg + \0tPG5#r2QJc:lE_b3FGrHcA45p1+n'I"7)KWcQ'p\[&s%:I+`X2q=.JH(U!,B + ;8fr])0a^Xl@44IT+tPtP1/kPW&']p[,H1J,4WE4h`;-ZduErdM>J;'5FA]'bVOUse/dA^r + u>:oCI2[<8l\ZN"+YX0opcK#G($fcTNQ%AM.W[G+`?CH]LHEk4L'GAEAmmZ.nYiHThAkK>$77Gl6DO8 + !h]fom;+Ys'Nh&o([V3hd=FQp%SFaIujL*!5G`X!/W>6)A@AElr;V\f8bL6=)0+K4on9?"Pr67&lo1[gpc-oPhGg,c + Dj#PCt[,X7i1Xu4_cc;:?5TfV5#scke*h.e!9kj@+2P)JOS5@D3%._oIUYef + ot4iBNe@%V)qG\]\>DM2Vd1HS`c^HDqm8T`H'b#4i_Z8f*0`Th5>8UG@!4^ja,d=D<$!cpX + T(eh^=]`J/u-#fn*VPrX4BepeD]W*G:6/LZ1:J-&U]L\c-^m(sY("A!m6YOQ0tK8!9M-8`, + `n3C[#7SJb1/J*&+Eh3hlcVJVjd##(K\*2d6FVs6u+(1*/FQ"f + QI.RC"`4#q6k;;*S5AgpYG:aD0K:urbWW]09>.=*a,dfTDic$kB\2AV*XpjN1'l1tU:k_?'Wm7a**1Y`]YdQr+4L.+![PZtp9CTaYA + "15:n/B8pp2%Un-RLQMKW6_U#-L\sMqYnA9LR#qsbCeT]'r+:pjTbN5pe;=iA.Za+f'r'Je + MiiHA7M1qCa8BG+Z'$n4R*ccU:spHU[Pu4bPVcltFH1@jIuKcZ*i$r+O4!$%8-oDS,Rf>Ji + ui!UGnVAI"7&:`"$i^!b?oUaOUK$TSs6HDjtT4CQE&IJM + ,aN5Ns`-R(L=qjU99b!eLYaJ?$EC@pR1akcgN"d0c;@4jA.?o4X'hUknb1.dfjaNF/ilas; + )/5qd\@*1Dj%RY02(l8c]aHUp?tn^a0S7B`)"SP?b]Q9/#$.O!F!tZ0oh@9.4OM[m=&dFe< + &NghX;d&&DGC(83)t4^S=7B;.iHSmFe%\B1np7N1lZ:]fW>2WGL&[bI5STgT6Xi + HJTU;mah#YNls#_b[-a.7]9^9L>2.#30-]Oa2k'@kKuCUT@dIEF]mF)iqS(SSoY/NYJDZY2 + _QW(u^W\]s0kMe`n.R!V%!Mh^G#$/I,;!Jo4dFtAABYpD2obXFtWmQAf#raSdWG\D^RA(\!6P,Nc!%%61&;$ZM65NhL( + aIo]MRmStACDITcXGkQgPT?d&JrcisJ8W"5WpFBQ%SR]9L7lF$DBYSKeU( + U6MPDriNB#4&I'QEJruF1Q"q>8!DmA8_4[qa]ES$?lpA_"@u]'T6"mO[J$3Lg&%%#li$"OpW?guh + 0#"J]gC';pN^L*CVf4Q+.p5*T(RLHeU(A?m_1nkH7sX2I/`.-qq(LprZAt+0Giid/7Sd2S. + kb'?gY]I3sd[^B8&%[ok/Mjr*dA4c`mHJ`(WA5ciV/jYf0)?CWp&&YS(a<@0)=!`XHh+`oJ + +/mfIpYig>j3hMI>^nRCA>T&3,=(mM)6&(?*OU+OQ5/M>Z@N0,]?i,0i + %a!^Pek72h1Q,q:,"N_#mrEuTufUFoC`%Tk.e5iq8t.V_N:ERO!l:o/cf/C&JkmWpJ1JKBi + /VW.11mYpBbp?A=Zl1+c<,9Z97#,rgmWKIbW"%qlS\uP<YgaZ&"<;lQMD/^]8NuiP+q_,VGZf.p(FPY`JXCcE;%_g\T5"Mo"cg[/Ll[]pp + /3pc]5X.Qqig,/^%3Dc_W6IX"K+_^(7P&n$arCme^q:^C1&=Er]W%beT:b^i.m\&(\RU)#c + <0_K-c1CbB?6-N>VY18\gdbXXHC"N9mm`@q^9YRnd.aT8JW1pA,\%!)KS.g$3-a=oOTZXV" + 7OT[@02U%]pbb512G0:PG"jF@Y9Y38n4rD% + k+bL&H(.e"P;$+5g]^p1_SHCOV%$ufl7lAd2`iOVV9:pg@6_W;(gU_[bJnbg[R?n&OtQ_7b + s2&7*0]h1fE10r?*iqW(sBh&e+'r'^I6F+k6KL%`;+@,4&(J:spi\2%q2:T^@nh;:9Sanp++J-W3qp + ;Dg+*Pqc1e1K0Pt;q3a)'g#AP5?(WU<0^6YF]28i<@.2(fJ"jjrGlMAruq\&W"P5 + ;pDnLs@M0MfYAQ.#UE'Z!]\rR2V_(#o46+Ym'_s)Ti"p%'ssp;Bli?dK*9&/@#q?NJ^c1sd + OW$uH'3(BWap"&iNFP@XjFCBSf1a_*=e0NC&n;)e21"nRFY&7YpnNbY')/PNZCrl>'f$b6G.2$_Q6T3g8<&Tpp30V`Pgb_H<% + F7GI9lH.o7<)S"'O$m'O%'^q7T:7Y.elVB + 8CW=fe=Q]a1j!:,mP:mD8^Z7-u4^EV:03Ro!I'j#``Z[7s?He5`RpUbeha*^t3HoKB!QdL3 + 4ds[S7IFm"u(YaBthc*T]/]<&2tnqI[k?4GK[msnMsi=Lc%V>_^_7*1("HTtfj2f@L + ;l_)%d$OU#5BGgaW"GcI*+DRH-)6[=h8+rUqM<=aCcM,@?UXAUj'H6[lB-[qL7U09.CRP + 1F>1S^BnUY7Z%qE(Jk8#0<]UcM;IRS`I^m=+_8IlKlH:H_mk4A@d8oQ>G33%_%(Mh74FJbj + kJ3eN#&LFUq(=;lC8ntC[sQ;:MZ]Jr-U&"TRYVYYGG0!b)%otd\/`r#GTL!Reo>/F_`h5le + UrRc/-=E_QkHN(F(Xi?-P=G:,WN5V*F`HDJo=Ik;MB/M@ea&?&Jdp6DZ#@^f>aS<5&fY'4? + ;iC"AN&,sB[R17B8W<.WAC,J*(0-@CT*SJ)3\9SX[VIqJqe%g*$(1IJ[Wm=$`K1jfO>_uJo + ?iZQ&SX6ABh^a)\K,DO:Qd^]P'3mhX_2EJs-#H'PAPS])j:gn6'PTC6.?ol<$r:r=$l^cDH + U7_PWtHj6PMVo7$LK6'N)M;hf%oBE)C25pC2*[K1<4f]ueA4q2jPo-$$PcRIl9L'TJPaWLD + 50)L"]@=%_PY]:7KiS+g'UPdd1Yc(,a"*H#k?[qe\5iuYSZSr.G\m['O^K:fW.+BeXcMN<$ + $#C/?<+h+#9&ga5V=!IJ\m8pNnqG!eKB?"1lV-IrYS8lR(F2sepa4tgY*.,ahJ&klQaIJ=4 + F[)^k>::4tKKPi2)JQ'ms/F_?K2nPC3dI\#HFLJ>W#CN9g4cK\a+Ct/Wj&UYo=Ef>8bsf=,^EgFSd:n>:["CO,[@"NK]j>;K/UU;%@fZ3ls[Xi*d,ER7`$eCM)>A\)$Et)>r5*PHEX\lS!ofp'Af"Ji + .YkO>\"h@bt5uu%:f#tVo)nJ%,\reF/%CYbRj%O"kguZMNm493QE;-8aW-;^5, + %7JTB`#ClFs1]hQuMh\*Et)lX%9jT"jVu_sE&'aaBKIHKA(0D>d*R#cck1Pbac + ,QgqW;HL$ns;JKF.#02p+VEN-H.nDG6\LN"cBq<&=B!N?($]>nLE!'@D-`d@DMb3/Q'cMpe'_de`R=I5Qm!Tja-f@G%VhrC?T_.>sZ/MnN2s]ihW8 + a#H4jN88-+)_X37CjDRkq9Xon)a.drHY.UocTQuC4c,5jO0s.\[>5]6>@>m)$eb=cdljNjh + 5Rl)kE6d4P+."eR4Ypfp"XnQN#g)X)js"*eWdiG66ZEVJ$NHULb':4s("H)E2[:nNt@+'TB + pfu=t:DH0r&a?q\bkFON8\Cp+%mm.$MebP.>\.Ls(:lAofDshJG#VZCga!lR%*I@F-E@p([ + Xi7\hdfeA`4aR),`Nht8J$7?:HMY\Pco1]G'qYLmZW3r8f4s0Pt32S4+?d_90TmT3&Mq1j( + )hCh[tmH4Ft^bDtE7ID*+s'B+gGcYL3K8RaR1+Td8s"0V]k&ZIY`d)^"d6I7a9cfR?^45Ej + HDSdY=0U"'1FWD/5#K_XY*7%m'eWA1Bb`Obd/#.1/.: + =dl'o6FOrIrZ\7L%::#oJ3_pEro0,rko^tIFZjct#:p_MZ6.%MH'gto@FW3kKPa*=^dPRdR + 92d[L2b[/7eN6T+obBVf<&H6t;cP\D=&[YWLp4$G?n:Xc$s&%(%m2g= + EZQUA6?97O(DJ&AY3Pf23AeH)F+J0Cg/gJ[r,EYf\eb##BAsWgik?lEuk!$e:6;@Fk:kD[u + K9j+6S4@HX@T&o6t%jp%\*r+;[H"@jo:R5QPDHE0:3JM15k.-dpRV)BH@?>-4*ds2b)AXcr@X9p_UgWsMQ_eE + *rWT67qN_5]hO:k8XBrAIc)t=g60E:T#Bn(lCcaB+Y\aXEe[BpNtg?k:,*CGb-"8jEUZ*LiRd4EW0u!IYXc`qL + 5]hFW^V'[I!d>pUfo:k81Jpr*LhtnJshieo:RpC&dpt-V*.kra2SkE.3;Ys8Du4d-#n?!,t + 1(^B#H2s+%+cV;E'1_iM*"b@_`<`:T + 7_DTUj8<\PppCn^gg,j@=Dn`F`>Km?AW7:n2%T5X(uoFK)mR7?%9VX2#PcdfOkr4@M"Mj"j + Eqkck!5"'"uO?TKTPaC6qFSZ+lm>:)FJ718LYl0jKR#N,Q:A.WA>m^oUV3Kr+JZi7js,1VN + h6M;p(g51WI)0Q1DLg90$Q7L2b]co%mWpM01I_jM)es2I&p)Ji.-k`u1XM@*G=41F@)@RBo + 4mbn66ua8FJVo3,s)\[=@dprXLRIW17>UGq?ejIF\pABn?/5j)\H+Ckb0&>B[u#^#eaZfHr + T;C4kGnJEJRE0$-e7mT]q,UMNYPu+bg:'n$5U!u&o;)3ts8eFUh"ZD>SqDM=\[3)0_Cp'(5 + `CW]S-7n8o[SjEg9$==i06G.HQbro"9GrP^"rtZ^ZPu\6JWcr9E6Zlg5>al&Vl9lhV&FtkC + Q4TT2MTH5)oH_s:"d8q9mmBs7/(=q9qHZa7DjhJ9K#2`Z9JT1$7`OQc + tj]m/!Ec,I7\^s=5h)h + ?T[>`(e[T;m2_q.S+V85K0a/F#68-iLOh,p"_TeB"5pg$K5`FR_3aa"?b@f7RUu;/QcBPB9 + K'X=II>#i[e3sm`n@UL-bTtS[^NX%MT9LP`U1hc\!HiLEMb[4/fDUfXE`_E'9dGh;]t-[/E + U3TT#hId,rt]kP?f-,apt]1]/OSlEi)f[mdi#AS?h_B9?,0nIHWffRe<8TB6oV1n%HruRe' + bFc4?[0^9PWRZa9.Lcdt[i9SZ:W)M2$fn?-]o&DF$r#?&gro>PgNrub@o!hUR]9$;5ul]q6_ET3nT58ukrUa]>F + ^B/qhj[+6/3=_eo&4$COOsI_t`+:jF"_'o6scI]GHdrI5S5K".eVnf:fBW4@=no($riQLC + SbpnV.!$'uG1loJ%=+KR\45rYS#trr;*Y=VpB'+/f'd?ho-M')KB=ID5=S^?b0k+g8W("L1 + <05[tTbBsb-u#dLYK^n_:PZQ?C++$e5lZ31ZC'FurU&@*f3SHC1/$.6RV.7$h^ZCD]9ZkkL + Vi5(='1@uKk$SO:e*1gXsZSW^EX#D+/+>[Lf_BJIl;`^O\4.il+ZeQl`r$Si@rh'l+ZpR2I + U':i!7l4^G_V,C)UbB*E82RYH_]qm_qbBLG:Giea6Xh/^"&qX?^?8`1#`$.9$4(UfJ6agK<2L7eMMFU'eCUUQc,n"TYW_g/$EQ$:+2_/u4_ZDu-"Wq3VJgU7T-[Z6Q\%`F+6Ukc![\%Bem + 457H\oS\d'q^^0U]F9f_,/Uu\OK9D?4CjVfEl^@pa1SFoa^`OWe&Xq^loa'[*(2"$r#.[_$&'!q]S6E,Jc4aZ=FZTCFG`QeA62 + Vburf;Ph34oi5)`S9pt%Xe/llP1c&qjc/d]Q1uH/#3^PqT6<#;W1ak0h.)MH>6EK=V9J!PU + /P_Na[$9jL$o-&F9f0a_cK$h">>V^Y2`3-G[:JhqV";!Y4//N6260tLl7g-ZppKBG:SKa*' + LK5R6dO4lU]NhXoL;X[8#1cP7$RhlZYdXl;'dLl`7o9"j)`0+#c2oGd%Xo,KN"a8HGsDp:u + d#DI1O;[,1[>t+(831O>F!e&'5N@5'n3:pat:#mbfOnM]_tIES=<*GS!%TX9%9okohO$?Y#Pp,eJ2!4,u`>7<7Nj+h,rW9(]>fnSJARE19h4b]=Fj3q^[$`62,tt=.:2Fgs*XY%SZaCbk)KA'GB + %$':g]'9gsZ)>h22hKX/PK6(9LC]nak-0[76M_=`]1oH?O8i7QQ#[-CE!m6`In:@'diZVVg_;8[5?_j-h>.p"TMo\'s]g*HDr1L3e@5dfB5 + UU?u-,XYAhW5MC0Ub#!h[/o"!BgeMocpb+a4A<3V#?qm/eIo\9)$(>[C9CM8fuA7NK@UjCD,i)UZc@M-V.WF-=rD+C(+sMHqDqeK + _W<,Z7T]>iLoEL!P[9F?29/.AN\:]QZl"e>_MSdd*m\ql@;/0hjsQ"V>K/Xr84XTho5S%ec+J1s54Xl?mYm3/cjM) + ?If#Yed+B>D0=H0ZTdLXf/7-Rol8Rn%%#d`f)^P*m;689&)e7?i?f,)/T9S+K4)\6^Es<=< + HLJa#hCSN9U6U%Q$Co^bMgRX=2#a-b9,?lI^0+'_%b<`-%Rgn("b>Y=B*l(9nq;2fM/K(6, + PHDgt]a-N(rUsZ^*5h"ca[`ij7_+fbEL6K]UL$.p5:(fj6SiD4"gc8P[Nu_^1c3[@Er!4AQ + ]_g&=1C7.[-YTu5+JU;T6!4Se&+5h]cGg2oYWh5R197G=jD[X2`8.SAd=8PhTF`5Mp,Al%/ + %$!SZN:p%(Y<)V)\H+1Js:u56^`O]W4CKnGTjeWYg4C#kZH/&96>RQHp,oKRV)Fd^G>[1lG + I3&]kaLPRuW3mdqF$E/]WRr8%$s[[WAHSfuMo(] + *0u"V;5'DJmIIs4'JjZ5i9h392Q,h`W3EG%@8WDt<]^q + &QD!LA)bd+JiRYWoeN^?2hiuZ4n%?][,6>VpNj%SZQ[?aRYQgE7ij/+iS2ETU+4^$?!j4s3B1KEK=4 + (K"-:Ls4\4f)=S2;2W>mAVSFk$be(`]4P%k!Wmn6tp7DCL!^Z7a> + \H%4%nn-1BC2B"]X!(2dCu.q\k#a,SV1Ml>ejf2Dlqn7C9c?/'"8G3=O0;e1JcmWurM%-PO + ;e#%QM1:M6+MFa=h?>@)c,s^uiKQ0o*aVF)S>a";b%Io_Oa\8"6UW_l1*3@-NB[ofHnKm() + SFRGcE[I7G0-TmF,k=GOninJl=!r$$-hEcWk8(T&$a(I-lTGOka==-g1$g^<@J+3`lUfB + 5V=H-5H4J8:W%l`AWGDbJ_P-u7ro@])uf"tO0';!]gOlp`YR'oui=-Z"7Qc:m?,-Ha3;78A + RroqD`YhDh1RcWQR[_bSQRrmB%$Sn%Gc6_0mf`M)[84md>UD2q$1Y1_.<3GlI/:[18`c:T? + ohHK(!DAM28"gR.n;=7%ZmU`c[>Z5Np*6mla_!-j3u;']@R\Z($G*]kXb^hq;d\%B:n^`a68LQBV2$@IZ*aQNH + +*?qI7nD%kRR4/:*X4qOa__VlT['XST-&qT#abf<4uUYPT->HRH%`-Cs_flG`KrMDDfVTN%C>2sg2 + 6O-i&Y?h,/'Q"(^A(P,+B%-Gn4*EIRD.NA$2s9mYkh9BVmB/qPT%dGDB(j1ED=;>M[r0PeY + VudH:k*Ih2fe")pD2?(@"e*8dtPuak`?SNYqRbJ=a?XHm90D4^Yb@Iq5XHo^MZIHO + rggBd`S\9Ys]<A^i&ord%r]\b>#S0b?PKXBe1?gC.q0:K7PEr%p(q + TkqflLk#>?E\?]gf\ka:]Crh^pNZY(+V?DYM8Fh)N]DSF&=4'Ng:".W%5uH,(/O`T;Ps&,HeWiR/"GgG\0&DVQ#Dl#'a=Fj"iukC + (Bn>rQ'FG2=$Hj`fR*NTkYY#H@`NhfmWHJ)AlfqgmZB-3]"[(d8[e@@0e/L#L[E+c51QXsK + !8R(:4Y*_&G_C)'0WC.Am^\EZ0eUKH51$pu/a/;ljiSg]Jj0T[T;!`5ASMP@Vd/rS`qatsP + pGl0f<`1\eZ'nq3V@2R"0^iH"?Xm:%5Ra/1?g\UTB + @BLk5#"#U$;DD):X04hT)hdqju]XJ"0i[_si%\0?N](5=,9frBFrHp73:4ic+mHYN12'+-6 + $kIp_[63T'`8)t3n0O8'(`i#q04cmWhJTtLQ$JH(2i8dN6m-9"FCs+:kt%)KG:#@PX.*/"5 + &6:aTQ@72Js6i:e]3*?W_CbLN>`Yq2&[1/hFru4=_*CInl%q5Ol*$bG+6qE(F@RN_B@q$b& + dJc$/"Aan067G?#Y7tHUs,/+,DoJZe(F&K[)T-fL`]2-Z,;a+o&WgbIaP9#"W83IGauIF$l + no"*s,RcW4NFu1+(NC@O>75oNA7mcA3>U/&hI`fGoAp.Gg[O/6:o9J.4)uifcTcs:!'@p-f + jog)A7p)%>"L8,rDQbnto=onZG+g'[+04j@lQW"0>bX%[TAh; + "@UJbRFad,0Dr6Ujq7VYXXX?X^,e!h"cCsBN(2eN9I)+-rEc8j$(B0>Dr;keDqnWQ!]I.!R + >c?B^(l9E9Vp7F/#I15rA5DDT,TL8Wecftm(#T1UU1bOFmC)fN1AJ$(r,`<6q/Oa?P+="72 + N=j$%8'+%JUdVk_dSb*\.7#m?PcUKT_L>=9#t-5]0Vm/qOB'Go3@`PBX;.J>=R#orW(!GT( + =Lf3l8;$`Pq9IFZLV>G%8eeo9O4#XWaSS4=YY&8`gB+>=3SWl1Ibp')C%ILbmC7)7\&(2dp + $&$J[.1gm^a@;lu4Jk`Ai#.]iUtjEm@P+3QFb/*K(>LNlg+^7sso1e&u7I5dI[CW;3%^/:= + P(r'*)Wbo_T=J?8kYZq)7d+T"icXZ@+_2Q[Hm`&`_AJgRoSSJKsZ@;V-i1e-'`>:W-^Df,` + n\5rIc>T8T?mPW+s(G=b'gQci_A^@qh7d=7eX`X!Ee[lW8m9SJQGOFSN]@Vf.?9?DVY=7Qd + 2kK\KgY$rB?1GV[Tj91fM1+j3pfPAk[k,`#g/bgtCmYB?[N#_-YVPEXCS@d^FmXhND@jY:7 + >p,*1POK$)8b$A$t6e3LN*HNM?8<*MNHI0K:"Y(@.X^*!$cr4L,$\@":Kd21(XKm6k$o-2[ + KL5-AEqk)N-_-29*Ia1.D>e=:o7T#1D=3RrQRg9tN@hr8;V0=`=j"`oYFWiuBk=EXkCYk#X + F.SI;%6:8-+ugt:u"KmhX62Hmq)%Qf?L( + _:uZma8b_INkl(.fR=Q[mXAk(>DCe6TXVbONC5i%.D/H6+*$]!9g + elcda8#J@pGV/q;AMSiXkb1FKTM>E3_JjD0.!FtS_mZ7I*+HF.:liePu=Vcb:X7?GV$XsS1 + \1]/R]-cLTs$X``UD*Gl7C%oN"T-fip1C/F!0,2MD1)4%j8S: + U.;CU=T3G?A:%u\[I$IQu*QIn/Su?_?]B>\Erb-2YiEWrN)fSUga[9#16ErdVQrCJ5GZE!4 + $S#f88(_9>*\O;.dM(rS*I!GjG1!SSU28/2/3$UD-.i%+rF0)cDF"7SRj/6i6GEXBP^&7H7 + iTop=4r*+!)a_V1'cg8e.B`;se=\Q3G%'9bRFN^c%#Om-I/AuR&`m!q2F)9FP/DbKqR*EZL + cB+^EE2:f2mWYn6cZ$,7bkBC[\9TY5H;ME(XV2+[(45El%.,+aE7E=Vi6qqtdms3D/NXG:7 + "A!+%d4]>-lUWQ>h+r2eklFu/Rm<;[LY#(URs/eF?:']H=1)m16M0uTII-CO9 + URdn\`ERa#D1untmjK:u5l0.4XmcB;nT5j@nDIXc+BV=d5+7="hncYZSHKX9)UCMo"%ZMWL + La;O2UZt>5W^nIG@d5F@-VgTg%^)[(ZQlJ`VWrFP0"e+M`[hV@/CI@nY0s:%eu>[5h#oDor_0.F-KP0 + 48H1AN"n5^qXHHg"!rrl?!,rH+o49g+lN-To!Rl#i'6IWUefFka';7`,i0XhDUD[GS1XhIH + 1R,lZ63JE#`8Khp_0-ZMmN8c21sDEnDsIVsCG\_Y#;a1$OE/P6K05+'#\PL.d#eV'P6iq64 + m,Sc;rn?Bk6eQ_$B4?[/H7q2]$Llq$nVnsN>4]q(t%saI-i@K-egq(/Ih=m%S])Q-j%mM91 + #Qh6HBfN0`=@XqLMKHeoX+,OUFQoDM<'Cf5siD0c8Ot*C[)o7HfU>5lMAeQ<8ZP#2jtG/[1 + /8]+rhCgMp]X/]ecajVKXi8Efd'FHmYV(Dj%bOtGo81o0#kMN`Sk&:lc0V1o/H>R'`,i6f/ + 1/f9s)LJ4tmR7WrD/h\4Zf](<2j0[[5Y!Ys(hbL0**gH0U;=,4+_!o + d4]iWqBcg'kF?p^F*pr-^'$.np`&cLoq*>.+45[,BiVJQRU6+YSE8jWs:+aV^f*Eo4,H*'r + 8+X-M4^J/$d3"035F*>!XfLp(Gt/.noZbhR0ZL>jH)VQ./6#mkZaO?1m#[1H/W?iEgZo?FB + <0Q1\JVpKofR?hOA*2>^d)!FV2s@($YTp(^ud(e1hn92"nZo=6rH8Hr+s"pLt`2Di5`>6!` + i2NqQ[1M:6!5rMY[2fj"Jek-mT_B!%X&"s3ITJL$mBkFei=26j!^d&D1p(0Ne)Zdu9p+8AW + M*&/)D:?B-[ZYQ-bm5)S#aV/I'@gi11C7*a4qO2I1[/[oV[D"g54%qMrBINs$hhqnF + 9QN#bQ&e + nUa/X6rCPt"_eB&E\ZoQ(aAY=Gj%F7b(>'SgdMCIo+X(=\Z+Pa`JKj%9o7(ib5ce>X;$s%; + piEtl/Xa_mD$`>eHi9Z`7Q;950qbtYIkj##_?1"@;i59:;-:GLrPY#eDUqbPW7k-qT;eH[3 + 3#j9VD;">9I/r&TaaWZog;=9cK&h/6HT^#E0;UR@"0!/&m4=SRfIe#p,.Xr`:79$Zj-&Ra` + :>Jom>?1+PmY\6Kno7]NEpn$q + &prQ[BYEqkq#iD/=!gue\*OL_hPJM>dE/Z%sNeLk4eY!0!BdpX*%jZ(L]:[M"lsd3/`W?G0c%p=X5abJ?a6.O)Z,X$pJd>/Wg[drmCGe&b/l)o@"Mq[X&]()tM + orksuYPS(!)Z?m%$*\acYZh^D]+_F7j,>u.%D^u3CBlJ?]PKR[eMj)g[4` + f5>:m[EW;3]YBPub^bfj0lOa)'F=5T?qQq.[Hahei)I6()q7e/m'6Ir?+sR!FG:2ZlFV#FR + D7^%n!_]B,I6URDX=ER>_oXc%4fY1VF8EuDN0[""SX=`cB.8=)h;g_CW,KWt>PMna>m0j^eV + =drXdp'+h%b_rGHP-bdJFg03u?=n+juo-d_.B`rAE$Z3R^,QdsXC/4[ISG5Ld3Y=*nSc!:\ + /.H]5XDZHN?U4_/B=gX`]4Zl;4egInppH.RIm!+A&*@8m>Z3!7oJf_pqp!*08R>SR9A(>W2 + /)h6Re/oY$O\:(U*4,]="O_TY34hI'90V>o3aZd>1guiIaG0&#,Xd38K]:!E\q/6]=^C?TV + ]NKYL>KrO71#KG:R+p;^)rm>`#FW:8RIjFlf,qaD,JSN/^TES.+T6?SDnmd?^j\"/&RiKna + 2*&I8M/\fHl\cbV4I.5_O9LXe?PU?%A`bHI4\%frZ1WKPOa*s\VI!`p),uRgt;PJ0PPqgA3l0G('a^2b42t\H!bK9Tj-%= + 7aH>C[YZ):\;bBeL'I(cHkIAi[Q1`.[chXp>%q1D\"WQua4]\cjiV1_aGWp_sn]!GS'\^+V + 'ceSD%MaK'2_9dMQd'^ut]%^M='j\LIe=B&"s$cQE8b=*!oRB[/^JXZRs\37%*;pX3AUYF\MFhR[!q[sQZfq0$N1YRgH.G6NZ9iGf;rj8TjQ`T(tUr*)rEQ%-0q + H$mnI24\GnF79&88[Y;@kqq+[Me2phf'>3J*JUftKMC3F)kFdO?8lN)hsBL7#D^1UEVS=\) + `O,,C;8H7rf9@Ee:4ec]oKj?\_HRrR;Fn_LWAEFH2QMG;b@#TpA5_%+)f?_oD::l?\e[Iq> + "\gDu#Lq^hC`6%0St/4:_XgjuJt<6;WUg$Q.6T0Rpu00q/Wid-Nt/(oCaA&k(+u:DH + ;Q.>VQ-nT81U`\F9%(RSf3ru=O+abF?H;>bhb87iQc,+n!0OX*Lr9U@+D1/sorb#5%0A'/i + &J7B\le@@0SZNS#t+6E#p?j@pGZ!fkt*IQ2Q,==CnaZJ(V1iF(Es8[?Y?rjfOe]g',HpCDO@\Y^_t)Xgr<'V7 + j?WQA)`UEgCo4dXU'h?-ZX0GBmhFQ<0XBJ)6O*k#u*cfj\*Vp\"W;i&E%gJ!L8#s+Ff_:)A + [o\4L"/TD4^$M3[1W.?*:(eiHkIkPd]-j;A_+P%4`1>^tiq?<*;&q0#_<'o2d$Rc^$HF.?i + r=]U%8\c.#MBR1GOJ!EH'O//%e?CRSOg1HtVC-&5cX(lspX/p)_f"8$LqW>VtCc`uaZYWi8 + bI$X5?X>3=c?tid^hhhmMm!rqEdC*_67No),+=e+A'<5MPjZhdP + :1IRjG&qgN1Mlhm&FYQ4OEAKUd2?Fjk_tY.J + &^Ho4/71DN\Sg@7p,%:j-4j3na\n7\H*d_%"'&4["$/"%j@`:Migmq7*+o@3sY\GP&a/e8U + 2m7A>oMJP"JKkaZMWFjJ!WWP#Ft-r]%qtWKhM(<_E0n/7iXu*)b3h&,Bun-+MqWEq"Yl1'K + >P=U:CY%#>JZOYPuq&L,/BB+aC$8-p!Z-%r.29j+Jj8X.u!;eVhfnp*3-!CrZ6.WUcjN)V0 + %:Fai#WYH_n=(dcfSg.XF(.-7WNR;Ys--X)iZLL1Qd=3i>"N/T-%73a2$SjOma\];lA^$R3 + U5!d8)DaWl%6YXf-D9$1qE[X8q6`-PkhMZFHuKQma>gQ6j.+#[F&%;73_*.]dpot4WRX!jV + kEV0qL)/#F[J?X'`/tNqL6c890a4KA=u&M%oYQrr'nK#WYr#R)f4#D.F&$H+a![ + ,@T59[<:f^R_-oDd7<'#t8eoaV7>.T.H]Q9%>%nu$ + GdQ4#@Q=1K.rT%_K\QX@=0Y]E3:.t(ip-pgd-_c(*:D]LD&T/9o&EMqEm62CIR0\GPhqm)a1`-U.:mrEl-pb"rHr5Y)-GEkD=&O@f + c>r4SJlb`ce6(($(ICS + >^S54;B@1^s7VKm%)g%b&P.4=dfTp67W8?%4Wl8J7G42JC>*40hEdd[F*BQ#-8u2bCa3Rra'p:+Kag5rY&9T`uXWd,"4Zb\[sLPYR-6=t`?Ym2 + Ru''[kDSB1R8N9)+Z(V0'V5;bCYEWO^TXB7?Eml@O=s#9l9q%/IDPY>tG + 4V*qI?HHKAaFq_$T]+&4r(L^'%QH,n`d%[*?Q?tl"f$H]$%VLd3Vf'6k2WY=oD + Y*%qO^:CrBIFXI`L4>s**jYn%&2m(ZG:aKCaOVchW&\lht2"o`"mDr',Dqrh![BE:V\DZ6i + ,h3WK(6HR:ud!'hJMZA\l@UF\1U"[G`c1=J!7'/I@^#XFmb^oRi-ZjDBC56NM75JRU+W!`Y + L,6CCjK.&SZpd#$J9a'r`QAArR!+L\-A;5&>#)I%"#]0eHFb[tU#=rJ3TPk#WA;64E"uoV: + G[(AZ8-HF\K6TG/?mc#;(4gEA+s6s&lT2B,5/6G:,#^fpqa&+-6R'uI,76*2F>Pgh27/f31 + pT52F%\@U9r&ai1t,;Z!_5Vh6o@&([7!5*"@+hK=2Z%Z202m<4'BKg>sqn$`+*(!_d4Yu8N + $]D#ZG2#]4&-%Mmb`C2H"7tCM5VHCPch@`@>a3]5+;aiosX2UNO$G1>t\?R + tSFG&k+[p+$bKO?20K#SaT\$LAh,CHG3AN/8b%-2%\"+_l3(noa;NNkGJ#O[j3/`Y + `qN`RhEB(RhNgl$GSXRj(rHbDN#e%>OH'Z7a=I%3;[ft9_E("AijinM+^Te5M@4IY4k]&Cnia&/4gWqWjKpS'q`Y.k93^CKHf6X\j3*nk^/O + i#]DB^gq9.R,9C0=7r4Yi-^J#'Q9H:rD'ZK=N#%\-) + =&-oLVH09"#"[2BNd;!emXV`FTE`& + AF2aFK6dX/snr'gNl2gU%R1h4N^A[g26."l3AoOkZ.d70%G;J=TM[4"M_D+<#m3.,X7$]1j + /R7AND.Dq09>qecqOOFd6%tg(dSMW?;HT?L48'7i%iiC,lJ5_o>a:s5g$_YR&U.@qQ;s]4= + Ckk'!LQWm\X.uZD4H$eWX%:F;8E.G+qm"6D3E2KTaWWf/>0/jC9hAVc40$TUo=WT[9$*:4< + @jT@7%:hF_Q@pO]MdJ*q-L^Sgr^ht8i39b9?SqMV62qXQMT`+o&r4G@4nX^4[!:-bcO$UY< + ?pt9+0Gj]kC%rBc,mdb>[9@CYu>5iQI1i^-urXoXdfHbL[HPf,\p6/Tb&?F]U_Kf1gEiIACf#Dg=Y5@+@%Q2pbC)%*<<;OJA=/IW'-?;B%Y(\c$bFOHRd9.e-@CELU^1b0NWk).tZCX + J7o0j&@;MN5!S]2g+@p&6o*IipY&e0c]+gJV"r?3nN$1dWK&7 + 9^c8ctl0/bXbr4#ds%+`2lWj'b!Lc:!hZjd-OQI%D5OH;FNa_2Ph)+;K5-B_)CX^d<-;FV& + I=l)UZMC2aDfp&Q8or;#K?ub&#qiR44Z3[#gXWuBY1?M>enhTlH + &H)>ZHFh7i@=S&c4h.]D;ea/f)3'eI;mg3)-@(]=8j.ISf?Xk*+@\E@I>,orZ5MQ+(>^(@N + Hb'(*m?A,iV!6@E!dkm=J,-0j$gCih`&FFt?a2-Vf)GipE%6[P21i0j/BJTC=jnYWc4jh#V + a;!LQN5fMMl5G)+?tZFuW7jt#HB4J-k>#@Mo^kYQt()ZdGh9^SN?hC2u0UE\NicWi9PD5K. + LVo*AMA?Dlm]K4c\9j5d@g?)/K%BiS[Ybado:fXg/`O!L*[*XIsV?OE*27M\+9i0nGd/6^E + em'QG;c,&[d7qBpAJNu=E]3S,[gg8\tBJ$?.Q@,f>e'I`uWj]l]"gPgrF!^a*$6Eoe\[.Hdc#DhAW8kNds-\2o + k;Fa8;E6(Q8>,[N/F6D#A%oump+g-\a_[![+8AG2^e`ue[ + S@)="Ro7l`N_`$7FG"?Jq8S"XnmHjZ0`; + AW>raaidAGrXVo`8V5Y[nM9:ejp8?;t,a?Z)H9gL=YhV$lC@M9sMXjeP[1A\?D(BP$=4Ac? + $D(1?T)=(Wbrd>hbm:1_Dpa+hV0gj=0KQ>"8Ib/PaO-rt0Hc-Ef\DUM3m%36g%*c^?YEX2O + lL;n;jA8[CofI:m";VR.CKQLjl0[h*R;_31B5(.(pIQ;:qdrp:^3im + KWTa$<:PYi0115"6CTpI&g&7;MomFAGBu?Uo4!89CIooWLi.[n:bW9m"]VFN70cY/*Bk_JP + oC1bQ^UXbm%#f"7U>d:j[>60/BG*p+FhRf2J: + !AqlCc!gkHhciEcs+`39$sQM4KgO*+2CKgE)le]l:e3 + @+#iJ.$W^$$@-.p`Y<1\YX7PHdF8%l`mm+St61bp/oI6PW5>Lk.:]1gPI36pd(Y!jdeGVV^ + I;d\bJ(=LYf6s"?RfQ2S)# + lpRA!6>S!fj][_4FTNTIdc:q%ob;E3X0OFJd5KF`'-\jU5)p"-M17JO(Dt==]B\s+Nf`E7fh-,n>elTlF`C@Pns[\mmJX'R@S?P!@_SM): + r&.@*T]&rCu7@l'KG*:kVkJo9J:cX6:cr;"(hT@"V-C.%u]8u0o"e4/bhs&#cFWg.nsBXM&]QM:8kd;lbPgfGH,C@ce%[TkcGrVHVJ_-q9LN'8QefPk.&pB/Q1(%7]COZ[)SEdHI%1D+"9%h:69uHJO3N\Gt6!2LA`LhfSm4m.\-:o + 5AokC%41/p6Q/NaDGuYbq[]s"\+!.[09?)< + Eg@iqn>Si^n^Md?n*Q<'Rjh8u]QY6,4FksNq2H7Ahp(&C[lV.lBISZ(daBj?/QR^T8tlWVT6'%nY,ih&fG@oc7C>f(D1U=kIlQO(m;@h3W-8pH + ?96hY3T@GMr5P(P1Q?hCk#NH!W_%jS>BsS`om`T:tOr?E^X"qd-bBm/+NS^$tqA+2@CE?T6 + *urEi@RoR5V(htI$kTB5qK5JQI!OecE]p&EA9bm)PS*X>7*+7L$J^gYapTO@eNE&n3=pa$& + NU_&-qXE]g$0a=;p)hZL\&4+B+#N6XGKTM:=%jCr[@9b:9OM2Mu(g@F-KJ\E[ef7&%FUb&* + 3JXi;G/OZ>5N3)>LiF1U7!OFu_FR@N&O%TFY^-ibaPIq6qCG"DZOc'V>Dpk.'>eG%$"G5hN + 3J$CO7>Ia"`P4Y^g\[FJ@$-Q9Qbm4,!8A?o`<*\I#l1B50+$0+CjX!OKojUaCu;27s'TMUS + :9#)'(!C4H5N!r_(A$/LKPsS\@'YNnNI>Vr@Z(KJ/>3c@>l@\T[X[lR"H/_ + b^BpU-:#\@=J8.,Z(she7e-/g5@L>Bn>m2BN-!'<]>! + mF\&CqObLlkR`7+IhDerc,S;0k2kot;:h]&\oLg-Mm`%3[!]N0A&::J&Z:.bG[@?kX/[Ygs + OB-=kM5drgXE[QQZ!,ikP5U3f[.#s]m(1neaD_,/9?7jtu;U6Nfc[)uk,WMMSPPqB+;QG$: + &'jdqXV6dUFf%_d6E3=oV]N7`1i%,7hZ*#:p\B@(rXTb*"DIVA2b@PIlA]VB!U>oMeZ#m/W + [(Gbcnm77-Ibkh8GhXaI++pA=SR@njZr2#U_VGY + LH:Fg"HZ'Osp?-?At*l#2];0*nH%F>SN8!jsYi'_a?ft040>hp:(Ugq]TT0,EL^DCA1IF"> + ]4m3ma%tob'':G1T).8746:g2YA)BRc%0r^$Q?gd0?V>+#8XsjL"ree7G(i2_(E`-J$Sg': + k,hW,,Ta)43jM1i)%e,>T![(s93]M04g*lLT@_LLe?]$M)c5e%t]TU['Ucq5%ik])^Ce2TPqg*NIh=>^<4>gh$F2+Hg>+60_mh9p1-Dn. + /F3:s\E*3N:93FU@6-ZY_u"0YJqV*1akrh3:d.!hmRoB4X^VKC_polBk]>)NoHBV=*:ea + 3B1.l,A!sVHF!6^bU=24B<<2InD4r1Gah=%m:E?+G/Di\'iX2DNODkGj]d#"G-:6?_mjJ,c + s2W#EgD-t>'DmgHCLbrm],9Xrk3R4^LML@rSP8SM0uNLFM_rp$,*E,pjcijC\#6<*Y!>#P<:_*SG'*41:!RN%lYU9J`.0<(fTp"qBOmgC9!5fkli]:u^04lIh/j%gdX;Xf0suVVUf\Y".hQ\in.I@dcr=d'+i!K8oDW\*>VpV6;.YedL39R< + +50e]BC7qSaW9H%NWlhX3NgAq0[d!(sl:T"[cDjmi]pL:9,`1K_Y$L)RHUOZgSI4.micH.^^d%nYJYE$XiO::fV:GnuHmY: + GfQ,4XA4$=RS_i104%N:A!kD>EWI,03]c]VRjsV]<>bW:M=*2DS2$P?j6hK06gogMYIS=@V + b-$X73k7P/%Tm^X&Le9FE>2V#&NB_.Iq*;RXG<[)+K1_Qq&V:WY/Kbl&1"`6[0i9M#-O/M5 + =*/atDG;WBBmi@`RA0(S<]')+%Wq_'`)09ZDidfV:f#;FU10bXpL1Cl3=)_mNu0k[HfEN4N + (g("ZY$hH.^:dk][;$jQmJC'K::mVJ_1C14!#h)V*J=m67XYP\0"s@F\Y_*4&;`+)91p_0\ + 8C96BHS`!WJ`37?_8Z[EBEmKL$L$X&F#=?P6BFoPO`R8K1T(Y@Bamnf3tTdKc%eBE[Zp:A4 + :RK:9q;d=UtRWM+E7'<;#2?>s#nW;&9/'41ZmI%b7SU8S+.JgD:oOnbad'2&p+33PQqj62a + GLN'C#/HYs9pUrnsp_'X:LLmM-IO)&9M3(#f0[0k3*$1D[65j)."J;1.GH82q;Z(bSNO8mr + Qs>8Zmq)6Rkg@'`/s/\Ea[j?&l3'*f'UGh_fW*%;@D.J7/c0:_lZX&c)t.hAYrZFKdp\nMen=:?i6Fb + X\8dob&G/TT;lL?C.p,;I`>1)6Eb4nq#'t7CI]3@/->F!$/e\bXWa'@06>nt + Og2:#Nk-V$N_1=Is%l;TD;qYfFr7^jo&.o'"OU0^h&DR`7S$VN0e+5eD+PfcJ&l\mH:#A3u1>_EkCCrNWe + /(DO\t>3*i%uNl:(9O5b+UXB6Le1nPVYZLo&<[]!#.X6'Lrs=9c&[VV)2051fkO$?KID_E" + 6;GNbq8XCcnK=DA7@8/qk6srohbG8;bXj + Y++&p.#<64K,bY48K9i;HqeCm[#qf^.=G80'\ng.'"%jKNqnW`gVOuDl/4LS-T:fRs$Z:e& + 8:JmsN;AZ'0Y>%o;.oNc.HOHlQ;KYp=]O%dW-b$s"Z@PuYHG'i,D):9K + %Ml2\2T$W3J1EqKGZq,=?V>)6\Ms>K\04/f&/=Vj(-_2A6<>O0M,aho7kfc@Q#Zj3-ShNOp + D&>#e3tl^]fOF?l#GcLR<4>\<`)>08B7:c-nV/9ir86RN$K]?FX=m1TQG";.Z-0A_(:K;nU + a@B8k<[B'FGN37e\.gHB_44ja>!e)K-0%6fmd51Yo0QC0t'S=DL25Og1a:#Tit$D(;)5j@` + QPS7j+_a2[@TM?(c=XOqbdc%H90t,^Rj!AR9&>IZ?DQZ3AoLj*7J>H<@Dff,!e6G%SR;amN + 4;SAD).l"VF[oDV8(SSBb+itKLN3M:Eg2V)e<6mL1.7HclE7B4oVUrSi(#W/FDf'>cI#;:; + bV;P+1GID:?bJ*m<1M)G*d,ApN'(u#Bt:WGDen6q:ADaqbQZ)<4)euPm:-pQVe->oD.seQ_ + >^.qH>a&HI?4KFYP5H>pfg+H]Mfe'gm@-aS0o$pXN&>08*f$fi>>Jq0Fg3=6B>oDkkj(U]t + T2(\oT3I988Ar&=qX=:6\T%fH!bV?Z(3\5B,j:SucCVRgcEqK&R'GGj/;W#C#]3e+)3Ml<) + *W-XZ+g6JWN#DIDU%FZr\d).nEl\cE6L3>A0g:)qQ6]%1oX;\g++^Pq8I>C6E$in3T)T.D8 + gH5\R&'q3Xfg5HWg6;9[T(j<_.C4WPP/Lj;nCW?U`0?a(En5F_+YSPEY>'$'VQ!K@ + 4Vrj#SH"e&&Aja!>#ec$5*@P&]$48+Y4$n=S%$lDC)3^4.FR()r#\4N(kK+16!tO\/eap`&A]+qHR]YOg_#)!%cIl._#b1a.M4 + rf'B(,JiDL=T4r-FP'`iOp6#[_#Ql9DfOdU"-fLS. + S%,(HX;bT\p;g/.H"B&`Ce@!>3[E&^TDY%RB:m+Rfnm7CjXY'4F'C+^c(:g@EF$Gu$7tP"S + Y4od(LMT@]oc\cJI"q

m.)-r''B)7t+Tr@-F:M^D'S+a + DJM7Kt`XuUq$t0hZ5h6SHIM@>+&#!GF!@/Q!=;A[><\gM#g-!1fo;">?q_`e'\kf-s#qUC. + V5DRA4kEhKm)pPM'`n(B_($E)rX[t8%Ns[E!7VH$4:GW9&n&3W@2%Qb&e&0f&qJOB5s>cdj + :u)5&AaMC_)`T_Qf?_giu4?G?8(>f1ZP&kj5*/`^.76*8`X2NjW74Z?;9O:0raqNWZ^i8M' + &oM&ZYhK3P)OQRc3Fu]<0f+kOp?[pk+B''EZ+\*'rJaSsKbTIHS3&J\()Pg@+21rJOLkANK + Poe$H7)c`hZ8Q%>SHiQ`-':)77,%q,eO^nM1o:^%tF!C.D$Ra*\H([b$L#@P=U@+Z'b+qqe + U)<&KA38J6eAg:MqNjak+rtg_\)n3?S\+BR.3=!&[XrH?f'`hPV+R0J'%LA_f$/e,Q5YM/c + "ot7Y1Hse!J4]s[ + QL%rhimcu_"gRu8kEq];8JbC_A<*8oOVueB>XcM_dC6tHokaDO2N#,)nC=6P`*L#:RH6hR( + "ek**P_jbT>^s'>]=Ii=6+.J-k38*Y4*X?n`])&IF.XiK2]64;%u*#Xh"aaR#-g.N2%e7#G + \DaC$a[$s$m;9dl4JAWh3]/cRJkbU-7kUqqf.J'M\JVAgk+5bHD:N%+uKrk`2DBlp@d4bHD + +^ZorFP9-]*@+H/'BDm"g?EdPum6koj@E-*$%Q3EJs$,TN70+1 + -$R9j!fnDl]66kL#<&B'A">Z\l69MlNnZ"-s;Q,if4)9,OLD];1oq,gZfEU7rm82:#odRAR&9Dc#6lh[H2mDfJW>U?s%S0Me#qUW2i,Dqqf:c#R(`Rh7&+$H![t>VFH]&Pj* + W?j(R@5*=$TaJRFid$\Vp_3T#EsLIN;?+6QB:M;hX708j="XuD:[-\`q.j4OW,O^ps_5Eh` + 8:03Zk\Q3"%AbhX%RV!GGB:J!1=lOqoH1#sMBK??QTBNj%NM:+_;TT;j-3'<_9^ZR+=@/D>;BuL@2[NmtG0(ee>ac3%<1)n-Ass_ + JhUr#K-&D7i$.dVuI^Vn_AmIa`=Bn-:^d`JLc5^XKJs1 + q-7O[OdiH>>E=_gj"X'2c*$bFk6)ZRDi:J%C"8lt$Qq%FU+Q.oLTPQrn=?jg<.#%0%(_-7G + %3Lu;%fu;m^_62I6+B,tUND"((l&Zs,X`Q'E.>fnQP-$':PmIeIuQFQ'dB/J)f\V(NB"B`8 + ,1Mj0nM'u&[`G8.+4(EUjn@9ZPqk?ClQi^3(Gs^1aB)E)[$"O8!s#lUZ?nHj#h+u)$V0N&= + \H.4;J"VDDObds,k_pC<7nI2'f8i)3S/s\Tp%[AE9AHE'/Wm.7c;W*2\sqr+-/I2'mQ87BiO)Y@J75.(7Thr?ckn>dTS-&c,W"oJSWYP\UJInQC>RU)W8kH9DRhK+ + DPKC=2VX[&&AKl+.Ego']Gtoj.:oR3qZTCf'@@dP%=H5*%*e``I#pFO&!OkQRVt'J&+L!C# + T^F02!7WbN9[(ki2J.oqT/@nS>U8N#TV=-TMFQT1 + rZ"7SL0_/p6?&7Y`K/-s`3N<$aZ2?puc3/8)D.>92E?Am[afsU3[/Cg;u(4OY6lUbc,/Yf] + (VUSe]:MXfIY-jS0fioD0Cb<'$[eA;->J##rD9]i)QNH&L)i8P%28Zm?(?F7tQ#g$DqQY]6 + J$9^eF^+7b@15T:Ynl/>>E.KW9r5:)2GDfm1M4<0NqD1TeBD+V>&16N2[9o_F@]4OF@2jJ& + 'NhWmqqF,(U!jHr!(np2<8:,Ig'-oj*'1bg0rT9^0s+)iiobG_[\=-\K!Tj0^F`=UTf4U+` + 9#%5\gj[PS8YLm>G7(2[9?T9*q:g3/9ctGL#a9j/dLo?7Wm<:F5&^(S^N,2FVr3,>3r]K(ak7NY#(NMBr6:!sV@8B!!F'st>MMcs^FBpcl;@jG[9 + &Yi/m8ECf`2PlI3hc.Y`=W_d.H&to;=\*6=FC\X9OlJ>kl_u9gA@B4:&pd:=4bY2Y:78j&(IJ1G!T6=$k+0#s!%)Id!9b[5JG:JqW4X.7BUd8'\5.tRh^EiiU[ + L4h3JqqBSWfkLQ*'g_`[Lg!U1@_eE3pW+%P6AI,eV\NH60unW&-m8L="'cB;hX>U8F*4;0j + lc.+,7Xe:WjPW^Y$)9d\ra4-`^T>t\mWfqqa"RL.A,2aQpt$LL375Rk"&K&BoGJuD]\>*W$ + 42,FRP(bsM?j;c-%C.5PH*H`jteSC=6YC1#q=1\78K`I_c_-edL0HL>3^H90cPR@Z!Zm!p+ + K=TSe\T]+ZF$$jPmetbMGJ-$(eY0D$m+5C#H,C^G6?\T=b(H=YP$bm!rF^p3lfS^!jqtD#gjbAI>>0'rs + .4b:KrE(-`QnY0tI-S*U#\2U2O.Pl!^3]iuGKK_nsc+%"Q24"(2oI!,Jrj2>RX\:bEGkUg. + E%f^LWu`5n>Wq.+]ArI:\:ru!]ZAH2IjnuF43)>(]X/McSb,"R!3?oS8H]+:t55]Jgj-isH + (,_DLJTaO",,Rh$U(:42STnEr`1)iLm6F9)V1g2-2F%JYr8g9HlU:X,Do1cUH:EpfTUJk#I + '2h-F;mdf22-_4\HWHpE=Mc7J[>S3AZWj5`?:&./_K0,%K470"A2[[B-%%5>F(i".FU+9g" + #)n=5`!4'0or[IJbUAiRrpZ"G:*K;-Mh_>8ockl3OdIbMR\.h]/]qK.>)"8!2K'O,X@/fM? + .Z`KNMJ=R*[h!N4?Mi`++ddMiT:HOf81M.124e6^0#MQ&^3W`:B:UgR+$%=+u`__q6uH6pW + !SrW*QrKIC0<3i>E1Uq0`Z\ne4E>.;)5?,b9n/$rZae#gV&^(Q($Q;auBE$k^#3WMDJ$cH% + "C&S*W0EV#>X8m]3]?3UmCrYMZ`p@Q^oPhhREQ7os`tWSb,]h"*FN7p67s&moI!3P`H,l.q + a,GAcUjP\;D7nV@]^tD.46O9cJZd2E^2)fC6fgHme];4i^(]Eg*/M:q:PBWdZTnluYRCaZF + U,j?aRk<[qr9jjejr2l^/Ta+1C2&HkWK%5a`N%ed=K$LlcGfLKQp)lpkf)'BM%.Z!]Z-nLh + m<++$Y)q8i%_WZQq;2V8(2%8ot$TKE!^M/2G*q+,m7mJ5c\k@"@GO!=YRC!/:KH$@obs1V5 + go1_%W-%j@1GTtIkN*4R0/*C2G&_*^f;`"gJ6(I=9&bOc^[ll4.^;8M>1[6ci>!^&d``M;c + D[?Mo34=$_5-8jO`_L*e+_d]G>.SSl66GcmBdTD1D:Bh/Sb5AKg5QCeG#lqUq2e\f#CtPWn + H8iH_-T!l^<8=r*:Oq@5akYCk!$-XS."u>:#@p7k<(9V\M*urS7$L6Q'M?Z6Oj9_c7+=oKi + 9JV:QT9^qcLC<+W(Vl,JH9$6-86Zr5Vn+%IoHe:U(cu]_obN]>b9Y]& + [=d!&ap]:k3Ki!3GcMi"5tq)&"E18(@jbbm;AG$.+A5:mDC7`=(&C$W,pnd-V:hoaoLS&^^ + Ihd1mXa6V$<"II5e`Q/c4a^aHr,_XRpP8N)0aVt?(%3WanQRE\(ZTpC#E&uD + ]Lf.ZbdJT+i>oln+\Of4>a]09O>Nj1_),fO`8Z\>aq[P!h-dY:;J,fuXa:3ho,KaNp12Hm3I&T@5s1fVS^auQ/4)tI#!E:@$9*DNC/.98CZqFt"Tkcs;FL]h^Itk1G9 + :J>NA.udQ(/)nI9B/]=lkiCt0XU`t9H5(s4Msid+/B#^b\SM(KZBl*56Mp/XCAF[Cs&Qc?o + A/3_MV,_lmfGA;,$mngFAPhBU` + fkm#-FdUj9RG!"Wfp/%0V46/gSD!"o=nS?=rLV^HTp4p\ZV5krSM&ViQKN + D]AKdY0$r)4;>/Im`h`0kapK+D=YXLXLUVdIjJC74M0RW)CUCS'@d6K+-34\^LDEdiFQG") + i]<8d*46dt@GEPbcGr3nu#D8rZ!_eNVI[Ws7d.L[ZmIn2/n=6d$]9<+A,AFL>)#5uaAb<38 + blZkB`F]-7s&>r3=kXjkl9:r/]#9E`2^2,rCr*jnri#hl$4/N]c[RjA4(bm/nIdKPR,bl9p0l%fod."TWS^NJ4rTLQR:I?o#2eeV]LVMr8"k:]W#1G + X$iU?TV9c]0IKj#V^rl;[:R?i:WgPrLmX8&E+rI@5\_V4qcgD(#`1/iD(3JAf+s"(u_>0a4 + 6q@7AO.IV;^CL>BQi,*O\hi[+_)qR-gqY265/NYDobs1I)U.>4l?".H3cEg5[]C2Cr'\WQA+i.*'kcBVfN+kUf!IEn[;8RTI1!s\ji!`$a8lBf3<[Us` + -q5:m<1mfl/,3\:9F6AXa@TiHrn6_!miNOraVl&quM=I"\@8Y`?otAr:3`h:#2 + $p$/+H@:#VZC863&'H4$X\bQmUbJ%cN_*iN@fA[Y/l>EKZ5(?"%2$EDGKq:6G<]S<._]JKggcdhm[]lBC6NgLc + C6V!E%7T'l6VD"8Zm<1tJf80:clpD!YHSoGYTMqr]k"P%9E!GtISNm,:M_47eQpp@'38Zrh + .8HIj[@5-I9u#Piami'V)DK$!.TXJ5AN0`_]E(i`TXdZl-5f-?G=nFcIAb0@r1d&k"%dk0N + mOWcH^7h\Gc'^XnGhl:"Q_:?]K#aQB26peu/9]!MC`mE**A-F\e"ufS,1FK`1>)HKUtI=OB + ^P&4,_0#6C20JnBSthMc@gnF%]aD]\Lg0(\O4o^?DaDg(gBIeN5bq-klYp#69.UX@NO!a + Bs[e_9#>i&H=M.YC\,K%@R9-QiG>'_BL)m06m6*a`6NdmKGN$AR(ihUU3L0q[A/:$<6M + @i,8ADM!DK!l,n0#V(DP*J.rAJWM#3fG#l4B$'*fJ7)(Sn7cK_Fa)s=cA(+LSnh*/pL__.= + 3?hhcp.^N>%cn^7VDs`Ql(D<*s/qt!`1]TO=>gcN.\&S!_>.mR(35/)#/G7!gkG>cK85<@b + >WumY1i,Q&9I\@E(3PPb9K-TKc'YP"%Dq>Nb:CVZ;$ucW,E3>k + 'V[<7LjG,F4#NmZkLqF0kY6''i/,Q6aa<.t^gmE'Lanpi*r3m=:`tEA.';dDs + J8F0G'kDsD8HcP$"gD;;?u1SuH(DM#I<"!>LXuEbf7m[I#:NIR:gDRB4b94/qNIJ2J//@IE + s#9NK8+ + Ie#n:oQgIpr_=5\GjOXTft:"HY!_O=m(4hk13;lWA[%gC=K'&dJSi[G[.F9J+!cLUH+r3.q + 12hMXiInX/:npl>DGA5YE5Q;&_F%>X=?j8N16QHSDgest0)s(Sq)'<04DI+"Q^+7pnDC559 + QdHO@@bPmo)VUOg1)Pd8)9rlgF%>qQ5TjFrfg$O,$P/h.@=m]6ZF'8Kj5pk[e4/L/'/K;$" + O7m/Qqg?.dE#:\6/Z/Qc@PISIAhP/(AMg\1/<3#kQ@Fn7,\sQ,4`^dY_8K5;@.8S=.92?s, + ;Jo4B0Z)//WS.h`n9]$qkc/.GJ;r&pe+WAW@j`N*"IfL5qKSf%gd=Qi/gJ^_du(0*NmQpPM + 5L"]H/sIJ'.e"1-H\^:TrQiom@k]0;lkMa8,]>KAqAVr/cIp:`:1*@M$%&O$h:l;A02tR,T + /unV2P1:o#HG_PQTH%3O]rKau#ioK[nk-#q'q&Z*RW0+VJfo2] + "EaM>b!HMMedNjSlaT]M*J&tT!QR9ifI&)7Xnm&4F(NRI$>K\e\r`^1:r513]jikn(QI@\= + J!:*S_U*lHUu](<^p$'T$UaG"7Ms+$AptLaWW,61F;5*LQBWC'59K!4@X_*b5di9V5fb\n: + 2?4"!OM&7'6!^Yah/Ic2e5":[3R8R!+Q>Mt0gt,8mEtfo$i&m&HW0e + nA:8g*de:RJ07pT1bf72]1%Jt_7(a]E=`nK41-B)hm8'NZj8?'T">DI]$q=fuJ.k'>6hSS&hsI@fX?k(TGkH4Ge,kh8diVIGX]F$&iV:bijEs#bD!@jnW5$J`_7VY@Ztp?E\ + "poNoj:m<&?b^!ZE.%kI2@rtilLEP+Nd_L6ejg%Y!6.G0.L?`3)`ru]#KqgX2WP^cVn"+We + 8*J\V-+]8$[*+PprN^;5\i,fN]n19(*81ZfpX;HQYAe%,:-odm<+bo%Sf:&5_%-bJ5_qf,+N-g+&OuQXLlj/4O7BmrU3o^n&PacD= + J@65=MBLOGS:./=sCn@U*AXUD@Su4).Pd@)e4hV`jjbB,BRpc&^E=rn[0;\]QT>E[3 + )1II37K[E0'O<0o$$g&OLF\"CRl'J2dfq?r.2]EXsJ;1moKi1/8D?.R^*.'I.XWNCm)08)i + 2J,p]RZb0j]Kaf.6HjRuaja_Xer%(RfU-=.C)mG8,7! + Rji18_6LJ/R&V$9KAo8Hb]OPRLO#"&#ar,2`A]2*KMc3TcJhTe)6i_lS7pp^2F(@)i\[7tu + f\4[#G&C$r^n=1'i/CO#5Njs1T_'Rc:iT_l#7,ScU2f57n-pY%q:.OhFRG_=4F#0akG1V8p + /4iEHV^X451HP/datf`OAmk'k#eHM/S\8aB;1R5)6srVSF4+ + /qC_RKQ-8u7=sQ)Xfte4Oo-6;\?Z71PT/DP96(.HJJ_+t[l58NH#H.4]iUfuhOE2Fmre06\ + 'FbU9BCS?`V7,KgAYHKqgS#-!PfN^s57,cJZpI+_,r&h@#Q+Xn9th`(f(HH>U+slUDRlcf' + a50nD9dimDN+\pc`LfqCtA#`D0S(in(`!hYo4JY?gF'8$.bj^m#K6M[k7;j;imH2oo+U,%H + RdlnOet`]5[g4mYt%nP2m&:/;J9m&cOn_gHqeo.B(UpQqN5mZBc"$h#m!2(/2*s&n^U!,^K;"c,J=HO3T + 4A=4$=t2r]4f;b^63r,tjT(AT!e^i6'!_@``f47AUI9-_]$"8I8FrtDgk(U`l9NE^#9e6`_ + 2tM#GmM51.074F#UR\$JOqF?Ur8`GHVRE)=[=G^+WT5uA<.?2$S'8gY9ib[EYD#_/P5lO.$ + FF4m_Y/B-4s7F"_W,r\WskjB).kd120H,i*#\ + @0Gsn1*-^R09EUaJWJU^sW]$H4s^P + 'hjYmB3,6J:Bp;_#:s9d/>EHS5mPam#V\/bXMG1D^Bj2:#m&+r@#PCEU*NNR!?e`WTZX1KW + 6PsYc0-M"D!]HO\I7d#cYW+[BL:&(`=1,?+5t)_iKbBJbQf86k:=1P^p4@sG1)1qd`jKW9e + =b,RYe.1duaeF.&,a1b^`?LR]W"[_.4>_SCQiP&hRkLB^$.Cgt)i(f9%oAD0FQ+IhV?8'>^ + o7Tl:h,Z4#U/%I\6:9q'bp&DAQo+g%giNNOGl[Zo0W,G=nfb/E'A>hj%*gqnHp%F?MTK&+h + 9.;e/aD;pp7l75TsR5(KfNV&YOWo'Q+RS=(G$H/g7DW#NMS$!!M&1!nK,tilY2+S$"B!OE8u;ult5kA!&R% + XdJ47d+kHkfR#l"7E#'nEI*IVBSQo!"oTgSd_;X'VVJkl@gOE]d9slW7#5CP%^hj3@=1G-S + F:(C98k`WpWXA+5qA["@s9$bQZp7%SN#l^lSsX"Kd^h8FI%k&ub=mjK?G7.k:&YlME*-eeB + 9"*/Pm69KE75/d)2%&tu&l%n/(R8?rb#/h^&o72GSM>qYSTZf1lrTe78a3!4\.!i)Xf%u2j + >D"uHj[j?:p;]*"JIefiB\tSs'i(aMNH2uk5!FX%YJLpjf1_V%V"TV:&CjHE'S.;bF!`N)V + 1Kac8>;^J-2D\c"F'gi3AN&"O2Y2&M1O1nmI5c4s2m\&pF+lT[K/iIZ3>7?Sn0n-`eo*7$I + uo?dPGWc4GmErG3p`ID:iQ]g\B1Ci#N1?WVh>tp^H9ol41!FXF0u8%gH-e)3j=B'E&fs8m0 + OH$$"tH1F7haoRL/)Y!N9H*g*.%C.6/0.]HXV%9IWXRU+eFoGb'tE + Q4\`dD9kqUED>iOh6c8"'^.$OT&,(:)JP6N[.p'ZYaR;P/^_#;;8!r%#V^8=;(9F=Po/JBd + N@a#7gZ3iJ.<7/8/Y:p*tCA2EV/<,4QK7/<%^j'dL.0t9d*8=<>#sF2-:sZBGWtc%=m@Kc? + gb#D'JD*k_`(ZOuFVgQK`EU,S$Ksi#2ur^^lPG*OUL`dTp"]@dFTF=t\!%A2>R\FR;tNmLJ + 2('tG_jM'ug37tLLC^nF.H$@kAJ/-3F618%1$Vd8Ac?6lJPNu5%G*@oG[9Yc/a5dn_+3Wl$ + O'B)>!EnOj(7eC`Zob-=_$S_6onFcJtp!WMB@:UJ=6kk4%"<^):1B^[R:A@Iq[SMrpASEm& + P;1%>\4-I*5odq<.KZ8,>QL`[+]&m3oEPA6$>Ym:!@/:$ZT2';3AuROG)9ljMeI9nBf;)fG + )p@GQ"eg;C%eB<2Q:F,X_N$_C::B_G-s8MRe%2a%hnC<1Sm:!jZ8Ui4ndDhf%C/scTQ5`"< + ^$0$OZWY$63L&+'.9;F4,FOgMM#jE$rnHpBa$La)LY?#ugY>F8%sk*YY58('19O+k]-q&OE + Y7F!rt6(HDn'.h+P^>#\k><96B.lX+9';5fsNYk='@a7^oj5 + Yg^ZUL8*s]DSFb=)O(QE]bH:Q\ + E_T&>9`CiGK['?R-AW-2;Ud&n^LYC@)b;W`!see[$Wg]3H28>POF)i1p^(PcN_!'HopcQ45Fs&@,=Lo_@KDdck3rm>gu.p/W\ + QR.MKZ7MLF*/AO\aU.m>-6]gbn0fDU6q&$V";j$1c&qMAKZS2DQPqV&p'7i%^'6.WXp'/)-<)+=dAegQ8r08H"!#,AX9lHQMGZhfm/oEH^B"tC[?BLpChA0& + T&DJE4M*M)4>F=ST%X+ED9+-/!L0E8-JY^#5OaW)7c5QT:&iu"*ortH&@'Wqf/%MES8l_d' + [po*M)p\F0m67e1$ur/TarLF>m'=d++b%F$mA/#q=L.TI^0NarcsY79=,FqADa:AO`#-U,6 + OE!()T]nc=<5)StBW=-%UR-V1@\8Jd_=oTuAF9 + ?I[mM6UB[^iVqk>,p/L)+1NsH2+LWu3E5c>Xu09((e'+@.tkgfKb\YR'mSsQQ*9skYW-Obg + BWLVT$-7?P&@8oKsCE0Z8-m/plC8VP?ZSS(W#qPTP^>nWu_6p9EFsHQ/RW9jbm.(93 + (ENGeaRbJdY5:;Lor'+dn>%D2n,D@9[k_Q5u#%*jaMn\2aAHP0t8o,QX;=,ZtRe)&"`/SG; + eP\hbF>7KNgFFpfgc/AOb3iQXAK8/,LrYi&o[KOOpI+;*S7QlFSBqq7=jh7\0m]t<7;Hf_u + Rj1bET^DlRT48HX2U0(N(i[%!RJ\;!VoTdM\/gNS#=*M> + *%/Y:-,]X0_>2!e8)]u.'u\eS`A@=\\_3O!gN+?CSd7Oh>\TdTm;r*pFF!JMg&&h#q8_]>L + 8,.si#Vq1VOF2B'S1%n0`Rhm*HoqEGO& + &!dAbah:\pc7Qi0b@$`^lpWq@;O)RSJ+?-:,EMdV2DTW,#]bo\f+XV`]5g=_*V#M&\0Y'<1 + 6HL"VA98']mY>o[-pfXfGRBW65Y[)m8piG)n@dI*oLXL$q26g@)-&AjZ-p'*9HRq[\;AGnN + >5XDG+U/LoC@Ja:[(*HLpphnW]doRT[@#G`2:/m!^^M@t(7#Jjhbt%,fJFqiW3dQS@0`O_\mH=W=qt+B]+3]W'R?!#?0_)B+p5q;Jd0RAcRIL^8OU^g9IEr^ + Xaf8`]kFjZZ?!ISo3u4$`1EcDrdVla'$GZ&EO5P]T"2hne]'W*lo\q#g#YQWc#aqJT,`F&gj/:(+GV# + rd%'fSTWSYfa[MP1ah4>9E(SQX(8_Bb'(tLg_UWmZ"1l\;W7Ek\SH/-kpH<'OC + \#$an9DW"m>j6UhCSlLK)SGUd=2G1"J'ZpOI.ZBG2qR`%Ii4ddIJSu]*?4^c++G:SdRgFdR + \h`aXfC"]e$U53JS">-WXYW\&Xpg?Qm^E:6QaGQK-Q)PqVTkRX]<&MY5N?npf"AeHKb5,=< + 31pI>+o_bpD2-logV;HQ06gRp00lZ@/J8?)b.IO@Q!grrdWW!8$XNllZF_;3>n4J&,LXL#a + Qs$ALq,d,hnm-NAt*f5YKJ@Po01/t_\0$Kai)>=g+/nu5aZ!1-@@',d%[BI"4Lek8Nmc#!E + O:T(Pq'%VJ!=j\!aMGsM<9Va6hPm*&is&5rJ.b1!,D>pbB=)7Kp"cgQ2="GH1Z!dQFD7-Q@N'8U,MoD]]:],r?,-/eEL8>94DVf+bVm=rkY + )c#Jd-:1kNAd\;X4M*p)VpF`^0X:l2\Q0^,TNUW]f(6YQOdHAiGj,_ITIJVal`0ih&_>IT@ + N1K\t+<%B=Z[`G5M6q#EQjcnG3!RO#Nuilh>_>*oE$njn8IurdUQLlgalP5Hk7c!C-&n!s1 + aW1_Tr,E'tQI6*Q"a#slq6,7;;,Zni+XE4e7A6dXmY&O\?!5ncY+4A-AWDs800)GSFb[Z+d + An8]P)r,3Rq&_(oC`tA.I+NSR+M]kX>,`a6C11Z>b*N/h-.*<0eX"$a4S?j_=;T-$t9="c3 + /=bmCs"[9s/C-1J1M"ll9lk^:3)4OZg/4+=Uh)\8_^Nkk:6UY>5YuO("[^S#2$5MU<*^Fsd + F;_L8P*qk.nW_1D)"[S1$j0]7rcZ&:t0,(78hX#2,dHrk8DfNfuHOTBcK$jWG1(pM8=#_BDo5UgP3P[HNHI".m:#MkWB + EGk;!![?.Wf(Gi!*3ja,dM[I\cOVWl6Qhm`R_J7*U:r)a`S?-]qmUsW:]S7hgCLuK8;.uIg + 2RJq&4(u@-1**CcWD.Hcr6^&H:$3-6$=\`%bA\!OtJ"%7W+pXEX/MZQY+;Z[#4B%*%g1c4Ken:QDBGYc,OEBJN?aRX!>Snmo0^FdZNI+LI(o& + aqnJ+PlG7FH;V2afik7,Gr.Kn42\Af?I)BA3'6=f-]C4l:D=GQ%8fJ!icmLZ8*#)5H9gAlk + 8[TB-)LK_RhNdU9o#PbnJ&WSWd8Bi&_5e@j&N])fog+:4Yp&X);F$ + 2it"ou^f+Tt2fa7:Ar#a#fFKPj-hdPsdn;'7/?1mAV=C+]b\'`t%O4;Ql]+bej]-o,n;8b. + U58p$gWZBV8P),O@k]W7;d=L+51?s+e4@MRn@Iu(?J18MK:/k+.#9ChX]-4tltAe`9&WK:V + %8WPVR9[JV^-9"0ue!FU`(':^lMPeeG']PL-aZr-L8UtRc6@NdG`)A^07-gB7[WP@0ffSA] + nXbr]HVZZj4_M_1T2YH2ch[B%-hRM5PR`=B.M4s[9a@@k2MP')(IVqZ&u$(8Mc!Y474?R%7 + EE>c(A):"Pu=MYWe-D/jYoe2eLOQT[2^V&@&3%EOf"Af7Au)_U!tKU'-`U#P&?s5]J;cE=[Wh[^,<`NENX%LoI0sMi2/?>&3G, + Ahi3jE7F'?aiDc_(l?5EKd*!;^\L&f44C%q[[K6_[EV=.l07>kK2&V/O2*Fr7@VA4^b_24: + :/=h1eB/]#q/ + aL)OW'38cP"V?uZ?D8]i&f]`IIFeh^Q0Du?h]rYL.fZ&!d`$gnJ3ARPMCMGIIY#Vc\,X`'7 + H6JofgFZL0ol-J'[Z_iVYh>0bal2E$bhLGT?s-<"jmD[L9p=6&]4D,fYq8)T$_u0QG;fL=" + ?cj7m$o+nRk;R1:E/3/`r/?;$,05:8lnTbXkl?]1=R.Rk"qQ6QfRN;7WS7bHB5L\u,8EO7+ + t.g8_f#c&EeqEX3=#">=5Llg\<7**If,KVii&OJ<_?P!2F6#],RP(HID,(`\!<2:s`RT'rg + =B?edOt)X/=_W.q/Z#EGdpUrN)6Em4Aa+HhP6,3.Esc::4Nc3<0Zn/%652bBH*D?`!/T#@2 + !EXD%3_B>=>4%h_D]$YP[@nISW0pu7a'H]2e?gmRBuUQdFt'WCMc5<[,p)?:Xr(4^3@OmWS@-Dn+rHfGOf2TJjIuUi=I4:[7& + r#2h5l8"iKV3ql3n&I49%!`cn="hJ/(U/o*1Hi& + 1Od-c3u,'fD%>:nCNJ3592'*OqVGHM[G4sY_sV^aj6-JV%Ba9g\^s9]h)has>P!Z_IM'M(k + 8S[DdYKUp0>f:M_]bVgVJ0s57W8";!.6:+L@IPcG0"5ZbugLg]7^ + P-3@j<+N1.*$;6PM@E/2Y7,2(6@c_'#('K6\s,Nl + .YS._C$B",](N5""!G\:d+,PDI-p/VG67"I'N%BLmgC5f=p66u5C-$pQ_Y8]0SI_ndD(j?# + cW;)nJL3=J]`qiaGfPUg0Q`A4nk9"5X8R(77JX4163WGK,`=uj-;/+Ji/^]OQj"/c_pK;5* + Z$HBYEUIHc0#2jM,r+%YB>,BZ>K.&R/Z`'t[X"=^ZXXn9+40C/6Y*kKWX_Y44$JE$OU+O9j + a"lI4CiqgbX0)Q84/@*R/9lsF4+l8J/k`jhfsf#Ukr\dY/ucOs)XTXDJZd270'UM@A;%-2a + @L(<*7N"lUkq4=d6+pg]ZWQ&iS6Qtr5djR4V?l!$NNa3e1_Kr"TVIjEfYh10E\Z60RMMFMq + PeUgn1C30[nl5$f=,VShV`-4rY0?>N7GBj=t`<0q&\(t=N69JSOW%%kQ3^:eI5MQ.Oo/d6#7DKj + *#tS6V+:S>''!)oW!2'gYaIT\#`[)1kV%a4m@n2E?8jL#s5grClj%W5%:-eSU_"\@hfG$Us + '.+,%6!I;SA/DXie\R'i"3:_a*7E%"Nd"jg6R:jiRFN<[b##Z. + ?(:e_&=/IV]j-I?$.K",CsQu^Ln&h<6E;*0of6]sGm(.BFR`B0@(;jOJC=#kq8`KQ`fbF;A + a>))mg;=n)@#SdAJ(afk9JcI%8';@rPUs`\%7U7E387.**DQFZcJH-b`A>Y:-C.jaY]12#Zl_f.]N5cJKri2M=eKLo1 + >_30[uSaKUgS^&3^5hAS6=sE=m_kbrLmBeU + *j47_'JNt;--UGnlfdk:HUb=6pAq-oidfLccl.t#W`('5f"R;K_SSO`N_B*8qfN3"l)(A8T + US=CYE;[71Hb1;isq"\iEJ%76FStPZ:.h)dD\L7?&N*<,G04MY0r5MMA'Ze>/%oND*[3 + h+7\J<:T(`4n2MP@YSaF>k4M?3(AU;fi;>P0YR6]I%&0phVYE*+8h_\Zf*$Lr]Vo>at6u7; + ]/ULkZ/\$5-8>"ojpaa*m1m'^?oUK'kp^6C=]2cZJ5'1b;!&ZDq=Um!T5&)@VUFd$@q5[,R + &F-P.!n:`n^%2Hh^kg($&ON]:O + ?2rh&$A%"F.^-,uO5%DSsA)9H1mQb;#%,*de"&jI!N>1)go%S;c7(oX)Y";fmp.oWd"->Z3 + +Adb?=^)m?jISo*0N83mZs-R0%F)=cICWlh<\#FngT;7G]Mr+\>$L,n;8!uSr)q"H_;$ob> + Xi'u!%e991T(u5J.[Qlil#)e-JYFFgr=#4^1giV&HN^^X;D:pYRC^iB88o.;Z:W@:2sQ-Dl + nFE?$/#1Q.W][g?35,h7DOVY'F=3[,(-,Rgn;fh;VBCilDkqe0KZG1W7d4JlG[hhKne7NeL + GkMWe`'L2H6__YDWnO%AC$?NcNT2,IX#:t!'nWVLQ7^flHEFB)oHeTFu.9j(i2>#i96am>Z + T:&c?%!*/Zdec_g(],eVO"Q2T_?sX#pF!Y4@n(*(A@%&WSX7N(?o[_6m;m\qFE-%^#D + VQ+?\&YdMD#Vat%a + OG_ctt,WD(a=T51OL.enn15m9\C'2FB>2UuYi_Wr9D;DFW]8W+?1jA>W2k#WW!pLUV4t"TV + Tjro!3EWoY:"jZ_l9H5m.'g[;0XAVTP_Po/VZ)[0)eoMU1+o7ma#,'%2EqD@o7!P-NGD04Z>+Mu92;JEokoAK.dqDMKeQJ]/Tf1UYZG + ]Y=,n*UQ5L',*\8-F!Jhq.hR>ataC4s8iY@"0[*.)S]JRpIlgIYP?G;!+*8qOkbq?nM2T/_ + +/<(c.""6ho(^0n9#DGuRL[I*Q+=0'ul^)685TGQ(kB%e/6g*q)SiAbgRq8U$MG1P8eqWVBSL\7TZNB&'QdpqCJ;`+=U@8V + Ju6!-ea!LdHbk9q!JZE$kd$)49S?#%p.)h0a8+<-7;FFm"5]?/H%GpC6W)^2hQ78iJA:^&`9XV.neXo-5@K2?mn1X6M!E + @$`i.QDJu#VVp&fi81F1cTNLd.tZ*&W;&sf\7Th(Y=mHNiHDKbImg)_OoSko!/eUEa=RMg8 + `B$VFL9.TUrmf1](GBAldQG\pYc2K0Ec"(fSI.^9GM\Te[9S>N[!&Ch(74K$Zld(d + TYOMPknUE4_ImkN(2jS6jaYO!0ToadWn6X<@(-\e+*4o`aLZ + `c"9mgUu!m4;l`u?\ci:l6_[FkdRF$JY2aXhl'1IM5,D,V<%?0CD.3HJY#]>g])?f\l*8oH + <_GOQ_dp1/Xd8i_U>p<6goe+Z[E6#@I4s6tPkpY[IR0b<`flfaXC!*3a[6)jgeK75#Xh!ei + ,h-u$\h!A:qh<0'FJOG\kP)\;6/13F]s1n;a:Zn!pK8elA993Ekh@NQ+\FrG)W\A**#Nh/UJ&`/NEui + /5&aF[k,^cr3BrRa8En39QMc41X/GcRk`0oADCqEPED=q)Me+_5a-2VMA:i=\X$s%oF2$k: + khiCJ'es1&nMt<'@$P@9SuEs)&ZC7D! + MCpC52Og_V^FFBqs.O*'3T%GG2,a8P+G??`OAHf%?iPC=@Xp&TX#=8@5Q+8@H!K,;NK%u+M + ;DBN9ER$%E+(FAVD:mAuA$q1r,=PQFSU:B>1#m0m>*!no%<#^Jk#[#<(bhtYk!as(n#m0qJ + GF`B7rdCGki.*&Gkdi+#Uk1W[Q_!?CbAC0(A/PeZQNc@JX.SLfZ*%kgl$'8r-,.F*TB3>#Z + gB'DF0k8GB&0t_]5Hb\P[ukuW@1-\p/Vr?Z2_T.49l&,QFm_<=`HQ,s2Auj8SQ38-'ofXQ= + p4Lb]_)jo\D/sJ!K,V,`&ib$p)l`DKb*/_6 + &:XE,=IoW'&l;QM,?/Z7/3SlU81YDYtcfE)1@#WKn1WEPE)WOca&Y + ^sn5OsUK_g$DW;;A1:19DUmjEZg6mQ\b?X>pMg!g']#6V*UW,31kGO]"508GNre#Y^I+E`P + k*EcWSL`18"0-q=T2jA5t=N"Y]#SP"`=-]*MCQ-ri<&+Y$BjW([o`2KJr\Hcoc5r:c\+d=QHO + Mqm-BQf=XoLt))g*K1PI210E>`)C117P5m)l)+dO""O/8T_P2,c%&+dY^u#a]=n-,f#a\jZ + ?0cR\3UUI[fUHEDCWrAuBs?BNe68$8b!rH]Kf<&4igNfrHt95(OJbEi;P' + *%TIMW.&:u<2&Afu!$djbn'sb)GlLMShHLA)i9#e'bok;tmY@,qhKj-'ED6>B'\JH1M>q;M + f=S:K\f?3fmelKqt2?LY[eXBn^I>:uA+N)0K[2jhb5DijC'a&+t"X)G!!(jY'/Z#fb@JEj1 + VR4sWj(<+eMVpCIeSs8s`GP>hJ%&JD,=HdrS&.il%4;MA/u+66_Tg([la:n@S!A'?C@F7<_ + WT`JVf\8rdCQekHJP.ZG#u>+]4([c"N]MjcM?B\Vi^Fej2?7^56*ht!qdV@fR:W4<"HbO!C + 0&^%&&nY`n8`O_q98No%Yc"E!d$+@6p6?(PW!\>'h6 + MUq[2%HG;%IO_0SsK*jkPZDOA\6l\nN_PV$R'd,J%AXdT5t:M*da$RW2(OhYg+%r@Ltof>+ + BnDW'%g3[\Ik;Nn\;:NL]WW'q2kGbUD%r*$2PmPLgl"_-N<$s@B!Z`ja:$NTOh,6ksA,6\( + E)=$.0=/CMOlfU./p$l^:Q).oj-&5^5-H9/qa(&qFNa5;mUfhj7Af'9oZ^j;)YR#X-%r5SS&+;NMeo#I`#+(CEnhlB@+4K@k"GHZ2rB!)JaiX<`VXXDt>5 + 2&,jd>3#"Yb[Xcj:T^:$%%falecu8@P:`>J@%[N-o!^H`%_Nb"9+T7.T"&18o&3;D&3gl'p + 'qo.TJq^\D?FNhG*;FNT + gnsm\XNthf.YS:&s1oH;#pr%o*KCMMg@"OPu38+5rUGWg]E[\uGo@WeD@l/hZ/O2QNu?X=C + rc@K-tF/fEXI$_Y<@Y^@iA(]g9P#=*M?::(S3p_Ubi(d[pKtQVHDR*# + rkcU(T%[@jUR'P#-l7mtWAg#BmL^2jh"TSi4;*>f;GRa`7'5l*2maS_kg21i8DD'r:/!Oq& + E$+#d%:LZNi"uKF,4G'ME:3r!:C/&^+Rq*%]CFsp%j&Cp+MEP8FjDKYMrOQs%L"5mH3;J@Ic49NB'-PCM'TND&0i(['*=l21Cr!`PBnda4OW^`" + >be]MinlbS-T+J#r?K[M+/$W$5.u@##hE(M:i+FD$O"C#6[L<#$;'CROG:5!,GgCi33Ssck + 0\/'S1KGJ\_[)V`kR!$P9F(NPn2A1u-HhUQEGZOMXF?aUE.3%-_X6?tuMf>7I^shSbsNNV* + d=D%?1^i!p&[OS9:.LuC&kWG7b2OTuH#*h/9'ic,ao'M;8#+[lPr&ofPaiK4d1>Zrmu<'j1^!, + `-b+]T0t.;K_+JYm?8cj.@:#dq-HQ*jlGAl2WY\?.K3.A&K"$W=Go+CJcYs+^c)%^bb[=@j + 6d3i%>,)QjM]j&4$d@6#IL4hanDlkHpW?2Xk4.(Ad9uTh@#6@)6du__FM+5i^t!8s1*=9is?](+BJdGFCN"!3e,]Cc[V'bMN + F'd'>d;:5>cJ78^^_NqJ8;RJ#R(OCA4*5KVS$_Y'CG8YYN`!/J9#uuga2k,)ZT3/RnH8hOD + =0$Y6YBkE!HIoQO(UnbC]6kF*Hrn(1GJdIhNjc?Z?E&ojE&RYPUGmDTrNeRhorEP.ms`Kq> + dIlgJblet#b/h9"h45VQ-PL.lM_s:*+A7P_3c2#NZ#>KJiFp*==\uj07nF)K%Mr5(cR)d?l + S.?"A-:5'q'L!IsmN4dkchepf\cu^b.p'(u_qo?r_IHDBuY"@2O/J()^Q5I:?<2@Q#DM7^a + ,LXogpI"5%Q@X;1f/H3`Ai&>9dOTp?T_j(/niAM`^bF7Y=7d:O/!h)#.kPPuM0s(7K"QI]t + e0\o$3nI@I,B@J5@0_5g@Lo>KUW5S"9'KKl0s$Oqs6j[dGp7lAE95:^Uj0:%K2S"t"@?M3X + '`hPXp+;,GQmcfH!2+37+HR*6p_1+B*]0,tWtB7!T'3^`Df1bWe?'QH!L==P?f=Hh!Ob)bD + 3Gcp%3X>Do[=`Nf5d32Qq$%0/!V(YX<6Ab$"2,GpM:,E78JsRFKse$2c3D<G@ + ?umB:K3E#9\$oFXJTn-qk+i,TNV + 5dRKK4o#GBcaTc+`?qAZ,+mG`<'INV)_J5:i[/RFfC?J&DYRONt.?7oE1'nKm4AsNLDkBQ"n@.UF-P*09:326[8]HR0,gZ*GNIkWjrXT=VX@c(IaH;XB)fn3?/n+1U?0F(I + ?7K^gX:4\gVm=#lp1t_(ZOrO)C(sPi^)R<1t,:q+A2$?FL#U/F"'[*Wc+uZkg]KH$aQlJ!_ + kGQtqhO\W=8:rWo0U$n6&uJ8,C1eT?)5\k`hX3Np$Al^M%`Fmg]IeJTJI_B7]`#Jda;f62F + @I@YS4JY8;8GRS>0&&=\I^t6W$ + .a\6]Q%dp@U=4[g(Yb^228^>BV>=?4Qgb3PC^4`iJYBcp]J[X_9>nIrIS"sbRV\Ij$6&gi3 + *[cH4DUl8DlapPF$m1#[#lK+kidsXmHo + E3jU@qTh(r$Hif4F@"Xk:au*6Xt)EoU)_+0ut3+8[f1iLEu#q-UrFX^%kS+C56pHkE%t8YuKEteJo@5:\&egg?d(kFBK0Y;"T[22[c6td9^75kmS/:#:( + #BtchEBcQ?0g@q(4ILHr[6`t+5u!2`MpPj&3Ing%B%p4j]lfeHupIhFr2C&!To5477I:[^'&Nb#=+ + :U+JK49YRDYgR\8(l!;[9mq@Fell5lee5,JjA!"o46lTVT>7.]_.U[-d + M'OZZ]i;O:VQI^ASaKr.mrnFINRI0pBTL7]l_8$AMTF5h[h#^IZ@!W*pA/@P4XRNdDgqdRW + a$9@D%"-=&+o^-R7hFT`#2J!*HL]:(J$3hY6@/UV(5bj/5L5f&&30WmgBirAkRR\=Bu!sY* + =h*fWV.2G7IC_]eQ + H1C]4mQS5STol!MC>41rPkK&LtRc"^kII@!W/DGRP^f$:*;Q+LVe'^^NL5$p_lF^rd)e'Fb + &n&c_J&J+`hn!,+6bi(ura((b546B)PMLrb;NM+$?c=L;BaBf76flaQe859>nW(<`WRQ;8i + 3/nFOp>de%JQ;!q:.$VpoIGoqbgjZ!#Xn + FfOPP]j\%E(/OfRR`f?O>&:53MuMOO\#\'r$a%kEGulNRpUfh`rIMZo#R0K/*eJAkWRZmHr + \kpZa=>_Yr2*[KQd',_WhiYo4eU-$oQjcNR!q9;@PsV1G/%IjfkgAO5u=M0Usnc+>MV47H, + &<9mpAF + n3:RB]'p\fNTT:bHCs$/7@#62KC'Fb+/n5L&*5nJ,S#JmWt-OE,DN%YN*&@=5'6ZDZT&4<] + 257p;g,Yo#k0f2lE7<*ER(e'RNnj\arPA8a06YPt.*=)LgJ"/7cKcnHW1lEVMP#RJgaRE8F\?c^`A,TC'[JO/'/\/r9lkF,3)(b?_`=@\XRA2G1UQg;: + NQ1*5YmTk%RlQN_cf0`UdYAMcr=j(7T)'c,tLn^Cu[J&o[P`m;\P,'/HfK):ID'LnV1i$[8 + `Pp8MTjg=B5$YIp(>[U&Jku-b)b^KiK]7%tjs#Uah;?q$'BSj_96T)5eXhEhCAAb^\>iXf6 + )?2T<.@gU;\5Dql]l_/\73--pW@j&kbWU;Qd48XkJYrb\0LVT>_`-\u48d=Q4n!oVE/JRE'DOCTn%$j2%?OR,@RB%d#qS#?6iQfZnT + ?tpX9rbJ"cfuA6,B=Z.`C$++ZJY=$/#*"m]qGrbC&1"\PMM2(%@VD5Yri<*bg;^@1C>.qPY + F5[8_6N?2-I9"9k.1N"D4=kd\8D,*rb!/(VX:t_qptAQD]>sN^[NWHRVM>>0+#I`S50o4EE + m5Xa7;?:-@_$8TbjXOJWOOeR7<&5cZC0,6=f]d.K@li4Z?&JFWG1YfCgTfOD"98YA)a84i* + 35pP8R)B6X=cJ`1jf+cYEF7>\H$MM"/,"_7JVVi^ti+p:;:LZsD$+9(H:med##_INK#/XV% + d+&S$irL[HNJ!]:Nj8&[]VsjM3`Q7@,r,6!.R9H + rrjOL^k/du?&SJ.#6iTuKEM2a_C.bWi>B*A_u:?:W)>6:lmd_Y\:93OZF_$*BanTlO[HE@8 + n%6<-1M,)6o&LNi\4$O+JY1BW)TLr[TL]JAM%Jh\!D' + s>>(r80'2`=QkJsu*E6d5jA9P\C>*5L2--cTFNp6\%4``-W0J!i?W7>TD,#kqVc;BAS+DAQ + :,P/1-Sfu`@c1eop/"@.rE4[i,G>'.$dP\R,.2(-8e>736[F%Vr;#^s?WZ*]9r# + iqHrEp?%S5Zj%^mH]:@5S4C<$iHF="$?3+G@ct4IM + ZoA^'\:egebeRN6$SXY$>L[ZiV;0S0l-ABdY?/0Zfk^1lDCrH9=;qP"gZs3 + )efr'm#6s+V7?;EU-t_-[.--nPQ*USj2$W"I!!1/&#nD^;3k%t#*EAqYetq"\2e;>.F18Rf + jkj9$HLU=.`cs-ZJ(q#K1Iq5Q(h:WLLZ(3%6+B;_5R7D@MjQrLcoVPcgg6$h`qV0j_0i_#o + 03K;G@,FXIkTCC5E!P[JtQ/=[:)o7"EYm-+K6 + 0#C@:fb!==+m%ph:"J4cO]67)iJRUNLCLLNP.$%Y&o3KRJR_@]P^&RuOj19h + VSr\'2WH8?>XbDd`4F"VI82TE8aO%]aB+5kcj"UOI(b_AVBdJ"qWo.M4[r.7#H#U"jPS!A$ + &=-7+S@E]U=QLa`TbSbIVAc0T9F!bPDfr$Uf][<3)=e!8Tg,F0r1B2p93"m@nZ,J\YQ+*Pb9iBl&4(h1NZn4fD6tQ%Ln2u8,g.>oiC1qE+BBnaJK](qN + GbW9=8Itf;8@VQYj<;5M)4X?=ApP#=gREBtF>BPj*\HmFb;c*q*Of18JhLQb"Y.i?8[r%)XREL^RZnZs + 6`\DrZ*oPataYTRIYn76&'f?&%8-_CC#&]7h@!PgZ%Kn-2i&[I<,d2k1b7)D;QZ_>GRpoHgC<*^XpO6*r&J\L<_Y>bD/V#.n9ZHXO9<@OR0g6L\olKJ<`,,m + %a0TEAa>84Al,I^Z"e_g7M)dr*'n3O\iIQ^_oFi%R@jW"qI9[o>W%C7iEhGf2A3(jgSI;gC + c6Lc"?Y^ + X6JqT1P7]V>+a>>cHp55)*5+n+#u_3cbL\H%,GJ,i$.) + i^\1[pa6QF:XJEu%)m&@\qi4^23Z%0*aUZ8+2*".tl9*.%9FKrPp2ur.Q83s0VJ2f`kag,H + d^:Pa%&GUir-U"`5!-ea!]FmLm^<>TP_)/r#oYAZr_9=aP9LQbA9Z,(R`lqu6bZr65FNI40 + aiq-7bf%XtdT\OX76t046L6>AgsNriLI%D"9gmHq<%1,O.Aja*_;$G5OYB*(=+^c6"*-dXf + LE_Wg9lmmc%P#_SCYt2ee4Jk9h?EnZ0FZo/PReC6N#3/N&"/(0rTV2L/;g-,_[.gWdl3bK] + lK)Mec*+D`6Z;cT?#3_f/C^osXWFK#9'*ns_R1S4"@o`'\cFbBm%G7]K]a2r%(pXT:i'!RM + f;L]%?P;gsJ2Dk.G`KZHpmWgJF]irSoX!'g]Y=Or*!0nY]k3/gq-'"#WK'!#PI7BB[Id!p` + ,;$HMY*T244$*Q?`)QX'N\SIR%;T2*uRmpnXXF+\AghG1^C:Y*p7^*qc!%+?>k(>an3b$Q. + *@\+%.]lpTd_p8u<-D0uik"H\;ZqEeXF8PCI8_tO;^d-M+D$?'>QsV@\,]]akV*+[(]fm0d + *3#Z?n2<*3CoZu47,;SbJ;3GIt]RVcI>b;kQ-2V5_L7]"mhGp/'+oqPcrQZa_[W&;eU,p$'27[ZLsS7ZW.2]1kZB[h:41JbE?ItTjB-6J + c8=-TN7Z>YMTq-=P1')Yb!Z9/)ZecR=L@$i"cuJ2M-2YSfQEG(8L.&8?%sUUfMq*;4b?G:R + T[%LfGHRMTKjV$RY6Xjg&dL79KH>s4%%8fcY]IREI!-Y,6E44c_GS2mA\F^Wjdrd`+?$gF@ + aae8'iikcm8hAA[0TV#>f9CgI8I'<i,O?nGj)ZfU";pYLs8-&lZ=,'ul7>2 + @CQ,0ul'\k:XL6WM/8?@N\CSX3uL"L7^"-pk^a9f:C"2I7]ihDI1q!+Uf2 + 9*F0^2\89+GSo2K7U+-NK[4#8m5ZAcmY8Aqq*M+=F2?Rnhe;IhdQ1`6ePuP`seD3o-O + q?.Gcb1$^To`L_i.$=!*Em`e"&Ouj9@_QtU.R,-eU@XVrE.U+5m/d!StJpa+EQ)_WR^@:&qKjs_?fR>O-+iIj0g[P"u4Ch>6,=5TE&-4 + +:nPZ&tI#^!Pfd\$ohE*U1CY'A@88f:/0fuDm:nE)M<)Ibknkk6e\n8g=6.+blD;Q*o@@ZXpPO#-`"Nr`nRA?"N/)*@Q.8BLr@m,Q^dlJ3iBY8/ZuW*k6p>\Ac=p#+:fiDVo#?7q&tL5FAilj*NJ;fHsa!($iS+G(YF;p-^ + H-m%72^IZ%)hZ38o@%eWX]Z=T&"Dh_KN5%!FFqW7/Bn#$"oct*ECGm"C?K\-q=6m=Ce + BEgY6;)W+,V8Q53CiFO9n:_U-;3,A*5D61Y`XU,S--YO:%]is]B3NC4"ju-IH1<\a"n6",H + :13;>2=>RBEP6S7G?rHK4llj7tu_;e$/%K*p@d(?;D\a(#io9,)/jNniDhP659\!GltQ>"P + HXk,3FQ_9a3eET8&Tp:6gV0?r^kiX$GH_Li5\CHf!E(PjP^$F$a-p2+MV] + 234mq\oqu&;[p8Uh?1mqtFpD@I + %q##D2/Lu6=:XJ:1!@C\KJuG + +R/2DEkPVbehB\GDBf(s&4&/;(J@KRTPAs\nSA]/ + 2q#k36WR_jUN@HmpH0*?\47NW*9'#<;ZEuW\Ph!H5((3\LLGHA7eh*-+/G=fb_Z+@p=8gEgF8t`[3Zmc,'G:WSKL+Qk.Xa;Qje`G/XA)-31k!Zp?S"DD7A9&(H[o5j*/(@0$VE]rqV#Whsa_f- + 3F^0_r"[92uYHLN5mAIC^W.%/q-)3gJ0-qD`aK8BQP-3bD#tme*47Y]iMU@YLYqm)Ju(hBs + _hq[k3TrpN=5dmW^Da*u2Q/j3i2m^/lbbhLG.n4V!f[WRpD.>.kF#s1@0+pUi(b$U(Td-ks + l#De)ro&dgI":WAl@s7Q9f^E:4s9D\[\k)m%O^i0to"2N8[8,b&S+jQ`c8!WAt$!;Eu[[3UjF2OOn=6TaDrhc + ,KQ#;Q>)u^>am'3dYskH%`?sK$,(j3#QgBJ@Idrt-i&p=9N*4VJ!grqdkjcgI*jkj'85I+T + B88l4&3,mSN>)/4Sq@8jtufu:c'&Z5g\XN'1L:Rq2/c]e:eu8dZg:S8/R(as%Lm`eN$FSeH + u4`R8k8#;H.7&;cT3-.K= + #5p3!'Cg/b3P?Po*BZ71[3-%)EpK)#4J+6DI8EnpCHd<1e1%Eis$VtkLD?<+8HsM>ib]P + D3SUEq.l4+q/LDBAqdC#Rq@AOB:U.+Tl6Ocq=4G(LI."`TnB$%;HXd5rgBZ"RYIV?Q=p2rX + (o5[[B/\Lq-[?@gBPEsm$f'9=-b65dC!g;H""*tqf^r/Q,S%K]'fI(n\+Bql2F_Kg]%,:0!Qlru&js]RM6IPsnRMb4>J8=3YfVG<=(@h%)g!o"a`U(3&KQ/.2SD-UlAT*Ih + YPG+F\0g%O7JES^?emrdN3Sc=E%pmq_MiVRX#_0X=6fa\ra\Uqr.V7Qc0UE:tKO.2F]DB[N7D9P9?DWAW,&fG`dc/'YW + >2j28>90$U(:)(6!LF->#Z^,`obeTT9@UL5J\9AH'aZNKQnfO"j'7u$HPU'X:M]n[A&'W]k + #Pe@m";68u!XP;E3cZ@Yef2OIdis;^"9=RQ2$_h]6ti0;:F'D-d?OEu8@9?\FDR!d!^<1>! + @/q$PGs'#Jb=SO?!]!ao9#'.k(ql)p$neEEE9qK#FHNBJZ39V8:>KV_9Y7] + <=jUD1;);H1r0.GAU:.Cu6PMP?[lF^7enAO':=%9j5jMQN0urM&HJY"TVAY]]ZW/&M^'FmY#eeh:C + _aCG_-Ju:@.7*-<`M.Mu)QST!hbuq`BB.WJn1^bid"gJ"7@;nKWTEAiG&>^BV4BXN\9%GR] + >I?&*X0uWZCYLGFD!eX=[i9/=gQR#'2X]C*VZ>Y1pnSV!aXmL-S,aO%MfLa.aB[=]DABLC>E>:mM`"gUDC0jkm^PL@SXYc$8\?jh2J5oj6D%HgbP8^"::#?T[Y&YJp1L2r + =L?g\H?fh>tCD$NOQs%=rIf"Mn1^"lPmgKA]u^Kf:!/L59%UnAKd::tH/AW:,N#c6.meL,S + $nhhgB88W34g5A]R>aDlq55VRj$dM8-jbdEh[R*%RW@ + KP_= + %CUPJdapZ&$$@jHn]dE3$=D"O%5;lad[8($A'E&[1N\;m)M<#M%q%:#!=!EQ6=Ob*M7KP1m + ,=H*M$BWpbGfVLOqRo?)h7r[.`PH\Ssmn9:OR(DFhI_*;tb_d[!+dD"ULd5%q/:\*0fZ)aJ + !!`%g#Ca&jfu39`%OAD,f1ZG/YT"'TkN+FTG)`74V3A^N*6;>tfH21o<+iG#P5+:K98G + g)G_$Vs-oFeTBPJeSTH6l-ZcG%cuR`-!%Q/MueU>BbSA1]+&=)99gb,:%_V,&UN5(M#Dm'aPP5ejBR7sndi:O@'CFL/QrG]-n- + *FLj])\NU?^7/G?Ufk!oA\lf%8&a]#0['U\RY50'Su6i?\08^q4imTktNIS,&o`VGHNC!>J + <1k*@G4_J..do)?0s'4geV0&ug)=C[XhtYP#N6j-]!,W;JWJ:JkYgHsrdEi`Y+o]o/*n$,n + !Wa&L+\oQ*^'m9fe&f8P/P+B.7b$oqeAN1?@>#V[>/J)Uek,$.X`F[5E1cCof>Mc$l3NeY(n3TcVpIY+r,):2R@5n=878^t[rNge(OCNQhgL%c&UM$Vf?Q0Y + F:mi80?B$q44R`G".$N=??f(aWu/i)lK]&M6=C5njMi>'jrEJu!+NcP(4k`NTFCfp@Ur5L'PeX%Y&LZY;!4^ + !kQZ^H%YAPKn,U64!@cM&J8,C.QF1^Sl,uog0mpE&@-Z%B(iH^iNk6.OG3fTe#Cq%,%a&cd + cPe/Zm:[m[i>`c:NWfDX#B)ZpD`o&e_&KYu)tCrg1!(F*boJO^*HBIIOjtJWi>qIM*RX*m1 + $]m#m2n(Q+*$`kZ1T3ZG4Z;3!!l^!O%gbcZQTjfok^5;YG!-.T->pn.BdF\YEpH:NqZ4t&g + 8e?Z3C:/kRR\#,;MfL&hbO$/B"h?+qc7!/<[O49H*I8-?$?c$/$)MjoHS;!DgXrYL+`dcR: + I$-u[WaO4Z2'(_Ru]eZ@U?Dso.Am/M"rc`d-oXT'd>"Y1gfd5&65"16I[(BMt&dSM5@YTk^ + g+@cJ!SY-[f6m)hN[f`iue5GM\&4\A"8C[]*">?X@?rR;[Ba'T$"pt#./U%D&'&&d^HjB_M%^>I5d.45t*BGnL!8nA8#epQDDr0-it=/d@*V=)'?mZ-0bXQR%hb2JlO;onB'/D#kAa=2&QEDJ=RLVpfVoZZ`:/eR11c?tF17+DqEa + FIHTJ#%3,C':TL")*/?(iPfe5aeqk+(HY(=&M?(RZbI+$`tB'-&oL>T[S1.OdcHAd5SZ.>c + =I]MD*DiYAnrbY$SN6Yp9QRQ+80jdi6i6RNXGim&(DI5B"JlO*c.&blK72BX)C>UTPr=6E$ + 2k''/LbQ5j\rK6tf$r^mkn8NX5]I%@!2N1n/kZ:8is^iCF`i4p7NR"[T.-J`h& + qktbA*FE\mK&fi-VUq#loEA[nIU?t3r"H^*#1L^/XGAU3P'iJ$T.+`\ueLc"*(`$P')H9]c + JHI@Fm/HROcW*J79`-l5YuMN7!0pV9f;epDOTDk!RC-gW[:t!Yc3h@o%11Mfi*lp#5;'0*S + 99am[$9-'[^fC + [U*JZ6^%oste\:!8Y5B\I_C,@P^0pEC2:`dCt + !^J[hp9R.aWC2P3&>9@HbE"]7()+MF*HqOI[el6=s-!18P;tXe)*O1/5EiWDP^,]^\O2VW8 + X1/fPrW!4C]eWMDY/-Xr-&[;[oo$I"`\g196WsL=%en5JGJk_#LYf8@&,1$)"CFi;7cl4*bOIH*X + Ptoh-f:VU2fSCEWN2$k!?;j,-T"iSgND>)'2;dm + 2hSG<20_KA'42-YOPTMHK_>3NQrQ*AT\icVL,u:n+M\D3(7K/d#C(B#(jP"_-`f:W&!D()B + r2T<<#c7e4,)LI2EO#Z'[P#(-usYKX:`B4q)tg%U#:6\XC%@d)k%:O;XpiFT7g,SX,b6Oh`LLi&$uk2ATH0r>EY" + \]H7Fc+l1dPbD+n+d$pch,BK3<#:sJg$4mtU,ZH0`^c#sF2q`G + 9L9U^*K#s,[YqSB7\nDV/0!h8VH@0dGGAE,R-j*_5jG!P+ljC#=;&t83!(0-5X+;/+FLb_. + '+Oq00qdrdmAsgCLL4c_B&SIRHB62W=$]Y59WY48E)`EV[RjgZ`okF:\eo?n7h/"1l1(usm + -X&U=>=Q/([ui;q]YMYuTei+o,@c@Fr9q.AKb36ZKroS"B*O_Oa + jhr%F[]$_PkbHZ`i9q?uH4+iI]/d[WbGLpiae+:EQr5^Fa?\hD&EO2aRC6YR#Im91[k(`*4 + %*1V4,qFuJtYCM"WlXZ:%gkqeoA'#J"9H\?\@$&pHNXgH;Vb0CU]X."pWARqm28$cn]Z&*" + b7(!7W)`iRpfW<@frDPi(d-W+*e'=rL^:f*cFA'Z&GOL3`D,=k0E*)_@&OFjjj7+pXjJ*84 + V?(B8?Wrt4WPX:VFhXjpa@1NfA/%-)Z41a]WtYUY^J`pI7O#06u)huF`%2R?r.",EbtjQNg + (R7`kcA90ko,fer+t-iE-hgquu"b#i7rnGn7:TXtrdWi)^ZGIPl2L_U[scDu^s4&,Zbpq.s+3P0Y]s+o)$l!aiUGKQ:*g? + f3M5hdb[NFMN^r,M)"`\O,Ek]1&kQ&o=!2G$-1CYgc=&Su9r=,"eE2$9T2+DU[hIt)//Wmd + 'Ui#SR8'SXb9b%`$2$HSN)Vn;r3=\ + 1e"P&5s^s(/Rj[N$5\cJs&\hJ`-?ILcnf_W&'"r0G)BQceH2)U7IF8#Of#Fo'pcYA^ZFC@o + .:mfRdIjD+M(*a`!>rmYh4IK&\jcfOiprSR!,J*Z_'rL7`L!#(&0HWnu0@sJ2k)b9fAJ!q_ + .*sf^pPUd)-n=27`RLkE<%E#']2\;Di*(pkci>>0&5ga&&)M[5`Qtd_?ENFZ0Z+Oon)6pPn + J&fQCWuoD\%";q^dR7Vqa\lZ1-HU:MU48SCS>RjZP-hPH96.\FAK"):^G*k9;55hh_m&c+7 + C-A';jGA-OCt3WPj(3RZc)!9cVrg%A!tg=!C+pf,mIjU'KQ1]R)WtX7a[HC,%?sqr?k4cPd + N;@;foSL@Ms:\5u5uf,tCm5"rWYTeMUW3@@8uY@9;#;V.O.:'tO?LQpsMe?ItJ8Jmd3=)?4 + 0up*GN)f[a.q'#:h_e1ci7&Un2]p9h/@gElBGJ(qF1]O)M!rQ0'7[m>@]>t=[C+tpB^dL(T + !iHATN\$>s\he!&bIc"`8omiBR$$CjXkPbpDi>>T0>aNM1+,H8nSgn")R(b%T(PhuKNoQN# + 4=lD-fdkQRDc6QKAb_+ig5:?QmS_S@:;uF*t_ac#_\'5LCS4Yc9s-lgT + EI$loRWdIl<+A(YsQIr!_\h5>2IGjomOT1ht_]XVrRXQJ%,%IIt,61rBKK?s54Rl + J<0g75WB8T?pkBR:c8Hlp_a2WF:j+d:('?90a1t/(P>P-%RKk",=GqfFU"p'OQB9K + ugjBfO50HO^]j1ef$nraUTK$3JW^_Xi'_67KplJ)IOBa!-h+O6&7<%!uPV[.!lGuPYm="qC + 0Bm1g,`)8M,Y-(rGLP6/b[sN+O%l#Jh2m$=Ju/_P#)lo.:qUXE-/ZGg72*nK#k'U5tH)57+ + S]/O0_sNCL;,RhKui:%X<$qHo8%HuOj?&9<:7kL[3cCBg,q-4n/aC:H;A(H4GVSc+,N] + A:VlsoL]%4qnRsPpe:f><1eESC*/5$1)OF@p%a4K.M:!Tc7+2WSLliZ,-)"*CTqjjsM4foA + P!;I9&o/bDRZZaT:n*;*D^?ZnQP._PS9'rM:3=RFP:6GF!gmPLE"2q]=\B#d'IkJpm651"H + V_cV6-o3-5ZAqc;$nI:cugP.'[iPGF>ZfCF\8U2Pn?uQZAU4_0Wf$2]2o?u?d\OuYSKRF=Y + u;*$l,+$#Pb,1;U0QD!=11kbqiPhJt"A=bt_>,k?X>FEX$L5)J>Fb&)s:30C"Y7(_o\PX>6 + \-\tI!AP7V4]-%"XX6[RS)B>9/T;Xf]">q]OZuK5ba'hTK&&m,4toIgr*]:foMW + i^Gqm[#f.c$:.-E5`cT\G(9Pr0W#p8"[*>1/ + lfs&G:)HWqjhhHD:?'4;X%IkZ$#Kbf"78\mbPLgG]*cG]D%W]?>IXETbJc[`?6uA_!r:N?1 + X#AdM>20^OC+?qhu+f/)U7&J7?M#MF&:OM7_J?J;=d=@T6&/]ENg>-5"+JJhP*C`*5l_r%8 + f.qVh?7.ReU#X?^k/GCaEgn801r=\.aRY`ba>8/k%r6U!gmDf?j'jG + ERc_=hK,#SHTOn-NA5M$ru'j(5%j6Y5Ck40"h=IjLOgG,-CR2gkn\."q8&k=mZO(rP&Y2a/ + =2PIRnW[#KA"F<0,0B6P + Q@.fT]0@Wnic

W\7@;l.gOC"5 + Bhiq-XinKK9!,kD*\BFtVVpsFo^abfcWg.%o0rhcb\4D\o@ito)S:-I@E+oc + (Q'^(%dotGN)eX<(T@Y"Ddd+kqY:VfKO7?c06\oas3;bE5nIo@_?ScO]f#QGo,@qhUR-/>W + c*Zuc-0,mEi^Klp>$KG;_cU]\p`qADENn:dY4^)RfR!j5li!0TG + &GFTqc@M!SYNZ;^>I[lLNDlhf=:R(l/"M2H1.RTMgjnu7p^n*:sifWK!`%:BY(hPE;>WqG$=0og-7!sO3*!> + L<6'nXeE;F'F + e"C_X(M7i[Jnu:B-fk+&Q9Y:3Y#P43&f.uJK=F`1tqYu3G9Eaii$:"TfTG@QI)RGY2-0%*D + _`b?K@Pdof1[GAME(Pb1bl[YpK=GKTRn#;MD*[nB[%n9jRW>dR&uQpq',s]E=@n1]>r<0"1 + &)uF:bB(aI5Pbg[>Z+`Jq>kKKXJCE-s..66h#su%=m@KUGtu%n7%0aKsj)l2DJ/GEE>&/.i + "e=2M5_PPV+CHC::0ZWDH"dHiU2S2$#rVJZEf,iGg<`S[Ut/QWuO0CNl.jI'M_f-^Z1]?o# + 4EJ!_1)`!_*_$\>=AK1%eT6qNFBLe7kB#(ULQ5)?)GUPh+\N#B6f)g>JiG"X9M#W`UR[OR + "]/0Rn.bsp+%9id!uOsj0RULsr5MQ^K=EuHL_Be;*"#;_!*j;0)o'E<^rJ!J7m(oa1j2A_G + /mj37uV[_S9N#TGt55f8%a7?b^N(#In/:Da6\=I)K/#2Fp"s3KKokEW!fm*>_W5oaCL + i4FIk2]."\*F/T<]PYX6&#iM4TT0ta>H1o6!)jiVr3%@T0Y:]pbbGt@_-KBPS7T\_lFY7:s + -9+*$kgp.8<,)U%>0OQX<3'2iP@>=jJ5A[kcj7l9I;STR71C3fQCGNZjrNeKa2*b]&?j=Nk + AV7KUKT$&?U]mV'4$[RE^m)qt"Niuf56:6rJnrU3YT*]]=G=P.1is9Ug2FeQdZStk9hS0r< + "u65:PZ_@K0VZ/0EJHR#68]L"gD;<4<"s6ISKW$214@2)d4XH+3lrUO;tTa@o35n/ctf0Kj + \$g"=U4CLa]V[.+IW!ZY,QY0#H:M6MrJPAF_0G9Ea9aZ0]@STJcn&8@Ke4[kp'3HZm;qT!s + &j6_Jp\0I06tciXh7KI_L>"@:BbJ2e1A3%9bR6&_i!7"a,UKi0-f]W;HF8jp&q.n[7[r(\. + 4OI@Fk3?s_+Cf4+^&fPFT"JBE.-j$,1"LkcfED7NLNkJ(n4%r<$\8ke`sjOi,r*IU1K,k^; + ^F;"4Z1\_2H,#_;iN]aSN'QE3E*%B;nY=m[6@O651A;=Tc5)p5fZIOL=A@R<&BG'A;X>l7m + )0`e;0%m^jH0m*.e%qMIfE]pdkUu;FI7@3d08q$%K_SH!^aT>`07Z0c7P(%Nbb! + )U96"UJ%\iu`m"/qZE'0iXsk7&F!8j2uJBejX.6>&8tM.Y>Gk<62I>cq2CWlq8;c9,mP + am'@.*;ZI@KJ`%`*5Xttt%>Uak5Dq-ec#u=Y^/o]s=,L(43(=&bLO`R5!)jr'3-`YO3m;CF + =:E9pXNA\oK*BV1#;b.'MhrW3"9TJs"5$q7+EbpS&^<X0Cb3m'B\GO_:E+)q.: + n.Y56Vb%=2K6mGQn>M?&+@s2`*HP<;JOaqp3diccAP.I9nU$NWVS;2q1,i3iBnJ"ZSSrcuG + *ME*3F%,Q^\RerZfV]8YM1NnaJ+:ur5+o9!90&XmhU74lJI+>SR'84G!@JN+VOJ2i6"+ + 63NX:?]cGMQ<>,s-W;",Kr>ciO1i!s2jVK8<;+k]TI@ + 70"1UFHO<"AC.oBg-Mu>A/rT7&Ok>VWK?>\o_IHb(?l<_0ghTb@mea-Vump>DN?KL]`r + UJbPhafNre0El[I9TZ>>MjQjeKt#jD>I6iqVR_q#-`!#YTNua-!S`n+ts+N#U+e*H3E$4#0 + =E3p&&34)7+R*f:Yk?GUS1!['_4\!!QN_D>)+/UB*Up(4u72+;4cPk%bMMIAXefi"]s1kJus>&&P7a + hFOt?B1Bi!;`Hb,R1BcW5"m?hTXjc/u21tRO=9JgnI=YKXK&_929W9!70L^[I$J9DW"!di" + n72`RIFuIN=Ooc(6V_&LD1c4TKL:':"Ua/N.9TUic3,0@D3u#B+AP@oAB98G;3OVN"da5GK + EtEYW*T^?TW#NjW8-g>oqXq?CJPOPQ<6Y1QsP>_(h5&I!r+JPorQK!Y%@P.:UL%3*O',u&V + 28@/T;5Np_4?n_Z_7K`-EKt'd/^hs;m;?q/A"_`; + f=B5[R;$@e\"L(ORi')e_>D!29"Eo4qFshf\=ocnY!B=#33.$5o+bLfe*)p\TE$kah@^pXf + ?,/FQLe%`fZdQX5K8;Jj5T^$r0#W2I(6.G52Psh6VdCdLC2U!8I]7(bWbT8IlHZGFeuWWCY + A3L0C?Dd9q@)7Jf3_GllUr"$jnJ(PpcMjZl](dWSd0&(]%'%n$g%kUoLFFV*%=4$"!BE&^*)DV,49^ + :i^C(:?7"TFe5l>[JTr,!^c@((98.XqLJBC0e,3\9^i)28"itK^TKE?b(P.6X\h;tBmQQE- + `O!4t9SPG;%QIsA37p!"OiW$f^cQ>dgjkJ1!]VZ^E&iSP4b^+HLD_b#k!@YUM2-%J"^#EC# + YtMh.t^T,O^8HI-kh/WCBp@KA + 8[Sj=OqRn8^]p:CpBW88JB)2M>J4mu*#&YR:Hsc\JZ1>28r-jf`8BYk%C0L#[B0o;KY]pOW + Z9KHOBN\6]F&B]c/$6pZk:fM@7;/-=0](S9Y4-ohC&:BIaODJ!tQ9tr2h9Z5`t;$FjMNd9f + k7pJSfB?*Pupeul;G_#i+!Huq6gi*6Q"U%Xj#%1I8@"7EaQr6\Q`jB:^n/h\p/mHhCJ\Wgg + ^d80#7##Y[Kg:)0jD4sl8VZB$"[F@882gPd&V6a>J0J03?Ei`CB6J3,CEI6`Iqa)0C3I@+p + B.2g"%3+ts*^^^%uK$t + 6p[i4&tb+9GDQHlPA""=Q\Ae:r0B[;BQ4#Q)dJ'ai/-#6Ea)!\cP8O?i7'otH4D4gXCC>]$V]J$n^', + qLcY`/3X$9Q"lN@6J7XFZ>6^I!"o"2ai,&Wo=GCs("s1Yc]CQ0h0S:c7hrlFj(_G&qg1(+i + 1R@rcT)E/:rqjG0^m>&ufP:`]+bDBspc,.qVAoTg&-3%N&/Z8Pa;j2A)Y"4\JJEP0&>i$d% + 0_hu#9OZ5?:o3Q>%G=728`jN*LcE&"Ns=D!K\1N$T/#_1]piJ"5#hl(0Q@L9a(6&Ka:T]Qr + A]HD?V^oL,kbs-4*J*E%2m9[4`/oKh$3g/Tc_b(d:17`fmn_7a#QO + ;n#T/,ZMFtc-!-U)K0JYaYk\<&4A;C9b"KsSKd:>QmGUsU + @!$T_aKLR7$MF5Eo_NN_us!j;ZnQ?!L*b[DVeuOT(Sl7%@9-oL?qU/e-R'!6lFe%0+s..JK + 93L?WB0]Ts.*q`A,)EV%;\i?OMTu-m:q8oD5jXB9>HV%CebH'G!WkG(Q0Q=urm:ZgON_kTg + !&T[J8b\HbEd)2uD_=rIMgnnCT9YThbG$N(Gr:Kn'R9"GoOAEY/Gm<@XWc#'A\C=F + !$2:Zpj(o_?!ZS^p:UMQ"s;:6nO$;Fr.f3#iY8iULk$$4+`C@Gl)a0+3Tto5OWqB0W6?l?# + )C5/11(g[0334oXVs^#AO,r8'(sQdb-jH[VLc?cV!npjE(u?cUXgp\k)@O8JkU^T\[Y!^KR + 5&HfjG1`6G4YZr1S_@W4Z$GoWH.1=$ibW-)!d+ghq`"`U5R;KSo?,Ze9 + d,u#f:kEAfS+HThNoLgnRWn;IQsW(If"D"bC&u+]Quk?q_5fVGX.5Rg/un16!^\;EbWpRBC + J#p$I69=9c%CJ(U&EMdtF@9poNpem4e\fX?qA'nq40=)?Vlg*eli6n+jHTD&f8E>fWNd!qJ + g`RT-;GAZ;_h<'!cJ)6O"l[.Mjc1u`o`c8)0i#!43J`,05!b7Y=Y]Lom(?Qcri]mRJ%$\_> + VIUS-r\T+lpte5rA-R3?P$qKG;3RC#-<>WXVD4R,AiYTWRc]j6s.!^kAojPVH0P;AKf6puU + ?KdN`\S1&nVD\ZRWb-,(j^A'WG7m9XBt1LQ8s0QVH#u#1:5ZL[VmS + n^'&;?rh@!@pUdQ!:f8Kpim,tVi\QR4/MAt'R+;K^!WS8rqLE_9C]V"2Z3S'7l[s#82cjMA + -UKDV93Y)D<8*4>H49%pc),J`_Ma"_:';]d10F=[!shfsu<%TI;u`uYbJiV;7GOlH1#5F(C + ,Sq!*jq;.I3l2'8?Z0H[B^UNS,T)ZLnqnF5hnpFdtdI[+9i]N.$\pdW3hkpJe19NW\m--qOdjg. + S&u$$":\hc]]RAkDSt];tb`;q?9*^,ls[G%YITH&OP\E%A?`^L/@^m6tiD.U=;qrd987okg + ^]K1k(H_$9^po+k(o,:&PE/76])a^$GT\5phpXO$Dk+K-j;lO\JO: + @+,tDW>_i)b%2/n86s,d^ibV:l0gpN1$Nd]Tm=DLH::/Z:*Z"nYEJos.0?,uh)&Is?N:Epl + 0D5tsXP@d$KD]>c)ae9CYl5%SPWY&c"(J?.0*WQg>78j-+;k?jA5X2u13QYAR<4@Lf);)[3 + ?oh&'0I+a/YKI4=^j^jGq+1$e,/r2q(h291Mt*)j`gGX$j.'tHZ,Sl + Gf_6Lub_;,K*^Rl8+!Y53qfm]OT)M+9^#%Pm! + W*g$R^(Nb7<5gl1L]GdA"2dB?%mr(4?=3t8.]&1[pi?U_gC#QXoRJ.RW*!s!n>2$bO:5:r_@OETbRgT,TDD?Pr2QNlGe)hu + Wj3<.d>>@k^#9eW:(\+]\:W_PnL7eb,`4igHFQ/2iN6EZ%,'UaBR%rUuHtK7LTG + ;VAr[S?L\g,0H9g6R*ueg?cO%6k$sOh;bN34=fI!,?RcksA3[t.CD02\"`T&7$%Xci + &ct=5iOR@]`E9/_i7&Zuoi*+<36TEu+@ukOZ3\8`2!.Y>($)HceG>M3kl"kgFcNTTHKSMuh + .s6;hS[tB(M2s(T<\*]c.]d"C[KV1-a=\cPdEAld(0TMBBm+#qrV2Qg%-k/:Bd0a*bFit'e + ]o6=tjf42sKkj%tXdP+%-Bs)C&X8pXf(<5Ucm=Uf\'#_.5*o[Bc7`bP`H*YH7`:NOoDCI?C# + 'rnbT#"&OK`U"U03Lgcer$fS5&M@@GeUDY"O5hfuV[XY*/gNNI4mHG^4G8L24Mic_rb5H8, + B])DaTO8FA5T/tIPQmknE#uU[,&>HohZh68YV+iCrnm,Hi%>6:JD(JD9\HWU,(_ES6!b=8k + i*!3"c7UC-Ml0p(iTs;^J6Q!C\m(o3c]$DRP%"hN_nf\''1`Hj[&nPTf*RX_?]"L$\H=)CR + +u$&-8H>$iq@2U&*qIFQ]@s"b9\\,ubN9/0$:p0gYF>TXk_Ih?!+:BVH7F+LVYd&FGSAmI9 + ?,YjW(Ko+&#!%hSOI;#:e8rt&$*&Z&`-![-J4_%ccTpA&Q;f$BeDX`Y`sUgL=-+H:iW;@`TM]-0t[qu + N91YJ;$:^_HlV)FbWRn`2G*1'9d,&0VGrVDKC@i;QO5riNR,bBkNGo%U$Vgi*,jO95Iq@NX + iF'EDYJOl4p@0N!?=!MCXa!>K@dpAp2I!R9hmM+n%J.(`;,]FZe":&MY#3@[ig.Y7R2_'Bj + _0EVo]pg>-E!%\V1mM&=Q"`*-L_/0AHd0:(1&o!=0accquEoW0DRI3DWN^ghRJM0iT#geD] + deb^!*YKWf#Cq+-m8a5/1'/RB!taB%_*eu'h[:m^16-VBd'a$a2?a7`'Z!B`&H3?Z3ZVUi# + iO;U13B-r%KT6a%q,)I$0E>W)%f^b2KNXl;f=RXEAl9e2j8GEdrdV`HT3g23)b_pPE.\EP7 + b`XV#]!U&W[bbUCshbXAO?ifBu5g0^QONlg=!6DAR6'<-0h]$KF)mgq + o#_Z2P1p>5IY]MrI'L)9]cl.JF+7r&kZq3[89P%Yf&O@HPTU6&D\?0-n4[LV"5UHQNJ.KUK + *YJ+)&c2=H-MeF3+E+5%X$6CpP5CK0qt-@$e=.X*s%5,FquK%s_3%1e]J+#67;U6pP@O$E*"sk&iEo,hJPqHkQEbkt5ae^WsWsCS<#S;nrdf3G@!=5Ac'OfK!i4opi:^$i + C2!0Yr^`3E+IL"fB%UeK85u8:>nM5!+$WA]`_>as5*`Tou2aK24!QYU5U^qUN!jr.jj,sV^ + C'jst%H0tpJOU]EPrTM;?rdYCQ4IAH'4BB+@29Yfef/'6).HVg@Wiu*Q7k5F*a=Zf5P,1G0 + o7r4pLI,O3n*MHPI3L$\RcOXAQE#H-i+UPFrG,>&3s1u?t1De8-A1H$m?N%5tGMEd&T&tBT + Y#6QB,Qk_.0p?A+lC%et4u3dMsC<$fKS:N9$TR.K[C@%A;bn.ukW,/OHnd6A:s'Kl\4j4WL + gp*B)Y@1f8-):Ms4jDKb"STkG>en.238#_7pBEM`g++$kF##JcE5+WM3E+s/XhZ8);te;5; + L,=qHf8M,W>1mpr01EmVo8ln#FQTK9iHl/R2%Uec=!+lG.JCSXCh'hmuTX5*"j:m/E)Do[l + XfhYFdj3GogN;0r9u0QrhbLQCGa.B9aESg$Oa-sk'S2tf^u>t,IL%'n]iP:"oGJ[bOB4_A( + L_n;2&I16_gF4qPRo!9&1$-*.0>M);XO?[194?_iD]D3i&*\7`7";_eM7"Z)kA%H-"UTPOhJU;[Sj + $,AY*^fh%0PRZRG%q0O=FtmI:"-=B*@bD-N=UgI\&!:!.OdQfDfb]eH)j2'dP"\AU7CQcmS + S=C`C">Y8erc0IMe'u@AX6\f\PHei:k_@q*?d0OiFEB_g^eT7#bW4>i$+dMD??JO#@N2n!6 + >WN&.tU"(P.A]R7BIZ5A<-E5IiY>ff>K=2k!U*)]>Um$UPnC&"%P:$7/@HLI_Q`:^*mp%Uh + =26$+#2Zl`>fCq83m&]E//i7$9.pN/cof%SVSl(;#:T6BOm_5B\i!$D%@TM&`5JJJk!;4$5 + )#2jM-EMlV]Wr1s^"i.,G!7@J@;J#shei+Ide:Q9B"*+pLqQu6Xca=o*JTDGA8^\]&(I+c5 + 4f1OE).BG?RVWf!N#i!0&V3ge5cP7N;?^HA!5K+M1'Bjq#_8i`!9t0TGpEA&>9^Q6"Unkomo2KU;(1(;#F5LBY3 + i=1M.A/^S97/-.@s"'=,40$Zt>fKFmfQ%X2ZKc@XX=BU?&:V7Xd+5pH>qocYLc+P(J]j'7( + pq+6W\%Xi8^6lNk'.TOI`E_@#&)hn9:4NZH!AgG;%cH[h+I!0Tg]YDb#6T[Yk\pT2-3Jd/# + !eM]+SH5`*>sRS57(N;oIHk`>hsugfqc<=iO-RP3'*83MW#GT9\lBQFo]qrTT0?ZF9eIqFT?Rg5Ze + 456j/$;B1R0L>(d'l-jF($#9\6s@#t\&X-_c&%cI*o@2oo$3Yd3t(nmB? + GpRZ`Aidt-&-&aMc0ecgm['3A54>YGn/,?dmpZ7bTnF:)rU<#PJO+N%&S`0b";L4f&NY@MJ + :\&WX`XW""Q3)\?p"Pl:]is>"@+L>i&Ur_1'@mT"T&<*btJ@HH->I=Tr](S;*8_>q& + N^YhpZ*#8mi>sI:EaBI8<%#DhG;,s\e0)pm4@",R:oA`9d7j.g_,+AgU.GK/=O:_V'FG + (s+V;jW*#/WDU&^i_4;\J53[1_GbZl_D%``.48L3"o/YW_&'hTJVb"sJAgO5^OD>=!WJ"OL + Io`.4\$i^5mYTs_[_([\\"@7!K)[J`kPTL4K0Vg?06SPT#$q$-+3tOdL'Km's0e>g.75723 + '?.4U>o#m!P[bV$&[Z0i`j*I+*CmBYIi_,moRe>YE\rIq8?tpM,tYCQT-_W8FJUBXEjVku8 + s7]-/^#p1;FZe'P;=sfZSO'e%7@t;E1c`4h)%4#dgPdMl4;RF\%9iJF\5(B&DPsnO!FYufk<0^HZR^gX%N+9BK3bfUhr^2h/=XHq.AN;AEP\"Hu7@0YBNb3.j=fJgUM+tQJK,hVK&\emeb<"]l1OFs1RnKDXkQ8r%AOpnD$U)`L<;U*+na]ELFr>VuJ + 3ho%_8Eb8Fo[s3Gd,s$P4VI5aUE)+KbgeTIEcP\B`\O($^b]qLYBpBU_@hKM-WG%c+A&Wu# + HL]<po[!8oC2(h4lo^o.nJi,fDF:aua3pa.\L6kk7B=q!^,1 + b`=M*Zsm'&OH1U1#WNf(bh=b$t9/CiG:'!d)Hni3*nhgpr/!7iZ#e&Gn/g*3#BIDJ&E1Z&; + BVkLiFU``0cr*`U$-Pj8LHbd=i:/XAk))A6q],[QidID$\_C/(%4=\_T-<8j;%Z\S7``C_,P%XlOodm?aWW1#"M@nNl"3WrPdct,jIML&GYhuA5F9>- + @2.CN<$j%l.>;`C'mZ3&b5ks$1ISRo1GnEnMa+rhCk`G_O)do36Z4%0nq(pK + 2A/-kAcn!_6d)dl0+IHZq*+!6qOeMqJC"&q6"hPLTQDN5:oY*ok]I?/'JbMAqB + n[EV&,lUAr>!I0h3YB*MaBuO\tC8880Y#Usu9+;R?du;]P8'$(Nc*ORT]XZIPi\;OoCSGtq + ;TbZQe-TN!e6;-#`pW=&X#X?)bIl?,_!'fN#?qP@2,V,pca$DUQm[9%,&Ju?!DT2n"l=]9` + ->O-I>/qqn]CLqEicL!]/[dRMV^Uqe]i/ + F/FnS^5(Gcrmq4T!_&St"2U=2jUb-bT4-Zg)r4k57GVkC4SUrI+/9s$.P/JRCA@JIjU@5l; + E%@$D:V3(=Bd-q=PO-5OK1VA%puAIWVH+,5mV*lG'NLuL$7Llj>*7''N),+MJe&RQDER'@J + PoO5c7jtfJ#GoNMr5E1fG+\+NbO^mI!aVbjZjLGebA6)HgE^M9e*2=i+[T=UbF"EF>\L6JV + '[.2M;bFT#.j\/t,gs>j9.=R-$+.8jo($)0nqEWH#tM2C;!WJF\]-jR_ + r3B*GMVJ[^+L'f3IWiFg#rM^Xmf;?Yhl#rZP#%05c9%R7TZJMZEB!Q + 9Rbt`$P>MKs?E:*VC;(g7f:)eJX;;?*$o5\>2D&At[>X&2?HDZK-Y0aY,dOMobLsK^aa#*oZcrj(>"_6+ + H"F%a/SCGSHX"a3IC$@%jgPiF$I5_=1A/@6YV7E5!(X&>R?Uk\5i8Uc$(sB+j2T;\>`^/]D]g(e$)J(-OQ3`jk + %JHFV/aEXk;+q#_gO9TP=A:UQ7h&)CdrGp;=P<;5rb.ss?7-Z\'JPS005r3A1/rbc9C'"!g + M4P!.h[Va;3B/e#Hk:f*nG*mRCA,\KPTKb9Y.iJb-c!T+,1fW/UF(7]>3PdY]Pi^>7P=kk] + 'H[G8>dfN6[+GV;:fN59B#$cG5Z;4/>QhiQ#JbEe^ha=mKF0u/$GaRf^si`Wj:&ij%D_TI_ + #t@cr"?g['1!lW_,LXtHiV!(BrS.`X,Cc&ij:YNI)3H8\&3JMK6b<**l8%$651NY*$E*D,H + ;.m6,S(:;/8J!)F:/:3!:\?&TTil]!sJ<3)hK>@thF?0=Z*X\:]Yi/2F`R1TY)F\GM;rj>F + I=OMcX:/E;p]S!:gqTIh[\6d(n.PWlX7W]@B:6kc+CU=_'16b3+_S"ee$*(IkUi4;$A\m"+ + )C?.!oWB,XoY+r#8gT*BZ:TiHU3nj)8!qHcep + 47$6,$c'aQ^G$nKYWE$$4CUEG#Id%*^K4$NO'ME!U( + SVe/&cX)?H3P_+^957%1qsRn9EFajcj&jIAg6S]Uf$aonE\'V`_YUWOjX8i3?dA>RFjV+QE + (BYfh**6@>rFgoX5V + XK-.f_T]^'>e\)7]L)dW_\BnrP/G0\MF1&o6W&o^CrXA&Nl-d;6_qi74-e$s4\WbZ)JhT)r + 6?4N5g9D$9b]q)gRh1s6pt7R9iOV&"@sM"V*q)V/ker5UeA:gX2O)S]$sP;jA<#G;U,BM76 + @h/6rlBPk:^pYYCp%?KNYp_>;+aD+&$!&jQ]2;lb!>:7MK>p1h4NfAPW@:]S'oOb\//!C-9 + ,O7^Vj$g)rNMD*8U.U3(/PlA@DFg;\97r-ha>^/#E6r63:^'&c/A:(W + #ID.[l^.U+Lq_5a3JB/rk82KNp["LW[>/&dH53.L*V%SY)4@kt3;,]Zn4H)*)6,-Zb;3OC4 + Kc7^QqFMVr8Mg9Aj8MV@ItZHKLH#?c-j3/A14o9S"Dk!YPpXpj-d%Ds;KXD9V('j#.8n,7d + `j;jeLo;30@JZ1de,=n"YnI`18mmH;EC1;]IM+<2aX=C9:QqoKf[D*43NUF94YG'4[,n54O + _82S:PsVtH]MjMP_N_`5A1^.7^XRkL9GL.C,V_tT_`4a:<7EbKF>m1bO?"=XbbR8CX-H + [A4%%6:A7QganA\Z> + :F,J7gC6W2oL>=o:Magug)\2V'ZT1[c^W?b,LauF)809pZ/(,/4T@oE+j^An7s0Yb.R?Rgh + G@L:a.qWjI!jT>$-2]H:pbbL6g2T91f3N'd/=PR'SjEhLfoCffoW"#XVI]Z(K"pZaP0$Pkd + 8*`O?\tl;8YphboM`P+&VMS!an":=Yg227u"E,>*UntSMXNPX4jDOg9n%d`N@keY1j,_>8= + ?r*O+9EZeI@EgF]\Zhm)V_) + W>EaWT`OO+a8)/g=e?)>:*Ql8W9O,6LbY?@3oZ,bScch67"ou(8A + 6?gIldlPM::9J]D0+"@@+SXlf48?`Fa#rRGfEEhhc%VJXRjb4Hnc(Zhie^Tqp3TQDD?Qk`s + m[Ic%SsLL:rTIUO@r#SH5M!MT:\ZfY*8@CZ.e$iR!0O=To/H@T'p_%:I(0d,QuS8m9UGQ9N + h38;rA>%@Z65S(Z#EVK@g)/f*.5)d3*#dESAC[A-nR^Y2)?"JA?khu`s5"P[n=JH-43IC39 + J[Gj@qi\i+B7ODSF-K9b-ig(s$VC:H,.H7ceil3S0^+PP][c.TK6%OI>DDJ^R`FD?T\.-&[ + A\q[^2rf'@g_Mp1L,r8H_dgQk@o^M.9YP!mK@Gn$j:Q^nSjM5>^sp"Q>ie\tXWY\`c8:%UndH=(61?ZgbJbk> + -"qG2Ni?bH0kasl6(K2916Z.8m4'o+#N!'R1]?nI:E70D_Y7,VY]=QTlkoAiF-0&Wem=XB7 + qKp6!,%nKXd=__:%*LN]E']D3E=iM1@9c-`uT%X@@@AXq$XrGdJF%JeB"]/2(n.l%4+QEaO + %M2%fp+Bh*QKa-jl'@ZMVO6VNRH_/Ml,K:Y^7OT?T5!G[l4a`g#*b(\1#rXfj8LiEm\]%"X + kZ)u\)obeVE4"N3GQ:.Q@:s]NiqYj5&,PCj7@n<9uuWQ6X6Lq\:-iUSjhsdMe%RPh"FDHH1GVra3'0!/]f[TdRV>i&W=et) + !94QGQ&@,A'5YHDA1K^!>.o&`taAp*RfeaUlGniT:YD3es=KmE'np94,:'OS8 + !?c;eR7W[3^s$nsI=F>4'L3ZmbgukCtkIq*_p'0rj8*AXdd#F+LrK1b&$fJ],i7/:NY(U3^ + Kjb?LBPeCGI@<^8pV/><@83E5oWB!@U[WH!ZqlI + Qp=cPki(Y&VE@EH`2a"ib#k$i*@&/<\]oqg`pE"R'IWsYSGL(X,-R7 + qYMe#='nkUS,iK.T"O7I\><>7E'Y.EF@;n65JAB`_(23)gmW<@# + ?Va4"?V,@;8]In.oY(+a+&-B!Dsf8SDE`49%cu>dk3&?!i&l7,:?lK,pV2 + N)#ClMeli+@0pfE,n?g`C;mG'qmG^o)Fs6BVC+S_jks&UFi^\5l'bj/o%nm]W7#:DlW.-.! + MCUYg"5F(Q]J2CI`p_4>G"b]2#:hXAd(smiP+7UbS@ODq)7O(cTjCUBtCVSpC.1D52-'UtI + ;Kr!uXY#m_V@ttSemgY,ZXra+r:56cT$S@^.MG^,7V:'sbfX3Ks%K?>WYIR[)4&a$ea#)Me + TSK1Xk6cN-iK(KA@"%[2HsIefl+?(R5V[Hrp+d>?>e!qh=ZnX^VB7hL40Lt/h5ru+AsZuI'%-1V!Va78\??H;U^LEahQ3H/:Hnm%Y28 + ^`Qk.EUcKD^J*IZ$.cmKtOlJ980WmJ':h:``j68,h"SE;e^#(c0,#a>*fnelT.eibj\e2Zf + 1me(A%J,5YpLL$U^5%2%kDdFDV^qd?K2p8/>1$/:Z*T`@b:/Pf5[cQ=p.^;,fCn9iBLV+oY + RZ%>;h[DjBF8s'jXS0jIePp0F/"=k8S+BqOL$?#&* + e[BXT'BaAX_d"^3,oXREg/MkLpDsU$)8)Kqi=M5*_r.RRWjEl2#:kWTmZiUT<[K`iZ?.Rbk`[C` + ;`ZYO#IGag:>\:f:D'63Ug^ + *Mnf3]915uCPXD]3Y^GF)T>jj]!o6gqJDkHtM9@@q<:Jo[7rGP]fFe+"ne/TJ^C26e# + scn:2[B6sK.paD6.e1&i7oK.d#&B$0rG4!3<+[dXqV>T<==PO0SWE<)FG?W$\jm*LI;%\_o + QdTU#\HW;$nL8(l]*d4?t(;/;;$Z)\Yc$;'L!NO$ur*'o;)n-'<#.`PPYJ8[1*"&V`&113X + VXkrU;I"u[0YNuR,!<%9WJ3CcuI=A&41/.q#.'nXk;$UJWk7D_?W,4o1L["g8fn79VX!@,f + &O>1>g8f4?ccglBe3nOGC[*[E"aOf-&fT.*>]ccQ*M'$UGMoF3G"7*@mF3btE\[5 + KBhi1(Io&Pl@t5>bDVT[Xm7(6iN%;:QOSP]Dn^8nO58[5Op^igs.BSs?=E=KN=$YGP:]9coV&0h;h!WZQ + fWs)p=?lT9!Yq@>X$]AusXi4>`eQZgCDm!*O\lTg9>jJeCY8-K;f@on)X6"i8^k`F$Y4MsNO@JeI43[qI/0qo*NqPeDh0S#_/;6opqEoVs%Bm]+^G+(Whq3.&@Ot'c=9e + mqr!a=D6r5&s;CY+_*>WUI?G]Q`$50;\(pM!rN$S6V'DU9Y@t@74Okr0/4A?o-eg\QAC-S# + 3>[1el0eMr@)G#,n?Y!u8a$pP2aZ1n]9T5F(Z>NGUF$D(L1o)tVRT7Lu=d.rm]p:ZK.SE-d + F#(\EQLaD+bLqH5cFMYrP7',e>g5%MN6TTPb%KkiURR>dELN)""Qrpq2\:YuS/L]&c<8)@O + WK(ERLXJ8.G^\qK_1j>-@2I*oqTqr8kPFDbdq5;Il,nfZ4\VHrm\ + s#dY)TB.>=`mj+AuCf9l!p*Xf4e#V7Q)\;Yk$"2?bn6q[Hc1O>g9l\/5?7?n@*G,UCC;8Qq + B4C/jT$<;+:/W`<;RL[b9rJY4h+[[V;H>bt6]XPoRh`K_ + DDr(6(pYQ#/g^AXO4q5>Q7g.dU+,03U)8l5n&`LAtaBCaM`RiaR%5t*M?+rJBGm2g2>`S+? + 9;cWEIEpqU2jR^OiG+-7H94Njmi=6o29?IA6atR1-D + 8PIZGdchbB=]DGp.1;^pgBB9oC1c'OiB0MD>]R/OtCb^#dik-'3%:JPm(=hObW:Q78]7X!* + B/I9Vn/bAT.d@>\]FF@+\MZ1VKS[ob.BYV6TZuSF[1[";(N@F\qZ<#Eg$VaH.chmA7n^La+ + qBq?k7)-<_ePoS.C?XJXU@t1$ZNceYe^^/`NGWbGFc# + 8'UO,06#L<`STq,.XHn!Nq!'LM0cqFps[n#lO^#Y$Qtr!aM?m2GmuaWk`Jr@cOGYLOmOf[F + R@duWF[m6N$Dgsflsrt8T:;IKNa=JIHQU_7MEC./-)D^T*Ef2*1_YSPi0+"1uT4%A:?A_%[?oh@sAo&&E2r5mA/,$kU7;/7FR>6%0>0oG56p%=pbP + +L2Q/L^8OR"Fs#I^^L4Y'.K+>?\NM&JN+,ZQV'p,r*jt1 + ,CF9F"K1(0f*l(1UAqpdlB5U7'eSj0;g**!:^[6+VCsq%F\G?;%#9:&N3JpkKs5?;f=TKo/ + ;eX2KO:)X(aH_>rZn0%J*R3lbMbi.lgHXl-V@KnM<3YT+eQ1'N;+!BPd"`["n0I40HHeO]q + ,#`>+MCm87R[llPRNeJ@.6m]h%#cLSlS3uAQ\(hheDcNtG?XRQmb)%9_6nXVou.6qmI`p@_ + "ENL07YfI>8)mgS@8OP@saRkFYob8ttn^dAo<]9L&*Y2[#/@cg_CdEW$uarucDc + pp(2$/En;a[a1*Pc`a;k`'GR[,g`a(,-ijGcbBn+ZZ#`Q57JsP*/NFCh;j2 + np[6IV<0fThr2ml4VohAFD93$+3*De=-KGo/*f6VY7u, + 93qKfGZai0f;cust4>Y)AO^D99,8PF#_DBkKnSp^sGdO;lQ-f&d + eJ[u^OOOPu:F/9MDa=eU5u0F*OguEL59'5<*"7rrO%12@"?&(m@&[]XbcaD&@72ZZp@Lhu< + )#c[O(*,)75c]b<*LJ:U7/1F7PnNKN:?ZXBd&^\7eCen'R<2h.f6T(AZZV::W"E=6X$$MK>8,U)'tHL#[m!R^NE*d4#i0b`aZg2,> + k+(b1]HpPf^\Xn)H7#L9QrcZ(LHe,1"H)Hp&,jE-QHhr6M:UAF>nV!X'lNE@8CBAQ6.1!,p + +AMR1FX5=7FIX1E^j#c?;DI<+bs77q>L:AC;Oqe9:^7=_2V.JZ7gE&iE4C4(bc\rAJR><0n + kpR3_?'8akskPc&1m@q_+3-Btn<2N_]G7qn-1-e=i'&qX))btQ]h.2:EF&9'\?hVf4OA.C + HQfY+LS%i2gLQ1/U."=_RG/\md46(q*'i=>YbH,\!\*+ds9/7H8hgfnY(9><:\^F?BMEtpU + NStpHeErHoK/pQd:'Q%7=4Jb44@13Io:kB1,c@R();!RP!=Qh7tWJN8XMSPdTG#&o:C,\BXG#Dm9bQ!@+LR + YT=A0](8$=^V1s#M!d:]#q@SKBH(fO$!!#t%+FFhG0H?IhU:@J&fX[b<0r'%JUUi=Xg,Yr) + 7AMt9U`)t'H;C?J;)>_qCU6ijGf?O>o]^VBNn5#+>&fRikGNWIVj_b+\q-dP@A_aoF;XbL> + )t\rs,Ym%3Z/$%3=0iU7s5aZOZKV,R^&!#*B_Ob(0\A/)(SoKZ',uX(R[+td=XWn4aDA+`; + )p*lr0.rS!=CnHIN=fPRi+D@6mu,?T5feEPk+D!LFVIYd`#HRJ[-t`MIq!]UQsS]@":ge\'Yi]fXu^HelCtiP'Zqi>Pp9RU;,Vhl.RjVKWQ2qMA8,Kr<]4W + &tI'Hk!nS&TbOA^_%['g69B%,H88@^KCS>4>Cu"Z)T[@OP6at*'o]i)oR'/X-[t0pNGs#`P + m%NXI@84otRUh4*aiHPT&9F]&Am7'jZ$[?)\(3fiK)dr>NQS?JLNI00>JH!LBKFHupa&3t1 + dD&UdjuQdug;fAEk-j$\+S@PgrM].mf#`IF@=R[ZTR4%5V38V#50RrTa](_sG]`CN#2AlO\ + ?]4R]TCUX&lK6q`ngNdHq"h5C@K[9\.Qr/Q8Fh%=49NYe=qjE2'e%-KjCB^415W&c#X4)*V + #=*;:_(64m^V3qFY)aUE(U("^M0jW2YG3>Gn\;:i#KbB@at0 + 6k4K(0F4.&.8Z-h`C]-k3nbgH7 + 6cPb5**;b@fRqJ*NTt041=m:T]XghJ/d2D\4=o5`:;pj;$9Y(Jnp8QJbD:4t'W52FJ@Iap/ + neM4M#Cs'pi#U:<`*^-Z!8r;2i4KXF&/h$Yf"Tnirn[!+iU20Pm_7fNhWaJ-p$Y*?miMGrE + 63&YH+uHln@aD,SV-G_a4`\9N1HBi?Qnt7lg^Sr^`O0ZrN"ZP2=TK^gK=]tImMh[_;"XNoY + &<#^JR*r+'Ld*`>a'JAYHKE3#:Z)XqHQ)L&4ph$K%H. + _RiEVl2Fp$^[)mK;bQ_#S0\V9&5kMnBjj)9-jbg4KP0?el2@slJ+s3]o["gj6_RloH4q7X1 + "?03)/ju-_X$2;a]C$,;08Ubh*&,!6j"U'Cm@1-0_.K9f[Xs(%UHq#Ou>!sS'pr_!&)NMi_ + 95+e4B-NM\,gr)KO_ZM7/>]ZXr5r_.>"n)E.D!K1s.3#aZhbC@M*QQl(0#TS,*bm";s>e)M + uKPPWN4Sb9$LhQ>urQ6p%o2M`FZ8'eW6L5$%,Pt*CPKFW!EM.!+#%0S(6s&ID9.Y(`UH@"t + C=JMrj#7Grk7kWh9(aN^G+Xb%:-hS@32fS'P_h)9iqH8"n@Fo\,igY>MCOCN^WpQFgk9g?e + OfS]:,:^"mV9S,O3/M88.jq#"Gg09sfCoD+peG=SX]lZB`Z?I)udhS-IoE,(YDu9tD5gUbe"[OhQ&I1U@HlE>XE0Mj+&Yrj2 + _*`2"a>=D3Tt#Tj0cWUp7.E*4'#eiY=VXB1KO5Tca8)`Q`j*0n*(P"uHQ:)rjFJCD1*hW"8 + 5_ijDb3i8X!Pm9CnE.K18LTo8ohSF/5($5].BE!>ekj5;^B759/Au^2+uD'dl'qlFKHh0ng + rrc::%n'4\"t#Bg0l5SH1rO1a2j4`)Jl/6rA&+)ap-,h$lp''U:1@;Y6M992ef`V+B(UrBX + :Zo_GmkeAdbNuoM;F'dI@nY'+<8,\9S=D#O1A==9>Rq5a + rAYEY&k.1M,>1TgZD-Jp[Z#:df[-\f5gU:tr?K,E3BE%)*m[LZV]Pi/2qI,d^rr9e'"rW + W?nD#Z-`Gn"3Ku7oNrub_f*02Pjo"MX<$:nk=KnNE^6MOeTLr;(od+ + "-b6:D@j=XSuXWZDf#B+PB.6RHdaRZiPh:s(UeM1lYB']PGn@Y%R;KiG3DEjlfW**XuAE3_ + q-[OXdcs,9>C;MnoF+@HLK$c.'V"42?05nG4E+>a:R,WQoUd0rNm63ZB4YZ1A%[$ZEmHi1E:IcY`-HNkjP"7Bo + A4iF[?"qN^'D=GXan'3QI5:fbAd=Fe*VScmbY^JJGq/7qm]H(j$5"%EaVVaJl#M1h"W<&DN + l)n:k%]H&T%h,bU/$?TS121*=N4Ej+[\S)FJO/8)_ZU]o_TE6m>tMYEVJ4"R7:t=IX`]1f( + lK8ghSeY#XK2==^4g0&Uuj(Lo'*N-I*5@,)kh`F,bhifI%K0chRZTR%,8e2%;;/f)=VLC/T + /5(q:O*5X[*%3YisL&c<*K"*oJJRfo,+N95U<[\GdBIsL/g)EHtJ.Me[8Vo15.YUFlQh`OJ + AEer7ns3.qR6rK94Kk#j``pVhG:_kR\X5/G@Eb.#1M/2cR<),9buqM6B)L8NF*;TL):(BW; + cb]N:3usS/nWH*q4L\tqTtGh6Vi[QMe@3kSiSWPkhRDWoTkNcRR^!74YEk`$ISoP7ZH=tN6%d`A4@P@&+Z:J/&2_:XZPQu%(UZ># + 3YVMj#oY8VAol,s-9/-_crqL,dF]go$&Df/N8PfUaH).NbaO)q^'6f)l;ST + T$23O2.g,oTh7QA>A\5LFh>+@nbD_QM9p,L`45:@j + n*d.!hcC:\_SkHFSI_9qN]XCf5I]qX1Qqk?X(/s`ps^7bm^;Rkp165*CJT;%e#':^6'MH'5 + Ql.n&KLa7/m2)54="GXon-HHNa*<]bk^K%e,iU$/mEaiVZc6nX^lEMSC7o7:2/IIS7h/EW. + GHLRQO,e'DZV5NdG"U\%lZd/&5`l9.)*oq&A=HEUcCI?1T*?J!nXIqS1t\%;;Tn)&KLJ95k + `I$klL;p]AOTKZDKe"8S]I/Q,b1tJ#iT24ceO0SA,ce"b\46ZY3_=S=?U&:N9j8OI*qC!Dd + TpiGnCEMN:.#,,.Rc!u-MDn&uhS>r%pKs5DQ,!c9^_2qk;e.dk!uGHZ^ftKfCMGF)SMZ$o[@.2[g0GKF^!lFMS?,)/FDq_QC:uTf82N2i=lLX;t84WrPQ^#WKggg\?NBB + LZNH7XSpiN+thpCHWp$^*5^(1\A]`Jj2)3'Pm0P'QZbqF?^0#7pGCOC[JKnFN6K4a,GT^'&pftj$To]+@J?'Cqh*74t`'a9H"L2$`c>:mmJt76qa_>Z + phSF)ao%ZYA?bA>5?+@RRGU$0?h]cErSi1]L;o&YUiYGe5OUQ0L\[c4;ornKNC9P-OO22Y8Mgk&j:04!%`qug2a!Hn + )4em.'M5'Ed9Co_D&5dn(dWe';4O:VX<#G()lq1(dG1Na",q[8s[^jkYdi.&[A@Y@@1bK01bKN".4MK9kW( + [W:6i=g(eN6/=`%M6\](AD&#`Zc;ofF_I9Vfj"]gFl'br_.ae6BOZ"Abb^:sCQ>4:K_.7+O + e49<`Ni<8:U#Ug$Q`>La,,:5nI<,u[R0;q3I!ZIn2e\,6 + gl:0(+l\lKA:Vfk&h.'6Mo?WACVu63`FS8aPBNBOqo0Z-=76W9W#Pr$=+1s>teJ9:/-/ + M<<^*2XBa9r?WGpu>RePMjhk=D_qZi^>[>`Hm2S]b9'bU*3qFMTOZkamC!#dH'h57b`W*r<\bRLQ#Rd=4kXNXC%;s"usoL;N + ,ZUB5HbcS0M4Nk?gqb5hR>>;QUFO3Ga!F(N375cJl[Cb2!1>C'dg[.edo\80GbM96O@br\G + mO[As/n5)=@h(8(*]%:A1h/-pj$#>1DDhogc.q6l>kQHM!E`VBhVBd=h)7Ge%;ak/5I;Hah + 1e7,*^`uAgZ/i5?/@a*D!g9.4CbeXAub*Mg7l]IlW2g23b + 2mM81UWD#+L;oX8S/fEC*)VXjL>7AX.&g(()ZW')p9^X*(IYS8i\)L(Z@U2T8CB[\_N($br + 9nc@%Y)c#S/)1=f@pY=4*g:)_P3GH"kt[OY?C,A,Qt^H*l$f%7Y*s(;@:PM_ek:aP77jl + YXtZt0F)hPO.@``$3H<)B^a8H)ZOAUe9?]GU&+2][R\-GND9>>`OD+([4f%&IAf0_1CB3s8"%(bi + '&kg4XD9gu/XM8D^hsLDkDAPKS+GJ$NY.GE5iP`i;*p,Op9(%*qDN + n7#+e^8?N$MG!&of95CK=7$<`3pL\# + HhQl^&H(VRpi=moUYMn\/.Ar`W/H*nXe():eD[hS\I<,&Cm:=O+s-9"AtSA4eL&m%5]C'pI + PZPYs,JfiK_4:=+%Dd;ns/ErRk,'eEu>1Zp\R_]EpK(3O\9G4U3G9=ESfT/pC^4)O%AiGRO + V[bu5gXBO,miMPZ!(ID=9*Rl;.iV)JE%7PAU7J)5FoTr0lVX6f:9CCr%DZ*Bg9@GFl*]%k@SmMAG_EuH_4krQb*'je + ,+W%>pm)Xn$[D;&,ZrPp9q7"cYQ-jD>[(`p@lQb`am@_5EY?9Gkj3UGu*^N>Wq%hI+l;_q2Q>OHPBi\\D*"1^kL8,fnqB<%ctJYi2 + ("5SUf/^Ag4__e:9%9.[5t^-Z!49\CK9Z]I`5a$==i#b4dOK2[9IK+qdmY#EOoECf.4hko- + Qr%)F+]8#6fas@;sjd)4^3DCqoM-lu#$6Oih2iffZuM0plZ5TD@:AHU4FSBllCa-DO;7enX + m2p>3\tdb*qFHX4YKFgg.EGH^_3a+M1dEHn"&+"*&jjDt(OhM3n`cft(JSUS%\X4uu^2uW^ + LLNhbIGkGBkDkg\(p3QE-][t1[oD!_t?XLfQToK#cs0)=q!5Hk=Yl?tcKF\4$cs..e5u;A@ + !h&2]rs?QHUb*"m&DSoK_bftc&&Q>+XFJ0N1eSM$;+t+,7F>@I(<-Pp9,\oBZuI"QZ+P6la + 0GP[C.L)tRit>#q3`Bt1.7BSac_%<.*9o%Tck:cRK"e\1;pRQbEDe40[%oOP\D62/C-+H'3 + V(dc)+It36fpBi.%.ND!sG]'AW7EcSPBj4&@7u#=+[k2%Y8aoMM5M;8u$D85]#=)Iq4&\c5 + <$-?V7BR.TK):K*!*7oGuP,u/(&2-Go=_TAs&mp\V/9Jh(#OXg)ZqjqXk5+855XA6\dC_f=J`/]qJV=&K!@?.WB@G*r#h + k[57JrU"u=pW`M^hq-EZ?^Rbj"bL-,/eN`p=>h"'(l/)2Lgg(8Hsu"O[O[9r=NPoJA#K;i? + C4k"8!.>M5#%4WR3#CYjgfhCQ5DW*AmT(\fmnS8):il"kLo*LSS0>+JmE'$3WGjg3_#C3kl + o1VJ*IrOFSEi.%'3f'U+fqlt + 4VDoK0^^i.2pqnaHQ0kss4r)XQNn_@hm`H!@+-<_2_V_?56Kss"dQ@.jZJ+PlG5[*ec0k\r + N>ut-XomM$f=VdQPBA"BgM.bT;4i_^%pRV>.gm/,+s5Eae-]R3_*a/JDQ9K5ZkBWnZVs9QH + Vm#BN`HhLNqnD7,nGF@Gr`5mt/;1PX9!*(*hL%H5f"LQMKU`#*n6U'*h47J+?^Ga/p$=c"kGhC'IZUjcdC;). + 7@ogP7Q(=.)+Ml)I(7MF!k@jGNBZ&UV0R*ci44Dg'T\i*%Qh\Y=K?B3ER4.c5L*oa8LMNRi + 4+9Volj:m;6nf7s+PTqB6;BeLa4V11&*@.G40TsN"9HtE4-.-`n7d/E",KW?7V&DKfd[_R. + 3E28T@T8W@XYJ!5_(:caZV1\-2+s1tSm"7[W3WUWfEpdMbBoR>i + JfbefRM>#`fs@m?fl!MNF-SC6Yu1T&D[Wmd9DenO5-D%2f'[X*JV>@g6?c.0@(L?RLZ@sW) + O4hoMP&E-kEN2k/eX1;:WjIFJ#F/%(->]/&3Xo`iI' + \n!g'nFr(,$(,4FTKO`F4t8IP\I0I31(dUr[(\MGV;@QKa`4X8o';1tc^X-NH]$qj8O/Z#T + j92a_*iY:&unKbieP7J@$.>or.1sDA//n[lu@uSp]eXEgVCX&r%2eM..S65Y2:0`K61*#PL + fcG:sM;LErL.tlNgF+Y-_*1pgs2rNRJRt8U]dPr7U"OmKjpDhkk`$;#bVkBa.QcX1a>uHbtY-VR0CE@BB-c[araL]'GgHY,fj]Y8-'2]aF + qHhTOTVc]_kk9+eJrF0GLIFYm + ;B'J-`YBOn*iOno6p"ip-H>e6)^3A`7hqRQ,gma_Ii`4:8(ZtV99f@p7FS+k(/rW;36'*7\B.tTdY$Kftg#0HjdNY2Jjk + L,euoG!@?qBd!Ad^'DAloEL5r49f`NtZXRIka!].I+5XAB^QL1:lU'RMaMW9h]?Tn]tde'5 + &@\\\jMKqQ%].^2nK>FBLE;-Vl2WhXZ'jn(CHcGTZ3-H#eRM*u..j>5-745;U + \ld`!WtQ-F-#3+kiRSEW4[o@`DokN7-ToBSd9g(2\H.VGJ)er9:OpIAp>@o+X5PL@2$]%#= + &V^dFlWg\Ru38]EirZ7s3c;q,/X]FVmB?f!;U:0A`UTN@KFOu"K\a0+bp?T0'YEf9"rWfF. + IjW6`Rlg7EXeW6&A+\K`>PGoP0t+RjrSSdTqgY7(rr:%:s%iLl&,uW*r;_o]!3c\.ck?Ek( + BEqD!Djd;O<4O#,6EdXi(aR\!s[],qE^,R"3qUBDFEOq8H_j^]+-s`E)>T!;[&7a"jg5/YY + >7>B*M1#jR>Fqc<1BI^rPGpn3/pP5CHGXWi88a=&&>9F@R-ARiksW + VWcCF0;Ur9?6lEIq?%^+-:t"^sFU`6;%F3W:%#YTji5e[Z%a:1*m$LsMU@58 + ]n.>TAmfr9"%1nC(nIHQN%iEN/,8#mWbm`+mN7d^Fh")%7o0u^Nm-^]'5gQW)cd,A5/IM/J + 6te5Ud-VVa%3!Y*B$q3)dYa;*%5.BnFSF++E,U2F%uD)m*32kqq's9!%:'fF>-/BoqJhPlY + Gg'8QMV3"f?^u(b5BnQ`3?Gi)R6GlcdK(6&H-U[g/$Y\n)"%\Yl_-W!YN`udF0)?f,YIV*D + tQ4&`4X,h&_^=*jOlLdISFlpE&l%+&VtYOoC%g0*#EUqO0S(NHIjL29-.%Ps%X/3lB*iAt$-Xg]DN>- + &FI!K?$BVEVNh6\5]-rcNlI(_%&B't([jdEaldDBEcH&nff'LMd%Es.cEjH7=[dHS6%h@tX + "buIY`t+?Un*I"b1=G[^$S??,bHfuu/B'5=&FpDR/ugK9$8E/h0$XYa'(Z%nZNAFpQ1QrppQQT4l3O>,6e"\ + r;V`F@L3`E.APHR&HZTFAU44CZ#o=I*u`ZOaX+L!RCne3;>9d+K41poR*^m8!IU9WRbb:X>XjYGG&ff/=(Wq\*MCZY_UuR2Rl'r$S + ljds/+aRo5BWIM'=S.jI$Y^p<@83>9A2'>mdJa]*CM<0^6YePfGT=&fu<um5 + #P\-KhO_-OEX:p!Z8RW@.An6^!,@7+F\%\AZgX$hR-Y0ZNF_aXS!,RF'C%o + OBA2d$p3'7aeT4Ek>aZF%;N7m^.A`e:>[qs"+qA@a`p@Pa(\pSR[BR0"*rPEq*[>X"TFhAC + RjTQ;\GT):[sE@/Sb-)$T<,=Y#Q2Sn%1M3^JW/X;Du9.o7'Z9ePe=ZIoD2&O6'E^ + bhelK\DO0a/G4TV&oktW?Dq=f)(AVo?I90El\R<;u't-JpX`9W,+]Y@B2^1JoSQV5OEgJG" + f/"YY!HXI^ERh%1%U3-r7r_U%6$9k,QVtYA,=4926?F^@2:Y&1B#=5s6apst[GuI2l!A(T7 + ()Cj1f!@:pe]D0Gg`F6'!$:SgL.C&%Q4i+2Ao!t]j]@(@IWpX<.aRql!fr1&599;%gulE'1 + oLmA/(XJejW2S8P5:Do/*db;[$`u>n`D4IaLGn\+92-DeE*lB=<&$o\4%=#AA5B(=PIs2"* + 7q9/Mn-BsUZNm9)<+@l;Eb))Vk<[1@$F(MbMpCN61Jpd4n%_,=(TCj8^'G0;j<*GlS>3;3A + kG6^GHN6*Gi@Mkoko[(Q:BmMRjmfg*(JR + [u0`DU094E3\isf`M)!I,h`ihq@fYaeYJS]62+MeE0ZG#hf]DWG$875\FXnZha?n@O1K7k( + %6B-$$^_)79L40f99T$QUV[%Or'g;j^jU$QO^L,WRAUl&GMB4[?ITaHeC\V)+BdX8Ro;*A( + 73R&KbP(pO0:qIJ;cN3G3e<3AB-)QMGT6Eu-QTq(b"rCAEKV=9Y)FNG*AnBWX4E(6O/Io,# + kKJbc2\RB'/*?(2ZgRn%a>(9p`I_42C^2jVE\g!Q>cdtP?`rgE_X4$g5W!OR2@[$sjugJbJ + k=1&uM[9HkD)dg$X>Te=tTS!\T\j`D6G9qPuURE.3#o/>ELSGfFUK`Ye)DelbICY`_\!fgt + n:cjJ9=fFFV)hJMG@-A7\#(#0]@pi/)"jc-7??&GO8/mU=Ts%MogLooW"`I7=Vm4+X/O!-W + Au2L>+!BqGJB*!W].7n:A9s_5@XJsHr1ET]Fsbg_gM4/Q(#g[def=e[=2rXQFbh:b$12nja + U/9\'#c%b?VV3K)N.G"q2Wq%hO` + 34*AePTlGFGc.d`n)k5,8hp,KmcCF?,r7B1Ufq`pg]"J%)f\:PVaX5cOV:b+GZnK'QB6O(E + VVBAD)ptQ0!PNf-OOoPl)&/Nb)5rd2Oo+O\((ZRto;T8RWVc^qHiq01YJ@`Ue_7KBGK[&?; + 8J4;_:m(t\(tTd!NeI70Ni1?g>-@G44o`;'`:5i*(QAY97V-C`>\]o'["f"n[%S]LWQaZ*S4r4h)n^##9qhL4eJ^$jiaoA?,/hqe + %arVPLo#2hV_b]aR@*;nsSpYhY8iS?rA?'OkE28"e2\i)SO\!qqi]c4HX;b[P_YIY + =V,ri8JfE=I:^6#C$LR!f([e5"uV\D-eGr]0RDD`rI`!nLR35XY*j^V2L!)Gf<800fsM?9=`Y]`99@WIKT.I$1`NKo% + tth+"?eg0CsZLo:J8=ha@"C;mtNm0Na\%gSs.\1\I=uot!HKhC&#(A+lKKp"s]J?ZP:YNO>JHdhtD.pM]E*D>U + fM#kF_s.(2l""/=0W%4;CrcF7McHQr,t:GO@\G1=UY1H*gFen9=[I$D_RSPk)3g"rbXWaXd:i#m + .,QjTIjf`8,X.KO1qIfeBhG>=l>&i%;Qj-$/ZX$EST;IEYh8:X"g*[-Za6a8G1Z\uhe + lRK/b_o-rF3+G?EZ<"p%JK7]dsppr$E?jLW*_BR"tOO@un^l1$]7N?!ioAjjhNJrc^SuctN + &T>mpU7Y1.3o3k]^G@B*?Y+8\4(dquYNDYEPl>-E@ihsbl\4uWk"W`;:Z>g?NVT@)P<\fEu + D=0^9BFo-TYkC:2(meH]$:D]AFkOj,6StBr)h=:8frVp5kgZeJ2IXduNoCi4!YQP135d1Y- + !^RAS'*^aAF;5'IY`'Y16Em7=$,\I(2%2ikoK*UOOVC"l`%`N5'1FW,t<4+#J;:RJpmlFIVAR@RN'm4"i:*sf)>G(T5G?e4 + WeSo9T/C3ZYit_os\knVHKdGgik?lF'U^n + f5ocWH9m_slGIJn+I"q2*7,#@t4.5F/Rri'XGR/[J]F + 8dP$qu8'@!25WZ#ZJlV#CsciWBdbo"%pqI/!*$"(/i*T$R'29"fSrIZJc$]O`lB]r4t]J87 + 5,2MhL4N'Gtm?$VZtWL'D*(RR9j[+gIn&-G7ns\9)X`e35gQr7`34MK0ml7ldh#.7CPD',% + ZIM@h`q7/3Yo@\d(_d:OlI\?Qhoe6S"FIPF:TaV\M=@uSg41e,"$)e2ibNKLq87i:J1,IDN + Pn^]fm\FgqRb^Y\:!^B2.Ya1IeLQN2l0SWEnY#U^I&me:(R5lT>oUD'8 + G#1dk74DdVVc,?O8BSIl.j\/s2Y;:Ap#ZqQ1:ir4g[&!,HR> + J7Tc'P_]lLCU=?sYVHHKP-Y:,oG+SA?GRDH#mUj'LG$"\]>A>$#jc@VZ4O/R(g($RTp:s#A + o8R*;b^l_c(BcQjrm,38J!(.7(!UDiP_L^+K]a:[Ls)'>5MrKD5F_Mf&k]@]5_a@e\/I.ro + %)IM/[>a_mn;FjBToTbb0"'0>nX;ecS%3`@jGT/+b4/,-?$U"7YIta@91i + ni.PGB%^":9th`KeF09VX72kK\L]CgYPS@6(T!a#m+9aiI + ,E3.C&bE?VDN&`aB`ZL[pJo3:O8N6/i&Y-1k'i(1E3i>+/`Y5ZqcR)jYkKN[4f]ARJ`d^3d + "TG&mre!(3&Wk_S%&*JE,\2cXg+m_haqRo*[br>[!sXT)G-8No8h**7[DK(adCks1 + Su%f1cd[4lfm07Vcgjn;s`\L\#-6U90?VYDXTLO@euJ^p[G=1!&9XB + J)t^A?(P8Y'VEn*HV0]&*L\!D'A9f24f%o)bS-Hn2En$!8T-"fs6nK@UuE(O]t&/:P(5`X^ + ,D*Hql"T"hTC`lsid2mI2`T!T('AD0+>ZQ^Eig(p8)>/QhPDT:;^gLfg>]s5%)f%o+#IrT> + magcOIj4rCeFDee<]6@7qps\2]=ksi3+,Fcl\Adug]b`C0*8r%Jm_uh8MBS"-c/!D#Y]%fbAb`DjqquN@G/!3E + >LQA;hl$-^3,>'9,GR\"cm.GM*%+X2!GFH)t^daO!idIa;mbmjA)\:Z7Q@(F??SbWZae$I2 + &FO8rD_:ad,"H>,6Fj[(&68j>/YDmMV;4B"DL.F&b&oq6rA.]]5P3m6^:[h`YW-q,-bV9[I + )mW4hJqj)d29neG@u&NA79.et'cRSV.jY;obX`G*dY[^K6H4^5;tEe-+*;HKThW[lr*YPPQ + ?lKC$52355"2D8=P?ZodmQFuu.$$e9shF%<^4-./ZK/gc.^ + `3Ece,q\3"Lt62Psba?F9B=3!]\Yi3&CsWg]'8IJN54a^uA]]6^>Ib&?pMJ3J8#20m5RA'Y + 2,N0ajMV4;YQfEB3q7V^%liF.p0%*3kBG-_)6_e#"k;*'/0b\s"8R14M?kWk,hh3ohX!fs3 + AL.(]R1_P=@kZn&)uZa.e,]3?o)lBA1sPWOf4]91eD@6,V0l.\"Md<'S$bb;=OK][KG7gWr"JcbraV4V@]Nlpn`M\ii\`[4&&gEk]5>fu2@m + +Oj0"Zqmc$&@KF4+X>m`1L7^-(Ua[o[PR!Y46AG"k.Z4GXY]1[ZY7(Jq7hja`RJ55jBom]@ + )_bH7Kd/>1h+Hm@Ra=,`a!&oA7ro(BZ=k&`e8(rOSqDUr5R4a_!@,e4:FKn!5Po1^jHGY1i + gW)#ffV[a&I6&A9Z(!ElT.,\GU;8qZFXhI)bdj5m*BBiafTI'>Z5MVJHsWTmaan(._;T6%b + cMC2q=HF"q3^\h"lCZPpp&Uqutg_@%F+npSA+OKoQH_G$,]>UR,4Pa`@9141T=X0BIXNLQ^ + q8Ye"2ZaDRS[^-At8`R=u4J06l]gHd`@G9Lbl99"f&M[ + 8Y`$PYO)`]=kSa`?\<^HrI`\'MJqOdUF*.4B@FE=Ak769P#]fPfKLD>g6P+c##)XAM;Kn0N + .Qtdj6`t2)/#u1=JrRdoA7*\4h<*EC',tH6kHdi3je*Q9P>^t5Q9[9VTcG]*LlO1cjmS<\6a@q?=2"AYGo0L&iZ$c4 + ?9;>9m)bB&aZ.tDW,l,OoUJJR/]#9k0e#gs_.F83aW5P)j`1"R(S&5+,6Oa2qD$Dd:#Zd); + d$4r=[0o?\Ut4$W:u%qpoVbUqW61mTd4D>ubcK2ZbKAo^9(`t<8c-[Mcb1;g90IU.qRMJ6; + :3;495P]_I3,&O\]!;HbI)r0Wl3=B^-2,'dR.@mZf0+C-VQBFTu=mtR+FND`Pnq!=QIOTCM + M=P3S>./fd/X]/Wn^DPhAKjffbYSXe+5ET3;DlJi0 + 1S-mDhXUtR]&g-qlB-mI@;Vuj*#>,A1O[AhlnWnPFbg?#$^'sH?YYZg_(gEj,Z26g@SZJ/* + [g>F0,7B,$i[p+QsgQfA)NP!rA]N_eVgZ?-'ot=T6^>'14g_I]]-,\LY`8!5h>XcNaA]NP2 + aP0_:gkEmUQ*slHbhC=dg>/l7D9pp9crtS>go]#[^!hS3+keNu>trl*Fk5B&em0$Dh$q`e7 + BtZ[gtdD7?&d[rp"3QHdTos!h+cl1*TLHpina"`hh\GP8c00a*l/,#T?X:?]G'Bjo=4_+G( + X2?lf>V-2h4?r8:TU?hOT[2@&.m#iKk4@"nc8"p*Yl.cAS]i2."BSe$DQ+^ik5@1E`S`Y7; + h&)aT'iFW^2FqN\f(?(8iJbU@>Xr?95)e#$QiI2YQ`ZO0J+CWP=Jd*6)2@X+*,iTjg@V-op + ^`NUX.cQ13@ZDnsDC+:c)e,BjiiXonVD!N".,tdVii!sYQ88R81h0!.@cf?'Qlb9G3!-6CA + !St35!.f54QD5>A"GjbmQY4T391i;@obeS2F$7d5\*&Ej,b\.Rhj\B+_hHs + is=lm(n!S1Rh-n=@C?fg]ejq_E<2J;/ABP("HAu9)>en\m5*G3$&B$P+%i#LmnE!!1^B'+- + rhK.hp*.cCTk?`:DAp>9Bi9hB>mO$r]2WO3p + jR-(1mUkVLG3A:JkjFc$m\]:;[d3A$m-`HlmcNs*p@%GSnF%._mj@Vo2Y6B,o^>iRmq2:^G + 5(H[q!XOEn##sM[eoO5r9r58n)jW"RoIrn7MspG6dVm#k4/en> + ?W_[gV]G%.MjXnE1;NpCHd!&FgPKnL"t>2\Y^O'_,6>nRiX-G8Ke))"Eq1nY[;q[i=kX*:_ + W$n`Lt`pE/r2+S$XP2^@l`,k>"_nn0Q +q 0 0 409 407 rectclip +% Fallback Image: x=1, y=72, w=404, h=50 res=300dpi size=1055868 +[ 0.24 0 0 0.24 1 284.726261 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1684 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J,g]g3$]7K#D>EP:q1$o*=mro@So+\<\5,H7Uo<*jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/mioWjnAY&^gM+`4=1jRLW!YA=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&t\ctIN)4XQLiVpoI(>.nOW?*DmsG$@,,f58"PDKfeXi0S^6MAH=;fBr>1IXb_>k + P+oS^^pnX!PjdJ%0OEX9,K>k6SoYc6)+_*1I=p6-ORa&R)KM%OL..Z_sm"N1IQ3c5VW\0,Z + >CFOVD(:`WI2Z)%=ogBbAAtHu0bAOfWmMa3kB!+iSIQ=7t4X?!)7:7pt!c-'U1nK(YOE=ghmmC"cUd[qAnNV8I5a:CFuG6E&/KnU)]-amn/a + +3O,jO#,"#r90k)%,CkMcu`O%+jkEQ-WDl/!hWWIFZggXd<3-P6eHP;YBAXpS[Wbu$2o(#h + fqJSJ`@(98!VYM_M=K=q#6>I-OM(WWU"lELUH7?rpR2^qSJ$*nVQ->bNsWYDoMBsT/YBrN8 + J:0pO$F`TD8Ec\+0ERT>17%hp_[V0DY`8#R2/u?qu7a.KmPs*m`Mg<`__=TBWt#eCVb6t)'0U,>Em + ],BX6EK.6-,6Vu1*FkU[NKMXN7t?X@U\nP]Tc-#65`Hq_7!F]sfHSC.)^/aFLCDTp8kA1[, + c$Mj#dR3"af.6;JfmIG&rKN-q)p1fgng96MH!2.>q4r`Um=ou>"]0=/EOOPQP/^l9A+A>jl + F4Ddfs8T3K5_i7)bQM*bA$uR5RW_GogH+M>nX^E>GL'FR4f>+MT2Xm$:Po$6kPY:O'Fo[j=p%(S,m^0n-AG@.*C?]Y%AG[ + CM,@G*7Hg9[U=VNgA9 + O7DC.WVo#Z'b6p:>(]XAT_KW%-Df4lR8`Bn;5prsHISsp5O,pbWmu!gsGmf:)MBlh,h4[LU)T9*Ja?7>J-K)T#P/eR + RrgOfe=LYZY/KAZFiqX0uoHeI&3eXT_V%PocZ'8r[P",sAY8nse@eD/71O1U\P-g"T_BCko + ."[j/oJNsg;Qa8N`^2ZhrC+l5?erPuH-,3<(aR<-FDm^X-+khRuVFDeVV]/OPUNn^a]Y3k, + Z=*/^o9?]IN/a+;NWqKbgh0SN:`oL+.V04@Pk)?Db2\7/jg + !/s`suJ#3J0%tn\LX"5l]((X"t65eXMT-)qjO@&,c0oZgN=hj)f=&r<] + 1)/r.hX-UNg/a?TFWtir!GLEP=TU#&r-8r.gF*/kO1\2^0-PX- + F,kr\Jh=bo3+eRI_U$1HD'ek5=D6F2!#W`gHei*=JP45)ms9#g+&Qs.shRUXQf)b3AjqIuK + tke[hqclWmJ'Fi"&43q$JBrIehVrlQuX5@k]1X0rQ/N\e:kE:*VE\dWK8h1tdF)854HpTF> + L`[K%QrWg:70+$V82hDm!/:[H0)rj&)On'PI@>po[ja=S[o0-u!q6)hD`YY*95(I0i:JGDYWr + u?F#98'uBn]Yn%Dn&J9!t&A_H`Jc?>P, + 8(S7'!Wku!EKqDrt@+G'2^b'nN@OO<#%_!'QH&GJQEQu<>=t+'ero-EEs>7@2XI=)0)2b;5"l4UD`Tu)R67[nY[<\XW(-B)f` + P1Z,%BA`>e?f*&5PTn]`(/b8kTM*KekmZ/H_njW2b5*\lt%EU=i&nK2c>+0kJ\dJ4mT"X#Q + ,+;,,+EXs:u&Ku00+gMb)neiVa*?m6f,'#7Z;DBB$1EuBI,I0cJ&-EBtMlADr!5."44Wf&Y\lSu4#`j3c8.'"L>b:'JC(9oA-.?^5NE + hOqfUFGc0Z*KKAnrG:O*4#.G-u1[jnpn'PHRD_4WD[R\'$dF+=!hrr/ZW9flY.sZCq!=+[u + 6"ol\`38oXT#;\^L>c')]>)"M9T!]+YW-X2-M@a"]*e*NS&1X5Pfi+MCG=^=+YhZRWbL!Ae + Ns1Q2[Vljgk\7)B?R_NR[1Cbmq"2T&3V`3W`,X?OVI=#P,22GA)0ZZ.niBZJSHa/k+Vm"EF + gSB4]cag6lIXJX,rPfj`-bDa6[XN\`# + m2%np)UckV4fp4tXZk3tjZY7Se4iQbNE&1p;Uk)Iehd7hZhi8?G1SLJE2_kjmH74mUG<2CIrDkk>[u7[%?KGV!.HJkmj*FDOT + jT[-DbClSQ9?'Ys#%\f!-<9WlD%eBmRsd-Y`+mB5VHmcr0:Obn'b;jk5J@ormbib&,B9[s&W#!!,r;HQ%8sYK`[>\=LEteE&1WN4 + ]u_i"7Srdn5idI>6U*u>-MuN[BTr*HO#XT#Sk#Y:l,L$`HeI'[.$l.S,Fl + *Umh$pU=?FB#,0]udso3ZJ*%^?6Lp&P_*!tgY/@($@R&M4W='k&gC'G3E8p)Og/9bc#O,aL + #k[P9'D04S$=@s1"F[R"Dh7:Z$]A2["ip.]*V94`9DAX6>-[UEb@AS'G,Ai=F:G&:kMEG'H + 5B=;qqep1p%KkNB$BGQS@G)p=FOWKZc-%\f$l + e6b\477Rh:KWZOiGYpuV2b7gb86B(e\7"F*OQMpE8iUZ[%XUg8F\9r79/NnH['o66_b9F,9 + THdP=HMo,dtq&WM6>B(0'M_@nVVGS:DE7nY7j^!ZVXFF:cQ'`D_4en&WCWuN3sSs=OC#+eF + i9E;O'%todoi[4S\S2Ng2s,YGh;Bo_S@Xq__@N'j"E.>5Zo[rGU_Xok0Btmu_Pi=.('.om^ + &nLKH5B!k"#NeW3ie0j0T&PH[&Mn5!4a=d3l6PdRi$osJ"(]9R>fQ*nM7(!Bj$O;;f)QBfo + +/(ReAL'K.,'egG/l[:sfL + S%6"tft!UJ`LJs1S/KSAH-`"kd@GR5S[m4?q:V>Wh4?XkSpB^p=n/)oo:GdNT=Ociq=gOBr + F<:),kdlBo&juG'l*;RCHa)?.oCf\Yr(?oZs25<(WDAkZT\j!A;3GuDeUmCJFlG + K?^_(8b9V7*pR)ITs6(q#9/Ti1!LpCOeHlZ(X(EXRj\)M)p1/MKi"Er(c:RZP/U5;Cb[13Z + /BQV4>;8M\mpWj=j2[omk-[<=)OG#rCYpMDe^F0&GiXLEC3GD!"KH`_+.2qNsI(QY^TMg86 + bY.'gEo:<'9]m>##HM[S%(UVKB"Zr1Y486^upVT-u'n"KdIX]7V& + n$>suZa]fsfDU4/LOa_CJ@H,soI3-%(P/^'6hhb2e4$f*.n$m!K.ZVGRrLo"6+:rEKN)eY3 + (\eu9,pk0@guoUCUMX1EP"8m$K+3.X`4LV%ZK]/mj1oSONd0m*q!X8go2L + uNO4@=Jn%k)j,utlmOZY2o=W*@t)cq;.=4nl#3@=h&0uS>j!ut$<\N*f\6!AP&>#hU-Gu)F + ?qiSmFletFK+,;GaLR!7=c:B^Nt5K`?e,=I4J<:o&"3?pRXD6+ + 3NpUdWt_g1@[[PFq>neY"jB*Nb^@[Z>g^d.e'.a?c+M`Sr7B4Vh9K9ac@#$)]^a:;p!3L0c + TM$Lr;FpP$7*c$Tc.pJg1mM]B-pelE%=YW4X$Y-,dWpmDYcl!]eRt625Hd%dlf_?rB5';;5 + 1X'-ljP/f)MHB)_G>3E1IZml>(B#E434n0%V2rqKo.:L"C+*W/I^TdgOea(YEpjWCtR:rK/ + io9r`W.Wn17Nlk?E9?eT<@X3pW,7=m,G[[p2]qeq*kECJW-[p+1;fLNFmAVE4(7sgB\oT+_@P + IZpG\fQ7]H`Q?_P=lsR],lprS%"3c]nk=&m$kme<7SX.c0BE6ML3t?qqo*:otFbBnses(>O + _6/bJ-$_m(<4UGhqaL"-+?);cr;"eMk<%8\n3#_7UqPoi&_Q#EZ.BO-:G=`?-+'cqflg=n6rI`]6t$r+B73CC#2oa#RXqfkm'?79u=*aB9#JqEM%WJ$&T4Z28gqcsfYp]k+*)@;rmp-@JnlqB;S];>ko9e<5O8=te@>jjYC8e3E9up + ?g;6^!_DbV4C@(%nFP:"U'Cm@';N$rY5iq_dX2PPWC5`KuK\SLQgFJ*0&!L&bu7C3@'*rV% + Qo]ek[iAb8q<'PQ#&KRE]@Wa*Z564.abGe/AK"rpH@TW3gc>(7+Bce)0TI>tUkjX&Wmf.a; + h`>->X+p)"T-/F? + r\hX!sqk"glalRR]I++M;s3>8`\c!;!8H3_B1tmpQVcg(N\MY<2rlN7Ig$Q5[IFF2*s5%\' + ]D`TOHK*rShu%@f("::e\@Ja`O%%FqE:nPUDT>bnh![K)^(01q_;oP1]at--X3B`!H^k#sp + WMe#q>J%BG3bFnqu4?koE,(XL>g4'^p#p1"@7!+)Ln5h*$5I'&8^lo + u($-OKec7+!P]'h'i+Ynk=Aq64g-0q;en7cq?r*1uB(^E,nmm!g/s`ps'Q8CB&M,tTj@X!Y + sOq5S>T17Y0j+6K9-/5&mb[kCC\"LMme;\?HfRWC=51X.^1V)HSqdJdiT1S!Pf:*th'J'Tq + +ofc1Co^lfn<$D6S)\b.A6d[:WSnl`\I2tjVFHn@HRDuN39N+p82c!AZadZ,V-E6uVe>?rZ + ;cQghrao/1I9frUFd6]QRV2S;>h9_@HX9bX;5A]o2B@H^)E&UuA(`7*P@RhID4bNFVM1suR + 22p0CYL1aYATaDrPF&Op?ftARFaUMA).r(raT4MIGAGk=.tV!hT,GLAm[LQomU9Bp&&8:BD + r-fi0ZD9KOBNPP^hZ%IN2da3.<<5ihuU+MITc62Ip;Wq1XpjjpjtH+8INdOr4!a=CC6F!K; + Nj3M'fNB)/lG?^RbjGFmucp4K#NL%0tKkdid0V!2IaQ):28mZja9"r=2=(V6cRJ!ZV,\ZP2 + 0#.@q+8,7=9CqDmUZu'C-J$SI\f&=.]qi9pOm\S$QJ+#JUoN[`phZ:L]-i%^fE4eCE`,U0+ + )oJ!>Ii_mfjhE1cnsm09>8Kg02pRaPs$Bq3*?"i[oGp.i>]j=\CXr*uq.\CQ?&qubf + _7h,H/B_Bmt'=2^'BHsGhtadj8!"9C#sWZM.V865+e\:HN(CL=;FF,\*j-bpVZKIKm@-Wr4 + bJ?=-apmg%GO\RDq.'XLrbZB^"FtQ5Y[hrTH@Z1nZ + 1IbKo)'`*nNH(gR7ekdZ@r-4H&^i[0=RdEle96n8k!$hqr?,VRK9,R=5tG\r+llUZOcgnY8 + @]feA;$/0;H??1Gpt'c[$d,@DAe%q(.&9h_][DD@8(Bk5t@mVR:H5.bk[Y`AouIhj5suK@I#fueCU1Bg1sFAs#; + A?hjfmbEm?\i3bLkHSFa13:?gFnkEPM.PJVZ@ag4/Xh"dBP:4H'n!ApZ:".:t]A5IQ$04k@ + pTp"JqOa1q78Hc5kP/NRtl7br2d5dt4gmOBNIn48W+=S"hV%h'K;lN>i.[7B-'qsAU$R&t1 + [+:Z\o%[F5eN'NRi4@D6I8>do6SQLk_,PIj;G:MZWJ3a:)/>gVC5JB7`f/8EHE:c(EN6'o[ + ]Z(j@VecX@lHm4d + K0;HcW"QH^YMjJ%i9Xh.3i=YQ]E\StI;@nc-L2;E<(E`7:):'oY;-WKB('CF8[8b#*FAH\a + 1dYq%<=\:+^^nfKVS@k2KnZ!m/W>.1ehCoSY08$"Q\H/[X5=:<7KU>Ddf@:%.>-UW4MdD>,@];jN%oK9MHKHm` + JfY=@eN`Z=^*Ii\CI*ZAoir8'0jO)m@Q4oJCCe;9PR]FX0nIZeXH2)CcO)PMoNcjZBK"FNP + ?u&>d3&Tds]E!n\O>]"Q18@N,^kC`OQ4%jBT]?Er!,f3QF8HF-EEFNg\BXLL6WYCU;?IiS- + G\GG&7PK6R/$iX7].o(5t>H,@\e4dW4F?H/+T:Oc#O=1nAde*;A8Fne1fl_n11U4?&>;efD + 4"(4m)JXWkL6$OcY@(*=mOE++S9d]V(K2K(+&B5\L#EiLDNKE;B;nkIgJZHr#Z??89DYeG%rb6]#dM(!(W9c/ + BW\K5t9%6TI^Iht09c;'JNEBW3TGKYNsQc>c@Qm33sCO.MSbJ5U)g6`!b^QPGOA9M427VY& + 34;d+EMl/,Aj<ZW/5+<_Y5^WqC85G5B!jlO@F,L?>f + ^9%2WI?#K+d422dCjkspVGOTbC]UeT.Gs"fLYVl@NT,S#Dln+$tQ8J]qcS&diFcu?0)Tl34 + 6"WsTR^:Rf&D?\YBY@-5[(a!!,C<@)I?HZ_51\@jA=7:_c*p:O>MiZ.I;l=g_mn;3j!Ms3E + mo0T\`r#6h*hG<5*aarQYf1iFV/i3e_sD-R8@3s^Nr*Zl;[&Po3X`0I$LQ%rKlJK/5P5SYN + !dA=93-og&0oNiXNPN + ^QR2c[@JWm@&)B!lA5oUc2UE:Bn*l12d)D`MA,SqAh(c"[b6(94;WNn:1Z8.iV19%^f].L> + 1]0ZlY)tPbR,;:o!,Vp=W1INQ0P;XE&cEi0Q*>:K).Fc5H4>YbE*Mb;Dq`gY#5VrR0*Y^@d + 6/odD7@n$I1fHlNHV$&H8Y2\N*udkjUJ7A3pVY0M2$6^nl2;_L<($cd2,dPo.p'-B=>cRN[ + B"GgCNd0t847Pj`-b-EX@bmn8]8hG7+=mugeU?):djAA7/Tp$$rTMV;ajAX`EZA2A5u'7=@ + IU?70G/*"U]CnE6la?5*s8fPo8Zcl$'c + 7Fn!XFnjACcTFaerab"]nm[kU=UuSfI`r'NS&6SpXGlo(G_2p_smT%#egS + 3!9:"@L[$6ebI*jTclt5d`^?6o.n@X@,mt73KGV6tBH>[1?+$&4N]4d/=PR/J:oD&^a;d;, + n<->o-@T(f=i^;10>0N>b[-)c=iudG5d>jW.3o>Xd0d,u?gZ&4;;U?TG2qa,G2^Cj!ce-ok + +\`b]]oWe8>+BN!)+7cc6loe/f%0N.9lVSmFP/M0GW.Ru'J-9QD!BSj98Qd^n;\Xbc0bF;# + (4?:o=;qmqU&sG@%J13j]a:kCL/;$naKK=d?\r;A7HD!=2XiQ^0aI8nd$Db9S9M"8>4#kL, + "1B\dOun>\4.+_`X]%=+<(U>^]BkqJ"2IL)=@pJ74?Zjue&>>!a&U9t%ti"s,K[U!bgm2u8 + mJ0nS>X%0A'0\R]e*hgX4:6uXK=;29&%t?oWVB/YtHV&/Yoa*e?r?T[TcIJVXSF/^,Ub`_Sg0>miCa[q^bHm*d:L#AFFS=%54\q%4 + SXfQ&]J@gV6Vl0!gDp8NFT>5Y!AoK4>?UM=;bg.p\57Q;2'iRUMcA'16:<+.d)/s^j',CZ^ + esd@;=25*%Dpm@_d#pS>[>8&D8fb&`a!r6>`Hm2L!*_lbM95D>i!Y0lqUFf+l*K'dOc]#SL + !#S-)H!5;J]f30T8qQ.:%aadnSOoZ=Qrl/`"D%;UmG?rA3:.gti2j?*-!?Y^tVajALEX?"M + R7A`m>[N76!t;q4(\jF#GSIaeIne/P24I"KjAK?-2F?Q5tDF[nu[U/[s\aKr0Oc1]ZBNk8" + ]?^nLG/Q"TWPK?J0.j\2&lb#fp.\X4D=>/NmiLS(XoRgK)")iEP*gm;L0@i6,tW/hS&+4`\8*F?Y+q/r[I%l + fm#C^XnHjiYbD'rHla5nZQ7F'lG?lD1=40JP;>[YLL^\AV7b3//GSi5K!F8jbH(nLd2]2fS + tbR7Pe/l1dHEoj'2nXQ8_O03)-Ff^u^_6Fd'W>Pee'kfi&nd(-bP>(!]/ic;`q/[-&@XSE* + 8EZ@(oQhFC>STird7)g.q$p.I_HVDB6j:HhN0I.Z-f3SLhkg;GSsgHSKAYZh2sgEcciD6h' + %Zs,u-:chA*Fgt7FpY2!>:kcLBm0[G&6egG1g\&isc=:H=A6eh=k!!?gDJ!L0A`f"cAsR+B + Snhr@ChBP]B"i-Ec>I7m(#9&@>ntdoQ0;P&-/NN^-,ntOI43rCMI%WT?8^\SXl/BUh<2e); + au(GS`8IkGN]LfB5QcW[5qSf4%H0%?CHj[[YIB6IT-`*e.k;L,_BA46n+u'87J1?Kh(A%\m + Ih-<1W^3ZDHH,pXJ>j?^-Z:CUR4QQrPG12,>g&6A;CBY#*!A43r>VjYZgF4g.3ollI-'ARe264? + .6W[,n+cmi;PcIU#]\\aN8)AbE^j_cC@`7,.)%kSAD&-@YI`J%.+ZE?9Mt/pc^T'QF7 + skQ_kn9fNF0JuG6i\X%oZ^BsGEL>K*S?G;5%eqpsrMVdZqkg(GP,2khBJ3moL(0&1(UcRn:Y)q2 + uql`G7F(\%T8?%nIH@[l=09.LO%dNH1a#`n!o3;S+27B\*oaK7!\!/QenNdESPqJ_NXNg5i + 1\+80b!P/b*jO,KOdLHQJ7=D=qplp2ZCVBjW?1[[.FV/TTGq8OkEChOFDVSQ09nT>3[.5o54fu7l[SG3_D=mF1/&hc44pX%n``aeon0(01+VcYNl-V9-W'JG=P^=[.K:9lX*7 + m_rZ!ZF`c1VCTP8BoY=P*]WVeibVrF$hc!3]<:r_uF_=c?O&u;[=YL#5@c`>Rq/L5B1%j&E + p#V(`][d"2?s)=N9oKGQ/D=qfdm3A5A&B4.G!55GiT(H8p9$`![@>Hb7<[2FD<=@LNo8V=E + UGCHjNmZu-Znc\2=>AFg81k76e;/!lH0,fa#,> + mQ[c2SrLaJ=uE1l5VGJ'-lYa`PO2/6Ao[o3&`Z?EfpF-Zdo9\R.Xrd&<1T2("'[=$Pr6l>8 + _];5UB/O8?7nY?A#/lKYc]rkW'Jf^.0n$E7#)[90PG]7%]TF1>L.='NZ#ole&UDn!@P#6BI + liG_n8-6EtZ`aU[T]RDd_PK@BV3II]6/Xamro@^5MG=/"MBloel*iD`4e8"[1h1i7IYM&8c + Rsha.CV/A?2d>,Cs+S2HE-T#Z2^aa2j7@P$mUnb\dHYg7h*/STZ.nYiHThAkK>$77Gl6DO8 + !h]fom;+SeI$lc!;_+mhg`]0p%:PBOQV2[J%uU0!s-3a/.hq*kDTKI6#^WYHU2V5isJ-"S1 + o4T:so:Q6dXUS%`;ql3t`T31f/[nE?nU#7?Mt'(r`bM@2_U4J`qJ'n]Z1t8!3^t+NLc-JKd + W_/Bh,'?i0>57pTV/9Jh(#rPS + ^pN,FBUSjrc?cd[B")tj + "i?_Jnc-Ms4@^.+nXMnGaDsfHWc%Bt[k#Yn7\R>Ao:5P:ch(KR3be6".CO_pI8%Q;.Bn;j*`&-aH(IU!CS1TeVs/N!^TH`<`j,R2qn + D7,nGF@Grau&"2p,n65:NT*IE#pSp`KO&l1k7uq3Gap+4p,_s3LB;N1m%3i"Q$DQi:J9:iuD\uGZ+qm<%Ss6VAG<`N&+n + ,1kou#*"5]35N1*SLN)r06hkgZ#$Cn]OW#F^Ga/nN@/h'R.ORXcL,*4[YB?a/k,:0Q]h,MbR&qTAdlU"io"-:H':!1<6^hp0O)bhU-Ie-[hj*`CX$ + Rt5K0kbk:?f_[VchVBioadcH-TaPeTpHPL"+@TKbB0#q*6+_9!NL9fAGbU(cXB']jOYI3f5->N!_lcO4MWQM + bTRc8p`!e[nh]g4$b%rB`nO\s]_81@bbVCgMl*NF#7W`;NfXrYJ^9"s>d)j%)Mijr1b53FI + CIrpu6eQB:dA_iW?ZnC@d];odb?5p_*f,`sXf9p_5-/uOT@dq(3aa,U/pO(TXr*PC#J_usd + ^mJ^3@JBGNN[t7NE$H::b]ZB5lp+:'^P8H2)^tY>Z2/TkV\i1DahD+&;Dn,b_\4hS6g^B-^Gon7jV]t4BS! + hCD@EU\j6'Ur+;KU3`e2ls'lWn;2G)jDoAf#RW?5p](fp=J,B9_s.G&;N.2=*J2jd]YPid: + LY4$DcST&2Wp)7fS:N84R6hlH@qO59k$bnU3M>QrO%01\@\)$G&W'[i(FAAPB*#9_M=e'Ns + `_JM^Kocu,CON1,^K3eTQP)0QZ\\-8.-m8(Y2Zfmf?nan`Z9aMitQ6f&XkSs + h3B"UT$>rNb!8JQV6janL>('3Ce3e0,ducuP1tFZ8c?UU[j7Z3H_+9\c.+@(A?qNU^0L+5G + ;!P*`no/\Hq`dk^A$.%R^5V&n/8\nV[is$';'qQ8_LuD>k+BVb'NRqkrn4?$D/!kIM"1i5R + =P6TO`,*:guZ1k[,X0"HnQS3ZlMCR6W.t^5GjKAOag"+=`V*S'k3J8NGTR/JP+TdX#%u\g: + mgI353UNfDJ5B1qLF/#[RHUXT(+ + m5Y!4rr:eb9:JFQ(<-uA`?q'PNQ8%V0Z33lb21_lS!VFI;btU?T7A*ILGasQuD*%$qZA+Q8 + *u@Adi4"^BNR#@>+ZP'P;T(Sc=cCbcq7>s/\in$q+g2;ZMbQCJ8!Q:_ITGBZBN\!K;&;7e6 + [TH>h.[Dlec9/;eH6OTTE_aep9(#;jQhW-"+m"O+%Q%'n'3?HZ$U"jFo/0Of4Ld!1Mq#-?> + [%+rhfT$=A#HS"=bm.,OcKRB]Cc_o@?kM4EY%1/"%$-1cI/K"mlU=%$ldO5t@%4qrY,_GD= + du_:rBL<2&51E+qKWRk#OOAjpf%#a\L@&0#9jnM`>h6uc%]si19mV^,GhNN_# + 2(/ONKFY&aR9]q(>=THg2S+daUm2U)\RuP'$\4U9u>[;b1p\rh!%FrEC/J97$]Q"QVHQV$$ + +n)khdu[QqimYQ)p0r!&]3\UGoVidkRl+::4DDEeEsNq*Kf4GOl7>.iuX? + G*ms'k&aLMnmiPF(+-HRFEW[E3"X"E_+OUW@&d]^Z%j>s,+cbFI1*SqmqX0D@:QJ-n$K1AF + m-g)u6SCFjE^K@N#kF#g,go;hcQ_o-<:U0Y,nDRRnkMXe7jaBan1-pPcTY'A6LO;6-dP]!& + qKWc="&bFp%"'NP*/`oFS;bepCa(-cZ&VsI4B$"\dsDel_aHRJGA]Hq+J0.d_]1N`;/uHq9 + Pf3MpX+Le@kG,qX:dtbN/P`k58-B@1UI5"r>!md(l^p0?-,j&)Ap/6N0Y"rj**O9J6>22ZG + QMs77:Fcil0u9PH0f`Gc6$B"Kiq'ZMgB`m>S[%!6B.707EQ!fVZNE&)CZ4#N*Zacg&QE(;f + [UBI[!2Q'DA!eBmRdSR*p/Lq3g0+4m@$nX[PBL`KU9aqP(eI^CCnD&bX@h*SFeaW#l#`@4IpCC*qM + X^Vt/Vr%rMV/H"5fhCckhQhFT0[H3&[ + RF,'kr(2M2)hFK,.Ej(+H(L&Sjj5E1?BA7aZOQEIes%*1iCA(nkX&OcLAT17uiSTJV\iDFE + P\9c@!#)YBUJJom`L\Oqlp*""D?FL!C!HV9LC*APq^6J!5u),-269[XkS'['3-cV7Bn9pFO + 0FQ6*Fj\?NQ:=ST*'^8Cmmn\&s:R(lTeH6i_+X0nn511l.14pf,HVoqa[*j"n&k+.j5-qEu + YYnn.&nP!>@oJXK;meP_eO&j".jhYM<5d6`00L\9*Dpr_5rB<"eRJ37MY5DZ;D2T1De?K,U + K2/FT7EO/FRMde_Lp/^***1?SJ^kkm0CRRJ//;G[;)$>\?,fr#9J&!,k5T%/8t#22,dEUdp0uC@g!?3"=Gu`cqS"(;`@`A?kG(A.idIY)'Zc-TK+O_*a$k58I'@As[7m30-4n.3@&E++" + V[egUC<^LMZhFtapHPd0&+g*C'G!I6#=1'\6'3#se/q!K,ndR3Ojkrr;)4>72a^c%:n'Z<\no&nU.8n\f<)l/2(XIM + 06%,*JZWGCLeL+>W6[TVIHagI3mn8!$pF'SdndooDj>0jH*dbnI#]7cHaK[T6m=Ao(`"^I,;0Gn-q("W@AfYTU_r + c8f\\8oKPnM1M9?[.&UZS=5Z;d_Kg!B(=B((iBH'f;fZ@?XWI&2doq>@ME<@ed-:;f;g1E< + R#"Mpm't(.DaiM2[4N3:M%qPEBFb=U6^:NRhNMF/'%$k@W:dNm//KPI,hdX(W'NBJ?o/InC67I?2R(bu[+A?jCQSK[0`s[A`KQMtflS0Le/`Bd7m\X:Ph+Z + -[c#okj$NJ'giPU5(@r5@o\Ga%QIJUbPZ&=E?#[?$E;'l_1hCZ@d?p7>EUFW`1j;IX-ZZ@A + Epbh0FH#9>2&\Dd"UDlfWFM`9=J8%):G@'`HjQ!H5YUc\`'+4j + .;\%T%W4t)=*nTn"3b[T:,_OH39en"eZb2ksP(_ElPkf@AK,()47Ce&\3;U%dWQ!bJ;WV,*[tPYr0/urbGZaoEc%ZtZ0R.)^P7`Q"jfFX + `LK3MmYQ'Cnq#GYnXj])/('QLK_hg8\M/XVpZVP#@pPLb,@QOMko3mOg7q4UgMi^qsp-4KQ + `AfbMA?=7=dugq+ToG8(3Hea8R,pAJOcHcMNqRV&&?.bDrNqsnO;S4`R12;5bE2Fe%7jceG + ,!N4*O^cZCAB*;PPLC5s(g:DCbuH^YmYBVca\f$Pa9g\=\inHQP0#NDG@m2e2N[/n!6p>h7 + !-FmH6nPFbU!SQBBn@bIEe(SI9;T\LF$r#NWo-c0+=_E9KgS2%n91W,_A_q[,f*'8I + 6je4W3&&\@BQa'Cho>Q@tU,Tc7\kK;28YX/NUc6Fi)EFSjGK3>cUHEMe*,3K!A]Td"Ie?j_ + \lGGoQM"EcV76QK'm%Mn@k#!+b0OpU>dIO9H\VtH+L:Q9t;Mcm*oK07J4Ff3J^fn:@"> + 8Tk7=A,[%]YB`L0,"#[G[4SZ4fe/X0Y5<+G]Bh@#@>sPd+[#W]%;aahSk[:CqUpHg?YoQnA + i-3YEMfc(*SR+2TYS@@bj7Y(q$TlVdkp9eCHpE2FEFM+4Z=pAK*".37kIV[>jleN_kdsQ>: + +P4c^OLB:/L-qb*"A`mWFt$eYcm=UVX,?eLY%[PQou1[VWWn$`1q%i]_Ue,^*7Ir'PF5l6> + $)iE=\O[9MX\Pt"pgRU\IkAIOd'D+GRIH$PukIVc#]2o$FXn01!$Po'@]R.o9]?e.[I@1lp + E`n`s["Xqi(E&PqRXQO.;oLdr`OoPTr/7;9kNSLP`r(>O)E + l,IUW=LJo3?(PABVtV[R-LFaZdJY>a]p/Lpjm'db?,Oj*4sCZNIiptJpb + Ujopa2EP:[d+WLVeDr8.6><&0\N>>O"SS._% + 0oc-MArr>cHU"k3p%aC9fFu)7(5LP+b(CX34Zc`-)dPV_T95G9g30N[=X)G\V/(/N%pcT,? + dmcW#h`&egfDbVS]P`>d?Qe_\bTC>1c:R7n_R=@Dk[6C?bbMq8/rBP\R.S[:rVc^+M!9GTB + h3l<\G5I7cR@B]O!f%>H[$b8K=$m[Tu,Z;T6kk@()EGTBfX-g1rWP9kS6Em-SLPTg.+dH6j + GB[\?ZcFNS\(Ro#t6C)'1.;.Wk35O12GV0]q^=hj<(9-'>GB.&Q=5\%B/1)GJL/t@Ipq1K#*KeEQ'oL'2="fl`B(K0WqQ3hkd-*;i>pb\5Wdj;T + qP@N1@BJ3up3RgWhefpXme7[Ff(Xa1iGZ5r*\S%`alIL;r8(L:REt,>HCYPT:Tr=8G5GS%c + [WL`ms'5O5<"MJjn/BI?JfD1fCAT_^)oSKfCAM3IK+ehj8T'D0C/oD5`cZf"@2HU%0f*PC_ + m=C0SCeY_Q]7V$:=731Ccb`oJ[;uYlmQ/7'R.r&k*Bh;\hf>WBsE&!!HpQ7^9==*(M`Vr'6 + A,ETVbAa/">2a@H'`+it?ZP9rkAFHn7GZ;KP+TCSe[.a%>3ZS"l`oX?Ip19AEDb`am'HVCb + ;j#,)f<8TT/'6N5Z9f&dK43o^ipH268eGDL,oG$Y=cP.hi6d[_J(IV,blb$FLtVton9@oamt^8>?qL!(B)-k>^&'DJ"9)`drjJJA`%]<2f + 7,fhNtMHFIi0FomGY@p%ic;=:,"YSHjl( + lC&b*$-Usf/l<:'fE,'^@s/kUudEet3#6qt^m$fa0ZL&SSc*V>Bp>*5#qgRY:mX(A5EhE6K + ot9a?pA;WIk2FFuE1A!X`,PW7[W.r;B-#"^r0JYN+6M/obOW[i29m.Gh`QCX-P&SloN`h?d + rdekZ.8M[!WHnXrJtC?FlpGUg`P[h^YO+)hg=9"`ps)fGb-"8jEUYAX6'.hl2GGnrfoDO0662a + %lr\Oht"b?(\"@05`295V(Jab]+5r^.o8,1@Jcs@-sLc#EFj;]&AAe:$nRPeM!-A8UbC-nW + n1Eg!UKs89/6PsJl`gHd\d,H"^R!B2#nGa@n8g$MFPVchP7Yn;j,n-0#'*nG1M@h[Y7/3W7 + ,-4ifiui!RR(!j*i;XYaDD9'9`&".kAf1'42%T5:)5D"a)6q%=$u"ZA#Pc7UOkr4@M"Mj"j + Eqes/:Y_Y#9]ffLQKpi@>po[,33R!(h4m^aXJG[jKQLa;IG!5+d",4m$s9fKODpT7R,ucVj + )fT;bEa60o,\/Q1DLg90$Q6L%rV/o%mWpM01I_jL?#D&:_nALG4GO_jGKhA'C@.04mVt+6) + p$9c9icVPMf[P>5qTq6r:pd(u38J6e*7s#B0mjIF\pJW$fE4%M3s+VVA?cS?8)-a==.jE-@ + ,Z8k,TnK9([RXl*':.o7T1SFriS'\Hd6V6q\-tNOVd5$SWkcn!["[7k[@^]?Ooc5RBb)hnZ + dTNP,,qPn:\5Kn;>Kb8E0D*2sQa7<5BsEJkk"*PA\o_G@iL-VYr6-(#7o;u<7PtCKefrUD; + efl>7FaGteI&-QC8G"3F_7[SWj,Uioj+X6Ej8X@a&m+'B<9X\ZIZV7>:W]jXqe[]"_"ph)2`c(=h#B$^U.^HaL:aP42e%K(1Kon@dH@k?jRPIkXn+^mIjb@@+fe + Z"rTOn!bD/Qfe3QL/uH"2?1G8_3_IYCs?oT1hKG[Sm]2GJsn;&Bc'.4N[C(]Rb\7M_KB + "Z_6efS]fKsA(f8D8&>CMgSX]/Ntsh&GK0Dtq/a\$m(1Z*T;,mOcSl2^+5eH^Vqb@F+!n3p + ,6sW-#Qre%3S-lWpA`G'QO3]6Aeoo!?&SO!Wgt]X=&sA'fMI;_^EoVY?\=B?\\9nn,ANqQk + 8,IG)U8k;V^t+-ZbTRH=J3Vum%X*!st'/d4O3,GB3)&;0Ij77$;W#kW3_Kb0snN85iiiJ*] + Id*<]MXZ<9;bX%&2L*Z%DOf;f;:":/R.Vc5P(*^+mMqZ0'`[tmB,d<&sdr\'@EZ@>7jD%bn5,EYi4P#StL)Emp#,6LTkqc^Y*r + MB'c(\[E:4M4LMNe(`WN=mVU/A( + I`#n/$&,Va1f08#4CZ2/h`6^TJXX(j3!i-I7P=.hi3`[Qk3:0jFc7BtLX?"L4 + .15[t_J@idmf*Ls/QZ7H?R"!bV!,r.P/5iLG&KF]i6&=p-Y19;*Dl/a\]'JfS$ZO8N0X"cD + %n3Mp#T,T\pKH$P2*1r-N6/t'`g`Nlf!jhJa1^cUBZU;Sj$T(LG6=LKL'ITS:.(#]a1r:.t + X=KuK:EfWf2%h)j1K#E$<2,:N2/?5ME[nJd=@5\8_dYL2bVu;=329ti[K8HI'4Skt418$T[ + S/kCC.fhR5dl8=6mHKUUdHFqD+%9#[d6Jm*(RIN=>r?6[n9pb@Uh?CAiFdV7,hl,Zqn03Hp + n]sL#R&?.tG,$<&bhJ3&F[G."nLuHFR.*LkCK%Z@=8&O[[?T`SuHI1Q/cSKScL5W+1iInqX + /jO9u)P\L_?ke7Z<_WjuAKW;LBZ)k"6oPRC]b`oENr_js:!RE#u5.D)4m0YsaSU;p0)\jU_ + A"/\S?U^[^0NEa'9Zb&`>]LTnga4,RYb^[[oJ4K6^aX1kA1cK#gWNooUEMYe + k)C!akW:I1m^dETY4:DaY]`p,a(8?g.G4O]tC5UZKXO,h7a#k^%lQ[,N28+m644G^-_EE1Z + up_otH97YCVC#1AO,VqTDHd54#_liJbMc\?H&:9;.Y947\f?oe9nKYZpi?1r$ea^qZB7^U1 + kmb7Hner\4f\9On,g4NG5o"gO$.S.#WrZQ-6_#dO".*3m^]oF!E"$c(9>Z9ot`1_.>!eA2h + 3c!fMG4;?%kik]+Z_0d%T6lA_;gVK]AT3U6ZH1f#'*]'nVZb&Cr1a#+'![(J(_C>Z)6UO16 + -F@#(_HTOIP=VBX.Dj"R1s8Y2h!`P\/tu(b=3ji&6WEcs@PY@02a`d7[JDs\Z + XC3e43u>;_q[H%o4=_MB1)i8,cjhnb7>lmhTSc1MLF;,=32IP'Ya.`\N5d!1dH"e,6]T/YLrN + i!7WXd5J?-\T,Y"m^MSs;Me8-QhQH&6\7gbVR)Oa/J/@l737pL/8gSE9ub=@JeW\f#mb_sQ + =1t+lNdlfV<@e0DCmglBlM)QZeH8JAp> + F4j/Q_`_h28N`9\*D;ZVTKIhOalQ:>Ptt!=:t5_HeGW:q'@Kk99iSN#aq[NKF\'3)V:98A8 + orcN]A5)uW\1\bY,gO(`F#e?Y-/PB5$Y;*F^r.LV5g7ZliR^ds.36kd_&V%B1_`Z20-4m_9r3`R/DS(22*n>#:&U*EKoaM)iD(rb=X<* + (9]j2;kZLmJcD9^XEY:]k7o$uOfqe&]ga^2&S>!Pk=l_53*ETYhs&_^tK,CKVkZpU.@f + :Q&LSSGLrm4O;9Ochot&lp%pi!a=#H[[dQTo`dfX7Q12NgEKiiXg?B>$TDW*>@^LUge^i/& + CC>P;&iP4/J?$.'[^*.\4V%cS\NR;^o.*9;6u.:,[\sK@7DeWdK>NL:ngE_MGb`FdQ<^l;j + OBSCUnj@guN=lodM=oSC5&i7m/G_4YL).U!jEn7soS\,GqJ@6IZb'h3H^D*S]DHgg.>ChnC:6 + 2@5W[:\?Jf[tp)ZJ=-X[O7ZSN+.u,ZX + b$"nak$7*1sp+Z`G6Xo]+U^Pg3_B:5nAC-'li>%aMJM6Bb$eju:u+/C+Kj:DEA'">X-n-s@ + r\UOD!o!#D`'"8l?*H_>#>g.L`-hu'nmEe + &:scpRd*th;mShl+<5h%$d3ZD9Ect;K'jK0i#\=e!H\Kk->g]'Igbg#*731Lo`Pt'\gkK:: + Y%*>XajN(YW8))bA85"XQRO1rk+e>M#&B.!._4tah)3Wl';B+AUITIWWZB\i)PkEUk%@]Uk + @Nf=G'2tsG3=U*kE^GWNdKrdHtTm8kN73Up3gTYIcq0Q&WQ!f0L!,Qr@VC@?aAbMX[ujULr + hOL?M996"]XAjp0Uj[hbK3h/r]#6OQ`R(hiufGVF8AS133$!)ceeuf?\SdW:A\Zd'c5;oCsm;L20EA3Cb^Q&rc48Ze,'[/^`@:aZ + ,Af*7'(pRrsf=MjC^)Ok2*6ctT1)/(Tjrho"%H8-Il_T=$Zk1&#,Y`G/=H/0sFP?'ah,qeg + @]MB=p+,.J/aQK#@f.>.%Pd='k@6-dj%_F+XdIe28OB8CCuk*GUJ9G/TC!7:cTXHY2EpLro + je!Og0FD`-)X,%6e[PgmA0_b[bKN98+"elA<'7BXfpg"i7f#A7#"Gq(/N;K:cD[gd*$[Pp/ + Fl^tJjjE5`/S=t2k6>rsG&uh'H/4L&O5Nq?.!u18;$W^!n=L#+V[N"'%.LG + 0n=W_a3.9uhJT2J;nKrhW2\GRE'(JI,nQ-Lr:D`P6(iaa:nY[8p[i#d,K'5'[kWhN"D>`;* + L/`lmXF?At2^@Q6MdH;#henNrDNNajh"$4JEi1AN(FAWR/A4LJo&b_XNg7ao+7g6H8sYk92 + ?7,+RaqD,efL@(p7?>[T?ARSWJUf#Ks^cDF7RDX)\KlBW#9ST:0YF>2:J9M + V`lG-BMPYtU)XlQpmn5>05Ec>-F0lYV1g?'-itF]<2TfjDQbmm4Fs7Xo2E+C^cHb:mcN_bLju^mA4Mqpu=uJW_AjL9 + Uq/(f]D3R+cVV:F_7d]&&mH8NWp>AbAYiNd.mPf=W:Ns92[;KMKjXmud[CQ%N&DOARAUV5Z + h7_pH^M`[I`PoY^gPPAOo&]dWgeAjYTInlDI$ + Y2QV",'k(*ciHIfX"rVgi\Fcj+0r99i_8LHP*VAL'FSAU6-6ZJ];F(J@@SS1amo8:g(@2Kg + onVDj//r`//9S,7fahA)B3k2tPkG!?_H1/pG@Iq4ld!gfI\tG$RNM6IBJh]rcSdsj/jEN%2 + 79U=g1+H+N;N>;E+Daa>/,)S(NWdCpr"fB`'pmV9a7G,G>O=\:o1Qf^jnZ$bW$:L#6fU\@$ + %<[$6'L^_kJgWj>ne*Mah07(u,"4^lM#2neiY=IiafVV)#OXW`q$@/m$BpPgX0S?HjiRM:D + 3;]RS8k1^Xs5M*P:n.1h1DuC<^bt[B/"2p(bPqb-eS/S`65FjfFP=kt_iMq.'-Ge^?KlRX(ls+J;JesLhg`Ff.jA23m"VGA8;=[2KqXNk`IS]n.c + 0DuZM7^#p.2*-^qrEQPXCc2)1VU'+``-Mo=XrGoT7VXoHZ/o=>r+Q + T8$FJ+`X9KjSo7g[KE74n90j`W5XWI9s[$\(5LWWVg*es3o$_qgO"/^W5sn>R_bf\U&&XM44V5Po*).#6&M-io`DLGS(WCBQ>o? + *DS8B1b&YsOga)T=K+32eP.\1RbEBfW/^*_ + O_(j/4/Aj9fP=/MLPQuis2pN(m!'ZJI3SJa#o=XZW:If-^4O(3/H0iioL]'N;05eU + 8'pFq(J,XsV(@+eoT_4+;cN-E:XaFq4],/n*B\QfVu9ImP5P+8B9mGN$&d+,JpDRF- + h"kPd5Y*SAq)Nap`P5]btr+oda;)(npQj_$=%J*#@c\T'a,W-U..8S,:e5J+*:Fr,Zfs+hbd5#QG=REBISDDOdX)+j"aeT.S4W#PklMF'Rlec + L`+V7Euib)?8.WrARU(oj);2D04TbrbS)PnU=sf"SkYq&L;?_3Y)?Y^+g*!R&huuJp=RFM# + rcf+GiDjL*d2_BTsj$ig5ZZ%"jYu3KRb8"A0;;a]qj^7T"tQSE@ArIi@JNs7lS*s6KR\hmo + h,+916pru^k6J.N-=NE>oVTIAWI0J$tSW#@n?T\P,18dN6m/HoP#&fP_(%1TTA1uS!KKCFU + +%4FjUa8"(%:o4uEW*'MJLtNm>`YlZBBas%.1)^_h*Y7e62Bh]XL[bQm%.UJbi\X2^nKoSN + \;iA"YlBRm8gqYZ/YA?V:S_HBN7Be:E/s-GM-A]q'"0.$,f(h5%(9L4CgAe` + =Ro-S&X.?QEApqdm^7VU*rWAV[(e!hT]D9^b@1$7QT(c-\M$t9/rB0>=I'C+_rr],/ok:WqQ[0Js9We + 5HrVbZM0M-8`m`.\('bEM#%P:8o,;93'5B=Z*(\gRD,Q=3[GS2mn[N<[3iKlU>GGa&)!Ng= + UKWdFq+2Pk1e&m!DNZnkE-O1tU7%?`W:!#GmD+18.;lk'WHG;K-f%%Ulf+d@):TBm>[N2,$]onN0lgD0HaR$sMO13H"65*b + 2YN3&1;ZaA)g\\/,E0niMiQ:,=4Vk6U2)VGYJR6.*7[4dFh*XE?OA/NS_F3Wsu!'`hht#(T + qe!O)n3?o$Hugt[5V5R7B5S.pS5NY6rBBapd.8;UbW77QnEIkV3/kZnJoTLOcgn2j*)a;"n + e]G/dj=q/=C3=,8q,1BA_'12J"Vr;GBLCi'8C->&siUf0e>-9&;31CPgL9K.t?7K_rBb]Rp + \VlStB'[OV1Ljs1N>i+37nD[Fp,$iOHS.G%/Y+#-KSS8Z)eqpKPI738fohhB95Vd0\)bc&P + Zs/E8o;g&j_j=J;VkREfkn?u#4\&Wc=66FLGL8`q3b/GISG*i^ZMWOi)=]Jc.U&r-Z&&+;m + V2'l0gb0K_FC=k'`>JrPb]Q$mL(:K+U-W7QCVXTnIHB&m?PPW1PK4`4qUYZ=6PM]cJQ.G** + JfCX))s3Mc;GWD\UUl<#0sNE)Tga7;9Q()b-ddDh;,,CECcAD2qNBICb9RVn/`(kG[CX5 + ]I/L)?73a;Nud6XIuX"?:W[lh,N\j$R.B*Hc?[/fk.M"joKIcPH='8.r>HK\?2$;4HfVaik + HV^$s4T(%M='apj7cW1hHfSsGV4-mpAXgFr#_!gra3Y?^;%ki?a',,!5ocRXm>sM`H$;%j8JG=q7elCrdV?P^[M6FX7l&5+L2niG#jee4muZ$Lk8#^`EP"F9d>R%=m@J-Zg/uS/S*dd-(b_9cX[V*mtVTdVU,TYiLIs1 + CfC+%d6O7/NjV=9SY2m&'[r_/QC9&=OkK_ehIZcBX64Z(5jbdN"(+H%=Zs3KbC?&Nm3b$&M + Vmorn8rqg1_2RM$huD(m5J)P7>,=/\mR,7hr/IP\Eo/.88Ph>o'D;h2U;..<+.'jHqB:hXQ + ,`c1^$(qO/"Ti%=mF$,#2YR,H*'iN]'7M9t6ZW8["%ifUI;M?)Yq%c]kkj0?D\.MSRO7c_t + @U#Ji/8hfAL0V`Mpk&+]WMFQaUk+pDkk:V)DJun/JpneZ$k_`oJZ39CVC?\?4l([&!_X?\e + (ba15lI$g@.[]OB.-o0olgDWiDT8_pb,\:eX3s9#Y2:LBipLh\XmY^*.ba>ZId`B&4jO'"0 + 23ch;'p=#-F.Vo^9*If%`K49CB$iMU?Ns><#*X#Hcu@'iG]6T-aY8>Q'81?ZM$R%=Ec@,_ + e!+q?[!%#sI%aRF+D(j6nN+"X4b*_:?jHseC^6lP&:mdr!L/4Fq[H(<4RF'sYi.`^,1=rr% + WL_t0_#Zpk_7C\&+f:FOTc;[q%1pc5OE!\+o + mF"D(cKS2H:F/n9-"F2+MWR%Lmj9fd.J5Dnoee4a2cDRo;tW:Q2*4c[5ua+<<4,S&oi4uA= + ]I=`,er+qg'tR5 + rBBUDLTppm[Zs)i'5=f[C%3%kZjf;5,`Ij7C@iB1;)6f:38&:6g,(['0h.:bY*(]f6l+/bE + E^\5)0Z(MoRBCj#oYE:89YX);6rhK\Do1t*& + O9[/j>u<.--C`jRfULPd;Bf2!,D/F7d9iQUMt`G>+$F+0L;kil?1^#3ca-VXb6@,4?[sk95 + _>:9LdE.[KB7pEHW,:\$.)6VhrL#41nH:sq^.(R682)'lBMmeqWN:<`Np^(U[; + 1[sL'EHhY4$2nOH]_0hTgI(.LNpXOZCfmPg6I/D`[Hq>IAYS212lUK3/9^=I9*_ + 1m3+s"kFCm(kjn,*^CqaO9d-B@,jq;s)RFLHd\TNU9;%BSV + UkQU!Hs5PCk5.(PCP[+BK%6$*o/f`a\X[udQMCUUuEp;KCBbA=OKD"1Ac/]3`k>str*h"KV + o0lLro=Vif>PNdF4&VW]ZMf5tF7ood_ELr\E%@+2r8?o48(Dh(uQ#ZGdT[tP`(FpL"0',#J + F.%P(oXlc4pIu`/c#l%;9fH?PFPQ,&hbGYPG-W-4'_(D>G>D38GG?C-'`9dHM + 0R^tm1.a=2j5M0'(-R*SbJmVcL"M)pJR1AHM?NLq=U@?q0mV`T@s+6q?Za-oH?Csmd9Tq(K[:XRfMu5p>@'uFL&-mBph)FB4bgB=L@\8tFpBn5$\ur + 7@5DVR;f$=gM.-1?M--$PR%7^5JVk;_sABblhFGj17`m"m^pC(VJIOX%/R'9G,WOEh:%D3[H=]gERO'K\TsP + Wk706UVVYP*XchfeIA!D/NQ`7G0tEfg;(Z7rNSF7d6Y8oQ(mLH'MN8\*ainf*dm+Ln>BD)\ + KQi1mm<)+dX'h*/Yc'[&KDd2bQ/d9?q7>&aJOYa1J?QFUiss!jq0ueXA+RVXBjq8pd$(fVh + d:G<*3XRD6INUNef>,?`27[0`C*Oe'&83*G2c[3*20kB^[$HEue-)C<4G,Jgr3_;#pEr$B3 + B.Dd&V_L+#R]J77F<)NDmO>D>8B"$2cU/3Bi]qhGSQW + a*B?\uUV_ZSltEX*BP2cu4aV-&FUL#C=LnCnD!CUH$uT3@UI<+C56pDo[8(0"UGR%3Md%#s + J.^N=@V%HO%rCh9iZkeNG(\I3aUf)en%I5^OSt&Ou3KZ$\O`nVVR2n + Zj-+)7TP1JBO&SI;%f9;bB[U!khqe4j;p"-fsfD/4OH[WB8J[f!F\@3-]c7>3@01gMYQM2\ + l;9MRYV/mG0jh%R7[(18TZ^C^)R5+Bi,,]Q&`H=8aFd](Kq58BmiHBLNW#'BAFRLDTIGVNP + ^=gJ%0$/@D4hmQd^]"3lQ^s]f$(`BJ^rkc1Wp=uKj2>7E`',?^hBV"Y-/uWfir1=c4tYf$1 + #m^Gj1[h>Sje*U8M:)=&)e%r]Mlm)nY:J7J(d$a4CdMrLW5cP]R_:Zr;Yr/H%EB*j`gjq\L"WY/+gMdesfm-^Js&(A%";cf@t5.[f+iA + LUYc]87-@UrK3C+B=V<;QHhK^\T;dgb=]%cF"u5"+-bl6YM5(K]2qM7On$'5bM8Fa+d58s.tu_7l>V,9?I7K3NU?qg!Pei,VP;rX4$Y&,rAc_%PqI?:3cP8,n):+: + >%CXp"rZ_-mn"<#QV\L;Mos(kbX+'o>AROa,Af3>dfHj71e*N^Y7UalJVA1o=D+QV++ffo3 + XV24@8%F.'E#EBBdgBG8N&reEe6;`c7BY0X[+ju7$[!UZTQgGi(RrtCq0g"a:P\R(XlBAt@Bd7h<[SaaG!*%_%":\qPKRLC + )-kHmrp@d>Zap#MFm#RL4*8&n5,VPaf/p@O#QDn\Z.j*gqd%51FhQSD2LGXbu,gqq`<2M>ldbH$oU08$CJ1o_\T)T0LHto_U`L^P>VNq> + (%2&&\Ht^ec31!Pl`D&HtG_q9K./ctjd16852Y$UD`eg4.!ogbfR/nFT'X`+LOOJ&JTS9b^ + $9<*L'/k9Nb=`Y"?b(e2OkE?.O3q8RPIZ2B"m89,E&+NNXZOX2+?9UR2s7nf=!S2-V7J".l + SY:Rj0e@R=Q'$S.@bYoe(F3J\c_)%SP>hX\PPC2/(SC;lM3M5I+krd9*eGDF*o@^]qTC`j) + 6-uu-%n.T&iQ4Pde86:!;C-$hA:`Z`,3i*KjZ?7:lcEO=RV&8'J(1kh9iP,8R#2LJ)%Lrc#d68`fBrTfR:_leJmd4`%u"FO.?n + Ngpi[cni`Mn>M;lSOPDS=%c8Ueo-AFgK)&WoTJ!gLi:R'rYJ!\b:THkW0lEAfpk-Oe_/%As]9[L0=PTC0HXqX(o5^raD3uf!o.V3nD-NloQAEY\_AU + KOep]?IcK$jpp"QTDP5G\JY4@n6k\h!K;Ns]DhcEE-rH!?^Rbk&&duXaNM2O5uYA?^b6qSpC;@N^aYfsjhA_lV1ZjqI&gC03b:0\SB%5S)PA'8&K=NZGm(/^Mk&VR'gj9Q + bl(+$>q9JTsI*_'S5L\&-+4LRH:^RRr-o-7toHo:=XT>)N;%Op4%mj,+*(>[tL=#QtN9HE^ + 7u6;bUU5uB&j7/.M)LD?oU_:FJlbRd;(j6,;+]4T@AT3kQR5,KS>hZkVMX0!-fk"sBuN/1e + Fc5YjVFOhNC-SY'hJe\Lc3&1&d"8?VZ6]%<'W\S!C/1>JD-^u.X]qbin73ZePfRMARSU(a] + C!-d&n+p]WI(Y@+P"*Ya/b?Y*I7B/Lp7t(:AKn(%$"CqZ+.-$KPL9Z#nW@d(HGXnQpi+`ik + *a^/tQ&?q^`QYdP-UCrMcN2Kar2p+",8jeK1VKOhAm:s%B@/%(>DQ8:F,c0^]TB+4Lg1WG, + d=sHCKXX`44ernS;V8Kf;9s(hXOP&u"@_%JpZaP&3FBa*0DU$S>?$>g_>U+g3QYu3#k8/A] + ",_,cQ-EWZ39K$#lZ;K[pH!Lll^_OcFmd9H4-sr9?+[baNtpY]pP/6F6d!3*6N]I6R.d,[$ + [Xifn3,CFKO6OsI@+%-^FV)9hnC%Ych@r7\-%Op1^&?u-PJcC;$1f+&f,Fm%-%1j#GD8LKX + fSI@q\jNiI3kC=RI/=pjJ64j>r,0'-fnmNtc&H9?C$h/k(_m(@mDiN&aooASB=,$Y4%4ZH> + -s.9S-sG%&U_O#(B!d@#&NB?aSg2J2m=S9&joc5FqiB3att1Y-crH07A(r;E:0mXrk)'1$; + ;&Y\htL+'@EV\g:g3ThjP4Hk[r:)u$"-8(dTL43`.]g4E](8%#KQ=ua2+=-*_>Lf1O=).Cg + 2tXhIes]N/Qs&0)[FVmZ-e`eeH0[Td&d+b1'4XiFrBD\^!nj%p$.,[a6M?ZS7FX\>D0dOg. + )+H1e67guqDYnsG2o@pP*'X.8\/Uk>htXBG1-4jR;%((VR4T-DfkB\C^>OJ/&qL;<+,* + StJ6tQl_g'>Fo,h<2?!H@O!#shBd$GhBX#Jh<)K]'*N,2d-n+d=ueGkr?]J:I,r:W^.4eJk + :NN=Oo\0*\AG'Op8n#dHQ1__?312[B$mp>\f%6=k29 + ,XQoCa(=dj!McRUF0#:sI7>:)o*lY.jd`aBF+2Zcqg7:Kh)4`:Qd=PMf3g6PQ2863ls76o0pI + m&O?g<5m.ie*#$.>'L>t=6#77OHko#$(InF'Z[N?FBZ;[ + MjM$$Y-Uo!k(c`2FD)T*'?U#BLCNgW+0S[qeN-GCoEI:[d6>Z_f3OdN[W(k`+^JU6BS`\P. + ,O*''jg2]6LfY2)hU32o.PnqgQKlIM8K,WXMfTl[d#JJs:4#!ba)NnuREk,D(G7KRcpf+Ed + t1E/4?s7H@ank\N]C@(As?3CI@;0PeZrAI&!GLAYXQFCrO?BcDkL"E6qd81ju=2RnPXWcOo + 8*,lXNV7LJ`3dG&CK;h@6W[k-Na'i]&HFu;oGrlALa/O$Z1jhA8Z`I*4XHLZ+Mmc.a[k-:D + aYZZ`Op#I4F>/jqVJlji]Og2]ZI?>glsqsZ + oo)LY*7;I+hOo!]tIC60(HhBI%3?>b/rB/02("^_Y(UlTfr;0YERou]AcFTr]X!;8o*OZgB + 'Qk"A50L8uq6Km&V9*#X6b@0XL7@qsbc7nt5Q[0`1LI)?W3>'h_d,0k@aX_\XHL%a1@@Z)" + t9j8dBH+$_*c5VaFTquf\$,JZ\OZ=F2*P:=^L$F7Ui#Q+ucdPW7[%O6oT_=Yme"NsK_bY6g + 8bj*7N.`e4j2u9l/>(I+D#Ff1h(4$aW?^'JkeT1."Cc$p5j_oHqik + 9f22bc6?[`]IHZMj>?XQ#@Mo`cu94pkY@B+cCF2qXQYDom*=7+cID:-jR5kQn4t%mcReT^2 + "FF)mnUO\#?V)SH>S@<=,"Td:TWnYD!M<'ktfZC:ZVOOh#IZ'rD4&Jcm8cj*<]1j"OK:8:i + --X"UED5rIt4C?Q2?TC5W7MD%cHt(QXAB&.j7YNonP\A'mCBj9Xe(m4: + jZ`q,@>=hHKFgb,i#dP;XH`'i\j]+>2+SIq8NLn!X))o>R:!P4G?!%m]'HIKoQrXt;&8MG] + ,ag$8U.=HZq'_s8/&J"*.l_!\\"tI84q5G_n=OANQ?aeX\=T%qnebj`7>of<[j`n43&6Aai + OY,4J\+jHJqFNjN1'?8Qt=@"`-bVlW2f/adehlX4>>)SNVSoY:J;JolMiqd'%6?f5.3S/)] + $;j%<2)5#^UdHN;3H!D8jGSntGk]B_Z(mQTA39(ND)%>6qlKhErf+7umnU\2f''?an\5F_J + `[>[bA&&72;=R-m@"M@kG!R('X9E`2b49Wk[Pp2_(=_u&B[)KhVa.p9VZLl:6UtOGFfid.$ + =ll2Wm@[V\Tiq&_=q.4[*MZe4Ufq'!g23ZiFf&>jWEP+-_A[;Y/Ik-AhSUqJ%fG5@"]I2"uM]D4W^s[1qY*]].3?]itZRNo^[Qgtkl`X]uG1;"_^nFi + 4gg3@EVr>\\`8<'8Q55#HaMd?PR(]59qU6d`P8`)RgTmEe7;81?)]WDt-?4A8X@9uPj6`9e + nir?oQ).$O/7IC02cG^u;k<)MXZTOq4]DU + M;uQ#\p$>W8@KpLgKB5H$O;\/X!8eXV<,880,]PkV8]N,M.qMCE9heuOB]#>)8"EFU*-s[M + ;W$;0Q[Sk7EI06k!EFPKXM%b-11FX^u1^"@'*j._V)'(Nn-p:@-Z[l, + `u,aY=Ua[4Q3Qr[$=3s\4MBl8W@W$7;u[pEabHgf/7Y5Z-LJpTtT6UiToCQPN(R.gce1O=2 + #nOFa_llIb3t:ibJckc%4$tm[mI_=A/1ggCMqJLVHiG5BVicUpo"#\4`BPbJ#bec&gT\*n_ + OJfde4>'Ep6c_qA;5A#or=L-/.HR>[?!UN@""SZ$'/5\(n8A/7Mm-:;<=6tBUkjDICsG"-" + k8S!iRA;3`fSkHD4X!ZZ(>TKo_26P^3,MV?Y>7+cm`Ni^lj4dU8>AdKrITL*:f%Z1pL$cJ5 + 5ge[F^?+*PjVC\>c;3R4:Jh6Q;$Fnc'ua$#A7f*\gjKpmKKW1Taf%AG6ju=XV9I`N)`IB8B + 'PDU"jk29+2j/e>roL^B)1o1'6\hc38`b]I + 3799piHhibQMmJ@[Lr+VV8hrRe'FFD3t!(["ii$5_h7K]#q"Zrhjl7N;+4pRJr?S,N?C2Ae + G`i)ob%5#!u<^mU#C>WG0&3RrEet*Mr:)$92CcrgTlRj"4NGlLAEQQAo4d;O0rH.#KG2_MV + CTHAc7="4BjN;%]@RK@1)AHfk\o\O01m:A%Zj\G8U``6T)nR[1?-90 + D'0EYOBD$@p`DVe];ic/kUq'F&GAfTU]1cE7+A7e7CDsh6+rQ+I3#6i13Eh3;9GGomJ<#]pOBdNr + D$Lg9BFc&%Y(GM)3+tL.kY41rQQTt%LiOPoE\<8`QAdh:,][""L9Q%.3(=&g'EM#IMrp/&" + ED"RPM][>hg&X#?C+XQqF?N@hn;kX5+D.9_aiN_C#/m3#+j@gTKAGBR"h>CAR&V`d_"qDl> + 9p:[:%Q4W7EcEC::0D?URq6El._91A5CdiqbVT!#F%8.Fjm"L5]U\7f]bB=rNm,#uhhdeI + 4Ncn_dD$h1u*!0Vj*UQ>Fj-J6US-R8=f"dm0D2"CncIt&A5?Y_^mWW_r06D)]Fa$&mpJ5sV + Is6*tG5%VRGGf_1YC(Q/I0o'"VTVM,pA4Oiphubi_G9iM556t[n)[U>G[H5NDhqWCoP[WDm + pKZ#7ec$%Jp>qfp_Na1cMV94rFS*<>[(_-#%hfiBAAgUAsj6CYF$`!#"0gCn9rXY^Bs_]EO + S@ZnA]Ngeo\jF%c);Oh1lU*L"^11gKi,>3#l+U&uR4H"KLZtnXge7(4g-H*G@;j7U>1t%j1 + ?XL\i+XBWna_:UG.L-#ungK]#]2j[9/;NeXU1hb8(T4ncIJ0Ah75l#/;WePC6Dr9:+:4"iJ + Krgc,P3,/%eX0 + FJ]gq[$b?W[.M[=f-VG.mndH;FmGQkf7(>@"aW?Z,4"A9okWaW"b&Y5=nS&2FhYf.pK_TJ? + =LXi@b/Y;UpY@\MQp29p,a,Xmq(kcB'fX=m2p%Z05>!XC]@_lG1ZJ*N5i0Ia!:RIPm0JIhg + G1-rIt1#XVq('TRf.EM>I2t!2HsJ,hfZBaDI;=00 + HTPti`qg#eirtr]@uGS]_pq_S^Q_2qUhsj2m\FVpM^$-j1iLlFSNM_:H+-XlLmI@Id^4Ih= + >Z952STBTB5p!s$+ib!<0bequhM>a(u1f5k%Gb"i8Pb+FfoZA0hRLE3(c%_bd^"J"2Ur57^ + -o!S7iMV^,%P`5h\nF[_F7>8P:VN+j+ua8*[j7kqr)*ljDZdi_1O%"f`GnhJ"'8Ff`D--=S + L_'M]$r22$e;N]9T9.'kJDp,H^s"@&$l-1p+VYnST9f#rP2,1Dfh)NWUl,nM$PI0X;cVr'_ + J")3=rBO0N%1,>Ie.hs'#=8fV7a]./(/C>h&cQU:1p&E(;\\%F:=A:D5>\!7_80!u[3\OHR + e875J(:F,^K4GnKk4GV`ps(5f/:Ni?ItJ4LRTDr/J!q_/h0<6"NTk`Ff7BWC?UspbCgWP%qg[NnH*2 + YTQo5>)iE/h^B,\hi*Je/!N[]hEa8)3[j#G%8C?$0$RVH=M(.*M=R2/S^AJnfSQC2?Qs-Mk + .L1Qh9RCW<.)Hk@/AK!SSJ"LBm)3^]VVuDaQkf/@>VAVF'Of3"!H2_>hR^T8tl@qL3Y+Oi: + SZhBA#0!m8gCDMYR)X$o[V^A/i*r+0roL_/H`lnNmmm20Aac,^qnMW*%g$1-8,j%:E98%%` + ,WG&*.O>\D^WYIL.b>+],Y%&@bmOBMf1lS2>uL]Z!K7"Jeua8ASeYqd[q(nhTHJ?g9e<\).&Ynl?K>p&(==N1'P/&d0KN + 3SO%7Zgq+a8B_4Z*H;Y(uZD;BW`HIeiUocnK-`?FqoV5J&h?e+=%YTOHLT8$Fpfo`E\:_Z8 + +iUU'Ds%8JMLs#Vi:B1gC-5D\V<-:_m2$-"S'mPZ")N8r67lARqj,d]-DAfjcPoPdQg?hHA + $iD(_[']9gt>?q\Uq0BO7jR$/S$9UUeok![F];`8;_q5#mEUsYX;:2YDVQ4XKrH*U1pncID6PPJ$7Q3TP90RFIhJ8EH>iR6Kf`rLYnFIS^@KDU&:"S9'MHS=0OE + -`miLPWjGb;n#0XFVLC1Wf0\ocUR-K*Bk0bpX0`CI8@g-UstA)dZb)i.:NY + K9g0if"0RB%NP:%b*a;hmg>*Zj;f.F+19=!2 + Rp$02lV6aKdjaJkk")c`p]jV]TL"lkMDqM*'\CVIOZ+H7@=Sj>Rba"FK(0PaICFNl>cKnh[ + ABpV)h.Tl`%*E-N8;\@376n7.T$%T`orG;Fp-gS2R1LC?3K9JCG?VKOT*em]G#m[_'<`0c\F@[E(3hB + U1^cT`70*+JW5^,U#N7RPKTN5k6N#=b+bVV*0^C\cSE17>lpf:J(Hb*1PFgcu:X@_ + f-Ul+_T_&q#N"Lt97ZA-#j",51qEIJ`kkQ9AVN[&R)F*F#WFfi(6\_"F]?Yga6YkA"7fNT;(CjhSSG%ShKgFm4+h + 4_4LQIX\N?J>g<4ls=8T(+\&(N$,0E, + m>Om@kQsG#9j=\Yk2bSNDmPdHWa%B0@D$[#1$pN[)L,D(DGf<;ofW@E/)j$`>i9OE'n[aQ_ + 5?Vptq`e7soal68!_I(lFVmUVIFP";UP2k3lO\'F9X?K]F^YuV?bRJJ4C:)tFO$07.Qq]N)K3f)"5W-3J47V8H^8+cH/SNH4H#c"%66P + $L:@FaY:aB-*N.?-cMqY6.2*XX`qLmq$haNUCRgfrs9K[sbHP]PTk3r\je0gFs:fpp^D-@" + 4Gn>Z]0j+$TtfpF1d"YLR^(B+):E39fr4h8EEF'>(nOpp!K+`E9VA4MTDK*XaE9ZYHI.YVgoL()d + jE&Q!XieHZGcE-T^k_9jDOT_cbMJuDr9`g6t$O*K1d-tMlLsj[f+HCDX#7W5FXXaWn/s-T% + #O)6;bsMbN*n60;e>Y.f6L*)qYhI@$$.,FS&>oj(WsV\*$BVG!:pUOkYm\pf$h1b:&B>2Ub + 7$)N%$8jFd*iGcf+$*W%M7?\c*[jh)V_C.iGeAH:(Z@O%,eAhgs,p3:&bcb!])Fm&In+>:$ + WCD7c+dB/uT6snHTWQ.,@`$&'bGB/]A7)o\kor4RO2b"6/m)8*Z]`:28poD]$E_kDHY0Uph5%Kc/0f9a\u\.b*];W(kR + )XmVX[cD;J?U/2>ul>EU0TYM#!+>0#cZL6Zn;=[ho + qQir_+:Cg1;?XApr`i5!n.,f5:Cpeb(q-^i[d/$B%j<-TKd+hm\;rEj99*L[3b+43oF%8_C + T/S6=S3;%Ae.jRVL%ou>.j<1p$ZMdZ=[iiE4uj(-O]56VTcdcJA8:>plCC_9EK*MP:4Wd^r + I*;:ST@o/LemdF0D[^Dm:dN2#E?K.h"]@Ejkls9)Pm>Gk/_jn'lWaB.V8Cr\Dk"Ds%&Ig3c + E9rt"iV:\9&$OT4eP0+\?gZM)G(qT@$g!GE=nLbf(u_ZFd,K_#i$0J8'\f$2Yc"4JIt'.3p + H.aINWM,7*s'/YT.?I4lud)a4cE+]BRd*aUd2"5cJ:it/_K/KuB#d$BN0Te8MB[kGR/hib( + nDuTmG;mn/2tMAU'8JPGM`(a+nX5-.rt2Y.e7L0:(f0fZ8V;IMXull + ,SEN;PkQH#>kI[f]-_MD7Y^T>>4r[@Bku/1&nr]=DJKXA;@dt!Ech'AodBDbq"6C\EfTs9P + /0qb.BfEon#7ciY5;3oFRQ[8DnRYs_#.*A`=L3o;S^8,O"?I0bY?XXP#>T30$#SUOn'5([35m[(!#ul[=;lr,[Z!`k.@+H!![MN?f).?P + b@MU%pF(,o^r4(D;RQ#jK,@c9E-%m^Oe4ELka1^+DrrFOI-1"5)Ep1HUU + BkCC]B!EFg?i`JmDeHt_Ab6"8!tc3(1M'TbiH@uU'GhJO.7)OW'k?>l2Q%;X4[SkG6GtTG/ + k6]l3(6e4C]nZEG/iZaL?=fIk1!6FnWRgS>t-Q@7T#p!FDVr!OW,)))cV#OFFP8gq?!<@!u + ?&3_))i,CIp87YkJeP0$bS\T,uLXm<#C$Kn/T2s%>eK9R#pn+6-1qtcFVuP#MYC*tpP-dfX7K6 + XL2Jc%;cPo*:Qm5$(;\Ff_D7`_X=7-^X&U]6.lCI0d^`BHa] + rXUc[F^%m@9rIt?8-A*OE4b"=U3LM29e->=BnULtIoI\0iVXP/QlH,t'gZpS_)<#o&eVr>&"s+h!+W!:`(IY]592-,PFS+=]<8UGS)Y3ObU(91-f4OXHod$7/(4gWK%^$U6&.tU9Qm" + F!@."WbfF@ol%\WG,@0."V64nbm('.AM+f]>Zo"+R@-.NGO(P9==H['RHGg"i>137_%Mg:J + 6H.SU52ks)`%04rk3mnT_ + i!s0EXC4>\iE)(kOqo3Y*HbLc(X"=L^O7%#/98p82043:eQ:2OrjrgBonS#MpK5t0Z4*:Rg + Et(T.ke<%J`o3u%fl5:!8n>Q4:NF_&n&WdJJ5FW&e,u9&qJsO@6P2Ep(_!W&g + JmICRWo\IA"\("'RU4'e47cun/N)lGOA;7bCO20_b!Fn1V[3"p + kg&-2bI^u,\'9FS_\'-Opq_&*QQ\e.3M% _^t]WSljgj@&O@*KTgf`)l\4SY2YI9;fTh< + +$c&8CM4>0ng_7Mo(W"lA_AjN34; + W[Wmo_8Y'U`X1sie;'"c2oRmt`FBdFnN4[o663o=(VtbBi=$;A-4/V)$K+)N!(6g7S"/CZ! + [&`n+ERkbg^es2'nLh;@Za-Dt7WUro%"I7/=ne + @cs(LY)gJD:=d?j<8>!FQd,TF&tq7Ke6<$\5r#2[Q?i\fPV'kdKYl]dBi6]:6@G7uHK":9/ + ;aJ/I7[SDrBDTsu1If^*n@ELg"p&aLMH,Z`MET(a[,(G28)pN&8LTEV^l4a/&I);!J19j.s + 3H4od3MI4tMm)'PRn3Hc3HYR=jo"[H/oTG.E4"ZIG-!.[69+A`DQXpmEu + (*S2G!U!:gJ#@E"-%7&nT!&+L\6Ooar!2'`,!0dfK1)$?X)o9Z$" + 0P6@!W*.jp5@>#N,"M5Onb\qu;6J4mRt?YQo?!2*.EA]"JW>cp"I[DfCq]Jq##Mrjg4J&+A' + pWP6'%,\p2ReY*JrN+ksA?n3X[T>2HC?Dj`p8`C#T[2hDNCMt.9rQP+Fdg!brMQRGlo%SOL + >!DD04?bPiZN'!+\NITM2k^1aEdE-dULU.Q+d#AaEQJa(6Q6Lt?;DZ0co + 1@h]O&>L"d&.b24sn5YLDn4B)Or4s65te^X*9\P[t + a\r%`bi0[r5q_snF%BLIt;OV5V4[$aa.Ao<:)\A< + $gWf;4mM5:Jr#e2,mkK#Q7k.e^]@c?e+hKF^*HFrGc,pM#J"MP+6B'RH/;W!ms,\Xao5 + FM=7,g5?oR%YJY2%\B7jV04qiZZG]jX#(.\?KM;hX708j:.H")9`Gj/Xtj<]7@Oe:`T?^#% + =5#[bYH$3"bQ,il6RH@YLBMoPBLrM1_5hIt/>^@%4&-N#aataUK.7ee#APrK*C)VPrWbQjg + V5sqh&BCK@_gpTBlFP!SZ>07ha0TVE7b>!6]2marD>TXZ\o&IqkI^/o`qR/b]?^GjE#\NR_ + Jjl]"iPAm:CkEa]Or>*niV+BaDu[;/'N%(f.YD?4QerTF?K"^cubfp9@S(^=&%7g4`=ERG! + 1UnfClnED;'13f5oem*VXd8pV$lfiHW'IO5bF+2jR;fe+iqb^[qGSJ+NMe&,$"o#&[>:d"pkG=E^inAODGBBkcZ?;'VS-nLec*499p + 4)=-]MaB7M1E&JN\TF5CjM$te"#EURcS3g9`#-%`@qM]t1F?A + L9t@WDN9QfgL5p6l,=$$=<9Ke#R+]ckC2T$Tml.Skhj`)O4\d`N3Ti/G:RUcrBCXS81^"&7 + #pC_9fYSWkjSVi\mXL;Vr'2HB:lH(60PI^@"sCu.M=`EV..SUXaHU50RRi=`8h["H`>BhAB + TD=t4\(>5R3Vop9i=;J8gufl;j<`;'#d(.FTF+l1rbsK0`Mp*n95(%c621m(S^RjQ[o,\"b + S4:&E;&B%i#qVPcBtL$;F2p2+"5l17qO2V,LEk:9'$0DH2S:\LST^0%I(I=1.-sX6"H./+U + P[Fp2GZ25[`31=?nH]QXP;_B?XM&ZQRZ5$mHZjMS\;cD"@4*6I*r^-3%[r,6Sc + U&VmUA'l-o](h0qnfNSPCD-80oA5%Q@ZIW#o=^sC&f*$S\EDSeb\5s4A+9V\f-R<0B'FMZU + 5RKeH4WL5#CQa[\[MQ%=DXH>.(P;*^)p*?n2<):cQLs$\eU)4Gn%EqbpKoH0I.+K9^DG>s? + [MNrDrq#6fEV?549:!e('VMk*sH%g)1lgF$c1Ibp-#msA=it`I#j59iN+m5heVPqfS0)b/4 + f^o/5(MZP>mPQ7# + *,X9='<'AVBA>)A=J%3`)>NNp>_j)/4`;78R,XN?k$1jP0#VE[V!fcd\DL`c1D3CpFp,H*/ + fP&aSsO^]k2aOpk?iERs?S?#V>%YkB7:bI^CVE1P^7Of%o8Ei-Q/MsiC.amgkBt8Gc-1o!Y + 8>(-"Ep#3c1sDC%3b]FZ=C^L%YF?2GD!O5q2pBe4S3#TUcBDV=-Tpad`oCKD)<\8"0#sR<, + (6""q6LR.c_)LJltn3U5pd$GTenNY;!kbH#!hVa!Dc$q>mq?4?sOQMr,km/9hXCFs[[Q_B%eEpUhg044eoM*WNs"mg7p"08&cN-cC8E&*&%=Nq&]*aa"sWj?0o8o + );_7H+u6"I*\GVSq4Q59.,G_jr*h/Dq*b$60iLiYJ`*K'72\*P5Vg]a]TkE@e^CmngD0H)% + Y[&s+QM!1cV6*"!N%_6]q?1cM%&igjLRJ1PVR_#iR$VG2Kf&5gUDOU4cGQ4M_s](2(jrkO` + @KeK=BhDp>i06g$SlSNj=PcI3"fB;.&OLsV!45Z\-Y$NR[F^?Xj2\-rAa;[$Q9[";DeUIcb + a9=1`n-*p<&ljT`3=bYLB/=9Xqn8"m_;l3c!5qp)3@o]pY'rL;#6#(9b";nPr)3$.Q_6S.l + HXJ4*@AeY3Vi]gp9H^cK+gu-6-i!!cMeU%)J#F;JUl0%If_m4CItYm=L%#ELX91^Ka[3cP" + Lq3\qfbC@GW18#__E)hZ[B7!Q`6Ar2rQ"!Jt"_,S[]>c.MOt-d4iJc15"4DKaPtQX=_@%&q + M_S"Mdb:@uceKO?":MPlm2fJQ00P\%,I1Q(_DBP.efV^8qCO3ODSm_.\J"4,!qFJ:'W#'E` + n'&V6$7"H[4I&Uh6<=dS`Z3i^e#]%=O2X1HE5]&TqF_32eNh)c<[XlA6+OAc^4;2Gt3O5 + a0^05`-Or'H`rX]0X:,\44Qk9JB/tbYnPbQFG%9%'?FYs5)b92S(g*\kX_3&^16-C`/@10) + R8npZ3glYUZ]_!l:?Sk5+I>30MR6$P1S:oV82Tan,:""8 + O38q`gmNZ`6O1ctU8Ts!5r]KV21m+9DNoQn4U*9!C>Y^dCAOXbqc=9(WKVdnW0F>_^V/"mB + EMlH+]u8d.:SMprBI9XqK8'dgT)VR..4`#(A<>g)/5_7O&%:t\MU@R955NW^H@'4jK]`K#m + %Vt;:DUr\S7-FUL=6B!@H(duN"WCio,9!A=Oa?qn+'hom9WDk[QA2H6Xe[O$e2sf/*r&RQ: + 3Og?g_o+\Npf@WtEWLt6JcsB=KW'DT3.mks`*8;5$qE<%[SC@)X,K\3>F2D/9hqIN\Y?i-" + .PU4;?]=+!I,GUr89sS[X^%h"'A48N*88a>*pjGcZ+b0h"\kQME9Lq7N%<"$tHEmRKTifL? + K*IiLnL0W<$*9JcJUMpii3\CS_]W8)/L4jTs4e$.+)-d$4u8V$kZ#%++)E;"Y:Er=73Y&^_ + =+d1$c:,ns36'[^@Ia7?2m1Y'm;J^L!>a:s>!9cUj!fhpgb8M)%*M\J`&OO>?j8C\s+]d,> + FNi90d1(>G#4Wqj(.C*3C;LErD3j7Gq7pW=4/^RV#s + KDWeH7`n%pL0UQI@#]f[5(loh;ZeM%!&=Y$W<`'u5b,&NqHQZ.7EZ$D9.5;>Ghr\2ab#U(K + *2fE0YtqqBu*KT6"IU8X;@c#(oI3L-QF++7)2J9*C8df[PYoM]HKsZ,"ucc9RJ,9^h8D7-8 + r/^9X9B`kVn-]70u+1e,&46&3#_:In_1E[uhOq9Jt("2Q;mi@4#eZCKTtLHTF4 + d_WWGF3!sq:!a\B;YZ]u6e`.GKZ99r>+9MV(EcE!@.]^2^CBS8oG"ncid*1-dM,(g/%Kn+Q + s;Uh'cKhm&q:PAPiPkmiH?Ve%:`[(LP0pRE27u!2LJAm&?PlLh]9a&OXRlps$N<`U'>V9u]JdB+;6`.VpqFB-MMo*7<%>]F3bS,pU::4q79<>eV&B`NIDbKF8.Z!HU + F+S7N=(V-XFa$o@H!d>4f;3h$_jSI-@Dqc+hs`H):e2oRo\WW7V'm\C22!PDp;P#UfQE!XK + o&p-qr"/@C + @'4^)GtM*T>QjiQ`P(jrbV6UaWh_^Fc?eAZ+N_-,!lhdFG2/N/$(kg=oW0>>?\3c*63XY@? + :DT8g-&*OB4gXP7>Bd_7*$+EE6>IKG$K!lbaTZiZF6Cstg1^NDJ-$i&Kt3F!jiUpQoR;Cb\ + UF,(4D5[[p@N2bii.K_@^"0u(Z7S)$/7blNa>kUh?L!X*"It=73!U@dZi'@L::"8"M<;[M; + L"K\$)-s>X?+o"KeAC&&+/bsmVmT[XSB!#\a]6SkhF,Ldo5'3W9Eb>_9]edHBJ+o;4'oU69 + i2k\PVl0XmS/?pN1'9d-k!\_?^`qS]_,f=lIIjo/WYS! + I/0!sW[Ur6Bkl)ZjUSfN*L((Rc=Iol2rJ\`M9E`FT75`KUb,BlY)1l6qKg:)*NY< + VF'Og\Xkk:&$"sI[rO6I^IkpDVY2O*GYQ0Cc(Bi^PaL6q.jQQ>34.()3k47_Ap-"9?8l/[J + ,7OMpd.-!37;>)L"*i*OO/8AcIirW(UFgL)1U?\N,C(;X6+=_uY02;KT^aL0'aFaOB-%^@i + K"*'-===jP+iGK;Mf(sQJ.-tL%>I,\#5U@?ZS^^f]qc]4lh3MuL;8Un_^,k*L6-J=?u^^9# + 69@t^`X'l&0bl90`qkN1<;QZpb@D7RKE=`Ja,`*UmM8r6Njg?^o4\/;teQl2*F<99)+D%e;h@$7hiL%8nV@nnTF`Ih\_X_rQ`E.Q7sompB=7WW;nU\!5K.s?l"=eI=bQB!U)"sn + 11=`-36XPE<$:Fbs+T-+T`K6BEeZKHsGkf#Z%M(kWtBi]&*Z&;298o:_YM,&.X6S('2JrJt + O=i0JiiM-Ng:oJnR1H-mF:'*!*i)!;A@r'hSO`4_Vs>]=_tESs\c?79X'-l7Gj)F->4JUj^) + [%HBt2XC?+G;VD@rkSl#5UHuY#VCpiD5jI.uNHGNXI4fU[6=T`s@_L + ;5tU#Y4Fk)*;^:Kj\$f51;@5bkRtBh!IX;p=Ej4NSOS+KYUVDW:'cni.o3LjYkqRIc"Fki7 + 0^>DDg3,ro;hh@W#LU[T26l(0p9e>\3'_AbVhR8mmA$KErB=V&8`S+?bAX9a.b-LE/I3s/: + 7*C&3mjHCq4!fP)7S=U)Z$K;_:k5SX!'T\i"0"UJVIVJ"c2rfQpS?MCqk[I?PdD-#RS"/&3 + #5/i&6+p'GM"2I*RpeQSdBnc=k#N7Oqpg=:b`;Eu]HtULtGK0l[>_e)\"fNpQ^c=K$%D-:1 + K4%2NA4Z5jq=/n0:6kb?L3[dAH581kK3u8TkrJg++G(.V",Jsjpe-:]Bn`3aLYuA?aFAoIi + ;OhW"ioYP5[+.OjTkQ[KkP]6!'u=;."[O@4'0]g$(_9BL1hCk`RY=@QS-q!K\44Qka3[.BE + \B4#QdOBK4mFNi*:si;[:*pAd)k_^1Pco_um;bcDRmCGdJ-$`%LdY]L3,f + -+"%4NX#/;X]5=a8gNtqA?K2fQqM$Hq39HE2jqs'e#C:RI6F6!-r]s_#)Pj + F;J*ih_hY' + &cMno_3YbWQ4\YW_D?N>'3'b@^'(ePI)Urc5JSHm>f't9<8I29;R@?`'pU7=LgU`u2-/t@] + "ruT5NoRS1,;CVTbSPhS6,Y:mMR4$*sN1D5a;P^CDb-Jsh/:mkX-QG"=_"L/i#l'c7MWNfM + CE)Fss#/-nFQ#7*;j#<`cK>&mDbkH=s#s6M\QDuOTtkam9XI>S&-QTrY^%1C+ob*]FHn7kS + 7hdT9iEn)4\jK@iVm[m2'kajS5gS-D,k^q<.C=5U#Q5@'MLb8#bl_eHQ)&Js'F=>rI\OIp( + `KoR`=mtAD)-+R:k4(NLG?c[T0AC)n*_mC>1Z.Zt^P8rP_g`(31_^XRHD'[N8o063!Y$ + JrG7?#_jni2U&m$3/'qn5Qtd`=H2_BDH(Jf?B%bILS`ZeHUjqR]#(C,%3X1I:'IG5?)UTEj + 5Ri?s)1#a57QI&ilIs(FX_WYT\$$n92Rfr-3uONlm,g4T[@cS1RC;M[G2^"">HZgl)/&s=] + l-!hrOe9RTlc1mH?;CjL>k3#72?(&MLHBh62k8Zr1>L^+6ET`bk$563R@gKA:\Lnbug]*Zp="Ghu#8?hf(YFm"&Us.AP%5+e + ]WQcK-2m2*M"]L.fMf4ET#6',\'O2M?4=r-;[Cg^o!$pA\M#[""/ + CJ%ibu/B+jm&&J@LYWU3AiUmI9CAkkpS4XT7fSWjbGR2V8j)Mj + 2p&#RGc,5GK%V1bs/a$f)`=_TJ5I(>Q`.18eu.kf8^_3`5/#L$-*0d]$&R1h2a9\GUaeYK, + e_UMj6l%(PN`7I&Ui`_=]\iW0M'`uiTM1CH,+2f"U70DgWb5#U1a8"a:PK%mo.Hs"7=KiUW + M%NKL4sNIWSk+\BJg:JlPitO@Th.g,d+#NldaD5<,Q077-cc>(3!`R#E"7Hh<%Groi(g8kO + s`joSnUN9:c,:>;UkY&U+1k'X"$05)^C#ce? + Sua!YA#%j@g2Q[?g2o>AIekY"IMSCMN7>9>,T%7#jpmMm!"drNE@*G& + !3bJ)Gd53@Id2FmA[2,E%#a%n"#]3K`_(6r<(:.l3rOH^GNnu[PI+k_"B5YuA\D1;Eh_%9G + \LllA$K8H2!0+9-^q:Kdi@ZKI6]lD,9'op90'-@PTQ@T&nN'O*H*9T4]_A@SRfPt]!.YfaD + *S`,_i5,C?`>I>1J2ZWI+BT";n;e2L+B + o5Oj+j7m+b#fS;q!=?W>L)ou.d'LH + &aO^TLdL"S'sACtN'@I-,!;!s^-0s#qV:!s"B!$DI(5UZkR;9Vm)1.h`YW-q4I98J^<$X5\N:iTl`bWd97V\ka`TdoQHAE\kbk29dQWX + `g>.X>>An'3`BTI;jBZjHYQ^H4*h% + Uoc;'"E'd\RTs>KVfM>dT)Kpco,F6u.h:9u#p2F/:>NQ;[\k_0A6$*3PN9%F@oYJTb336GR + %ZF[K!*#8^@PD,\rU4]3au:?5D/98UJp]uZ!1j[d`L$P@tp8[q\mM$`KB6dZlF_&N\?,8mBb$Vca+!H + g$L)ZBK`JkZs-Vd]7Bo!/JM7o;7[&72\EVfA5A.R_;FY/?u2DDZWnR]E;W/EDHA@.2qIORL + .M7HTY):F0G":N=E4ng>414m4a,ViTlV$*dU#SaHTag`ogA]-&qOLPYdccp@9>(9>O_&e&% + Qsf!rlXZ%tT0.#('H*"lS"D/d:23"[K96ds9.=_cUdh_VAGUs/h*LT@n\'RhF_^;ZH8l"3& + p1nma4b!V)PN'SDh)q^QIt6BH?25L'g,XHEIT<_EBp4rWMKW6-dmLB(Q_^kSk"3WPi$%3[V + EJQU.?b04fEMWh)B@hf"f3WO[9!7N9\m]4O_YBdr7YVn3mJa8HeNQ"Y`XS:f.C8>m7H@ + "d!:!n7MqZBa4'D#;BouE,c,EFU+lCe1,>H8QORtY1lP9g54VlE0T#ESCTBcfaVEBOJpdUM + [Ih3$L*/p#uH)qL6uqT$o">76!tJ%K]PH#WaJd0J6j.&Z5lc+SZn(`E01WemRNuK$fJr49X + G-lW!eU9h,+=2,b!R!gnG,ohMNTk@D^ls]`V^l#iLjQ,qpE9!2Qb!i+c/e&NJ_=3Zmc)'Fk + on+e0Ieg]a?U#)]8]O]#21r8SAsW,=SnKj<[NC/llUWN)>b`JPb6('KaB,%d,GLDCQ>O;[$ + 4k&,#R9$ZSeG-+MakL$*h&ZDjMLgI6AY>12D9)0F&q!"gc*"JdKcD\!#Wp:.u*@Fa-0&Eugk3b]s+5qG]@GD#/FFeED`_>Nd,\F>6qnB6A,C + P;4U/@b-an]R7D@+l)6*3*C#"!BcA+\We_K_+XnoF&>(01rm_>4jDookW4QY?H51oEW0^g^ + -f#5sQ-XZM*a/pTfHskEs&$#5D)XpZrUhB=u'rcj`l:VVj'.i'%e41`C;2+7c_TYHGg)Ykr + @Mr"$N\/6[S-`dlC[/M(Lb&($#=A04'Y#BFpG7gKad0H2\/aA;6Fn*nGT?A7kCs2\mS&-*4 + @neMS1!rr`>i9h@7(a&>k!YZj\ka]m/-o$8C%fp@h-#4Nq*s8`q1baIco1:c;9/L]a2)A)e + F&+["+@E>$J]F + 5q2b<]SQ_2f-^#i1UT5V:e(:$3+G-P<'P5n33;^j67DN)KcO'(dI`:(\03/5OY\6SV(uEC= + ;FX<$4g"Fs=K@2'=45o)(.<(`&.Z#&IcPo"ON(K=qE_&O)=:G]jQc6*H!J:%X\n.1'k(-us + ZV77VpR-,sD)AJGTNdjX'Kc8@F)WgWaDN:pMXW/sa9.L;gEQq.W'cg/ulI"0n1iQ:,N?1C[ + *j3ZPOmq`pgh/A7+11,P2!H+sm3"]N*T\)*C2OQU";DIgn4S*POsFn*'-dmDnZk&kDC-=%b + Q1;V"9:al;t_4E:F/n9,n%I6O%iQRb9drhol=^_O',G5Hl-LT+(0C"^nR?LKF!+8pBQ7MEe + a=,I/"Fr.4bcSC`L:-5STJ'!>Q`G5Wf.a<#!/J*>5\/CdD4-[4BHlInWW7eYt+*d)IVG`c' + *RQ+8J_AelP'+(0*o2-QG6HjAT;0.8\2FkD@Coe6pR0J&m>Dudqpp&cEn(^[#J!9aoS)$.& + g16<_9E$\ne4:]0l((ek`'1"S+GrPF=@Q#69Fs1=\-"=;;@eMZgei@4u4(EFsA2Z_aG!BNG + 7:at?1NS/s9i93Kr30F.GE,]k6tTB/@G<&@H/'M*#C)4199EeqQ.^HK=@/!boLh6 + (h"SA2]`\3>Q%<;"$T6XZgp4Ud=`FO:%lX?3sR.h%:*4A@Fgd'gWV_5kGO,$7QSW+Al\g)W + BL=;)4tC#iLq#6ErXJoJK"Eqetm<(L_t9!#h`6aP"@)("?)LG8V1g>t/pG(H3V=L6`(OE's + [M![&ls@#PEmiXVXl'7iOf6&Z]?gIX,k>OeU_dAok(kt3e-)g&,"Pcl&CCp8#l*3m[\(O34 + Tf`PtK"CZZp_;.gbfX)@M*q%NkGF5d8q!Km:H8Or6+eX_O?lK(]!Iu5]J33ql:Ebu,+tj.; + PmFl7%t+$`,?5Fr@#>6JZCHZB"CYUPJ^%*c0-rRH]9P<$\+mV$6RFbn<,Yo>'i0?mneXUU" + Fs--aFrY?";@Is)U%huokPsRMh:VBpad!OP*oCL\uHS'RT8h[@7h78buntc=d$C"pfUcu_^ + or1r(kL?0@*jF]e-Y',lq0jJ4'NB(kO^e&tporb_\!\g;qh%?9"3(Fm-tUi!ocWWaMb4JGV + (rXT>j:M_cjbN7'PUYR9=o@=h'^(s-=`c"/l^N4@>qpskA6fo>dYN]>jT=Q,9bm>e^HNgTL + "q"?.;XQs*0k0P6f<=63K8sNCY#o8=CX1Y;-s"?s'gE\@ONU<#$SS"b + 9VZ0nWoEo8fAL[JONr]4n]8CUSS,[l\T=HZ-C*Fgp+N\,1lh4,LHL'p%TUe9Oi*)+1sF"_K + b,C&=[:C"ng(+-E%t])/k(KF]l;eWPKE]%IKNiER.,J?!"a$_YVPfgWICO1BVrPm!)346B& + c+Ya=A9]3!DR;9R>it6QXQ^e4r3IeiTIDGR0F"8NE5faB7jd?nQ6C.5[F>94H/XdY6RUn(g + @K$MiJdEmIE<(Baq71M@^RU_2Ecbh2$CSkas2Deu'&co#H.JLSEh@k[$,E8C_"J;t-Foh7@ + j1F+oV$EtY[4eMTJWjF=)XbC%dH(iTfO-dNj#/Q+*[(Y$Xg[Y=t`ToO\NL(B,'C$dJJ5n6- + T0rmT6G163#2i]>P#PP9>8t)I7`u%3kXYVNhp8+F4:nTk/cL'A@sYp[>%>nUGkEd3h/X11\ + &m"X]TCdQ3jt!IIfHcjbOENBe0Y?lTr5EZUStpR!Z`pbd[`4+in_+7eIF>1KkT]).7$rNFrW(4Ch>+N=h\15J10!C.V>+]]YOO,3ZX?'dnhYUTggP=USMqu)*h>QHkhNP,Y8K`ru!1o^<0EC4s*S^OYp<>kX#FDuPkWB=<=ae+[ZiX9nQ!r + b2oI4\rQbnV]RTqN274[Z'Hg7EV)^iXo;?e(f9>pa(`EpNR>[?^/>$&PE=2=SB!@>amDuc[ + D"lNhp4;n_23##eJ'VY$!>`VKYA]].393doo(NZ8dFeNorb't)I_6'FO(7^&h!fnHF>e<^s + Q(qU'UR]H$gs,Gr"/NT[c+MYSTiMpPZ>bZA'j7ri\&lqlA^\ + qSN"7(aNT-2ed^+,A8B-)phi+8m[96%0mu0EV;?NBlb>!C,']Q)gHKX5-Q#Vn6$h]>fXN\q + #Fa3)csPSZWmnq\WVAhq<`iFk<)XfAG\o)[%8AA[!=]('3t-^b>O4KHLKZH@92,J&,O^BLF + Hf#$7m5nG%r.?Ef7)U9Ssk\R,;r8#*?ZkkMLbP=ajKR8g=k9dZBqMT>Y7B/5o*eSk9%l>o% + Z:M2/R;uVJ=TrR>KRFNdL9;b^''Zu\)PYrp$V3RM"DH:^8WTl?6'o'h/ZInNpRmjL+D7*TM + QMZrel#=7>me9r)me)"@hsZp?p'1nV_dK_d>YZ@UNQ,JuGoV!#+d:)\Eu@[&?T4CuBCjgsm + Z0Z0d:m_AI9)h?WRgA-gDu$p?a\R([[V]rkBXSpDYO%b#7lO-i:6T4N]/>RbNEoW*8Z`?Pb + Co9/"N9ZjS4LN3oBo9%thg]G'JE6Db%"\jn&AArstI2l1+WJrnj?gn*fpj*N/f7!5K-[$N` + T_*"rE?:dc*)6*Q"[#sn'N&9FD_r%AJr&C`cO6h&ku%n'7t7h\:q6qJ+]kP,GfJF:orQ'Q" + 'Ns\AGN,]3FE"T[%*hZ=F#"DK7:-B-+6hpUEZj'T4/s`)/.,9@J1d36l/1jZ8o1]^gQB1T^C8ca2l`4jZ\.#t%O271+O7'G)GoJ + oap9PE-sQ'Q9N)r@^j7eD30(;j@+X:t0Uk92S[=+oZAL[8g@0e[:*<=]O"Grb`^@[;fF&2< + B$fMINg+8OaqLkWp.X)?45M7hqG))cggLB,`3)s(Y`gXflSG2W_/mRhi-BDql]D^N,e[4j, + (5!#=6sh=lW`,":E\IN]#N0c!TQk&K)HN?)C$5;VBkf]#V5ThD^9V6V2\LV-L0kb:nXOeJaDq@G_E@7C<6e=)`gMKj+8 + k[.@.X==7FH;V2ah0K8,7[FFhN91fQM\0CY+p8.KA1I?-d`PGJ2+G@NYRcM;af@+,T(S*`n + gd9R]d/3Y(660U)cMTT!0D#QM:$qk!>$?i.S[`.++\+49]Y5A/UB(FAVOpjQS,EaJ`P\?IQ + *rj#6SJ.XC\L7#FdA,ZNpBF=j[YdtqG/d@6$*s>$>&;"Ei>/:It"e`Z@K$\I?_<=-K`M=K, + E/7`8BM^8aZm(35c5+:ersPNZ/JbS1)N-Ri/>:9F%ACK#^X)rh!Jj2gKI.p3;@l6j;&G.CB + T/)KTL_W;W"gQ-(P'#G!!#7i!J5Pl%\dYS8[1#ujRP)r;Kbo0.FTmNJ5;3`'EWf4=XteCYT + 5.]FX,CPO"p2ga4UJ8j:m')Op47o3=R0C7#R/t2FU1$)'e4,Ockko91_,s-"Or`P>ZQtan[ + qF,kRdpP)`I5.8_O*9W+.Ebj-:*kXlP_hFgbs/>Z=CPYYa^8Q,7g9D"c/%tZj0Lur1lacA/ + g7*Cus2It&-R"S=W0UU*sV1T$,S&u'&;b7FXV!.Y\$K8b-"l%5+1eE`GVfe(MFgisJ@ + o1ThJ0i(+7'EXA!S,gbOK9YMg'@M,#ao6o?l4BUf8r$8C2++=](8.UXNb;:@!!(@a",l0k85FQDuq^a$nbXOXbgWXV="X, + seFcE + \-79jV\/$=iB]!DT?c)];NAo-Iac2\[`!-=)?N>80iG*cZAFc'oC.B?SEWn/ec>&91Qb?7l + ]fnN.7Z]25t2YPoj`CR20d_l[u&5qE?orE#6(=tO(?K.J"PB$IJ^8`ofEBId\nSdCLtHlTDe`o3PqQ]8o?g5gaabI1N&J,e;>K*S4 + jri'(5!BA5OkC(a'Ai#M?8FsC)oY7I2@6VG6e+i\E[]'WHBb-Y$M&)L'ZQ=+Ntj;D@ol6n/h + dakWaniRPb>hAupq8ZuLED`[bsHA:/R2EU*#6CUJ>hCt"S(CAh-6TNDY()56WpNFR7!N3P9 + -b&_a;f5/'.kE];rF8J,ID%B"@%DRM_fjZDLB\0Xm[b,^!AE'_f\*Au=7>V%\3!?IT(q0t( + ghsjj/lSj^SX/HobGan_T5(s`*TPtBRG;,0F%ge*?MA6L'1-qWM]rj'Meh&B4;6kkDnEm]= + 6JRMX7s@1$q#0*@O"9k`PA]&b@$\`K`k1)?;:6`4JPMW.O>;js/MJs5Ngi)p&< + Rrqk%BeID$3?^L9]J'%OO2sL:5YP3LLiVS+cn,?\GqL="WE-*+!d%VUeZr:,C$Uh@ + ;=-%,g2/EC\49Z8=::0'e2'`FBCJhJ8=KbY#5a2DYR.Q/9IfPiC!4$PJ(hABK-bpu@5SIet2\6$Rm18/6\rE!3+5KgLA>94PD6L-b-:2L:1r$m6sd8#i7$#&98Ye0R$4P;`Q/2 + \MB7.[\)C.2CTZL\r[6\5=UR5_LOY2j%f5ZnXM=E5O2S2sGFnj>=@T2E1\TL\PTgi-mY)8: + YXaJ;])ub&@?j$io3L"9;(3pjO&iNC5:UX)X+Yr'_JJOfQDj/#/9GMS9t=S>r5["]/gAF-e + F_:-"c`)QZaq_TB@:bW4t4Rl\g*'$m%WU;^YE"9:bTg/s!#49W!f\q8SRb1Wo'WQ"/W!e;_ + P8h@JS'>!_\]*)Y+'='\^AhIi^4&`Mu>3S+`m5opdY3G.3XDL&/ItTdM"gT\2TIU,.^n3F" + 0<1Bg]Yak7e9sI>cc>@uTbGg:,7cKo%_XuW0cS[u6e=%PI + tRM]7fje7E$p:%gS-/D1!AEEb9':-Ndu?Z_#b_[0G=K1jJ*[V5$R-_F"[Q5(4mdrMYm=d[$ + \VHR7XKK8\CJ5baV2RS4VM.8aN*AjIo0CTume<8j&k@9VT[7Ue50o%0QR-YjOaJ(dW6X9"Y + -7//,F=%*Io?b4H,j9F4?3#g4b)&SW&Z":Ij*[PX(O"org1'4c9cV$[lc.b!F.t( + dM>M,RJ:t+(]f.2/:pD>!ipT6)gf'#I&]"[_aAjiBRdp12ho!%c2?D8H*i??07sfqU=]W:e + E%kS'7iCbKc!dD1d`d&Zn$Oi6qY##"@fp98&UUT`1)uZW`Xee%"RtE/4<7DV"D4'a2K%m75 + Xd"oMdC+[`)IF7<6b/csY*leY:,.![oP;];W^!!QFZ1Xfu5g:k+#:%06M]K)dWYql`I^Yao + 7a`a!.<1hXgmBWeDF*].WZ$LPI$DG_ps+>@+,\,lXQ"!4Z_JUei7$MZ^tFM'GQ+0?MQA&]V + #$in6O+oj3HkWd+o"#&1L4P_8Fm!^X`clic)a:f"R)q<<*&0\_;87[Fq=B5TEH`FapSA5kj + ?m!Ij)nja[1$"rs8Hto.!oP[69Tl\[-:pQP15.hH-o(lA9$)B@b'cNIF[8W:6<"C\e + /^nU9ghrd6e#sA<-:Z0I7[Ct8lUL;<1Q\3X\91'W2BctbN'F3oXr>a>"[Q`bA=E579)tY<_ + GQ\9/u]3U,k.@g*@]'(OobZ>]g=&-4&r602cRHm) + 1B+dBc]29alp'f=6'KlKGTd8qSBbq(D?20DtkIt>Z]JeBW:fF(E=H7CI%9scc + PbCE[Li7I>Q=7kDTY\sdFJK*fqK2=IYom';jXq,mEXMU + =SfbX&ip)u/;%4!cW!\#SG"Lm;&[WKfm:fP"T#\Dm3f-E%)"3D;P\rY>_QEg/-Vd]gi* + l,-*j&V"b9\\;^F4jbqpVm2-f?pK.&L.U2U6k8;=f@-^b-Uh/^c!"H0nk0FT)k&ekP&&pN?s\9U<[)lk3B7b,qHofj'8R*Ji")R(s+[aqLRi)/I + ;*(0C0&9p?o^qTU,iFe:5*P'_IA_\%*mHEd+:*'=%3U"?$$1)3eSDj*>ieJXP)4=Hd08<.S + SYmja^cFJ(Q@i=<89GR!f*f8qg@g!Vdp7<7Y/h*YKS2!H8t]^gDj)NEF-b=OO`^m>as.n7$ + 5&\MKJHLd_GsKLKL-)+1+FnbAL[Dap`P/R!^X?+/8+_>e,q(J/`*>_]aA"3j?s-`X?&/2"! + ^94#!)3USDof7)4k&9:9Aj(E+T`3/U<,n*brLWo3p*L*e&s/GE"%9<'.3nJ?;>HG7WDa8Eb + >l&k8n\W:3:,TF6@F`B6JH2IX,RdH=qtZ%1VGETK@e]Wa:\[BD-j'`V*:YJmQ0D&tSLK/qi + *TKkP/th[1K5*en?qph2X:JT)F\RJr=n"=WbekSWhsIXpV[@c+<`Bg._/&q#:mP,3t9"e]@ + 5%;KUJC#D-il*^[.et#ja^S1eSLGfC&%LHkhEF>rE.>A8_Au+]_;$B3k-NHkp!*`kDCX&lT + 9ua8%?%-3^J*QCY@;Zot%>&@2(u^9[L1k`5(dnMb*s)]o,1m%Wm^WX3$NR[GKkOWoSVtESm + )33V:?.Gh"t-r0B%[ZcTC*j-2Ci7ToYgR*=`,ipFAge@0MeM@@jLtTcHQUmSHPVF8G/k?@RLA8H-oJ:E1g1P@e?KKq + t[e8s$Gk#&9VjScRi:aU$g+AmA+j\10Ga;U/QZk4JuS0*;. + 5+ojk2#[M%#A6dP0aQ@'=p^r&o)C!6AJ[d.@RPJK_e38%lZT''.pb1e%QtO-ZkFVuuNsK]s + #Ono'E460n^C>/.%WKH!E8M2qmgrGNI%no7KKbW7*u/K)l"D[5bJdCd[i"=9)JF=5k^9b6N + V*KLhiMC_lsE#5Z`\`,@X&1@U?i1-7PSRRNM.L+_h`R*V:&=f0F:%PqqSIM3Q-#Ef$1a>F'=7;X?F`TLoE + Q9&NK'S0(!U<"j:coLKQW6i7DmB:J*hV;;[h/2:CA7_t4"V^;>i<.^s"YqX#Qquf"9ckEO] + NlO2c*?uR+9Io[K:_FIe'.S.mbA$88,cY0'uOr@K4Vc&Db\9-pfN6;ItO+LGCgqC&;!h3Ms + n)cDnqEfBNGH0o?KrN!pD7id@]Ng"L@K#,W?"^"l]&o[pi1BWCaqu8;5cS9eh0`3RYl/F7FkTBpgD=^iL + 'sBOeD`Dg1b%aLZr4-@a+:b-f?iphjF8%qA"r+su(3D15FB:j.7n!!)7eCA3oRGKbO4erBf + 9nUGKd]t+5(/*/-3AEE!>#io8ToV+:F?,L!2p;0Y[7V3OFD'mogZ^hB2t@l=+7Pp%Bq#I+f]RO>-BO3-L>2S8W,g;:10Z(S\u2L]Q + 0]_.5dgOD$0d'ZYhn(bir3)?1;*`1iB=Zq5_cJ&^NRaRk3Q,CcnaP$'o99#Hq;=453;rHg) + `ba;=C_Y.n/S1c5\kP?27[fB9=e9XP1Pt3SGODC]irh35geEYRf_u4ViX]H;hg=:ei@=PDG + :V\@q&PKQ:BdU`D?#I9(h!D)B^#a(@ZJRP:,IN,"fl75#=[1I&*VTS@YhsF[HsT.M?N + 9,.3IK\=/M``4mJ>DOalR-9<^6JPmcpoTs7(@sIU>U;`i<$'rLWImYN,6FI6M"Fa5?R`rdK + @#g\h,6,QWG]^req#2jEe7)#qG!$l]Ib<@c&tGdJatgW/ + /5"@7\Ll"QeA3c]1Ao*Hb`b001sFEJd5B1tCuI9I;gdafcBGp.4O1:gC/C-"KF^>Hj1tFWS + 4?HV6d[_Z'h6T8I2>H%7i`16+7urq9%-bQ2b_\brB4!+[2hGO*G>=F<)iNb=]O#&es2r^<*(FnG@&m$rHVdPed[IEf[^e"@bEY&Q"J^8H2[mGi&q5GQ:8oVB3'33D/NDJo9` + [JG,u*:OCWc.:fNcUfht\sPp(A.=,e&=d5S>LHs]scrc$`Gp%s6s8,8Zi*EOJg-4.Po^iU[ + nDBFXC(n&^*ihlu6N1]83.un+amQlop=Rj+H@sKaP#_I:fgb4URBt]]LV^_Y6k..@+SECdH + Dk?,^q7(HY)A[t!kh7YOU1mdc[W.r9DP*hA)P3e*l8lJ`Ve[[Z\#u5kpi7D]H",JJLS?6'E + 1d#!rbQFUG2'Kr)fFeSDS*XI]UJ_hp4)P.\\Gmu49l&,n:n3\J+4L['?0LsD^!,@7^!KJ+8 + iCDDY!_W1X5OHl)8JAr=;EGFIbWaAf><]86$M%d/HUo!hdCDKct[H!W]h[H/>L9(e>C0-@t + Ms)[HD&jEUYqSnjttDkH3=*hT:oi.e_Em$Z_N\*\O6_40qA8,8TgTAXr;Q1rEdXt:*[=f-] + J+.)$`rnjpKJ!icmp&9GZk\.a;">ZVY"N&@@T*-OO.GFZ2Y;BgcC4PrZ=M$L,+P^6 + \quY$%P_VOj,XR1'p6\l>+Z__)Tp)jIB/7FA@,3 + 3N$EPS[5JC:Dr+RkBuro;qcOnH1a@6jS5@rmXNR:!C5i`"e[[nJl%=66>0p%RQSC<"^S0f3,mFU`';%V%K[ + :h)M;FB`7U7d@n^S?b>sEf@B]3qt0o]/cC3Dq?:7D)h;=K_h(]_$:1a.#B + 3ok_9dcI@2MfT6CjX/!/M5=aDVNlj?:E.!tW)#CuRt"Q5pjip^OdJNu + !1R+N()+A)rkN]Y:ko,WG`8-qS7^]jNN&V=8J#m#+_agj):Jfn9P!H(aJ`'=o5r]hdJ=$fIh!ptdfGk%)pHRj-q?[/#'a'XF + +bW\&*t$5;O4l_"82A:0L\QiA(ofWJcoMLgfJCTVc/8[U64!Z>U'-"ci+e6H#f.?3"@.b[A + Ir4M[@eZH^kZ-0J6eNCDS-)0HkpMrKqM/&8 + >ub$+qO/sh]gV/AI9CZWbTlCd[MNF0Uop/igA3"QkV[('dGK@e6 + -ej)ZtV>R34ffg,@u*rM?C!2Qa_tb?s\m,DU:tdb0!EDWUj:SbUe,h-;UuDuNl=\A()bNA7 + p6^7WjMT3pnHdIns,0Yn,_GW,YB0tOpU//*Vk?bFF]Ge[J6]XOX,If%Rgms"GPhSQup_$hO + ,7JM?cr-tpPJC"M8cj53H"T[a!^p"T+bRjlW)$1(+KFfJa#YtNG,Rcr^ouRaGi#Vr92MS;# + "AD)!$qQ258g!!l&:mUSYZh:X?E'TD#Jd4aM"W>N+OY*PdXc?RJc6/[pB?Xe#Z-9^K`hWGF + :)mB#u0LfNUE0QSiL2u$t1?J+;t9R.,\tP$V@5RXq^%%*Wj,.">1`]:3-ihD%,pK%8bL_OO + 4Naf+%6(%M7e5;!STFmgbHL%aaeXOS9:4oah]3&2=+q;%!qt%RTT,kDjrXV?ZH#*i%$8&l+ + C]Nmd0S/EsDnkq94*nN-"K4VNrA+6BAu$+T;]]N_Wu''2XnT\p>QRMeDa*,a:>DV5YV,jk# + P&[(>umdCt;ob[&cXC,uP5_,7F('.e[n4sP`Od]g!',h5GnZet]L/7qInqF-1(U#BoVL#Lc + 3:g"]BWjQ=O$[Rh-M41Joe0)5:K.ju";5IB>#o_/Rh[Ej1AB*.i?rqJu1poAACE + XK:VLER1sFH<%;Mhq58*r?Qm)Gd7T@K.OFU`2gg+0(P@l_4^&M?#>f.6p]55[o#4r;DPjrN + G0+O6$;$ke4%N)M.nf$'Gq;3#Ae"!"8N$:^\Yt'.=7pJh.=^ZAQIP,/LK3_9=2,kYN,;8.I + oR*\&o^6'<='E5YRE.kF*laGu(X9GhS>%7&\\E^_P-cRf-5#2kaNXW/k>hZkpr/9#hUi5c> + Tb:O@u02PmX?t]jCPhmXHhIDTR&=hUmW!\(KR4R\.0XD.K[gT>P1/SA]TLoFXaID]/&9Bcd + ;`bR)4Yr9d1fHauP7$Ar1NHg5n=eEG8G6Ak + S&^edu+iY<>.)g3A[!^&N:C;T+k-h<`)DeTjeeRO#aP2koe%Ui8CT3RNE3&(@Kcj1P[ps\6 + nc[(c0c&o@0mnraA'nR32_uNse/!SFHsV)Ck=sNu].FrBL?-)X3D#Tlq>/\7X.-*&JH&EQ[ + oQ*?=N>*DtQ3PU^N^e.LXh'agCWU#d?!CEGB"pQ>;kZ2jYV::;ZhplZ']##@#PFjp(61"'[fo+10l30j!F$BI$.rdP%GT;B-sSh + 'Z!rk13A"$Ge[.+!72E*^p-_D&Q9E"AUmqfTS!Pl"UVLC<))]cJ= + /L@&-X1S#I>E.oihf#]anL_#%3iT'5KNAD-%Bh=#FntgoXH1T`_Rc!V.3e0uD[04b/8'4G:JTo'_]dmNak[81j3G&$d$;'7NC794V$Y + E)-%@n#r#7WK@j'Q7ZlFX#D!8%+aT(763&U)Q"_C+mjKm/%31M?SN8#1T%=e=fJ"cFm8q]p + ,Ee[`_;6BYFca'qo4Y1q`lZr4e0iDH+e-0GQ!T*@S;JDb)k(n1.6hRjm=rE$5i+p/n(5BGW + t`.oc=?cqeZ3"&K^4]/X_UO'[YS"i+@KWLm2>*oR4[1SA[l!3'#,TinjgGo)fN2jJZtR;HWRP#Qa(nh[l5&!WNd=K'sW/8d9i11I8M(k(%5BBj*ha%IT=dEFiRdE\R!hII + YBrmTWeh2P$26*?Lu?"5lqd + PDFLge+c1`9eH2aS9Fo@?Du<_J.t:i2S/D*^,q,AN^!IMfo=Ai)pus + 9V61dNGA\%?DH+WkGj>A!)(:g@Dfj_8<8EV`RudnIE&e,UJK,;ICmd34*&e[rLkTj<;0"G- + `5%rO/1l!)066eCF);jnaT6JpG6bTF+5i@iR4XU?:K&'2Fg.=kG@PeTBHQ/a#Zq"f'k@W;I + 52[]$d#L]!."mC[s7Y^HaghYR>70_Kp`g:R5p=r=dthIT:'u"RG"F*)5iqVX.'j#2bnT-au + EiHc%.mk(\c$=L6/+;b-P*:;G*I96 + rKRDY]K"+g4dTir)2_>XiC`XT:7@+4_"3&cKB4+mAaK:j>u8Wjq,RN0sUK[2N02FF:i,989 + bAG/`.fO]dAeYU)K']FA;TlLt.[.a3-LU$Z1fS)`EFWbpeB@7.'V702VI%#- + `)F1?DD:6aQmnd5rqd)bVb#6)i(rXMn'd`\Qb-*H]u"0T=N^O\[,.\8i)s1&#"4aS6NIm3Q\nYkFC;ESJg%lQeLY + kn%BUt',fqp)=(T(n\,TO$:(U!WKI9)'EM@]TU4->QkdBJZbRIsJWC#<2$OL>RMl4.*SN$= + =td_G1Vq6@UR!BR_1ih9a.u`sBNe'HQ:4,DRUO.NrY*+=(Fkn`iOriLG=7mFB + )7"52Knl(HUYN(BK29VHBkI/F,BaaMH["#R]"6\WZ'jk#/G85OBTVR8pV>nX=rIiQGZ=`bA + =c;b?3B5LO'_eGT:MjN\nY6Edt7Ac#31&Y?:TL3<-28"&<&(c<6kiB/&c6Y'k(PjgD^ + :++0mmOZ9eHqTT?NC:!jN1ZWTArR6K&%7?g&h#^m624Y$]o=`p340TGIbc0^f7?jGV>^8VY + J^ha>SCR7sIQ]jM=rC2gM+hAVjeidZW?"qkqCY'SFf3N7FSTEDrGLtZ'fH#b!l6Em>\EDWL + J_c[-]pjD]^V>-7TLuQ'gZ.#"bM(Y&]g^`iHe=@cKGaZmgdNB<+Ng*"EWDU&m^`q^g(E"dQ + /3Je)/fn4TZ!h'YT)Qi_B&BeZhm#mBu4SI(SQX(S24[/:PDh8Kosj&<%Z&"?fDu/'*132C7 + 5u*PT8IeVn?Yu4DBV!Dl?o^BHBZW>*6k8S&cG?ML(o9I#l@YX6%p,)A]%`_2KBW*!+tB"lX + 24J\fK0^WO"*XI@D93m[$HMesHFGCa-;_7?^2bN^(nNr7@#(A"_Sqes.+c5Ql2T"D4X((qZ + =?(l-Uh#3Ai*sDNEm6Ub%U&G@+S)2sQmSI'j^gWF?p!LB6/=drk1?]#%OmYErn4GLc)-=4O + 2mn&n3IO-"[qZ2AMqlDc"or$;*DnQa@5F"*$N#2&]nQ'kM=]UloH-Tps%Duk:%Y&Roj:YjT + 2G:=5b/hS&'WIIlO"&pg(5]Iu3r3hqQ5?g`MERT7E^5+/15?Ag + -rM^"N8VuSr9Sc$_C_).F:f(E<3r%>qk`$4GJ.=#XP<.@rlo39E-NoA;\&h>@PN,/A + j-,)#R'Pj=@/upeR^S0q7Q#`5G;Y%1jU!;s%fW@C0DO8CF<<,f^.M^L47`m[bPcp9RXYNrl + 8YjG^M+k+,n4unt5j"1H1fMkJ,BbV.Nr:#@"C&bXPh:^#8AS=DXo8Qd=f&Z@)Yt?ant1p3` + Lo,,qcPq$7*iVDBq20at8h"TXX&oF1c8YZ(o55]Cf]#J`\4a?d[^X=,ej&>Ulf6ZDZI0uOY + <3t%-["@Ej@Yp<6`71jW80h)b:_&11F(r$p.&\Mm#7qS=V(YEh1oRc5OEBS99iL^"3]ITQ&RJQ]og + Mk[Q,S%K=&p&a@p2oUZ2Rdhe\uZ9TAi\52Tr8==;D/g=]U%)p.^Z\=ggUqB]\IE*Kd\^N!Mf--pB3_5O&91;eY!;r&6Ia*M'9[H2dB\'=Ai?ao.sIGd'X"b:! + *2@G1GQrX`OiAbEkLLN5\-\u6!Q7Io'psq$W@lWm8O(:6T7uu`A(/9s&q,UF[AJoYmQfYc4 + B:3ABNc1A!)7FUXB6j2nTP(hGJ"^Pe-BFkb3]<8!BmOrlW+h]dT;h*:VQ[%93j,rnCRWhY: + m;3=gL1T0d11+8kPirZ#Jb;K(CS0#r)(qYEJ + G&FaDnkZ+3F($(E(Dgr6I=GF1g*fc?&q#2Ka(H.E0d/4Z?HlFe)TLiT + <\-]og`1tG$pggI2TH[7K@#2\_AGiMiA;a4&V1Z)MA=#P[1+:d1PZ'CE0'd:% + jB^kL_0P/6g0@;7`7BEj8;4LTfE8!E6oO+gkl[\W'gp]+c>f#O:e3^8I6OjN!-^GP4EWfaj + ap^"SO\dnp4.XECZ#M1o1n\UE)t&Ft9D@4NFbA=X3HK/1$h=%q$T2,&rl4%Oa;HBP+ikds'Or!+)HfUF,r)8SEhq2WUKD;kWO+QQnD)2IA8k9=^GrO + [j@c8g+S1's.7^!H]-j<4t#^pT@=!Mce8RfkU=+]<#UG:M + A8KIjU%X@reSr_+>@Jo()N3;?&Sga7d04ZUXXuA$bAfi&fCYEoVRgM\knM%LeV>/Hr + OaX7^^eM`SUU.ii3''oZ>IU&HUD?#HdP^:@:lBjE6nYngjK]N;-)?L.94Y9Lbb&,QN=/00G + L(*:FETjqR`EiZJQVT>eg.'772s\9#sf"5_H//-WAt_Dj=Si-YoggnSHmfPgge0s, + tErVFMMk8@X=l&B$hm!\Zl*+QJ6NWEfG#'Rol!DkEL+_jsY/8o/&4Y0btqpSCnJ5:2bnra?,3)@KHNp$&X#hMab;S(,P;^ + Mo;&;`[qHh,:$o\\gKm8bns8%Gt$."4unkP-<.@ZG1,pEYK+mE=aN0,g*`5Kc-*N`l'[Zb;ufSI?f/7g9)Pie4\9"g(cc%N6c4!`J%6Zo_HTto@pia + (q3H?3Zq_E*$SkQZRjSLjrW!AcS&DE4CJ-CO>Y#L@N'nUrY&q>h-c_uXd:pcg,ojkh$6E_f + 9g488IIT3MSNE&ms7BGCZCPV7_eg)3=-Pn;pp5)\ql6V'2E+/?30N7-QC9"=7>V"DNmE\sM + TBt@F-l!6#^)aFBg#Qh%ontP_:Q-F'7;WecaX)kkWLPX;qMOF?7mtlWCLWp!1'h^6Mtm + Q$';'o;QjRULJ$u=6eUD=4lB`n,cY+NP0Dqa1mD[r]aBYhpC9Yn)t12TK?sU==aY:-R7] + RTD`eCp<"7*%D`um+[f.$ZK)"kg]=*r!j_ta5XR?C6T28M#/F[m,")]lq_s91Iup$X/Vjme + n.@J*5="ck5l3g@lTm?W0r#(4Zs4:i0VBXp8>ChSUQ\;9P>&fe4>bh4[-Bc\qEG?L;8MlGM + `=@_kZp>2!!,n2JX]^d@Rn:C^9s+q,eI;JP?YH+:+_8bKE+`Sr%4aU0"*1YNI3FVHni'M>/ + Mk(V6alr1c;-$CI&-'94+ksk05+AiLiP + H&FZ6p]7"]uuidA\=ICM(b3')[O985E0L,`t)\8mG)`)4%^MT%',37EpW;iiG\0K+Ps:b@J)J1(O^MUT2"C-I)?JP@_$%icr"24/ + '1"%4_,Ecrb=9<`8O7Ts1o`S]][=+0:/*Pb6/"Riqb]Z[n-39G2jaV#:^MZF$pmo?Uk$a/F*r?U.a+JS9!Gl + Y(<1-c`e1*bP.Nc&Cq?EY3im3re#Y)?E%HBV]**/p6a=["FY):RKCD)t;[rrg:'X14];0dF + !H&&`;2:p5!"=<.UlS(&0%/i7d[`)?oeBAK1"/!8dmZ#j*;Pa&oD6HXWm8Y&8l/O`8h7S2]3an21=9Ybf<*`2IC$UK"?j + <)8"&Ln.`2>fcW7@S+!6b)?hus*W#,HVSupABod1_aA)`eETf)l_Y*WVA)%GrKRBp&_>CW!WXBqUcsi6R + ^YsUcV3siAG+X6It]:N#0R_`?oS:B7^p>6>!meXX+<[PVSq#_W'!i/Pleo.WmF(l34r0dAH + u%S#hnoOP"M+P,mqVnZBCkbP+YPR"e`*X1P84h.R[ZA,[oH'@`ChO;.O:dF/(6rB?#qGX*e + \[XW,ZpCe!g0XBcoG%!"E%,$]+hdPQ'o3(JXPBe`FM";#!>K=TIQD1l"[#(%(Hh9Oh&4]Ac + ;h(@,^2:UGne_PAu?%pkcNS!!Og>/U[h4--h;)p30i^GuFWZ + k#=cD<-@D2Ptg,3kh`../WED&`Tl"ULbaTR4P'KBbbPkQ/Bculc/C@H_.,sdDACcN)77C"MOf%OOKmc>:eb78c"g2.p)VcC(]f + ^=YttL2&A-3(8^6$=[AAW;<8^l/J)Y=%XT`MAK4!9Tj[0)N:23lh>j%+&OLCF;=WH`c]p(m + ,Q^dqg,"/qQ9ij?JOHmEg2_^? + "Td>J!`1`U`?bZ@eZ?_WNS==:>6-![e6(HG%,3WJgHr?&[QS-VM?51Zd2ZNJV%Y>D]2;74> + KgY_gS3)L^dafjX1]Kae\]_r?H2BO7h*HhS]B.sbYgUFQ0;/#3-r,Wfe!1\K1J`h7F"er4G + /GLKh]rbmGD4;DVaGnk2pVpp2K1AET]6"B1?q):35T!G3'K + GHPl/OT]=\qtL:8TV!5D1.WcQr,8]Ko]O45'&NA#Kri'pkokommRI>#%D^Kr)_b7(>-Dg5\ + WNFfRq@8D;#dg<5IZlrIZ9#LK&;\MXe+q((P:k)uAK!=m[-k)9P/lgrP:OL?/-X;gdYXU9=@E$kbW?[2hKpnnHtaXMBa0#nG(4KYO(dC#kVe:f< + f(g.K]m%b)if\&n5n,S(@bV^bXa+dG$<&o^TQ68Yb?j + A)oO!rp#\1li?"`Rpbj"KZqA82;Rl4%@j-"uR&1?q,[^o2%aW'LPG9efIGKs57UMa1\a6/` + nIlF%Ak`H>ThG'!A\=,*qF[:2;a-oKuI@=%2FdIKAXSJm/sGYQ\gEK\0PdleWqqY.nF + gLuNXal(W%ucqjon_kgt`im]_crl7d*0B3Pa6[gcC4RH9Xb]U.>K=F)9Et/?R;ML/$j-?+o + UdAS"e$j:([rl'\'C(9jE9%efA1$!Uk1\ldC@/r6`:^+jIr3.O8^Lf&DC/#doN,64:1WMKm + T$Uf:k+2/?&/2"!_6hpi),7<@-(X&DUlA1%2IXLIbX#SjhH>nYCZ-p?>hm?dGFNX#%:C5+` + EBN>aL9%LfO[eIt=O:n^>bb!"]0^AQ"E9>ppD<806pD/b$4Xq+$YEH)I/-f:9o`%P3VtMb%L%%pqN%O + ;BK$$L50?tuKa4Dds.]$"T_gYmH4HGMRJQt>fSkmup35LXb]"?^dH;r"mQ#[ + Et/Fr*XQmGUXl>#F%=1H5UZ]$0?8Hb$F"p/ri+ikE_Nr*I)eE-G<]j/d,B)p@>5Zl.R)gm* + c"2lFCqrZ^ebO5flSis/](Ip + )-8bOr(%fh`rGB?)S,YQ-.`LX0@F)JSmbol`"Wp-=--#W[OI9JK@&"-?+AO7giLVKT'@:_) + :SblN"EhKJ[C2:\D@Q.hl:/+6M$u?i_@ + 287YUY,a?_m8Zc&)q,FY$/XELbF!pn5BXaGU(5ln.nok%9cR:UW'c)>-UXE1BMHg(L:So]m + eKp*ts&H,MWti4rFoJUJ=6qgQ'pcsSYcH>.qc0n0DR_4apF#gJ?Zj!:Y8=h$p?o%a5O]L@% + M'#&`';e0a8&Q1I?NZ3;D8Xf#,+l+'hYD'D>pT]BJ]K*-BClqe7j%dI9*g[YLVu^gFY1K9( + rDgJN5mUo\aXdm5S<6-GtbZj)TDb^-=QAcK93E>oon2XjcV/HQBiPGI-2pGWXb(-]cO3oQb + jKIoj,4n+HU,h_0c2mIgC*5@8otkP5(lcjT^O^^qap!l5F1'a$a`9H+/Od#9+S_=4NP$UTr + f2%7Ba`'"RInE`FQ`)/'Q'?$#$9bbR/>[5^A,lur\`f8pA4V6cGb^[Z1jEDZd&c@>)8 + +e/#WP(1SoCm?BiP#9OP8nS"%/'=%!XY!0Ygq>>a'&ih/8ol8u1ldcr;S'6_Ebe%iWiP&R8M,oJHk2c.&jE9?m&_5Z3otD*.e(i_ + ZC'bZ,q%-qVjHHda6G?hH-i>Het*7Z$fu + 7rV(d](^:+Z?oTJBKNkPjGNDEFHW]'fn!6kP,T]P%b,JI.c[TD + TcJ>1!T:)GTUhlc/;nFLPABpJ+VPe:1Te67pFOdk!r + Y/G!27cf_4GtF5?Qqq2a;)rUHH;G_SGngo^5&?EqM$GdJ,8$BYO)-5)3fT + %+HA&#UnMa6*W*nQL1L6iB@lkV"rGtg+9/$fA:D;6n,33?&+KUSqu@EtS8,c*-49in3u?ok + J&&Q5"!D[tJe1V*MrBKr@(Nj_ + \d,A+fN!i"9'<.aEJ/(!5F;O#[P@lFV1?5s+p1p,fI3Y/k,:4MBj)i&:tBZU;7G",YWrTGe + ?J$aL2H\/[A'\a\q,&bOKs512OXkEsX5ToU[dL.)lM=qCZ8Kl.P*GZ^dFKK]YB*jdC`c)E3_VCZkq[OQm\W_7&D<&- + ]=lGKJ>CT2C*e]b^nc!d[gNI3DUbeI7;BjhDk[P4Qs_^fd<@B;RhMSY-LM"kWmJanf+p-d# + =""e(B%@1*cCsHi[M.[SET1M[ + WO3MD\@=9j7Ou@6#OWF@\;P+]SrRCYhEBP:[qY"f\!HZi"4VX8EUDUa[<]^#S?DPC08:VEqsT1*^/*;ThcKUPn&,"::^ + -mVg\="Ki#Dlb=9*j`8h,SV&qTg2"[QfVKAcG8K=FYh_E9CX+\io/E6]<;r&kLo"?;1hNZ^ + ^aIh=D'9Z9ji-K<[SMrLg>`ZuHS@p$6/iqXLO=Qk]pO>VRQ7Kic9E)-qafc/&?E@3atTB+Y + rPKG%?O-0Sp'b%*A8t:d#q/7iV#oT)jI$9^k=Lr*I0de6lBp4^5`9,05S)kK`'Vj.Z!'gui + _;IE`;SI=Jn;%Xn/U/Nricf'@q`;)JL=$nm7;1KjU\&/[dFq)_Bg"&D[)SB[3aYTqh)VD*h + P&EPMbn.O9rW8)Ut&Y@7k^jFiEjA^ln\KH%B4'2`[FVA2_lJc9@4XUQEE4+r!n_256@;Z + =gAf4H-%`A6NL*b]9k$+E19mqWa4W(MdHn26Bi\fFo1LlPm.WNHXbP4]CJE.B/Z#k@WP%j] + jsgOZd+/>I-j;Jssl;mMUgZLHEmLNF/pCjBR!9bIlS^)B07G;ohX4c=rf6A*GjEB:gG + Fi[/]N9gOhE/u-00@ho+]FA%JKcCK(%]-m`qs,4msa0L!:a.e!-*PTTRqtu%@AZg5g181i6 + E%R:js%&fLL[i=/!P,F4*EL6&BS.-H):L(1Op^lXGT&MK)igFNN3*4FG#"+%fL`qH7DsAXO/'o&a?'dQFgI.3g/=!HcRaC[u*+b[P-*kqS? + ['EWf1:r#=%!JY!-C6R@gLU[]m?q7B`9j7Sa=TC,B!,qr=O9GWg&HM;8!72Sa0H1%3*D + $Mb7#B4QS9q&"h>S_mO#>faQM@L-3oFT7M"or0b7DrXIfJ&MCX(JD_",U?[k_\#uU<::1o(J/LYK+7?Z'?q(9L=R8f*\dPDRY0ac# + JF.-])J>/DY2\khYjSE^mh*(B_0QrLkbV,^V%LY!6145\#(FVTnIaE'Y8JAE!Z9nq'#?ICb + le%i&clEK(89=MX<<-K15$/p_N-gs&e+D+4!?7L`$fMY03%TMJr"Fmos-!bcXS0fB#[V[p< + RdQkd8AY]SnU6pRrQ>1$l(Qg&YVU'd;-T/Eh(DW(^iU9&?Q'D^rd<%68jAH%:MThU-Sb*3s628/2G-[h^u.m/op&+ZK!S.kq<\:0/E>VEpEjF@0Ef1$F!MaXih%B&IL<-##Jd` + Y\+6GGmMkD134ToUpgTo;$\k216?Q3WalqiNZ5'Qcp:VH!J`CN:u$#n0U#WsF'N4+m`3+(c + p5Y\i9(LuR1.Pe%'t3fiNudMdjr36'Hp3fYh]dA'!2W]%eH&qc6Eip-P*@/3ch)=lA?3k": + ="^$dLsKZb6s5-Tj+/j\[F=nM:g_MWRBr'6-)IO[2.uFDF`CZuJe'$VqLW;Ws6M("$G*!([ + 7.>3hk7';9[fO`p#f&Jra!6#1re62s6uM^uUn'1$s_DTNdad-X]/m>g'BkFCt)5^KU%6uMR + Ai8P6QYce%g#sc0E$g/O'kj\4TnM/tlC]?4'iZI)D"=EuK!1XB=d3$[h+B40gEXO$)OMas3 + oTC-bnfLHo,`&`Qp%53%;DBCqYf852,Hl)4&k9o`^`t"=(IA'p@K$AE5=_`T#sadc%ur!_P + UFuAq:+Vh9VhN*Er(V%-d9)_&q^!ib59E-.'erZ'b$;-gJK_nPOI$O%,MVJ-l\-K"/%[Y@8 + msbq^s!Z"@-TZ5QNDKoi*^D<)WZoPtb5#;,jr%<:s6k[96N&=&q1a<`NR/F_t0eEE8?I0@K + pJ;["l(?!%hpl`B@OTnjS;lTj$CEfko=<#%^MJJ`JQl_%Je!-^cWS@Y#O6VF[OB9MZs58uFH)m + i7QTmR]NliM\f@I!=oJKPa#>t-YYnCG]N[`q + 'm!FKGBikIGrM.:(S,hqT6[o>H?Z33\"e9DWI#G`HT/K^GJ,ZjAF$XQ>h@)&\'8"i`E:S(I + S_:6I^QS3NPH+A()'HS'#?OBNPSlobCLJU$DjDTk*<-MJdi+\?.sdojs7gAb5Y + SZ!&6b]A&P&F,1nT;'`Rps'R:UnV-=X_V4Fu9Y)'c/",6dmUbA@![&eB8i(6J0&9\mJU2U>F)Y:_ + NX6i(l_+k%Tfn$3OZ0d5?M^(BV:gM:pNV3@!rtR6A.bC=o:lX$MW$6PT5lkY + q#VZh7n^]o.Er%UfrJe4pUccTp.56($>bq`R7.:Q?$CF8>S2&@qt+^)f9'm4kAMf_HGoT^G + q`[W/cER0L=?:_ggTh3ucldqTFs)f/Y!Cp[c:FDcU(/%Hi'P8fl+_>XiC`XiWl7:Wu_g,OQe=+bYRNocK9uT.F`EZ9ZHsO_(@E"aJ`VaAg4DDh5D9"bSM?r.3=FpFlg3\4[K\i_r&A]%oDRoq#3WoWm+*s-'D=a + `SiEj*fc>(!sQp\f$54;moX#delFpeffj/a7)&":P?B!*4F1L&#'!<<`58$!_uIBGP^&fTq + CL4cY%:*b*E!Plng5kZ$"R;F;RU3R>QdDrrns'k(-!rb;bTS.:k?!+6'DTa2L^$kXWo)3!k+r:-E7GK1bga'P%q^7"6bQg-c + *l<^1LI]_nLZ0IpglMe6A>^E>rJ&ja-bZr'/hTa.<5KTMIb+@5,b&PQ0ccTSMmU]d@iSXa0 + )?K9;%:J$'Ia=p^_sf8A']E445R7@_$L=Ai&HOW2Yg(HSS'G:)nnP`+]^;^]n,NI`i*?/5P + G.UH",:(dL1WNo^J"4XrX0jY%8OJB5e7DJ/RpphZ'I=3#)ECClo1DY$K/?%:#Hu(8^s;*AD + 7WPZg$"e'oQ2%eiP&ti@kq]GhouXl_&\4+J%,OduAd)lPSC/>=&/`7?1VB%Ue];EM.O.pY! + (Pg%b`YSZ2SUn/-_B+5iq%^KB`.U^DY(gfYY\+8d5i(.8')"G+Bt%!X$HK*l!@TCDW767!A + &*`%Xn"E$4?O%87D)#8j+JmU4hnp*p&HRtI:TBk0`b%!5s5M))*bi3b.S1nUJ(#&ch6__sV + eR1KrFWNB>d%pd$N5\XL.&16@mVs0GlP3VXg\+l`nIo5h;@O@h + <+HX^0fI7Vu?o+Jm0jHG`4g4rc1%hUfe\'8@Vqk/Jc\*SnC!Nc=c`A>mpUBU=/9_g7:]qBr + lfc#I`PQ,dQ1+^-9;6o#Y!q@P2c%!3._5pu")qnB8ThLP\V,UnYC.D7ks(K + +%K33HQ^B"'T4/+nf3i<8Q%sC-V:UsR41_uS>.Q+o!G_QbAtP3/PEDQ^G/C1,f;C]F#;?&9 + liGO2,2P1h`4FfUu=C1F1gg$:NP%_4Oc:2rF]B[]lD-PTQ^N!L:i!]\fo^k[TQA.QCE*`3\X^ka + \T^"ZOpJ(2Y%n$.3H^H]qR)F$I>p&2XU*XCk7DBYVPKJ=6C)!6,C@uTUL!$D+> + J<11L5R7iai(1j*YUTjma;kCkJAiCYrX(r).g=k\)2!S>@LTO@)?XJ]I\oL:!2(5]7C_Ou6 + R7/ufO##@&Hdc,^)B@`CdFcB#U&1JL-tMZ&4j/p,(rd%O5mMVTdg!J5qk$Y!V/lTM(U-[<1 + 66rHqr\N=XQWl?&QgL0@CQKaE%_59Hf=VL+5\(nRsN[n14aJ.OCK*N"NrEXqi8CcH5Z;BSDtN2+F098lJn + EV+IdJ#2%VOo4i5=oWsi]aPr_8E-#U-\U^M;MfWp$1ZdVJIjW,X6C9AKeGH!N:Ts]b'MB*m651$q + k!EQI8B,'5Nig_TTfM1:c]+QBLN<&<&TEVqAD[LFV1@4:5_t!0h#d#(SarQN`#X388.Z?U_ + Je/.A]$d8FU^3$AjA=MIB0BP6[t"R=E\.MbY)m4/Z'OS?#*<\JKrdW&5'[<*G0iQUNA($a" + U%7@-fLjZfb[opLl68-siI9I'YG3/?$R5F&W2VNmIQ9h/Oc^Y[^Em(k6g/7Q*IcX*Q5V;1'W:-TFbSf?Oca!kZodVF7-0[3epu,D + GA;t%_:CWgRiRnj0j3Y%dQ5#BJ.="3X:3[d?5W&&Aj(M]T6@_^'D])02pkY(U`p"Vql`-KC + =6H4:"/E^1?-2J.SH@!T3YUJ7)=^^lV.ni-Yt<0N9"-E-;W*nbsBC4ef\7Uc&L@LCCE:3JP + %e*(*hR%cQb9L_1.26s,9W76msp2784sO@BNWGaoHm8h!2bXA.^-4NEiW09dm*(h5!c.Cg3 + N`lQ\mj)JBV!jl'inB;e=BGgmpLlkAUrK^*PK?KDp^ZFJR,0Vm)Oesg9R#K%#VQg"=D:A&88.*FX3Ygbf4X>PTOieYQDY6[b;jL8AaI#NifI&unA#l/BGUag=HJ&P% + l+WOe![dhoM!r3W+MIcRhJk%+)s[sLPt^)er1,jn6CN@4XEI/G<#aEnqcIW-J"EF?Ydo9YB + C,ffNT/"m!X_r2E&\1RkJDjXeE(ENmHE`=))T#Vm^cWg=mQbDf+=H[l5)[T[]^`d_QH"CTV + 9OeCSn^h;M>BPuQU_=*j=`l+Umq:u?'%>?%W4.g;<\h](sSqZ?l[\\Q:4lr3jL`q[1UKNVM*RLbFK:i\ZDBqJ$Ht + bB*P7f'MdnZeZ5aNt@0\_Nq_7^jHb'nZ4o+AZ=s\Ttjk;%rH0U=`@X1`%a<_$GgC5%O'=j$ + Xn??Xs9dQcDor1hViLXL"#I\c/1WYnTE[g[24IG.g;$c!EV3;,LPi\MW&;.D_'DP08fGtf? + &Bs:%Q)#deO<hd*(Cdf+'8UGqKNg.+G[#6(c0cOkW1?9#k8/Y#Gp=>2?s + 7*^)2L^(jl0Fn6sTcpm@"pOM"MKr_=jXitR"@dJb6k+KG%Rn,ihJX;jZRZlPOlRUbQlO(.k + DaFBGN7X31@Io])hAT1h2U%BHq.'a)o!%W::&;[X3>K^/m66noP$emBE@kthM1dF$MKrA5] + pcO2`l9$0j]FZHBLcC)B;hngZZf4lugHVNE6,<=u-&Y,V)HmUi7#jT?Kj[r:cohK`=TR.r5 + I^d0aT+089dXs%2"G4c&o'_m;Si3F&#_;e5Ya2X#QTA`":.6s!f[VnfY2+)1>6[Um]F?2dJ3l@@:[kbn26Zj + 745:a*)[tBT,'8sdXXJZp+`.C4R:&V4)XL#N9?/(QCJQG%VLabgIYn3MaN$37/D0?O + *EaA3"-hk?7m0_=8;1nl]Lj?>Ze8k\;1F6H53k5rFN7-')Q:^)Ag.R(pM"X%VFqs^l^nhL" + b9+)q.6hW96,_^2?LNkrakYqU+,6I+a`Xtt\g-@FI5te`!^]_=r:r>(/!])))1JGVM>QkB- + .>"Y#6C7n+:^7&T$3[k4L;Q3O6k7$@5sYlGbfg_15iW"j;[7P-K:l0Ep`N4[#0W,A6*j?i^-aQ`IK?quJcpgY!!rXm$A1;qK>:[piP%6 + uqTls,\<(hTaA)b4't(u7]J*HAaCiJmQ:8b/R?V$sh&9.])b'C,VeYRQW!S!I$_g`@JZ'Um + BM!nDfnZlV`e1#*MU+8DGD#ZW"Lo'F-l(*tciabq\uOC\gTAojElU8Edg\!$3R#Nl$&K3&! + 8%ha#U0>%&Hi0T"^k74?tG&U@Y[7lL\-)FgU`+.@Y[WfL+<0*pa^sC2:4)qaCR/dL`M'1,p + &.t4BokUS;PD)p:RM$+,#\P$d?]gqkn7IL91g1q@\h0*X!$B"[HFL0RHB.=!2=H8Ut'"pii + ]F9:1toaCm:1-pYoN(pN(oat)Jo1ZQVF'M;:@L)M#K&oFWkS/SM*!mo>K]$K + CR!p7/YG21Bf*nr048"KpI`f`L"5#gA5UZ\q2ZYTGJYDt=Cq:l!6;,KTbM3,o>ej#1"O%61 + 9I568J6Rfu+T`Zu,A?)]rg#5Y$1e->7Iai*O&`oZ!/fe>X.d + hHfrH#/Qa7&:`*MDP6cGKU>GeEiq77"?:WO"&LoYpdYIS<6!R#KofPoTNm3ODBVhj"X$eL+ + @H?G;$/4'K?YNimjq^)/O=' + ?(LHY4b@]!IN*!+D3!t[6jr.B\o9n`RWKcj?LTN)/GD?UkV#*mL=+?kGa;$,r8"3;:0NV6L + L7=gO=!YC-Bj^1Uq03/iB8im[:4u,<-A`Gpb@nYuJOF9\3,iI=8[(lh&QlCWB,m+o2"8FkY + W(hp65_WT4KKr^o=@\)/6j-n'";jK0O?NdA<J^>EhU2T,>"(^H<=^9P2D4'K9BMGpW9q@aYrcN + kW4pBL1dl7[7TFY4N0nT`cKSVeofNa)#7##AH"GgmX#],/b6j@$^K@jFA^e!XsH%>Ig?r.e + r85oiVHWlSW>@"^OhMl49\*3gOBa0W4%[(NIPA(esBeGY75*]i"Q>(f5l&Lq]MP`J?Y4VOcLKT3L`UgWGr>:81u,J!W4!>X"hG?D&$@S;,)lJ>`lfSuNUKIKY)T;2sL>m]L<[? + X>(`?66@X/Pi8-@>7m*RW'h[n29L6Wm[7LL-TC,Ke17S]3iRj/X?9Ajo=63^Jc\,CNdO"V? + DV"@#%2`@4i2BZM6uI'\(\KeD3jXH:tm.F._B?JtOX[DSb8dFb*Y!L$3bjm9n'U&T'L\ita + FO5`&uO1feMG"jgW^a<6'O/*u]Z*c=bYH#3k"M(uD-)(CG,R$YjSenA/2]. + W3U'$h2IR(dd+['anpq_3_AolH6qXmGDo%(2FtqRPu'i)5N]Qj!;@FlUe&pG^]\_+G*Z.4= + $DHaBaN3E4VXQ3lV!e?2W.e^#/9,C[W\0%j:GC&qP!=!Q\RO(j51?&p!ljiAS_UanP.faR_ + P+L4db:0$8n1bFe&,#NG/l#:hG&%tM`$Kns^8-"(f]@Is=<<]8Lg0%TEF6Z%<@4lp;_W..K + #3-28o#jf,q`noei6uE3,fC%X"ro8+gEH\O1'OC6iK5lYbRR`&M59$>'&+S&K#&%b,S$=>RI@mRqJHN7`gT+\\^?a#snnn?rG:; + eW.:;nsnu%EA=HESD,m)3kLVC`K"H25&`pA)4F#Fg#O#8n`2"Ou%) + ta:LhnD=cmCm5I8D2aE1C6@-3cm<>[S5=Kn^@L6DG"/%`/hunR$"o;!>VE[[G^>F%H;HN$T + jZcL6+&?c883gNZ==:ccTNd3H2ZnS*-9nkR+'%JDf@bp\=dN5bW;?1L*s.NPK@dr,K\cfe3 + !DS^p%g!.%r6?dC]+4YI1OX%+4,E`d=![tI6Z812qECQf)8t-2W&>Jm[IMq,UM3OE;EH_GH18<>A%%/IXu->W3//ORtM^Mo+]q!6!3b;&"Pr0G6GWV1_2Q_nlt< + 0uX6FhG_Ejr-9='d0,"Aa>*UH,05p0LEssh<08KKdVPU(8lC&7.*@^cXXqW#gp&J);XD4Qb + Yn)T0[-jCbr!ZY>gG=Q;fp\Oc;T\d3)7qmmlJc.h"CZl[A7OP"%Jb$M(H*:iiYLdaE1`o0 + oL-.g:(<67iW+][0?5+`J)n.#_inXl7TGc9R=85)TB=-lRV$iJELZ*'#'ZZ.)+VVab)-/N.XG%WX=Hp&]WW0pk*UQ'!a`@WGY#&p,7=[cI!Dg.>5.0 + _//nWRq@!0/@/?r\+NF:bca(hP9^facZsObCe9oM>;I6`LaaSXHj&m-$JREt_qY3=k0G*aW + 6[[nn`tA@l@ut`(+jbb[39ibmXE>$&%6LJ>#Tb5+H&j]E(6DYjM_id3`K9p)+cZ932sgn)k + S=68KJ!GU&:b;h/O7[r"N(?XJP`'PL<*+/TiHU77ag$inR[pOa\rQ(qUXF"D!HgQ8D'UeJY,mBoTC#2b+_D*92EhN`" + Lk7sO\JA)qlqdI'6>f`<5V'SVW'Bj*$!4<28KLLdG081[mfZ/h.7=R/Ch8X5npfUED8bXt[ + Qfg.%J'W%$ESV=9S<@VQi$t$(JK*n;]1$'[YQEo@G981YE7NgIk5kMBN8*gBZTGJ4"<)bXX + >g0'Ec"9Z'BJZ'Nd&*R(R`uI\ZrVG!L]#qsN?TO<+bDI>4'%lrBKh9PPr)%M?Q7Lj+!`O(4s%?'11n_%<,hP;@9+X(kXMe"<`L7cPeWg5R + 9R[+Zr_^!N,"Lm3QhMEp7:H6?MXpe5I!Gl^_gqVSu*+,):"&9&mN*o_P3hdJb4U#bW + G.8r]Gs&ct=+G7R#Wjd.onoBN+!0U5f)]lO`qT&j4Xc\:gg(VohU)sD\3V=^3he3E8$]bj- + 7=,SfaejEOA!+6G8f9.b(!)Plc!F.sIJT*2pf`$Atnc,+-!s'Ma"+]Q(a?ZN$ADhI^ZHu#/ + fpF15:K/,Pn7p:cRm]K[.&\.p/1(S1-!i+PXWg<;"TtuP'uT6`(BUgAO6B\qXlM4Gd*2a3= + E/Vh`^Q?,)5,7=\!#nA9PNPdE$C.Z3G2%KSQT''N1nV[!m?`,=Nl`ci1^rR&AK(B + Sp[u'rnR*Mrh,8-JiDs,S=B\7U!tk[B9E0M#Y'3fH1Zat0m#=kW-K!5n/m=`Q3-X]8SJ0$0 + *W!Q/4IA1F&\Ne + !!"1@(+NUgGbXYaQD_Lpe;r7(QAGS6n0ae#N:*lPP&F='j]iH`r%+%hKbuqs1B<:d!ims0Kg$rfA`;lVPJQOFX\_k%YMd/I(//n\!TH$=\VA-n;H<\*p$]kh5 + L0WRR6_8F>mQ)7biP[\_J1:^fs!8lI9F69sjrBItDS$KEm9i,SJ3'8)lJ5OCF\]?pqVPHAS + U[hd*kU)%#A!1,QaCpbe_.QIC$iU;[s&nPgsnjuO`EB&GFTJ&46@(BfL]K0?Nl-G,1"1eX2 + JJ>(#f'qVSm/DC/0g;oJaa18r"PfK#G@gJDuq8[9E6Op"e_Lq5i/^gEY`)$*tA + ,inEg_/%cAiP%uq"%Xq'D@,2l96Jaj(/b:p8?-A)EMj"VI)/iFJb+QHm@j'c+,!UpSLlkH+ + 5#>iW'0[M&+e-"Vm"e_4hJDLgprYHh2*s#U]lZhQ3@-3+8\*e4tbCp`BG)&h?<8gLbU$`>> + `Y+q("+X@uYsno4g'rU$'`h&O7.fa(k_]l2mI@4qOfn,7Yo;#r)j.TKdBsr+],WQ?*)Xm!O + j>"edi?cc*>-mDdG#]SfN&LKA#p77Z0oGBPjbdV+-H9cO"haf$Qq)NnZiNFOq.&g'-P^;o$ + RU21*6#sruY[J)hIJk_)<%dXp[M;"COb(0[tb`+p("X#XF7Rd)$CSj;adYqp]O"!ti)km6JJX@He8e]7&$7Y*D8(/<&;UDnc-Id`uS9r64[_db=Xnrr)BM_4DS(s">#X0/E#= + _,qI=W1Lgh$Xg[Y!ltXF7-'>V!.Y=TTL8oj5mS6W=;Cqp%g.)CYjXm-,7eG`i,gH + !;#q5-14\d/&C):-mN@+!,nY#EitP,G<)4,Mrt:Pr&p.>HKEr=/mLOjE(*K%P^t](N:Hr4r + 5HA%VJ2da@BF/(F$@qkA_.XNk80.5*+7tsmd9fJ-@nKU3k0[pu0ljdaG3>fKM_S*G^l/Pld + "K2$G[NW9^fh'fU(8CS&:of^;;Y+:#G8+4;H5dJHi'Kd"u*+B:KH&f4L^5?(B7dSL9]S-dm?I`h<(dkUobfC=8Ft(%3SL + RMrU,?]pCU[J'QMo(-:W&E+H?+N=i*mKd3Z$6mOs!)s,PAhAO,EF;!UTMu*1?3]u-#iMurJ + CY6>!"siH'VTsnTrK1)`6D)i/Blbko$8m``r%UHArk79EoR$QlMah.B2@88G(20RK.iOGBG + QFX/Bf-.8/5R!&HNjd+P[H8&.:b9%KM_"TXOlNNFDm%,G8-@^d&$q0EWF\@\>aai(=,[8d0 + `C"^kpH+D(kOJd>bY$:(U"edr@(0G7e>'&dXB!H&j.DBG'(E0EM*dlfO,,'2adEK`sNlWj'41l$1=^GT5*KRun67gf5m9&kFH^ojZ[HrsJ/I89'>[-UJG9ZV648=X%cD^? + !29Z_AdAB5"X%CZ^chn:/cu);U>T$R!,jl@l4%.\%\Y9`5qEi8e/n4XHBZoAP-3h`]qNIJ;qAQfo5oJI^Xb+(6c8@@e![rY + 5FS%,hg*^n;-V3!mB&"e^)CTM,Q07KlkH":tb>eVXK[4p1jcJ`(0b+G^:'KF8G?%CQf[!;6 + qs;A*Ch),Vq*_/^?cs+V42L6Nd7e0PVX!8s(n&(E;l'JqjD,_;1@4,Ym,'L]:79bGQ9720# + rGb9i?-5MhGMIBs0@K25-i"dL0&cj0oi9Cfg:^c3s&Km;E@+GOtD@+*V"X&(8TOn;!3X&&J + ":g.gf#.]VO9Im="CPkP+G^8qHj^\^R@0D0fp/!DS!l@0RTZ\[RBN')Z^TRTRi/])ft3al\XZFZ;5J + .?ATXA`_,=8XSPNHf[9HXVh]LB/<>A6"RI?a#o:EMeT,IHs=o4dO".gD8P'PfQRLbs8&nR? + K>YKtIZ>$$!NEmGM?'X;Q\ghG*LF;?'2V$tsg-7$OrM3HpUm:seZ7$gk;5Hb*V0Z(:RU;oF + A#6V5q_H?+Qqr0D1OqL&5WDeA]i0%P;]g5fpYPA!kf)8WBrqO=J\SZ3U+W`lI'3 + j5OaS#P,K+8WNYqRRTV_5oqk,s'/K[h!Ge&ZmiNEE_'CYUKY-ir]X"2)A + 9'oD$>"PDfeU*FScGfgCJE?H5M4".GG`FHQ070+0o)kZ&H"M@?n.iVCm(nZWfLPQ`Sk:LO1 + h`[$/%IpVV@\-%(ooI+-1EQdDine[AE.4Y=p-2rB'gEIPNDIc:6]:$4aS/3>sL0LXgG\)UhB=hBS]9mAh)o'#7-\kfKZ#K\mYld@msQ&/?a7JNRj\=JLcAW""$M`^gNS,\Bd!#>TG+8cAZ]EuC/'>YY)Md./8iF8r--'CA8_ + ON!;*&!Jc0p>`tPHeqXoWc=?Dj5O^S9`#jS57)Y;oH=M`>i%">[,2f?cH>V`ggPY]P#7>F2 + o8E:-$Hc+QSZr)(!:FT\:-S*/_eije,kDTU_=2=o6>Zs2nLCb*>oI]W]GIZcV!^bFF^9q@U + eKa'@=AbaKh)p!WRq0r"TtUa\kAi&>L-J]c['0KM>*m1a(;i!V_>l + aQ=Zum'5"6\B,ZQ(\qp>hQ)P[gd[#ZgR^&mK0@O+,X4k&3g;g9U^T?6]XW:':>tqF^h/PL5 + ENMC,7nKkS@]!dB"1n#CqYA_]p)*N_"ID.c>6At/$*bEYYs!k$HRH*\.G6kNZJD"V)`cUk2 + ma-M3r;?mQa_=a\$\B.4<%>U0VWs(dsVnWI@G#\Rr@+!mG1uth4'2U]8tefTAEeI\fL+J^$ + iQ0\%<[ThcOg04oGmj.1a&6LYL2fBG]VHg#5IBLs@:Y1e58^R,Zlb76BnrfVOIs1V_]Ij.8 + 3iHkjHr6f]:S_>G=j?;JGP;8X8)_`96bEG*2E3p/?+k)jl+I!Nb;Ds(GJkFQ\VhKS/^HfuN + +k['225*+p!Om(Ycl(47+bR.CJ\]=d^be/_T-?IAc]?C2]l^YB+*4P7j_sAGbli,)o?HlCI + ZgD).aM+kC`bWeIj';N.U2k7uSCPliFj9K8c4YXkUAQ#/naKfPV#:8=h[e&J'@7g;d!lsNp + 0[A2&M8e[Y:r2m2_`d1Ft:=ZeRCtBK57\1#M?eXfU#Ce+.q4nTsHnQZe_JS]/r'c6e\ + c$[#Lcdh75W%/:oK:5*?"lJV,>gNRVL-:ZDc:1od[(4BiV79X"+pSrk2^CfPHj81 + ]:^eZs'Ri]61R@C0.KDpH,M)?Eh#tY;a)a\R,]t0: + 'goo3']AX]M.AkVQIK-BV9-J*M0&V-^cBT@iK'>@",`B=Zc7[i<$SpOU!9Dtn]$-Qe>iipu + mbc;urucfoM^SK=t1XkE&aqYf:&Hb&&(G^Q(n2il;HoQpIBhbV#?nFq4[E&7q*5`eqU"i2m'*sY+dCaTKTOH_7(6L`JV%RVr:2\/;2"@j!@YnTnF7.F5T + (.AgWEiI&/76Ej>U[a + n!Z\/PED1^FdYboYiLXF#;&s9TrH%29dn%ZuI2ZK]P2=F0t6q:6X2r4jPqFs$'=9m0>mQPS + FQo;"R`s7Su!Z(Iq5AD'q5>ZZRon;\ZVq/]s=MY><1g=I)sJAHDjf-QcMMP]1#u#(> + 6rPNX1=!c16>rO@eFBt4FVg$uFrS`%Xk2#3IRWm$NHsaA5qIs:2$3,S7VUc'+RTN<$KOMAf + rb.c\L*_Z8Gd],]@[P_"M&I6!5(DVG$*fWRVM7[+jOm!nB:A#_>F4I)L1cod\W1Tq)9/r]S + S+AIH_>$J(4DoiVJ^UQTDD-JC?\o>RAXagVPU:1R\$U1lPe?XY%p)d[XX]WT#7i">:.\&D* + 'B$J!iclgUHS)Bqr6,VH2rnmb=G!^$hluq7lC<8YNogS/M<:E1B]9`Gn@s&]^:ZO!i!jk!' + s2o%_'OIt>C`TkX-RiFZ@8jtWBEL#i(8s.j'@*hTS"qYo*QC7kLo^[$7gnW35&tT:bHIIfJ$`?PnRqrI=;2?OSmn!?`?cJIif-^do5*i+Te@:c\lu#UKXCj:R"(rX?WQ0 + a7gj)M@&?%u[]C5N*k7KWq$_6BQa(TkM#knAKgo#\OR>Uc4"%jBiHhG!TR,#D`L"L;Yhj&I + =L-,1.Zp&ZD'e,&%o?O[PI'1/SbmHrhYWA=LQ4G8Eiu5UZdE>"0]s)+mS7.7HEX'mSA2UN' + 6`KkR_]1?oNgMIFt^-(^.:$d+\Y"Q0d[=NR1gScA7:mh#'*8&j"P2l*1&M + 1@+6*i=H''H0pDs\;+^RR-tO[%PgZF,b.1A",uh"4'#%L@l"M\[XL"5@k$1QiD_G\O^6b#^ + @8%Pq0IAWoR.D)(9Y$EEAk^O=PMWIjRTO6"_bQS5Xlj:m@?K>o3PQ+jde2W\4KMfEYMjh3TS!FK(HIrMh"_mm;aj.*05YjPE-3L^lJgpT6Lkfd6)'2BjP)BHB1MY]o-"dm;(^i%u'Me6%a"r,OU"=i + nZg(pASF,^UMPoiBRYZEF:ZEJIk!L5fb + )5@t@aB;:c+EEmGeG[![oi!5',)#p>0(M8U#N6HHf2,G\S-`=c"n8SKu=EFGrg'2KK?/86t + ]Xu")Mk8\!EDi\nh50'&j+_2RLO>8^]aS?VtAA"1kE_]EPR6r?5*3#1n[Sk9d="NHYIl6Sa + ?Uj"r+OLp:6K\Pcbh8g<:jgqVW#flM=iQ!T4Q2heQ?CLkh`YUgq4;ZmBV=%j5q2(:RPZX3U + &$,lBW<4Ekg#7:e6\0`%4@ucrg$b]"DY"V7@5))-)4VX'6pPeb581CE$('%8sMi>2*5hX_b*scF@ + `)[9]8UHZ)!#]f2DXi-.*@E;QKL37f%[Ce!lu:/]*BXlqLpk4Z(JFjK=6VC[GBkHc-4c#>0 + qn1=kKGW(Q@g3sn1Soq#YG-9G:2nOnc1uP[5K=`sX.t+9VG.]1Bn@X2:ph7ENmc[uQ5Bk%$.hd-;*b4_6PO + K2c9mAd)N(n1onJQ,]A^;7RHuFr5hFGY459K!7Q^736?d3kg3T`HuS+HW'd!@oFl$X)?oU_ + i,H*"]o-I?`B_B+S3%r%dq:5JW_*cGZH"7L=*^X%B35Q!a"nU;"hpn&]cqs=XW4k>Aa*p;9 + k^T"#Ba4AZGs%E=(.JG[9bgUlNnU;RtVgWSL(]tb!l9.l=Bn\@n1s2J+qMo$X-M( + EW:0ns-Tim^_:'?,@DBuWk^u25YA#UlX_tB#.@s:[Yne,P-d-c$b!XU.uXqGj:4*?&%K`^oT&1/2MN%8]nN2`](OI\''.MMt<)&IlIO]_:$Ug1`j.oK=Fh,/Mi/K)V$g+,G + *+)X_]:C]I'b>-TKQ.\F_5[l1I%Li53&%Xs9,j9J3J7jZ-h;6PGh>X-gGUR]`sa3Pt2:oJX + 8WT#5jo\dJ'j*'1YuUG$@*\l5Inj/32'5J!$U3if9l`'V&b6_`nLYUVC^40Yq^ZG_A#]/p9 + CME&4a9=G>l + `RJ/3jBP.G?,c_J`WT_i'Oo&jA&]d)7PnYqA7`c&AL;cT_6I^fK@rH*M7otT1FqY&P\ua2h + F@\1^/T*d9QIj(F%7Ks^,+nAA8oQ[kf2V%7apV/C[9$Im)QqK^AI@hj6jtZnhejH^Dflr8S + u4!Bp$m:^KXUNA,!Aupbcj`aHQM/EhC,%Mm]Vc29_%Nj'G:K!PgORR7h#)>`uN8#0B7 + I(JLcbuTd7)QH$bYCkd[_%\f710Vp*7g'B;b_j678q"@>8K_;9KY(*`dDC"4aT&a25`UkmC)MRA"f8AkA?j8_a;NL7'R2=-Gj/?dF=_qqf,3o&[slqX6^M$)g4h!UHrA.SAce`XJXEeI"jDt8pppE!*KKD*3@l<9 + !f\_\`6T[X]#=X9(Rp5"L0r'fq0fE6EIN'm'TJqi544,bD\)IS!BYC43Oud3Ku>CdLj[h2* + '+RbQFJ2'(ARG_F(]J6bPq"8;bo;q8;R>YIqr[d$R3HatN<=6psI3A$=,2YfuH*9`d5t'+. + GY[C^idUZBq/j13E=]0"8>YoCDON7q:;8O[aU4=X*+jQk+!?d>c]eb*?O"_#t>@b*tW<`NY + \?"DMtB@_3=eno-PKkaYRC6'(qd\.Y5LcWP_s3L'#1jo7',MUO$r)Z8Ac^]2\ATc$-ps4oq + =)E4aF5GM3HdBraOu3-h.Z%41J(G99cm8]_m=CQD"'@sB:gAV)AHaMVK@"`H87?X;S+r>WM + ,:a5:rE3-h2]kdO3ma#fbG-&,nsK=P>PVM[R:DCD&B;g):>jZadjDCc'm&-ZVfg+=k"$L/K + iaZcd2Fe"o)m"M-r=k>U0K+S@q/bs3FO^XORB\c"n5U@hJVJ]phX<.$jYKKhMNaAf: + [baYZ_`613t:/'/->a)E;>uBC`[7%E$9c?,^h,Oi)eCAtK74$?W4^-C;g!-'mh,N00m;(O# + CL)X1?%(C3NS3-qg>0a%?.I]ch;$iJhqdt`h(ff^%s7?p_eQ-jsc=`Uf5X(ke&)o1 + !nfs>Rs]cP;WSZCSZAeUV+Y'./iq]>bf.Z8'i2c+##u9q>emMtej?ukcK%F?`:VSc'\69Kf0qkoY + r`\#]\;1I>trT&?7nt5ekY'[:ZUY7AG/j]-;ik_;.W3pj\Ab9<`\M + iD-=70AG2=fWFlt-0(jn<-F#%D0[@[/P:k#?Zg`V&A-?"L0Phg=j"SV4`uL>TFUfPLnLrV^ + *aKE_8G=KD7"`W<'WMGVS#?qphAD?a`ps%sJkR-k6fqd@f8HdPRrB@N3oL(-DF%S;Wu@/b` + ![Lq^XZe]'IAJldi[`k.L$J^$GC-lit;lrke";__Sr/jR2X?B/US7HnDRA*h3FCt? + erhTXU.?>QG:CmNK2DJVG*e\)QhI@fENLUAa,X/7ZDoeD)YF.UH + H2Cj'"EI`V*Hs6U%Fm&GhWk;)Du1Y9q!o+o^DpGVCaI<+4)m68R-?23f1CidVIm>Zt;DV7?cmT+bE:?La4)/b2nfpcaE[LkFSK[Kc58cJ/N + -(n-gnE?.3mk(@.ALHH^N+h*NSQAsb5R6b#Vp`+;, + o7copCaNjj#k+)?kens:j$++j']K6SbrpfE.DB7tMRL%p.PpkO_$Q\r + '2/'rBQ^CMo;T8r:t0DPi3Gm?5&-SbEM20h-Fq+$_G5EU582p_5YH'^Nn5&0o%C\,<2m=.: + 6GZ?Yf.b]lqppC/55tD[^-*E83ZtlA'ELpm5`d9\b+1<::LN#8 + HIjbHR!oCCbaiV9jG+7pREDGN;\Ho=sh`9NCO(GdiQTO5g-dj8L.]r\rqf8*0_ok5L.uI[B + 6sS7m/cmK^*nHT28n]p:Uro7&*Xo'Y6FcPtksLTCj+n8uYmmJE^*$tasIq:I/pmbY>Pg + RJ21&2fd:G%Z=jtfg2c>tq8A@+.]/IVN>U=8A7%/i)hOfm>PG"d'Xrt<9?0mmEJnRP]:`Y!KC(.Ms7C0m9)Zu[3W;=&O67kt,F+\7b)JmcZ?6$X^lAc_oYa + msPI.EY*WT.:^uZ#qWkL]"V79="K0>aNM/aYQVeaa5go: + 5\A:W;,b5o0)=%7-WJZ#rI^FB&rYdI`9485a*rCIh@q[4ki`eDY1=<#"Tr>#U\47oGZG*E$ + &VeK%jp+6B%t=]RDND-GL:X`AoWEF+.N=(U!,hA:V$J!HjQl$HaG^%Hd1Qa'Xn+8>a:J7)J3 + $%nl3=]^:8GZG];iRj/+<>A%'.>qZ3?8IT"39E)Yj;@kbO(=Xg99J;g:/R;;q/03!AXS!YR + -'fkD40P^Y&U#q=mg@'kF)U'T]hgcNM1&)/sE'p>&Kb+l$AA\WG3?CXfC\*VR8k^$R=WU'D0c`QS]*oF[!r#Y%?n,.YGan,Gf + :WrG0+(s-trZ?XOpO8]@Oo,.8c^Xl_+5d"d?+[Lg&..(%kR?cb6`bi8*X0?H)!ZjKg`Q#T$ + ,J#="lQa*K!9,$6-(ng@0@2]E/G8>p`osWJ^nk(XFu\@0+5k&0-fP8)FHK#8-t58-.(kD6d + ee++osTX"ULa==G[X4Hq>cL.&TJ=OB5nB>)FVFXVMbT=N`O(/J2[*QH6^F9=/,d&WFC?Esc + =81hhpR1,^!0*+YgqQ&[1bcl)WFB8mmV1o@-6)M;(,NI6771'HAO;eg6ql+iE:/E7^Q*-0> + HOHN*l80%f(FAA>UKoiD'*Q,iDcO&j#8$)I-;roHhRIuh8r;K=A=Fh#GYa.ft"\Vf3L(LGr + 6Ls4DU*N![jnuq`'+G[#l&:R53]BNad2gH\I5@$96V,ZuC*^0M2:A3rRo.>du"PnK^^.Y`L(C(.*W;Qc]c_- + 0[cDaTfBZWKj!MGZ6YYsjop=K=Ss;;+$#(>@GFe;fYD)GIXW4XjXcX`m[nj%G`-m9cp]HrF + 3^_!/`@V^s$_0d\$\C'?h3'b/I<=#j.5h+=Qq.\p%TSPs9W9WD)VWRVodjK(GY8-K=1Nu%HADMeZ@E$^2gC48C + 9ETf'0#^'Lq.&c6Oajf7aB;#Xr*Q:fZVSlkfpb!7m.I'Y[Z0WV>>8Y14dNKVV;p4L$#?*>L + p6A0g06XO#[m>9[heBBgY6cTiAJH](Ch#giZJ@I9nIJ=rYIp=D[f8Yd3MQZl9i1c%uGBo-L + hEg6t1^kicIo-;)7]243\JC6rY^=Xt.Jcdha4uDN"%N1.EeX?K43ENE>>^7cPG;be.u3;:7 + PH=R]u5-4,_fo[QJ`*?]YBs?;Phi9FTjJK<6nH@C1g;L76 + Fumqc6Z7I)XaOI'5TW=_h4gKS#qfDJ9LCa5me>E7G*8/5WOIcu;?ZIEdH=1bqRn(ph]dTS.e]m%BQhE11 + NDr'Tk[uIWJ>RtlE>A^pP7D-*1qh9"G)$t1,_Vu@NM@gm]EUHTZ3o^'I%XXnimWfnc[ptSA + 3.DLM7Gk`4Xs@?Eu?RtHgn%<(g*=i'j;[j[b"EWg0%g)CY% + t^S3I_i;[61nJ'+`O"glV]Fp@At?(YjZ#=11.=r1TABS^*c"pW_qct:YXu&pVo2A\moPth; + t(2QItdNbKPAfEZ]k"1AYHjCm!](KZ_EI6>%OeUG[(X;TdHlDIo6GQ9"@c?rF3n*?nNHD#`4JuW0pTJRAkd*tG>BV0^JBXWnq9-6b'>!006]d+C9[=]4F4)\s&ccu988*'VUTCr2Ug&(ennbp5K + pjZ*$rI;l^s'OO[J"QT;DuBYi!<@'N`oYK\]eHH:DJk@BFQXjt^*pSV$ge?QlKF0#%'[ + h;&CBZX!l->r%BKp%$l$OBljPk:X@3[Qb7IFn#S;S1BfbX<8F?m + kmOVb2ltXPAE:55f6#eIEY5].\0Z]A9n4sD4l&cZ*ABBLBnZbbXXJYP;Wq'\fo$L^3D_fM> + 2\p_\lu'h]P)pep@M[qK&Q'Icd2t+rlOki?& + nD@_l#,t;0f*_Jn8AHNOZo/;7.Cd&'X&@qnP\oiD)1c%X!o1I-N*>Ira2(Y^aO]oq,?H\IF2K( + GgcT^u"DTrc>oZYi%nUidJLH"Jj(sM:`.KMO!QI#)j)=7YI0rE=ZB0%\s=03J;F0RdpNB:" + S>7nm(Oipl^cWOiB4L*J7o?TPf]io0&r%njdoFkFC'jF\K>;a)uFh_.g_-D-%>d_ISouUJS + c!<3[?$4t/]/5 + CIAPc8:NbeF_69.8CD[(/iXgbP,2l,-/-o'8u4o1]kj08q=j<7m^*;m^3F!PmnAt2/R/X;0L__KN2nI*m%1k'TksbO\\m&<1;9?;c?s;2*5b + 3CGrG=<)lLK\`@i+N%FCA[dD1>HN\e[Rd2FV/>SResk.T36WCV=<3'e8oc/!q;,[V&2bF(Xb$B2kuap3h + QKfn4I'KcW"W1<7nl#Pu0KJ;cT$2 + 3&-J7;i8n^CK?Xa)>>q.DgJ-6VfU7mpm4JiPI!>qoiT\SV%^[f1W=KTX',+d=uTjdZ/^">* + Eq'5H5E,tGp/=G-'?k0OuY%UGqG4?0p?l9PIWQ7ff>8l7?f[5H<'0N_%1"aiK\WKHPbM'pX + b]B.t'ElI2QmL\Slo,A!_I`I@45e3ItWUkm$I-IeGXK(,gn-Q%*6u\kn<7c8/uh7qBn@89b + Mmp/L.\PFH`!k#PuCq7hMV?Q\i0>MZT5O&p>O><#f#30e!5;ip_CSTIUi-MVF&Nu7>-\ + G#`cqcONPe(7TB=']sFrK-lSV\p532euUM6*#9$X-lP[g;0i$`N/jUX?+J)qTZ-'bH6*3a)1gZ*G#+3]N`Ljqouc=IL%'o]mJMIS(ndqGTHR!Cj:6!fR5NbJV\Q)1V,5:FWI6'2c;\"2 + &\bYqF-:Q98_G#E*?7\f*_.r>JbS!N-N;6RV.T.LK6C`Irh'2F7js[PH.e;.9 + N*B>F>;Ko>.F%"0+5NOOn!BpM"g)h8aj#Xo*UfS:ASlJ]N4maLm/Hr/&N8NQF;NaaBWWg>t + W3l'Th?Z+BD/3qoma[,7[kjOq_+B8jG+QYJ:-` + 6)6Sd@Z-RY.S!Y"'q7jBheXTU4B!eD)gOl$UlaWA7SQh(H]8rOH-(^-lT%fm0)l'udgM1W@ + ]%#c5>sX3L\(#+Z]88le9NMgs9oqkBS[Lti(h>Qi/p3P`.qDd#LCS/"t(! + MoLq2O-XfHm,>Q';a:s_Uf`cRVPpUd8kHAVc_\p*(H%s*WLRAFq$ + "caV3%.DG!'W^&&B<)\Z+^!YI69 + C[[UkVI/i@[a3\)qS5s@N\1,fCdEt;Dd@Q<3>E"R=OU[[D\QpY;RI>g>o4d#.e]hZ;S.pq59i5"X!_6hu + 0sq7\rdhjOAfgU#nK+k#nhap/B;/CaWP3[bc5T5N/+ + F^]&WjkSo$8I1hLQO3s^1krINA%&Ss^"U'Cm@1Y=j.N&2>_kF=0PRa'a]C!/DY4_%88oBG9MR/`mXW3gkN-CIE&0c=p+K + hA+=X&WpM9%+66s/oG([ItOd]k"D$D&.:0P1KZAgY!A.M[JWi\Ne_*)pRC8ID0(@/:5AE!r + lX_Yh4mu-Q.i_@^-=7/,po[alRR]I+DL]^1'=ug'miRLS3)[1tnKepIhT;l^+\)I`l6-gMM + ,7c'IVo]\dW$C`VsNH,es6FD$@c&@;?%`8,Oc=QOp5Q + $c=[WBadALr'4F$nI.u$`$Mcf'c(d_=F\<;KNf4XZ$^4f`eBXs*?XgqGoRT^jFCB("c6i@a + Kdpf,Y<#URX^V,Pb0AH&sTFH9(JLH=-b63[k>jXH!r>L;[g]&'ubIu1egnBf/HF1K\1d>V0 + (l]cBGX,4AU$t=B6O8o`7;(1aNAo+6?X,6r?oX(Iq?0KbRN%8,Xf-dY1s]9i2V\1JUm@og; + L(FTk?6<4,.juroFEF2dirHhpSFqopCf_-VR@p(^$QY0H + %Kpuc/p6D[n>5"MnCKjiY[r5K["hAVWpDq.l>k^,)Entq.fl^T0L#7/]f;7MRhKQC!Hs_*2 + qgDi'jo9m>3$nZY@!FjCKOK++)hd2FAg)q=32S']@T^W#N8jW_c;9`1J,tdCGpZ1JAGJ\a2 + i`POUhub"G%$a"3JjZ#k/[I@SS%][F.VQ?p4K#OH2_]!B0&&NV!31pS#'iQAtEaNqO.A`lM + @BaX(tnY\$'SZ#._!Sq]ZL*!.fsP\/L>TOYgSX#BmX)D6]P[Loql&u[LBG9S\eb + [IE4e[OTOsUa)jt^DB-;&'*1g + !?(q;Op>)^@Cj+XlFl%?@pOh>tIJKIMTDgd3j7tlOQfiX#LR[N'r^iVq+9&aSlh`m/\*nZN + #J9L%k&8B5+9+"Anf#85fOk*>EN/SZ7W7Pg+6I4tr;Rkjqu/hSpAeRm![$GRBu!g]!MDggJ + P\6-_"\cJ`_b)+OAZD$;@Nh$9H#&CFGN6R,Xc&Q'nh6H&eX>(7gU^s,`oBs'!bJ0+d"O7K[ + LeQW,PK=$p,jKoUuuYQOjL?6&6glX[HiabQDG)]Aa3CiXG;8SLUDV,X + j;Z1^N,BpCJc!Pt'^pE8LdNB;.*.Do*Y'2U.L6?!D0_,OU"hOD2OWDtgheB4C^WeFBO$U0)V\QV`3u;1-CP?hPp"$XbHLU?.bJ\X$!kqb4 + r:K=hd]ed1_cSjXpoIEOZ#>ZC4t"fi?T[_)[T:ORGOGE:SXY.qFog:k!GETt&[[jadm46mC + 1+CaK@"Mf60LZA+W[h@f$oWjfMr9PF=`.,Q5N%&:CTmd_miOo*0ZP$&#&&K-nu>Xh"ZSF@S + B:K=&c+'\>Dq*t^_`aPH=K'KN-D]c`lQ[LDipd.OhNiEMt)5U\Z` + b[i]UZ/D'*^2IumbHiuH8T+n/(P3]K#f@/1rjcQ3a_;HWSMGqCCSg%N^h\OY/GVR+>V<*,pl!)EX?#)J#X(QBS2!cH&pRC/$7A2($1IS!.\E + c.(c5B)YY%1QO%]Bu`9?eEg$'].Zr"^HF#g/>Y@n11kpSRWCB%bo*]DB&)@71R;t)H,hsYI + -mMHY'\J4o0$aB!MB!"IaFMg6KgQAU3&k_d/#V!;[R67FHnaq[Ap]:b&sRs4[#lhK20uL4Q + :,oT?Q/qqd6QWb]kK#MqCNuP^m + \@+ht6$T.(,E(0P_T"+Zb&:FA^)XTW`oBS5A#D,t2tNM/B_Tek"p9rH&E7d/(!pJZMntcJ$ + $9[`,k.ZHl%c;KFSn-HnGXVrL^I(RHqu5s7'=LWFUe$A?&U),1m]'4(#sbj0G1d0a5+16Ub + :Vg4-i7%R3l\CCmR$MGC*$Z;^qJ5f9PM`K$/5=_bT"'ib+MD;Ua5fB[%:Pr0NQ:SjU\H5;2 + ,Ga.)[,GKfm_3TrU"U%Fq;dsC!>\H`_XA\ + '!AhW/72tgUI@q3#^X=lJOWW)7W?P/j'b+HKD;6Lqu^I+tJR;C?dF_m5W"C0&X)rll.ql/I + SnJ00C:93qY.IQ,3SZ3IE#(LsEPoq`1DL3P,3nr$OMJSjJ@.,SEL!>6VS_#eEDTNpuU+l:] + "k$o$Hk3eC'eRh%Q\W]J%u]%agj28gA`&q8C4CLiECY?Z'fL5nq71<'_*)f9?<(/?; + `B7-hCfWuPXbGo7D)pFb<*D54d^Hlg;]=l + pcWp*&HnEUZk7LCdu'S7^R%ON+g6/Bu^Wr55]$#lt+10hSrdl7pGT*C[s\pGX$S$5BM2lg\ + Jn%q+&TD5I::.jF9)rrP/MT2^6M^)>h3*F\#2Z5[4IXA-BB+Lc;[I+P[;pW/.qD%'@^&^uU + M8,S.^!KLlQ*/jtjG`"P^(M+LAU$@EXLX;?b_-8*dcad\>_WpmjIn\NZF6/!qO0Yp'hPtD0 + (3HUsRRQ)t#35SU^\\QiaHN4Mu!_SBBU`0Cs'ItFk/35mFb0p\aoJ"Gb%(_BPb7OG91CtiH + ZpR5=V*m5Z@hh@a3BI@)3tsb&bWN/S4ZcR_]5o#]$R!A=^cSVc]bh5rp)Z>mh!UQVbij8W/P;g_rJS)_V=63=MKN + g@QTuT?7TX3F&:UB[+[^,e:#p,HCa<_js,A.*@dS_PF6LXK4*kU<;7C,\*Z)X=u\O1/^cE3 + g;Ho8HI">[^ssI_gFGePHi$+Z:hO1bKRUG,qhpX^*Zc$1'Bne#q&Cl_6Qol;d@8Z@510'`K + QP,`1h=sPY7oBb/D[t9Y8>>Ps8!F5L#`b<"2/n"[c606e!\U<*_plD+)m%7T>(3?(g_%1OlOUJCdEN8G_8kI0WT$Kt(uF5suI>XHa,E%7tsW\AT7]oU8VcSCe2>61#=_682"HK + J.]sd<$VU7&nN3=M',JsC*9,o_8>VslM&]7SV6Y2Q]S@ + I"JYtLoE]/!\jloi8R0LQ(!6khsd/AX_;TG"'<;f/7-KfE#%UdV=He$V/i6jfM_RYZh"1>4o1*%C4_,L"+XI[$9BAjgS7NA(AOr;pPQ=<< + CX9D):_\ga.t5:aFAPuj=(DkDIG5'V#g"Y)\:"b:]'2e'i'Dc$,'ABA/ + V$F+&W==8fHl/9oH;Od+BW0K;1p2]?&2*a)bPO9=L/#pRfgg>Tig+o?Q?6+/?i7dOi&HN=Z + j)H.T96/'2*Zf99o?VN&jV6RR>p09ADa7b8ra)10O#G4-_X+#q](c]il[NduC+XX@_,;4O^ + ^87,A8LgeQe@ZXD%b@,80ImLA^lYXi02 + +\`(fMc1SnW`ZJFq+5uW1@NH\%/g-[L_dZ@]@T-^o^*d/c?eO_( + bAYs\0NOIah#2!D*RS.-da-c-/LAlTV#TJ&MHsfE="4khKT4S_Njp])?+c;*`H]K^H/7S/? + 0i>.ejE#OiQ7AEd/*6/'nK4.K$!)G?@GKk*U-[2UI%AEfS/Y=XW-@"mSVO'?NlEe"nGd_o2 + u7g_[Z7]-'L\Zp/6>5=^CeqFdHI;Qckrsfpk1XXo6/u^"Ln"huP$0*M5_X!oPUui)E3?AnP + &.#Nh",`5ZOj`X:F68RTOS@:g>E1%CmNq&p\RIH@@Qo@u"s)nkIqV7:kMCW#DLC5WLglZ#BUrUAe]I$fIH's[ + @dEt`!m06a!7mu\^a7@l/;3%XdUQTAcgB66%G'cgKk$q]h2MQI*^*>:G;2I]TC^_0+Idn4C`75mn0#2j(u;l) + M4Q>5R$4VV_Uk@VkLI`gi.bO!NtCu3BPk07`1ctr^/D"c:o%as_`fB8HFR7,k?Sqrr?L1l% + gkJi$c:>9Wggh9aED8+inbj4YfLMdX:hhpdDp+41gmqb`VI">.E"&FZ*U2Y9X%HEo?IhqlA1;9f#N$#rC + F9I)2HA0q%+u^4AW;ql?P$S)>M4ZiCTX#B]e*Bp/ob3+?iLpS5/:tU"%6,+i*UA#IV8"C*5 + nGh>$4)VK.u#/.WUSmG>EC&QZmX\GP?$#GFs/)YB]fVH?[DVB/_>]]OC!43S]BAp`!gdB/" + QpK6T=3pg5gA+,'60L\O@mo7r3J%uWrDjS7%_*j4Z#ml]h-0\1t5F8dq/-UIPjL#ZN,aC^_ + *pQl.*I?3)^n'u9%B)OsM/.[_]^Y\bKO8]VAF61 + \bQ"]tGl[DRUq-TD$=%#jY7H8lboP^/d/Hb7>FS))bA*!r@k4e]Fq"3I:j?7qa%%:ogj7Os + nIpZ.CkeMLVW-)^Nn.qu`Knj4%"8/4BFf&!2%gN@B!WK]^5p1,K6llQIKu6tgaF!n2'8T(d + ;.[oGru$g9QRg_DJj1EA8Cn6ir16h8PtP\Oq-:mW.%.j4X**4OF#]^dB8^?MrddD/Xd:jo4 + W9GUV(HBQkP[\]\>[q-F]uaiBiM4Ll`3IfHGa-o/ZcC'SXS",[G,P!V6.&_Zad*>[s.f)o6 + "5CDlYDR`Ff.d@5)q27NWA"J#jDfgjV=.PK@BV3;%U:s-_-51V*1-HUFE,/)U2/5+>EMeSB + :\okM*IZInO;gKhh@=KI#?pUe]E#D'eM^X[)52^=C*k&U:QKi4oMdHU:gI;[(EZe+SgH_(/ + nLV;^:Gl6E*8!DKdp%j*.s*)NGW@1BeS?FEc'&4*rPQu^>0[*H8aYM)h4N'.<6o + giSTCZmV2bjR.Y^ar^[-Kg";p>&":Xe,!50sua"DlB50qh!4<';*edBm1!B[G7ks%62=SM< + ac1qbbAe!0ei:t*r05uH.^/Q#DGFM=4DYL?I1SN$"?_ + 34cO!t(0os5c8L*=BN`oGWG1);Q(DV,a8ND`>B]DMDqnCda`-%E*R/]]Ao5##+89p:G29$t + hg-0L[IJS!k,%g$RCD;WJ!lYc#D$9M4q32'\6]c%@9>g]BqP0\-])<"^+G<'\DAs#@n'=kJ + )hI_6]pHLN^]0M=]S[ijk3sIPSlKIBRf/i[VMAq=coDIB;SFRSs^K4Lk"W70qY7U7RiT=Bm + OZiJ!g(-Vl=F"Tq//u-9eoAlWX:tJ!es8a0BHM+e,`R-8sJam9>%lJ!d*Fjgo>VY49-m>IO + @?mY2mNJ++Eo"2XK^0+l'@>T&9-+8g%!`u&Da+3JVOY;+5l>bQksF3"*0A98h.6dbP^01oX + E>uCXQFkpD5esQE&BOLOG!K;O&*S5AgpYG:hhKVKbMLYDm2iH_l?;I!MH,]<0J,14QUZn,b + +/S=Mrm/.BQbLMSJ,DMKL%0Pen'CY(a8(#OrP*"$q#2A'G[UKB\*AYihqAbBTn;\3O^]_-i + $8DT`%[3:Ouu:.@3pK#\k43(In*okauO"pV + t8-o/:A0cehdL&B5=VDt>`DV + (H;L[[O2H=06bS/#LhPS!/8C/pYDqB-QQ0duAW*q9log;Yj_k\U;]r"$G3=oj=d]Hna7G_r + p+V@4ZSP0rb;qW$<XLiS7XoVj..u\I+#NSl6moeqh=1)5gm;.-qK"+"Rd9N;DF09Gq + ]K[n:rTiM>Iuijn^XrTTTG<02[MXh9U,rIY@3[&9_?n$t+U-f*)^gqX%70!2;.1.].?KE"' + c9XPD'<):KmFN#.od#:R;RcKef5JVAIkKq5;/-'+@Kn!@dVZ9\'Z\_RBnaaU5d,KEEXh1l] + Dr8A6Xs(L+lmM^DZ&PeVqOHlq09(G,5r.]@V5q?-C32miCB"Q_*eTS'--H7I7]h[eO2+ja4 + 2-n\2KXqd5i_IdauK^K::2?i1(XE$?crZDRh5R9#e(o\7472)e6DWJ'c,'i;4u$r%&#L?;1 + MRpmnAL + 6p?2?VIf&1i;mMS2]6n9r\@[ce8!2<(A1+gtbNJeLIHGg7qU3PGYAQ>=^hZZ_ZmR@+*b19N + G?;=g[58XY3UL4hX9gX(11m09Wn]RdXRjA+Y2qj&fCmARF?b%=f39>X3^W@\?C(Z.f$ZffL + Lai.[FCn7p05]/9h0m`gE(QI/l<:fZm?W7o4SC_f!A[dAP"[DgSID@:0giV@uYf'd]bW/#> + 9A\^)<%cPA/8%diHb21_tHsG=7^931=hm&%p?#-B\%[G__GQWV\]5DO5")oVN, + ']NA!N,a;(`OP(Kj1K;kEZRNfq#;Z$>apmsIT2GK4pg>mTf$6%D2g9:24N75S7:o/N"abkk + LcQ&@]k4-Q3JGpSKFQtSqGIr?TdM!kGM`PD9VjS;sMK:*b29uf)"!(a:l%dcRak(Z7fe1q( + @-WhU+Qd+)T.@]-ucU'$9^bFis>D0BLLPQtXfob`m0(dLQLPZVQ;"[&Z-nm,La.7WtYX5+: + Z<-(]LZGL.'$b\-YP;"Q&&d.Jd5<6n!3ZsTZW\j!.jT2AY&A%0nZInY1u2=0u&B@H!#G[>Xjaed#?>>6h;'f?LQ3+HLpi)mBVpOo!f`Q[lCkB`gaIZ(b%*m#_DUf1kd\#sb%EtXXl4!*"k3gI#mcI^A:@eui_%2S + -&&s:lCsMN$etgOBnM(OD%i5gOlLGAMcYk0Cl>H1]2%klMo;r;j/IN4d'(g*9d?-W@035*t + .'$-P'[L*105/gSQ8KX!2+f<;'VO + 07,rYd008qlQ#/lSdFS58oU&g+UV&91]"*^AVoUL(E4slX9]fkhM6c[fZ^3:"92=ruI*V!% + m:R"%_;<%,UCE*ltekXo/:G"!p%V#m.IgZl^$M$t"E1nRU;^IO#-.5j@nlmFRB-i^8,a)"Ob\mm + ;7jjICSFF>\&B,'<`a7$;aR7@`E758ZPjT0R%PZc-9WIN"m4+.^%kP=C#'BU4bnEf6g$=+7,f$;o@d?-ii;+Au+[hTE`0 + X4V4%6GC3JXm^Tl$LJVtJG=,+pknqbZ%!S[K!:Am1WTdPOc9-rIep=;)/Y-*9._%I1E + ''*)QfO:0sKAmcJ#P4)mR+/&]#G!`r<6cLTg.e&,/%Xf)N#0!%m.0F-@Z,$QcMc*oh)Z1SQ + gi+WneFi6jJ-Za2_,[j.*I4&`aR'>HY.]f_Lr4L<'je'gGnf0&ZZ4]C0"PMVI#59p_F%A;\ + lnp9Vu]-f@R5LOI8$"50D@mH7`5gj40'FQ7os$*jm6'-LZ<$Nd>pF#IJ-oGW.uH+no".@h_[nI+/9(tIe8l3qjXlT5VK!hO'&P=(. + kX`c'(YJ^oTLqH7WX\T8^dc6'*.^3#r'l0'_,oh/:"=&i7))FI?Q8qdGUS@FV%dO'PB,$MIH,0el`aEFBV`2Uo!G9QMKS/R(!8G;Br^1J + M\iDk/]M%5]p$qNU;&c&5?eTf$LF26XnU&'7W=B`.N+[;XXpZZT+;'"2ougiJ$+FWWT(rA^ + a@;K:SS0EkAek)/c$C=AjWQEn)E5ruD?NE'mudDBoru[="5sJQR&qDmc]N(B[i@UMuP'E46'kf+W-?(N?h]ERt3V26fKeiEK8;%(3We + 'u<9s2-a/'F4_\OW[D*=`C=bF??BkVFmSVJo3W2U/QM[l#30)-G4Ke_8 + 2pgL + XDi)"cp$p1&M;D.[^q:CWL`<;?AlFW0t!)bWsGQ@?5bK4o8BBTM=8Z,p\leS-q0JCPrDNT4 + b205?]<2>&hAof4*P\N5`[C\I47Qn`L21P;&RK@i&7(d!C15D3-3KU>PgGZ0:JHlFpQ,AJNq/d@p + MUY@*nu#bm;\'[CFV+?:f3)E8^NLI;G#8WS%hXe$pKr(AG8GO4/Fdr9I<[X\8N%4/()M5fT + 1VsJ@Pk&Vq#_?jV.01u0pXhGeie@]ZXd2WI[&0+)'\lW;.Y#'1_ehYd?/#Tl$^%uB"-fLFS + gNP!L/C?Ih`3%'6/NT=#Z=kJ5mJ;R:7jm,AFKFC)&;0[\n\o;1]>o;n+,7EVk?R]c\.rR+P + Q0(g!,.O-t9WR@0\8fqXtGVj\L&RTZ\\)6Y7Z?\S^0L-Z>j<&'4(Mhr=olSsTS(l+YBf:B_YI[kT;qm>[m,cB:L*\*Meok\g(TDX\VtWCFo!1M'Q8tT + BP[?Z?^,tuf\rSps%'VK&pW^U)"0+:Mb15QO*?ea$EoNd"_)\L9,_.P=Ue%"&UO)eA+M.3c + \5']O(?<(HrnFg$Pq?X7t!BXW7:3_RZh?Jo/3oLPW;*,dBf(>YC@4'[>h_^FV*=F!L;'VL_ + '$+\-\Ig%;G-bQPQEK\TO7@q.itjQuW(Vfs@.$kH8"SRp,'ZqX(I)pTIctY<)]eRfT_I"0e + &/S!gi]ZgXD$S"*\%-lM'8g#]M!D4TA5SQdVN"g>3N2mQP$T"4&kH1/#+pRbUaTDMEVfVsc + )"1!!tOOc:dFurA9DbPAc9Xul*'(rB5K%q:`10WAgf)/(=2XnGYlr&UJDm"&!OR2DZl:#PgGuQeFiTstZTB^B*., + _Q433qIL>+r5%`-/qmtpp7[5n-,%%FZ9"j3<@b1gFL)A-8F[mdc-7M>=s*;1[]16qS\mTfpAU:q%O\onVHcr=4gRsnCZ)YSBT=\Yocru9_oX"D#M[Z'"S&$.W1XEY>Cg`MkDe + (0q8Qb4g*]ItA>1W>ka`>sP">,i-/VROU\f].ErfgNR8^(\Db^-l!W44cB].'cUm.:n)ISOj.-([3=!o3t5HcOMuLXCIpA@d'JOb&[d-.6'JP+)qiUL$7=t(8I7UqR:oPQl6O@\G1=U^ln.$I87P.>\7@4dkik#5EYhIej<:8\c9o + _*huThjlo)C6Ga8YXYt'ZsE6ThhXdWg+L):9@[#;`#"@l>;V7%dL;`(+=PRL3XaS9sEd:oU?R_AcAojBQM`X.sJ*8 + _8/LI)aWRC)))U2FF + !^SLs'a-g79Etfpn;J_#6El\+$:7482%CKXgcZ,b&DT['`/ulGB>h&HOVPj?<*pD1&TNjG` + eXJ/*(HfID]P58m!g'I1%^'-7rg'Y,KV\qRj:R[1n?$dZBUt1(oCaA.n^MfZS"mkoX-AGZO + F!G)/&lj1X+<9f/6:/9]%`Mjr'FE:6V464;_[(jZUpgjSqG@1YIIJd&6gK6^\W_(1C'>D&G + i@7L^Y9+7u*U9%)i.O%WE9h)^%Q"uSbFe5[ej;cP\,=&YIJ.KZN0@Lk"$ + !mu=2\!c4Qj:Hf!AnpNm]&nc+)H(]FJ@Qad5/K&HE)&mQ6.rYpXOfS1c1@^qFN> + N+5Vs,E?+hDpYWf`js/A4$#cmmlA^ + 94U4L'o;,ktJ+W\8;:Tg&B4'2^KjSEF'n)]kg@kKmr`((hms(uc9(.JnGWn'Aj7p>YQfWM, + :S6cq*d=CEH@E?"m!?ClYO-\NkJ-E^553%cI"+)uoR*94ch76$BA`H45B#`[I\2tmr-n[tm + J_T"lhc1Y"opnIrrHLA#(VdUmZm@[>$uF_J`(JWJg_oK+Ij1!&53PLW&P'Ug`D*PIM"m`1^ + >/q-3J4]'gd.J$=Ra$L![gV6TAE^@?`[)OPV!FR!B/boK.itp)la_Kb7c&7u39O,=Ioe'&n + "*M@h`p`;$/D@\bE*OFf&N34B-#bSH-LIPF/kV\rsNAr?,Q2+DhR#(Y>MNR>U%`u+%[^ta* + o&:-IZGh3jDA5A5jb!AQ9#or"_K95X>6qFj',@e*0KlT4D8=:nPjKt)6&lpuCO>I6=jIVR@ + ISi]T8K9#>)O0fi;bFSt.j\/sP;6H:auMa>AYcf"ZJo(]R<^FHXK[u\KFGueL'D6@]pDYEA + 5'O^18XIWQubM)9dunnVBjc^dq3(6\Zn(?eD6W.IVVYLUH)qVk+%X,F\\@X3[]%.S'uAHrO + Xd-c4s#$.DRJ*.(M<&?]=E-'p#!CuL@JS3aa%e^kIh\/oj5 + 8`HZ+&q^)+fb?T[M#Dmf>S(Zt[']D6qpp&DfE#6A3p$%[%R#Jh6)"^nt\K5buE_AGcL@6>u + mE4-S#(hX:d]K(adp)h3iLCmuX8VqFF-cHq#'k4gYMfEnn`Yc`6@m"IbEOIgH)!;oL]QoQX + p-6V9#9dV?M32g:8')Vr-"OZVPB(hBar*\uANZrWEjf&m).tO4]XaALp0Z#]LGFLF1-;=)Q +q 0 0 409 407 rectclip +% Fallback Image: x=1, y=143, w=404, h=50 res=300dpi size=1055868 +[ 0.24 0 0 0.24 1 213.726261 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1684 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J,g]g3$]7K#D>EP:q1$o*=mro@So+\<\5,H7Uo<*jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/mioWjnAY&^gM+`4=1jRLW!YA=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&t\ctIN)4XQLiVpoI(>.nOW?*DmsG$@,,f58"PDKfeXi0S^6MAH=;fBr>1IXb_>k + P+oS^^pnX!PjdJ%0OEX9GI`IODGpB_@VYP$,Ve*/ITH-bV]jE"*ife*(Ma>&B&Q&89#X>*) + dP>Z#!2L7M0`OEQ'Z+h&@>r9QG>IEU7fiS]<9[+\4@.N?ZIL.FB9[E`AEM*61aH.7u@cXX_ + L,UpV_rB0"Ic9@F$Y0[,_#bqVp+/CQE";f(,Gc#\j"3DL6Xm5iQ,V"HFb1?ABtck>.2-W#t + BRj6"=9bAL2sa[I\cQ=7,I&?[*%HDd[3tZ%?BY2A(Z%=D + eaeS#:EQM./H9/W/O7Z+,!f=QVQ&j//tRkN8@bBkVf1Z?I3j5*Mc])I_E7l+2VAW9TgsYH&\`L9I^p3o7QORbJ%cY&'R+bH@D + UI`&Q6`cf8/D7b8XG8[9\l+:iF^@f/--=NisDqkQs_!mj9%!NS9rtEg/`_iMVoDJ>Kd@!,s8p>C>8YOXVI90Np)$YS!flfNNCh*0E_;&OD?0?FgRGHbP@ + sEGUL#_s"7ucSs^(6ZV(oD5Fl$>OcXC<'&GHpNQIcn@QHbTU:@@:IEbOU&<:XSeO?SC,WIH + D-*pjR^Zl21J;cg1Q0+.7^9#BTnc_ZBg'8-]0'5jfHX!X6B5Nru%O`e#m=U+,4,'WFNtLSZMl?.fU[3Jl6IJ(h6/<][7"lF4*^tuY)^FESOce'[=c7je- + "OZS(/emB%d>l5Ko!4&&rKN.!Gs9^5d?REW-8UU:Fi8T[?`Sl=A()X4,&'fQSRQ+9;-&P-2 + =[21Cded;-kg8`5n>+\5u@\Rkk54d$d;4B[&iu.n,EKRj,4A9mO?0-M673K"Q;&3QjE12!, + NoP')fDCUT&R2&:Vg$qVr4PI1^nR8O]at`4U.S^\5Z:N)ac#mX.bb + K3lAUI2VOdE4-mB]U?NoRKk<8k_=G/LV>JWg4!,=%fMXVku;p;c>/\4,#)[V\J>m + ;p'JLWU2sK6B"g&lA8Al,tP"%Nhk1@gaIjs:T?9=WE!-$<6A=#-&AeV-H90"*F + c+(NNtH+a]VgsAaGtUZIWd7=h?h0D$?H:06eHnH4:?]o2@1H" + (BojN(-M)8&Kgd8$/mEaL%&4:*s;NP@E^ZcE<[N'=F1Yk>W7Lpr[>`fT,(n+:-NYm0HRaI( + _:FuNQ;#Va#N*"A))?jn_QPQj62!R4H#=W2G)".0-fQ5Q'8Lc:Wcg1.V0$jPk)Ogb8G4MA_ + _p!ZMm,5)36L^9OjL\ShA8iY;P=p6]!UJEQ!*G$t3E.STDd`_:UP-@%7h$n32CE3"nT=rmCg-@2YPlG)iJ_IoU!+DG3IadSJ7.`gUm2TnD2ipEt6Ut]m%3ah])$4DpePE7qVeZ4 + 9L.R?4"FQ-NW0Ns+QJ!f>BRt%cSI%EK6am`H]-5iuDusEFHi,\H)Zir/6UGSjgbV%k-O,r, + @[V=j+?o0BPWSQZGImc+1kP*;n]bNW+:Na8\?e4S3]:*f;nf5=lsq,Gk%8RF;B(jWFFkWV% + mCebZaQ)hX6%otJ&i3qm(L*H5kU1&opR5A':'YI/O^[G/ + LqtG[1@q3@M0u-#na+s1K,K4nIYN*97h]Llcaa$Z0ja<0@)oM^#H-\G4I0ZD9@oq(S;>rFB + n#,hBUX]nK:[4mf!=dB)_hrrs'*/XZ!O*FB#;dOl,'2*V)DEZCn0iCPrrZ(X!minF&2s + ^mOT\KO+6R,:_4LlB<-JBC"cut,cs$\TNs8EgG_,l!cu9l,D[/Rf#EXaU1>!$:0j_d'+@+Y7!YqISdW`iIi'[]*qNR$ujF?iVs>ueI5:( + )">u`_13B=rsgaG%T)C"nEg]OIMedY%rhCVOSo_F!=sa/(kM\uYn%D^Hl-i6&WmkAU&Kc9, + 8$%`(SQNq0eX0e0b[u@'(J")nNR^2K+nne(Qh0'@=THi;&)r,'k)8%nPp>:?6A-K((kN&((W$+fZ$mLoK,A$<(f?/4iG]TqOVq#[)0)2bZ'lHuPo;sf(?'HUZ*,(nYo@+f(IA + '?Z+_#/_]3*%*)Xj#iG0E1diB%M(D5%8_.t)(g_Q%8*hiQKJSuV"neQdn*qB(BEW7.)ru_p + Z+5puXEXij_'-Ufa+]97&+_`+*+r4'#(*R93ngu(!0-`54,B>CP!G!;C5pT>q,]ZU)Z9f;b + @NY/@-%TJNdU3g\?RA)<'u=6(Z>V)bnRK.%@d.'e[X!E^(fs!]^i.8lQ_n + sq3qP-$5iYpt<('g_jMZE#Ll="I7k/2GI$EmH;Bd4KCh/[EhWo%>W.h(CJI/o + p>3;XlBFo.KV,0=(C,o(Ognr@h.N0QR[WZOnmT'eo4q0f'\%o,TSB)_uIX16X">ZS=6,2)< + W@1G_*KF$2?95r$UQD\5L]KoZh$O8gHMu)5*S1ID"BhI778^$7'%KmH71qjPEW+h_a.`Xmk9:#>T.G:6#nE:))\Eo\sa6j,Po + ko_hS27o4(j:i8rH:0-I/AEG`djpl0(mVbu5E9CZG;N?:k_>lMmQHs-=#m[LiM*'`cT&!HoIaef=g#V#q8\!gY + D?ehI8M1B[,6"OKdEp-Aj?6Ns\m"kHj:&sg3DEAnP3 + `=phE'Gek+;+YUZ>nsg$(,jtaEGo_LF`('%(X[dNp@0!Nobm:fDmoHZdA_(>ZZ'on)c=Fjn + ]c&.aW4G#*VUQq2]j*gkoTBX+*%#sZ2#NO2'A5G+OUiFpH]tS73W?j,?ISa[ne7#9dK:$Fg + kL!Ea*]BB6J<*-1ZC12fU43F"#IlGIMiG;P>PKKm5A@.oNQqpPgZN_(68;44g$K\"PocT6a + S6H?ZK;\$:7G[ng\;#fWU2iVV.?G8i3T,oW_#q[>\t?9A%E]=O>i&ac + 1D_9iTM7FOo^:\&nP>DCi`,d7[/l@("]2`8jS96^%TuXd*Dq(tk;=KHod8 + j^LQZd9P)bD=q(G\>4&d(V<-^W+Y,PD=79**rd`5\RBN);hj\Q! + ??s.i0@O8G_dAJfS<*-/n*L'4#@R69@9f" + _;ocA@`4]0P@'p;.X]bA[Z#Yp0S+AB;/FBB/XZ7qCr:QVk\EbB<\WKg-KD\LCEm=%<14#p7 + #e8IFVC(eVmb%gd93D+f;MPXDL.9YG3Ke9mr$_ND_[ + "c(B(6Ks)9]jE07Q.nZQp%]c?mZEDb%&HGLY(,B;<_XLBc';>mUU4*,EF(30qff0^^J7s)$ + cFZSo4&i4\$;0CUeFqRl"=)5$!Dg,Cl-dn'"gCJLpM(1H>Z'UY)GEK0IQ[h.HJ9V7'q^\ul + /[IN!Z[4jA>=#U-6aQYY[(Ao:qa\%U9sn2&[-LQ%kZ;L()[fbftjO=+hNhKih3`m"rYh<`]F_X'bV08umqHn#!B;W7S&c-K`3d5 + C`c.R)(D"1.4p=lNc?)Y>sW;;\n)OD9:R2DB^(gT2_olQYgY8:3rgA0Yfod-(,i4B(S^-UfY7H<5rj??5tISJof;<<7&jh=aVhHAt>A`c0jjrSC%IW+A_E + T_dnju\#;bQ38O>t'*s]t;mJ)q2>2kD#JL5+A&+ls@pR3P/M-lCP,iT!.FnZ0Q4Y]H3o[hO + r,"S*?.*ks!AUpij5mH+j34LbrU246sV0jb[=.^J.Ahpn,=rTtjd.^j)oq\?WjeZ,(OYMjt + ZD57t`e,>3D5_JR')f[5\K0\r,Ro"6p!o\Y[V6,\$<`0g8W\GPnY"8$t>`VBn])%huNB9Af + gjuFITmWF[m/!?/Na4W8i+*!K^Tp-XEaH!ZmT7P)=QhYi-Q$HZX3FV_GVXN<@b4UNl*4;-G + \QJdGqQEW;>flk'PKc>$>J/_8O+Y^Tj8=;fcG.WM)7Np0l'qVHrOi)9>ljpes'PfjSUClRS + Itl^hdINAs1.J=&,&po"U&Bn?r2YA-2u#>6_UaZPVo-KM_ZK]L\i6e)ihr`dM=rg7fQu)Us + b)^VHCj&9:tQ_CrU*CRSDG9a8G(D3hB.1e#"?As$NO..+F\S&VeGXdqSI:)^_1VWRYAB.]$ + md>@^cMlmiNnK4M;Qm'_fs*mne3gDC?3rPn#@\m=aU-2SGWHbSAjfGk(bI)LNnE9o\^/U`M + r^XousN^8A`b:dnHL\gTmSB^5=)7C$3rjuQPFS]iDX`hJ-l^(fbPQ,."g$MikkPjXg\m2F_ + Hk$)ZrjZ9SGl2GL09sfBXnhnfg\l'>lLd3o26uKHg[;4krY;np`rB6;4T8p4X4XiQqk*1M? + d%HC[&sTFH8d/'kJ'0U+ZrP;u"M6B]o,PY'9_1*i1uoM;F'dI@on-XSFo@5,=OndZAQ^p&O_7g?I@FnRG*T+Ag + @hAJCKi^Y[lK[!!T3r_G9tFR>k^\9EY&?c%X=ScrSMkV(R=<2*`p&GJ);'uoQ_MPh>r+ukF + M^+i0ZD9KOK*h)hf>g_>f=#=H4be@^sE,NFM)Hl\OTRmQZhE3@7%UA*;-!Pb,El>Ep;n[Sr + RU)4#',B)/$>RqGmDGFrNZ#'lnTqAt9`B_jWNRH9icRAXWL5"h%'3i4]nCAPBFNT)p@)22k + <"r2^UGSU1`?b8boH0orUid0HCIb]FE)h.%^m^pL-]l)T0hKg:@pAr%u4=:BOE(B`[^[YQ# + kJm&TG9+cpkAMB`TDZG[b8sp/3-OnKB0joer=;KIoXu^)Gb=q9=k32YLL8^Vk>C3lG29??h + 02$FCioF-n_Sbd4r9(7pqB&nFEh,!P]qE0J^NZW8,:KoqVJEZm<_%[Y]Ch_s1e($*uE,kHj + ;+QJ,MT=eb4FkGN++BT3(:Qrhj,dr1'D:no9\`rr:mb!.[l]!^Lc)31pB]JM8\]5`sLu#Pd + 800N&4n;:,O)aPhbjFFT;V1C,-$+bYr)F2DAb2rQkKKeTYG6BTM$+ctQ80[P-&^!/T8F7Hf]QK9Fr+h;er?*?_JWPLdD\)XQ+s]:-Och=aN + EI>J`p!F@Z;+$68M)4-T\LX[DGJ1@ + q`;GFJhQI.'+FJj"S[CJqeEGhnZJT'rhhRGKWiS7Y"EUHiH\X+Z*gq0,!DM?QMpK$NaCT2I + Ee_.R$@ddU7^NXT"j1JHZnD8N6H7q/?]pGnr?E<%3DiO5"8uZb^-.RH_dch;%"a;m?l$uuM + !ah<0Zf#EtJlit#CY1QM0Qs\OnaA9@:bta0n\kWQbS#N]Fq6m[25jq!UP-4='93M + t$fP]uePO2&_mjason5D_OTSfU4`:pAF@t/I2dSptWN^OK;iA7'/k/!d(h3D[NB9$VD!:%% + ,JMWf1#bCaGkiJje:`p(2FVK]]cq!?P`epSF%LX1.&?k.h,8Psb)&nEGEf1p44[QX)/m6#b + da$-a-jRUAk^=M2)nYDRWF4%E;gk:B0R'"e"gUGC"P4dbk@WlbDoA4s$$5mro + A/+P>H!!as]gnjcgsCd,Hq,BTXF3KThm&RP@9gIljFtGahS4K1PhJ+f-J;P]UY@<2jk=l%O + &R;c:Zc.D]Yp<:IBH%^AgQV+*.](Tu8/%8pqR@krIuW0sg9'I'978IZMhO?WZN@J#E!h\(^^$Dsr,3_=p/1hu%tZnpZ)5qL:0ar>-OPhnS.JDs + dRB(_?LS*.*7gp,m/YkPm9AKs@ES0q#-tb8f'L02RS!,*Q1`&Gl^9Vhu12IjaM8ccoahc.N1=Ibil-D- + f0/F"Z_@&.?S0G9a1dXhmZU*KTHTAEhjIU0:1V5e:X$3-@5=37R+$;-llU2hZ0=3N(+0#/p + F%/!58OH-C1rM9pC'c`O9sj0o2%*5JX&?jH&YVQk+`'>"A3RDC5W+cl22jjFMd!O,6oEG"[ + G,%Go3nB;8#"Ea7%7aUX(F8;9LQL+[]=-f_eULWCprA@[d$@A"B)]"EQ(Rk[mEct1NoT6G" + %%-`LD0"F*Y7->U>P<7GE5M!b&S9?apGQ3&5,9,DE#5A&_/tV]99G,'-5"FZ?sU\C6L.X,b + 32Fu]qn7bhi@0S;APJr!p%`u'TBHABO&RQ6gn.'&ho>^%U5NOe)A\aZ#d,GE/hOZG"fa4mD + hH_m]^R6$M#8/nR".\$lc\1.lg85Qu\r.d?&_n->63uH#ZWi%p6Vo[#\4(>6PC95%"\.*Mi + ]:6E(.^?V/^,t)`]E5Q6@^sO,_)sKb4?T&_bJgOGoZ>7k/0(6DR/+Cp`4Wt,arAYhld!0HU + cfk+]elTL5n,t]eNI=Tb+%%sHeBuLfuhDS]u&C7bMKA.YW_oh4p`bQe)A&?p<-.:93_*_A@ + 5'H\4_4[96@aQJL1Lqe,j#9;@ + -dN2LZ`34c.m0E!/'gm?T(FWl*;7810[G\5+2%G,I/?8ks$L!^ceD(0CJ2Qk$IB+f_kU##o + 27s!;a]#6:g?5YRp!ZM#GJLld$n:6oL?c1$oSej:u)RKPn,)c&2m-97(j3kE]54s'L_$t7/ + B,:$:3'dD79pPd@5/RXAm/T<^hug7=00rRSlSU>GZ.A2oC'Qdsaaq,XqledT_8^4CZ`\A@Q + /\3,E2)X,'5tM7BrF7[oIY[4\A9D,Uh)VuVHK/!SLBKuYF(a"iB>6AR%[FU9mJ\]m/AN-Rp + gT"5Y"8$m]be9sjQVT82-8.:)@'SAHHSB+(!aCD;4jG59(Ue%#64!]`CPI%9JNRZ%pWkD.h + I8npT9$n5as5.^FJ>jr?qOVG8 + mJ8dC" + EaoR%p4c$fQ>pd>Q3;=KH!(,5Il)8'Go%HQ^cLcfpir + *>j#8&*R9&rc?p_>m-DM+dDA&?*LuQO.lGWUn"*5C1UX)HPk3UV/PfBFSp^9!lU!rBX>Ds5 + 6Q@mcm/,,irA9c=gDU7&e[&'k:J`@F:d)61$WNff5?j(3[=&T?Gb28^Z('ZBW=[\IY9Cf+Gb;FNIb77'PS4WHn-*aHm5[uDM8ls.sa?;`@$7J.s)CP + #H<-rQ*!6!]VSmbF-7&5(bm@2uK0/)OCHCf]A>f"g>TCan(TW)#:MjI@aSY"MQEXO!.-c^h9#m/5TB:f$F:>9s8R70,c`[5A,rcpbEJKa(b + E)-`rLjd\\BbmGX0^?*ODg\hU?,YCoB9$nZsd4@sZh8N5r:gA,[>_54?phE`Q]W>mEs?TIbchj>s`2PSo[A$.p[70B3d(.MiFWW0B\>LkFFA6e8T'4QJMG)# + eQI1,riH;jAW2Aa,T61`BIrpTjF%=8*0%cAaA4'm5g4ne3ua]r.NrBnD>hg$meJP:6,g;p. + 3B6aQ<0Gf[=,t:Z&9Ol!(e0oi#5P^#/1C4<7J\$G4E]S)>Uqi(M-k`h-XH>!04u4QTmWMpY + pu$R,0W`E^B9"enQX_H`?CK@Ih@7/sRe?`oh,@V_!CC[XZ-F7J/Zg06NCL4D + aDRA_Y[VLW,CQ>uAT"Ad(]PFP2@R_WeFsYgm]3$?#@s07pp;:!3hFuc_=XM,J*lhBK0^OfQ + CmHbkT#]kCOIF`Vlt)AiT#9QJ-d8]ZA"B"HAXYo\jOpmZj4*4$makV.l=:CX=q3mdXR4_"7 + 30XKA8S072Flhoi%rWWDB5`QIB.6D9k;eWAG*+C<`#GY;Hk0J_i@[B/[`L8[Te,BgQ#@R%U + *)C>%P/4>NH:3G$aW!@,)6"Dd-f8*qro/p`l?Kd8e3K?Me>O)J4&1>cet-S]k]L!)_8gB$U + NV'74!q"=:1uE*Qc5(32l%eSpC^k>)FtALpa7C.>&SE8-I[bqUU5h8^$*DngJ(,3".@olfT-%$)SmN!O.LqG;IZ@bu8b,SJknWpAeriSs + :WW^L?cJC=b-OZ@/78-'4:P<4QS1q1]P5Vn/,YuQlcC\1TWBuCi-qD#rhK(3dFKYAl<^B0W + m8;$WRc0mF5,D`]WSA[XPklYlK)iFhQUe@]Be';FAGAJ7n7Z.8+^^jFIu-HY=S<#8p&*HFO + *^(hbQHh^2*'1(N.Z1B#-/)Q?>q\D#VAfm`kEPcg8s$Fe6dDT#pZ>0QjR%FjZ1kL<^W"2)& + &ZbeDHaIoG_e^gj7kA$[M!8T21%p6nunRk('2eW-0ah_N + `k1eJ!0aqltdklJHrUcfEgF/B@\L(*[nW@]s,i=;ns,d)5"b]Fo/oI24^5>?P*:AkDnr=@> + )+4GWSe9s""rBJh\Dq9>debtQ\I@&S$QVd>frTjJo=c[(Z+&qS[hKBB/*E_=g+S+)8 + _];5UeWDu-kNY?A#/ld,:T?Gg/j=Y.gu/YW0sE8']fbB:h`F1/M0[oW\1Z$F%>YEtQh!s%5 + .[IBQ"/ftAMDa3cZlu#$6OifoB\D4f$]:.#&F*ZsG;7WCJkd6e,2OUJ91H4E8'&b)I)V3rM&!(EB&>P7j!cKU4q2Aq+&_(kBa2TQqJ&i6:K-A + =a/:q=KBDe#A8TJX@-d"sTL1Nu?Z%cf3P16fibJk7(0H]EhOYP(d,gA051G%+,c*MfMA'NJ + bJ7B\mXRA5H'AW7B(Zm1H5L;4j"[^S#!IS>uoM$,j(WII385]#=-sk=9Z2S's<6[?r;j@*' + :"1Qh7oG^3,u.cGjk506eWk8]=4Th=?!8%E[;^WHp!n+hQR22%?e?[lLLQT.-'!JA"m)"]= + a"YUBA)C[R;*i;Z#3k$p=6KdTB3>#Dqm8T^0KI&3GihC-i%P!?+46^G[JBjEn!j=T_!eoX + !Q&e\0+l+l>W3P=n[r3LaRU=C+3O.Z[jfMl>gGFSF55Y;cL`,!7FLfk5=s@Hh+q2(p"`\Xf + (M7VA_QjK^Lu?qh:HZ&pYG:hhKW?+LZ%s!5E4b!^0d#bH!YE_kPAM/WTa2mT<7JW*oF0hqd + 0$-n,-N'"Aka.s:nA:dLeS4bA1N@Dd!QB!@18i + g1PX9-*(*8>%H6)&LQMKW6l:7^iWMZ*;&1EFGa/l8A1iR54r2*CSJ5LC*fApsU/=nF%ENr\m=Qh]-.%F,nYj4:^T4*f'Z@*Q-/da9_egA0cdJZ + 3!CSq%Ft57#2m^.S#NP*]kX;PooQp9?Cm&-3mZC+7aSNalu)>jV4PU,Q)Zp#uN1K9WOH,[T + H.qdk%>gruE>u>>'fk/nNu)QP0(CbQ6'PV@:D1Eu&9rH&4;3r61SX/ndr3LcL6(s-f-TC<_ + %32J1nI*2BfX:'nZpB-cN-dsuI6M:=c1+R>S^\ouBpj?]];$(dqDLC&4t+ocUsXdZ7EV#pUXXC`Q.!'2kn.&LFI]TORT*G5 + .U\I\t*XS/.0a8cBR0nXE9R3&)`aI-BP<*=#8YrE+U8OIO=D[8r[ + 5Nd%X;eKm0TjaaS$:$l("]kFM5ja:*\fNP=UDI8[A + \M)t&?!$uGY5AF7Q`^KuCZbgRd:g8uUAr'2"bGVJ$GmW\'k/"cLLG?Q/=iV$$g0 + d\,UCpt*6p1K99X$:6ulPMFtOO8'F1MDb>VrO:eNZ-q\a4U'd2C?B;hZcNjR2tj/Cm1#;<_ + ^](5:,D2SWpF7;p*cp/7hM_Q1Ed7bF*i5AfSFMo*/aL=dLsWI+=XkAnfN#Y;^)6iLKLgFj= + .E3bNYWq4C%Dce89k^EF+.@oR-gg)!.B=NplA*fZ+T4t]:+-)=qm=c2^#.;>0K'_LOVM(pe + ^l55kd[0EDUg6Yc+4ZfP5euR?T>?F2i.2R)r?Apr&7n-%qY2C:9d2MjqV36ZL`Qdj7`9r]4@X&UXOEJn(%QVfGb*I/2@[>-qhK\mhCKfm?+]G4V4?;-"ZGk.n+W( + )0qH`k3XIi8%+[.>)u$bX`@A4[`L'HMf!;_^TC1$UY&e_HZ1S:[2:@9gY=X_EP:2H\V_Hir + !T*m'VE@U8n6p2FZ]0E"l/q*GuZ@*V0G1m[(Nl-?2^8=p6H0R]m%?f/6/NoruLaGbB\")S+ + V84''R+=Y5tfM$%^]kqYn\$#%5XqrpZQY@-f?YAeP.k,63ZV@ZV);N'P;)6GtEKAIM34lr% + GmB`a5q`R$6W-?dDKP/a^ca(D3h:dE)+]#X;2aF%FEi>#WC1ok1&naeM-9E:u`#P>7ZhhekKW`9kb%*c + O?prdEoh`E5<,\%bS$*$]U+u0d;^!-J=M0OH#kG%AMR=2VTgdgJL(sM+l[k]+dh!gbDRZNP + aK`Zi":FrD?/V"L>d"_Sk7r#c5>O.,U+t1,#0 + I/c7U>:18'Xaj0>cImRJ3g)W\07jY=L2%T]?%`oYKU*6r/lNc,pmetik$/Q(B$Y(7Wd?D`k + Mk\c;#%Z_],QK_"fl0=#g$A>AGW2WOlWU?l3"'Hd7>cM\M2QF&A9"6i5`2kT9,0nW3k\V$hRZ,fNiP*rT2ii&*R?K(;V + Zl_,'^?&,a:6%00'X1=-BGF#*hq2Z\Xt!Uq*2#$j&^.fsZA"#E]kn2iL(2Ztp@"7SrA&4UI + i;)XN0$?L,DdrPjpFZ0tX2q*+5'6uQ9IlMM%30TC_duubtQT5_I3E)D.';%AEJEEAMarnp8(!JBd@3[(T,K=48C=*L)(%4&-C$b]b99h\+X\oj-W5@m%lGaA162!)Pu`rA.F1sdcWEiD? + FM\h=449%bC]I=O"69=p_(=lo#/+;`_p`=/JXjUdaYmOV_*U']MdV!P32Y&\Lu4?0,;3(1A + JSEb5C_Y0Iuj%C]+.]f)@HC0iK"j'-:Ni*rkou_N5!&ead0G%l:OP!HUC\1I/n@?H#i9@*g + Vc/6t-!(26T22=,Skeg!']Kf69>@ahSl'hejErU7q7jV6^HOr'Km.E5t$i6DTQ0Pd + 9gfdgI4tN7@*sC&T\aFB]$(>R^?pO%L5L!%2e_a)%i3+!Y\+ESJhh5)H!JT!^>Xg2WLN78b + AVoc:poN^aF6-*/ZZq(K0C[\K+:2*ONe/&`Xpsb31\-k`jLK[+=K.G,Q#U+8"XZ6U>rrlVA + =S2,s5lY.IK:ruuZV:j8KkFUKEhfWHGdQrdR-nf73o&0mr&.O(W.Os>>l/?;i_SjWu!* + u*E7&une-.q'iQLQh$s="i+I0N_Z[X8+IBfeGA?L + =P6h9G_#V-\mc]19_-Alnr.8s'Nu=1\d#=-<"<<%5`fR@2PSL&340XBjp]U"BHg,:eruo[q + 8ZJMqGY#(28^e^Pl:ab$q#>/P)f2JO'"Le + Sd5,Vr0&!bgD>@OV3F`bHlU5pbD_YHb(AUC9iC1+VE*!-2(C>&I'1WiR7s>o?(EZ0A+Q>=, + T<9T81m&]V$$2?!F35:k(I4]oWH="LFOA9S<3i:K]:>,#9D$,m(M?F!@d$m1*u*1J<8DbBI + s9LsGW1J(GE#BC:?tmR+jRbscG=eK]22Z=H<7(lA#"AC'^aMZmY^5s(V$o!8!l)-E^5Jea + AE(acfDQRnshK(Yfh3&^UM4^/)&><#2Y=A=J^o3:um>^0J,\7%(6dtThp0[Jj^(iK>:*J[h + @s-;i([HiV@onX.j_mkV3[KVL'#;gT3M6fob1Jn6VV6?(_MR-Q&R'Ekt[fnbYYh89H)"Fjm + &$]=XZ'ui?HR#fM-*eI;ZJ-n9)_&*t0=-!]Z]*Y"=SFn-r>j)+#go%N[FVUF= + YZ&,/Wr+A[bGS4)(i3HDq%i(S&7\EA%\-5E7YK2$*T_aM/dNlR4]:r3L69R.7dMl8?)c\G;5Pq2k' + /#F7Hc72as4&8"8pLFVM)DQWKcR&EH;qKUj\(d]>7e2^gZ[,?B + $U^aJ@=MH`XSV&te>*@L:QpHB$T)/4TW + XI3Y\5TDrX^"t6>kQ>qQ.;r(iJ+Q!X@;NXO7S-J,@8^tXa#H81GrOQ(LS0C1b^2o$s>ESTs + $"YYILL6o3[D;#@a/ce(Zt6)aDgM7FXIjeD3dJ*D+de:XskleX^'th.,!KB@[`]Zo^\`qa_ + :lbI%(8&c"`F)hk]3J!55+fZ:P1I?Qh+EKZl^%GQ)inH$'=O$J,MCMe3USV+=@gUS>O\PXZ + `]u-+\<0;@W\liIQR:Wu%5J('6D]?0P1f&%hKtLIf]X`U+R>&n&Dg%=S*qft:plhjO&??=(c"KZVu`ODlmg)%ad+/0B<`j/]HLiai@C?UK>ik>! + ;.KD9,f9<:rrU]TH3\urcYZd/$kX.8p=Fkb6d%F>r3XKs%cqU%'#IF-^ + &?SaP?4W0'h*Aec[dU,eu]d\h[=jo@3a#u@>-G9Z[+)1IVDtea&pRcs0hh1[< + L\MsJpg8sT+-(J,Dq>Eucs=AHSU6T,[?MV5%"5>V%3aGT\$sF5fj1IgmauNNNM"S@Ck:EPi;].GX(+_8/rBW#DGI9m[+tT)\7/1@W + %R7?2j&E`hP8tFCm`#ON?JnpH3n#-Bq-a;V=^2gAdO6VDC6>eo:Y0]-gP!h"H:`ThcYm8i- + Fel_IFX1qIafeGlIHW\liFXuAGM`Qre + B2t&]GrVHVDF'nERln0=YL;`(+=JkDLs,Y7^10IkZ?2iW"Aoi7!k'oOcD7JL"4YDpmWRP\? + mB%_I>2n3$gKQ81rNbD;DRV^@p@j-LCiHmZ#Q&"jaQ)i9c$jRormu)\H@7ED?*)n4p3V4jk + C:2!CA>H&p8`D^kk"Y!NCMt.h=11:I\/iO:[e$p^Y41PlhLKEAs]06?%4D$c;&Brb+ZSDApLP/`,R>"'1FWDS.@E\('R_2u;KS=_ + AY8V62,)J@ofGj7Al8S*<0\>UACQl=;,pVn'iS2+jq3_YSo>pFFVo-olais[L%Io+ + 8."?@bCBSP@Bm@.K=$a(4Drm>'>1=@NY<_[;;2L*OTh?f*00#g\3/r?C4e_eTBVkEHu\5#Q + ;^=pZ;Ta@$i\KK&IP1&mUdDrYpXO`QNGTT;3b\RZ#kKWq2'0\k,#M#S"JfFBq=EU`dOKq\c.83QC7`3U?L^QMku_ + n[Yq&LHB+kPl<:?b>3`]@AZb9rrjf5uAZ!5IQ#*"cPnn;$gE=7H,`&]Jb&B,F[7a8[C+nm&(GbAp$[=F]pimlA^94U3rHQ"U+ie + +DHd=4MGkq,C@:rJ=V/p:W\-g33GHF5D*]pNPNh^'BGPGeO]LicuS(PNI.>GEqB;^5noNHG + 6;\l2*ZR[Hr6hpUfpAT,594r')RTo6ihVfCXK`=53Lus-MRU!P,fHtF&u'(>M=F%_76$tU + ,*Z(Lj1ou[R(!j*jBmhmL+^J*_&4HdnV_C[J&\G?)Fq/MNT8#p-D4E#9[P&LQKq26HH\K4lcI#*`>mD9,VF]UEip-&R&I/Lorh6<5+31![LH!NA2V>`JDF?AB_=Y18\m3*M^ + bhbpfDEa8#"i;dsOeRCP6kp.Q3"I;k.>XuoH1nbU)KbG)R:eC + cUgc@-S($fTW%"/ef(;Z; + b]!NWWp,jnmaeu">)sQ`4@cE^*lHQW:R + -.,e_'dA_t8I0nc(i;%0I^u'k'pJ$UUda`E8TBL(N>_6l&3%U%D<@m'W*HaOM-\ZtX.bZ8b + >[q&^joRqWL-7^X#&,c*o(CMdNW930"_-6$Wc/[Aut$'sBo7+^?>r=-T'`+?3(L9b8>I^Z_?/JZ)_XT.1Dg>?g]=eRFoSaN;HPR78^'cRg + XMq#aT866mOp.Om'c$rY$]At;:.E%adClTXE?P$1cgD"m'C + dhL(MfnB0>:HF,kIiRH$AASGAcJp3P('pH[p?"eg#bLX:>p6YNS6U@`VgdMao5B`0 + >-VY0t28Oh@i,!9^/UkB'eI4qrVhAg!`#Amf/i!`cmNY"@6[rpEL@3Kb;$97q_i4T@/b6pfn/QAd=;UXmUBk9!]'[rTi*d: + @nm5q6$rh13eLX?Na7B%:SV,Z`HdqW_sWkCgTeUFJuWkD['#;WN+Wl`jfD.p#aK!q6hg"@m + *B;]YI%&N6VbZsseQcYYTY5Kj:eD\[-sN@><,L'p^ + ?=9WV*T9QmG1dBZf,3oA+oWTK_q_5`!@:.!.0d\3%=N2?!/;sH+]Li-C1DH6kR$iRA^`+(-bQG,,Ssg + o.Z([mHZ)&@L#b#fTZ1JBle-iQ*Ye\^2Z83gmo-Kl1.)?QE15dTNl,,eS.i^>rZK)dkJ]tG + '(c(\=*?-cMX#6Gi*&DXoZYNK*o23@$;SiP[_A"32$mWV7,XO=5_IOt0FZS?bJjkcU3')>p9O"FlunN]B.[\QY(D!g3pbCq4f#.84WYF.4U_S>hF;WN)Dtnsh`STZ9s&\hg& + #EhY$WGe-_rWapFeF/UR?WXZUhWjA`rnuO>-J@`q+acs8PP2bTpTV7=5HF + [rZ&>k#rCfisb[^].6ib"J!ASuD^f1HPCs/8@b_;f3^mr]cZQ>RJ$aL0MZ4eFO,S.1K&2J# + L5pY$NoFoK%/'X:nZJ63iZ,EsW),8U":#m3K<#j0c1r86/__gAj/E>#$jA%Pmc=H%^FQH8N + k0AqKcBRV>V!H:iKMPBk;:*fi75 + AVs>CB?G<5i\d`I!-SS639`FiR+F\h&&a_k^36Grf2W3b:(e`-kQ + PJ3'ei.eJ:m'SY + (Wc;dd''_W\1pA0''.bnQ@L`o1JoAb8F\@Hg&X>q+F_!YKXGhj_(e*n&qPAbJ_"W*5+R1^. + [M\9FYmSKDDaq_S="d9O2\,/B5I6a"^No^b!UC6(L84SkR'-)EiD^Of"RpcV4hp1&TEn/CI + 6QZVbg#=$uCf/N,TYl`s:Rq;YPY=nqr(r(F + :h?NN%0]QsVqRE7g81`O[0=nW>tnd>EBMQ+t_K^sTh9dE@M=738u0M8j=ndMupiSKg-_CJmfk`l08oo9 + ijs.7X7b\]lWp,qMPT/R8q+;XMo\r@qSCcdO2jNIBB;C8j7WW`*3Vdqq"SeMiLfK.B)FaCR + Ego!QPXp"gEHaJD'kPs@F55tKdje3&ajnC7* + #?R23pS'P(P0%DOOdS5@-##Pc4A,%^fLBL5E*H5beq3'F$JCkf0+7X8 + XELiGb)fAt8`"s;J#%+l==Z9;/0&mEpitaE1-8X['1ugJO@n&?9FuJB82Ii%3j.86U-(W`VSqH + ?1gHoOV'aqF>q*U^Wd%3SXmA4>[=O5hmg3'FLSZCgp8E>@^jDISu*^f#-#[7L"`5T"-Q*Y! + "5/e.Fd-O\.%TD,1\E-^(;,+Z"9Nn?r^#bJa7FY[AS\`^])b)-j-q2'Z4WgVn`mPmK;D#uF + S!D"2QROb.gq^F)eL8HPEnpM\>m$a3Z_:Y^01VUN\jb1K.]---fSuHU;dulb[";5)h,BTph + ;(.ur.`\<[,)dXhCa,qXGY*=I)sftkMs8*9TZmaO\%0VhS+TCc0XX[);aDp/JUt/jmbpUn` + Z.g?Tn&3(%o6JdRkLQhi+>DA(YJr6*>'-ho/ga1Yps\X'bi+^)d;Ij^@")R*-6J^2!Ab/d? + %/AkQ@oi14OhX_>nD%*Pd_f!T + j0$8c(;jr8_sCFBlt41PG/`fs`pAH%m$>f\Nm$ddbVW'WA'FW+fL+a8[,_EUc]+Z0-;E2_Y + @LDY7&EFWek7nR8`Y?RUXn(7[B/H7ea:dem7,R$hG7<%:g5j)mNtidDWBhI\5=igmTmD$53 + M$E]LAs97E$*4hHSBn>hg$%>V>I+mU+B4LscE=jq_Fp161.T,WTIJ7ama[^!,9fc.q`$;QO + s%^!WhAD!&trk2^hLG&JjYf14,+3h1]_,HPN@gcFH"]*"WX9E[7XK:&_<7?<-uji$Ojm'k6F!Rd%e%5+W$oet+PM\+8#:C(:YQ[kGqDChcEN + f#]JET'A%lCJbbDQ?HYa_K*=bbWEso,?olc<%pk=5L5W*[,;WZqX=^_"P3PB",TX7,i[,@Y + VKsT"Sp]dVJ#;_;;rncV*W69Pi^Bi&>8qou?!6XgVG + GjS%&ODJsF\@Wq0o=(R(%DPb'b[Tcu@>]/;SDY@Atp@-j1OPF15.)tl6Ie3GrEn(QB@m*+f_oaArIO`[k?,W'=me4a + FgkUm,%C5qU@s@Tm8%>C2dh)!B)Ylmrb(;&5N6hG>kXA(rV#4D^MEf(\+\\=j^2roG3Jr.J + Ti/0Z7/Y1md + %>](?:qc;Bn]%u,M>ME3UIt+jFQlI.T$&?E,RWpr!ru0@'j0CZRQ;scDbJ2b*XO_GOS^92t + 0DuZM8#=^LoPG%D[JnNhC\Bh!QIVSYJ*ltlNRmiL\7[[kr#QpfM9"Xs?gQVc1R?SSoNA:k? + W!6Blp(@FY0UsNdsq,i4qAHrale/ZRU2Uj.AQ/%qn=.1:VZ2pqu2&h&'+4%s$-hbOCu@*,R + 4Ce#!<1q+6GSS#/TeN+UOO_Hn'W5`b[@=6SR:X@[W#L5nQL>*).'b#Pj0e`DL/I(<-r6?=B + 6lr+R)f0uS0Ba"cQP@7N'UIit-kr/+oRngT2;8Vd51-V6ciSLGC2Oapp`o";:[92b,e//RF + ts"MZ<'Zi/Tdh-t:+7ial2Aa@QgGr"bRJpO<;oJ,eQrCZo?I/6Mra&C-,mQc`V@##2FWj7=Z0^f>1kffSm5HBWG?6PkP8h,>B\Q,>ME3[BNn\B+_Pk%#Q + ;tk>fEhqG8>CBAU.U;rTAMCQc8T!Q=p0lIp]\a"b>L,JRj8gfL?C)iL!p]=-b65+c0YSH!u + P=ppMl+Q,S24O(5^!7?HStnQK!9q*%`AAQ`nRPSJm\s-VqoQ>)iNfo]<8TD@>jSnHaeJY!T + F;?*BR\hR9_BpqefVa)RW!--5YILFpX=r3%j"o3C`?c + h!\44j`hl+&1.**A:@?oj)K=fCaQQ?.jBAT5EkP?6\VVUfVap(48B`d2PqM.o0*^;UOrF,!2`(/Dk?PDCT:MbO! + aIMuKHqfJ1:+8V/eiZJ#=,SeB/Oe=&(ZO4YMaOFf&,=6aUF^&;;5Q6@Ltk%$/8u^$aE3I;k + 8g>!GE5V^F@U$@5NDP&*RY!Ta=.E=j=obYE[ZFYaW2SdKT2->R!+Pd.4(dkQ]kcf:,/XD@mL%2s0WD)tSlGOg7,1k:mf!jH=[]\^jGGFRbq + 9^n-aqEBVFk5:C(CUX.AJ5KXR9.F\d)cpFU08uZ'h7(dg7(HWDX%BiVro2^O + 5Xh,#6:&'K'5LVJO1<.OGF91GW5iaU)63^9+)mK,)%._%mj)U#N7?ZKKu=F69nTYTg1;cYe + D*05]%'@fKG'W(aMOhU(mbY>RUYYGK9794E_4N*i%Csc]s>N2D"bJCE6o$JB:$A8HBqJ%qX + *FR6dc(c65"jB@)MOZq,HPg/L%dOZFbqF_-7Ec/H[dcr2jlCP/qHr&@25U^ulTs"NS;0S!lWCtI400m-K + (TgKieO$7c3ifte[8nf#C94VUZr\aM>q?rmYV6-r=K_I.q8c8A\!HF%M9J7M + /k4+fnJ$a)-s;VHS8CfLY%^b(0HE@YVlN=nM:,QX=G&q[M$OKK,M[&D2g;O7`[=%,%]!%*b + 2WXc5EcNa;G#P1]35p=n>0u%jg7HgmOA_<_gdt6RcSOWKm%G=j#E5/7lK-QZE@9bZTEMjrD + L2o/:=,\Z[i%bgi2(L2Y@?]/jREl^[RTH-[As46PDlTBsV4`(Y%hQ,:U;,S`)%36' + j2N`g,:*=7QJ#2,qreHR2)oFg%%CH,*JC)&/6L@JTF[i4X3hKDeh,t+C(9u@McVQlD2\,K0 + 6.4KP+6b27YOmj:M(qM0B[)MO@Urf(;1iFrfYJKdG0nr?[PPrpiuDRKfoLJp5"G"R+s\f"g + _*8#8H$^WG;PeQgT,Khfhj2p:@R+*Dmq3VfhIEVTb/nQI3R/@/!N\1\qGXTlGZW + _)4fs3mjh[^j*-?!m%+3!C\'qDpSF:m3]8geO",]h1tnk%k)GsqG!Zg3nkRK5Wd'a<`)`2l + dm)+)L5%4j>&M:!e$>q!`Y/sL%]Q`<2=H9lnR'T]+h;]SH/<@K\fXNXT-@n\>6Wi!IERi:R + 2D*%n`VoJ_/p-WpT"sE+A!]3j[5cu"+?nDSu-j9Uq&`*?)H$TH5"9=NU$%[%`#f0Iq#(ZOo + 7!PdK!o.]8.h1P*AVr[;4T/D%-fs]uRBm/Dc(NBGBPdF96N2rP>'ij*X^WaP2TOBPjC)nBV + 6m]ih&RaoCm&Z0\@>*thHWIHErH6b934L?>hbroPD.O^^F_SGgUNoZmG5E6I7.C!)PmAOjn + gN6oj3>8p[;bQ<8W[9ITtu`DpA"S=8)UleV/MB'#q_3%g69OPdJ3F%9l'c#VFgY_Fu@_X>! + p21rG%,_ls]j&/)p?:gfA(!UNcobZb-B0P(G"B*L&(#4QI70P^o4E-IGWeSGNqEcmVU8+qfaWUk9chpr.+cMldg^L1-g];YfS<13f!+$A/SN\Z< + t;/i!l->gi"Q3"(CtK^&!6`$0`_i)rt)F=&CCdp^cDRj4rhDg*g#T=d,b`HBbG)mf'tHG/L + hK$>Sci-&!9-l0gudr8JGSI&]#sL0Z=GS^D3l3$gjJ;EFfn;VV3W$(@c4$D6FaM[hl"7([\ + qu./_jl%2c4cgsM5>&X;*c3o.'.PKhu'/`RaDlPcnhh[+!9%K>84\DO9D)t!kL%M"nE&t0: + J*:>$l/g26Q-m9'SilSp)N\oe$ka`n]jAGF-.HKQpq&a><+7<%!.JW#(r8l'&T[FD6DJUg5 + E?V'rkAhX,Z6\Vu0'H1_0/>5VDN:KPPbaI)l)p[6m\M1=2!VA=lLId;0"3-AB'\ltlr%6XW + dkj.E#W37%D`5OJ4p-N\L4\2-g_HG^`EP"C^2)?%)C@(+Y4:!R+J$"q(KrGlheb!mG?\$tf[DD`Fse"B1Ec + OA.`S;$Bmga\Z1L';Z&BV[E$p3qKI81TNTYYre/W40uabo>Nf?aZMB&4EJb11XS@,cTSVb4 + k&(IoAr.lk\K[q-k>nXd9f+OWsj(J%($$DOOVmH#hKb"'/:q%0eF$F)*ou$K2DM2'I7uIp( + *i%6$*i-ZnW$^&OWo4LZ(uC6"`N$%M5fU5L"sJO^/A0=P59o7Ib"[#fd_nCIYRPg1KTHe#gi8;.EK],LLtseT8F`;-;5m-,VV]SahbgVDEOQn:VVhph9(Bk?NYP3)*1E,*9@3?:m>Qt1L7$UVL2,jPU$ + !!X-#!dJD!8%j65SToV5sbAtEiQF'-pR/X.i!T@FbKAT6WgN`/5j>PY?+k;6S0rq/OXFK1> + >uCI4c/p\*nFKFgWk1NA";Epi?H1X.i?"P;&9G]-R+Gp!ih6WqpY;?1T'51EKGT\MB5ar'a + 7c/)rDH>;K:W^G@r-liOrN6Srrp=jQUpZV`RnMR;!G-Bm-*U'CSq6WBX?d788h3&Q_4obs3u1C\0f?'FtYOF$k%,D"KN#- + tu"+W@+>Z-pbKc6/-5,eSPmOCmOTVG4e6;7;\rs&>9XInAdtA$#:5bE:IDj<,EF1(>?-5gD + "jP1jY6H)RFMf+o-QU + ^[*XT\-Pgn`Xest7GGO@al;>d#G3+9IGO$m?H# + 6"3<7moPQ)=4e*e006GF]n*K*/.YHX.o@k*R9B;9[WS?t056&ZijWiG>8$1)EpC0B^FsA`> + Zah5YE<)9Qql2YqD/djP8@N2)dZW`1,$69[I7G@04.`Sr71ZB3/'/Lfe_1a@!2nVfTfl=(H + ")fs/!\l2B`'fW5BB$@h9;`/4X2KF(99>A(,ic&2e!7CBN5637($(1QuBpff>@*3NuXu:Pq#ui4$'7.TOEh:t3>%C!&!=3SC3T>cf*c?h.+,9:5PDW + Q.#i$YpI"ZG6alBH2X?T@(N#N2mm,G5^uoU'=Fo-kCjA+.0[P6!:).k@Il@?>q'VMGO)[!f + Qfu>,ZtYnLF)gq!7npTcfp-TBJTB?u8@LF;G<8AT4\<+KR`S-C;6=rL8P5=HS+4EON[0 + /'Z/UJs!;&?hhPoG*#78`KF;E)ZPdS)In3IM^q;[R4bfA'6q?orhRQHFS2k0/E?VZMgeG`J&@ + 1J)!*'8b'-s0Gi-qXl`> + -]5,=!;Ng.n/7.r#m&\pYi[$<=N4i:";#!h%:Ia#@+5Wi-*i'MZJ.+>>9XM\,I68eZeISTT + e%.HO(F^5A[@qf#)QqP*Kca)[C]kTqdm1iBsp*=[X2l#4)q`VDn!?$\(c2;qh;OAM7FEQptM;:(Ukb^oRjr!O.&)&8=?Bl/_um9(uBA*S$\r4fthd + pJTZ7U90(1c[p>OE3D:=@SPe-8gdQEGG>4T%GM2B&.G:=Rn0NAS+>-elpQ?sd"ie+[:V%'_ + EZcqRH$[mfTuo0<;Dk0u5GFD\U=)K*$<)>tXeWAS;`[1@pXngW\0!uO%"R\ihugUH\]_?*' + Z!liH>-IL3ES=`=IOD"klOb:/0cM0h]=@oMTq1SFYcgqDdK3YLp$:]*4 + +Y3K(K]Msr$O;hQ7NGR&c&Dnt3=iCC8e:Y33WNV6^?)i_@ULUb$Sf_pf_4cA!AOkH:jftF* + 4rMA3'WS0M9g3p*X3?EIWNp+JUUg1R\m+!SHe7(spU`N9%GS@/)nX;6X:*gd + e6Uth0f?Y8q*;XB5Trj?\+1)s"5IRlQlX*'#dM-.)48M0^\O!^Ob4K0/\mkeRIl9N=fE;Z) + ftE(Rp72T*$q*28DfphjS?n;\^,FRd@GYLS[mXKc84U.kJZdO`E.V8Hs7]J?[0G/`]Rnt4D + dkt$[OQKTbj]KasG+I)ou*kU.RLp)C@09OX-ANlB4rbhR-8:TqhO^ICuKp!Ic"2=9ZKYhO` + -^0Y,C.Vh#JabeE&[o"4=9H1%k+Ji\B4hY#.dU!=;'[_1htRZiC($)tZJpY)[#^4gFj5U&(CXqS0<3s + 0_^\\beV'qgZia-jAIVe9WuU+9 + 4l04gOL%5N'nE4=1pRLI*&%#a;hE'T'e=&U;f/3>m`:@PS2dMFetT8EB>E6iB]X=b%h4)3i + E\24Ga$#kqiNs#Zgu-e'$oXlBAT;5gjJ<@4,)Vc)d)Vh=uF<2nD`'meD6\Q*b$lgO][NSe3 + N\nq)026G8;[_M4tLNUJsEk5QEf4R7lpA_kan!G'f$Mrn.^[Tm74sq5+a't\/=(XrMP0.A, + fpo3>adg[AglB&PMaO8gBj>PRrhihtenas5*GbVmWnQhugRZ@3e%FQtGJ"1\$,Yobn[Q5\; + hL(RJ+L5khJ8aUe8TOLrJoA,ZeXn-]:K6,J\C.95Q&6V5GurcEkhS1%tDXaoD:c,N_d^]oD + APPYQON7S7ha)!l0mS'*LU6q6^8ictjbC6*Qcb$Gu!%Z>d]8gbfR/nFT'WRh6W0E$+5\:DC + b[<*Fd?nN:4rS+3"`CW;LIrbEJ#e9N(TZ35Y#7u"+%,9Q'I]-KsG9U@+FL>i.\8lCVG.n[+ + (V_$#(q5/#PZM_Bi9P0O(18".uUhrg>>h(`,k)$Bnc82!P3_o:RjCh=fiVmL0#PhP,)M#%E + %AqWD'1Gc?AK+-:oS"M[Pf7(.A/Y^O02.f+h)](FPkk;ge!1qB;An"":h/$U@K'b:'n'>!e + lAE'=k:XhMIopIh0!%*p%<]A)/&o'@b?u(P@Rg/q1`H`[X!U1%J)n.(9pB4#r_JiHH"hDgk%F[)Rc^$CEaKp + aep(Nag&Z8ekXYGf?>-o6NML97QK?u"Wp6%]Z3m1l + bZo!h@;E:sS-iao5f$3=Ne/kGXX'>fQ*OHKr;aa + #HbA&N_XP&=&.)!`/uBePVV/4'V_FW7)dA$_^NK!2&lY6th/W + U51:VTYq7l.+/_pP][@2e3o*(m3ADtTb>da=\;Os.n2A'(S_[a9Mr)P88/5L;fmPP;F3lF[ + .PbEoVkYb]g>@,"YD%MZC#B)@n]Ij1F?Ye<)\*\:C5UQZEY&W,T%XsMD+=&kYoK*?EOA`m`D"e]62p=!2Fpqa(2%(2$SL9W7:`)s^Z@k;&M1$1K + 6=WP+cCoZAJQKDY_-Zj#C9oa#<,KjIO#'D,kF,nHCQpR=:W_DJS7Kj/cB;Zuk;b7A,^*@m^^!(3iIj!C^4X_Z"#oF + kjX6l7Mp3OB;[TKqT)]"lPrXUQBmhO3AYtr_H?NL..qI.ScNa?rTeqQM+lZo\:-JlA(;g?d:"&Ng3/E>A]Vdg[VcT?;Ln(EI/HI"185'E1c3nm\bS[nngc + Q*.]oG3*sl5G*F%1GDl5BB[O+%7.gY6 + 1[:et$c+JPVGL9rNFtU89o3`+i"?WHC+seN0Lq\C,/n$0J;YlF"]3OL#`jC^:%Q.AXLPL]B + [G!J#\3!i035!1_Z+o5\4f<3:&?[E?!.eP"7hD0T'gm_AC:EFWZrW0KZ1^h + k/__hd]%*;9MkP=IlU8G`%qE!U#1N9];Sj8>X^SoNM"^^]D&C7#[oV=GZ6=MM)(!&,[fr.( + .ueNd^Os'U,33"KLs[i'dd`n]l, + [!Ao-oZf=YBqb4CVN>'CH#Gc.B?a51q-YH_ + eP-lHoO'43@OcNW@U*>d1\J"4V:nJ\-X.mqO0Ap5q0+0WSo$\9LL?U76#=_'&bQ5H]#>EV` + ;Ue.\i%al]8'POV]-NLM.0Jn.J!c83(>a!=09g:aZ=#0S>0C"DC:.Qro&->m'R^K_R0P!'5\X*Wek?JA;Z6&Oa1X[M8>*/!HW + k+]GGRTp::O3^USB9#8)^/<*GriOZ/A&!plHMB]YXdEn]+WN3nU*DdJNGlE + a=>`BF1+/^fK5=P/]?Zck/c;r/rR]iq5?jkW@k6m8Lj,?]Ta?cb + rjsp8S[l[qXhH>pH:'r0:UKT;qB(4a@Y.=YPZumj4h]agDrrBat);P9*Dro5!B&S^+0T6Hf + N8,jIV4m^0CCtXK5%,XZrV`b07"\r3&a%Y0.fh9-gc8/@8&4[7`?b92)e;>dm@b\4`@$bH/ + 6I[(8oA!5H.95L]TtBW8g9_)0(3^a-f_*5j(L"O(*\b\Rg*o/T(b6AUdoZeT@BGZ][(?VGj + f9SCG[A.Z+a_nd9_3F!65"Oo9W:J:oA[-SU:$lHB')%2'q[6,D9@obnM=3]2sc0QNHll`1D + i5-^F69AO4$mib`@D[(r6@*dkS11mo.Lo*c_TQ[Aj=[mbm6^to:@']rCbn!gLGqWl6W!kCA + 2d:OG::pb:M`*O[.k414?(@u2r$SGF+5X'5S:&K6mVcA4@?V`7)WX<.P$?*6]+]5M\AFU7& + m4S`<\I)5,usS!^J80\=5LW!WiWO!e + q*L]&,g[2RH+*Drb>L%'-d0L^[d.g!dMJIiEE>Ppt=[mo_%/#7-q,]OQ@Ei[]2X7UPGA9>k + %Vpa;;(*n,cO__P/WgaJ#mQb)kP+3CBpG.4"_ueG@mVQeIu-_t'3kmAqbU!%H_)>3go&IBZ+>" + 1X:\cqI#p98qV!9[J_l`7RJ2E)YB8R"/9C!,s[q5WshR@0]&0L:%\!otJgB_,4d\;3_tqFi + -Z(*RVjG>[>=':h^15aBXg?S>]YI!':;08qG&(Jj9?e(]l3F,uS6F`qm8:*@SKR>[:Vbd`' + fR'eW!s_p\K;a-g^A,HJku1WRbs;c.a0"ZEt-38PU\;k5t]EJMV7L:`qdhJ@tZe;7-,^/Q& + de29Ql4H1sn7(dA+aTRa_qoMfoXZWDDe>i#f_Xc3u\4M;FS&4mVHdc>Zpq9C"aiaOpI8jRr + s4(][*AjN`79JIJJ(:)nX%cg&KVr3P?G8l58rTfP%<#X.E+"\5@%Ia6Q4.0(%,ei/@."M4r + XIfr%q-4b@3-(j/eh_@'inmc\Qs7.IMc-])-C" + c#c6HZ@1aC[IjPuer[Q8VB]pDKbR*MKY:96%Fh4(jc0/hp`cM7"W'qc(sU"U-9=uE&#<&iT + 48J&kO;4Sr?7Dcn%Z56qC-PV;"/@38M!\^0!eN5*WR'J<1(f + ]mTrq-\D,#L+mcTFh_Ml^ZHOMjt.:p?,YMfAR%C=;9ifZD&V3$Bj?I*>cqhsVJHFg@#&%3K + W#]N#dmL=f?/XoQH! + VbJ4%t9*V;trO45OO34/G.)I6Z3Z5ff_8kcB_LZ(0[q77c8'B^Cj\IIUYNY2:NuBeAqhh=W + EQp/L/IeFiT.Wq]qqqHpSZeMU=`e=kddU"+7[i!ItB,n + i^CfG(*T6A'7iC`#?Z3Uk.5#&_/#(JCP+HQN"WGGe'1oDlt]@*d'@-Ag#DoJit[O*k0.&;k4P@K>-j-1m#[Q@W-B$Q>bj40RXkipC46"C=Q6Ns#k2F>;F6pj.,cSC@9W:]Lg8t8!h5o]86JCIRDQ+pn2H*g!M<.K3gH>c&G#qmY9LRU:")' + [t1i+FN#)$Y]KmmHKIDBc]@Som1d5GNI?=`21BBF)F>\7^4`bY&RCL!fKn0\;Z:g""D/V"R72fXB1:9^)VH]<0qHBVjt+"mb^glVp6ikhM3Tk9O#SP%bTOkoV9e1mD?99g%d/]aa^!, + uD^\R;&IfBssO37\*OkSitX*0M#7KIK@urT[h"e?rX()pGbd=l=s)!CBh'8[le`84`eA$o? + TUAs$(T954fp^F=0@r019nH7M+rffVU:/*m+k6bK[n6Ct?h_m`otc1i3HCj)37>5131 + qdnWrn:2=BW<%1t!f%H'JA*o`<`nB%hmVTq6mG?[dmr2&di9gcE6_*mg4S\>3I-t$0:W30A + p?6&mH#B1Tm^94c`_s`umFs2rj^eZ1L03mZ$'UZY.giPl`s2U3aQM)/h:EBIk(#Ms2gS_ZkV!e:1kY!af'$E$n]g5Y[HMVtku7iOBY9:E,K& + Bq,O"ejB`=+CQBFA4nuik<4O*NqH+pX4C95T6oBno*h=6*hPUQ1\4?CX%&e + (D-gs139iC,oNt#YB?0q2a*aN#I&G)6YKVC/c$["GI*^+9hp6]]d!ZfWFE:#qLK\he8_5mo + CM27#?VU2hf_&&97(CmSDn\*.u/l + "o6cm-TmEBC'I>2[nk[j,[eh.W]cL-ME0Vj54TgKphYLD#[^Vs)2dB+)b[_Eo^ii&+8A"-: + lici]!KOs6`O9"9#I_JO(4b#S\Hn$&1=r&EisWY2>&S,&p`&=hOco7jE$,"F`c=C<#>?8Mb + N].Vd-Qch=,S@5=4ok0mJOaeq1k5MBtbo`BV/U,8e_BDY9.8Ysk7PkL#Hs&#cFWg.nUbap1=sOLrEkD + R.a`Ys,kFaZC-*KAcBhdB(N+$Nc.:Xd:l`E*BX:"X4/s`>38SjOfh9Q=+c(O+\o.SZ5N-0p + B,lbiUL:AG'>$laQ7JFgn&lkN6UU03dk>P'q#$gG<:61#Cj\l@P[kQD_Ia5l1D45:]GXVhS + Wn%5/0n&jbO`"IMF0t6pcUG*\ + J'X>KF$NPj[0&`;jfJVQ;!#Y47[CDm*e0K?ALC&IKtSd+(59.B:/bg$B22D`"[u9([09<.e + E)rsS + ]s-h7*l5-u>;"=0m9;L%[-bJ3h72klT&7-I>INe.mp")C:m;3=]tj,)f_e+--[G]5SfCNB` + GqDne]%),s!Pt[>e_`3Ennu$c15LMid^S@(IQUBh(MXRF^745eFfA^6?XD?s(0o-IBRYFpK + b7Rh"SM>IGjlA++N;F?8lI"q-4`'ZN[%A@LiRW#Fk"[U`g:>q#90-mWflRCY3dE+3!aaV+G + =cHGgs:8+YNfiQgu.T-)*mr(dq%s'O+Jr-uVX`'=A'!2(J&J8c?I5_p-Ui+*-9j.6?=#Tj1 + WU_A@,8dn!f,WUWns+?DZ%`.p/#[nu$KYs$t&,9!/ThHtjd'acXaCI:S9Jdj\g)LU4D[qOJ + 4GV;MIuZ86&7QVGLli`'7$rlFa8"YMEB5Gbpn\[6B^(DZ>]@YqZ9R@$i>iMN0?*.k(ul1cM + t*0a`e`._j#H4]0t;0liP-A-T`:$lg,b5`boNS7H6;0n4idp/+ClaMOO=DPN`#g#A6b"SP! + 2B-)&b`Zi8g&a>"Zn^2'ukpQ5a=mJ'%LR-m]S(P`hPdNN#2nV+dp0;PmNTWFIL"pt6dQg02 + [FFY77d\1oH2!oSM&Ch]MK7;?H_#RTZ.b#(qLDk + W*,09Amid/)B`T"iLZd`P)L;?1hr"=*M\jHfUDD^CoJSpUrN\(*+OeG:1Z";B0qt+,G91YU + "DEhK^Vj.E-G)dkblJ(T:XNoI*]Jp5:>Cge,Dt!2dHtd@ose>se%Xp)bU(K0G,bF*[[)l1/"ReC(rl+OQa + [UNo6p)XrP+]urSSXnJ + ?T4[^t9G+?q_;dn6QF;Q`\"^$]t$d,DL]OmJ=Rfm/14;o#'+(&3./Al`h\q`41jA4u^04hX + 2tC(qUPG=-NdURb\0Ucl7g=1+7.CJHB4U$B>_D4RnG#`Rpq2@XOGu?T[MmX*+,"lo=o^hAL + dP?S"G.0`BoT!R&hJ"i0KDQseKVK)(iDk'0T+o3!=]2*@7Lk9Pi.g?8]b$2&p,GSWp[:GGaZ>N?O*FUAhUo)_KU/=P-FXs;C]p;ju=3KiPYbg]uQl@#t9PK;MAjj\-P:gI08 + YeoNSC!^XR$>jPYI;PPg=t)"D,)+j3G0,g.3"k#cH4["9%&P>+Q,AKWlN?BOcH;b>70_52O,iR7R*aGu + <-VPddc*D1L"q-?"Y[0)dq,N[.qa#0gG+T>?OSPs9O;lU*P#)jUTK@%kGkbptQeaA])&P#P + ah*/,FA7\qAdQ-UC_lQo8l!3PfG-;3:YU(4d?RttQYZ:tDfP:e&PBD`L[P#RtV"$Fd:'hYX:^,]=H:F0kjV6a5]m:?$NI^?0t_-3mGi + iSKT^'dBP\\=Zt/k_s^4_<2O2dZdd*$9gHVm?$1ceEf=EUNe?^%Yk1hL#9Vn9ub#ptlVBr* + PTWr`@A?5;kP87p0rD[*J+4Ts7MS:)Oe(,Vi+91ZL + [Y0kR3k.,p7@2Jt?0:-08P3&p2&j@WZ;b$sGSCuk"`jcP_(6al+HR]%]b4u&eonYSOrEuM3 + *)\.1o0t-95L8tfF)tCf00ug9T3(3;^l7p^VNi*n8Wi^0AlU6@H,_W=\],o,cljh2)EVCPM + c:h!_Yg2,K:>G7f)"($>mY(N+lRsEAp9QCRZV*@KCHh6k&@??h,7@E?$WurB@^`Hl,R\M]1 + .J`AP.._lo6@)YLb@ed4M + QT!K[sI;WBBg<5oR=rdY[`9PG/JAB+pi'Oa`P'#Z>o(]f.*)^2j7Ep!J0))900rGWh[1;s< + G*s4'r/JBoBO?k[Y!mH`r1[n/a;aiO!%0au32%Ws:0P:S'>)"Tu#1.EL:iOa?#m[eR#LJ.O + :kjkp)N1h^$$1^HOIlj+SI/(Ne.AN<8Gro*;%%H8OV#8g:q\oO`7B"3%*`]P9l/EuJNM67% + P[:!4E?##V:Kk"<^PU$3]8s"Vd1S'Zpt@nPnI(3oRG01JqO:XsE"%.n%9YUe3uj1fk + Kr2WMqZj=Wql:.hC'NCM2t7g$dLc=7b"L?=Vsk0^?2/pU!M\ED.c)pX-W/r^iJb3=#=*9tJ + DZ.,lfh+GV,*`THeMWX5b^E2g[m$,AjZ2Ch(!?^a_+H$sW`9AETlQL$m+fd0_Z6LOs,.#6@ + n?3?Hmh[3!A/,2,@4P>?-qq?n0!cX''rP5*7k[d9!`Jq#k?%q]C^2hJ%04rh260n7;^h@8QZZk/h7r!0W[$fK#7e + 0a)#77,-,i5u]J(=8fO(,SMsK1SK$CIK^G7<*Mfc8O_tAJe9u7[.`N,I87eEok]@7t + ]>1.Yt%nW2WaM8>HJ0:4GhBWuMh@WmSp[j?ZBjSK1J!*NIF+pGX.;3_0V=8@6'P<5BY5`?. + _Q9o45XcduEeWEe'oHr[@$hBbh:XNHH-PLPOJ@1F?[>?]'5@4+j?[-'f:fTA1qhER$?uoJ+:h_g9F( + #o8@;p]Sm1G/6@Si*5Q7C=(M.7*idrm[-:p/;eZ\m5)OL@_kF/M(O]%0n`APgccXa( + B)_.Hfq%J.bU'@CGeToIOUBD-]t[Y!6lLHa2YNrj).l+]HKP7`f'&_$k2G+h0eV*NND5ku& + 6!&7RV-5-DqCQp;%f"j^12f+^PB+,eR1\+EZ/M+2ZV*5C*k+W\b4QjC9 + ^L'PF`blN\[N]j@b&uL'[I&$UD313btTp8#Xl + ]!YsAi\lsTp=/B)!L%%`i/t((N7#5_M)0XeZ(\>a!uYmX[A3gNEM)YPuge.dI5(]c#u+]oA/?D\)M,IA(0bAfc"9%%%T[@WqaT7^FUpBUNX`!FiSYEDX>:M`ulP7^k2gX`)57,f!4^)93Pl:#SC23c\V(`)M(G4V!hNu<Y + uK1J"DQXgJjKJjp'<#,MN\3YKQioA7_]^E=!;I5d!YD&+'&bef + +^c)%f9iq__'HtSjk3?AmBF4m6[Y.UpjW9dS]Rb(KZ<6LaV_jcuu9m:&B+]&o)e.Q1@) + 9:AG+RBU>8e#1^'#;^#!?MXl`Xs3%&HL)c+Wq5Eh@h$o%`(OE5mS7B-kbL5'd6TuZ6=FQ%< + oA7:Rq"GOu7KI4*]A@:q>m;RI,:J;4h,)T=:_jodNem#:?i;T\$q+H:AVM:*2bK&j`r;@0R + R!l4/AM%04Ze@)`U@-4@W#&V.4r+Uur[(^tr7&tmeb5s>c$iYr)") + W4gZWHB,pG+5P9_V_BEpCe@,^S,b_@/3j=-jh-J^l>)F#?P+]-TT + RhEgWc1n:hBmoe[td.#@(O%,qSB7XK/X^l@CVGV>1JIuer4q2XgU)IHKAYjm>CU'Y9%/8=M + .T6F;b/;%Y4aA!.k8;-3CCThd?Wl5Ze4U`!jHD'`hPVWGE[24_B/7f\8hWgIR^d9g*aX,GB + ,p!P>UF&dVm&$32sdLT(qp7Ke6@$Quqg@4DpVAh"tu\Tmq\d1br3s(QYGO?9SU\IFYTZ]P9 + )(SSUUU<#-Aot4-lP*@hertiA:2nca`D/)#D:+^>QF`)J!DRi]\;2:FDCVR=R^_ZebQN=ls + AK"]i_""NtENhf=p(Lk")1^KP5p?RWL^LpS"(4Ru?jHb?J,pc,!ME[$i-Gbr\dnfO&S4oX@ + H-O6dDgAbR;T!lWnDI9iL1IqRWh?E)66GVYjHqS9H9,7iCjtPc4C!^(1=ib+USIiIL>]i"e + ZJ/+;sZXc2KsF"X$bL@$h8Uapp`.n.)L%1$0)TBeIA3ZATd6WUH^qT:<*MoK7hFej + =KZ!"j&;Fqjo;(%L-(/Oa:\m$hU9P(6sMuIQQS#b*RMT2mt;_SO[TYSQ'Noh4FFde'Ds2uMHT.eI\7c-AU)LVQRcl-0h9RiL#f2L5m?"[Nr)C + ET+flipYCYX+3RA&I2Vtm!%GQ&,(i?./qAngdSn-T5I`c8)0q>QGJB)8*mWdgGNa0k9&T's + C*Hf#/0nbg-#R0qnh/;;Hj(dg8d + 0a:b[L="p!_jG]ZiOHcC6iJ2r."X.m9L=R*)C._UKb<g]R>Y3KjHf7_9T!fEJ<7`('D!eJ,#_Tou]3f#]RVmi1;SIt8.T"ROTg*:4')h + n/O,RPZ?2Tl;Z>YRU^79rY7t1^jXe'IR*@A&9'G[UD)Lgb?'T_Th*M2l?D+7UX[d6*rV-+; + t>IIL)ak\-B=f">beBq/a'bdGm/Pl1)*ZFRHUY3sSgS?+)u&p1KRM,?GaMc"XZK-4e< + ?pk;sHr6k8O:VI9=>GVN%P7k*nW&aG4DQN[+#UESO0TNea;oOM2>!!_&jKer?&9ifk/W[4- + ;MZS7C-PNW7hleDH/bS`)A$itlL%3_B>$/m + p,.o.7h*/:fj5!d:N(FA/_T9XG7=iu#./*5R,QubGuc$>X6k+(>"F.R[&3VPVur:ck+5)%C + 0X_Z\a^=q0b8Si1O:::LIf%6r#?okbs8/@B9fIKO+lt9\kh1ijMq__oc=C)C:RhRA*`Rkl+ + >4*DN,B\nDBrsi1[=(BpFSg")qtGM=2WiiHgO!l,(!*nV-KFld[rRercagbZl=g1dhk]<"QEP75U/L1lN))U*?<^9;5B$U,u*TE%D(C3WM\RJ7IoY + n-/l##D5i2KR-)mj&r.h>.)7P[F?Fs1LDD+@&/up[PMDO;e.dk.4#r`V+`DJLe.`JA--]tK + 4"ucQjl@"0p)/A!=0JE#XO0]AgWF\-B`,QP&*(XH8i0Y6aM.He3&T2J%,nB6h6DM196*G;$ + WI;Js\_N&-YH!7?"ImJ05g4\1,!6TZ!Wt`3Z4,]Lp,\9uPOV`6;#qjA88::rO\W`ACF\6rL + ut;oM^:`FN&h>Zese=Xq=b2)D'k"/\N1]fY8P\s-QaZar30]#H"(]%gf-MmgW'coZ5*'6;= + uO;Y-g`!2<=LFWH\3s5^p=9[H_7RO^U4CL_o@P'T2]>-b8A%fkA^W"]Ga((s3-m=0m_5T+g + Kn*OC>^X1NiOipea7AH,KRg4GlTDf%ac>D9+2;k@5D6)/+D6G=[0;A#2Y^=,TPLj/Z(AQXT)1 + +Sq_-,l!D?YuZQoWGH24dG\\LnMZ[4]*_?7-F3jZC%^nX#b[(r?05JY`5_!DT=4N*pE&N!b + 4TnF&p4NO5?'XYdo1qB]A$WI,-TgKY1!B:r"\5!6;4>j]6bqi\C6X@Q,6c*BbUj>X"Hmupk + fsY?f9sRbF83)=$:Ope$JLo)A!6bJb*.iuF"ZUZa@s:[EN2EsB6``B!RLV=`NrI]`-4YoGK3\f`TsM(bm;NXre4@L-YCqp!FH8uZO1qoI4f"7Eqtl-3&:kB+:E#EO + -jPG3&o_C@dp7;U4R-J%3b9n_2$"=c + $%SBPKJ5dtr8kD@k(sL,f4UUR_VHg'hac/7;Cm=#A:[W>ku)&DgN1S:ICR^b\]A&*]]CWkb_ + ]QRM\u+>g[j%P"Yd-F0iJ`_;^u/'3rQ8W=9Z3H!14)n[=p%X"b9\Z"c-E0J9s^s'^HmHi&%&`2Th+fl>X*6?@"(EusPKWnf@7EF:9 + 58cF7V"f>g[+)rR7,OhY_K=D<,hbS\.+]DM[Ye&920<21n[P5Q"0b_UPBaR-Hd77j\'9;u' + n-JFIlIW5c4,=9W)X!lIgmMWi3e9b23Z]FP[PUGi>**c-59lH']EX=iH>rEL)/a.(ZCYuiM + IRQSfFdgPqYP6dFFRejrc^%Qsq=J:lVJE%B3NjS)RGI=ksfD*=jSV&-9Kn/Kd0.kX)O>&qM + _N#;C<+jsFRN^LBYfO_q-Q5X<@Z+^3\AgBFe7mPgFN_-7p&gG*G5mQB60[21-tdTs=[L-XL + XKc!++;P7OJ`P&HCD)Dk3@/?%:fF@o`%L6"YYg(It + i)#+e?OY_+jSE4Lcj'`A>mAR)e7D*TN2a)ph`ksM?*p`i^0jRg0OI`-gis5388/=Z:eO8ie + K*Wk7H8!E\Q)9[5ZV[9I9%HokJ2rZ8kt[T0Asd:UR-CK9l*d!o`gZ/;S*ALq`HE: + S:+QN33+;eEl8Bce]pRe3V.)TUl?4N7*iNORW8`u'ihiZ/pi;._$NR[F0gJ6^OA9PI+QG)P + X`Xm/41hUi2$A@dC.:2KL8&1D\7;;N>9h'rbp-`[,[4ihj7CFS'tRor-^[/=CbA'/ZBGH=c + X]_,;VT^Ur@^V)M,C?S+(o+]fe(2A;5%+g'5qNn. + pS")]?G>jh99qG21T%?=if`jp6J(rC]uf*k(i*K@icXA`76T;DRr[hEG[\T1f#?]=]\DSiA9Q&TL.Z9s6"HS2X(hoq3Nn4j)1e_,n@@]--jY?J#QU + #BJ3<0$0L^Zdk%-miU1eU_i!m2\7oPSU7-lW>6>a9bjj%,\No&mki8tg59%Yd!%[&\ + '#lp%p"peapL1ag6BBG1PDK5D97d0!Q]C)e%k2a6_NpUHhmU!'be^!["p@0t'o#t_mFC[Y7oBA(NU:L-PX%[e#g$>GjMu2`d=%4(KdH/C7I7Aju=Za + O9NBm'f<15NKM496<.+LH/B<$I&SG&:l5]!pDIoGY/,n=bXq1*WUJ?cuFj@nGa>FIjt#EOE + Q=c=b]a9KE+ODp`P/f7lZdSJ?WShR!;QW]puQ'bAa5aHC4EEk?QXj[60DsQJT:F6jZl1hdInKaKl5A1tW(FAVO'ZFYM>8NV\FCK`T+jcha/:^8*#)3/ + 7KI_s*d-<_-KTR`r'r?KD>8J[bR3tS3H#"bLBDec5QP-[3/5&M/7T1;&&IMda"5$KcLEI#U + *)25K4qE@$BjQ1Q-(4cocL\F54jR'3?s+TCr<#;]<%SDDTB1?.7+#Rk@78N8EW/=f"sBKbN + c_rj9F9514\aE+fJXDfWM!c>#srAP + rJ:f_.1bA_C+$P@rR7G[;2BeLYYBJKSoT*K("Zc:nnhBrFuFkM;nSg"/ZdFP+Or3->dC)2L + !U,`mBq$_YQr'&(DL*X@;k?'aUjAn@=kH&cU.k1S$qS`^mQFe7e7re6tXRLY7Lkh8doVbI\j".RYIrDlM@ZgXm90YZ`i\a#/.8-Ri]hZD&Y+VZgCt7fpJ!M!TAL/K@'N`SB#@D] + ^L3uq7lCR%f=*3*%(p(*9V>A`:84V)9;:&NuQ43*2b+*o,Nn?`H,\l44!Mh%mA<^*?S#(oc + 5DNGOsX!dFWO^De7PoI?/*sp>*ji"Xb7`&/b[\^N&!&Sf_Wg4#ms2n]J%OIr>[cHo:P1pg%PCEGN&As65YS+r!4`P=I81oej&'a!.XJ + B!^J^S!/L@0*De'd^o/+Qi,fQ^YYGHhZ,Hm>_#K#UC(.q``&_;U+G90:IuY@3$)&mtKE1?[ + KK&Hf"`Ujdj+)4^n\@0DZoEg#r,9K/HOh:l6&6*]DiSLV&Y^]R)poQM`2K(;icJ%SYtcME! + $VR)GQ8D/KN`mk$3=-T!ru^?.Kse0.n,iI)e4hV`jjbB#1RUP6i:d2.-VbIXET_&jD9hiE\ + g3N?jpJ^!tuNW9Z$^[)WQ?tNH)]XA9_l!_>1YB.4c)5=B6+;S6udl\j(qJhBVTMO%N;88;R + WPPqoLA&gGmg_a&#KX'c+PEYK7?aCltJ/>SMEI4n3D:FW,BWm"A>;Ub)-c`"MoA,;7E+ArPt]c&$`L]N>%-npa5]L#U)%+J67L60h((Nh/?<7 + FQ>d6]77/$/]HBBXU-sm:?//;N.qS9$Sk)4?pf + (YI#(&d+UQUBl,?S$LqXc#2r$%"^qA=$QN1$95H*FXgkh5e5\&8HJ7)WIh1Q8`=Pj.#D$Q* + %>'C7$;p86APCJ5ssjuU#J14M@icUB[OCO8,ePknQIJ1necqA + C\A(Qlu,A\4WNL@FH-sn;FeDn+%Q=/ePZ3C$UJqm_'gZ7Q7:Yj_3d("N/k7Hct>fj#cU,*3 + XB3A>n(L77qEIVPgYYRCq3[uV/FZ6>'`*i?;i1-;G_Tm3;3bMlaiD[80cO&dPbngt`gdo + p5C%c=Tq5g59_LYNu#&NKn+ea!%L`FPZaf(?N3T?Q0%Bp`Q5`YI:!5'/$i@ZKI5f";ts.*& + F9hK^0-DhKgQ?4j/0aI_Kkrg0^&+K-3"W.dP!*7&>YW)qXW4F&#&u&?OND^>^@79cMZ./t= + <9f`L7X_BT$\pO!2LR,Td2)kB^2^Jm^2`2h-#+$;=IT&\g;[m?$ne]YI3N-fBW7n7 + C%jo+cnJckBI*;fe='`BPpruqd`XrLpI#:5Zs^RBC+oF[.]^7HG#]F/AQb6%a:k"btQLj0Sk;HfZioH#q.?X:mfP0k0D3@$7JN$XS.Q'r + .]%qLlephD%MOU)%5gJ$n.ju:rq&o.-6#?LTd1>35)o'ie1ft5D+tM=pskJqeM-RE@d + "j5A-RR-YsA!ni?TXVqQ$8u7gYh"+Uf#-XP#^D4K<3T+^Oc)Ic,K6JrcUlj&&g#n% + FVJD^uu`0^jUgs+:_BoLm<>ht7$)8WBri>rW*@grJP!$YSS!DjNIU>Mrei":Y[.E^SJdbr" + ]iA$RfWSX463hD2ribg=,Z)F;?0]Pb*j/ND)@rUL$'-#tZjK;8u@ufY'GSoE9gAl?#5S*r= + ((NUr'#B/;&^MN$HKYie+"C7ROne-MmcdUL(iGN[:7;o+?lZsa"gJ-,6imk$]'Kp>6]T$_D + TsB.b,Q^fW_V!Bni6$^i2]0QOJ!3m+I!;.1)#5;(@L'+9%>5*q^(OX-4^,(D[*@:hFd5DnE + TF7Y8&Fg)Y+73nf_];+_L9g655!p"i+I@WcSMSL^Yq!oF!5BP,Be59Mquj.kb/8%pLQ8rY; + ,3cTlZ]BO8*I?FDM%pCbo`:NX(lp/o-f'-YSp#dRgq/.k/\):/:k_'BX:Z3`d\%I\rN:TlA + =r@l[o0_6#61Dqn\*AMUW0s`#YF!WTJ,;Sj>1D;>r1H@744Yp#&1UBG)o0kLB87E,3VbdT8 + :i?QBE..4c!Dm>cB*SSrD$>!!"cPqRZZuW83s//sGVa[K$998D%Lcah%F]AYF)_A&>fm;l2 + DAc$Z`,hRXT5SIM_Y_Ha:S\V]a1sZ#n5Vr:lu'cjU'/s$;C%SE20\>7P1GMP\Ce>[P*3-*ia,j.;T)/1^!8D'_,/ + XXjotI?JX":.rf)AoTh*Qq?*BQ"I'$9?sj1(GRra='>\u3U"nQ_]J"..i:OtGd?`a$+W7%0 + ):>9!0rl44X\J>kj-8XEN]`u>]h[i/*)uPjY#IGJrXGOY&^(jj!&=Z85SToi*c?6ENdDi8K + -+)8+3odBDO48,"!D_?l(gQd5^Wh;VOC,%'EL`D1*[tMP3brc<@XU-&if93?R*u]X!OtNTX + "r5p_@4D*OT<%Fa5;kI.,50->M9^NsSpoE:F[)99OV)Nu_B/Kj!C7.(,f?+_2@^q?^Ps%;D + W$^^(T>3#g;?Z>uo9CF^n#2_.C.'ech%M7S + /),j:=Tfa!^edLEM&_71DdmmiT).E4T@Q#693FhbGA2ZM[G!TZ) + 7:`872(CH#'3RBfX#8*]2NstL;e\7UFZHmKB/XRHYWFfLo)k2(a[[Tl!GWHCUl4.>).B7op + 2O_gRW:mm'Hp0R.'j*nBk>l#Aen^$iQ5kjhLceSXfRDC + kBBa7DJ.jU5o/$a@T7G4pe,qeu*+%*5VE@L.7p//I2l)UoI\Ltt@Yj[&V`O&RVrE;+CHrYl + .AEtZ2&JXE?lUm_Cq?f3+RnRW=i;<$h%Z&E(Pt_GB1[<0(1@[bJPU6:a8h%/#9\0qi8bC,*YgtM)PZjGai<8J]hEF+S[ZPr + FO9!oT1t=oied=Ho\iPej%bs!j:4\/EPOPO9i#cL*5PMf_7BXI&-5@u"9:al6!tJEd8=!Wk + B.^+EUeUf,>sdQ+-HWgOpQ`J1E];#6t(TfEBjj[U^05?$WTftb*c6B/QGeQWg-bfeTN%mrX + _A''gZ(FQs1f`=bcKQ=7Td%22>:SCPUuPn>*$2=GZKlNENQ:-odJq=ICS)S0-[Z$,GDd5b8 + ;_k:/@2M^8s?cVoau+'ior?*_@RcXAB*?!ahe?GZXnZP3X0F^>;2[L9n(H,:-E_*Sk;JOLAMfi2/.E@Z0rAD-]_Fr+M/H5E9S@ZlRa$p` + #I'SMiTJJGFoG]\u^2ij`CNMH-4&)_f,6kG6k-jTg+$38Wdi=6KO1)E6$(h)/:(W/r#D&H= + #I,d:_GL]F507BU$II9KQ8LcOtTg1bCqS!Y9Eq#Y2?FOpTLuOLA5tsD + Z>L"M^HB=-T.(W243op+,NmNM0PYL[AG&)A/9ampV;rM+'R8$6Fmk<4nGPU'!l.-M(]4;m/!:bh\3#8OGoaE,Kt,lo\GMQ%4.3VuS[j + *>\\"QHH_nBFdEst]JEiW43;7,&XD$:PPI;)B9i`LK=qL>6(srG5Qe- + =!WYu;)RaLq\M][,?@7+9^c?7uS%ZV-#_;`"L#H%!3JRlCO$$YiK@uE@QJ4P?T>L<\RTq_= + 0WCM)h`a'(Dcs\jRCeto9q1bUS<2)/@1X(idrEVuS_#*X2^ubK;4a+-T!p>7OldXLOedL-% + `%]KRK7N[=*5DhF^11FI$o-LD5"FMar2*6JXRV;(]f[OL#>]0+CQ!G/U0CoUKTO#3^KOjQU + 'N>-Tn`V#CHW(3WriJ#lq+hi><4O4;\1S*`U=\%N?;5Tm\BWI;/942qm;%AZ1a'W8//]R[& + @@QoXl9)G)I[8@'>0()-dC)Od^(EUA=B++_i2X0MMtR_PqK02i]DeMo6Ka1<4.KE3L,$"02 + 1dNaFVl)r1"KebSeeQoB7IBYd8Y`AZa>7ZN)Nptd>-^C*V24_$W/$bP0(:Bpk5t2Q>ccYZb + L#De9>?Qn89srEeN4%])pslgBD;F[1\Y8PqVn`N;E!i):]05iE4o)]1q + qli#i'%2"IK/6>"lS5Di;OJM4rN<#*T:U!]4hc>i=E`7HfEep^jF&\?c/\ddj?)>6?: + ]*`fQZ*+Pho%:+s3fKCm+jlELZddB@FWETFq)*j(IQ8*"CosJ*5ej_(^/NQHbf*e%tY%krG + *Ifq%BHs`Fd*UN]bgf")9O,!7fO)Ci3!GQdYfS/,6?nMr5#-7 + Oa+OW.m1rFI*JG&&eX)'+Ih]AMD`h,"9g)L$c(s:+Xb_>PW46biRc=K=4'`j"jil])\hJ6F + Qob#O+//j:SXj1ofX'AYTu4Zf]M53Q)lqDBhgQmpP`:";Q/(Si)K?a;u5dLBV'M^ + !*@_.FJakosDOK`BRI(ff1T=g/hEr]P`W^Zei>$.tN"Yn%)lpkpMBQ66_2U>cqF)ls3usUI2&_^,ghouheSk<'C9q-J<`MbirJ&@%u%"lXk%:\HQ@:Q@C+^*G8j9-hflWfDti$-mgjufiGj,_ITIJVal`0ih&_>IT@ + N1K\t+<%B=Z[`G5M6q#EQjcnG3!RO#Nuilh>_>)"e5eiI(N/cR6XmOk0R35L8T8G!F=n"Tg + sY1!9U68,O506#_Jr#f6"p.geW4S-F$TVu/I`_iXLX&4CL89+aS>,Yo.D0k=,t7<*=mG9?@ + >Bc4pq`,?YcdH#?i`aDUW"hsOO2Al^-]R;(rEa4)A)!5PB!m5O7WW3fgd?,8=EGW0%bHTqn + ;jMI2MEh`+Hnc!r1L)F4:%0^o"@+c=!BWO;_tkO4U0PjT'h*CsELfVu#=?dm'bNkVoM$2ld + BnT=FO?>@`CeQk%5CR!oZ]Bi*>IU#E7@l66W6rsq8#m-o4p1[W.r61&c + #ppKc$;g + +WP!`@38QK,S%.<@O5r;J7VWltr_Jd?)-0t-A`rleP\+'YAo6Zj\Ykt\H,9JoB)2FSS!8V( + O,5N#aZ`uK#0HnBBu\/dW>Z\8WMpct\c8aH>0aU\CV'WHB,]M?ag(3E7aJJYL2P\M%`?sGU + [EK+kQ:h#3*I,R,4NN5&qnYY@@:cEOCt3`2\Est4@tUOn]1YPA!u,5.EV.n_2ZTY8,8*Uo= + Y)dcua[JMImt/2c8&s4_IjFR"ecDfQLP]BA7UC_>[l2?.XAXTB/XLDPLSLlAl,mR"A_6#QL + .Qq4;SXA,3j,J7B]#\$>8r53KED+6EMjSu*7C5=9A,KNK#`fRl5m.t.1 + +,J!#XHHF;'=WCq&/geuqRM + 4?C:C4j,-rKW2a5W+W8m+e9`BjZhZDppIm#*BW1p*'1aEp[\L_/T!6]nV8*5uHOPD?kIT,B + d?AH\clns3-FBh9c8<2l.GU/sst?Ro]@Z^+M?=Nbu(/g[m8Q]gcD9DhXX(Ke)!`>T/4t=e<[,ppPn + KiD24Csg;V,,gG(1+NU(mb+l!5gJX"O)3]^!Z4?jld\e`srcfJ<\>Fd7Y]Fr3*h5gLAOe[p + ?XSk&o*7g^ef].rG:?f++tK)(P<-@37f6k + >f2oiEP:lPS8HksP[@g>;E3sMB*'8 + 1U2(.+NGL%*SJL(N5Q6^Vj(YnTUQR)!@%gCj%1'lR'[MTcXUeK9058J&kcn,"hcnOchi38K"+0`!Cl1'3%& + KEFgp^"8:F9[BE=r0HfI0lW)2MohM01T]X + +eqg!e$_LCCX&;"74-l\S(`%):21fJFU"V2kYcusH4*"r([B=fWCTjoHSM"]l:2. + g=5'T\>50:l5uF6jSL?WOkr''^)?:1l[8st6>9!"-/^5at7JPK7laj4fHF:UkKjE + Z&ItlN<+]td$%BU$k[1E6i`PJc<#WbqF$?9<+/%p;jN/a#W$N\BfKnF!G+.$)F%:Yb>5-]` + 5Rb5E;''(m)$/nQ3fkWCjD"D*!G&krOHUlL95>)m&X/=U5rZd)`YXflmO*,d.Pe\t;#U)T, + M/dXtL\8WrG8\W[LIKp7A#4>"8%.5$?LD^GEd'HRh`Jl[5/<6n!u"b="Zslur"\J02n-[fq + qHK\7*\&>D + @Qd)MQPU%cRm]LiFfSLfgDc@]5WO@X]tf36Ea]33sF8<]Q+@miMDt-6^;u_6P6":unS:Otp + QjajK1>jY+Fm$3jHYT9h*#I'o;j;.]]oK1QUOa-gJJBak,b8,iPWrcF4tru^_05O8A7j8hL + C!Pf*F5Wf-69EN"7"@-K$5\p]kHjN&[$:'OW^mkc?P=dEg4:fnR[TcHl$B$f)&iCn@5s,fH + 8oqE#"oM(9g.`-SeX6]AsFDhn*BK.o?nW"B + %Q'Y,0H/(@@_,F#8+Pq2Hl/.nI6KN+BC<]QY=3M]bGHNAe'C4UUHJ^LMeb01d1U;cGm)U"s + H$IPt4@N/gT3cJgc/7aHj>DD2ZJ^^\iMV/!f%Y5i*KV2`X8:3KBbFIX3/lLiP$b/9?EZIf` + Y-Y\[luar-ItUWg"ff(cW(dIH^n-GU8%SFbbJUNY`Q-s14F6tI$d#$ZJhRSVYOGj?jFjf=- + `?6&KJ5ffgXF65#X)W.]lOQES;f[q%'\_/4h;V)$P0K]hT75kU.ge8$PtuGihDn[U:V>X>8 + siq0X:8>5(KI5J:%uu\/m=P!f/m$_^()%4/9FUau_#d'VrJong#?UZH0(%go1Y`0>I9LKK* + NiW$d1A4$^H^5g0X0%'RhgYY,tB9.[GC,dg9-[ED7P9743AN4-p"\4`X.9<>d!]Y-t.%BYH + aKKLXs4M_^g-;5s:VX%$p8NB*G'g`VT9PHE$U__1B+Xa!O[=fIBR2?.B5IRa1p`s]9q*-'B + \[`:NQ40,uadd;c-m=Kj;K1]m/n.1(4HETC&4=e?JN-ub[IaY:-9tJuC[m75ZcHM_LBS8A!SHjZ(?[3_:"?+k,:&'AlaCi'%l?4sZILKU=Kj6:mE05 + &?`g8;Vm'&HRIcJVY.p4J=sgU5>tH14/IS%&Ka.VD>GD!TT`D47oi<7\k^;^dq'#RGjsQ!W + aRZbI(GPI9B5!=k/L8ZKlkqa4B=r&T#UtGT%sBbe[]p1qL2J(_Qj(9*aQs=*Y(r8uKNpC!CL-f=]b + fbeY^MCdQDCsLJ, + XlsW?4crMCnQ@?I7!"5g98UTVN'sK8=a3mLEMG+Ie#:4%##e]>6Wr3J04mn1'<)H(P2NB]hLCJ>Lq8Uf>uc.]pt-,S(uhg*n/@ + t4oCPL<0gJS(s.-c#[Ke]@i`[n77k"GJ]Ji5RKJ)-$%V,/UQb)f781j(9eqs1eF9?c!c!@$ + 2Pa>N1@t3)-r^@0I2JUX-E&s-Qr_d$p/Y2eej]@@IJI5mqHo6YhjsXOc2:r)qqpf>?hOD*r + W-C8[T6k8_!Jjs%A,:#Eu@"&;gNWnC3.9GM%Sdg1Xm=A^%K&Q$7fK:s!%>EG7LLMik=F<%d@VV:;3`r + aO@XRGVSW1q6Naf*Uf^_C[fF@o[Ogmc4`*Dim!j9j"M,-6)=_9KdDDbRBfEXaQJ7o/fJXm& + 5S@KpE=lGW73l4/mc + "g:c%:1W2)L)>&c-o?SJ]n!N!AN?N9#V:Sc3C_)<;9^*.jh"a@FPG?[a(6G"peh8dCFb36K + ]lr:XF*p".HW34>_C&;!rQ;kptVu)gr7NCc->C4It99eJXQ[kA'`soe'i0a]m\J\Q.J1$3l + qQZ4k89L9Aeo9,\LsYh;-r;(#RLk*8V*mhBO-!-s6_HItBp2"$-PA0NSF.IS(:@e@Y0USP@ + 7,ldY7l?IDL'jmPg2p:uIn!72CVW!EBR$j>K_LIM>Q#*E:7Om*@=Bg._p:6OjJP\F`pBl9; + PI[OnnRV@eNl'TKK14N\$)I\p8C&GeTG+i;s`F+U"iD'bq:8B7$%4"+IC0h7r(eZB8%"QD. + L?NX_]l.X]Y5'1.WB#+k-$I6tpm9t47.r829qJno6I=@TdZ-FmST,AWuZ("lpQ;G6"]- + Xr+N:6m?q$pQ58F5k%=JhSGOQ7g*=P@Fs(ffbU124!TuN8n1;=anQ\\X`uaMB46?JZ'N=r] + 8".42iPqA(@5^j-[f&!b;2'A/Kg`L=e&'C5$n:#/-sGIRb=58D9ff>.q#W71SDH9l;TC7S% + *FRE1,-I0;f)Kif=Gi$;S.!bFje&g.ni?lK8g>)*SigW!Ad2HoPflK,^odL^n\TKE@oC:s$ + @L%o-_mU7bK``u^AgjWbq9eC4BBt#L2D^b1+.<;UAEka'ocRX%f/99W$Ep + k\=At:_34bPkYCF5Zp:'Ko((is;l@$[SG^H$%+2JR)!P")BkThpNk%tO]mX,&rm#Y"?6UI% + Wd"U$$MLd-P08+T5@C'Zf"\ig)*2(+L+dF6k8X6i*]+3&ZDF5\sa[lupD/-'V1p1XBCMm)I4Y=DJc*AV^7E9aX7> + fIm>E3peS9=nSC`%b5E.emo#(7>BJ6DfCL,&-39"UqN.MG2:lQ"1[+A]cE\KSbD[:a + J?Q_VD2hQ?R1oLS.=9sOE1PFC[Kg:*-s"8ZU]mK3%qtIgOs1s'n_L)U+Hlp9(0>r6F`I)E4 + EuI^07nCMC2/=#OI'+=XG+r_!3-6LAo?D2pZ/]SK3U/k&"P:,_GR1q6CPM>'F>l,&Da\#K" + 8#qir4bbG(WlX/7J*3ESi.R1hr/\Gi^62G:.Dho:*]Ed?BsfJF\R]3Y>til=#[TkI^s7e\* + [u_5U8Je"W#*f(\)7O8_*7:OY5$D!4'CVSnlkj3ae+c`mtchTDl:FVT%jjL((2GZ) + q[:ZagCJnY'SA3a@WVlUD6_R@j/@M&IO\2Ftl."F>rKP`[Yo/UUWblue0ce5biP69=#E.8O + epBi_eSDJnJ\`&sQ(5g*5OOkRXkr_r]!3,q>Q+F"K>Cr_Co@7S'8U)&EB*<&4oQiDNGY)(\ + S1-AA3\]tI.i1Vs'RRgRVa[L?O6b7\f^OQ%V^/$NGI/ikd(V]?ikMPi[Xr!^;^YA;UrI9 + :X]DhhsBDHHH^p#'n"J=e\FToUXZ#^)c0SC_ZQEZ<#J!MC[oM$!RaT+UBUG1%c$6kl"+paq + <()&OO=3U85r`9j+Cu70H':e/E'r>pM43o_$ofg]c^iUS(o"*=/N + MRLn + )=d*Yf]8;V+npH!Jt/h0i\`(*.m6fN'kdA(amWR:KIO?(Y`_(7h::g,?XDCg,E7\S^*Pe\Q + .Y2Z:U8gm:1EFPPJJd<49sD;fYE=+07VhNtqCI,;?gnU=hHmK%gq=7urNi4'g;K]*bS%u0% + FFr?(p=H4hd@b@8*MW5Q123-]Vr]-'Mfa^T9e.ntA)oS\C>,8_Wp:S[ZuR1UB"tNc)]lu(loRDQZ + u"JlfsU0:0"qJiEjerQL*;q&\0(V!q608!hUTF/]GB(k6]Lbp78[.g\5!Q_;9CVs*N[JrXX + kB*<$2'E-XWMrbqT2GFRdl?B;)L)s9/Lm!AZQrbm-)pUfnk!RQ'7p#rAW=W$;cb('A^GMDl + k?YR#j+9/P5qgT^Fp&=tErVR,0`]rAq"FtG,"@.29"9=5kI,n+$$A!L]LCjJ),DHRlLbAs? + j;W`uC(LpGYm)?k,(tT#&OM&R$DCuZ+6-ZA6O7$-7f4ooUL"nWQu*>QYhgJkp)h39J-Q)<+ + bA]J7gUnL2+Hg0)M<&X70oM"@\cq[d7Per33sC@psU1%Ft^hu_&22DdYmX_Jjsbb6"=G@NP + mN)`tB+,a7q:l+Qao2+ZLm)!@&j%r]_%D@3D?h0.)i%R?M'K,)PB?.)cajaVc3rMKqmYdT0 + $UP,;39TZS0!8l*Rgn3080UDVt.3f*9g'#NQL"\fm&k&gqoE + rKOH2FL,"E:a@qdCB?/t?!>LR2j/00.NR1iF5A8jFV6j9kakE()C7M-&[?eM83(mf:)j>B)/_h<" + (C+jFe\,fo>YtiJCJ-7Xcl1rAe_HX!kFAlGV/C.qg3/fqD$AJ$K8Rq0QW82L>HO3o`S%km` + :0rT`OrAaoK8&]nul!CH?ZpbhY3W8YG9m#(Z"UF)t`qJ1phZaXn^fjiIKhH!.WW;"@))`!O + -/D_/M.790m/BL7B?9d5/t + a`^H#I*)C+0O3H:AUQl]Q--D19c-hA,8dsf0L.n5bU]3ED_ieqLqbG,djTD.7iqI,/F + DLOrJ]kPjY9i!C/%<"lR$<"lQs:"B^O!ii#h;lR-5Qk6'alH]IXq7EBa%+QV==PO`$Wb5#* + jjZL;DEt2L"H)EI\grl4/??=Bef0/;es./d'Hp?u"QEq?AmG4hRFmT-%B3.^RFUM'rqNF7V + (rKpclCCDlM=F/YWE%;Y]fWlKenNREj/O(OV + n]gIMX.+8i-+ja`*T&Io.Oa9^/r@r?O*]]?JF5L([K9?Q`pt9mJW)$lMt$DUcP1aH:PuE#/;QM3+Cbgk/Hn,9)&V8pA"7 + Iu5,;?&=TU?JJ$g28<_<2,%jq8qR2lPYC;dcjJ)1%*mgp)@Z-:@?CS!L)FFh'1"l#T\9l!(^GS=$)Zhcc4k8+U^EdE + Q`5Sbn4k2l^FA-O0@$Qu_dT`,J\Hk^!W*cZYm&ZFnjWuDYS)c<^VOg?"3 + [i<`4*"g41n]MnLboDkl*Dt9+OjP2sf,aD9*YIQV7R$%9VWdIN+&;[sEVSuqXWQl-mICJHY + 2LLd%-l-LluEmc7jREbB^@6j+g0!4CK;2/C)Dh1iID=R_3c1i%j:<$mYP\i%eCeU:EQa',u + OAZcN,??=s*8En;-WM#A!?TcR/sRnVf%o[(dX<)O(cE!DSrYu_e<6JYB7^DJAc8&/P(-]ZJ,*_>kps+'qo:Z&#.* + ?VT7#aq_Il(YI;N2OY@3.r(NY8@A"bSb5CJor@c_IO4sRA].rd:r^dn)O7(5Qo$%h%P)RpI + N7a<;h#Iuk!)2DR0G-.g))VsF+7iJiJY+f@>T"2[/qN^/iA_YsX:$0+7kkAgK%(Ea1,\(fd + a1\9cqst.ZFM5=_coP$'(fId'`485`9jnj+D*N?K""8Ht(rOshF1&oL9U"s@J$<1QU``cKN; + $&Vjg!4tg?Td365,d`<4OneCPHUj=KG8/VOnmn&lC)?GYnS?`(D^-XZmX(!E^9WYmP)2u_N + 5(3a^[FrJ'alM!%k#S'tE#/OR;ZX*A2AN\Yot*+*rZbn=)56n`+`7OXIL3k8!&Mt)TM>\8r#aC + E"K@/9br7WRO"4Z<%7&\\F;&$3reTKK#6:#MF/oo#)VPcJ@7h=tOXUq.+@BT*$@o,dTE5*9 + "^i*$QF#'RF5%FHjZ\mk(%^g'K^h9*obT%IW:U*,e7_c[-l`A7"*QMe^a9/VKEsi_%RB"g5 + sQ+BhaURPBNBb!QBbm%L&[(f13O)>`jDH)`j$Z(XkV?ZbMDbcrA@=fCu/Ro*$E#/9gG4t>Vk=e + B*](F6a'\.#^okZQ39o!J+P%KO=Rnb>d:9P[5f/kAK-VlSn:\=N4f1:@1E.#%rI8pS&mn6M + 2>>Yo0;6m;Ra=9^NZAA/u/'i3ZPqc2-d8N_2piSWP[6lOtNDnf9LXekT2+pp;UrP>lqNY8b + Q!l>p"'.2_HW=c](V;Biba+qV'@ReZomnkB;_i1,17-S9FcS=0bun&3=fgLa'1Z5I:B<>TI + oBEQ2u-\.\QeaA>W;sG_2]D`_1;!C)Sgk/@&6""PSD[0?<-;O(%*g,o3WWK$c):EQofr;d# + Pno&S_DUm@QI7V)rE5gC:A#_*T)[`W]I/L.>PYTZpuQh**__A5Qc"&SXG[:DVl3#AT0.71W + sqXrlUF&`KkoJ1(NPAp*#9!+6-hi6:KNO\/3"[d(*jEd:/q[,)P)j)]>4F88fa<:t@B8!Y$!^K43Qo`Dn[= + &@47Fn]BBpLd?iBi0[9JqhH2l + c`fqg-e(S4o,nF_]%M]iQBh)?I&aolcXf`I+#sI+Qh&\(Om6OaY\;=`ZZ-P+, + Z@fCrJ?=b!p`J/,VFQ,/!\%tN:EU08`k+auo%N>-sa#1t5.M2qg"/NFY&K&[gX^p":cTqrS + g"i+LNpd^"B+(9Ch5?8^9Xg&OJL+V"sV)icOQtqHM5<]p0A1d1Z\9f!rDhnGoLffIhp/^*R + `Y1Wn*5GAS!-tJtc2cc4!C2)=i4KVp$5iR(XB=ScGj2;uc`GjLXLd#c)VM5rgTDIPY$0YaR + cCQ^k$aYFQD!TN3;JebrNI.9CqBpBffPD+f9!NJ8@]Crq\sM-(7'kuOkV3r;E>cE6D]ht(u + #[FoYS05'Y@L4Y`AgL>6D)',eVf&Q@VntpGnL_4MCPpFBN0sRqf\uBY[bdFZF=VpK8t+GIS + H#Ql1=>H]+7mD"[%,;E:!n=+-L8>qGY-G^6'/[u\,PP)%_m]1AE\-m+q&URSg/HC;TLoj`> + +D7ph5SQj]I3SBsKgO'mrI!Og/(XZ>deQ@EM^KL0#)>Cnif7&#HIbG*Wo2/]f&"tkgJ,C+V + \-J`J+/4-*(t]<7JVY@IcWr7r'4F3F=uiANm0.l(+8SFE@laq3U($n%`Db%cTO4P%WB0_bV + H+M1PIm^.+;ff4`I)h\3ddfnJNd7kA9`ODMLh`_or3$/'Pa%r5uJEuq&*TKa'g(?]ccgdcbhUan*7rHjgPn8dQC@R[no(njn$KO@O7p\3r;. + Sj(!mm[8:)ej`g=9ehJ#Q4e:]0)oTr,\>rEZ7g'SFKQb/$_H]OOWJ%40g\L@"u_9[>7J%>HO + GZT^r3N5ssYIe#F]-s,)N*aLQCV07@H<\F9S'&h,b`2GAgfrI&)rKH(fq$S+rEJLaq<<rLmI4i&hsq)C+1q-%Pn?+5r.rUAuC/OpQZP"2M2MJE + _Z34OZD]?Q)GQGV1eFO9o5>b+;)Y4Boeu&]QqGKb5cR`"Wk.3#\G.!MCZiad`GU1'D)8Wk` + ]`3@G:YRL-d`$D=O4+Lhfq"q/5\&AZ-k5nr)t+6icr$t.3irho$&WTl[G$t.3B^:EOcX9IN + jeq_SlfQ3uPL[>FmW47>V*o!XiQ&'gQadggl!."mC[bT_1APO9P_&a3)X3LRqn6Z:eSDise + %Ih+CnQut"T+UVN(\.MEnfK6=SE,_`O44e`cTOA]Gk]O65EIr8]6b.^SHJMi9d'BFDZ(6RN + o/9?6mj/(ogrgA+`3=$qNSNIO$,0/rAW3_J$fe%pel+]="Jq$99i^keN$pcJ")`e?htdS[p + I'srGB$AmFP^O\64=YSV-gqIFpQ;QCWq3gRm.XK=[JEg#(E8=g.e9\!m8p]:H-)PtcR&ot4 + 2sgY'Vo'ka&R!7>Vti(ura((/E>_Op>;K45c2LW!Q[0rY!N)3%,GOkB;G/tH:SRa:dTRS,# + n8Qi@@F8_QXQqXA5#,KslbaZnMo?$XmJN]L.g2Hg6)N.+^;JDANq^J@;W739`)Qd;1KR*=>,)2QIb%0ja1r + ]_QgNJ@V1>YG)L_du((=j5V0PFU"`1g3oqLJIm!YaHE3Bb65,a_^[??+/X1\_V/tR1X,FI^ + re"pfpQ9(QlU?0-C,+D_ms3k]7O<::'n:]Bj^,.p'6*4t>Faj'dJZN.aPO)3"q:67IGY;7R + HX=7XaTs&o>NJ$7o3r>)UG#$F2f.K9Ld0JR8E6%t/-J&1W5+ULJ%OJY+PM"o+K&nQ>rJ&Cd + b5nQLREF+.5nOudp7BpB"BXX=4O^4B3A9CNGi!%'t&*h=*=1Y?rscGIplHf-')Kf2Dpb1=a"qWBN^XdT5!";V6= + Z=2QaH)>F*gOE*N&O\58CpK]:%4j4Oo]*=)PtGMUl)kBUCCNUM*32g+bBhj=$1J7#eqs++R + f+i8?Y\5!Wi@CTnkL#FBp-&Nf>Sh5iZpsq6X*E]a4EF]q&5E]P;/k;+:3C[)6jgeDtQ=Rd% + 'F!:uaC#kiq0mOTkIH=sSnJu1fJjCEZ8NGOToJ\o@sH"cVeO*>7Tns=d/H=npc\qc@+t#4H + sYP3a0Asf&;VXrjZ-LOS*)g3[r*.1^2n02Y3EQ:-MHc=TDS'V^2Q1."i,))2[XGmVuIFLnQ + ]]Ka)X:\-cg=:d!3TC`DL)SRqEW=cZC0,6dg()2au5<-J(K4TD`K2EHU)u@G,h;Vf14W-OK + h/+5hr!$0^_Ic>4I*s,5q4hFEpm)ZFZHJ"#O"UZqV]Vm#E4`j*1+qgS"EmsI>)a7&r'YIX7 + ss%05.rEjKrpO47k6keeGH#.2`B&W`'Rt'JtJ$8hP5SsPZTC`=0n/uESW"T7U>Rc8r4G48j + -paN5'8".^KoiSd#=tO%,/doZ&e[Z+@500a"LOO/nnSV)Tf3jn^n?n^KahIa4=Oi&*5f(N< + ?^m2.o?GN(*^L*@Nf&/UF%7PR$_.%KMF-s4M/QflsF5,HS@0b_3blE@8&dj(8C/;JC#2;>- + T.aU6ARo8TpBMM&Fq*7#`TSCZs>I"I*5"cenIhmBTORH5FVrc+uFD'P*Yat>Ug"V + $5)>;6U7.F^pG9c+9V]*`:#TlbSc)U`]TS-iL^e?I81+C5%isVT@='ocoM1n-kuoZ'HVs)C + )K?OD%!&e7Yjo&+%9k.(8@%"NDZ-)7O[pKU\&_k;E@6\\GXgRG>:?h@U@LP8Qe1-9l)N,SM'dBE\#(4d4F\SkEZir6TNXT(8:M+#aEc*#kWP95DdLDsHr$'dgc*=KT3LN-&&.-i(Z>2l!$Qfmg&&OO!Hhcjeq$X=" + 2O65"E*Q=K!8_S?5PRfD^c@5&:3u2;D-AU+Y/6LSeYTf=A:HR3//^@);hr?:grEG.C]9uPp + hE=9&)leGTn?MZHQLVFA]mYc70W$WtmCoKSr]S%7T&35K5+q+5t!r#oVSBKK,rjZ,SaA9N` + AF<-=66hE-E6W#FAL%fu`10ns*ANl2cD%(Q<[j:$a)E[F;tGpaej&BH!bAj=D2M^[ZAT9XG + o=38:>/EOOG+N"CebP?K:A_b1kEs?:$=a2Z5XOn@G?>^<_Il6C?gRIf+GKr5/3X9'^.#puV + 33k7&aLqe!UiqNV&kl"+iNG21&hes?Um,nd9PK>^.V34aQ089OTK-\;V8fF_d + dg^<$%FX?n5'H8dA+or9/9@$c^DB+(aH#ZY7]:9836hqUfM=kBi`8PJp:%NYhU0d+?]t'D; + ToDiO)5',6VE&EK;-&8Z>JBgY7L$mQJCs2\tN2)kV7jD:uLEDX6-5a*;Mchn=6"p>2:rI;b + \P^,Pc_hu!.uE&B@C,QN!nFO%U>^(nP"V)dNMhhY5-HX-?L(LZnG[8^Hia(eBlWI0GsE]uc + g=aa4\XP0^2Y"d)Sf-KE^Hp%_^'-BL7+9[]MIIXns^fm:HC=U230O?'QCW/69Hom'A5+"S# + o4f#+$,'HdWFG_3LiCp3anQ5!&XtPR??6\E7LXkE;D9MJ<`+hNKT6+."Y84TFlhS0!!cHiNoG + <)ehG6Z+8589"OFHjgiLVpqA0P:q^(]BP=a`l6\%b,hr^20Y;","1^`Nd^_rubSr\R-]59_ + BhDjQ*NCesmE7g[-oY$\oO)8eFX%E8+ULWB2((o,dAJQVsgK'7?r34BV@Cg:`8\*o@YA1e* + fO'$qB^_juhr-tpArl+YAL\q+e$iiO,!l-&`^A)/&9CITT#!d-75\p]kE*g=r$4$?4L(k$2 + bR?bd%`%"/5g0cEr"24?&;CT*_)P_T!.Vga(-u1@_1WVYr"_Sc>sYP2L\r)WbT&pu*f + /?s-/:>bCc/V.,m%9i-7KG4Wb"'9B1&p=270`#F:rbCN(B9uQZu70HB)$r=hV;T0n[`>hjr1fZt4)HDQ4$!#I*e"g-10D70LP?Qm85ukWZ&+@`NN%\m0Kk<*u,ZHCKhjX9#aj`-o4uI + QGWn3qKLF!QdD9bfDVmXM_>q&Yls1c`In:]7c!B;n5Go\i[Q_X`TL`YQt5\fb[!//_lH#.c + 3?-_`UVVXsB4643"F5a@UHp+5=nmqoYP;bb-0G+=cb@o?aZ:m]RSY4X8FZ1nh*XU:k'g4_T + >f8eA1F:*D)$]rTpA]A5-grlVS(4pq>4d%\5m+>>V2^/O,IA*1LM[N^C2^7,JD]*f#<$pN_ + =97ma0"7JGC.>"c^,QJnf!%=UP(!H1bbQISoMteO,0S:c=RQ>Tl'F4s(rP.t-5VrMcPfSRN + "\c!$^m*'YN6T/N$I&,Q5hs`%lk7,S&C!$G9gE#N9Gbra(JSjS_49.DU`I^U*l6nW66+dt' + I(f_,d],[Uf0Z,S0j$d:^%\S`-;9N3!"OH!X%@F"`u<@P>/F6lD7JG[/sCH)c4F&<'rYK_c + 'RCF&l'[#Qc+mUZ=D33b:1]412q<_p%n`r'&H#+N+@k`"ZD$d40hM:PX0WK?.5JLa[ns'`h + ,QJ2dX]<(N#4-3@R87Ek_WN*K2E$IGUWd%(Tl[1,sO%FFbXd01"W'bA\4&CDd;d5;Wc/JZZ + $>=I71.o8e9@7lF@K7P\A]RMbDoIGB2U9o38A:0<,>Gq-IOb!dR>R> + pd9^B9V^eZJA;TuaD_Bl*s_^d,La"TUt)iJ4`mPE*2bro("Mjk4i/(/XK5 + GIhb))M0G(57<3Ok_"\TNk9.73k46Wo1]2l;I5,p-VF7%Q=%a1ZYf)U;RJ4bOD5QNhY!Lra + `;tdtB_H1]+L'ZA?5Y`Qf?K/b]@N9S4/l9aN+6N$1;$W1'&m + ^4V7V9Cem/2g9A#Q_FSJpUY)fmnG\sde\'Yu]Nb'Bg\R + 2%1P;Z\1u'rH;b\4dprTnbIH*F31t/E+FTlL&u4Y + Q"5$"2=a1)5;*"`Q\e;VR4iOp:_p_W?h6FJA#U-8EF9OJ!a7\B>V*!/5JR12thF0V7<.b3R + ('0C\89I82F[Xa-X]gnMN@CtVmaW9lm2,)+Z`(=,)aa;+YrDce'cd&LK0k>8l],(%KX^Up=7f$YGGUKRMW0FhTEG/9&4!k'kP**Aj/<;i5 + 5`LKkW_nBtfjJ^85X"]Bi1>'#*aPf)3'E?ss;)`?XRQ!140r>P4b]_;V^<%;g?FpdTq%Ehb + f>5u[^*NZm$8!E]1(fFB1NmNpLJcsh-OfMiMY7>Ts"LeWkefWHdM`$&UN,%C./c399S%?Sj + UOau'o>qqfL?l"=dQ<@dcfo.R1DE6COmA6YKcLsB.J^>Dq-)])6^=QQDajgJFUY$Kfl(Sg@7S&Sj*]u4M/gh&"Fh54`XU38 + EA(AsR8gTLo@.<$U]K!/^r8Ht(oKf9rj,KFKZlSMT*IA*7r\/1+d1pt,X^UP0gj"m+]5rSq + 3:I_,J@kQU&2%HX,daoMgDXS(gaY(`uXlf`KY8B>TfhMDL9mMWG$#<\1!ZbbNGNh7Wh]QNk + gIJ7G%p.:>W+/MlmXo)Ppmm>Mph/?n9kCM@s;k?m0Aq0?mtaH5b_%U<;U* + 9(iYX<3Y<)Z.=g?Pj<^gP04lge@jG$ + 5o$?'M;&#Z?_"#s#*p^_puYQQ@@L-AjZmcEk;k2k1WDJ&)"QK[b6k\dinm_7uS`pN3FmdAB + hPjVm'3',hIGjdE)jrS#NGr\o'u=ja^t*Dp5r^3,3^a*?Y5O0M)\7dC"5pYBk + didIjq%mrk2'q8j8a8esf[gfSL7:i*HW=*X>&CJGk/BO-?(H`(XLQTudI:bV.$9#(WUfCgLm(;8^ + >^LD)rfOhs922FP`M.4M(ls;QcK$]s + 2EWikS>GTBD,rjfBMsOuXI.p"[C?04-mD1-'=F]Cl8X#.4DY4Nm^cR7;%=m(FV + g'?d&2EVYE;+R"pP/*[O*C>bHM==apRcj-[s`J@IJk7JDK(6`J + P&_ZFd*NKK\5YYG@7%JfZYnHOR@(D/`Dd#3ErUZb\]J7J+\lKE^7H?nB;RH*M#^Z_qEEQ9) + ,%ZbW/O4<>9?WSFl5I'H/b%XG:EbYbuKiW5kQqA+mhuC4WhOJqi!V5ZLo)CDdVP*N-#BMd/ + o1l.5DPggn2emh%Y`3Bj:(_Q]&m"HaF7738it)7$*.g49J-aL,p9MmM(j%u0oM'RP=4%9J& + :kZ6)E@#mfjsL&(.-q2n=ETC.Y>OsN`:ZQVlt"oi'G:WSKL+QnBOiG/$\q,Z&NG/=,o2A,j&)3)@QbZ + ka(,fC'UD*6P"I"JR:NI+.[Wk@1AR0cBfDI*B%*`tmX76k:FK0<*9rM"U + <9g;LX&H'"CDFFoG-tYZ=6rs,-%aBOZe912]2c;gDmin&f,LR3m;fjFD`<$npA'3@W)O9-" + r@`(`+!^g]J8(2LMo2Fq&Gu!.@84iEj,39908ZiBQP.Nqk#mGdcpF.SS"X&X4QZ\&,-9tCU + Di6G8n60pN5>8D)aWf(DhqKiUV6KY"(.9c0040>lIPJ'FVp(HWX^,aD]JkG^LPrT9I7Ui/P + c%rnk2l>qd2+gW_Kr7'lW:EC\Lc5@jf&/W3$C^lTKlbVO3+'Zg]6\AJ22"0b)#sln]0iOSQD,+7U=D&k(,@;&.%>CfM9?L=sRr`_iOE)TNH&Al+`Rg`[bi"]8Z;Rn"(j + ,09=3OX(kn$3)?onoUQSb#5=9/'@p$0&Q"8R67@\ + j^@B8'W4%->5?(%Rhs(pG%V7UZ6QM&+Egik?hF53)OQ>)uc?/'O.f8\a7h<4TuBjTE1ms>s + 3jnr[s\-;e@hmaj0K4,TGTnD<9:)7AKpkeu\)>KdRMIP5`0Os+PiW"rUk&rtOA2t])@q8_. + s-4G@DHE09\UJ+WR&4ZL"9AL"L1Hq=(7D(7\p9F8l=Lg4X + 6PK9K;n0_f"%uVi_Z!sB[:Bj + 'kG!2I6>Si1qESYh%hgOAV#Pi;npjO$8?2._lDrl8"phUo5].4X`OQG/1>T`sUr`XXikI9l + FrnkRrHtBdro+lgGe:7E]B@'i\T7LYM(14!+J,[4'kZWHXiFMrlLpJ2m"p)Lf#m&s@!YtjY + J&&>g(W-61H:MX(L/B9JKml,M + Qp2J+h0Q<6!akbT\:>R)1F5Y%O"5<"tGN/RB8ocW2Fa(g)WPpZNY0Pqa&qFDUSN*p;%REW`?sn + p=:-0d^a<5aJJn0081M@KN+DEfMOMRHHVZ*aKi$:qT[X,W;+_me;)Ku&.(Y_>'Q$>hOoIq9g7=V2JPdnS9W?RJZ_]/&Pu + =H$9@`%>=;8kc/Cg&ml*WDe_cVE;[@jn(Wn(A^G\5God(,K;8Z(d/Z@<&XO_2NM/*QD`\n2A0us'2%cUrB(f9!-7%8.4p2.B"2X>#1h4\a=rmEbAro%]] + LiIU?`nJHQjR%Sg[SChe\[OkAh*nkrp?gA@=']b#V87?#e_7Y0H#`7.]6Le:j1LkOo'UJ*( + IS*1J^@7O70')&:MK2Zlb*#]"6[<`dIk6.c$-)ofqcu!H3Vs^+^o/5j00r!J!VETE + $_O81t5kjoL,0"r+pj%=qq=1kli");rSO%H4PkLJ[PN_t\j-Ua1LCifm=O?n)=G"oA_EPY> + MU9nkAl)T;EO']Qc2MfFJ&``TtniuDcmn[:Ht\F1DIb]Ste#!^12q]]PjMidKf8]_i',"RX + c@=tP?RF4GPAN[&+VHfJ211r45S@p:jq6H4tP#5gRc'`.EYrK%lC7WhgRUr@2Pe!,AAjQD. + !\dBa@5oZhWA?6S`KchdDIuTOYEa2dO.E!hC5#Kg(oH_`6IU[0Bh^0=d5:-Z'tnu!K>)lINNNm0jae + PVIJ'o488JP5RV6mRD;RL_Vl%LtK<6go\RW5h$]fi9XqO]>Y!@pRBS#C+SU[Hj$];gQ[?CS + s6YF;Z.fFn=e47.`%lGH_>.i@2EQ&o%8fikFnE8aKr\[*=t?!6iGi[jr>nQ(7=_,^@75\L_ + :%Qr;n@V^TbPCFrIWC$%d_Y^Zb4T1>!?c\3Q7o(>j]SchL3^TUQW'/f:gq\ + EGmGZ^$./aVHaLaE3:_-\NNOBgrl;\DMMA?(J'%+i#W%KX5kA3Fmes)`S']+j1N(-pHK_Sr + 86+G]q*=?h_+;E09u6^JtuD-:^dAT7Jjj/rrHL9#D+++LqqXn#5Hsq%Qo8(#D$W)L="R$Z; + sKe6khTbnP=VN?q_&:B@:>:9BQ13WV%=9e,%s%lb-r7p0PenHOju[rP,c7*q.p3hfJ8bkJ6 + iL#!V,7ScCO%LI2t%k%L:to?=(bb>MOsjq"TeI`>eRq4B`scb1a5AAna.^H2>n]D6qHkEL! + t>`8]^!'h(sR\0o^$Xn?<9otn[H3SV-5FhPQT`=8L6NIl5iBE^'C'MJ"dS24?&01eU+O_o& + !c9jtXZW-4W#MH"!?`!U9D1nN8tV)A"@T?AC&CX6@+EV':'AEPm:OqK>1W(]f8:R"Xaj4WK + a02cUJQ3fn9RtAPb[W3U[=H%b!o>^U=sId#t81T.UH4O\I&[Yg^UpO-'I&C`sXAGh!#O//_ + 6nk>UoM]2%8+.fMR;9`\u\`h@I=4-VD@)0cJ]?,A>WJ2mN/Nn!9-W?ibI + o@b4o]pq>d/q!:0>?TRPq@UuYAuZ*$_4M]UrqrYPZp+5f$Z?qC98kS/SG)i!@Yd7i]Bq%YD + Jk:Upsd9O(3J@3E?kY?`F^Z8ElT3@")`,Vhq:&ksWl:F*5C-#AP8bb"Va)#8+S!&A6g%&Wl!E'S?KqXSh,13k8#_83uK-WY,pmRlN5I,,QEk4XF\3n2Z(J1V-Y.R/ + 2r6k'Z!V4bB:b;a10*:+>7WRW^jCT$U1_m:M";"&Q$2F_Q!mlYe:HDE8D/MhG\ZuYgBD0nI:T;%E8rfZ](t!OUb4Si,J,B`]E'2%cHjR&.njPnHCuA-t + k=DY@0[1_4_mBQ3k@Hj7+DU0eDCJ0'*[$'6,iNi33SslG$`$$=MO$Y%Y1><9EL>(%GKD&S1 + (pB':sak`K]u#=B:^Go'5?\*%/a'HD;dM!Au=Ga'WAoIRQcR(?^@(Er^FTFLu&(@6gk6taU + .#L)Qfdi4>#)p[Ob%d&92`pXUQ*2j/`kSsca?jMnKn8[IWZ0OpK'YuNpn^7!G'TDS_M?om# + R\7e3JLr0ITI0_K"sB(tTNDAJKh=+',rE/*FM1=H_+V/Q9D$1s[)l#6a%\D89iTM7FPTZui + D#Qu9?N`m0Z;[&:]M#*??Ob(Vc+hd8(\2m[]eo7LEAM%-eOX75:41RATu + n733+@:i:Gc5U3SANLJ/@WT"?<\,?NdLDl`*MC[e@1M?V5s5uP',/nZ;qjI/.MF7OZEQ:h@ + 9s_#f!F[*58Iq>enjLW;a6$N5OO'11^=fg#e(KY5rOQ9Y+%_^K"+c+Bl9tXS0QG'*gbBdW\CEDW!C;H4khPnoKUh053>;)a2Q + +M\Cng:q$/#/GI=JN+?!ZC&$a<&\QT;ZP9)"EA>gXU=1CnSY"0\/a1e`A + BlY>g9LCk[.Fl?guWSE8-*9iF%ZEXOERtQ4l + :JpL08`3jp/COU5Jhoj[VE+5fTkJ6O%f*YN*"j;=9t[RXl2:9kX24d`2$TL.Zh.LEn`%1[/ + <1\2?IEBg?3(,Q$SlXN2e$"u.,kuN\N1`5Ja*k6u:Bp":_d>kcGT5""q)4%6(J/F`K4;8sT + )UqEAZqkL=_]*C?7Ca8KG0YUPB5L<-D4)doi-#?UXW=/F%$p,HbQ2VRJ:r+=P]Vh%e;hBgZ + 3qf_R]f'MG>"4\.LMiK&*^(t!)+e`D4;EJ:g(>QH+d%22g^@-GW1IUftX#cFe'\A)F;e4f`&1[oG3Dd^M/ + _Tq5^+_WHJ8H"orD,TPt-$K7"!KI(..h[;/i&c:Ma;TgAYa+ISER1]RPS(&-c]'o(8Jn"VZ + DIoVg=\+uf[iM73Z0mEeNAHuE6$;ibp>9tkPW6X*qg7(u`-URLOY75b-OAT + b?U`Hq[*TrY/d=(\Zmu)C?%iqIP6SA/ + \11a]`=5qJ0?"8#I"rb'5WJ7>HMAITcN"bBQ,eWd1PQ7"T`IWDe#?L$N*u>D8`s%=nd#@<* + >kUu->#WfZJFb0>\;ZT9fUi:%6nU5?ZN"5mP!Otm;.KFDRp#G?ANIGNcWc/0JZh`^emIJr&0!T1Tnhu3f;^' + W`s#N7)q]a:aQ=2I@q+i7P3]po8?]BA1f0u"s9c(hu44tYgf6reft![&dll#[6)TF3V?<$! + :G[.U([9G4Z%^L4tgT]O)jEI>DhI6:0A)B%`&ll%%O*e6?Ti()O3$5FI.0KM(@)E[3]F/2! + rl9:r9>"4m`AYf6Tl[4PcQ+&PSNQE6\>PsoU]TuNI5QisaQ*nf54K6KYX31$@b9`FsRYQO< + IETU=bPI1R"g?.;;9Z&8WRHFC.]q0M:&rd-$UD956$*qeff5Mb3A(?XfPRZ/orD+6c`mSuq + RrsiL%HPgc#">m+P%"\)m,YNZ/$R\>p]1?kDGqL%"SF"TT0=$_2J0mN)TP2Q@%.& + EpJ\\8m_"['q&!N%\DV`.<]uC!H:&dAt+5n=T!NmOF#S[aT"bT,!TKjV;9Vm*9ePGsn8g>" + -7:Qp#e6Y'ON^r-X)!m8K,`tZ0&p[HF=XfZZjjP'fJ3GW>:$3's\Qo@oQd=jE)2/QO7%mIl + 'REatR7X:(l.$]+:7kOcHU,e7BQN2J+onr0egb>RE-02#sRXD^cg*5sZTi.*!Xa + D&bJA/sa2\pr!]SE2e,ofoY + !"s;\[kL&;9XaE)r9ZRA+Mik(QT1eJ2(`CfM3qTiflgJc9;NosJdDf/;Z6?WU8/rb!/#'o? + AO[RH6g=Opp[B%WDtSRMe3W..X@jKUUKR>.F8DBJ"DVf5mB%EENp[meDY)+KbgG$W^6P\An + )S`P0(pTQak6ei. + 4cj3"&WMk4a8([+jZ-XIQ=]l3MImt+Q>)cL\]/QYB#j_'A`pW'I@t8"&KXO\5Ss\,>4!r*! + Z!)mdU=!urhb1ig:jPfCO4R=A*WpdJ7B\u(Aml + TCu?C4sS!Pe+d+aLkBa8ZajE;V_K`::h"J:I,PT-_X#r3:ano0q7kcQE)R6-o0d$$'AprAR + U&QR2)tf(GSX@U'_)s(C'ZS[W[qGC+7A>PHtDGII98^PUtmjfrshH!X:.>l!6-D$rFVrRW>-(4f2Ip^Ors$,-95K//Y + J5?jZO7;uf`Mrh:03np>5ft@kE3CACZr]Oq0*S#e)hUt1$];c&5N)_hKJ8E"64FB=c8DO01ECc(LmSXN)>lp`d#TD,;a+oOfgW4W8S\*N,O_ReiHl;JCk>c5e5$knPWb-dD:RQ'/6/8r8.@-/$S1d_]UeM3`?4FOHW6NeofTu5,,6&kY)=dkC94AV[%g7l?E>&.86*OM=X + uCn;FR?Q-$bSTRkGE4bA_nQrTqHrK!e][t[H-K#jGJ<6WCK2A]dUNM + p8@_@-'1'U%fR;nI&^/L-AcRVX>Yp?Wh=ue?u;e>b^-REFZ"u:fb8mK&uF$\'5@N7iD + ]AkD/i..+RF!tTnV4I5*XUjD&8m`/1+U"md75HE(,70m#1ffI\LrsJrq502Xn6ug"cT%@r\ + rl._:e;=dEDBZaW+T"7SZ/5Zl?('/ASh!7'Ae2a\c&UPJr63^dp#Xp$UE4)RA"^]?VpS>6` + >sG'nX_gWnKD;B\(@,oSS^rqE7F72UeJ&,*a`0>II"QhR8[D-:%o"agm%)r05]*5(S* + i6oaLgC0Er')&5Wtu<*M,Oqc)4]D+XOYSra]U\QX=Uu9<]5/)lRrQVFfD*9`e5>L\m^N_[Dj4k$$o.F=km"3/Z& + !d%pk1E01!LE)dIc9&=A*U\p_XQCUl7AR?!PKNE!8aX[,/dm2!Iuj0nT.3Jn$e4i2qn9+pe + uZ]%Ku?A_ZLGL/eLDuBU,5)!e>8A,tA/1-btNM"#:&a^pkTu!LulGdN=X1d!K/>10/ul7La + bnZH5<)h>?Ydn,=->pn%ZKHp<\^IRhTW?T7'Ds-`LCGL?8Z:[(M*SG@X&g%p]F2'7@)&ZR(o&fWiaHh"j-"$e9cVD3^Y + :]g\O"/abIJ/Ck2aAt,S$T(1`1i#OWK1I>r%jn5eL7idC+>_:l(D?%52 + )9BlM)."eH'Gr3?_@'U+ie9]L@(#nW[K0-fqdMH>Ain"fG!gP#JGFV_U/_3g1P8LiKm6dUqgC + EfNiLcp6jnR69L60LYs^[-6q`7oHq#"cU,kDE/OQ?dF/DDV&I!]/ + K.o+bBFKF4&IH(N*ODlfS$&n=Y9$qb/j$?k9&t]*kJm"@Jf#\`o([h+08(jd0%01"S%H^Un + N51i47`!`PJ@Fu3s2um.u[)WbJcS[IkT9l0D'f=)p5B)K!6YZ]XTp'GZ1Kog]=?M"Sd1)e' + gHFKE,ZiJ=%_dGWWSHdD!,W"?8aQ8FjAj?NUQfKT#4h>`_NmmE'aA8VEF%N1*>.Rn8j78Z\ + H(]U_X\Sk8jNapan7'VJ&S"A$kfI2&$kCC3@@_b<_B:6&Za,+CGC5F2?m>blL$pnO>hY>s\rD'\"F'> + U5RgJ'E%CU\IjnP)@,8-pk#j2K<$>bA'AUCT0u\:((nSlk"sM0dHTO6Si^W=`Zr0kn,l<3"-C + ;rKhuTdl]8RQO[T+E70A[k,ZE&A!\@/4KH.9_#_`74.KZh6KlC3#A6U$Na$n3W`T*Vn'%4c + -U/":ml$7\\eOr>J/IjuX8o],Yceg#n#:6\XQf!`1]W + C?htV\jp8B;G-K"KQoTFH.%!:KeQI2Mp2>LZB%D&YLCK6gjmi<&BI!g0H83a(l4sS#,^#[J + >a!2GW4'4;hs,g8>u.kn,WQ/(BQ!CK4n,9+Amohj=m6(.PabqE=7;);$@5MaulCt>t%_/U= + =T\;S&>)QeD9?+lb?\*ZoX)c+=&j(@eVp_4RRp + -*)hEVseWd.8V-OQJeHbRZJTr)2\G_'8Zc[u4--]H5AS+S;>6X5*4gRf"O6]0Y+aJakei)=fo;@dPQ3Lq%flAKgLr>G\!k=GM,a"6`]S&2&KP#:GWWd>?dN]PC!CYgL_:;;cqXW*ESub(0g + 1$+!9TkRR\j7g+?R]:C3/XjSj)]XuuJ8!3bQTR?o-1k`@F!0@X/TEKd9#(b,AJuVl"bq6SL + aPHcFden`()p0=01'nd9!LP4D]Sli`*s(RX"!B=OWZ6`V8VE1BaG>$D3)^('<CXArEnsm?iC(5p&\V3!oP+%?q + pi4?3B,!J(6;]fjd+VLe?=AbrpZCU4p#BL#]k%9mK[t + c%mOUpK3q5fY0We%,P++6V[EBf_(?O+`76Wk27$L5!kD1BL[ZL>3%ef"m")r+K,?oX^eP)Q>Qs>?>J+7glI4 + ,_UM"l$#O"tf+DtEii'.C$=9Y(@7ru=@?.;blrIhCK#/D^?YWVuR$)M)qK4$Tjn0nF+45JTrhimjV]^Q#]?1FX83WU?9!O*o&BK:^D3e_fR"P@gjE(GY4C]a)YKO>Go/#toFP + 5su6#He8=jhT"&R5\SJ@\$e>S0FpVT[$fSP0@($P]*Z + 7t,_Vn*Xi=YCOrkr57&8WKC!@ST^AGRs<$&*@E=!_o4r\!4/Gg*4mr;4D[nrt@R&oLNnCf49u]Ng=_?_a78=Uef`"BOBl:7+LH:;4i2h"h#L$NT#.!@SQ]-l2nd(4jL2!Pf?L5T#aT'`aU.!5j]`) + #aOq'EObt#G,R(oM8ZiB^n9#k-l'V7Vm'X@N?#.!O*OF:C[XC\@S=PJj:Ua&\A/?3-;OFKKr!1cp7d_=YlFF".2.QlH"W,-\@F<`4.:%^_]jb(WYB_>\tDC,ogSSZfPUhZ8qs!-KP4nKa?9] + O0EVSLJe0(,n[O,?pjp4#Jrj&fJ:Hg$Q_XN98,<[/VOD53KAJ]5M/X%-#T7W^\`ss)4Zp$# + GR&c'('.M[JiG!-:b@:O6rX.N"6a%o5XU9.7YGGhKV1nkiZO!&70H.o";jE/(d3Mu>D#I'K + ]jcbE&rQC)?Ojc!3d$gZZlTI5DAgDlaM"\:NgNgGB[D:PMkm`>b\o9"0N@2i:4dpBFDp#T,^Wpdb*4O*EhrGSbo6(dn + MQN:Ei+"/%snIc3t<02.u!mS5MN2Df;iRl+t^mYV6eSUg.:lYb!.fK:B\=iU(C0sUS'YL]dE[Ng+G(.Tqfg!uV&7U^a&0KckWd0oSqV5]2 + KMsD#P_!rn8/[a#5hg/JG9Y+BMo!^GP'cfl#kg7+A(g`Re7ucBuZY!31goeppn*>1@Sd"oMZ6KI--76\O&pS`NqiqN94*g2":Ild,mXN.XoO&>@*j-d-5?o + C6Xe@:KIj]B2RXS9pp9q&(EAIVE>\lk:p+<9S'UmeU/*5(9'+L#KXPAlF + iNB]\(tEN!=59T&QUHOD"bL@!J8,FnCUu5rLDHX@ki5`L-HuEb!4Rhbe@:9Qtk`hXPE\O/Y + Zk@R@7t[s%fZEN.uu^gd*EZ?*]nprO1a(b%U3%]?U'TJ\YgbGd%*2-]Q,ZE]L4mCEo8i&72 + e.I`H*/g[7F_@aW??E9go;+9#V3S'gP3q>IB:CB9Coq0bGV?cV'sp4'9uYNu2f5S+n=!l*j + eLk!5P<"`OTE)[na63m=4@3<@RW]3)Egc5k^aAFZ7Ra[o + )Fd/s@iQTPWGrs!Z/gV/cMgb + VKC?uaqU218V0:"aVof3R9WhA;aEHJUTol$Qghc1ueFf@BtOi&CW=K&E"[#DD$f/ + ju\[edDUOpR.`V&j@:7SOrIO^;3L$D3WFQ3W=8@*/FNIU + 1ej*Oe_/E2OUB*\mCr+l5H7dWp3cd&d3c+87!-fqS'eGCc]#DZL"&Ea0b,c\U8.BH[Rr:O$ + i^bTP)s?mCQr!hG_."Gn0q;E*3tZO0AkV>Sm\"Nu78@ifL`e)s?cMN?\;#3fh2?3EhTBl'F + ]^oLZo;SFjptJ(1C0;LkD@i.?drQg-o0g8[cnYc[Xl_S=s.b6l\(X^2pa\h6XbNT@7^K%^b + =::*-tYO"ml_KsEF-HNL,ca_6+?Md"6Hqc+]o)+JSd+&"G@K+k:&G[_K2Qb_Whu:n;mc?G= + ?haQ1-U%M@o;)GIRFQ;]*X!q-L_56[]GO#koq&*-H;AVt4pUTT_1VId:T=UaL^"DVoGIq]= + qZM07MI"#H@skkS4"rH"N)'e6/d,>753O6,1Ks0dB#K#B]^E^ZuV5e,!nf!&:_WSO;>^sJq + e8-Lk,OK"pD/s)bF3a,s\1=P0tFFM0UjG'\+&LCh-8fNB=%/eJ_piIo#Aa<=4lb%7-pgM@+ + >mW:e6/'\%>fb%rC-Yi3J,?*>CDm_WP#]s + PhkJ4adKIfB2%I;g)DltR`u;C_ZW*R>5Zp?do(-ZH,DSp][:8@9Ig;Qr]rp+FI^f7^J>`.4 + D#T+QrDhX3$WZ05qjYiPHGaW@+HB(PE^+U>]5b)&e4rkRR>A?cP&>bkXgE)FHg(^]#lkpal + ,i6oQf'#'Kka&4108hS_OsHHrqh-`-=,O[61@J9^.[YH%Z*cds%>S&Ep/JcW6\U(V9=?,ss + IG*Uc01P\=D[lD`+7otIojP$a.=Ihc5I^W6"Jc=,#!cjm9#':K@r\3uu)Si_J\*!Co`JJ=j + d$,IH,HQ]T`K6?#[=-u-Wi=$c\W&4drQqi=]PUf(V4UR8?67IZ1-NsLuZrn'S%:R+(LK7_' + fp$<5XQ2a)ep\o5G`FB89r&,i>Wg[fA-bI,0G@o7ECH+lPnLH.Ml:qTVBj>&j"7s'nZb43# + h9l\+K-"7kTU-`bA*'uH!Kn1k$8T@gV>4N%,u/.bDW]Mjh9H]=*F(=qo*c_-/lMGNq6SHd< + b7EmCcT4p0RXXHuGkN^=U[-hcoW"n(%BO2u`e,WH?,seG6o/gB5cW$U!oh`L6KbGMsF5K^a + ^1_GcL(9pW<@V\U;CPCEu"*%]29&BPn7(]sb_s#XGc4J#Y:(Uq$hN:9k*)^C#cj*Yp.1"JB + '@n:f(S"tfRBEf$B-r.*/Lfre5`3P=/CXT2]o8U8i.Jq + ,NlfrbC#LJ'\>^5YX+<#(-L$UE`Nk+]'u3i*[&1>RUZ"LT8PM!nG+,,mE76/I+.PY'%aoT) + Ee<@-)Wc2=_>q$NCD`(<(u.,@hr`KsHL/\JN0I1PVS=!l.nKeXKeP!J!KRK-5u)d-#@8b;B + g3lpA&C"*@]pPIcIEUtrJF,`?+1)FI>de\D]2U#9l"6BJ)["4Gk,+^?P889N4=C1iQ;0L,mEJ;Y",56,'W"3UF@/`f+0Q?*OAb^$*B + k1n.MF'`q4q:@dmSH#/P*duDH#=I_2(Sl.GiIKG4)%cT;*d&H$Ck/R/:d#nI2i\/ + L8KF>WQ*$Lut3aCP4CP&d=m,rAiM3fLQ1ap)1Iq1$b:s$]J"(1bYgS_qlQ;bOf9Co0512eN + c`SLMR$V]"-#B5I*6&E\=c\cSVi'-X%J-AJL'-1g?V(%U6ab8Omjk<@@%<'rIL>$>&E.RsJc+9g/ + DMY$7aP>oLk=G7cA8Mfh@8?Hc/]Y%E8Ekl&OD*)+@?7*5QTda!l3Y&!CYirBaITai/S$"W4 + :dR'&g'l\*bBK8D6kZ&/9[b*e;*9!?atbKXW@6b!H,nEfBnmgi7d&!6;ld.\(?q[a>E&5`fH0b4hX&J3>'&WmkAd1m9+)\O[tl%Ko;cM6j'V%X? + =ma4*0N"#"a6G^ssm1-rHNm-j([VO8d'[^,!Z!O!]TH-b*!N;[Z&O,WfE5p9_(QgKid4lIJ + U_Q1))%.Dfd"2;=?4OdC&RptHO!=,\T,J4Ynn0k%U1EjF0V`Hl)i%DadD#6-^`W#96fO-MX + O-WY3bi&2WiqF&Ip>YMd,X1tFK_S>nW618ZLW9,8OSMTfq1DTA1(O-I*^dalqQ + I.#;BF9T\?&C_3\SGLZ7c&NcMYpK,=.>-ccm]n')mArtchQ*$DQ]og!)e-H&.[>H&?lV"Y`KY%clE0cP1ba+J\h@8ZC<1La9uCg%qf2MZ<0J + -?3``=$39K)5ti$PAfd%3ZD:*JBQXKAdRN_6Zd<1d0PH;;gtg>!hBDk6D<3D2j;#MnMdY\. + 19^b/#QVZ'#scuSJP7*RP.#EI9$j+8^nhAD]\sF#iK1)!0Wn2%LuLu3NZn<3@0RR!QjJ<(" + 2Hr$O6H(kR1+f-'ELB<^toO:8dE.X$](S\'!Oa-L'#/h$@r&@;("=8,reaD3-0^3PE.]PP; + on83>7f@;k#f]T/ooA3g6="Z_ok5ZP%7-;,Jt_Z%Eka^?SR<4HX[#&Oc%\?PS]3(!JI&L1$ + l;5Y=31885R)PfJ0a#b4f9cJ1DUQNM`dDJBb`lp0[G*15G8mr`t>oiBEVGZ?uDf#/&lUNdT + cGK)FKJs`VV]V&pcr'_fOoVJgP9?*Uq,.e#&"`VQ'LSn#dSI>EbqUD\^=/.b,187lPeSIX_ + ^![M,P8tm6'NHW//*Sd%"Q\9!,Ml?MueGGg=(!d'I!Wl*Xr,a&]&:;@Ikh^s%kBI-V,meb3 + ]1O'%cFG;3HNaP(s($N3Mt&!V2KWEgYO#T2RU+.F".oiHQFEndhEd#Cq+--so`d%/tbY0*# + /_!2L(_$kXY'(-ug['g>%%GXW?k/<.NQ&7""-bhTMZLnod]MamuWk:X(3=d&;X_>Y+a66%, + 6$%US-+;=k$hJM%V0Ah9YI6P6'_bcO6V'6+O8b7?Js)]B17a$UH%P!u(^,!u61,:M$5ObY$=MWm+B/ + RS:]UqL!e;a&Pi)jUYR<_jKHY"DJRBMWEE?7b=#bP.'meX*cNSHCZZ=_'90l\QP6S>W=gIE + tpmMt,CG+88'gZ"E@,;A:4pN3Q<8\;gLh$VG4p@UN$393"!]n5Le^"PC*F2e/ + $(1#bbB/\FnDRLB16X.C($)lIlY&r^&qMAKJDq%>:^Oq+%Y.^dTSGGK,rHOV"i-'&T[4/ki + YD + A$iRALV2AQ;N%Y4lm[f%KPL]p%G#!bKX?r-r53!8)$"5lbB?tE(5J-_g.$:)lG@.Y%^,S]3 + L(Eo_)_>A2\$ZU):U6cQ?oKmZ/+/;mdE`tQ"RQdLu0;M*VF';4*bggeIfg^9gc8*Dm5N?Q@@+ptf)<4*pZSP-Ge9GGOIf + ;YeEXa:tqHeQ)YR_=(B\eq-.+8X$Z]#YZGH6'3=).<^]!E'@Lg(==d$R"@5i0"Iii!@+p"X + %I]5Xkp-3!5gGeGq_^!'d\Q9<[8UZ+ssk+F4:.J-mEh$389X^qpV-fFhU0&cleo@CSK%p_[ + G':g6K/]1Yt&!OY!j:j<&n13Y+u$pU*m;8`V=3#I?/*E&7LJdn-"H[esR1UoaNK:@^M]8O3 + =O+Xra+(6i:@@dupr"T4Q%)EP_cnZi=S3,YRT:fdWJ5k&!5Q\_R"2H_q5Y8TG8d)qA"s@F^ + J:InjYlb![gOTnH5l)3=,SHf8'nK[3fQk7LGJHNELR#b;i\;,"OKJ/ + m+GmY]GZV1Puh8MHHX2@K%$EV8PJ^MEtPa61bcJ,T4DM%A>_>JE-rcGRC*?"TWV&+BS^G1' + Di*"535K!([6CBE`oi#QS`Hi1:7jS.7.7'*/:KJULc(dM;jo+8i%Z]W''+gWNE[NYq/DI(t + ]OoupP/N*,s`"OiG,s"dD!QEH775n"\K0??`bdV,p6bMJ#E1=*7gc*h$ecrZ6D)ECai$Vcn?g2fYcWJGHd7j-k + hbg5i3lLALm=WqVXTOsT3.TrMF#,J'qV*absL[B#b2k*j2h)S<*-F0(sUXT!S0JVY1Ps6(M + XkWXdchM^V>L`bT1).&Zo^7"548[NrAlCOp=HV`rH9sn(&[U,,rpKr9is*r2KK?G?lp\_F(e]l:0PKVjMNgT(TGk]FB(!P4gbq:0aI<#-)Du3 + g]qk(4l^UO*t_u(Usqu=k;?d8M@ci%5"rL_H%*Hjg.jC;K=gY_>O\Tuhu\,S\eBmm2<+opV + P[\-Gf*#9+kKgDDoQu"Aq&Vrj6nQ;#Z.2Rt5A!)T>DEf/'8E[l_:spOU!9Dtcs90-1)4jL. + 'reqZ&RcK6q:n,B4hX4Q`^H_$2k?(BJqpj@ND-_JZJo(r9Ch\Wj'nG+qj5=TSEWU]nmIk8q + 3rdV]0$ipAoYTr^MRV2O3"i4#G*sY+dCaN^38,OkF_T#HJ$cA(]3tOdKq3):VL\sj\7'S27 + '?,]"=Cal5OoAl/nYC"?`pqQ"*169'I"03A3Lq;6OrTB*8EMac-(jaBN+U2[N1h[mZD!.f+ + 7d@X/B`)+].cmA&Vt"jo-DR9bk"f_2GFGPL1O*NN8Z^`1T^"QcI:#*@8>;9s$)l?70,^$<$ + `#^cu_kZ7FAJB'P#OTM1\?d"u6-N;\Zo$:/c9-1k1kS/ONCb<@(F>?@27NFl + NuelBil?'hE'H!f1S"c$M&Q2QYkfbRSJ@+dGPH"NCT!WQk;QCY'm>35^^D3oG!F`6% + _rP>T*J!S() + )i'`uc5VW"=Jda0(:]qmJ)c6^EK9)sr^2haR0H?7jTV>rPi[UmD3X0#H2^t/)6R\CB/uZ$S + `ci8H(U`*%Yp]j"f@!KQ6ht!V.lB)S#C&tOoAGu>,JF%lMB)UL'"E@:Q_;uc1AU"5L+?jpI!ANI5aUEFM1IGeTJQK6mt]h + GSem=GGlCd(k'9./QfiZYLSs>3^6bteHN)O&mX$#L\*s4/#KQ@^^CSZ]I31Dsp3hF7ebFR- + O5Kb^^SgPrs$+!cr-s4frI>FL!C->a!+7&WJC#VE^pjsY+B/f%&3^CckU?XBX:p34Adgu80 + *bf3TbIUE!(&Y:$"5Y;N)1,0Z,*-<_Bo,N5]21Q71E)j)J + Z^7FF14B]r`3Zu[igoY$TYpgPKrXbT9I]ViLHlrPWT1I`e[*)8N4OAYr=SB3'%: + :\lHg;-0L`?`WIg<192iWcJSW2*lrH(*(7BZPKSM:AJ^X,:kV;9:fp3G.'aQZFck%702'j=cD.ToYf#H:q4(2tXQ*n(:h + VDCOH#@Kd]=6Ari*8?nEGcN2\aq.%/p5@JpGV5@4/I(;NRtb.@ar7#71-3jhhc&MicG^`LGNr06CC2\&'Xa(n5q_L%`6IhYe'+j*tuqrk;J=ptgr9HETKnN0kT]`gG + /+@t\c[\3C9iN%#cdXEQ9OlO=ZorueQ\?$: + MK2]d#&i21hqi@<'s5+4Xij"r>X2-Jr]*,5Go6BY,$0/;(HrhY:3I'eI*.\;Y?9\olW:G'] + Et/]k:,U#.E+kDP=5eO&;77872$JV%d*m\Z=XGBJG>[LofIM[\=/9oImE27hH=q.-RTQJ`Ne_-*o + IB5H+lZklp4n@fXTDV#EXS3r'O*\#&&[h@^@=,cZu290t)+6.a(O)cj=aS@b@jW'JLEdh*4 + H"SelgrG`P4-+S=#<1fHaqi!5'@1Vf5:Ms=!oBFNJ,2)mknjN!FFKS)H=p%ni?]@$4Z!f1.*,rB"_]>W_Q[&C^nF(:WIDj(`9LXebZ&Q^#jNM)7>Mit]QG + rXJ_.+I!NVk[mnHSk2GeAC`EV\))Gm>Ncr0-5_60cR4m.ni[TeLlf<:h&3@Ecs'kpB/1ob1 + 7XAaII)]ln&4fpaio%,(#\/oR:@e(mdCm5r#THh\9#5A/dNR.MK@csJ.ecjC/TIVt+lRQKb + J$7VcmNg6?MA*muQ0;lPXSQ2,2:f:;3q.Vu@eCpdc/1jjq5Ba-Yo[d4tJ*B\Yn+YE*s'4%g + h>_W`s0_ZI2A?1YqdVN7A?(Ne2Hm/iC^-p##FP*t2Nqhm;R76W$b"`V.r5Z__RHuVE\!iW[ + lb839G4g<]XN4&_+T&))gXej`^,6)X@n#DS/O?obJD'>\255-n]3[WRQ6S!63JIK'I(C&,J + k]O68Tt)A0o*7,sm83_MfkZPUaPG/&If-_R(m]`%Aju0#IfE6PLk-Z^aB<'fjW.](@kke2F + nu"?I5V\fdY,UT$;U42\Ef_lQ#=`'-Q<5,%#<6^sc:F03A9o#"tA0QXP3*(E"4ZFa>J7#SH + ,bI5?q\1mfj0a#!Tj1E7q]Hh8,Z"(L&;o$$Gf.ic3%AUhG`)\/T`NeID`K9;rPZk_X,.tKj + `R?*Fb[.T$?BWMj>762-kes3F$qE,7Arh5^7.X"PNsWllkHMg1s>9c"7O:rn?i'i^E`?b8R + B/:K9dHW2-X(gHhk(,SMcm?^XS+Tj'F2K?V&"Z[Nf5%iI&EqJh!"H^f+e]`!82ABJuBF^nS + +mba+Z=%4bBf2XCs`'"YmcUGjWt[q$.Dg_@m$^d2GH6$mpoPbPYAInE*IS-+=.bbd?N*l6n + ZXVHCZ-+u2b=&b]4LWF4[nC8@bEC^L/2PJT + &qc!%6W?On>e[fI6c4;e`&'JB';<>9_o)[BbZ)MS>f=ioRER5!9I)sKUqo^r7&*^Obg]-NF + @j^K8NT;K9b^:G,YUVue%;VJ]@.HgA%tGPf>TN_782CI/&]DKgH8/31#,pEF!*o`ai1Vm`Q + QF[;^O81?a=`$`Z*;p,LFG'd`,3<`ai^WUXmGDlb/0o4e"9jKA/l6D*?*\^"dNhr,=$Yi4^ + K/a"-5k9QUpujes/C1d$!s%!fRWHG6J-W3bj2e:&gaIo$8u2%0"H'S=(ep..UT5CA13>+-4 + bqT*Ym5K&CCbmM5??-'YY8B_-+liOUEK#nUYWiZlAA9L,hL + :_fVGRB-rT%7XXJ + 5*,/GqC,8!>ZW-BoUYNlCG0_,af$n\87.o.&5YlLqNgr=2ZbO1%1_aUJk[4Hh[_' + Zj9g5c\ZY`0\T)%^u%l;5=ah";3X^*D,<+;=u!3%%u#h+]:1bXBZZHU<-;#h?O?)61cVqQ9lg23hCKrS=AWh+=Ddu6*3SA!F;YKZ)ue'1 + A3?*_rH7+;(U;tWH+27B^mZs/6l,4.j(V6a-C79d^S$EZnSjfd1I^WckfeC$M8,u2]B_c6Q + `<=$cJ)njcZ;R((<49\_I)];A`=#%YL4Cm;;Q!;N:hpp68>np[.eR)'O@+HXRZB\qFMpp6M + eCJ/D::8c]/F>p.CY#QA?.AC6m;5WUoN(="?5#LuD/ha=FhVF-^3k1OV#+4fG1U8P=,lIO` + HjB6He5WUcn0[R4U#0b#$:bR^QjCAHWjN.MHE9(d*)LBAVWh3O&5e35YVhU>A%Qrsn!aeNh&X:X&ASQS[O;<8ik%3W-]U,UVj;E^Js9Vtt**Eh3h;MD/ZjfDY"&)c + je@.k/j-5#2p&Re-AiD('FeRcs0],TPPim$mRV-VDWjBPt*d&!Se4P]#'D.$+U@Es#HjARYdHtL&u8)9GC` + glD@j##>gn(+l8f#?KA/)Sq=EE<#1'+Ns_,kp.`pt%Cbc_?+pd"I;7:J\'V+)TSZCk;i*@G+*X>hr,18(N@&==iZ[osSSO)k#XSs+#jqqb!)JB>)kSAY + ]-@k;4J`o"FkXL9i5)/9%LM1:Tb75J"]'rA<*S_,G@br0ijM#*C/%OdlgU<7(VNO>"-d80G + Bi^D]As"6cOCAXBkjBO,VCq,AN6Mf*eBTf5j1!f=TAr%1@nr`BG,!M=a^^%IUuCUG?"=[V]]o^ + h/JiT1JhhDS9PA`R0(<8M-Kj_a24Aa%IrGYe$9hR6W'2=+-[m8qg?hX4`A:%2a(JS#s3jt- + sQ4'PP"N94CUm1Wo1KEO2.C/HS[k.Cs:h%d_r(?XZd;20#2SoJ-i)s9)C?rhDeiN6L)Qsb= + d@#fJ>S>>>>HJ-:HB>l7]e>_YlkNIZaka(FMT':-ilgD4Jm]PsF`pR`qmdCAKmhY@O^5?U( + mqBG)iqTnFT(_C&#\f=ol!B]pVYT\hQLDQakq8?:rC?=*RW!BBn.t[N^+d0:T&V+mC(CKm4 + iZMfa7+W-C.2+P?OH(13bie>c/,Dgi`0`k&!!eXC:Nu_2:CdH6JXgmlLWEa?Ei`B7r'tnh4 + @2DY"I99gi:ORn`Fe<*spK2A^@pBNH> + V/\>7qR]4TOUK*C)7+i2[I%p2g32$E3.d_a)9F_%IhsZguf5Rri5#Y&\Q-sj;l]M%FfB!GO + $&i]dC%)N9]_&luB2:EOJNR[iK#:J!*kT?3\%'(P:u(^2c)OC^.[d?GZ2E=DTW=:Zj0C]>RKOkoX/W\%[4-7JiFeop<.28&Aa0A + RcImp2j#PQNfOTC./qRr0M3gSu$D7VY9`mpBSt;(NFXGWc8hrlGn$kA'X-nYOPe,EGsCQQ: + B\kHYZ.(jJRE,Q[V*3\8gDV4m.B,ru^RQ+o"KGjZb#8B-gl,Fng\n?C&P#O"+_[HZHjAA`X + $big4ompj!U*^U6bQ2s-uQF-HrIFVXMUs)ZF%oA(;+Y1q(-TP`: + HqAkjgrnuI[J1HoRp_4>G"b]2#:\AJ6)%MD=i_^Pg3EWA4`T&\/e93hoJ#_%>)'"N*-^./5 + C8Fl.=Y?#?jtf"+dUb;+:$5GhjQ:eNT29283W0_I7\WcE<17ABSP,m0C)&dpF=Bd6a`U`\M^gHS4lm;rFK>V/<(q`$k'o + p:DnlZs06S4MFj`;^c!&n,qk4a;=i,6 + gfA33f(s5S,Zr0Ff6RfDRCH/1Z>;l/%2Fo,H^k@_E]J$n&1+!6E_kOX"I^;!1Eea<-Yra,& + ka6Vu/G^h;phtQdC^Q8Qr!5O[9%08a+//&-%:i%!S_/P>=qneE$/G[0PX>(1$Y66uV`")E. + &&`GqY2mP3>ZBfRO[6S>7HU[])9*8iAK++$]QG$ZL>i.Ra78eQ+imP4K-SI#7#`J5#+U)h+ + 7b)]-ctN+UFR7'`3h,=P0CNiRSpN0J';sb`,/236)?G5P>'dic1=5qCnL`Q28YJOLFB'2KTB:F&8CDUF.8/+=bpuN;'UbJ,+8#M!:fLFH8Q4,]9he + TT[09``e`LSH=B9R0Bil:=`H5_D-=qO2TCql0?s$G]M-QH\RK$gNG#arCg").PC"\5IOLLE82d7]lP:i7FcJ8QV!lp?3X-\A6sJZraZo@^%6VY=7,g4)#);WJ)Fp! + SUt8E2A(Z%=Ab'1iTL1a@i;eB.uIfgc7t7pVIBLbj7r=6OlTXt6@YAW?;Zu!3F6!Wjr%cAJ + !ahKBq&_FTp)$[L)tQ2B8.1GTca+SKOec-7ZXA0RU2:fko,-QWG7l^U1P`fJr`^1F^U*9Qjj1R-QJVARn!:]^b(#_R + a3jR.5@<7?QCh7+8c%f&.S_*iG/Ie5dGq0."B%R*#eH# + `B:,5N&mcK(*@^6+B$A`IP7I&;h%93)']f6m71UZY_B=((rJ=L6:`pnLCiC$OY + S'jiUIO*`,6be3/1%^5p_@b,a_!KCD4sg;)WDh-q-\''k5*_Mc!Y17@:\&iq-Z<&WSNa=N) + /Q,\N*6VD^o"h&>=$D2o4R3Cd33*TNr?NmZuQ8%AL=U]c+-1&aI0=U7la\[a[T/k-f\i#PQ + B0h#cP(m=[ad9,c?o*VH-q4r`Xd03`:J4hL/YqaB + 40haj8Ru"bAcarZ8FBo]7$s/"6;!YE*bEQ[VNauSd$dOPC*gEjQfc.k9Z`DO:"82^dtH[SZ + \^qB<<"u-jQKC>)KmN#fVB3tnX^E=:%%SM$pdDa8?='Fja$_;LF-/uV)UA%u0mZoNk>/1si0joqHK[W + s>9phC]VbCf_e(T&gfd/moeHV\Q(1,5M2+_@Qk,'qhIoG%f:h\)_TsUi$dqZKpC2I.Ve^g\ + MWrm!NQ&2hJ2Se*PHZH5GXh308o&"58J?CLB_72E=?/0:AEfN`4=4]*4grl=o:5Zm!2bRT8 + MlsSTA4fd=orul9e4H%EMd9]- + K!Mh:R8h@PK"<0bPa<=I0X,GqY9-%ea5@h')6(n@eS9qHl>OI':N^^Gk$J?ebC+n(n)[fF% + [e4:Qj\7KgP0*sLSm)2%F%&](tQ#[`u5,B1X?On(tNiRrQ(E<4:M;WeZcN(o,hoWlG6`th< + 1=,X>$0?.,6;dt,IMD<+0a%2e/@JkO-AgTgb)%@M.b`+Au4O(u>2JC>4SMd^+fuc*WD<4aC + 3%#bMS+E[5:7T!XPN;+-ZatHY,orE:^1j3"Y'_[DrL(k#_)Q'ieUT<)QZ)Wd"=g[ItXdWg)f2Zgm[@M_KX"M5iNLmLn>1KmI`lY?j`P66Ck?V=EG-< + u&^$T#%@!9GVF;o,7fJcO6ls$[K>DC*YD9VE!V30RlB$7iXfk2@ST6f(?crrB9C%oO*I!2L + ]>.\(ThlX,4>RgVGbA@2qbQZr`p&o?U+)lIY_h),gjDeCh#OZ4B\bp4,6OU)?@C.o(;!RGc + S%TAY4lbWlK(DCU>C&kXlGAo]$M.SlUMi*f8;@2bE5H0;N^8Q + .(&7:[24IWc]A_:%5k7lUM(;aUElYZ6hlX]oN_7DU"2g5!/-:G8]ENh:m:21mC/$\otg + 9r2m$RgWGM^*ldH4!h=YIVPR@08EDGA9&0.A3kXZ&@oA]n#9b%7ZdQorEU:>(QN-lgFno1! + Q@=gueXCuLnt3igPj&(XQgDaA&,Yub*!L\kFm6tiL/csH$)BRL8_..>!IoS_^Bhht'T7ZaG + \Y=gPI.`#hbRAU!YLM?-=W2]Y6eCp*be68"/oj?Y-[5+[r/RMKMVs2eTY,ps8h,gWe^"=87 + q:9tTrTda&XkP@->(BA4Bg@,NSMZ@f-[+hVeF=k:MmDuQ:,'@bJG4!f]kBbsG1 + u63A"0LS>NTPQ3m'KDZhbZpmcqrW%rdD"Qi(aE`NX"FgADp1'iCeQL.tn3\,.Od,\M/EA%Q + OeFT"8TCj3C!Ok[`anPe2jg$*I)KbJaf:MWKcB$EdV[DIX&,XUB$E$]_Cun@]5a]*rYp%$8 + F:YgEmKeI9gX%5?NGE8;!Xi=9ha%^>%)d-2&+i9N#)@Z!9b!TJFF'HlnJpe0)"XllL,5 + M&TJg'Yos^A,n]Yp&htgJluMPl&JK-j'lUtq'o(d8lZiaSGB@[qC61TNAt?]EahK\q[Glin$sJm[U[Q\*j<@jYK!Qd@/R-Aeu + `j4lEI>8hUbQ_CQ4#HXb>@?'`S^;fkTUlk5s?B%'L3Jg5lfuckm!U[o/;F%M6/DS + LuZ=eXhNS>a0/D^an">/]kn4*WKo*$`A"gD&3b@5r6FTs@3#-J`NDArgU+"2)Qip_FGbEh1 + /Pe%,n#`GMc&<>h7V$V+5.]^Am:0.dAY:FD9$W-@DnF[;I`@MYB/2Gfsme8E?4YY!7'JW67;aAH#&JXb5nZc4RbbGop]^i==22TEjZ"GsDeFTc^2R.ND:I,fEK/ + [^C(p?dqao*(A#oPI\p/.4Nd@WERUDf*s)K0s@c[e,3ZI.(0q!B.C'`";5%tVm`sqB&=59hpQ@0no3NMG6Vpq + -h(gu0Q?#,KRs[\Js)0an:<$-KS7dD\oOJ)&ud@@:IoGq.l+El[#g,^@4q2?03"DoFIu/^S + P"!t8sH/E['<6q[7\rZ9/O7RFM1@)_+\sc9VU]'ZMa$+].c$*9sZcQo/.3Q&Mk"X134U6o^ + ,F1.o0.66o=.(o.r1ej%rF-1c%9koaFC?7o=8+;6s*i1KRI1>8B#":/Pjdj0I3tI`@F1!"40c]f + $=KOY*Q'!*[PruD#4O:](;==?OEE[G?r5O9c'sSkpk1=VVrY+`CcfmZ4kkB/Y$Jp3gU4HY@SRBU4?c[ZP7sPVL\89e8.52%`QkUJ;O1C6[UNG-NbI=\k + ?o:NjEI[_YcU[;86:C:+:]ac0.UV/98.;/oE)1;JEe[9TY]3C0->P5mXne"+?$kQKrFr.>`%U2Ai!'D`8Ii3H-i:;ADQq5p,G6^nt&Rp!:kq7W:Y]:=/_S=.?d\^?rCeERl@KS^J^BqL2@;hu=SLNm$YRI3PsAVV/ + tLH57(q>$[9q4?:HT,:Y$=m;V>I>E1eDE8d-ZW0m=nVNM]Dj[/8QMi_f4c5[f;7Ng)nUZUB + %oeq.)&U+gG9)Wd+SN=iV5c0BokdbH1AH+K3nHLi=$[gpFZYkBNu(SS:Toh!hiG@*Fn]C-1 + YGs"$':FW+5%5K3>HaY$J4siWjsFUk1c&ae#RaG,$?X+Z7B5o09`a`XLG(3(%jmm.btdl!r + 5h`<&m>A>F"mn[2EPAQ5`7CAX+Ij"[ukNQWd,\2#0i;eql<[$X0pPbfe]^42dQ:%POKH9Ra]218!LU>?hmX=q,\lRjd + "=_Hu`eG]("Vl;gheiYNp,22rT2\JR`t\Yka:!nsb+CIBdRmZ.JrBglVp_-V4+dA_F,l]2u + B[V91p"arIP+I':]*Z-.O&bIk1)r41#k]]e*W[KCq3(lrP3eMNb4\GY1-Um&;NjgZ;p\:!V + fI-iWCiOI\;c.eGU]:%#-SV]BQb.9%Km'jR;0/a_)MKM(o>!6ho\Mma/UiV1$>KMgCc&>cH + V0-3IHe.pqgV*PW^+m6Zpu9_Km3(W2gN6nX + `urRrt"d-pPl5:+-QOV?+`%$1,Hhr4'qhRP^:s"\$`@4]<4%"Y'aH>1e5P(o6gIKYE3C`<[ + l'/Fg-)Oa1^;=gC]g]6cbQbaPH7*197]Ra+4%=ao''6r5[_X`Sc1Gi1:,1^(K<&(#[pdiBA + 4>IN;&9bKS?5&3,`]][s\Vqr<)"j0;2%*^S*N7F*bKj.,#ehG!G<1Z`[Nir'$XgRj=+-g#J + QdI+'Ou1?$iRK(O'm)WcRdK*oYb_YJQ\Ws8'F^\M`('/(uqWH9"?WH + IZAkfp)?h`>[]C<'-AR%0W2,@9I5OQcsn!b=,V3`nf*lgBE$f$\!JMYqA&nGP8s(D'jD>.CWpO@Q4r[6]XFj1kppp3]VSi,UOQ/q + t5jZf+-hjaHAV>6utXo*Juk<=1SCsCCWJ%OQirOPLp]>no + [;2d](e&?n&^6r#nT8d0NNLlpg%2\mg"U'Cm=qEYd-5Qu!6_Y+b6Tue1M8`s:GgoTO*03>: + m-+Y16R.ddAJ-=HW`]:21M,#AI)]kVRB]nUq8DQ>4.f;^'?V$VLs(:qB_6%./sT/,e(AK7r + C3\_@BsWjMS3W4>HP-1('jepKZLFOXLG(WtQ+l+q\DOHG\*E[(:ZdV0Srh^\.IEG.E70g93 + E8[KHPP]\W%$)tj4%L:cEI\GV]n,F?R8F80O9oDbZ:l)n+$:=!FXhX8p+'a?tnEG5Tn8,Oc=(@\C[ + $c8aI3.I65lp20#0dK7"QE`PM=[`p*rt`TDA6<#(d@"HHTCK:.)oj\oG99=Fq-[>r1)tul8 + FeU$+dM/Gs!C=cBjOn5d[[-`9(K5l=.f'5\Ll"2"MZZ5j_8!U9[bQ=2,,l;bZ5I6r80KZ'9 + qfD:?VFR4B^dWDlQ9N-&H4&&Z'jZc + UA2'kOe<-:>s<)n'@>?'/GARSY@25Oqi<`R+S>h8SuHX9eHh1K':.2DRD-GNPZYnN@ott`RG5]U(>h::JF^6q;d%VZkpr]cM*#+eDM+8INdP + N=f:=-mK$L0L&+L?;@%RX)VgRqI#dH(ATFVJDisqAtQgB_j'.+qNe1^CA + OO.X(tnY\$'SZ#._!Sq]Z0/`2J%'(FL1Ro,qjqH4DRl'%]jr)=8&&t3ejI6A>TX + ?iE;V'q_f7eO$tu+Xru]>Q*1&1uEr;OcbB$q/2pId2LHp@]>pi"dRiNSIBQ[Tn;qH/?Ip-K + RkPo2Kp78[6gin1]Heio%kA4cLa8^qSpmsF.jEZ2=S)nqP+e.]lBE+gnqBiDHm$YTF[VC-& + q4$mOBDItar800RomK&;dJ&'=H2_BDJ!]EqrkHMBrI3YeoD/lhUd*c?!e;s5AIp7#!2)^fJ + P\6-OD#b&@$(\GO?eP^W%^T[OV&gKA;`bK4@fbR*J?hNP!XCe8VpS&-)B1]6Lnn#$+W>LjU + N1rEh62Qa\IUfkc.#@XAt/-6S#DQZ5+:=>0B<,M+,@o7-L:Kie_o;L(qHm@gNn0\W/F"E\A + :40,RBfSW&;Nbo.6dB]DCXNJ=/d)P^hZ%@hI!L>^fVF+S>*e9O$o&jIB=Ril5EK/tAd6QP3 + r,%O")4Rt2uaOA.UUlk.1kL)tp'CpV5dK,Pf!`'pa&qQ-"%?uZ8;P@VJK[A5L6c.4`U+'M$ + d0_9;EmY_ul#)G+_DIU8ocQolJBPL?h]9!^B*VA-^#Aq\N*WNA!OH*!Oud + WV*oiAi;4!2!m6"i[pTi4ZG>N#H6DtV/UJtEZ;&_APWKc8;2]?C(C6^TTCqc1 + _lEK1d4dM'\<3(<`<`,* + gW6jlF&0H`\'Xc.e-gFN?oJ]m+$!Q&N_/o0uj6)P_[s20$>ihY?7SYE?@SpXVLbB`!6RFo. + 8%nE(`r!-R@"gq](&84`Xj,EI9]P"45!@/2L+0TlO%nodi%,S;Hd2B+td9PLelU%?B.dcn]pNj.A'>gS0kO\D + !@M^ep5T0j$+(N77jj%K;B!&YZ99m95;2!W\m^FB\aV+l>8Q0H;:;9I4nK?`gbF\G>.8pUS + W'uUXG+Y86&qmU\.*8X#_]DBRO;j[?cVf24n)RST.gje"Y[OLZIq]3+]Q'L*NG0/FL1B2P&i3TX$F':Xk\p$T?h3OcBg8HY/\!=+ + L2T1P@:%8MJ7CD$=iYCqWA\`0-^u1OVT\MV_EFJK*\FBnMnFF>8DJddXV7,[ZGbSJ]_0W_[ + C@9'Q^W:=%>eGHM1>[9#H=K\:jd3:J7pFtiq36-=*7d9C??(I?e:d!e"RAUk$R5(($KuLup + @NV`dU]_Ql%ej;o_^@sHBJd'P*`3-rih*Q@N371Nt]\f9orp? + ZoWEURt:q$Xf/,fc%Ec[*]3GJ?krT9En+I`a,b4m2b?iVZc6nX^uGGeY?\I#!pV?<>5DIjs + MB\!ciZDpN^460EY,TCcHVdX$C5l?tFlcoM]h0qdACoJX&,3n%.,I9ES`eu/7gVc%7]9CRB + 45gu0S:OHN2g&<).q4A"`qp1t9IZ&L%hW*nOYANu]YM4$mhd,Q?^%bjP^=Tsj@K(\/>rG4= + f`/Cnm*21#ItPbp5Lo+;Dt19E`noI9;uRkCEOQV_2[[bf/-@IKF[/<;XZQe1Z4&36W9nROYJ`Nk + ro"MrO4ll]F1W0s<*kY4nnj9na!&,BE%dTjn8k, + VG"u(-R&%e^7gScg2!\hFn]!O@,H;lrKXcUp?](<-eq%UF[Z+DftOcj$*K,MT()S:>86l@5 + ]M]Y>JB/1Gq43JkeCV?U_^;Pjj4:Fh;K.04#e\oY\7+9[SgeU?):djAA7/Tp$$rTMV;ajAX + `EZA2A5u'7=@IU?7XQ=r,Y@Ymlr#*^FSrWS9I1a]0CB12GAE;qt + [[BHbYL92OjTF>AC6LrQ#BGa;b/ooE%8dK>8H//GjN[C]kl-G'*jP=cdY8h;2ICno-Q(aH\j6@>\k'WB0E._4h3b.F=okj6IB+?Don_]&kK,72O,$;sPS\ + sj.%6oMr0XYCNu_kRleCZm]u1On`%rGhrA.+^1_SQV;d;je2)e2r_: + +@^4=nLs76=4,4AAgAE;8"#n^= + 8F2Dh/I+i]P>I.p:o'>c7$Y9(QWK+^:scG_Q$Z*9HnMgT,--c+lL%7:J0P';=XqhF=9J/.8e'@,Rg_-'Wa>003HC$9'T#H/WVTC9tGHn9-cAmCpTs"%oS@p-#G/"Zo + h7)\N;Z$4&!97oK^k70[h=(9Dp]k$q'6 + iKNN1^'X'3cL>2?75mB'RbXsh7\<1?2'X]e_hF&^\thi%Z[O#;2mDh+b@8A1Y:GlY.7:[rI`n\f4?H + q4IS>87cPl/Ss6*M9kgO^ls%?*/k0:E6p0>?(@"colJK&FPY$.f4B,,Hkk#IGtF5;;!iVVq + htoJ&D-Re0("2:4-E\i0\A1G%.tAr>bk5DQsRr0_J8pEXm[pk5M2MHfWC(ZoW@"4U"JG0hd + !D='Jrr=-8=PFb>^GU'KP'a.c8D_bDe)VNLAJ&!%%<.6m?'meA&9]T$.p-=p)OgHb`RFUJ+ + n0>7M!E"q+W6$t-4Hi4]Y.mLEY(%H.d-@29D_*YVs7'O`91!u1tU!3_-A<(X!PO>XgUu + Q^"\8]gXPB?Sf]D9*b#U"iYfMi]t\#69js='iLV?cc!+F7o3>:$5=NWD"tH2ZaPtIda48>6 + 4_2V3bthor\MHsbNh0(hU/.::1"Gn^Nct&:!l5A2R#Zk7F= + )L=[p#+mmM-^o,32PY:bn._1jk`hK?>Q6+($O:%KAO!OhS"VFA!kZ/ma7am=H'7k/b.6EMd + J95dLGhH'd6I#YYmXY9,jhQ/LOnjqH6fIho1pTKe)'K?op^mi!l/S('2?(*P7T-`(R`#9f6 + )aBG4C>i,L_aMnmf=']D4jB%Cj3c>R=ODs$J,B-)&*/o]u;Eb@j_B23V_>[(WK(9&E84d"- + $7RkMCVkDB3Bh+_V'(BF]VN<[;?BIV;'NkIr;,AP6K]Lglr)>YZg)DMoP&a_8sO + <\/]5m!i9RA^=[;ko4Qs%Q!?+qI<67@qF+i`g6s"eNf)o?#=N?Xk:u`EQIh-A+eVf%\HR`6 + /CW0\8C-a6\j9j7EMDrd0#BhN=h*r8l7Fjjj62Ac5bKr_h%T?i;Y?/nW>@An@TN.V`RU"m2B\dtG + \6:2FQ\F_m)ZCn>ieQHJQkbA>JJCu3A%m`fSAc>6>qj-:pg[&DYmsE%GiG.?9\@'D + 11\n!,@MV$qC;jLNlY4-?7h + Z!lI*`D;W$Yqq,p)^VQK;A#*&+KS.E>&f0KXJDIr,FlcV4SK*MHo[h@UKEX;\fIO)nPAp*" + q]\Arai6-Qm5I`cAP2Ej'Y,QZi+1GB]*mGFs*RYBb?$H?[,PGKpona"3U\eFTSh*EP7G.^s + J;KQDp%X"Q;N=,2$=L`CVmGb6p>XsJ.]K6SJ!ptU,^7uq`:jn%Yt>aIZ[[ZC5J0\k8Hm^.T + <5F5Xk2/-Del#&Fu#4_.'3H$9;:aV[QhXoDHouS#D5njte"ISn7g`B5u58Hs[rbk%dn]a7nJ=I%SN.YC:ksbP\inI,E1rn' + (_0c@#rDI0jlepOAR&JFg71/!l4nDi8k$fa,PNIeTgPEIN# + n@hrA1tP4"Bpek_P&+-qA'pA'2+Bht,P-LZ5P8,9U`FHpcMInJb/fm3E.oZMc,h/ms#FS\G + _n"uiJ^Ja,g;sI'fHAnA/DlWJS!WUHEd7*8<2d-IhrHcA45p1+n'I"7)KYns+a@no"&rK(d + //XmcGoDVofFRcLJj1EA8D!D=3pobSQ-3P*aaC[.1RB^qchXs-DRj*!-aUqf#(X3i5Nn>H/ + KU0KVG\N%H7_IU;-hZ9X*VHkXT)KWX-Mu;EH>Tp/Za,2,1aeLJ(ka>Mf$>gEOoE+QOCG)o! + DWXYID^'gB3(2i39E_4speo>2h9;4DN%aPCMZ!\OD\1/"4\segilVK0Q'XB_:!^K$"%le`s + Q2rH?4bs&Z:FRr,=U@Ck;GpNu;e\R,P7j!uqm<@XX?hg`1#lm6;*"N+e&6oqS_(^6S + #XQgr.1*lOR(r`bM@2_U4XDo3";7(Tga + 0FEJ+NP08Ij2s31l3I!;Gi=&%@i?.WB>G2=$< + S805@Xm^UD(Z"b5h`(N0J7$q=$%!Q"2@tArpf8#>R^cB?LLM*l,\EL4<\KA@(tk=8j/;l@O + Q5"86'>b]/l@hmk#nR2jdC9rQf_G2Bp`McSo:'c7Zm_AkIK`'TUIn/L1P&05)H*!g.@L%l) + 4NQW9N$-TrJtWL7t\`qTcT]CDuaCY\ZdX_m1.!2USht><^PHm$i"p\8FcZj16*)[dgrt>Ij + `CDO^@s^[L<&qna3*2\Eps>WMdmR@J1;a)W/l--Gd@^F!D-BE'j7F+!FBch(K:8^<]8;?,, + <4\&I[R6=>Qf6/1,A_@o+Ir9c14kYtRGGZ1+i:l(GKAQC$s,Z79hHu@Sq7_-)@it]2Vs/N! + ^THl!^>GkrR&Bttn9b0Gamo;d(XQM+L]>ouI:_sUpjJcql1t>"5Oa:K^Y0M'+iI?:O_=gsa + OqG]`0?BLQk2gq9FW=&4ISP;A5,YN'duKL$peoc;Ta)$K>f[H65$4tb/>PU7>;Kl!m_4s/1 + `BlXL4BCA6iW3HS5Q\^Vc6>/IW=U0a96P`"NhpU*5URnJo_?P;$Vn@APeX4src4?u4hm///s4k(_!7JOT>:79[lZ(19RaOM2=8aAMeRQ*e.d!Q@UG)j\q4=>sV*TPXtO-0E + )a;FjuU]c=3dM>5A8JNE0>`"T+5"A0^)^OKtP`epF9M'Le-)BUiPHo(%8f9oCjSYj=;OUKU + GuZ>*A>i3okrpLeALu=LL/A?'GtXD>\E2`0Qck+k9?"Fqf$)'493#\"KrHh>ABUCi.VMN/U + GM%seXDBGD9Q.j2J1sJQh'iXc2#;g-,T2Be!>)KWRAF].fu9j[WjJdjZAi7ojOpZLsMA95+ + 'ju@l+(D126s7BH&FoZjiO%Q>8gTbme:p"e\^.IaXZP9;9p9UbsL"7d/ACC!CCS2:fN5gC5 + EFPb#20L=Y,D@r\?Ic+UTPq+TPdJ>UTu`OM6P:?g5\Em@P+;u210*=J]cFV^cPG;ebmA#si + 24k`m@C@3RH\p"Yki&j',nAW)-u4pf%LF_&V95dJaKbJDVr#OL!3KLJWL= + r9m=@7ag[[[a]?LgF:oN&7r]g)HBWE*o`C5.8i?+kr1u&PU0WM$?ErRF=qGDB6WPd88M$Gd + ;2GmEITq!\k\,UDP4%eVr*`24bZ"-Z$CK<\;6gG7ijQ\VK>X28e)"8)^H!Npt=Va&B]tcQ%Ltne2%q + bVLMTH0[[M@hbRLq2MYZ%7)bH;p*T2LJYXkQ?)'tU/XI9d<[7/o+Ocle8_O$RQ3oBgKSJnR + ZgX>j\"9E0;\29N + D$K=1%#-XL4(r==a + 5$\7F<#5GnY4jYr>uo!N+%XLAguunQ)D1ubNN\nk4C5nWELguk`$PFlBe?q3``;-R^o9nAQdKnDQl8%61!HNB_L/@gZj9 + 2WO4U)eWn<[CA71uX4;[M^DgDTOtQjDk.\)b5/mI@L%P'^-eR!HS_9(3#52o)fpb?Am5m`C + b6R.9gLer%mC$6.#OO.*gMuXZrssUT6JY]*G/S7VrpC!\\4u2f$]'ZJNrY1/he9C-c2NYPV + #cYnN:%&SmLJ2N&<3h4N_UM*8Ef3]B*3$e"->_LcpIo>7g'Ap"Ah_p&5NI+9a-VW"gD&3cs + m7kB*Id?##K.@ODbA!E9C&D:[WJk%_h85K'3]#lS+;)%^>0BWp3EJlB%JB&>';+SI1IP#uI + f2n?ADoKaX!!#]RNEY/^rI_[J0Cm4ciQM>%HK.dcSqb"h%.%d<=mYUUc4:V)mi)j4kjdqD&^Ma9%.\=t.]3Vio'HAi$@MH+2"RgTd;MAd#>"K% + RaOu*dLT1i%5RTp,21o[[P-SZlY?EtI()onVQ5P&irh,=7V<6I[A1<#%^K)+.(N + 5nlO4'LJCOd4($<.Is"O18=1LX$r2MTb=aCoO=MW\OMau&+[UL1;ornb7N&58(-!$D]J717Z'/0`&$4FNO#e^F2_DDkYLab4h)H"jgp; + bV;iEFcff:o?3Ja.GF.WGRUJm0F+"d:LW4b8-7_4`^4+j@kncn&o_c;NS"&$[!oD)Ee@oef + pc.AN^5p0#/YHi5kIK5'GEj!VaGU-6`G&loIE9D + [p)kE$[&FPorVI5)-So@=r#?m'M1]W:CT#275`rE'O*';k:3dD7TIEnq>uj%-@"/o`H6(*[a_T43jA0:"6^oVQctdZ7R3`H%6]En/3N + [kVpt1NSmAF4W8Wf->B=+[f:&FOHXa[FmuFh*olYFg^OB(Lpe6D+9f`7e^gP'Qme1UIU@U/ + G9ND((SWp+%UPm/iFeC2C1!,.nS6[@hbT`2DhD^#>3LMoi=E.2G%G&9,9EaAI?mtGJ^L;FK + W""Ai/0senIkm5h@jSB2n0K&#j3DMbe"DBN5(HP@FG*O&2.-;&5NN0@r.J(B*iS;:C56PDa + ul.37%[CNVFb"&WkO-Fo-1!E6*^2U4:):BD80PgGZ0:J(lO51O'J#CL_u\=16l-Fdg[&F + F]IBhgMq;MN2esqoqi$KYGB\7TW^TGPJRK@K/X2#BoSm:ZCIo0'H!'N\Q^N-op + L>u[H?MA[Q9R(1[uc_T9:6;+)&#\F]WH?[HrDP,f?KI[d5-o;B"-Gg(3')VlXt>+fJ\/B&Y + W!em5<:nPgoeJeraY_rD<7kC"%KEn]B4`.\p!qHk%phcI%CRUW1R^o>bfs@.c\!m-)Rp!FoRD57p_jm.2,a`_0AI"!a%s70 + #ME%6");^poQ_'S0T%KI')=*nGH(J-3T@r7^=K.k#;ahT2FdTF>ppYD"(=OTdK$Lq6QGW$9g[uHDMlLNTr8TlIQ\FT7CToYP8O4UA.(T_hK$VOR.e!.Sr) + &IYla"j&)Ou1Q\;0r#*.t(CSB%PbgF%;FTTmf!fIWY6r'`/emo9Bu#J!CC"n[j;bFb!V);4 + %f->/K(jD/-I)QSg=NDuC_;H``:pQoa+cF[(KI'n\N/K6Ge5G2%ZB>'3C[7 + %0r`@>C'I]O;+m[>%_AM[uhB:V\:)KGo1j1+(]bJAhu6+RZ!@'f98&QPBYn@ffadgk-A,YP + ah-AQA_crH?`4SUYgf)03=2b#@`EZ]fS6s9@@R+-HZESgDg'tI0/%'^][/>E]4G1 + $ZkHnKP[$N_mI"BC%Kj>T%a1QVRqdFVoC!)oWb+!7Aqf$-A`:%jI\!qkk>ChbqLQ.\WNOt6 + [H]sJ+rM5pRNuCsB)kjibU3(a_]):ur)J)Q&B_@g299d2h>m%`cHdJ'_I$64H>LBBreCN;- + 1cO5ISKfue5H46O^Z6ffbt\SY=`mU.]lN`oQt*JD + m&hVmX4M?+bO>);WD?a2smqaaMLjS^H6Ke(pSG?i6Ifd:`!4eD$qc$'SPX^)9!MS*=S!04juqTBqBifKH(u*N@bAfBkcTm9\Q8* + oHBlh=sOks#1hH/gBKtTFoq;!(=eoBjKR:i+6^7?nR4Yk\sJbt]7^OD$)IZ;, + aPNdKil&EgLfFsSBO27Am_:mH)eMHM2-\C3`\fSI_S2DEQ`P]:_s2"d9<@XY<8/qM/"GFW8 + :tT;B(D2EjU(KAl<\Y;J7H8BTjF068Nl^8o8JIIf-^@9"P*p-h>qq^?jmuW>g1`L::1ocIo + ^72lTMXiO=q)O08>B'sPe(ueThVK7l;_#a?Dgr,0UB_@B\@"[[PdMYAVV?me7[Vf)'a-B!o$o:*1:7a5q1ZdY[s68^a]>.n_XkT.S6`7(Ftodf + d]G+6G\r1X+<9f/";nCu/[Hdu<6G:8doI2bt+"o0,q3D#HU9e,9G8cu_#A@[WHL'h$GkFW3 + kKPa*=^R"ecD92cP<1JUkjoefQE[/l"n+8$pS;UjhHP>bJH?!CE2op&Wf1WsIF^>n + bh0iV]p"=t+fVe\)A(]@$Q=JS@BnL'OV?)X*g30TtCKi^9[qja,Z$p/SpBANTgikX!FBk/4 + `a9R-1V#'Aso'-?KR[$ELgaQk'1,IL]2q"kZ + TCqD%8>3n=Bs0\/h`n@iggP4 + n%(7Td>J+!Q7DnPP.hXXli*"co#Q@(=qJ+25h&6Y=3:E@HIjd."0nm&(GbAp%L29ls`RK(T + T*]Fo)0Xpe+Op!EXp/Fb\,u4IXeigr-lE,n + ,7aNj8XUJ"ogh9!VTh,t$Vl'-_ + CBM6TAp`>GQgiimb@0`!?>Lp\%.jBmgbL+c"E_&22h;N28h1rX^i)e3DsNH)rs7k!=9j0Wr + i&^ig4GkW7hjD'X:"uF=Kb9:CAA<69W4N+an&`dJ`OjZ]g_g*[@A@+=[&lqFk8Mql+]UB?S + pe8gJ7jt2ZVj,W,9Zd*5.:mV"Q1Ck:8h"B^V51SddchFm+\#YL2]jpmN0YDlW#G`Eier2ZW=Zb>:WNi + %YA<&="<67CUJ)`29raf$KGIrc$lkHlFb`qk?mR(E3T$O_,j8=@r+Du%H5IN=Xt_-lue/je + lL$u90$TONKCL*^E#4%Os/tL;"(H1U8];urhA7Ms&&B+X%78VdI<,/iH8'8Cm9MfBLT`WB[,L4!Ql0qKf6(uH?D5g1+! + c995Cq]4^%K9:0Y8&s9_1ZU*TN3[=oak904ANZTMEgBbKR9qOWS@Oq@p05`9G;*Vk`L1cWB + ?Zd=1M301Re(];c5F)Nk5=>3F/F0,H078%XRVnRp3k:*o0I$f#G?/cLX=a&6Kl)pUGQ#2dM + b>AB^I7EFKUhUg1O)n]e?:*et1?ZIo0r$8Z4L)UX].,<,0*HX*%1@eXFBWCE7,josV0X)V8 + 6[I=XELN.2 + paglURkM7)TF`+nG]6AGehAaL\Da!?uGII8')s;Wf^%oEepAa%j#D$hX$,LjF#N6XMKlFaD + _g$A9iZ(IFEBYhjGdeLL*+t7N^,a5YpE/H9LQQU@8]c6:-fl>GQ#aTAbB\:bjrDF0F$=<_H + +,`q*9Wl6^3S%MpHRj^#GH6'M:$W.8*M$AV0'G>ds?46l5`BoFZueTHFHuA*G;Ks^:DjApL + !8-LTu"dak;#"BC-_;[I(,I6*bW`C^H(J)pRh(!LXCE48a1X^VtD+/eU"u5l[Q +q 0 0 409 407 rectclip +% Fallback Image: x=1, y=214, w=404, h=50 res=300dpi size=1055868 +[ 0.24 0 0 0.24 1 142.726261 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1684 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J,g]g3$]7K#D>EP:q1$o*=mro@So+\<\5,H7Uo<*jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/mioWjnAY&^gM+`4=1jRLW!YA=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&t\ctIN)4XQLiVpoI(>.nOW?*DmsG$@,,f58"PDKfeXi0S^6MAH=;fBr>1IXb_>k + P+oS^^pnX!PjdJ%0OEX9GI`IODGpB_@VYP$,Ve*/ITH-bV]jE!-fOj&0r,*&B&Q#!^]:p6r + +H7OWalB7H?jH&k7ut@iNE"b[heb&_qdU`ZuO$+V>>1#"$D:7#rV8P$FP'&<_^B6qT4NPq0 + t0+UonoEn%WQbL3K2.Elh.Te=S'"Of'LF)9Acc'*h:36m_Xk;#gH2"`oSnpIPjcSDU#6-Br + ^LI9.fjV8lbEWm6t`KYIK'1C5J/P3pg]N.9c<8eEb7uk"6c"UJ7A.<$U&,$0>jh#BiT(BO2qmbcMOW4<&^$pC[!Po75Yn=qEHrh3+n_A2okUh_h[">IN + M,6pO7UAlhc\A2tQqF_dE!7KL>fiO9P["=:##G=DiXC-oF92*$QMA&:oD@#XM!+"lN3kK.o + IpL6sCpKL?r9:js$D(eY,:5\q#DV@u;K;$s"-0no_3'uN;a%AC]tLI%Ff#`*a:U$O^nE:tB + l=D/T34@G$'![\+ZdNTlW9S`.-.Ra'4'I'neM\0b\L4KpQ@f0Pk&WSS7i5ceS6t_H5XtW>D + h&2tEBT:E^3_#4U*M^D\Nq)=$"uu&nUWe(Gnd[k)5fO[Om"gb_+>F'l)'bqnO-3C18kB=/( + hC:FP&ae^Lk*AjAGi$`&qD].kt"[IX/40iJQQ^rdbL$k;ji + kN,;\!,,nA-iUr5"aV*&#OXSMC99?P*r@A5([M1o@)%RhK[Rc,ln#6L]f$'8S9d8^%u`/Ed + ZHEcTIIf/dPtlO/p!GL'FJ4m-YQ$^9k):J(:D!n^]h'F'%_g'L(/'aUIS-AG@.*C?]Y%AG[ + CM,@G*7Hg9[U=?.Y*;i,gaX:oVu7W`;*^<3!o;X#44cs;j=.9"mlh,h5((S+3>)!_XLj:92\3l<#lp + 'iZOKnLIGn56FAZVj1*d1`3frI,Gm.I'Y[Z8NA61@,+25mnUVUeq_?#$KlosFG6J(mV5D!f + 3L+#`E@%pO3?Xo6'XpB'=/.uA9629EA;/:3GOs)W(+cFOfel!7MgFmc*Y]%:Q^?+[dnY6Ea + p2h(:'>KM0Z"j"5uWqK\ugN)]S]si4*gY5qRI;c6i[MS6.>5M64/JXqo(]qq>>;9[X7?YJf + 7!RA^)F*_?&]**ZHJo8^4E.7g_`2I^iN,+r0_QP.j3W;84A1Mc2CZT^XFH8s8W0ik0-5AI) + 34'!NQ:h]`d$ehj1Kf#8!ZE]6aIor63!(Q7RbF%+!?TKi]m!W:s&N'.V1`63er7cb8G(Hjh + /9nErc4\3G1)//BBpE(2+^KX#:LYd@)k?DB7ER3Y%,3SVR"'c?\_N!NR>m5p@dG!qhK304h`i[+TL7:X*!dCh7Il%gDj`'H#HF?]m%KjhIC>Ln'V+mLDKpJ4 + 9L.R;6i/Ors.LF,DC*n)8ZTu&E3eBIuTI;`ON)fie(TtnZfem`kNVV4G/c9nZ4[)s!QnjUQ + olV=iprP0]kb#C5q(ac+1#:kmHs\5)[fXf`)k<-`-I/j6i:Qj^C0!)YDD\#;oSOZRH$DSs7/?LaE^Y^1<,rQo0-[/H/CXF##N_ireJm\+%tk< + =/PX8r+,YB>O+FrZ1W`:foPZcm6/4mpB*Y7Hbkj5^#6s+57E%#ELlWK5KY5/G7H=i%T)C"iG9;rrspgW(GP^siGB+Q![$B:&((arOUN$<#n]I=&WmbCOWP2Zr + YC''&ofB:JSc$>1)=jg';>F>OZsLfCD65-(/[^=iVsV>;\Yp>'erl-;-F_YAfpPS(6N5FZ# + LQLAekDX+7XUmdY1c?Q(Ys)H!IPnZ<_G=r"mR)f` + J/OhD_h_&Ir-'nQB)Z-OE;d2d5W*5PYj@K.AO6GndQ`t$R'O&+d*Q`Z6:C^,pC\c+u1YmE\/Lk0dC]l,I00OdQ&QC73jW[,SEfsE_dsd + ;'g6_-*gGqnl[:P>p_=@-?)DgMO+KXi3gfJOg7,h3H#kS3DZY(#T[Pj%P+:Tg0 + _m\K-NVX$)FlOL/cDS]1-&F8c.m4QQK[34l)h!Ya;;QfIdcNQP4o]9pU,DComZ[sK_Va&q)+-,O.BE,X + ST>lqG4j&Q(_@RT)C=e>.(=,YMWP;lW\;is#%IhciIB)qZ+%G!>W]W6%]27.0:7;!d36"YV + f`Ei<(c(?Ed'+2=Uih?XIb^#'KkKY^'V1G@5B<#SmjU:m5j%Mh2;[QtDmm15LS&6;L:&Ii;+(M.S*AW\8O&LS1\0,!e2'+mTDnNgeB721k + cBc + bUHZo>J!9+]1\f5$qg`MAJ@#W5m.!@."$mb?6&c\3fHERU.mc3C6>\L,pb>,M1t>PeK9SSg + =?tH)77=L[KWTZUoN'SgNCEVEfX,2BZuJRbEZ=DBL6QsRfPa9%L>::4LQmnW'T\%aP2B6,L + m4/rplS0d$&NO%9.h;Z(nXHGV+\22j&F'VoYjMl92*W)jn./_eC..E`D1;n:/pZFR*UaKoo + 3jO:X5S/=Q&I%$L:gW;,55nf^VhKohmEuO07)4PqS4Oo'gc^OH0j4mg2r('i_@W1m[ + pn(.HF[9L1ds&jbT:]m(Bc'WGZ!Hljsfo]QZ-!kOR?eObmdu1MO+TC%eBup8Mg'72o+ECO/Wh2T + $QD_512W*qmdRf$SQmATS_QVRgE;g1dG\Ff.9GV]'u]H@Mj(JZ*mKW4IV[qMD0iNN"t,WHt + ,7>+qq,UT+*dWk,10qPUATX_q3/0OY(qo9j_WN3\W`3-V:%lTogmeM:3qXg`5NpQ'Nej0!T + g1cKugpP#dhl`Q]2cTmR\[4`7e4E'X9nb[.KsY8X(gi#%[79q,LD5)j426PqHb=TQLc + `jQ\;e0YTtP]78skm#\=\W1ThCm>MR:-UfWsPI\\E7>N#V^i=N'>=ac+Md:DhKpeG$d:!KG + V+Na*T$FUa@r(QSD@;)n5God,Zds&.MN_li*_[5X3#'?##^O`a0V\KR*c;3H<0`k>?D96q"><-hmTBkN + \Vj_gNEoQ=R9[E+fpS8``L7[BRQ7>Neb`_oeHYiWc*e"Lfu))hPFS&eS@0T3(+5YL_4>PYS + fI^"nDDSN7@of4T,(;Nq>4`(>L@`tA\&+Y=q)+5%qaN/d^F@ng*>')K;*ZDB`1#f>sY4tOe + ]T]U[b&-p8;ZZ6e-1SCRS&LqH)6/;l0G,V6Tb:>7KD8%C$iaHJQ]uC`0MV_gBk%ZF@Sr=3Ip6,)cE>FgF,8a(MsfD&5,gTS3@>Iu_)L-D/_HbJ](FbcI*]N`SEmGklad7JSEhq4@1gZ + Q:TjbeERm'O[F=LGncdD,SU^_GbC=NeL%l+m;_NE#_iIdp5s*KHX7:n0')ma8oL1[oN7_k. + C"4@/Kt&Q@4InZNmoR3T[N=2j&GP+5qPHt/?n=-J+np";RM3C/mt8!LE8Poi`AIo4;EDg2\'J41SXf^iRGj`C#Q/a8T/lM8T@"5me^9ffdFMZt]d7:H.A;7UB:UcO+ + 7Z(&A(0?2U7F[p$O)an"U'Cm-1j*RSBZcH.:tkW`;:HdqSZ]LIIn6k`gr^-CIN);Q:GbNBX1=W`A>,R^H + n8>H_+E(!@>@a'l8Vm+.XXY0h'n>Ab5RQ+H4"]%l`*S&OgaID'!kQi%U($%Tp$nG$6X0,9J + bj7/NGG"4!Fb@Ips>g?\6S^(m>H5uX:6)2nCFW*tTT3&#CC\?F&DU2aufkkpPIFd.u\_ccK + g`?W-6D/Q'3?4Vl0:C)JE_m#e42pRdl?0\OI_3e(gjf(2*T$,>^Z9Zc]T59QXoI\[q`WgYA + 'sfGqL-)mJ(NlZq#84pcS7*:%:g=Y9`j*GY!(V%YHN1^_PM[NS1*b`L4L.>kn%;P'RdE + 0lKL]#1Jb`am41ei$Orc_&"I,-b'F*-Vi:=I&YG0Z< + ,ofc1Co^mE*8,:h(d*sWX78YmQ&9XQlr?"SY'U:1@;Wml*GZ-HT2,7*Bog;HE2'#;=SL8h] + <7M^`>'*b%H2_n+eWjiOf!W??>$"5mH!aWr"aaDma(bQ_=Oo'bAQ^p"rc4QqFeNI%2Pmh:RRF^6pT`HL)#rS;_SVbQ2tS6)]-I,?;;rc$c(rV_7RVZ + mH[+8@H#JfOPT(iSBcJcCMb=E2TB@`JBuDduo.s,H'kmQPNYVuB2LjI%o)P[oO-aFE<^IU/ + =QH'W2pRqEd\SECd@H_+l)eoRLVk0_/BB\G)"V&Lcgs/'MaDP*nC3bbk!lK@uIE,.Mk[B=< + -^:1>=k./74CtgSg[2R'Ts0ce!DVr!B4)(b,mcXDGDXT[DotDO1Tt.sX>Rq(WE4e[DC%5G* + [XX`nG9P#t4E74?ntsh6BlcDo29p7iM6_X'4Wd,@FT"-dBQ[Thrb*5kG@B+s-i'?Lp:\4Xg + in1uFCuN4s*ru%*Z'aoGj"mli-AX#X604)VkN>mSr]h"qVK8nm2tInVs\ogn%\C?57*Qg+9 + +:Io9i(/dJ!O2EW0MI^RsWaIfGP(r1E+Ap&;_Hr%m6k!Z;O^!tXp_!2*!k):1,D%!W]o#Pc + ;iYYkddfImFu1`1jTr*kW+4U<2.+bYr;&WB#A5N,![KeTYG6J,0i`_c\i:s`ajBQ,Za]K.. + p$6-nHHOn+a4c!ST,/c3g&gC0PM(pb2%!\0S"ouXbnP1RQ8?EY:`*4>Cr*mXt^DEXJ@uO9@ + ?OT7#)<49*NH)=47`aFN`U%E$EQDQYB^dZb]Qt[ZniOnUq]Fl5I2F^NIuCR_+ll=QO]"#AM + tml/"k8]uKDnHaBe(LKaM&beDGJ%k9#5EqAVgAP@bb848T + -2W`4Hf%k^Ue/IP,4@_\U].@ZU%p0W%Ob(DAQ:$X&rU#PbZoZVkLqH*9+pTXL;1E*:HM^d$ + uZig[g9EDBqR3[[c(SMRQn$VB#6"c0K,o?M[.8aS]S`:P1YoRkK\%7!Hq"!PTTK+UF47gR? + I,@m=-;#22"M2i?I_)Ebg"Q: + o[]F/C2$E,eN'Olih'/\Inse_6`=!#T[\:E;CXOqWKf*6<2uWjC5l#h7Qf\#F.ZKJdEW9^" + R2`sSs7Z49!";;,G'"3OfM2l8>uNs,Kel(O1I>@e^h;SUhO.3ROPFrZG*'urEsFFKD.Jg@X + NuD3B+O8bUme$Z;XCC4j!12+=I%JT'$VQ;VoBi`no?W"eXB;sKKDWb#=Tmhj1J#*CFq'`[;S[h>:X9(?M27H='Fh%Qd5E1/+C2)_Y7DQqYm]=!5R5# + $G_TJ"#.R-Jf'CZ_3cg51)qFX0U!Sh9WaN;G#a7LUG9ZRD4!-QGDKDq,%qDlTeq^)d[Eg-C + )FDq2"n$%>/O[dNHML9G0&=*7QYH]V7F&DJWJQ4G&Jt_^>sJ:hjd>pE1&T$34Bsh*1g)L:. + `VhV_7/TRFUE3iL?D[Ls=7aD=" + r`r:]"mi6[OG6GB!%qt*hWILk%#5E-Z^L.hNoO9,IIGRLA+?CQLB7o)DF/#Zk**7U'^'1+9 + J$$AopKpoJO6F^g4@G\>G0`P?F9lUcQbY*f(pKg8hK+t'@>)N1m.n3XYZ=>6(N7"@W7ie9C + 2O&6BOjZD[Gk3'=#2@m1'OF=5)7X3`j,uKi9C@']\n(g+PL>eA8kDH%j^.PDgX99K.:k,Ue + @_-s"+O!^!APp&".AaNK1tO3_IgTjiH'$Vc.U4WB-.eWZ^uA\H/$>OI.koD + RVFP`_?qNjtBSO]^Q5-fe*l.@eS=OOGIr/" + jkNb.^\_M#:C+1uNj-_.0XD1lnXDd"&\q$N)6T3FaVKJ,080H[Ru[$s]1 + qb4mg"166H+@Ao3!XBjT3$>8"T^4]^_c*sB4+lXV4.O2@U>W)("6 + sF!+"(=)D@Dn(8`1(9^)eZU#B/Z5mUM;j.lYIg,:c?g#,VsA71N=[]E[6!Q2cFfD`)em4:c + Eu)2g]kiX*H",H_mKbV:o3$dsj=BI^f_pVE/;..V1S#Bgt^0``d4=CPXrsME$fWKpm'PUPp + R)N2M2/7acQB$]i=(IQ8s!3?X's/![Y;K=Q#V7pD@kS9HuoS&^DG\`A&bbF^jHZq&>f8)(A + jjF'PbJ4KNg8+_C(%"cP?K1J[h86gegCkYE%L.H]K8;rEsKSrBkMo_uT]2Q./ki[Tnkn:n^ + 8JBK%!@YRUPUea=]\C+kB_OX$^V%A]]TejIT`i#eS1I@)4A;=Y)otV#T?7M[4Ii-;XI.-mQ + orR*a`OA?F3^l9]#ojU8Lr/JbIB]'X?AVo]lVcIlI@J=fY+'5b8ERY]X#6!hS&9`/eLQ:%( + ?qgi\"3@'_C@fKB14elH@B65(pX6P4-,[lq@^t^=:"Q>4e-?pHJf"0=17DCAKU*H-D0E^JT + DDEIL-)!>l4@9\G-7X8P`fMSIpH5P3Au*!*:^!Z>MNc!([GZj"^$&hu,85^B0MWt_gd$I-a + 3:#$.Be-qf9Z-Eah:)jkk//#6>j?YmC:0T&?C_njckWt.FZCDi'`#MOb*^STK1G%r[q_Z.m + +gYu/:Dqp(90#=<-I5=I_L"F4N%./X5Jk)/1b:/a6nU<,0#J&e_])0(X><4IDuj6p"[B<=^:".)^fO2Bu_?AJ7J%9;>0SUN + :.flYP4Xo0]W0LEPW]/I]>USsCmN4;R7]l8ag@gCW>it'Th5Jn/EEf^rDQ9$eB6$Lb4Mi"@5COXf?5GE/ZRT,lJ4VQ[CtY097#V^!D#kCF= + q+8Y']9R_r`Sa]rol!^8/Lrm(k>"l]s*=^?!2Gq//D+J3PW:9Ou709Zb%Zp.VTB9[/;G/,? + rL"\eLkfUMk)rJN@oe(DhO5Xg\NFP&iXfZftL9sb0E>hr,l(;RO"c28!q$QMG/*jOedfpi^ + 21F17VkLn&q_0d+qP:Jqp)m\c]:mK+\qq[:DK`Ih"+]s-8V\b>++VAKIY<#5u4>Z + 6H&aC<;]qtrS%YT:[.hq1'"]#!5i(D_fZ'h'KXTE%a]Sc[9X*cN=&&W]MQ7,d)mM"71I1F? + ;Uf4d1$^VP@?q^`)/Z3`8/F1I2>b=Cf-bF,PuCaXAiH<*`:]n>hiDW"Ba(ecr)])2mjpZAL + 0:s-dmr^\.'n,/7^_%K[Rbl`cPlA$u!/@C>I*Lh;'hllu(981FKRf7jF/FA96ThQ8sBT7qF + ^;Cj4@@GWEs:%hqBpKrp + Ra?24;?qpo,"pX!k?sl-,8VL[4,L0[+ + AP/05#j9Q%S8OO + ^$G>H56s:f=cUl/UkG+`BP"1==,@Z,fjG?H;((5be*VaNJH/-qcKZ@=KKnP][jcAFafg'\Cfd2kmC^Jcohd_W'=aM/lM7(/H4AIc&ftKX9>j"C_-UEHX:7\#_`^=AS1D7/, + ("C$=X6a[-[X?9^q>To*>[?\EL%UVU]^I13q`+2AC-,X=W8l7]Cgg!QcCMII/:u$aC`>`[L + Zr?+u7GQBo;>tF\"j^HW=sYcJB-#F>h:#Xe-XC$.3$m0SZt1ST/1.LP`dQTSX,1-2NEtS(h + =]\qp4$BoES`41?:?d$N-2[bk>S%VMf,ZN+fAQke<$imZ(u[NT + gq6BbleE*g::jP\DJ0Bf__jXntZ^r(>6F4,+70Xpo*Tn6j4:@'0R]NX9*Z#O2d(C*!Xq/'2 + N)T5$psC$(u;7\+85%I(4H,@>/c(0!c7WFQ'OfiOsF + #D0O1S*_YU?@KaI3Xs)EAIV[-o9L^::<[=")-J0>k=>KkbPP80s.q6HAfSfXdgs<3Pdc0m! + bt]uqhD/o\ODN,Ej&M7Yl(_a1$<^">R]VlcNL+._QpD3FD-Raj2!.)!5OFre>@dn@FT + l?I#$[\hA^!>B\f*s?^!O,Qd*3&4'u8T.&DS;Bgas,4ji$:T916`7Dq[2+IVA3LC1a>Y;9d + q"NGf@%(Z`bd:eL"kdu0PAqm7co0i/`C@R'IX` + JW'5fH1j*J2G6o;Rk\WJ&W1Mk[Pj)g$ER,\??QSap+*%l7EZZCo0-P?p,'#moE_e$&7ji=^ + NK,o`hmSopebn^/ropgil,KX%7]'S![H^3fl;PK5(8GUk#P/`hF!2Z@D_N\YABKZZ];?'$% + 9%#MWflMG@4&*b2Qg*.4NfVuGG[^&4CTb6M[_-Ci]hC.qCZ + U'3q/OW%^m@55==a((T#8dm<(uGn=H:EUFtR8'a_\.`@iX(mWt")Ybj=EE5`FIN5@7.HQ/e + /k5j[K5Kpljjf44+l=k;8/hV-8FlKuihjAt,4hF>fa8$b\D>$U[mm.4c#p%JmgmT/Io/G\^ + W:g3SW>6a?R2X8W^ll\I!ATmLg:O>H^3VUhh>Eh9h0(Mi?6"TKWjpXXi*=la7qKPU^Dnh#b + O"-pmu3pY%kp7!bT1L>iopC9E + <^eh(4><,i91+RkQd\c14mtsFP^>VEJ/N*S9WnW)d](!nqX)_Dl3)TVg=E&HCq,3YGliWWd + ;F^HI&a?a00gHYPRIoBm2#Ng\.9rEo,kPjT=]/W])0JRH]L,D\Kn + 4=s7<+e@.,Xn8&KE!XRkC/lJ<6I"L@=jZh,R\oJ>BH8'=(3)Y@B+iPr>ZVB*k38aDrjoVNd + R^XGBm9k$%UfCa[[oC;/`fQA5_ln*morkpK,IX?D>)gYpAX+5`oQPo: + ;XG8s.=FF%s?tT;WU"r6RM,1?iLB64oWKQ5p1"-4=-$oKtl9eLjV6+c%e_)d:f9iR/`.9,B + ;B>IQ#,U^Z:Pl/=oijQ9X#t2ad5^KF'6BfBf^aJR1n9OqLM"AcfhS/4XQ-.Uf/*=BDk + #h:oRH0Pm.%"s&lJTX-NPSBmdjKf^-%8o+&rY[+aojgJu)h]jY16[nF-jH$-g8n$i!"qZ=F + GY1O9_2Br`h`\6j!jjldEs2a:WGu`#_s-_-oQRgkJK0Q&ZXr!O45+>EMeS:EHI=)!'[pA@/ + ]1o`qCq>,apT'kb"`;'6^Xg%-58eI`jt1k+kPEYodcdh0]lg((UY8lF44!opK=J>YGns_)6 + iYKto.<+3jbh=_nbN*sT7=>l@'Y:]s0(J]!5K-(?0E8,RKI7qGqT+NMnMJKd + W_/2V#=-Y#4Ujg+Zs=85;,hBU8((@Z.7hmoSLmOa'\tE;do?!# + :XiAR8PiC:*D]S`'jXg?eMVbX=B3n2Bj'$;Q#U!Z28+-=f95Ab:`]T?M.5,g4cs:.<[dr5& + EeW0J(b['UM8H5Xg)VF7ua9]+856aHX$4Z_f=J`/^\PZ=&mn&h04"rGMYj'h0L,D[IA[;GG + Y%^?^L`iJD[jX!J4]]!U+nQpeDWZ@@2KaLuGk8+c9`2Xtbjp=LL5Kj1*r/OQ79#5Eb)05#H + rD=>!a@h`&77R-#3Mj-ngB^!h&XRGN()gU[9jCZm\%KqTOMU&UnP3_#C3l'd'fW1DX3VLRm + /ETaB<3l\S1l\BAaYTN`Xaft+ZQH-?Q]0Y,YD9)Y1\8D,'l*MIDM>t3V43$s-n!j.PFWMM5 + #JkAL57PR#]G]2sCAk*N/pCf + QI.RC"`1B.G0.E*S5AgpYG:gJ"3(ULZ*K5?\RP\Sl_;/S7book^!/gWTj99J]6u&8,op`HO + !?Wn/OY/aFB^-8'U[Rs%h9dI33J/pjQRqchRL:s6olXs2Xm2SBpU&NVEE2TH"+Wn.Gi=BFm + *m.0"j[3X3Cp,m7VKru6TR$UKS%#%6L2K4A_;O8)!HTaW0!d$>@Vn[/X2>Ug^i\ekmT?OV_ + bfc2]s)oH?/%cPnu)\CjJO8+;b#6[ALYnA9LOfo@+=T9<"IO@PiT+kb-ef2O+.n,8t(1Q?/ + MW7_j`Y&2-7dOWDZ'$n4Of12Dr+h:sZ8Y8ji>VukFH1uiIuQGX*i$r+Nrr!=a:\D.L=Cj;Z + 4]MqOeOa(r/6]C1.OnR+X72EP`gZ,IuOah-D\kTP63p_O82'iV$s,`d[!^giN,9*<32@*[T + H/t<\ne[Q842Vs-f-TC5e + j]25]0_RtLWLc+1_O-PY[4"%%(SOc_[P=T96A$?se[iB51!"X'TrHk<"q500i(,4kSgOs3% + SaUJ=uA>4`RE_]9LN=@6,4I]1\H;;T86))=,TbO5_;5iGJUW$?7dMbAMMZI+Ze`]$tt?@G_i5P..#]fj)j/FZlY8qU^r$?B<5.YK + .'cTJRjGJUbIL=@M_FGK#h'k*fElqQ*Q4B)l$%`Al5![8#lAjg)E=kebu5L[Z] + *CRo*qf4fu+m]%1'^ASc4[YdW-.gY5qPDXGuf[l46B>RPt!DC*31f*^SD>Ea'pSTM#gdo))-@B@)KG1KK=&0`-k1ffHXg)A^Z$\<3%`cHIZj"tBB](doo?G9QH0##rgr%ch! + am\78^T^MaWc;PuKXH_iee" + On/IFYoq=d8K@VKT,c/7j43n(m,3bF*A0jqjKWhG.X.moAUaSC%#:A)5DUIIQD9`qc5?lH[ + _\otNHFqt&kAcbk>45KsA)?jmChcmM4HL_g5&S.O)p4qBr],mSO^+,)D?&o!_'$ZtG\L3e: + c_YAHfFX0*!YmqmEI8pnh6qu4Z1+?uUW>+@Y>1VEY/B.t9(ZR]mNA7.f7fA%FFprMS1#>+\ + R/%gL"G;d++)"35&L=0$e_60I9$*JQ-[+R@gX:OM8km8RpCsltgRiOZfkE&"[()H#qfke/j + Y?M"aI&/\B5a@21M4B"S%JZDmD_V8^WN(E.'_WR" + #2'^hMjWd8-cAO<=Q&BaYaLhAcF9/M'%crB;*f$/>:2^[RhMKnlp^5/e[\,`+(5Pd9Ma + bB]""N\'(XZSm-pH5iu/I4(c]^5p`hT2FnP5HXTb]@M<.AaFiCYkHr"g\Y>tmetbNpHJW6H + k1YpIOE&.h[ALm>@Vh/kKWhSHLPA*ObYS;eAE,/E*f`UKlDh'_H4JW1HXiQn42UD + [-]!$6b05rnGmr")%+KT>hT_t,c,[U>8'9+rQHn[F$-`7l:niE@!.V5Qcu?)5VuY3s%adfU + dBL@4Laph).1D1ednMJ)%5QD76Oeibe87ni&DkqH=Oh)HeYnkd%;+0cBT(,R&$9f09lu@,# + 1q+MNbsO_m>?qKNnHu%&ZqT:#n[eEU=s<.g<^ADIhggs+Fc$$ + R+Yi0Xu>(!Ys/XnJ2XobME>RjkZ$$,%-M$Q"Z@SLtK4EJ!/p)P2s5SusC`U)N$jpn.)Fip$ + I&C'`jO7cX$=Td!+&Og=&1[cH^%UCL>dZ,38N4Q\8*Ui'rg%W`XcDCk>3kK\>8l=-WNLbDM + ^0B2a\@g].%Q8,Cil:R$#""*;jP,XJClRJRn;A/<5Aub:9Wrm\7UP/!OOWoh8m=V&m%c06! + i&_G2XjTE%$K37&kjN9Sn*9E3MYSPo&?monYcei4Y8BRo,-eFeo!30(D_ib:0(ZApZ\3?hD + a=*dPphfN.*n)*nr"q0QRPh%.5IMEP+a>QUFMG).ak.D'!!NSt;`XHbeo2'qb(j%&&7mgtBa3%DbK#5?-]J@i`d+e?c#]E]PG6>6P0Qmh$+$e\ + F0c3$kg/Vu44(5?:qI*Va#m26dI>$^XXcP*37APq%5?A^NBi5^k1!*u%J0"D'CprR;\##fe + aW5s&GZjd#1g$_&2=PY&J7<'M%OT`fl8#unKO&l-JD1gg@6mb%CJPt^D!`['N%(_%FZ$@7b + mN1Q>orm'N5bVm6tm\(:7EaM6TNEr89;N(Tn&\mLJ2IgDFYd8'W0Id?'tTPo50#it7t'EMZ + 6%3uZC0Tl*7G%SGfb:nk509$U]^;9C?8di@X?*L+;$6I;QkiCo8a*g,S<&anlTeEJ^[l*9q + :@iXa\s%\TOW<54)[.:3%&PnFqWc#QpY/K[i,uBWL,-io(FV,j@(@BE,X\S1-%d`X^8Q(H2 + n.JRe%9&EpnPWjr&n`Q!7e-%j<5_E#c.AQ'#2!P?F0;=jM.?o$^c2N@f3-!:qdH''1leZSF<'0+[Y/o, + .Jh@5-.D>d_m4("$::dp86iAJN0j'uMcpq-0]A>B;<%'&tRM2.)FU1PW(hp&]$6+Z+-_1sd + g3@E^u2CsI`&4\X"Ci\\f2\8a+o6CL\HXi.E#'N3jB5]cVDTiZ73He0APFZ8YeN-N + ac82F\OI&#PnN.Dn4&`ZsOKW:er."_d6?Yq + tNa9CXlbd5@5s!K9c<6Q2^l+6Sm9u@($FECgf0o'f@Zs241!QTn'HYt>V9FEtWC[niMq;]Kndk#Yh]K!BJ:f,[q*9Qt=p;<<")k + =os`GKuIK(P,=_LJG=::A9mD&eB86#C3>M:`9jj0$eid%=4AE;,T8u9$^B/]'fa@Y#iM=[4 + cp8213q9I1$?[miA3*g@?'86[.Re&oW6)GCJ(HV:('LRU^qqB7eiOVHRc5)K2NWD5WhDVn. + )Mg4Q=BLSt!,W*51ZRZ=qg0nV71K6VsI#,?h'4bPJnLGeCNphb]:;hfO*L%o/Z)S]Rh]<%B + 8X!QYY($]'AG$E[uLS^c$p%PUkKo!6qLmA?(N*hE$R:.d&MA@-gefV+s-Xq>,@[OP,2DqaA + ^Tj(lZ.R/Xq]c=:PDq8EAG0)aa/NXVUQ/9QcC"!ZXRcc<7lsiIB6a5b\GqXn"-Ah&4W+^;# + VhHBP_CpP5$Mrg2PglpkE<1K%ed3YfensR3=khu&1r0knHkF_6B0Yi&bI:#PU>)WgHsRt]% + 5t1[clKaCR1B*7%%t3D:t8!rKk'GQgO*XS'r-uM4!&P7eCV=[!A(HHUkW>i_bl23N1rjX%= + *d^XSgDDF9ocV&S+**#DJB['On#cV(XWFtqZnGAG&^kFONUk]GLLcB1iKq+`Q7Ga\FNpPkh + )P>AZPl_A#7FTWf`';9OZ;#205H6IWU4KWoXXd?dJ\hd8$aeOn)-+Z$!):=ilhW8",R_)O79aVoh-$b$;!j*3[OjXim6@bIk=-gr%>Ua34D(bZHK">+:-er)q%?X + utS;HJaLCor>(AX]ho]]$8Q'@Ir>Emd)t<^!Y+7#Eecc*.o@!L/`a(U.]g'uuUPaP9a4RKsHOYlk03Afli+$]pk,>Ta`oHOe9s^h + pXd$F16=jC>4)unFO/:LMOZj\F"LN1_\[?7Ch!s\`*VDJ$bkF8/rAP",+%TE7atUZO_l1,# + MI<;5"S\^Z0/8`,\'`O?_FcGpJhO-^@;8P%Jc7oSkti>8i- + I)8>FUI/@M&ISPZ66C6^i,.A[!FG@q]&YI + *hQJKrmJW)p^q\ORln/m>LrD*R361n2R?,GM10IkZ;2EDFk&YgLk'oOfJ$@U04Y$u*UX\Qd + >1OIe?N4Fh2HrnIf5SR[DP-gNp?dpSi34'Er[/YCa5q>dX7e(DS^?QlHC[7+rHuQMG*gE:a + *YGUnLo7G8,Dn4kOeU6?Ie5:hZ*BKrX9m8flk/\rdTF$j8NE#E!^Xf^o.):"2N8LEaKpXA/ + tl@-0uWY_GI$\>Si1n1C_5uj?F/B7D)&$(MEP^'1EKq;\KqcdJk"P"jCNr(=AXgJ!R@0FW? + _[m!`-YdL#nV8(&rF,8ThqPZejN,b/#[&s<2Bb*%79NZ@e9*?7;cP\DZUOJD-g:$m<;o>2?ekCf + X;NS@T`>(P@dr5FdHao[Y:Vn>.0EoC0MI]ZYiujosJaC[gg)l>dl$*ESWQ2eT>)@Fk_.HQ^ + -HRhD_;"HXA_6pO$>7ebal(p_EaZ?oTbDK4-`/(PC\V")"p=[A9g(%P[q=":R,M%Am^hc2W6u!*WQC_S7ak5GFiHX_#-HZkAfX6S]@UcUZgDtrcdT + &DOID;3ejQ1lFN:QGTNd^Z`WOsk.e*93pt%mChJd9Z_.+ifdWb_@lhl7Z"oo + -gqZ-s""s!fW"2LiK!oS%\JP\U]_'g`8@'gT!j-Y"Npb`:!g`;%ZIM!+\au^al-A8Up'ge9 + j$&9cYL'.?o%_-'Z+e1EF",q@5piR)jgc^H)rZMgB`=ClY7#95;+qC!,'1WI)M<"MQNejMH + ,+MYk&R99D8@CbBlsG['ShEPV^DS7Ms,=4W1QCqL)C&qPNUar:`sCiJUSMuNUQg8KR.hYsj + F<6)OjPQHUL2Do1a#M-Oe"$$*J'NWV4%M(7+]HC*&TT6^8@8(8"DEc + -)>HbE&M5;>U4ErOCeX%M"e_[PSeH^,6V8@0.>9C2'V_Em9!&jYe5WQtZGT1)q.VD0L6*1U + P$@0*EA!KO]2Fgf92!riV?R%<;_o2!BsEONk"_HbC3F8!m6@MgE7,Y>LheIpX/Q/;@]-AW6#LTo>I&YaA>MX5@577bG?,C:hu;;IR0#n"p%oT*W_AY2 + QfKdte5VeFN,?VEATe0Zn]CD.V'9if9d-C!qh/[_u]C%g@24*[DsqMe?2"\)`C/>V\k`DT< + Z:>EIGV\N+-\edM..ck)]=bA>=='k9"S%3\pp"R+QG48g"`HcA2kId.Uo3Rp>HLH"A^)+6Q + hYZD_Dl*3BGNSd0X7:Z-p&2ZBo)W4r#Cpb?#Jet>!oU^&`0mAYB@o]\1Y.*'Sial8%5"DcRnCAgpZtJoA7\%D_Sj`d/*>dB73S+q7gatRpX + )1Io$5-5Q'aG?pk0KYVSF-kSj\5`dc=g"pI=14eZsEKS?Y5'PP/p%&&V5LCi?76YL0F@J6& + 9g0,`c\h'F5<5nXN3&PMfee+o@CH1c'[Bt).&ct8_]lgKR@J^=lD98U=eiUeCdd% + Yh^k]oIg'MaJ36CUGX^E4reArZ@24BT-O+QWd4P*WuDb=ff)jot4A;_D`kbWe$?eD/)cZP- + )Jkb+M(pA'e?.nN-_@^0K%,*'a_PaVDY3q*gaA"ulRg8+[s%Z%>RQ>S<-">8r + o2qmeO+C@mF#",!p\T7WIoXX$MaBPjF"_-o6sK]q4C%;I35H\rjU>Q+'7h-J$o$"kMQ/dL[ + T[F_>+$hjo$9bomVDBqL<)4_>$h%Ik^dC0Xaj$QtHiga*[h,b9Oe),dKcO1*cq,'-K#G".` + @45[tkNEumT"#/H-;^e>*N6joKc"#;D.^^L]<'E9h0&2JVFZBP2P1#at$'XGjG1?%N(q_:B + h0=+#%+&j,M,;hd)2F5MG6/so1RK54E!OKWFZcbNggHM:B5r8]iZl0O`4=;9k7)G-W1eUA+ + Z55?m8A`PDU)YdB]2(cr:825p2&nBGMCZS3;^0[j_dc[k*&jWd344W%24Q]bX'A$)4JQ[uU + ^LKrlX;&L5W/^R6mH3;P"[c=6oKC(V$h#rN(:g,0_i; + Z'N=$>\.)]`.)NVmgYpg6j`hJQo,\sAHQRPk"\XJh`Lr%+5RQ8!N7jFZUgRgLtTrJ3h.O`c + W7!H4MfT!Oh\oXf?ZDf=SW]J(HWme#[m!k%AYfIK^80bDN0_S%)ZTCL_aCD\0_mmd?\k&$t + aJ6BZ=U;Y"^H:Zf/5*e"lGcBt_E:3'Ob:Wp1bpnA@K`e\BB!)98'k![(=H!\kCPbbM9bRiE*dPq90GC0WY + o21]4P2rP8o]^Zh^_m)QmRZG>8;Rqr6n[*(_Nej,JE^jOEJ*6ZS0bfph1^a.@54NKe[e=/! + $9jumCeAT,GfWrHK_,T,De.sh*1!S2bc.b!>XP^jii5+4-_:(;.o/OSqjKl3:TL:$MF=#4c + ,r>0@Zns0f/1IrBllf%Q_US"\RnQ08nBV.]:F%X$r%YSn1W*2bU;ATOFS@Y73('2rUMN"L] + KJX#4i?VM6h=U%'`XsS6FK;`6rH`IAHSb*Bi%c"2S;ULWa!i&DT'+p7+6*ClZ5JcEjn_>VS + s%SFApQhGW2`:`D_-21OV]-IaM)E7@SisOH-6nK@,oh;58n+KcA#'@45WD7N>o-;iW8rAl9 + 8#`dG4FEEVgW;##cH1CZ4QT>[XI9dH:m0V)4X?S^/>(H[eOgM*Xg6bT1o.gTV:Cda8 + p%#SHK.Vo>a6#(^(T\QeA'3"@*@qGecfM`'k5`_A(FXi,,Y*/HeIY"N5ut`/OuJN9e,a`O!$"%C\]J;"RX_$r]*SH + Vl@/-Xq;=FBDC>>#BQ0k^r>+Lg`qZcRYNlA`6nKnW21ZWBW0?0P#=9C;G*(^<@ + /*^8iA1XenQ8'@r3D;3,833)@+WuQ + `'%6RS=KTC7>]2_'WI6_++]">(3?=*p&e\l + !oR)]i*HQEAd@7;#iKS,i._SHQ3uQi$fKSD@-.mUmL@>SE*!co"7,hdD0+Z(_b<]O=>b7dj + `f>u(g$t[bcQ9Ejr<9f*7sU!br(!AIMcMtJRNF3fL,U><7t\CL0/iYfTb7PFc0A(Mk:`Dih + _[Om>oOk44t@3c@.BK9^,\KQ;4bS=bW2!D4*`/moca^ft&?Q2E/rM1J^^<6VR7.'32GgUKT + 30A*!OIoqbi6rjDEtcijHg2FF:2X2J=5[Y.3*j(V]h8m;1s`5N)<*OASV9Z71Cd,T:RL/5: + C<2VSe`FZYY?+aCl(:YC!`P!-K<*ifBJ6XZG>W!R-73>D3>hihLgj>)[,EZ5r,>pDA`i-2_ + _j.cLbgLV]>ij;oFE'ebEmhWQ>qC*hEo>/pmO^>bQ]N+Ak9U4,GQJW+e\g8$o&j$8PfgsPi;T&#p6X_BR-Ad^Bni&?[[Q + I#RVC?Bl/%rpk+Co3T]tmSnMlJiU + f`ZAR>YNkNtCEBQm2QN;/WaTrr@O^8E:*\#l\DgKC6$=VbCu/WU]],K@ii>"I[?+k;/^@r\ + c@.HVq@)C*`7\0I@l?/R<:8da2Igm#@tgJk4eH5ebi?E!6[\[HQ9B'h52'=-_nJCs%RT5.W + oQ!)g9n$LG"5`l#"5#d7('8[Zq\om9goQJjQ3i)DGX1CDe0Xr7:d0E1frd&&KO\7M]4<]uACta'beBjE'#De1P5]QR3HD/Y]k7uh + L;G&Y$NWnk?H3n62qc?.n5ZfbqE?55Y1/pUbeH:WF?hHkS/2LT5u^>>ZHhR7u,-1'-F`OpC + (Oj8Vdoh + F*'G.-\-Wp="`^](>,[^5V<'5Jqso]Js@p9O>YZKk"Sod,33QF.W\80.]>C509@AVY&tLfg + 7\Fe69UN8\R8>hIWBf^p;/ooS1a06S!llo/CjD3aHg1/?+MZt@?]VT;oLB[7_Y_\UF*SY,9 + "c<:_'j3H!EnhOBBqe[Epm54dqIAqEY6#BZQA6.=#?:XC@C#k<.A>QPJ]aJ]'I*gG=gLl!' + %Ca^piS15-`GgKJ9bqVNJSo@,7F#$1Ee6ab=h(obEWY4h"s0&9a,n!^'$,r8&agteQ@Dq=o#.]r16T\\8g;l4\!'SC?K[W + "'XX/:3aMS3tPHuOauHD_)4rsIW#`+U_ohVCfnn#tUIK]l]:8Y6Vq"nUrC;L;?kBXRS]G8K + JtfBDPQB`2&t%9\;8/d+C,Ya?tna?fBF?HU#3F>q)/+2`1R_u*TSr+ErqGK'K+q#]2#Hf)O + kLb&V;62YZBI-(W\T?@):T)?XRI#l_Rn'c[@URA=4bS0jm>]CSNck?:p'`/=*Kg%R_ + tHUjcDJU)@n2#:.mhQpTt\V7O(qHC35[)d__sP52f6lq4RDc)pB3PE+FlJEmH>5s#6fZ;>d + *p%1)CCU6fLo8j,ti,MT+UQR*0?ds,T\qnei^59`Js"jYDZkMGZ!C9I)gT$Y1md + %>](=sS^Qm)Bt!IHX3L)1d_0$1m3^Bp$\ZE,=tQ*?0,okjA9&PHqa26eb-j)d[YAQYs(Ab3 + )D)G[9r6:ERa:q5ZWF9]C\CQtDQdi_C@4#Seu$oBbhqnsr!jeVM8Wu4hu''Q/"+`HEg]WLs + %fZBlh4IeNnaMrf^JRI*^90_bNED]]eB(H5M>Juqu09=GL?E1B79eEO4X&`r4hFm!b3+P'F + Ot-a8(U)5u;).#/Uq!,mG3@=oSiGn?anZ_bdUmAKUF>6=e#S.>JO(kP9fE7<):/A49@HKc* + @8q1BMRn[*9U)"FtZ@)o,FN.8oH,_mSc&j1nUR&BkM-V:UkV'sH^nVD\;d_r:q(imR,/ka7 + \HS3N`'Za]V;aGaf9o&Oi1emR`iAo0&%-i>/Ad%We:G^f(?'k0%!aSWE.K;_2oJI1bQcK%B + 7o?,)HUgtUPpo@m"l]JTdra,#:Xdh@Dc'J&g&>4*eO<85eA\\7J(<)#F'2Ot[:`\cjfN0$= + #JZN>hA)>K3sq1-&d>>KtWU[(59/YB3D/.B4kCkQ)SL6[[".$gEi,sDkHar_etd,%E_e=BD + p5#>rQ'FG24HqA:NH;V<<0^(VSs4Q7)Y,Ip]$t@=M[0('oDT`I8DQ@5qR6L>j%H[Or-L"tb + bn(sY!uj!)RM6'[.4r`,5/*_VZ,fhPD/'h)""QK3=2=.*ZgNbt3Hq6"_.k?74oTBDXIIA#e + m*fZk-)D75KkuqtmVs/MfSZ-?BSuo$Y)Q'pCl[$jeYNspQ]q^$kje!Q"810*B,]S4=%,nXNr,`Gupu[Oksuk:Om;,/C?In[so)c + L`+V7F-(H%nk=Bgm9J6Fe$3(c?1.g:YGD(QYY;uh9U)spAOH&hfkP%6;S^DDiN@XUoR6a`iVEI[YN,NZT?$ + ?sreH'i5Mu)5J+*XD5WB2QTL8s70HLB_fGOeL>S2P68dI^B/HoP#'SB4g%DeDl!h[scIuj" + 167>J)+'[u`:o5!l86krKKB@M6EZ)br[.IeEcl_;Qk/58a%q5Z+Lf!pl6l:;4a,'bP&Lo'E + \=F:BKM77jW-AjJW>2//=,Tpf/k(T/3b#0u+7Y//NqgX4UF^&;;5Q11Ls6lor,U/&hZUDfb-Ni..!*08k@&a/L=rGOrTX":(Qm93o + oq+POaH+8hn=HL]"'8187c@fi]iGpu7POb#lhID(J1N\g't`08BhsQh(PlbW1VA-5= + NCo."?GR?BSm>gOG]8nnn4V_t?Mh]Pj2K?_gR2onDD+XBa;OQk"M8DtDV6uI.0dSn#4=:g/S^50!:W`E%TLBuuP]E9lb*<8_KhSY7WZ=H?Bj`K'I'_3udTR$@Dkg9D + N0dH3>moa;0?*S";H!X*ks:.\#"Ze_g/qjD`A'.1HC>_h1hmIG3AgBlfUD)R:JGQ4W(,FpN + 7s/-:#WfG[4L@.@t\q6D,I;<0^D_;W-md;R*f*hWcUkGBMI?fiV20Nk5#% + 6&Alg`Ol`q]u^j?/EaESIP7cQ=9?I#4'XG#?@!6Rh+JWKm$9_g&^F/cZ->+lY%6H;N_j_e< + bP?G&&<:qnm)\(_96*%&s6a#ZP38I+2IH%WZr4D2n2T6(9ecrp2JB0>RTZpo1e3Q!dB]\0( + =Mr;htT1V8:aUWA%R>BR7+iG4i'X[k5ME62<`U-I(J,:%`RfMXb(aa#>?p,&A)Z>NR_/Y04Z?I>%m8m"]bBsm':fU"s_?L?Eq04lQiR + *ug_bdj:T[m8l?)ldSrNSQJdK[Q?:c>dc*_)H!Zq4*+5FO&/0^Lr<&S`HAg'%ue)B"]WRAp6N]pE<@ObV;O? + uB.86_M3A^S[.Dk2:E[JO<4lfm?d-VPP0OY0!9[G?t#1.>9E,u6I+<"^:!O.(T5SaB>Kas*e8Wda&CT'f;[oj'%F%l&LrTurf + []KVND3rD/SN`)pt%*Df22PZmRkE4[Z:5'a!AuKU+WCi>Q/s@ApQE"hn+3L>C/up+4Y99jVX=h$KNkZ`I + m]C6Zm%aLFcHtrH'()^h,4==U0&Z.c6LM#AY_5q#.h;"n?EnNEn*:t_,_5A[$F4\UZYpL(. + lp=DBXCPRZn"c>;KVK50didbo.N$N.r;[J6R]--;.Ur]:JA9P/G3eD.IUOl04ac0B(iO3p> + p6p%s#8i[k"Y*]5N)0%t[t?aS<'b]Z\h#En"?TRedZP^89icDl!5fo(pQ3q[sI2=#Dd:2=k*1!/_&TcNA6*0b\I[1NPKrEAt4H2q*%2o7].X!s17W@o*gu + PCY`8LH1Q>37Emt'9bH*WB%;e3E*+BZ`nZc5<:/H"Am?*0Z=GS^BG&s$]nEtE*iu,XZ`nh4 + n*!3:j5*BF[!$Sd&6(<+SH@&]Z7hc&-35(#Y@1D#n(ha"]NT1-`mNlT+%sE4A\W= + VX`P@/q"d9K2JXtk`1Er.Z?<$O]&?M+-)a3'f7sDY>_4'lAE%9'h#,5[]Hl1lbhfM&fg7Y+ + X-:",)YV/Y2*-!0-bUUmW(GXnjGmO8L2UXYnBo91/tP4#pn,]-5DX4"LVBJ5:4=d-S%aYFe + nU$W:MmJ[SJt5:H^-!\FbrI;A,=$17Im$aXR[b.[A@\ZEGc/X'LF+?12n0P/+]8`*"aI/?@ + #Hjgb*YpFU16/W8R?('OI(kq6cA0$F2Gp(]5](h$>=r)F>XOE#t(r\eZE8".,.-Do0fh[7. + aDu0a/,T.7LUQfeF9Te7dPJ,a?f$7hI+=QUOG"2OFre'JtW5oT+-:P23B?Fn&G"e=dW,UD? + pJ8sHN'/ccc1[%8lX(U8NanV9e5(DtP]iaS)1[*onm'cJp^)>)l_DF?'RZVJeC:DEH_PkuF + Ta\VR5:bgQuPn)t3j&';\*gIgq1$qk_f]_ktl#e]E2)a^aWDh4O;spc;._Qu?8j.;oJ+ZlC + Y/ihm?9iDW<[88$fG$Z821`%mmZKY5;F)RK93:e9=Ai&d;HG1>33m[lKB0'[Y9-oj7e/2]> + &nmlY;!C<2-VrD[eO8&=Cio2^pk#FL@Urcd[_jK;?a#^i9B.V4#;,&h@++-4V?;a"'`1f&@C#[E1At`!-SUi80_j%0(t7F;E^q6`\3)b/F?PSZWU.:AHm: + _gfo&O@HO@1j1*eX`,Z'4DWHRH'kVh4D12'4DFaTf,AK\TJA'Fd/!DO\\Q8@hI7r)'G^;(]Xs$KJNhCBV.XFBTGFbOI2gR)B:04?>f;dpb;gTu^Ut# + E?c=\-nBcPaD*J6:u\oaj8*ISaTkdT5CfABL#i&A..IW>Tk(\(IKom`>,+]VGaNl?NH+b7' + 5Wq%lFOtB`*3("N+=*h]7KO%[)p@&nT-$mqDG]ZFop_$kXY1%D^m8@+l(e27&c1'4BY&R + mXNo8[I@-%Db!1iAM?83#e6Y?i+8>4(Q'-X1#OC@&J7\]f=] + km^$IhDejdjHQ^A`Z4BXXi=a?4tdm3_JbumWR3EW + 2mD#.b6'h844>fFXLVpS'R63bEbu^<45FJlKRJ.fB3NI1'X[Te(&G0OT*+G)?24M'87bP`n + RDtQNRL0%uA0E!,JOai$"/$?nFkR.F.Cl,gCNb:=Tt2'aH6::LEHdqTGLX/rqCLT)_kT'1U + NFh-)F!0W6Q)=aH2:5RGGh8u=&,nk)g(h$:0i3m@sb]"VROHj2oebHc)5d7I0t=LC5^GYnY + j$U;_e="/tma>Vgf2WWL*>E1)$ZN.907bddeL/1*os>Z%75-X10/pHG>&LnK`G(,l5bf22Q + s.O+Y%"e`<=lFdXTLT7kB^K^s8h's!%R[4"*7>I\A=\8AI.MhaA)Yq)HOG^f@LYe%-L?4t$ + ,+oV_^J&h7J%UeK7HXC.'h27L)@%2)Cp(8qn(Us1Tr"$RNQ6BbrYR:[r\Cf#:Q7orkg`QE] + _7fBr4nW?TVr"!C]q,8]^'3En#N.cP^)PH$>LhZg);r4KDOqLArYOPm)rU]Gir1?@Hf\d'p + ^cGrNVL`P6jrPT0S_2XA.]CkXt\^ml"up;S(!< + JU.8e.!X^g<5aBAMu3WTr+"j&JtT^D!cg(ha-UNZ:1U).mF>d'ld]6GdnbM9dUf9,TIQ$H? + \be29GqG!e#jeZ5<:E!H_3a[6(lVJSkoBKc1>&bS'':HC=Y_,rUht-[R!ej(9.[MSWf + /=`"pI3LS1To@b2hO$BJgBsa=WH!DqeVK`K2@';cMEVI,*h + _18>jR`2<%X1D`^YnUZ4AIh.\^]mQT)bd"23%_GH*CH,--&]E'J:DH + ;QA5O.38#/irS^EsC)+O.lE>qD&jD9%[1&:hlaDqEA+\:$,O!R@QA?$oN)jJYk1:;9J[q[$-_n?,6gk]9\BWpn/M<(c?#68r_E'Qe9E),eG28RZipCp:XgZl + J"8CO&OXqnI1Jf<1jpT=;J/E;8tYah13ZY2g!NRDo\odjdrc[";H3?4b?b'"m"eTkHP[KV.h=3(-Y?.d^[Ncj!_=ik:/p0mr?SQ;#TC"g#7YAIHu'tJ6)p? + DQ'M?:GrS!!oJ7W=n'q8%5[f59Dj?)KjMHQ2^]n:0=sjmmJ"GPT"'S(JbhJ)H\X%>8-ol,n + M7\:,HS@X+1gJ")3?09"p.eiSn%prTsaTD96oOlX&B:R*$aejl%fR;p33Ak(?cRige7_jk@ + Bc?Gmn\d!7mBNcSYC`F?OOe_/%@BZJ@a8)lVl8kN5WbVC8XD]3Wf"%pK]*?$PR_'W`J"!8& + ca<'YoK=@hCS1Pa;:G2$B33QM`j,QrG$U/AgNL9HratjehfNW-h>s'[G? + u/TiqV^:NTBmV=-R9bBE+UfqAu!(l?WY3Ujr8*hm?r552X3Fr')?gn9i:VdIhnLLXYsb++N + :"R0J0(qYm+lraYrBht?sWkOefZ49C%cr*qtt%fqE$$A!gJ"i0*r0h,[cJoFaZV$tr1T@%cC%T/CJ33c(=qd(>L='FY),k%=*j1=/%'Z+Q0uCgUiHlpQo:s5n/MOlGaGI2MlelqZ6l + %_=0.ZpX6:n3=aIJVCsn'EM@Rq1<]W)aY(9'T0%0@'9@Rodjn!^u + h;VeC!qS+S'm`tVlXGfBpKlV4_jLTJ@s?N#12HC%Dp'YAXM4]jGj'1$9to)iB$N6tfJ9 + PJ`/V)8)me673!_Mr!'[MHQ2E5i`2NLd>_]Kk4U@s2a7QP*asq2/D2K#N`N_d`?VN*L6UED + Brb@s9Sd*6XgmDMoSt7n2fM<^Xr2$/<'cOceIh]uc<)hi-?^nD4hsH$NEP]Z6-,St900=_[ + ;8pViM,,NF2jV=Whnh3pf+npV,2r+>]ks"fl[9YW%eHt'GlFAK0s< + X$OUuT0@>li)]=T=eu>%gVR7lIhUgm`A7G>;TX]OrkfSRbK\CV;Nr-+.jmQ&>`(Eqk0K9L=>:W[lP/#0 + KRLX[qTb^!hVj]pE+(iEVD:+nEdSD0Y,IXf^ipI"-K/uVlUWE$%MR-A>Eb#peVe.>VhlA/K + ]=N]%B:t9ONQ--W0rk$fDpL3EZO0a!T>VUt0eDfTZAW6_ueiT%,Z5m#GFT1g[H=qT2.E^!2 + q`Wk:-)4=d(=W&*;rUcqXRU`tfinhhW%Fp2[f01\.slR'e]ku&QN,7cY[ + -UR+iUqq]23m*\mg>SJr]BF!+ + j%7*\9!lM_U)G5_r$XDn:Gj+A,2t+11b:Lf1UU._6L$G'N%d,I>!j2k28ENClWuHt?DL5$[ + MrV_,"X[L3@*'b[S(801M9(l + ci>\!)%qRsW)sHDaD`\)Vlql[VD2H\lcj`@.)[PCk7[KZ./LMIWC"H_Cj(M*#NW7BB]^\=C + (PU.(#I7GM6=KO;^b?q),E7P%re4CUcFff@/q3J:m39P?nCBgtq,`aLFS]XteS\pM%ZH`P2FX[eS\]0ptc]U(_ZGRm%c:Ei_!G6YB/a-]V + iJIrQ:T.^3Q)0MZ=X,lS(`/^<6[Dm'&1.Ys0089.TX!1pfJGoY4qF0f/UJI()>1\JuU#0l4 + Fk7(b7E]c;A:^\#8qdi9A!*l'&<1+WT&4!DCT,f"EU*TY7+M^gKO.DW%-5eAYaeB>/Q%^R, + d1DCHf)>5jc'Z">.+2esOS/F,g!jgAqTS1oQr#O%?5f*BP60gm)$mHjJh)'d0+gXd8r$F85 + -R(Z#_NRek1K$tA.^_SsUC8_@i\Npu=1+5o[@:C<,VqeU28`$PUdXZ=KJte.A%"3TcR_/qb + k+9Xod+*__u"3qZp?V2q*[8O`(^%T<(83DB1.&[`1(p/;K*ut92HU%7,7qNTT)cA:XE4)\, + 9+$Z[p(@;lA\g\3#C@]N6Lo-@okJj + bX7`?3A]bf)@)(C[*.ANH]h&oS6+OS^_;Ckqj&rO^Rcc80q8:6.^D($s"GKINI=S7(!5/sNcjYHG%?J#JOR4MKKdXrI.*-4\;lqCfUVTKaccEE?8CWUI + q+3/Ba#opES%WX.(Bg8AHAF_Am&oEB4 + iF&-e=sY:ho\UM(]3/kH&G:C=YI.]tWpZ0KcNm9C>ggmkB[h9.[AA4_-c.[4 + rB(^N@!#P8$6^'[*n!9?S/Fg\sk4^d&%<^_tH`/TfX.'a@qg+bcD3oFNd + gF%RBRPbiO1-,g\>)I6ft/ZYb1X<78U!e3Se?6*5bqbu.56g1k0-)f^Gm+#boq"-if2r;Y+P9#SY@V`T6%4AJ^NG + ,L9p:j')lSHu.C<%8=b7:LeodtZ9k\ITts;a%`s]t=`D&lL$i>FiLV28]%C6I2gs]mngXit%`bmNPX.&A4gbJ@,jd5lD1h(:W#I4;7UW^i=*d_4 + 4;I4J4eYMEs$"W10lICtK"tm<7rR5$m4%P@X;oRcqh*/on5=iaYaQ/@\,KIn06cg' + 2<+M370j)qQQ.1LZa_aZ"V+fbTR8i#58]YmY2,D?JL]i6#eJm8c"]SigW@[ + V`mQK8qgG4A?'#>k>g!qe_BEJ4K^A8Th>au)JrqJ%L>]rABRXcT>f-jS*0l=%!Jl>"`_dA40UBE8#nQ>*Z*#c)Wc + tX@/>U>3uA=*O%re:>7*j:jc,:KMt^';h[J`"Sbdu?;kE);hXX9#@:GB5YpKr=Tn_+jec*j + BMsO4HT4K$$9NuIc-$RK;["Rs"Ro1JG%9YmA7f.ZAo;/AIV*2HB4d0=AtEdMQ>C09D!"OqJ + 1(MuTKRq[>\:]eB.e4;5&KIXF(\Nq#?XiW&:*CJ?tZ%TkGEN;Y'alrI:ot/"Xm7R[W^jED! + 1LtkN70U2LW+'JnPpBk,*`D:5ERQM.f)13W77%S^6.UinbH2>p[tV"k2:&OUuZf?)EeL4kF + CCP7Z@l!_=ZoaINBO-j$Q5BrrfImGZ)S4^Kb3]J\cP/O3WF8mZX+e1RM&VPGfWlY9V[hW=. + H<0lD.n&^1B8Yo*f)X-J0f&&nl#P`8q:.LJdp+FTfeNU<>9V^LY<`O;?lYNHKSc9Po>@7g9 + ?l`M1/csSk"I\pi^=?XUg[DA;@pk<'i14H1G/QUo%;]5]YuV?!],'?'Ci#RZf%e$TrY&?3' + B)X'@9mq_FN-S#)*jG-iP$6r`Z8L3%:S>Cfj,Ph + JjO.de;5:%aItc7W5R:#c,5@h(6]Hn3l4/E9,M=^F"(^,O?VRG")fDX?f!oJU3]S>:$bA)3 + a:mdrD[U#ZnOj;k%"6VkCnccl:lr^SAs8C.!%>2DnK/[+XP9i+*6ct6&-5#5"?[,h] + p"^k@9rs*Lu#Omc]E1[BsmC,Mi%;:cZka8[YY!u_8dqG$M19;0qXg2.?e7q(Q2^*#Nbb5sla,ej`3!E:]c$ + 7Pi*O1VFKt0HZ;s'Yokk(DXpaSf[qoRMCisIYJ\UQ8rTp9rm(Zn.liCqX_X-`#9MWrNrYX6 + ^dT9rdD)HN=KFRZNu'Omj]=KcpX + `oBBT/])BXc6h[V%t#R3jnIIY@lESsL,DI3b,Vg=kdH41coVN5Asb?m + r++p-MYoYq;bIoDm3CK^AM0+rE?j*-L5kDVZ4TK9^i`kVgedrQNYZZ"n5RtE0m&5YFG$j%. + M29nD>8Vc,&oOMK'^Nq>[40kFd,sU\\&fH=*RrDl@tpWHs&nqN%Y&^T2\(RITj(ncq!m1l: + fFS>sU`nisHAQBC`>[/cQ]J`hS[koS*:^AOtm`2GB + -0.$[G-;OMn2@_uYLVR&k$jb#F]^ZVrs4SjZGTWf9ZqDBIX8Z#DLlh2k.Ti.i=lIB(UrHuP + c;;UP0-C#Frd-r+d:\dAD)1MX\(,7OHJMKPFT:$22JZmLdrW%T5M!H;r + qh5Uhf0FEDF=A:`9$QUn+#W:XoRa0HJO%jBn(%=m5"[S5t*=0JmBj?/)OL-MG_beQ,d*u&-7FBV%+\#@/I4%S6PeAYBd^7gb:/b-Z5u6!0ogr!0[2ht@e + J2@ceMK(Pq&&Q6D+EfWHrd@p!\D'88>>5jJn)T_3Vh@dqF"j[!9X(h!/fkBL; + .S_]as=gcduB3+GGDs,^EUS-<:'qfd7\iJ67Bf\_KUE(7rTRZ5&Okm$Pa%!,2CAQ5UF6gF5 + s0$6##0!n7R]4MSTDMC8[;DC^h72jaREB[pql]D"D]@R+^@+/Zah)L>rs-E3Hm]DEEBIS>J + "1.Q*QW6uO"&3Agm9.'F$/.Uc#^P\4E2B(mmkbIk7:)4FT"eXIu/c(Vj1cqGplOZk;iRspH + ?Q>hY2daF5S#:!PRK;a8_(YGlCL%DOdX)S`k@uO/_C?hN@eT*1kKcmT/X + sbrEi@QJ"'duhtMR)(Z"p,?aoS%TB=85rVs(ks$.D%!8m"mJ?T`,&+Qn+?sF%iO?A#PGS:4 + QLu3fg>\h/h1Bke&hB261&&IHq#iQID+6-B46ASAq@9bB&Yd,(+nYeAiPVe,6ef7&%FU\PF + 4GUiPBoTWR&>BS#Lr2h.7"P@NL61,GEB5GbpnT`9aT%-[=t7L9[2*5Md#5/VIuOK6(h4OpN + 00WCNH+t:@pEc.OhNbE&cOS@Zu_84dQ9sdnK%mNFcWF&t;"@EE&OK?@.s#[T@,#A"U_tJ/42'+I@KfoDfH2N/LE.V2;u*A_;@dMa#m88/C.2.iZ9dZ[pONNFckFM-J"_h1Y.) + 9&Jn*9A0_FE2.c.1RBr3.cWV8LB^Gu!ZrWLB>',.s(,^;Z60pf + eeq\(mKsYK/rLVbjFLW3`2n.B3i%p)apXlmR>@1=#T7qZ#D?c4g&;t+D-Uu + l7Boa_7If8?]6_0^TIIO]XOKjgUhHOmo)K[N=i#E(7N]*UJ#?Gh5I`^o[b#;rG9`>!+5X,g + "W!8^obc^@$]=")u^8^c2#"399>7 + Ng;l'+[E2f.q;UV"Ol%H-95R.Be-b/_gclb-B)OIhBiHk1EHA9ZFahd0!)\Ma0D(\7O^el9 + eR&ck5p[?b3"#Ks>50L4F4"S3lNJoGK^6&7*!+V&A6RBdQ?(h&rnG'&LYjr7s'nIQlA7F0? + p5X`,GT]225d*Y0-7C]UsoN$j-d=s9n>pUBK1hMa[[nLL\fcK1fSW?s#5"Fi8+a#&deC$EX + 8KC_Tj?/rr3kna2ks_]87^\LZVHD":>H[(b]brTb>>+d?YiY5gf[CIA9W=)Ya?IilZTi*I& + hc+#SCSraCMT(^\[LZ8i$Hj8ER'Ed:+&a%SPunhDOK\!kH+9I)n`8;3VU2M%0%lc%Y7H&$s + TZ!%&"us]beg96g0dMU1Q37;190J%%e!(`bUAJH:Vaa*u(O+?Be?o@7k6J\#'B$O;na!:!Q + HmV[#/F*5e@i?&gR"2$EGj'?GJ12+OWNh"dSbS^+6ds/J3XWZ[=UB%1OJ\.:WmsqKeVPR&.6?#k\<@A6P_hr^a\rV-G + h4XpQ>M);/V'.hMV(b\QuM'<;!+1*SD"PBg.eOW9rsg9ZOa%J,iVSRYm/u-B&%A9h$q[B8` + 2"Rkn21mT.qPH$1-MX]\]nGh]gG)NV9$irnkT\Tq_8=Xp-RI$K022WI/A(CFEE/jA?2B&p4GV_Nc=K:`uqHN)6&jpHo-0p^t%P31/`4c5ioOetARI2^8 + /)\.n/&[k!J^?KXOl1)XTY,<8"U.3PUlL,/N;:b65)!fN93psm+Z0*-)koGoP*tdr`;=,Hf + o,k73+4:66&dKNL$Qr=V+Hd6Y;A14:&L#R=+m!b7&`>_sQLU#j^9<-sc[@%!Gk,clpMrPF7 + E"fd73p;S,nEKl7iWNMl2dOr!R`@r!>H->.U_3"%flVV5U6Cr-6/`1+#6>3-?CE8HR$OXp^ + :jd:PC6YMYKj6psPnNmuo5^QML*Sq9lR`n#F2:])%@$qbOCH:V?SN_Yb%lr2`%5/2mHC7!' + eL/e?mYn)VGclMc`J,*2;=b\hZ'qZ&DO!##G7E")f+L3`8$a\ratcl1$T)LUnGIL##XYUKY + C.a)K+")PXQP;^Xh5m0_;1^qt=ZUW3\9a(eu"\e5>:ghUj=U&qRLQ)B?aGU9T"UC6/MD0Ga + '6a^Q^s]HIdVtH"0UC0!RlJYXe&_[@E2\kt'+57N3XI#=E4\/m^C2]4$uKhinB@1mI1;:q4 + :-EG/WSM@OZ^aGRA(1[%AG#jX$+i',7se-$*`0(![Xt]-%]aa%ET1kb7Q>MS^#[?Zj.3&T& + ):7&htl.YqY%^1n]j$64l>R<$rAW7,X:_'WNS?<&W]t>2h8A1QEn"0k1u]Da_KP2XW^EFB5 + 8kK]JA(jY;H+K:W=jI3O4$%VgYZC5P//T]ZoX)DS308t+r5YioU()cW!b%[BVJ]c9e<*-'4 + q6t5H2X!/7]+gMh'nmh_4j\$429GG68<3o8__b@GX9[qNc'\?%NgJ(Z'9pFO1<9$`9acS3;[UUM'jT3#aYO + f?8)#d1,6ErQ1=bC-'/K_]+Z#(n1Xe]Im*c*fq_ZYu2,IL7E+<:>>0jNb#5/]gW0mm-%Z54 + X@M=#GdsX1eNAu)73)bkEo8sA<3fkpd$I)@,p.%0J]k#Ap$jfEc(09$#\CXbg%8bR,2J3K* + _[[@%%[5(!.@E>(/8G==lOgsVT[7St&6aWP.<1b/5WH6J7:DZAnV0P^SAP: + PEFVdJh2b/"W=*<+pFr+A+2d^`0CNc%_G'A"Nf6)9RGB_Yb%93S:<4*He;MTR]Ki-I)oH8;^J:T%4QjKF]o"]8N5^OhCL'Wd. + nu[A+Be*2%Ihd#TeTFqka&j/U_D958ZHZMEP?AWbrDf7;lq(@t)_RL">(QYm$s?d)AQr0Ea + #1P8289,+aY8LR0icOS("Y1jfePG@13c_Q\5tHW1k?3"L'S`*0LK2Ioj?0@LD*G.n%I\%=$=RQ>G4kgC@0.%[MbO$@fP-WZQGF9Oq?$YiWQC5Mh + &f!.N#nIns8'6_3T:&q!88SkY+P8;W29J(ek>5\.0F;:?teY@^2V(UCJJljI@Nk^2=]7YEYa4NO[GX7!dMTJs + lKU(96=ArN7YD'CG!uPbLbr`@F48@F4;omWOiN_%s4]C$oR + -h2HnNKeJ5.LKr;tE4fb1p2c&Qo7.(8GUY%2)eUC:"e>RoaBN+$phi6;@I;2Tq97c'*=r0j + -RQoKe^=A2Fumj#KhOd;p+g=VnVO(_gJ!$@!>;D/`Hk*Z8YRC5nQ:I-/q6]*%pKpE^b;/!s + 2GR%(sn\W<,r:NQ>qR?>gcq:2!\UE1;N]p[?_Hfr+Ilb5IL^74Z,g[i1LpV5JU^`30d49*) + qZ^m>kS\Yi3QfiVi1S/nE,C1eag*U%WrL\Hj_V?nYJE.35q'J?R!e;a?_:Ti*//uFF*Zu!p + +;6))g^q+I_I^!h(Yqn-*Pn6`#/7@bGB7(V:ZA3RULb,(rUIXV + O/UN7pDl2X3-3$Fo$:Cg(\_YrI^PiW*I4oqMg.U^Sqbd1&+PDHCH[YT7r('?B@+GQs^h`\f + Y@$(a6C&_+GMP(_T,0&:kH-JFjB2+q?m)&E)mc@2KO6#SQ)+&YVT*@5>=r)[ZQ%@lsg:N + -+#@Q@OLAK"LpkK(-dB->cM-,rHn;3"5iA`fneeVSf"/i*ttbf=N:iY=$(a:N>iIh:u/C$h.IZI + B2ItY@<<>0i"I7r(nhWUiFEJ8";G8+%cJfF!8%k@g^ZUk%cI$o5me2i=;1d0)?G.$Ja&4Ok + HcbNZY@Bm'D]!+rNnJOh(]kUR1#-3-UCJk[<*@Mfc_$/(_qU4*C2@mTpu\q)AaOe&"uT)^u + ?!Be.&)m%"QV8*c_i6C_?qA'-QWP5su:#&f/;=';;-8Tk#1Pd?BeWUR=>74+3>R8!J@S72\ + m>)iJWXqDgX%j]e.`fk!YFF-a1[]"ZX[?In;EX-`^q]DK9_)2E:&aK/O.*8ArXUmMjI7NR+ + #&YZ-6+S$;@h@7:n%04Ze@)<2-!UeB6h+3WJ&YuD3heVWo7g)g[1uij/O6;3LnWSUIodJ@7\jD + cu`ulbaCP`Q8TM)R7d7bjR-/TVI!N$:co;F2D&cVD)S?:Ai-:q%U^rbbiX#tm9#98ZkSBVV + Gf48ghujjh9Q%G^u'*UOf?RmHU"1Mkd5Y10*tIS<@ZOVeT/?2YurX555,H$`UNW^S9a2lJO + 6).Wc->j?hl#o#qjZ([F'9!5Ae1(PKb"I + _h@73/:V0NbCREX(;!PhPh!u+/.9MX<5Ud5K%_khV123]b0:r=GI@N<%TBW<^#8235\(!*[ + +Nfc_;"ICH,Z?2]e8hsfJ57m!DgjdK;bcr`mZJj[lbQgPT1*>[Q_)'?jcD&4R;['O?2q:m8;B3nUdWlT!QRFd4p[2K + ZuPbYmQ#/_[N'X_JjH`O(-LQM%_+>IP'VEEoCrg4.oP:!n)h[q4H>hD]HREh/Pnm<[X`ean + 5s(\RE@?J#SaJXhL%$%q0g7Mgbe7iBg+iV8EVM.3ul08\^KOi<,dL]0GuA%:dPO5u'39p-J + V:0)k7-Uq=,jk5B$R2d)nCk(Ucem9[0ik9E+8YgVY;4GT\s.a9H5*MjXH?PO&S#7m6WTmZa + YH)G"Y,6(VL]4ohle;WaZ#%#Yrb\0P7b+m5HZn/RD+<+<\f^(^`Og%-^@T'WB$%o>n,*[s_ + /R%7%Y'rf#8P2IB*&!I)oq2L>8cJc!_J58BW_ht_%6juBK[&s#D(fpC)[E<5hJ7670.@=)F + opiDP-m*D;+^^f4o%>L];_YpYH.1iHV9rVr)b@3Oe,GVOs:>q50LHl(@+#[Hi/\^U*?TAr3 + gCqu72CnU/)!^hA-<;>t5J+(sF'rVr"OCS1X)mJ?h^fCSt`k&An$ciO0^(^U3#1]SDt+p8? + Pr\Iqm%Y:BQ#`nLL.n5&&%AF+H"(6]`!/MA3C:"!A+9i/R82:Q]Ho[&"6nq/-d3$`JJ&<[J + 1e7>_L="p!_jG]Y`e6O3"!Si*+o`,Y5gP7k+sY$nQndSg:*8L9B13Z8'V_C)(su#X$Sf42_ + g%"C_#PkaO>I4d$Fp9>KG=Y5WZ&fPNtu4.A.Pr$1<9<8Nq(b1N1p7mj64UGncWCB#mNW6'S + );9)FP!j%3l&kNfjMD3CtY;,il=t)"L9mMuel%jQ)nrZ@5YC)+[!2pr/KGU/T4&=XrN8^)2 + M%=A()P08:IPQL`a7&+Ouujm9XH_[(aL_-f:A+G^eALbac_J.R=B+Tu2["G!-_)o>!n**,+oX%s]d9KHRZ`S;r + XXSO5FUl=3/94X/;9bsVUXUpM\0Pcl/b+,[/Qau)LjJe?Y+i!tOh19nX6" + i7ecrgFU,??l1./;PTM(,M_An2aWX4513QI)<#I@%06ASL"CFi2g[:H@6$*hp<"UI6rTkk5?i*0uI7BoG2MueUiRN3us.MH9) + F,,;6/AfJ2&>85-"abt35X5UD[iqDMn/5d.&-(aB6L-6\$ja>*rm#\,tCND!-S)?F(nL`fe,_?^VPd";nbTL9&OOA-%Tbg)/E<%: + E3&[a#,DM>m7,!iFj$34$4`b[u*!8nh#5p.D,];R:-/l9QUUDmg8fLmoq(;g7k%:R%.LO^E + gbGs_;i`o9:EF(69pr>7*1f'V1^)=h5G7Wl,olr2<-H-h&()ST.N!t`"c$F"rBsQKW8*Q$s + [qI:CE%VF!Ih@1gb&;!PcD + =+[SH(R(OgTu$@IYGI]2Yc7$+/ebfRDsQ^!%S57b7"E13.YHi#.QPM[D3V&"Ak[7"H]Q"UJ + Q9K[tF3W-cUDW!Te^h[)h*k2qZ!JWN^LA[^fUaATPOQMJd=AuFTX$K"6qWAqqIYQ%=m@K",K!kH8u5 + ))PPpnU!sIDarc@1+i*(]U)G=D![]*p-p]%)U9#$+:gaNI2M6fT!D!rVX9BLJ#lp>$!oP_a + \6=U=FeeRMUZ+VW!^]3d77&1gUhh9m1a=9W8jZu[,fjB1H;14c;5FCFV#".=<%Ok^/L>^HV + 5de6h5]DqL>F`6) + YEL`%ltD.YpJjM(#B]6t2+R\rA,DO!rpMKD8OLGSgK%9Z5^\7-%.^cko1[9`TtT]=UNb(_q + UH(W>*079im4KMV^e(I_k<7@[Q#`)t!#>(Lcu`TS+,r*[M9@E#Gg!K\S-,-]'dAB%6N.pQ` + ,EjZ7i;h8Tt`["t^W$01)D8rr#7au2UE&RqE^FV3^[W?$gE=`=q=+fNdJQ+TcA5[s)*s(RX + %^T>oN-%S:riL4n2r`MmXE1I6)hGLJ7eD>Qm"ECVKuWUY"Sd0P!8<4$l%'Z`JYN2F%!WCu+ + G'kQa4QN-^e9G"1kS=oJbU==o27r'NC?)BJRBl-GWURt7Y[_"afpaq,_JWeKLfXEam^IpFG + LMj:CVptJdYN!@9&jH$Na-MK5aG9TPmE6BFjD9"*cbY0H:,/.KDF1J6\nYQi[71!s'e_J\7 + k4FK:*PTZWY08^*c!KV->%LQDMJ#>clOQp.h*"Npfo[3-%j:e/Tk[YY]kMLZL(35k^(V#7`mrKBU([j + uJY>l_?F!B5-%cntmO706:_5_=M8.>'aV0GVr;6AIgLLYrPj^^RV9/-=0P(%[2Vqf0(Rr_M + "#cbk[eV%h7DCP!ZR!aAV-BNYN9SPc/PdBfCSoc1OP*BFk.\f7H4CSXi7N4OpH"b9g340KM + &PWKhj4#=8^Ppi&F5m/l5cQ+O@#Qk&\".K>$]]CeEo?U>I\NM1b)sP!u8]4P#e!sf]#5EY# + GY3Z\'7i[jd_c*n:^7(N1kWkXL0/T44[?_QjO>@5^2)q360crul9;mN^>\[th+/V$mo6(O+ + -H>KGR,kD7KqFQ#46u94-aIcJ=m;Qi'7G3$p_*>^tKo2\g'K<&O + ?aM_&=];1+S(B@L%uN"%Y^8:^>NZ + ),Xba4Kg\goW-h+REf%_oM8Z-M"$e_A[=)0)TZ[lK`aZ6n?'%O5\&O,b/#Y%OfL+$O6;?sGE"NaUD9oZL$ci>I4!MCeLK0`Y!p'FuQ\28_>+I4Rl9s`"+LH+4ofQ>>f" + b9\\e$pM%!&-7I-AV1=Y$a:\Fe(KG'S/drcB)!^oq\_/V6rai%LZ&CQjj&B'AF*IYl']:n4 + \MNEa)N/K[VI""5mZLgGp(&6Wsq6l+(_3F]t4.;KF(!j#]QF.CP`=cPn&X/1R;$?qr4UL%I"Q3hoi,&dTc + u954/P#CpeVHV$=[IaK2Zp*['hhTU<)i6\^[(k`r^`]45D0kKJ7o/fL,K[#)M,CAKt(KF79Vsh%7';dpKYRfmIAUQ&-@m9Q!J"#N3,2KrC=^XHjn3NC#%5<%EFp1?Ai4V\:1V6&/QZ>0K + W%S!70\BU=$Za%:k3P<78qgml6eGt*=H#`46qB_"MQ$K3fZn86eW/;\OQ + WI&fe.X)4d=W*QE2*nhk\RhO=LC25V0DPB7i>1/ae88OG=k@:;*DmqV#V%J);IOh_<7-ji# + lLX'"2j0T/Q,5W2ATK=BbLJG9p$lXOYo7\reUYqkaQC%2-3L%T5l>\YB/!cU3d7_arnX + Xr&$N4t\DSso!eSE\n%l]/;f;`;sCK3uV7m.M33"VirP'fPI]@6W-RIKOFcEo>6oi-s;9G1 + `$%6\JciZK`$P&8;?YEc#h\L-TK50Lpeh:>d2BiAT,%6"t0Ojd*X,QRQf\N>PNY)t'DA@GM + uB_k,9OK&RLgme.hA`Zhr^,>puXdV^I2IOS`d.%/()D77:)?7]R:q7Q%?;^O1s/MpC,rOkM + 8,WKeY+=D)ZR*`naKV2$CSiYoG4la`Kj6f0BoBTA)6?&M`+--fj8bZ30=YUCUeIG.n!+,pa + FofB!Ji<^nC,,ei^`'&k<4buLCM8YjE./ + nDBa"9sPH!(>s`bglOZtF$rQ!Sm)7kfFo2U%(u2hQ'IJ<1';h`3IU$NRCBoFfqmK5'%/bDT + E@.?;FTFn`:\go56FX[r8kmk"*0gOZRJ!fc=6Gft37C\5E-g>KhB:JlWB*OZSJ>?RkOp + NM7+gON,`oGt^MJcLPG_p83J"6:W(f>?R4a%b&`>e6[U#GG,@K%?X+/G9InG6RPmiE_X[3X + 11ZhgQkmp.=Z^TXoL&U90*#&<.CQ+"8IC"eS;#[%LWDl6=CG84>moaL-srt(o0 + JM'8s[-T[9&-4WfbVA5YDpD)CF76'TMn_!H5HP"]Id3Mb5C2j&p7@mpHIh'n6.,;_Fr(5#, + 8(JBRW#Qt;jW)RM33i(OP%h$d`P`""+2]\pBUsb,e;'t*-L.'T3A[\NE"U!*6p%i/KsU=!l8.INQ'qa&W0RDSWhE6FB*Y4CJC5H:p[O_I + Z%L8Yj!riI@N5>P0i<2qb?4NH^Hg`Q!f\E2dsbf8&tf8"Kh.#k\p;ogAZuM6C#`*r1<"FFZ + c:Sbo`%+16j!I^n.Z635gW1:"8>-)N+!A?;5A#X`j*I#*642 + sI2tijm"6CI"t?;!&qn@s)+F+4R4#qk+V%&O#!&,h6rM5T$HnUf[fsS=$l(Kj'-$s-^QV%c + reQ.[QI-G$Oa_?lVu5d@::%LRFs$>2q`[g*%08c@oIU55S5#A/D;.P%_LL=l"i#$3QQ&qgMo)$)N+/BDr6P)d#!3KOItsU26IsINsYX=KX*2TB:kONFOs$Z&I` + *L-q)qk'=+ZJ`)-qckg:+c=\'9ra:5D\VWjWR=LAE$XPjY7,e6$0j&---@u/,B\Gq9BBa_V + OeujUq2_CfBDElJQ`GauT5/rZ"p0qJVHRN&jqc$H)&k3WZu'#]K=(%/ro:QW#!V.\(^;_p] + ^L3uq7lCR$3";!]H7NuE845$`#$E:s2L\ifQnq2]W59=!.ZKg4r!+pG^o>*q/=UOI2>F[oN + ab$eTHZCCgD2>JS\Tah5=o>pB@a7h&lUSC0Yts#Ck[(j$;.&o%m!pbQ-aE?@Ve-LS*f,Sp= + L@+9'$]m!E(-\a1&b@K5i%inJG!$Ne-KskYeeZb=EJ7De"T%$>[#Ga6/Tl!>m[-,%,F^B_$9)@=X3Q1)IhFB-WF!2hs*T91i=t-07 + Ol-tfdY*/!/me%0:[8Z#Z<\Rq6U_IJp(Pm/C"]e>J9>"!EA@8'Wl0d\q'7VOnpV$WPGk&f" + =W2qb/JD=gg@2g0gS9iEtP;gkPf,K@F!,GaAg^UD[8pe&D3?Y$6!)_q-"QA=Q'jO6bLqe2+6((?NM%Bs!2W%?^h=1/!H9 + ^4;bF5f6G>;=W7(mJRhrj["+Zj$BRj1m!QH6u[`]ZEm"9=r(/DtS;1dW*]6A2%D89;p"QJ3 + "TlF+i`]3-(jIlI,MPdU:LCl<*>qN=(Y-$4.='k61_md*Mg>4pCG4+3%_0D"Mj&1e7rSD&7 + H<2`\4m0:qmW7*0O,ts-B\<0RF7,;k==OJ>r4B*nb(!4X;r,(.A"XM=2=MLe??oFH,ftP"A + ,c0e+;&V8E*/+HG1>@c6>HZ_AeR+lpR7$">HaEg'I1[2!YApmNGpWW+Y#J08DFkFBQM$%>! + QES`IZ3Lo+FAXD%XA>L+a9?)^CE'd_U=As;ASHV2k;/m>X(D/NNm\Z+0EV^ + mLnO2.%p8VZ)7eJ)0pZ*R?\'i(co97=&,0iiMq(oqJ!YLW%LaX[g/XG2V=ZS?b\LKGQ!^ls + $Z^g[_gqa_gR7ug[%U=D[L-$:.KHE.dT![as*e/@C_K2J18+,:TdsCXS;.<@(Fl`nQeeh + DJmAZc?[DhKRf($qT?ahH+u@`>LNU\5BJfoOKcUMbVqe+%Ka.**-mOB;kl(Q'eqF=rBa+!(MDB+nTc@UsC@6+i%RKsGo$EDK,"_,)Tdpf,^uTb9/_-R + JHHfFe6ND3Y@`VgmAa:#l6%.Wk.-n:>m\BAPO(R_3,^&p'(M-#A)h7+:R`^+p#J#!_Q + LJT_K'!4QEI>Bq],T6-Tgg2I6Ii!.YmeK!)6a#eoLh%"QV7N`tQ4AJaYhk%=,FnVFNsG-mB + nE@2nI@>>,*87!DQYs:8NTM,N>=pL^:#2kXL@!E"YJd>bg#N1+>YiZ7b?j7a*i]p>F+\?MhcDRSlZ%(\(1+Xb:rZ5@tecH6XFrbg@'+ + o2GZH4,X3.BgUQ5[FWPN!@(6$7IS91-c0h?j6UmPX>-IVS6/-k5I$T0eVn-DY#1\;Z+D-/7 + D'`@Hmr&2?aMD]MNtE8ki5WX^"Mdn=+F457@L4/n#,%X(/?T>QBEhk5"b9VZ1406,i>n + j`MWh@(Ebt_QE%38>qo3rr_S38t,'*_mL8M^:EagllE3Oj,GuETM+BerAL;,/3a?ZC*!:_a + Nn-OW2%Npc;i7&!^GRHcA"e^YS!'USW^0"1#,"rCiK<-?eJ->pK'1Ia1'&==2,pm:j%7&>T + _R'tB1*";V($18f1;:]Mm"V1=$NUPdVr%9erqd@S0&R6n-^-2meM"6--1_2U1CZ%e&M[3%1 + ".:$;^jK=j"h4D1IDg8;i#JCN!.Ul0F3Lh;[GJ5'/=PI1%QVFF"9$+]/)*)2o=B1'$n%'N[^W<_^NjT9*Oe0PI-o]a$6/f]jO`d!46?5mUH; + #UM1Xj[p;g2$j_o$+/dJ-$os_[gE0D(.!`bJC"dJp^V#=:_`e'2$4Xor!_NG"@,EZj$X.QL + '^,c;&'OeS + (jgljlArEih(u^pFKbfFl"Y(1E@E+lK8uGT51o=WshK'p]s7RUH6j:*H&8i8tTD!l-ffJAMh)rY+6M'L@s!iHcNU[1`On$UD96:[SpJ//(=;+9CU@JV#!&Fr2"NDA + M,I2WtrcIr3d)(KU-G@K(qraq3MI(Eg'mJG'.3*$AL^HDE%X13\=$d6:U^hTCFVdhr2c9`c3DkDq#%2*N5bJH,kBrE&P#5hlgB8;[G8LIcZSo3<9.p^^eR$:(Bt?q^^T&e9^5!n!fQi]"pjB.@XNmC.T)"bt\pr0cCO?!Q-\IXfYZM^,^=F$Veq!X8'p;e*JRlLY6R1 + '0X&1uQMRA):$6J7cpOiA!0@YN)jLMOp]C"(TX"qK$9RE#"9:[k!D4&Wm@NNAOf0 + 0Dpo0.g6]2R?Mg2gb35LP?OdA&RNB#OLVL+m43/f!M2EH>#P8T;M6 + kQSa]"FDIe=MO65C!MQ/)_8n<<"uJU'%Uf%/qO#X/h@(OJmK5e+5R\RI\/gFfVL#XB6g^a3 + ?V8[IT2`3&%=q0`btMOc*1C^)uC;Qi0Xi/Xu&E_&n+6KTunJS.C.\YZMQMbTsu(ns0K=%ZeIdQHUYuje/>Hk`JD:DeX65S@7?3!4n5Y_nW+;C[o+#T%fFcj2!Q!'T==i%_\>F6>@Qas + s3'`hVY5`,j*VJ!]3$37YFou="3c+EBI>q3BjE>r36g>PmS1a+%V!GZZ^bUbY/$,E8D5sQ. + COr8hL+8bS:Hhh)SrF)2s^O_Uf63"`J$c%=2(W"L85iWPg\dKXj%P^dp[7h+JrW$1W#XF7S + _($*fnen/!(r;\SHq@sU9kTtm+5r"nHr\.U=2h(&(W"r:!6,PJ-MKm;$/hTg&U`_'I2H"]( + q-;[QEM.nF_`nEaYp_`6O+OFFppXY&>:]iJXdNbJfg\2(d\2F4L`L7funQnDLBE@>fgn(c# + 1AtDd:b0SCPlmp_89W**d.gUA+g:i!Wr(&,.5f%\"OV.3DVE+*%5Da+8;[$l8C5d/dnrdJq + Ub(*DLJd@DN1*>f$SIBhg!%NsgH@:C!\s3nYGe(m=>I688GA?C8UeD4!PI9"f`?5=^aebs" + /!F?N^A(KIOf)9`;Nk:c=ud$t1Sh;aQ.g:FBT9mXq#?+Rg.sL^7D2 + 1kMKPqB3njKJ5@nf_D/76RE;9l4r>]0C`P3IEWGdZA5rfJp@kP\6^Y40E?/ZK/sG7_46;^q + YYmu'&gg&TaL]Di!Q-W&,j'ofV0_.r"#KHUk7;&i'%=n<"mscm]7*D5e[WB)r!f;%cHaj=s + 1fgKEK"P"eaQYJOSDn`$3#M'#DEo+OCdd#r^d[ShB9dU1t2]neSSF.^NS0 + 10';+%>>rgmu]=][H_cZE=+qOd*p5B>+U#$-,V@TkN=?qfNT[D_Djt\@;ps7(C_n5"O_-^-4%NiT+<:n_[pG=llJi?N/mn:YNLZ) + Sc$]H)A0`pbZ-O&4cMfN'KFPq)WluC"_b=oM)r]9)Ai->TmgJ.1&:3uR>eF?<2#TsZOqjmroJ\bb(pG< + 3n$@fOYEm(32KQG@Mq0,lOO&ET<:&N[$jl<[L4QR`;\b%lN+S + ^O1_BhWVIBK/6-sEi#sp>>raHaaZnVs+0]Y,l('`u#'o('+KE?\>3Fl?b7L>0+`Naer(e/+ + "?Q,:91&i4`;9X5+8$W0p?^S,5M'9sG7#rRZ`Zb@/8^_=#@EHrbFrgtVGYf+Zj(R:@'r@&B + ?C47YGqr(B/C-1IV@;-:(M3^J3)3D2k;sLOPjhsS;rRa>cdV7\5F3\ds$@ghWH*BFj%13o! + C1a!hLL,!$r7R,iGMqno[3YB(KC&n:Xd;-8Q-DT/Q"o8ZqV*:s)MA`L!b + #XpJ':iQ,S1/Ghs7%lZun3M-#5PD<3Ca_tDr:/`5YpqB6!+AF)fX7 + W%n8ePb!-c,HR2F"Nb*;V:iK=5/?$\UmWlr`pCVY![ukBMM!Mu@D-'EmCPER]1o&7uf:SdI + ?nMNU(j-Ys_E[6ZCaW"bBd-Y6!R+0C/E57p"i'!0AJadQsO>-Z6Q2Z28?*Bc:] + <_!BVFOe<6e!+X_(/6G=HPOs>??nDPU$qLZoXSLWbUDokf&gBjF7U!k)Y%c+9EBC-`IQL;gO3PF + mm$\`qa(K2ek%,'"f&W$=]f0C1;Qd%bJd'@OW42+U%_e6I8"r'-+t1'UE_@j?Q'1l + :GL(>_FDH*4j5]/OKliY0=UDY;o-68J&e/;'(Il:4@4+X:-oTbIUG6jJhM+ic"t+7a;EaiQ + Y*LM6^P;Nao"=]$X0<2PqDVH1jM>q&9'mN;#F>0BKS/nMu5QLa`T9Bg^TjqPY!P4Wih8WH$ + t9Zrh;2J#SHR5A`T4ZJlO[E>;1C:.1r5PVO>I2u70rui]TI. + Q"2L'O*Ca_dR8ZUh9bjE[L8*-8F'WNT@B)6Jg=I9J?uB7]QRhP."UHgo.1U4,3VN+1g[h[h + %aDaMf+dbH)j,-d)GD1P]GQ:3&0/TtOr0rNIY(s($r?L7%`.L*TUW`Jmg9W=LedN7<&],"e + a#T3Y[._mlj^eP86#6 + >u!-AGAY-q9q@+8Bmf-FD0VE<9tnR#s;)AF#+OU3X_8#sdMC!25W,LW4r=6XsP".`X4r"q^ + J_KM[[G<9g#RGnj<>F\\tm;QEhJcR14HEKjJk;cjo;@qBe?Z5-C)=j'*HAbE*kY>P< + QkKRcS9)IHV6no%VK[H><&Zssf2QI*Xl&545M(rb!NE^mQD,Xqbf"p#kY+bii6, + =\Q4f8NO#$'EIC,X=X$+bsChk"Vgt0jk;r/3!u683OCC;fa-C[t#>-+iH@_g2A3CNb2>8?> + O&#j#_/FL8H5LLtN)#-o\g6J%=:)b1UB99?o>pZDmf;8 + rGk-iV`M#+:jprRX"<-fA^q^ciNW$ZaLQ)<8^c/RM>Qt2D,mS_]Vr!omNbmOD[!J2Wg:L^. + mCp'X3u9(,NJBhkJFt7"pM6!6[SQVm6U)suF7*>bIaTfr"jqRPHVgJZ + !o;>'L`TX0M?Y?+ZcNoH4S^%k,5JRh`Lc.mZ[h"H/C^<4,7^HF;'F_)dc7M$(X[:RS$J)>4&]#C=3j=SD + TF-4mqhB.J%WiNtK&Nj@)D2rfNr\Z%;"?50ItgMaYBY6kn.1(*,(TG"JNu6ua@_Nab^c'T" + TW$c*%;G%$El9:01,@c;YH41@>7U._Wa&b&:/mU'0$m0JA;SDqYZJ72D=Y#0FTop!?eTs"@ + K3n&qiA$F?L89/!P8_K&A;7igg8[3ma0oSc1UD^ + >+)[4G\Z*7I:i-p8k-\GSHRQsZ&(iM/Y;&'`i'i\9j=]Uf`=u(A,15T5#d9\T-@29M-(-Gt + ?9C88H/saC/`\TCe'/IR+1`%Z!ZXd`*FD3OfH8DgW1TYAuab3.:Kc"0gMJk0NlVl.K^_`l>A + D4fIC<:dJBO;$C"#R^<$E_"JCkPZM#SJt6A-Ik8-i)s(j`\#1ETgoaJ(/X,]C$mSK$(Ba[r + U+>c8j:Qq4Su`R"OPC4 + c+ESl9g$m!9[_0iaLj=Gc"^d@^b#WkX8jqjL@BC6eC72s]EW=i#8t"$&bUbf14s,nbnSN"9 + ]RLAcFk4\-k_+R>LWiRf;QU>cDli:X4^8'm7u-d4iHhbqqk0ghPll]cSCs'&bWjfSgl8OW\ + `e`Ms)X4q7_4@.Z-s/kpX!kc3Itk + IP#OsAHJj_sK('[(Od_a$p05XYe'9F!gbBoElZK%Mo^TMbqiA&-V6/L1iY1=Zh + ^*\CjQ_(?b2HkX=^p40L!#.,2p9&Dq7iDOJ2Nr0p6e/gpJ*fhs-A.k>)!uSnU()b%4GC1qf:XW<.Fjj]IE,/)!S%,fi$s>W5M/,[ + +]X2j)DIh1#hOYC"@tpYe!8A?XUYmGJ63*!c#pDJFU$8LU+EEL1.OuI5>-'Z=M9Z]FcG&@S + QPg3g&^m#*MZe@jB:Hf\Ab267(r[%&5^e9+5do)AV5Rg,>j?Af6s'Y23=OaL"L6tf<'lK4c + 3ESYhG&&9Y)t&bi[ZMceG8Y:0c4O`8r9bh:q;H>P-cVrP;_J_?Sq<#FS)4=nRAK-N]jS>^Y + '&p`P/;omhPuJ<4`*3.+r\cIDgOp`]bqdiJf+cD9d^9^Y'VmbM?8:D;R1$C+C;hnA[ + RL],u`b*dh7S"Eea4uE!tA`5+7d%q'PW(mMR6m$(LYBY!5%J;t1_i(Suhg3:>?26a6'aO[QNC\K$PMr8R*Wc$^;7m@?. + +@)o&CKhV;@F1G5hlgtHF9+BKb.2f<@'qj-,H/\;J>>nHlc\N*E&aF;R#\CW%8$]Mhol>"P + Fb!FY_Dh"OfON6GtJ)A1g4A2rUSmL)i7+#VlMQ,(e/p"3!G9X>`M+6"2*#@Qd'-3sDj94lU + $E@XUke4t0s"7T@;qPC"$4Khk+5:W14KPT3E&PuH]`;ah!f"k\9TW#Ge0oq,n+'sN-B2-Mb + (>;C(X7D)mUh-QBW5#r,#7K$[:Cgs^SA6)uI(@>_RI:u*0BuZ]Yf#;F2]kp6gDS36]g4cu0 + UqTH16idi2!M+:9Fgbj!]iSq1gBD_()@V19(BQ&/#L64Xc<-Lg)1dS\j'h)#J-Z.oHe:M^+ + 8@huLfIV8KXuC7+t[a;e9(bq$NURbTEk`Y-<]FDZ/tf4b8cgZP"($g,[i#4Lr#l)R@<94.K + Z7m@H%^9E'.3%Ve!K5B-q5&ER3d%BkJc,Uiq"FH3%Z]D.i72B5+MZ`.4Wj6rNdeKWn('rM* + Jh#%-(.JWL_#<5n")k2m.^kHYh7/#[&KE9Frr3Q8KP/Bk&0d*@'Nkrt:mIEpU(QUGg + %NPXk(7iZOnK`G#)NgXU7eHDgEKdAhakZ$eQU-#fHl9:FXp!MSEf%iIT.(.X^?E%\!fNja2 + h5'UI`:RfqXp5<+C@_^:l/pguSj$H>?8^_u:#mdOrG-93:Z.,eF73Fp!T'p]?CgCHAHgr6p + >4m:hY$@FI0p!o(SJW"K\0+T`V%V!p/*P&+/"PL!XiYa9-&L2M6fT"u[ + 3)@dud]1r?EY@?!25KJ2;])aaNCdth33i-3k$8tBW.!AG?&UAiX@,%ie<[Vfi#?7; + h<0eu_[OUa;/(EBLimo[!Q7g+ir\9+(eMdIrrDI,a!`_ciKhui@?j;.3!=ZZBSYFoNpSX%TWj=L]0Q1%RJ)r0P!=W`ai_%9kQ7g7r`<(jGdqEc:RPrD:\$Db,0Y-e + ;XVpnt0kr"P@h[ruCSKQY71/R,r?65WsbP>\/kIjjq^%TL=H + (B$,lj)WrdF%o9!F5k?okFOrQrb"8D/9;Kb].!*r':8E%KUs/h4l#"ai6<7gXAndlM>Tar9 + ]'(C*'e,5mG&Em5phpBcH@T?++'9We&0pm3]lkK=Ft:&#CXULi=C"7Q?$<4AK^W3,Wm;H^? + )?@lm.4ulIU0g_?06n^r:XG:qJ/R-CL4JV?\N_qP29p8hL,H%(;+Fr"Ai"(cu-R5pPiu*NX + ,Li"W13n!^pH.12L<]?YpJ6JAC'6b$UgXm'i>WI`r"fbQN_pKn;\o0I27Yd,67ui%!8T"pF + c>g20Z9qDQp#5T9a>+mkHM6'_pe]&;Mr*:;9*i.NdRBF*n?XN^^-1$G5CI;tnGa&jD\;%`' + 4`h;nQ&*Wc"P\+(M&:],^%Nuo'4pRE* + n'Tk^MdFK)dS&q<#nV/hNMQ!;I(UE]%EeUkbj7SnJc<-lb:=R\*qPro`SmG?:#pL?442L]H + M9!.mLEd!.r*'aoVMKVO6!V>@5m5N7f=XBmb%N^b8@\GUM9,EYQ.13,)ddgVO85X6G1M*gc + r8`m@)1u`@4MO)FVH#3a"==^'-<-dQmPo!";>?!)*(qa-D;CQ^V[:Z[JX4M,elK$Nc:T2cX + F^#PDa6i=*L`9]LDhb!P=Mt3m5A#L$A%0;W1He@G9a6i_;W!c]QY_BPK + ._6\Zc!^8"J,mFZNf7a'Z7(F$!b-hZ%r=j7.DB5(=rTc0=qJrSF`5[Jo?5I/^lV\**;Yqu; + .AoD&@$:^dX15,KcpCq#77a>8h&/-c8D0GEOT]Y_Ik0YqF!!W[a?Br5mec\p$7].UHf'?*g + *=QMus6]<1EU0KU0PsotIELfVtFWO%mm"$8";=o\faG1IR,TlSVR3]o&>b5pU10g_+8r*5[ + .EaKb[4NC3`4g371E=2[LLKD%)PI=[C1Eb=3W@%Jmn83%S['P?=FJ(#+^1JQ>1fuANk'bs)5eC?-J;BP&O6WR30BuO*'-I + la4elB8dE1cgJG?n:8rI&)*VtmT'=LKN7E$+(iVD$A28/qkd-:skSR`=mtCYLbd[;O`bq>P + -`(DXf)gp];EF'U^bW,<^OD;TPD(S0Z6hC&,sG@*;JoPNJ%nY^nE_@VMNg#t[0!s%I$P]n# + mao@.U3.[c\@`S;;Ch"=51QCG7H*2YX)&>T%jOl.^J!n`k'`ocL0:C'kaFDfUZkrWR"(MnQCXrl7ZO%>C3m%fX-l#G[X[VSrjZWWgD)2@k)+Zj: + mC*fN+X6p0q5Z?4,2k.R&4Oh@Hk-IuE1B-%_dS?#(WMk*B-(LP4IQ1APG + UOhK$pLCJn(GNl2G'-h%*$'FIbWjdhh-qs4-eUB3j0`*LZn6(nIjJ?Pn3pGMRJ6q0Urm-i\ + p-QsM/$j*;\KH/n^us.ANN`Xed.(S7=D?m#Bo\*\O6q1%f0-.g3.TDp:bo6h]HEaKpgDr+h + "5FHTgI]*!\?@O'KoDS^+aBK0_*Ws3XcN=/T&;st;5N#KLJT*LOMc^buL\S7n&691TBJB_1 + U_eZYrXXoQD[7L'Zkh#?'>e2W/E&]BKlFI9Mca$hJ`o9q9[GbS+\""OR/_r'qAtOqA3]&f7 + #5gu,Xcm3/&d>EM30hGMXb*gU7>,=O^(rpnc'A6gffVuIP4-`=t>C+@uR[U1IdbZ)$OEL+7 + Zcu`ouA$j-4Eh@;NNZEq!pUi>iG;ls)Q6%MZdII#k/Y6qDS;D<,5a2F%rXJ9_7t"=Ss@nh* + EJZS'J_R0Em]kUXc373c@`R?Y"IMMb8%4b.J]J>u9N!FS8E,r$a1EccqiE==U6UqJ%i*$^3 + 8c2bWs"dMI/?PUs"+i[`!%UmR^9UV_+-D\$po%mVF5R::XbgW$!e3!tZbq98g*<79771B"* + .:fKHO?rM&$`V]nMkapGF&^O\\XLbiI*nQ"rbp-iPom2KJq.1J*EXV-PFFe@(j3drK?f^j + -Hg!9"niKhuM.0W<#D(8A07;JKc9367,Y*r;Uf.]$(DeOlnd1a,oHOn]BZ^cY1CE\GFD=/r + Z?ZQ>66/Be6K%cQb@+"EaXX/0m?B.gh@qXSUj.PaJPd2SjFL68P`K1]/PP>$FP7fN/Eep7mAr^)^t@IBPNP7>Pq-e80B"?jq&@8?#/`#N5e6[asa"0r& + f-/c!*?S8jQ/981F3,9OFhPiFMPH9$6cg`M;^c[TS*i-H_Ea*6f''r'84>Cf_m%BE/7PfI69BA4/rPQc\UB4,Anb8#Ts + Eg&&^qn>=OLG^hF/H\7_B:2.uK[lop;[:dkR_8_!j#eI;LPk3!P,D9a[6atW4##NGgpkKQ" + qtm3,Xp)We(C&h25[a)Ra9jfa*d>UR + 2lgR/d;=)B*@.c(&-T#hp6Sm+2Dt/!X?pU!=0t=^!FoJF.Ze:ja-=.I"0c1cX:Q=7Bl"L`H + n!k@a_hgp!u=K--p.7Cq$hGK7P]QKZol(4eB"rNmXc^KkYC@WqbHd6!FL3Cjodl[X,2MLqt + @RJ>?'jgj[hAIX?X,/f;6P&Lg919p.hTC.4,5V@L"FW^W%\;9E8Sh%g*4L"&'D?YWBUWa;'8< + )XJ[OL5'ED0a-X-_baBK9L-M8Y\X_?`,&-PL;Xr@Chu@K#,$S.Tf<`PON+A6`l@@a%!MuSn + AhV]#uHe)XGY)s.*5/C(e5YT@K$NtMY[J*#sajd/?6l)^C/8r$r"*QN55qHbfkimbm_nBN7 + N8Jg-li$kZnf(b(sbTgI2+*7HVdK2E + uENBDj@/qf?BKi'bXm7c%>8qoWDL59Y^i8P5`?AK&(M,@7fNGaV%U=Vr<(.b=T#f>3`CtP] + ;fTC:.aQhG5I+[hRO"Q%GmNaGO1_V%TRn4q:+Q*tE_\gdr)?R"q7l&k[`\t!$Aq5%0@'LVi + YoF@OV4dr%0c"AOfF;7V)A0>ai=6FXPoJ`V1ZA$b,?AE,g^TrJi?J8;i6i&83?BpWOg3PAi + ?/`kY960C"b9YZN*6e/$5jHh-V6N1,\dT-Jda8rkQNUiFosA!"h!:/Ir\uJDq& + hqN>UE(c'2a:GDCG]dpD!0LLNc9)qHm9(];doog(]M]VQ[B!Q>ap6-i/jN.'o0)$kQpPV)= + lU\B9L:%Cj1f'/hDj'q"Pr/^^$&`J5n"ff@YkmgoRS:G&,*Y7S6_3U3`#]8O93*l#efi9!>DpB`V^EY;AcBP!$Qk1!S5#LcN + :H%!q8%^ZeAqD3BdieAk=O@_XLJgCb5gL3i*&N3[PE`ll`MbHT3 + 7-Z]_2D[hJ[%FfRGoQq'3m1L+Q=<62oDS9,-T=ehL4V[-3?d?Is1+f26.oZ*l,jhp1oe + 9**S-bkQ*HI%!6T>8"5UsQQ"fjUjkm3C!Td]'K$/hBbeR;EK0iN`A-o-QZl<4M0SO(A:"(4 + "j21-\-\dPl?Aq3VZi,p8=R\@!DUt'KtQ$6<;Mc;RN,Sdgsot=MYc6p$(jjb"!&plTmc3^/ + 4%X$,KTa23N_N7@5'>Z^MD;SG?Gu`DY-FIDcKqDDs"^DCU.Z$5:_%/7'LG3cO.rr*Lp+%f% + q-t&WmS`.VP3DA=[L,LLl/A3PU!$rOk3lhS5lnEW@&aS7^LK%t@ab9c2JZc?6:Pl`0^B6(O + &ott>;=SOBMW7rYAIsZ0H^"NBaZinZT:j&5ko8[1q':WG,mJYXYfWL225g&dp[%@AN#I%2X + e;gn&[M,`;IT5\e!=N2V8r,^)t-;!e;hb@;Zmt[5fo,>Vhj0pA6m,W;j(%?WRI4Za@EM'6+ + Qp)c-u`?q^_(>i;:s'ber(cn,=Jh*$iU4to&#O?SakljlB057Au*OAS6Krrr-p`"WR`B#;L + Q9gKS))<(Q++Uoct3XigR&9K?,<%iIBN5mb6$$8/JQ\oe45C!r4+5qkj!WE\I-5U:>!^J"5 + 'O(tSY=9>7%('bnnC%goM[aX_8!)\"d,RY*kg&gVcSUUlQeP(cs%2_bIWVCtoVR,SDLXo@" + Fsl_i+<1DO%ZT0&sU7cPfD2b1kdn&'Ce-tYsO12AJB/76ZGg8'^HL$/JZe,K2$V,eG.ZNH% + Zn7(U8ICNK+`^;b)55[XcrOHOLWlBW*3Y"H4,UN&qMAKVTs_M8W#KF1=]KJ2&HsNKdM"]P&au + 6_"nSNU1Sou!WXrYnV8?S[3W8kMl(3Hpqqo>eW3hF!uSk5=T=K2>Xl6V,bcJ)a9QU>">"fq + -FI+sOe#k3aDCXDP!U[VGcG0HYDYCKPB!@+BdPdi*;'&)8="+%=^*C7B4BG%Q<#kPq.XC5: + )\\j*GHVrVToF^J-`]'7(I"bJ8,;(G&6(NU7o"8+m/@hBkDO,Q'NY'p2I4t%^7j@RnF]6)7 + qY&,A=D4B\&A'3Q[fBQ(4Lj#F5]pC=]a-Ter1tC./SHqG)bRTN9 + C3KnVIof/OQ!D1Q(1iLl?A:'DbID_m_?V8mN'T`:4RE&4CQbLpjd%<429ELK?8WQRt?i4*)Yl8JI;9%1.J8%=XcZIol+]q\TQgH4"5T!e;[$La.FL_16b7e+ + #:h\.q_?(JU8:+8@!Rd7B$,ieTGYK(2op/THDbpOg(f:UL53LtPV4#DAI?Kf.BtQs#5T9HK + $5!=$iF8hd#C7Ca9EUtB]qpij7m043aB<>ANEql@8*8d>?7Z6^h!?mYuCRZKfV&](N4@+Gc + Q5&B.^"i-"n,j,@*:r-2@"d@29H$*0)ko,m`Oq]pO + W2QY#&1bTfu@8h2RuGAql5l2GP!S.)RZV&40+IKe#s23Y>F>aRf>p;q%$[7YXcsBYlit:lf&iuZ+Cd$:p + peoT'>H,+-aNjRkoT96%JUP'9,S7RmBcUgU*GA:&@["GV/%3=\fWB[IjF+<;Eu47CLiA[cl + PV>BPX"f%^8r%j>\N?u?2,V8lh^\N?0]qjk=`:VPs$%tO?i5*P7(S-YdGioMNOiDLBkpuu$ + g0%Zs&HqFrY"rNCK`,39l5VrW+c]17E'7i%^*j"IP`%8EI"8Mi_IC,`!('53uksEuCHjJ.X + q7ulQNr+4"hV7Gmj,STcmaXbrhVn#hl10;+,J4RRHk`73_@.GEO?flST`Oa?%G%K^O1\]^, + \ubc9d$aP`HsW^@WLmbg/ohq6!lZ;F__[1%QKVo-'WL`LL3!r`nmc4S/R'P"l + d\1P(UYJ,3#/!9J^>?r0B6((ge*6(sAej,s?PLr9UQij]%E)3%,GOkAAB,,9"bhsrcYP"d9 + ?8L8gCpS=&rbbC;O\BCFY9?=V%cOV#k_N/=&Z/% + m:Pth]jS%OCj&nWq2/cLn4XDt6*OlA"IOpHrs;#RS1>"7nBq5DR8Ds-%`=# + p71pGW*)(jL;&iRMR1X7A@7@t^+::?:iC$V5joLEja)UHk*uF&grujpr*/c(5nkoBn(m8F" + ?PmfoV'sK,Z$Rom#Q1r8Qr;_Q0$EhuGqVjHr67&lV@:d.Q]kQq?m"unjZ(P,Z%"c0"k<3/+ + 7n:d50rirEaKp\,n32--1).1d8XZu>Si1o,>$<_V(YJ>7D1QR+6&*R3C_9h7T5hRWH*L+8>/bbrJ(s],^fQg<%F&qKpJG0sTD)pQG2;nXj*$]aEW:Am(VTEh?[)&>N + U2@G,%AQf%e+6(q]6eVsiYUbfH=]L2d(qqed@sI])OCPg25ETKdfW6Qa(mBD+NCUb9U$13rUT.f(BH#alMRWikJ;"k/H + 0Vu4O0WSK2T<%>]2$\[D7aC>-4,qI[#Ykgaih*\4*GJAlT:h#)?te:osaF\2L%fV7c+-)Jj + >DV_sk4pHlia/@]g,l+Xns7S,A=3^%=FR8'(/3\i(L^ig2sZFB_mG<$-W$B@EVA:fJP0S_% + /QlLD&mN"Q8=YMJl^\@*tu4obV@@bp4,P&d)%QAkDth`Kip5(C!(Aq$)GlLS9D4mi0]n'CE + =IRf[]H,!-C+:&=`^]8*&LX#Sl)CR'BZMn(7!%iW'^PC%tSm-bbE"W%6[RfC_>K_$363TT@ + .0SS9$PXe8GX"MO*Q-Nb*(-ZSN]9H6+WMe^&=sV'BNY[NCb(4d`.*e5f*pW>1ksB>*(.5S% + q3sOLQL_C6TBf\+tQ6?OX;;@W/s#K*)7UG67G=tW>&gC;i8t7/4I(P(Lkm#MiiHC70.3X#$ + `,TiFFW3(tf`NFCU=#[Q$^;S.l`ci'nr'+NM!C+(>(sLCh0c6R`6K,W$hR@2($$5uK5Pr#m + 0Q5]I:%,:+V*Q'2<)8kG`V-RA>R*>3NlNYt>9AJD&,P+6J`)-/u`Cp0+e``GeV^cCLu[[+5 + SIuX8@0*h86#GDUl$K5YQ`pi+4o,_GdM3Tl/ABuE0,_jP]WAM\\f-1l9ItY*Yoa(XC,nkru + '$86Tag!Xm':bb\$._qNAF(`9`2&Y"Rlq+(;BPMiHigNU%,RW^!8nu5NKNd69*o,[jdBR-4drF7H1\?QMho6%==0ShuWmD$o#[4_tZk9@Q)FE!e%q2@O+>MbX1 + Pt_pf$E:d8FE8D'TZ)gPgF"p$8gci\N2'[UfXJ?Tdl6U58tDSU^2gmO.0/h_KdTKm&C7*m\ + 8?$Tj(,_k*RPlT/LYR`9R#a([E>:]#cQk([T/:Wb%&-0e>)^Jsqc'n3EDp>i-3+Q9TP$O)j + cl(Z:)>r\EC/laBo?AWX + ]BP@P@Fuf@cWT@VgJ%'#b]e(=7"X#12hBp#LVS3nncBNR\AH9T+1*UfU+_/\P]=S)4j&I*/ + #U.C\ZBPg[EOn3.#lARr=0''``FmMRZh[(r&)/n.LgTeiQQ(R\2tND2#c2($osRj:=<7sA` + -a\:eBjK`lkITJRCI.$,1[%YeiOZg7^oq&MRV,)^$56*qS!A@7eK0X]JW)X1ue1e?IRO:Kr + cV:.Qrf!4Z2+FP/MkiLJOAZEU8Aif('!Q==#QZYQ9m&4g*M/\JC2)F:[5f?-^tlK_C*5o(U + 8S;Bl-s&5FQu:gn-=L5<:eMr.[fVlkJ+tX)R\TjW"oJR:cF.a+'>QqBrp0'[+=:sk::+`Y. + fT(,0rheCZhSui1i`R!A#S;4bcK#.4*W]Aru@t\$C0G/(n,UAo,GTZhAJ!-JsYoD&:2eWT) + cqTfsmEaFk-,e22or+;Fs^EsSHZ08!YO&qN:s#2k"CK"s>N!AG;l!.DlgO>[%K!:o2S++[fo#qLX8TqLhq^b4GB^fK)eFDBI8A!+9D6jJWL_#:_J=-)h + @DfJRQcR1&_1Mp413hJ9VI>KK2DY3Q&3b_o+T'e2tS'4N!V<5OmjPW*7&@^,4g&K4$Qj$pq + 3A<>tb;:ksi/!&L:Sd#:9;&d&t*KVKL)A0s_gOtb;;K4m.H'N(I]#bI\C!r,: + )@LKP9eX`cc74G]&+niaR-CHDW`4Sd^geL/j:W3YM7@[VeN*o&l9gmc3`\^O]oOOTLJ7q?&E,KYpbX3Rbo?7E1i?7!(s@>X1dfCr^l.[::?^^cjW%8 + @=pha7Oo'"*M[0G.2"7[krcH"*;O^DDj:gaF&fbF(Vl(Ad7I>#It_&MD#P2aV'S + o;MP"@Ke2B!Y`+FZFt$phrC!7[:Agk*U0RRse&8-2=@H:Bi^9;MH58chdIoV&btU8:M83$M + Nu;h[2(Kt)//b'SlcJmYA^@AYQ59#>*ug1n.aXgnFgb65#Z)K,oiPc$4%\S?6n_jIu]R4!N + [97r^kH^lIqS]e#Z\"]DTfd^.5-#!DP@OPA,4K";O^lDqPF/(Eq!3k_HL*3L,NL' + -'kUY/Ed,/(N8&qeuc;r3\thp6>gc\(1?$f\l\3JD-KLL<7IkYhP5."c=(JZo1U+9P(R!hT + HU"5m1p/Hr;!"-oYc/r^.>[0aWpln7B-^n?1CC^V5q$njIKY8e%UA.UD!&j\AgQ[BNF;1$, + j(9c`r(i:,+Obc;sk=;e;"TV>REXi,c*!2p^dKFp(42Lut`:^mHP2(bAt3J3HJ;A#YO8]Cl%5)&)GT/j%S+kkCE-%ZsWK-`,Ub[uFQ*58pCK + j[d^(j]+L,L[OFKD7Y7m67'P$NRQo*`tE)LfICt[t-4H:@[G!#XARsAB(n'Zo'OmrAW2b3< + 04D;m8:2`B@/]9iPhi<96nTr(PSA44DG;eNHhKj\),W9[U@BKVZ=(THaLd1f]D:KFgDmg%k + bP-%gkn6oi`F/MkqA;H$RR)XUn_P<6h-%Q/?ua8Khq'l?J]K1W_Ef('\LD/mX&E_'\JaXiZ + X;,"XmPTrINQ.>Lpc1fKeB(J`$]!H-,uX + 8i38$qgZaWMTT+(b%l[QQ&WqGNln,QScDg6]8Fb\Y1YY7#ZS"\;io01OXB>SVYmb\A@5'([ + b8$r98f63Uq$hk]=HG(=o_G(:gp$T49Oe;bRDA";l*/TVqQ+'9N$e[XN%iYa=-,k>/88(XN + ShnObNR?LJeT`0I27Wd(<#YbpBa/I+);0\;$r3]:)1mJ8)XVf\r4C$ + c=$EW#*q+_cVgcE2+$/em)*KW,>gk3kamE)DFk`H%l]bKa,]^*kk^"i,o"pl(HGSl#-l3)& + t"m#2:XR_NZo;m?4^!SGqj5<)j0tn+OXT:@4-rktOFFu8("Bp6Tfrb;q^]6'](P-fU!q7gq?q#`FB>eb=@2g\(nYQG&7Y[ + KSRH#sU>Ir=]+;q`Eh'qI:>tZ55)W<]q@87Qr@ARCU(h&^K@B`_i`WY2PZJe5(@9s`.IMh% + 8)WACT1$r-aAQpp_6%fuI+/E"-Q7aqc:5%`5T;>T>7+E8h&7H>80d[O)?8Q0r)qM"btWX89Vc#\6'1k.J^'god0mJIh#+lWhTFC'K%&)`+ET"1VL,j[ + E-\6KBIG_@?NC.JrJJ[Gff(K@i6A,%tL%57]\b#1`M/B-ECpF4R&aBKQ6A@tkR,0)0TK\"l?/f> + oeA_5aDVf7(hh^FQ_\V6L2WSaVkj6=[D`73;=9rd[okltSRg/4$2DlHQ2F7C'K8t4!TKIkB + >&Vfp1;%MEr]QjMkO:KpD9:N$IeOr06_=H^8cD0>>4D3;Ir'+nM=I*: + hU(?fmj\"!K\1M`t[W*)Y$-9,UaE/BGd>7Ujt.5#/GBO%U&<#>B2NHA]&cFG$eh?6;O(L^bk#X+1h^nlU*a&NtKj\&<5&'/=PNK&)fjKXs3g'P^D4C;hKRcqQT/jH*ET[ + b>k9b7CT0:lmI-4M&B4jJ&c(bDf%L#[3#%i6d`P:(p8KJ>1@;:DQHRa4iXJI?.iu2pT33+/ + Ds2)i+E(p)hLi[pOTFK82Z't[GH)9Y(9loB":a>#i;_r:r^,==?,0m%FGRp?MtLD(;P0,_- + ]CBYJ.n]<#=mr`.HPelt\t@-o-Ie%Kmn?L+`l"mg:+a+s-qm;tela@kk;CP'IqZR["RsD3%bfTM1$SEd87Vn;5W5(fLSdHnaIqH*FAXTE0TD&)hqCK5``&+jL;tT:+j(QA;& + WpQlk[l(22W#)<?KO*cqc + Q`>A:j7pQ!Cij#oOq'3F(445(JCk>'q;m.:$\9)u*ac$s0;+iM_YFuU*iBE*hpJ7?% + =Kl>E]533rupn[eo;K=n[BE*"G.*cpd'R^8UXAEr^KG,7h + 0Q2i&?s-nn0@_"cCsX@e."onujYEen+j1XG?g.o(gc+%l)a%1'ffqB"p1UB/T9_-1bj9F,) + bK9XX%"qTEbsk9iH+7mDLbTC`D>2;@Tp'G:WSJsjkrTEI.n%uCnC2lFO:af9:qaIh[UX?%T\lCYK1`HuM + FZQWZ^T5;'p?UKZruclp2,iECpu9=QR6RsW;d,+!d.`/;/jHTeeZCV'OSG.^!L'-k?U@He"md*5+7PtM@7%3 + :0+>aPj=q/enH;*](PfN%/.S*fKlE?.OqUjF2Uje6T8aCV&^ + +NX+.r`R>5A="Xc&m9!!+6A`b.n[+[[47Jtpj+"hnX?=7N^Xgn=Wd5tdkjcfH")9Jdt(pf: + 5+)Y4!3R(?TcYUeG2>Te-u%OQ,ih06Vt-Q'h$EF@9A$,e:eu8dZg:RJ!FSN0hkS'h)m7s6p + /DEMZUEnhI\@bDM[Q"*s_9q, + _Ier?A;g%MPECKOsm$3SmpR0l9Jp:\F[>^!s_#(`i2\qcG:M6h6Z(FA0a?8n_C;\Z.>S0p` + [?.XSa(_+&2N's6&KALcodDfJeAQ`VNQtBQ7osO7W[7,W34'!OS$7 + KLY9G6M1"hjGOn5^:(=9oH@!M"$PL$(FF/]MjtAhAsL4Pon]FI_ef:m;3>j?&Dj3Pok[rI8 + >0*%+NlgNL9FFkiKm[sEPurXXG9p]jlYj.'F7PN7!qDjfeaIZKG-qL5FDl?`H=Z0hEpn$DK + [T*N(!r1>(UnU2K4e*e93=o\3m-h'/-"?7/J".Ftth=uF@=8_mOqs/c/"p-nqTJabd"9h?1]@:hC2!pN$SqYX/Vh/V+7k>VFosZ5a@k'p&,o$fPVNL![sYN + d+h=LBKSl6i:l?BR:75oKe9F%NiVdbmu!Xhha'=IuWZQ';B;=MF7:^"2KZ,Kj_2'j)f=&pp + h38lsG]KL+\J%a?S]ABT2JY2Fb44)P_t")e4hVa#NB*,ID`VJLi.F('5\uJ>4&NLk!K+$R# + _qi?.V;7EDGG,NNs/Og7Ys8?"('jLEh^&n4SWWAu?eoUZulDGcKe83u?'#*hS!JW4*N5Jln + u%Ln)(NFD>#^6[3a>;+.*+?9ZaTO-Nr:rCVk;!8_#i",ljQ + R!e4'^,/RGD#,P:aM6RCV:>")WA(:BWE!i#]Y$db56_e'(JrG$/>NI*M'9;4!9I+Y[dN!%2@..$S"6t229fAF7<-n"T49etV$R/D28Gk!" + 4m22&C:U]^aLl@!?a4ROZFnE#k>?)+c]ebrm%\fkuLL^Ts+`YdI(T9>g5$l0D;'gYEKQ:c! + nh&QFV(>L!mQA;XJ6i=A+rrQ$a + +$(Q$b23X7HHJBa/\MAh;lX + 9PMjp(K+M2mj]ndM+=mVF)/h*g4.i2*>9&&V=e,(8C/-AULDH + hM+m[\`s`:h#-S,*$\iXU(bEMB;+rUM1q&Z*5d1F&RlN;Lmgh)`5n2V1rN5>;,9p*(o\7E` + */+>K2P/5gDj^PVq"YY4\'p+X*&)K$.ZkAa9`"o,a=CRHL"m/.5N9G[%5iIK42nO8[7A^WK + iXHGaiB4$;M(tk_Hgo/LAU&n/T=dkCcl*A4bnUSYr+Ob + [as"gMTshY8reaRE9gu#D.AOpUOO@H?GL$$Ek!uS>o2F4eq8Ft`/..hEC3*,6eN?,g.\G3/ + Io+Lo7&]bsUf=]d,K-7m!PgdKi?#,OaG!]r8HQ&;g;&l`"_I$l4XsKcZe=YWBM5)B[jD:_> + a&6/9bl["f/%CAj%hQ"gK$:A*m&3o6-KZ:5Wn + `5onrgCILj1AkEd=:*37sq4qWun!=#JDIVpu + !Y?FR.nI_DfYm+Yj_I=DT+m$p`DJ/?]2*5p&rTg?l?'9t2KD]U0mZgegP.icPt&c7!P + V-dCB\WMJA;gaM]#$@o8f`kiRDQl\T2S8if2,1`:/r1,GCi$&(;NWu&o!oWJ\0Wl>QC%iM* + .cQ<`)IkZ^$4HZneY3<%2q'@cFkcq?Cg8Hfd("]11>E-")0G' + kI9)o:NSEo:VBd1&PG%?TBUD,Wgo4UtI7uAG0!U#!PftcMN>Sp\lD;*18a.+ln + JV`/i`,.\Of[QiYo@\i2/YJpZ,%CP!9?a!2mLRd;:AA0=??*hjmh(H1#h>\+Wed3*he0&iV + su,Au;j.kN!:TY)OOr5j8D1<,S)fb*KZuWa,TL1-c0H^^pSj1^%2c"^lEW@!E#$NX5^&$,E + 2CTWSYFndE7K&7E?`@13QpbRm6X&`CqD247U_butjY=nSat+Wjn]M],Wm)cgeQjH2?#J:fcJ2e_8kj>&Y;0(\E`l`:qX9F%'O!D(-f&rf^V@A7%8r)K\7d&](IJ?l)g`j#)?>!oQNNi(aMBC^.\L#U"^-+ + Hd$Rg'Sss%jnRi5dCs:\dO''$in!Rp3C<%HY<&+BJt]\(6*!9*X/3@>D:ZZ[N&aW/4J,E@M + 6b(%sMcBk%hbY#GANEdlM@h?3T8H@puEH[\?4!VeP<_0FQ\j^J93)(1abWB&ONuB6AW@5[[:9tZ5 + E)q6l782'MT*?0gqu()d#"YPYi];G=?KRKQLK2WA@#.As;a&7^4+,#ua>r@1nfJhB9C;5S* + r='hItV'D.J[R!hQJPtCb)'ogn&[&4Z/AVi"p9*(_Z!3?PNdVL/^*Rqo_Z$tT;abqbP9_?k + n[*mlhhOZHhiL%_^;4eb/d8&s2isH)g;6o6-4\tU\U&[:Y%Rs`DaJ/j!j`/\/Ok-=.(cu`q + V%MgN[3q(/,X%DE;[*.4JKX.+2]p1h<#%Vs:6eeA#cT!H7"Od+$Zs6C?j(<&$c(1"JKb\W* + Z+oM'g]nW!I,^&EYkGLQ8rTD=_X/;s-FUF(R]?2RL'i)KS0_Xb':[n8XL*,_r)gZ.o\o(m`9rD0>'fpcB#!&]0WcFQ2?i%+,H + @%>i(ee!Na'a9"fP4eIMlJ-C#f)FW-;%kNh/fJLB6m]4i\@E-Z)XHoYDop`NUaW@2$&_IBGf#^Nn"?.\Fkoq5bjIn; + 7QdjGmtg0=&PFu;RmN?j6#Nm+-J73;pk\G1'U(q99V5,+5j"'X@B[,7Ke6L%04rkiC"Br]G + Ek_+8hCi@=?8NCW\YOb'@Qbr2l=@Z'ZDcbB\l2>eQOCN#(*9bZUR,3Mo:!X!5j(>_JB3H*n + lb^E_/o?$XDZNo!I*g90l0?DQhVRG"/%A"G^F?a^'/*=ZD%p@J?B#b]JsKNGB=qpGZnTV_g + ar?EsoE4DA4dRNrK4FX<'5Qh'qBB=G@qZKs28#3!_V"W]E"*:DH:0r8G<.-`+ZYBplckCq7 + "e^]jRlX"]Ad2Y22S)c7/'bZ<0)QWF?$_Rb-Uk"'f5?#,Lcf6r!bh1+&tL + "Mr*@U6Cn?$Y&X^.$k1fO7-*g7%PXT:_F@#_7]B`WNjoC2$Ro^BR9'qXld1Ol%IOemubX?* + MU)i@t=GnkBh!Q]5\;WEmt9qJiG7j4+lbl1Us$A\B,/@gQIWpQURC + 77Q[=/];`A])nN9t]0+W=M7Ao?^?J&d + [VR/++5?R\1rmB+ZP\&n^pq)IR)!)QDAV(?N4^%u[-,9?+J=31_CdLYOgRu[AWC+T:`M;M] + K$sS#iup!;%_>UES6F+@o;#Y;ERsA4B_@Q(WGq9`\)d,S7be#E?Hlt+M;b<`'>!p8/gfs!- + 7-(?ucif*Z*V$q0\ONTl\?UT?:;RqL>?/*3]4oZcebhbIk[g]ZL'HRL2 + oNF[9Pte('l'2Tu7]B;fJO&gqeSPYIA(9i[.hu)5%0hLOG*qQ32!;*r/f+>LNo#jf[D5$M* + NJoW'+'FAoD8=HEOoTH"QF.Dqg:,cP'C5t,(jL7@r4-IG#LDUcXc-&E&]L*C!^Sc\r%,G3p + S^D<.iT_Ca'H#Bm1Mee8.T!)CUh'*EN`3QJKgW>YP-pR48XMK`$)k2&as*FnrF":oJllNuq + /u^;hka>Dr6NZ5F`sg#U\!2;8S+dr"=GIH+Pfrs-UCp8.51?J)]'X)@i#mZ[;$3)BGjPs!. + +%eW'4+He"0YX10GPm?A!%me@-k7"+3;,Vm2m.62=\M44jpFL.p8HZpDUg4nXn\TlSPQuk2 + NK1p(%WEENr.cK;Ou/>D8?uC7-HYghUF/Z3KVp=lB+2$s+7dXd/^(I'^G/C1,f;5T1Ha*)b + nF@42GNd_rc(XGSE2e,o8*=G/0oHb):;)Chf2%FX!,=tdTR-6VnF# + >a.do.g,/'=+\5>b!]&;hdM"Z]anS+7k-J(<)'>?Oth2/-86orW1_S(4Wd?.ZLgIplF0-%f + [ZFfDrmTCu9QA_FMGT4qHKpiIP_E'[T/)!9euJ(qF1]l73\fuANs[lquX?$B<'FPZ\"rbYo + lS`P/;=-`8l?WY%/-Aq:Y'M"C"ahiPcVtmTM@9?8?E+(_OZ&%CaL+A*&k.Vpriu$1YN+9lA + 6'5[Q+foT(q)2*6jZ-pSQXsTkrNFCV + 4(RWTRZ5rOgV1Rg9.W]CF4nDJ*X]F]s)="(<`qSk(^RVm2Jp-Zu*g;iOL&hk^FgD!&+f`RD + uN3^2Mcos1rWND[F9!Vtlh#EEl)7`pr2(*QXlNT.A$'^jmQN^BM/qF.#L[rad!2(J3$H]gk + h.oUT.bf(JuMU"=HW:Ln!Zh80)$)?&04h=nVaO5>+Os+k0QhCk#Oq*&GW@sK,@rXT,AN"/^ + M-D#1+R"etWme`UM_si&&l%Waks#8?sQtB.ApALV-iaI8MRK*,N7RukDru]kh@)&'-5MuqP + J.M]j^aK[H"u-&]!6";uE>Tr)fY88urX'2+Hncb^)M9_O@DV.m0;/4e+6)u)_IuRK`Y9_Q@ + eLSSnbt0b>V_$fX<3*,WfMZM3t/ru7>m:U5=Nr6+7Tgb`(5k!#Q.2NK(^M/iH#\WOT-*ung + [(AWtjXD=c8DE>K,g=0$!arN)>`m``U/2L!]jZ;6i';@<+TL?qq/E+>a:r-7oD:rt^4C5"F + i6*sK$'+6(R3aCu#*jA^ttU=MepnRWr@N0'3#<_*Ti0.',DZQJaSItPO_^^rJF%RQ+UM\5t + + mmAY&s(`14,OS?#k?FIR.DOfN`>L3o?)U01YRda71Z,I+A.JV]Gj4OUBmP-1P*n6$G[)q;M + n!ZJrk:Ra5%I[PVDu2l4lubU!J%_i9D=tAP3>TgO\PGn/a'"B2F0c6?NKg<-!U[kfk'A[!n + +b0EXmGN@1PHSs@f?FA_=')P`OCOA[Q"<082#V,_'&e2aUVl9UsI_)[u/1KZ^h2012(a;DC + .%m"L".](1[Qr^g!Kd`*?WtlJdfOL/+dHZl)_'/e=P=g;3M>#OKqdF8WJDM9@lj%$D3KIS=Wf,ELP2mmj9jl(R&Gg)NCBeRN*t6,Md1)COgF(aCteF.D/K(@\Du`,$:.cM5laR + c!I"b+1/imH/fDU;]V+<)CQ?PT;9S';YiW1.R<*#scFN[DQsEHrFq2(6Ap7mV?)tP\0&>GI + r8@%MPJqcb%HV5HS":[]H[-,sR\+f,1uBP;HhnOF5-5Cm?\XJeqDJ.79d'ICk*(:6('/,_$ + )QQLRi!h+"i/iM"*$'ChTHH@ZJTfd<7+\NM3]=*+7K?T5-o5Yo7,"DiQQfItV3i(o+H_O&>pu$V:T7,UA + 3%OtUuLiFfeK[$0@9c:eTp0djH[S<61J + t9S?+XT>jEa'UiQ8#bR!4@Ee4I$_f?t#dG("lea4t*4M%CC!r'?B.QItSHN$nBhJT5Q.ImH + r+j+5*f>'UZlpd>IG&I'0HhU_0PAe9^:C + :TZ5>1GMhiQDsZkdWO,;-l<]5lgt#dPWZ!T6'Jo1mYPqcBDgrCHh^cXGg+*K!a/!ls+ME3& + ?-PHnN^,[>\]n3.T+pm2ZS=q?U-/Q^-k?T-F3lXZPSi1Uo-8*bSLUaefP^e;EHjSV6UmlqP + P\X,;R>m4YZm]*_-GC0(5hr/eJ(#](`chh$7rX=@N3kZaZpcnWN08ooJX9(&Lr>4Jk^d@pM + :aS-jqZK_d0EV?[/.+'sMO<0-0N/62Hk%#E$UFRq_#k(53t4IuDMS!R"@u=Nn5'9B?nil>_ + 24pA;5Z^T(E/e_R`ld,Ri&EJ)Su:iRkl(S@j")g+0kTRL:D_`^^PbX"9Cb%JVteCq^+Y/_m + 4Y@19<&_].HA9/f_^P&3sHRlS(+71G^R=1IF%+>;no`gG>UHHGnjdpJU6cab30P6 + o=l4?3X"[bR41I8r06Sq[5Zu\\YM>/1'qD>6E7"P=obXs!6:,MHiTMU,hH3Y!K;4tO"[9QQ + P+qc%e;^$cq`CjAXUfJX@0*;JF&b%X[BLoQF@`>Vu[Q5a(dODV0?T)\5[WGq9MD)E=Cppo3 + ,BRKACMu-%HNJP6UWPhkRR/r3G,/Ya[u*\qds@gcD`=G<`sPS;j*da":ln%p3"f:P6\@@-? + ,q#?\;FcdPD?ZSMT<;A8'?l=PE#pDb)`mP\SsN\LfBN+4Tic04c@$Fk!Yn + `15M%R6J!j#Ld(bgRJ8BA='JZ'L,K;>G>Ko^$!NSE!sPJ02!qagpSO!(!Kcp<=%@Ll9M!D" + :ij6R'*Oan!2"?9u+7#E@NMb.=r]5[qbKVU"0\WAnab%RJ=q".)cR)slXadegA\<0A,SU_O + Y8a).*r1M1PWBFdo8[P!Ze=K;E!)"T23M`YboW'EK21njCOVg?:!+8F9JH2&D_Jo[/#S;QG + U,)##aul`IPa;nK^J%nR&\HaJ@3D_Bgs!+\9Z^@#)rAg)n@o+Q/E>ec)rf*ej39#.^2rF#I + +A?I,D($H9oRa7fI6m#02293c-4I-grkj*ns)I@'jL>`lhJ5Bi_DT4^T79->ic`)onW;W!F + Qb+5ZIuGs$-tm5S87V.f1$p"O?oIQoTd96j>nqoK\$/0JrGfX:7GE%`$g(5n*QCXSK[Xq_6 + 7kL9)'7d!p`$:kd)\"Aho51D<'T(!nt=_93Y"Pm/t=+OGKX)a#:KdCI+W$Cc,_L]%XP=F;L + D"9IF&!a?qeZR3Q4.)OZY6Mi=];_:-``",6j_^X'^S2F0E1JteBZYMJ:KcV9D3M&#h6b>KK + dlkV85""p+O0h0j)ahOunAI^a6rbZmI4?>O8Q*/p7'/#54&30<9iCfk`8a[TZVe[L;`.NP2 + ,mL3CfN?V=2erYTqDjFi"D,:(4g]P7DiS'ZsC+.?aaHT`Z&Vk'OoQCAB$`A`a`m2@rA&t^O + `J"U^]mS9hImmG;jSg7f.J\g.\uoERao,\*S2r$\Cl[;b)O@-+nP(>%:(/MH,"]a4#toMJ: + VBIjV5/X&?8,+@1`2+"0OtedSmHFGGt$RFW:Basr.i%h$r]R-@8GGh2G\ + f.D'q9m++.G1ZYK=;>p*]n3mKK@g0sfAP]@omd/&4jX3AK31!b"a^_U;["`_MpVY.O;S+*G + =\[WfRplh!9mVQKKIu2P`+9KUu'>GR`rXi:#sV`gsFh + RhJV[H<(!([7tW,;j*T9>Dq=kqim))qgJ,$jD!0PeDZJ]*JIL + g=[-Z)^mT[?m:CY!&6%iJ/nX)!V(\rTK*"-"#MEo>g^r6!&>l*5lqEZ,!>`AYeSGg8`=9Z3 + KebCqQ`<&k()]E3%_8Xefh$1^^+Ae%'d$l(QZm/Dc,uDhpKihJ_aUs,l.g%UX13>"n3Q8;# + hbd\;6M97@=DUjeDR>Wf@+=\t@d^e9Fp*s*Q/"#ZES<[j# + .@ZX#gih4TJf\G'G.Z-2YKXLr[=@*3M08cFVi;IKFG^n`2gg]t8%0`%-4u9l[i!5?#"#s#* + p^_psZ!tk^@rTlVL+Oq[H&oV8A1DY%_#)9771)27%GcnOXHmA14QG9^j8=_nN_Tm]94QO:A + +JL;".*PSh.2tL.CgN%J1(N>8e37K&KW?0Pa5hkb[`%Vj-\ZaIU3FOR$%'0c5BiOc(!iIRq + =gRg + ZFj(EeJc>Bt%Q*qJEHP[0>:3jLM;lSNIlp)#m0=1mr*:Oc`^J>VuBKa/W0^g[n_a(m.geGd + aK*]:/Md1'2glrI+c-#84O'WrS[F#)A-n%BPcVQdOR\V!1.BQlEe%u:=?#H'9L"GRfJ&$S7 + Omk8]O@.DAh9e#-?2`_s*>Mm'J*JoOO?FH;p?Q;G*?F!IhZUAS"d^2R2>qKi//k!\ShjlJip&$[8;:AN'+]\OcL&f1r'na"ZKb4Fg&. + sJB"^k@8`T9?`rBY79@UQ>H`^UBV>t3=OIt^^"LN+DE%M?0NcXB!N@7 + o)QA,m8):^imiPecmjrWLDgGkFF@KL'!*Ed3m,,UjAi]Ma#9!]o?"9:[k&3pn[3#uSu8+(\ + E@^uU&-Kkek>7>CSAE_ABBJsZ="+^F_JUZbgH`QM\&_7.EDRdFKcK`)VdAWZ(Dhu^A^cR7; + %=m(FWfSA*&2C6BoaBpU)ZclcE2^8KS$N^s#['9b=\fb=2I-dD)=N[WjnTL@W?Arkp$r#iB- + /:0.9Ye>E9:`4nkNig(UuIK$a](YYBg`G@u + )DkFXUr`OBL*Hh1t*LIiQk?@P`""*;2.o??^f5<`M88N!keBOLe]DL + lb%UN>RIrSpOTU-<$\7%8FNYFrG\l8GFgnET`tGQr)@Io^Jg")JrhGaGm"3$0*CX[i+-,;hSmJ%#] + +Yii1sk!NC:53b@'2M"@tt9#0l*BcaR6-<6!tL;qX:V_Z[8(`H`Y9gYR>QGquDfb!8kPK=N,Ds% + (p3i3.[kj<\+D)_FEp(tn,g>i*f5b6NAoH@C?rr&\A+HhYIE5`L+D + cI50)b\>rD$c&W%3K,"@5RPI48TG:Zc;`C%H9,F+.mArmH?`+Dn&>-)Cm + $_HJOXr&u>:M+(m9GanZ*&<4])OiG=S6I:k:A5DoG?;W2:b2q(!Gf4\(RS@DCH,Sie[U2#I + X_7MgX$NH+W%CV)-(.<#^Zi-%AO`4gA[L@f>.n+D%gRdN1saMH + R@Uj\_Pm,p-mj0GeIHkm!HGJs++fin>q"$-Og#b@<&fsDF5\haQ50CnGo/uS$4F0=\:D6//=_q@cnSDPK(<+L) + )Fe_Gr\9@b;u[nh"$"_#QA)UD+eRkdM'PX_9UR3JjfG&'Q;WC6.8#c9UfpDHfj\*Vo'F+E9 + G8DY*f$K&I-_=FM+1R4#Q3C;:%O3H=.f)cmlO;Yh"<`DV/cN"cnkn06I:rWr`0bu9c$5qP^ + OQCd:HH]J'sRh.ne>jbpl^.8,YSI;rq:T:Q&]0%lCVE^ofuQPdO.d<`OQ^=OsmND-Y[-OH_ + #MePib;O+l,&@b4X)^rWGC6hc!CI%TKRhl&>Ub9DHdo?[r_Y + DC;"SejOlA2gt,9bH/@.ATG[r0mG+6CUZluLhRCK2J_NNT\K5+Y!6-`+GSVHSfb[rKKr4Tu + jb[JL2C"5\i&D2MilT2%P2ggG4d>%D5u^gUU%j'iA8;Je72bo-:J]utF.27frn4OC],c+$jr + 'K3S*aF6kH/S>mdZ9kTr=TpVM)3(E,W@4&V1)D3bQ8B"M)Rji*"p7T5FM@'5$;C(8o8q*;; + 89mKEn^G]66YlobpBn]ZLUSTe;ip0E_nfQQ)=rbqJCf5odBh=#l4SGgchiHUqYNTBlK?]jJ + r?>kjCG^beTl?_J`$]J5.Kin=KrG7\_ag5XM&5N.h`L2b]`6[3__U#\4hYjNEh_8nk#'L[as(*l:mPnBa$8V + s]'3sW2.h*kGN(uo:LO8,sqU*92SL#pD + #[2),^g`>I2H"9(MU#lZ^$r.'m7&jo3X&L%F`hB2;H8''p<,ik2TOtp?7%9X\uL]!Y\UNTD + T#sThL,bpT6:i6g>;(AD9Wso*8$ofC;,)$$G+B$jCOBHZlLh+2cKK+o&@P]^8&KR7WJdk__e/a!gHZAJQ&)fPbs_&e5iJTs!s%.SdT#K> + >%rg/VV2l(P:s:MragiF8Wi#'*_pi70G>E'Z\Ra.mhIN$lG`nJ!l%MdEM-R1e*ST)QGW1., + hM-VV[CR@sLViq8/rJ&sJhaPtK>]/NT-+koET0e=&@T&a+9+e!%^r:H^j)#JQiPj6'7bX[G + A9eGOmE4:3#mV%l@S"X'80K90gYWlS5B+Z(Bud,eQ6Q#RB[<'lIX+tH)QW>J[#D-G?(2[1D + G,._a?OA[i'8QFRnf6!Z:d_]"feb(C\>rm:o*p8P:qCV9'18UuF8"G;>Dm=EQL`t"99S,># + :S!T[CNfulj\NWR+0o9ij^o2JqDod>[.pO@J<(7gbsFtDLKQ8DJcadgVn=V!2UB^fF!1+5j + ?0?AJ5l>=^@n;G:t98>>5`eZc80Vn$VXcm6[6t`iFTU7B1%go)@R:]n4h[k.MS2p0R(lK.p + 4P_:PRW\1?<\n>`b/bjtsG7LU"5S?Zt*X>PkD-]6FKUD-)>!WYfpeA,H#!a)^5h^1jA0bu` + LpOUWKgj>-fo%%oCRF@?blKY'q[s#;@-tN'f#p'L$b-+,BAV`X/ + *0JO$*ojlda#PULka7kK#hDVg3t/Z(k8LW"?_OpSgEZXT;pmigP\,&J*R)b&;!iY'iA-f7G + ,S^j"A!'n^9Ef?%oM++TEAmX(.4[QkR?cBFt;*;+MQ@+nh^hg9OUM8St`@jKtMBH[\l8/]G + 52B'#;)SlX!r/gj)oV[-]J.*I2O]Z;Y<+hQT<9M'lNjr##'jEbD,)3m!iN6`gJ'U;1n;X)$ + U4pb/8U6Jfu`908DinRRU:9q4GpsebL'?IJF^-t2_a`=#-e!$-`!C'6o/MND.Qr&DUbiit& + 1=SF9o84gI"VIB%#+3t`)ElHlTsj17Q_YT.;psnram%iEPmhWDO_#a0KRBIp-m&P+6-Y]HKj$"jiEDd*AMAUno.Lmm-RNQrCfU + ."W6o:hBM6hn$L$b@qb^g5NF=7JC4B=kl%:S3!SnJl,$h%%TlNjYeSN\J)[ZH#11,'hJN`% + D%$XI`N8f'7e_uSs0*>J:ZD@d"TGUn!RN=tV.B]=kl/dR!@BB!E%Y2LoFEP-E'S4D^nUm&l + jb/tiF`8t5[k$q[/VHKs):kX0@jctD$Nlg>:bfV/t`k*eNaNc;^H3>imLG0hN)@K.S2Q)!:*+k72G5^coL[B'60F20 + 'V1DMY(UNhd"Q2mOptK8f&p<9_;dl>E@AWauYE7dBEKVdn4=m_:%Mii(:+m"'CG10bp`5QN + 3V!8nCh:=efIXK&;+6IL1\0')G`K's&bmo#&3KOZGA!j/KT%Y3s]E..cok"&8bY7DPTCB3p + ?qXVf6o1KYd.gcPILD:#j%=m.GY=Ps^de)iVolAcm,lmW(5qXg&[$']Pmqcbc:iC,P#DG>E + !'USL:(RF)([uVO')OBF%/F[fq9osdY\%1fCG?&ZnBto\Aj?Aq$u>pt%C2T2l`Dki+C$]K1 + !Aqh#%o_j-G`@r1Bu22!,`-^nGK16?8GK!&,p<_"#Wag!/@BOdn;>@[_VJ03"p,B'3G8).V + a$32D]P:`e-8bP\BZV"-W;aB"^1#55Top`[k_8k;L0*Soc)u#B@7m5emcd*sPYU\N[#GaH7bH*ZfZ=%2#lY23[LD!6EQ + 7"#cV[Fj*HBJ(c8mJY5+ZX]jOG`PEU+\=1,!Y>jg"V'@i6BK*?\?Ok;!?CeOgqS%j<8i+_i + jt6Xs.(3p<>Y,#Tjj_gNaEE]EVJ3KBp\dQA?)MX$(2lH4#jP"_d6]ie+H-'#khoq"GMr+uE + F!<l?AekXP.t?: + \kBkI(/_@D'W,VQc4&Y_:6/7[daV(@h"?RY/iFVGDeWYnlr1*+@ZuEtF0PGTU^BB\FPq0E+ + KI\iZXcaL0kT_HZc+aiJM`ZZ?>kX:#<-4K2?gm#?'@JM;r55EfXl4\A8DuE-7rgfHMHE-"s + B(G!*BDTKf2N\^34d8-*oN7O#L*mE:\Bho7$Z-3altlC%cLW(5Z_EVeHp3CY$XM2Sj)#P<- + %'`"Rt%euW]r?5,fG0FI)IUREaE/S^"b`WI/o04r9EJSm.Zi+n + \+$[+7EfcVT(GA-T1C[i/)m'YO-VbJoL`891Eu.Dh2bS90:4sfJ)St4ue%B=P@Kk1*$G[82ML;]u@l'Hp3iF^61$L86\?kr!AC;DTN^=X02]=4iBPon/(nK^,G4,]:' + VE`/hcMi(9KMKPR.@=THpXrm$B!(M6lNm^4.X,Kg4-c@Y*\BeBT8R*"N>W'W.dZl7I.MDiZ + F4-^P!@M1dXRYkd?DP%\P.<*no^c)X+5iXqf=kqV\-nu@@($Z0cVn7T,;)c#P(au1R4i#\7 + ?^0HCIS,9O;JQQ&/g7!Mh*&b!O6UPWGi&oA>[abq,_2g3;Q[HQ3Q<+R(NDA"T(2o@pp8fR- + ;pUCqBFAa#8'SVDFTp6#]9iPrY:6+H-T!ctrbV"JBaKDp=T9P&7Y%^IFKV`M7;TB580)PGR + *!q6cb$GFBDFC3ceIfm0,8K:R:cR^UgWn$EHfZ[SeT"JA?kq7^#:&V4Pk![&Zh:Z$)ddr5' + ABN^X,g&[cM6#c!_EAnZ_QQ(/TCNg[UG5=9,&=91,#BJErFNkp;W%N2M>Qs&"Upj:!\k/a= + Mki9]b2A-_k5oTfp^dHHUR*22btCKu'EL]`B_lf_qLM,&Mg0Q3Gh8.Ye9@ndPSX@L)ScNVd + \1sma*Nk4HF/l-Qa(mZT<'uY%KP](VU9a`jH5\=I.ts81r0t%es%!,Y*\+M_>esfn09'_ejL#9*@F:Vm']l1?%T/cu)7FPs_=!9\qN-aQ.%K%1rpiW"Nl6%` + %gB^ss?[4bGQ8EG(j;ts6FGZLM?=u'2i[ZsWg=DL)D:%eG[ig9!")NUHd8 + Q-^W2H3_6f.KXCGq!tUgkDu#*o%=,[cSLZN#Rb%NXQHFB^u[.q'!eq^P"R`s[N/g^,5/GpD + _nLTFtk]O5Qh'pFS!Q])Bkf?K9`=3Q2EsL3YFs:(W6Ja>+;s8B*`J1,%.'c#t@dS-R9I4qC%1C9/5L-b[H+?Xc(q@FelV>X&a4Z1\,Bg=KN + &qMkS7ns3f;?a`^dbPkj!*f^.CGinL#We7&g6gE7Q.T98fUAHTi04P-g5m.bk4DJ8c$"0m;]%K`fhNOotY$0k6rQhXKbHF([Ppp[=!+Z:aG]_kQ#%2^OGQ + $D<$*a1.J3+^TNX`8m![n'.%`'#4!E'ZkV&YWBiKu(#q`_e$R#/>jS[g_DkF7f?Tm'WVUZbI + b\n[4.&n/BHZ&;D0"%LWK5n"N/Z])Vr]QhojcL$Tnfj&mU]sZ-TJK,:GOd>o`^@J70:EdJk + -S&s=?T\6/qu^<:#Rs&PgI@2.@!E%6'p78h/cPXa*$LLc,;0RH?C\B)*4Z*#apass.KKAWT + m>$-,.egaB`(L]gibp[Hd&?KTY?k0@."N`A]+TX&AH,M4E&7UDkWdW5Mn>`4FgNU-1b\i`/ + IB&QGL\,gWA7]>1pDtG5(`b[E?K@b7D#M`DuFMBDG"Yp8673++n5kRVI>E0gC[m5E`lk:^% + m#E*osWS=U,(]AV%cQbfNEq.0M2"o8D)p'"Ii?f"`['Q1T%dJ[q]hs,rEC&_HhVr.C%63-. + t\):(R*#9+kJ#JUtO9@`j&W&p8O\d$'PgFKfUQCl0.R,);8E:CT'UDTDK/]H$a8EMW/X]8o + EuCe1D(]V5hVcao3\*Li:f[sI>iof'DRj-H\ns3n;5dIYoidVaQsh`hk]PSj$GaDd2k,gtr + KbcAZs)itgJ,E8Df*#ApH,M*mJdqY>;O>ni0mp]hC;`F@@j#!6D5s2\`V3BX`O0p + Kp@1-*DSHU-N8kaNJb2NL_3J$noPa8/q"Zc2:iidjY6XPSHpGC-ISs*_`?^Y6<&0,'<#ic: + L#c?Cq8c%!,?SNa5UXkE&aqYf%C:TNFBpj=[-s-]eI5Ts'1:NT#DP?'ME0E7s)lhC@,J"MM + lT_cG7quU\!n0t%5_([\]B:A#])[F4+KI7'mY_i?l)UQD[%%lYGLie4+GU3$lJN-OERAQ\W + Ajp]U=VjO^KP)+?k#nR2`pq8m*CmBqIiY0D;5GF,OrT-gTCEI?'!'!VG]T9"Jc91PEic5j) + =jT1A4r*UraX,M%)FY9dfdm)bnF(%J!e\Ah)q;D[ZQ2W>6X,m?'klq]5QH + U!)aPe2@>*dMPH;S?Q]#Rgg8hRO`Pd(L\Ymq?MD_7I:'JFs*U.-%L.EKjk^Ati7LL7>8Kg/ + )i'b>N[@t>=KWuRQKR0o&I'%6RPTpKR:]YFq'Jh!Q=[3nQ'PQj?@V(iN`oUu3N6SYQ6ht!S + S+AIH_>$J&cPlJ=sf6'TB"l+fjj6=5S++\EF+.[HEO6JCHBJ6Ggl$D`a@d&-N$(@Y,#@+XolX*dlj$bOp:->M+(hQXAG>4f<5QGlt4H(N>9'*QoGU5+SW@--eR^=U]ePpJ`lW+4]d]J5@5 + T?Z^#*N,P(J0(>`qgZA]s*ftJ"+Y>+ra6-5JFEUC5e%O264cHlDr_sld'j'a9DEr(mL);$2 + ZLS>)Gnbm7YoHi#@TG/GEP]B)3"#QCN*_P7juGh<67<(lAg/DJ4G\%;>Y;Ya.ZtpU8@]H[A5&%6V')9BR3s:kPa@P + r@S9:\3@l"t"#RKJl5nt_+bF,?M36_JpSHaJeR + WDIjB47\qe&9h++_iY7]D6[o)$Z.CJ8j]9]K#NdiuF8WF3^$U3Ce&U*U4A,:@[><#F+i-1X + eb?)"[k060Laf-3XCZ_&4K%rL`@EIF&]V5U[3JTScd%OJ3PfoYh1]"?T=SiYi)2'IccI*Zr + <;Sjo[$TVG,4MUCu\W:_GZ,^Pi?P"<_e.4tInn]mEbZZpnni^UT`sK^abfZ0ND#X<_Cr]Wo\G7'hYXbDf7.`#r,5aMV+kfgaSg)t=[\L*5t,>.<>k^mH)V6DT(iN'cL + mFqD4f;>@mgCCtP2`5LNbq!O;&[aVbEgJcItBhoJk+iX7%kgj\JX%S+ZZ((\7u_$K!P + =Nh:6'j4&%$_`;`I[DGN74ciUE_^LqR563Sr1T@]L3LoJqN@K):k7eK5GanS!n/W-9_pL`knMmJ#N5e4mXQ2X@"Beg\qegN. + Pn&(+WtorRZUK^ + A9P8QTIX?EbJ0E:HX(^KkE9ETk*2Lft?#>;Z\ + 9c2n?Dh?%]"=*l\,>g]Th6>m(0`2$EZV6b`9u(6t!pD8M=u-8:3!D\UU+e]^bMnIRj9Uqi^ + sU]JEHApK"#G!oT*,bB6W,#q8Haid,Q]PAj&&)k@)4rA6.+5gg&//J(,O,gT?B#W!Q2[gdD + +0H_CQJ=KGc!k)F@[L_IOM"ROtl9`?L3D_GSslWosJ^)M3;QL\*!ckVNB'V^^O2J>a"_5Z< + Hn.)EG]_9=YXbS;1X3^T&nZ4g.hK2f6_=#X>a`!Ei\e3^V)6T4//[VSC@@q>cK6Nj,K2_YA + hQu*(m7Q,W``6:ro`(l+"\VKAV!3cm-!(_dI;oD%?6l-!/cJ$=1A7T\/](i97s-`KK*4M\9ORSKPEuL'*1?6T?:@?,(@&gD + l>L3\I4V"A9,47ni9rs3GY!P@Z#o%o1+'cLO]R/BEt\j14r+\\_E`:l_-?@QT@4&8.c(f<. + ^4Y:5'Nn05(EiJ38JR9KKJR8<^-rUTKZrY,Ys/aO@@CMm8ntZRX1\]2Gam@]ko!\4J\>a]# + a^YrkTl4M/P!X*V4di.&kL^T3Jc4;tk(iCl@,_j'K,4Art[6Gk>u`OqG24K?IbZH,/Er.J0 + B(+[^'U6hLe(UMhtbdJ>RgR&B(bELbb)rIea + MjV"E"GgRg8-GIt@YgN8/un8%N5Da`l+WYQ0,2C/,4iP(`u7JZJGa7+GTq_f1-\,%0@TVgi + qu#es$),:9b[?,.DER'1r@-6+iHMcn1K=N76Wsj!r,:'`7i)_,X>!5*^&0s/3&f/3R,/?"; + jGJ)D_B'$\8jeKck_:iQ;9W6A@eQ9DlR2N"JekfZiq!c32]nj?"D-rLt,eK4%Ou:ju/T,D$ + 1DK*hIi0M[;ZFY_2-O*p?@3ShO:&KrFrT;3VRC)=aG,Fi9icJc[e%.VXpr@n`QcWU"gD"j^ + =;HIN5QXb/Jup=sUeOag:!t4SK + *X5bJj9@j;c:j^;@P@6!(')2SceaS"KYeG`AS,%?+6Xi`Uu72"Za'-0`rgacD4r>6\88-Fi + J8`\5S.%Lt9S:6cQ5r7\D:6fJF\n@":4BL)=l!qLU^29?.`e7m0SW7!0oBFk$S\a,Ns:KQm + ^JItN84+fcT8QokLfI]6:rKOjo= + >FH0CCY^h%Z.[_\V'=>k)0il3$a[nBPP-RZ%Dom[#]@CmmX`D&?S0**o4>XlHrH=G%^t44Z + 8j&p$UWLYiHcfJt8pU0RN2`o4bt>XL(:qmce?6sQED$/4(dNJ!=9[(b/-8?g""W"sFc*"7`Z(7gVBK?Y + B%eD-e:"kqqkd=BqJPl\51W<^ZZ=F'gr+V6V2>)Ot= + 9R,Ajg`cgo,_K].VFS[%]tf*s[>@"]uS#Ra]t[n[2mHGbR^g`<&"9%&$@,6bN??u#Ap + b)bdhHS7^t]kZ>V4bUmDKocjBj/JdF)sNn@MU6pX0fOdiBU[7JbFqqpNaeUk + 2;RG\F719SE1C;SE1[YaOD!r*Q\/MC<&3"*"M^AHrLAhhCm`OLV<@F]lFM;re$72$<(C#PD6?q$'A"1Y&U4e]:C: + kZr^@"ptH]KYU@eoj^%rA7NQ9Ou>JSI#Y`GY_GcKerf^,V/"S_P,eGEaOMl[9"'14 + 3_h]R6Pe+]_3PI-!H5XIQ4]PKoJHct.o_4XG_QgXB7uYD + (K0Jg_iFo&p/]0n_-:4f+(=oXNQ'\_-8[f_i>dpcO2e;?'17COHucjX4-?V:t3p`JV_ScZtaKIf4fO>uBQ"(mj5 + g\W%*Zj,:tr92^tYmp:ArP\dP`X&R,Jq,5$\4Zt'E+k*OhT`SV!&0$28DO32T[!#kPW[bNk + e[7XT^C8c/H9#,I$$%0_p..rVO_G`T5"S0+D.=p%\i5M#&<(QlLph6rh,e$ZH:g@%P + X$Y0PK;"769qm1og0pb3'0C,W;eLX+96-J!$WdGQtqJIiJ?"]U-jc=!O)`#;h^''M7c4G2i + e`W1KeAeJu>'@X,FmR@lMQruJ#&!o@t+3k`.4D4)j)cRc2DUNdgghUfp6o`hKj9TuXb!9ih + 7>-1i308RJ6]pA7pXJ;#7ne*cLAKo,qr41uDq6H-i.$Fd;hc-E^,pdpclN6J?Nqp#j!'dh^ + BTe.2(?'Q#N.FkBL)!LI\d<'A$h*6[:O=EA`Mj1j6^Ji%e4'En=4)WaWUmbi')s"1Bf0Sm0##/0/`,Z!RjSY`RQ;YAN,HPI(AP1#In7!r/@aHT^KE&G._$M.Y=T`bQIl% + TMKAFYoE9P"5l=ra;KPr-=0f1k^LZ/(5\Z4MX4rO,Bfr,U$DgM54]/2JhSC$7-.0SgRR[IB + P0Da'^KLJhX"pZCk#D@ncCR)VX$dHN'ICEE#/R(2M5[?Zlj[#*:X1JaI758G#"k'6VV^dC* + 3/ISgLE0HSF`Yia$2CSX3(\Vb]V1Cl0$Gp?uqo%rq=pnb5>1pJ=\ANjNN1`V:6fG6`+E08' + !`CEkclphm[f9/XT>$A-nBnR5k`snB]q*FY@rg^C&%lmpr!p + ALRe21hX%O(CrJ=!aM.&^U[7`ND/S!PB;LKgeGtC3!2;^[a=[QQfD9]mHIo%%KiE^45QL!Y + !>)espSE14^P/muL8U36i#DdE+GGVUL3DTc)gZC.'RRj];kG+bhZABI`W;_O!UNj=!W9r$l + L)N6@E.]a2BHc1a):t:D[>2[YU7(L#_7^]>]\j)-I&]OJI&&;BU`#Bp + X;cOX&NAoSkVaPRX0k7cF8Wk2PQaYt!9*uI)AE'(d)s%u$lVtTZ$*M/<5L7FlV%V@2k)BB0@\"Q\AoVK.eQVOF,l:Y^;ri&quXd:n[g + <"++2B.h<0UQTrB7tkf5geN^X(]"0biXSIc"d9^?7tuAjm?c,^T:ISr"]qNKu@Ei:\B=_)3 + 2_snlZs03F8t?jko)qYr:X7Rfe5X61Ff^AMU)Y-l)#I/DF""S]ZXRj]8F3.i"-Df(4+!i,8 + 8WrZ;kV`8gBu&+ri$REeh)3a>FP8#b,+G'V'4dN:bo//?N[Y/aVgjn*g]?@Q=[VQ!NZ"sXa + VJ*f-(#6dT7>(odOT208+p-=[\%.Xuu8Xq2d,Ke(;/c_h"&&<-H$OXc_]JU'aONXd.J=o!l + \.GGbNt4Yjgf"]n^h=Vm`+d%G9cW\.D&#PQA9Ai`i;c'1`[q;`'bLR/QQA^/48sXc"LoW]X + O7Fg"G%\/EZD4POrQ387%BG]a"[-k(e[D9B,\uM9YiU0mq<15JTE:[2U,'s6kKWD%,M01ii + (Qh'L_IPG#"63lKOAinTkoR+nXj&$A+rL6ILmaa%)hE4Y&luPh7f%"SBPF7ZjR=2cJ)p=B$ + "Y'bC@/O/:Z0:;^u0]iR)E"=4(4G@&n/ + 03;(Q`X@JfF5<1:gGjp6L12er6;S^>l4c8i"?Eo1TD8CQOCVKH8X&4\:/.!b#3!XQjk5AqR + -'g.D4,#SY&g)q=mgR-kB[VcJ"*>gM5+aq5*_r.RRWjEl2#:kWTlmN1f`R,8PW>(3gRIXll + )b=YO#IGag:>\:V\CNT'[S09Brga8_RjH+nd)kk^m%r_8V'cO'!G!%86XRV(7QnGIbPaD$$/ + 63$Tr7b?8dID:9nq#2UsZ@Dl2a6mT/kMU"_JcjdOWB:NB+Nb`/@'U\hn:/6p*&ZYj&:rm>B + +8N'"e_q5+77!h#N5k7!C/(<0F3C5J.McenBVu):k-?l>72Tnb8Omi)+-r-%:R16LJZWQN1 + m'Y@Ji1+6+Co7EDqS7W_jn,9ASZ=N! + GD1gu@uU,%'7u6Ld#$Cne&dgnF3=R-B#s2*9\53N@VD*` + is,n!e8BEkD,Xr30P=,=CaiQFp7Qd3*,623aq+3$3/>nb2VH-]+<&0D+[EORKFn,*Ko&bNK + eT[KpLNSnf`0l$,<"-JWnRY"F$&^Mb)M + V(p.V-bW&McgjMA^sD+>1l:.`'Beqh98Rtq0P"LqlIB.D8"GDblNVYN + MQQq%"9AQh87t8&Et4nn=,FaO3c$rq)[jGN!anV1-IkX$JrG$.F5'_0`IY5$Y-*iY59eVjU + f>pNdil,#0@[l6[]&JS.OE8T'30lg<\DDEn=*s)SX$MSB"$C94_PubU9p2+i@0\AA#tBIZB + e\<>Qd'!Y'$AD+A<:!4%jd[jh4S.rmoMDdL*fq!Ma7_$AAJQ*\EP]';BGg#*@1Z!=0j%FDM + $Z?kO[%:sZUc(=Xc0i+4Q0l/!5+Wj_:#(Y>IA]+B_2S=I!EW^.!=T,J`gl740GV97Dh0t%XVAF8:,g'5M.P_:h + 3Ijp6!L1BR=Khi45o:sT+V5uSYeD$n0PV*VX>bp]`Yl[e=pq%"+bW[^$%Y&e6i\+92.9`J7 + gT,"+S")6k@Es#mYQ/a.H%k*Nq0Q3O*FGplY0'EolR_D"[K:0A]cq*T*+5(3fWgt8*Kk.6h + Ah]k>jfq>:[]IklTdPV!6kQ"b=*%#b`@k#&r-8`#smN69IjJ!AZ$Vi0+fufL-#(;B3TiO)Q + 839nPbF!-POR&4.@66LDGp+_3TgRu;;S\:qKQ$q]8P+*GN#TbDX':Pc8J.Og1T3TBc.N"MX + :Dog0Ap]PGnr!k^U3;XcWpB]_U*^Xi&)8d^tb7l]m9?Bp\,inD3POa0!apD?'q#%8T&tW?$ + BkPQ`1qVjIk[+`Qs"B[C_3h$BREU7Kc)+akk;,W"9jW6*3WLi<+U_KiTH"AkTLKS<;$LLo5e + Fu@0GHF!GM`-`49Z7hoMj%m%gl!]*hD%>$BEO*SOaP+l;@;^$E]FA,t@YB!^L\COK\ifJ0" + ^q+T*=8i2RMZrH&^hbW1QroH$H,gCI`V>DH&=.t^B,'<:+<"AmK_d;Te,[4f]#OQV3//;0,VFeu([2"np$HU, + )hW+*FfW9!;\ecb+CCR2YF`VWQGR2enuJdDJ0"aL/?E62#iL^P5dh&/&6M3q&#"5=TpE,_` + Z'IS&g=X[+U/KDLA#%npE;S'+R0/]BF2ZW">+H]i!'_MrYNoW+7K2%:]eNG"@jtK=_mU6E- + ,$:DHUat#R%m47-*p#N'-S>J5?m[C<6M'+TbIh!miY(:WW^C5!`"4"E.uWcqssC=C;\HoFP + R#4KFYf_((fX$iG8uHiYBElr2k(U86%m&IuTUDRg/`USj'Oc:5,iZ+Tr)5[2]_!]8pM&LB0Clshun + W+VDP%H+)&k2XhJ/!KajVT_o(X^CD/V4Wh3uqdL%KPc*J[!KUim:C*=^b-MB7SBh,gk$7dO + 1@0&cg@c!n/Ng)Q0#/MXC&ndX^5X!f`3GNc#4QkhP+ + ePi4B`iQ2Z\d\,S"5.1VKib9HsZEW?61m?@u.rIK0c:6km`8iOKjKd"k%To'SGid4Sjl-bQ + @2'n\C7*:aR7Wk1Epb66I[Yf+^dJ:!*U*\e_Esbpi3i!Ek]?pmW;%4:/4T&X!WH1uribnORBf + Bd'q$"G,;g?-9ra>qD2H,2gE4%AuE?me3"2J)J6/MVKn6']*3:E7pC9on+XLE1W:/D?p':1 + g+^'c'h&@#d]A:kW'[_'n4o@30[Eg/2G=Kda'JI:mR9 + mj`9$VmU#O+i@[]W/rjp(oq:JGo]Qqb>*AS++S$%o:Je$t!oP[6!CRXBUj1A0_9Y1_V%V4HK(pKY(X#/jsk=@&1P)`3h\gI:Mlc#s + a^aoIjccmq&pMWmf\1,thgh.6_<35-tk_V9#I^76l$1,W9LP(7.-!RlPYa6M:OMFpVfWee- + j\)QW+;D*/2V[LkENWq3I&1cK6I2eE2hD-"&]_>coX2+HO7ElTqUZrKO/=)W)&8^q$apAJq + W5tH2+8>]\r!(11+fZsP#E3tIApDn95%<:Mu#G?MOjn]/]A-WnSE!B0X=$]8@TGmpo"i+LM + f"amCSdI`oGbP%Z(L]tD^/`1])"bunZ1graeTX_D:%[18']9q;gNZ#bGXoUmTS*TDT62=s+ + k60/GG'B6s%fpV:P#0fpT>V4Z5*IY)\W\af9g=r#QTA^%flG=ob?1=)^SBcLc=-9od;(D.j + idu;[B!o134gS>S_m6h\AA*nqec;:Om-A2M6g\NRduS$?$K/+CR,1@j+9N1^4gn+h<`m#SS^A%#W@-1'AngcS!O(p<.YhC=r:+SA%BFGpEuLJ.@[D(&Rcdr7d(X3IN&$qF?-\!oM:og0H"; + p*OcoQiZ?>eo6&YoG<-.TbmIEOCNf7HGEgRMeCSp]J"h.FG^:s9\hSG27NXmq$NSA*\"+_0 + UO&A#H?ZK<)GtiIZ)(H.).DA,5^@Dh!']<`![&s+5sQ8Gdo#kkVm9ZXpZ8>2-W9^'-J.d:i + Vj]2o2D"MJ$hWl(^/c!#unDDWji^nR^"=:+00NpFHWS"okf<7`E/5:P^#-gl!7CPeUuD1KU + >84(elU`+,*^bR5eERi_l3^Bngc4LDp,GfP24rrglCHVIFQm?st_I`JB*F>aURW`EWi0dkX + iOmrdHlitl$(WAE2/ZaK=]:CiA9L_YL5W4G3@365PHE'*lP9W6kVZe$[j3K8@cP&4*'/GN55Ze@[L8N-fpYt1T3OIqq5\E/Z(BY9S(p`/QT`;=9c + 0P%$K/d.XLoB9OrQL4`jsP8]MHLG=rK=k*!Q0IJC"e-8&Uof`-a_d4@>`lh52)(O=^):TbJ + 0O$d*+(G1mNL_-M66c(Y'GGB*e0gqe4r06j#/b^(fYJ[fc+dLj;B'hrZ=Q>*-U8#4b;cFQS + o4Ro7,8#;aMcY+dMT\p<2&ch,k_'0_8^mGnRH5ps$I@>S4\pmVHKmn?)d^kK<>*Z'JQU<%[ + J!^,6'hI6qNRVhTJJ\pIqQk`NTrXP"aJjZ;.updNgjg + N;(C:-fVtBo0"CT$)l*6]uZ+D)4q] + iCheQnHnZP]BB(oO]=6&PP*Zhm2JiEdW'6k4b4'i>i_Y8[`e!\QS?^Z8E,Ta2LiOp2p8/j" + (2e#g)j0t4eL7m%nH*$sS[.L^jI'!PfSn*16Q7NR4G\^1#44tE#4`[")GXT + S_$h(=6cn?IdGmDg9Dd"0>LVc9]tK*\^#"E'rWYJ#Q+Xa%k$mB5X:l@US*@f;l5jm-j?;`O + dGVaC7lt*#[_rb/$3<>h#%2$LMLHOo?'0Oq]QkObA9I#:^[<0,Wn]F8f'(H]s6j:eaW-B1r + 'BflM4YZja$1$P*tDjEGTL\,b@T=Ui-TCiOmtXbnK0@';18gZS-oj^!l-L6Jbee\oC+t9n) + $`Y#FHT@_@1tUoABdp+Bes,C)0*U)LSNWdIP\%"a4HF'caQS++[-K_kN[EUHHfrg+1DfebO + ][q+,$;JFF)#+9Q"3"k;k`=/F3tl(kubqn2a$I/Eg``MJA-(1D;(J8PT7M7DIh!amVl_%mb + (GL>,irj>qRSBZcH.:u%3.J0c;u-q)I`\>!_nk:o8srD0-ksoa$ + R,OKWmrTLQ9A9W_6'pYchdm'_dg#UD%H=:(=d6/gMRf(Q+GV^@R';4Mn8_>GeW&gfG3fG>V + I8KYnY\c4a+JG^[]tcHL67_b%7lhI+&$HS6b:o=q)/F6_r,oFK:\us&H/NY#i3ZNl,G97O- + POJB3KZ^#*c')rQ63/9%PjJ=n;# + >@02GAc[6Z + VLi&DNI<&I'e22u/I=;?W7(^:%?Lo<`Q1O"[Q&t8.VK[GA5h%gf5+f&U\)(>R14/*?kD9jG0I&Ve<$TdFX\_.\[U@dWOi$]ZH_;2=*)r(CKt<^&g5@G*e8; + c4JC>Ee1%7Un"a,lutLNaoU0Cn)mPGFmucp4K#OBDJoSBY$BeUhP-US##:.7oCjS@hZu(.jgU-AFB&U3@j>e&PhBW[C9Q#4>jhG[q#5c4)r"f + aXE;Udk_f8kb1/F3[M(h^@>+[=EEr)[cbinm=J'W24;.*o;iBU\-:ge5^#S+c"(2B>:iNR8 + CrK7(pp3j\mgin1orYu/0*Ag][VE(_r+9"3]j7tlOQfiX#LQZEIrgEfOqYm2#l[)oe\*n[Y + #J'4?L>i1'I,?U'omK&;dI\O"),aoOit18aKY_%"rH.H5$3L9JU,GO>!J#+r!^Lu>!amPeJ + HPg`5U[]VTRb[GcqnCABJBZue/'4e4DVdc56mk^s)GpF)?XAtrtkGE!ha'`#>lC6#GB9ZKD + lc"nWl#j`&`Z-mN4gKJI`q9ru"0r,"-+&&I+1dM1[9(`9<['`_99fKDm==BW6<%9eD-m^.8.?b4"Esn!l7\_#Nf\^.`FA>oi4:VC?Jo"ilkQJ%;7=C + ]3^[nfT^+WDCt/Ualmbt@?r3mJ-;_o/4P%iZ)7i]hMZDK>hX-&AZL.A%)(jbOL#Lea: + +1o)7Isf6M:%`ZIH2#f^Eu&HXpg]GNQq\]_I*Ya*m_Y/l)!+YYDI==j`#gkYu-/-\J7GnD8 + XC6'`WmVu1<`GX=Hip:(Ah?o?hD\_HJf;">^HeWlF+amP3S"I%3 + &_%Lk'M]AMa'NK!=8;lrtIem@ta?I(k?D%_o4Y7!$Ge]!Vh$R,&iPQLi9/!X?hX3%o-j6FV + 8g28;[2/.)a31Waa8SMn6>L&7&[0it0;_EJcNNTk6_ap:DR^^L'JJko@,I_'W^d4\(>%;"d + aVO8C[tPa_HAUa]H/&i4;NL*n0CV5Hsife(]5"*X2Mj8b\hRi-[27okep. + p3tWJqk(l^^G!@s57ccPY8FcH>77/Ij5m7b+T_p(&htXrLk_i^!A#?MK0W080N*\DodNPp& + mOH^oEu6]eeAf(^q:,D6KshfS/Kc]YbVlI.g`G<#;6K"_-6<23rqWp(I>R0CB + JeaHU-*07KPNG%CSJaa2,nFeOtF]NAQ4O8'&cU[fh + ^LppuC=O-Hqglm@Ue3UJV?f0aW)%uk*Wh4\!+KF=!"a]p.0>ksKHNVccrc!pI0HS29+^3pdu>]L + N[D-@3>An^%(@S!>.FdGKq,Z,Pe,&Jmn:IbbLls"_>a!W)cTfbKR?;9V>C>=Ur[JcHQ469aoHDn5U%\dtdkN_!KW;iD6DNGlr"DKd + 6YN&.O0B'Y>"#X(1"<5V.N.6A-1AL*O.OC])I?j%`;":.43+Wh"p421n_$JZp'pMlt3s'ad + JIWUMp#kWT1"im)B1:1Woj<:\?RPaI8E4*W+SBK?0-(4mII1i*9pp^_pp,6B8M",Jpj8rAC + _o2#kf:`T)R/HJ[*!^BZW:Ls2qK_[25LJP#Kccl@6m/PEmOO.-jcYW@%N;9^N#uAM<:ZVNZ + %004!'Mq45)2&5"^cI+H=+VPp<00,>q)T5 + l#W?$mef!-;odK_F,VHXlLNcjbO&0``Rg!K\6$^`W + \,,Qa&a"(48\YZ$7K>m@"eL[6delP2d0<'tM&6[KWe[9#_d=2V'\_ot9oX?Nou5r(l76k*G + ?.jr7t8&TOY6u?C$KLo3,8jnn=7/^^u>Y[^6BLN74`B@rleS@TA=4YSsaJXeY=X\A>8=G8R)mC]G[!_`akWN1o6\Ucf=0P/gRP%nXJ9"4U]n> + :gX(H)i)tcDI0?3:#&lO(OA1=g.KbbRJbU^G5SSdj%tO]r!6>S")\<#=%Y3aW!@SS3TGIWR + +9BP'Jp8\qO>ql#2[$o`KMZ#8Y]S&7fLm7<9.ejWgQ&fBga:"r95WM.Uq'e/]1^E-?4GmA" + li8Z^H#Q8\V9/VPes>G_l&E`bY@@61s'(:a39dlhV?B"eB6#edEMjb?OYIZ7IsDW,Z6,;K_ + TaCLgC0C1PF+'JquP$cn$!c&cj*o>f[YE!"&_!%fj*IJK5Y[+;'1+hLY/lE"U=oY + N\:SrP!a%G?E(gB/5DAT%4SB6D/du2I%UdGhKJ5`I5q%FH/csZnJj:R`?l]9h.i=p'!>#hD + Sf(oI&Lc9AJ>`g+!kqI7(BHK/J^tggO>;Ed.YIT=KLeK8AEVrA4CUK;@ST&Gjuo\=-/reii + f5JIXt/MRMW3bO!8%uq"u0U71DXjb@obKLQ8`<.3@bk+A$V\9lJZ7`-PD_3@\tYb`[t_%07 + ^bjJ=\LU/i1hf8n2]UjMj4?Xuk\C>\!J5';/-Sn90H:4b^[+K,@3h-ok%q."Oo'Ja"N_Q5O + +c(Lo]D!@SogGSbrc+bCOd!miKO1Ui$f7u"6J"\=,Rp0?c=?0=Co;GAJQYW7?O6ds&#;q3k + VVL)sYHl;T9;dOoODLU%t02kVrBHD_I'[rY$kIud>N+fqN + *LYXFmZ(uVg1^bPBb$;?*E),b<7PP[ktP4/L6ko+Qp\8b6c;0&X]lkf?.I2+C"`+.p7GA+@ + +Hs'<^]RF-"fX>UW!+E`>rYKUH?FN4@N;M\(IjnfZq&tH61_n;DgRQU4n]Km_9t.^f`7Pffm7t*L93,Q.^?"=c, + )iSXQ$Qrn]r5d/8(sij>-^iUbB"(*F7"s + jg@![!*OBUqRgbgW\!=@tI%FPYT=<=Cas2AX9tX]=j'0dAgXofdmcK5;kO+An#P_L,f'7.] + lL('I=EZU(cSpfgWLN5 + U*68!u_DM;ao,k@!AEh=Yp59Y&#/+UfVEW1pf#)qd_0PN0okoVZ8B/DP?>1@VRK*oGgM=7mg;oYg_F^Da`p:`Cr^9p[tlkFI+/X/aP_sC*b= + asaoDT:A,a@Eq5m$^"+oq$=aT'XFhm+Z.=m@hUMd.W4BG+=I9B-GM_J"T'1Clk3gQHDT;/?0(/ + 0MKPrL8pg3Sc,E1H[ZsC>PbC96]V;H3_qer=ihELmE6?2J]jkb'HVe6"R7fmWKSo@1%q-0M + 2Aa]A:n*l+#?R4"fQ1OOTS8k0kn\:Z@hk,2GTR$"!q>[.70;=!&'Iuq6%$tRr80k`?D + `,H!d&?OI2l7/T1OGq979T0FM=r#hQ_1c:[:O(CN!'m02_&$\91U'+s+C>Dr5\d^\*@b`ue + K-^KX30jD7<)p$?tsrl<]i@Xa5Ep-7ge]q6PKYD+'B)pMIAEDVE+ + ^s'FA^k1Tc_YjKJQ>9[$rYB=&NG4#7;pOZURNo>crrGoT&5p1,K6K*1hKgR#&fRuF6s'*A" + Yt21g.2lF2,L?%gl9QVUa;6UG6J>3bJ$!8Pl"n%Q/X`_QZZl8oB0)R.f(5=+dXnotd2%s8S + Ka%L!4:g$dJchabQW3iX-(0/>2c2V4WfW:Bc25U;V=^(4$bB:h`F1/.#0Di09@= + #X^'1RQF"F.%_cXoG*Bkd6e,2PpDQcd"+A#FE;83@7i.B0j`>^;ra/0ddcbfLh-S-#[Fag>H_(0 + 1LV)U9Gs#D?8"\>op3M/$^N[`WoCr34?Z2k"p%eT95L8*`rkK'b"TlIn4:)-UcqG;\_9c.) + #/W'A,mqMN]I+-)d*scZ_pIa9%Ra.k7hEV$4A?O.Z!:-A78[l0(WKGtajO.^-6+d/VJJl1]W!I)d`JgEbGY9Q0[%ob_(n\_/C?8u;` + */ac*L[236i04ZuI2ZXRA5H'AW7EcSPLp5Yol=#slqf'bNd[1d)RCd?K%q8C>qOrAi-p]]D + gbeBL!Je'u=F:s0Rf[49;hCnjqZai\&s'aQ + g-&lHg(0uu5fiE6`B:?7TVLR=.]r5i&f$UO9>LqWHDVR.tW,<^O/^JKD(L>R7h)Bc8G[8=[ + c$CK+[IA%ipX,mf?e=-8Ic(Eqon":\2@s_5pedlB@G"m0L>iDoZ@Cl$[O[9r=O&pcA(]K6B + O\Lf6]miU%Sg\K3F5RKj[]-ST4f,aKqdcl5(I8.g/7mIBu + [)ug0$gk@)d_('m5oTXBcm54kIGJb;5:\j43$ + s-mtOU/^`WIj$,H&c)54ug7Rn-3+8g,?BCYnd,K]N3Y:=&Q>g`Ss)!6$(cu]-r8^Wp]#>Td + A-8D"?FhMEnf_,LuR"+h#[qjam?-dfOGK(elJ+t'$KAlU^09P69jr'HGq7^L-AGSKXLZs-! + R>'mP-i^V`)'7tUmsH3!`*iKB1&plo?U)hGrBG:SpD=)hs7uZ/VsXE^+5-X#R'62_^ZY[ui + "Q?F0Do/c62q!o>RD:b%L'ZS*s>uI',Rl>$]jh(0VHo8K5buF61K4*+W164YauIo6%]K;=M + 0>slsLA!@18ig1PWdG)+/t!%L(?ELVs*4`!Ci(@L0L\`!Ra-@Ltib@0VS,3[bRWU.!(:(Q- + .ANQ%&B(.g&_,_WWi_7*M&t2J>e[V + ),!Ed@.C:CJC5%2Wh[.Ro=W<,BRsfB-cS0!QcN8M:AQg>-4?P/Q'h/90CYVT2Vb)Hr#%\4f + @B:T;lm&ckR]hPbeO\;uVGGl4lMH>m[3h4]5,pj[N\bBaV + ^\1nA;XQ(L\56H34;=_^kMa%Y)"2-]_+E%`,O];aNTVotc]03X%Bf;ejYO4ADD=)5f="QIMt1;/4Y; + X>UFXf^%:#YLVmG2Z!SagN.%GmFf*1]+j/(>CUV['uAPs&i+9oQ#Z4MbW0krB'e!;F*;u[\ + gp3_>pHCm%\_M.QXsp&gV1)(^#$DGQ#*Rta`q_jgcY>5DVh5&H`q/N4r]1N/fX,)0='WPqi + q"c^!2%.3W<:Y%07S7e@O!^lp0b\G2TLXK/\5i?5Q%STgU5]2k'A*kZN5jZo,PDdN%-]G7^ + sR7#6'DqF%CW'B3h$+H]:s77&@:aM_7,nSTqti^Xii@4W^eOI8S*bYBBc4"%uK3##hg*FkU + _.WOH2iY&V"E?p+RdLZDsn/<#^3/k;9;AL'rSQ,][R9B2Et-04b_PS/:?R]M,ijZ0N6 + PCFigI$HZHAkC+T0.1o+ko]3M/'=N6Rf4Ao#ZEX + #Nf+`At9%Fe&RTNmZ!"7PinLOe:IMHTZm?1.e3?:`N=Vb*ArG'j.nF@0,+E83K$qes5A + /+lVK:9,e(NlH;E@ILob,':PcC&7SPcEOI]Zg2d[TPC@8H.M[FUf&GgmXgY3J@TWEStG0T) + @(Wd_pe>8F;VR^H1>@jKaXEj9I"_TD+4A_Pm>Z<$hT[eA0AY"^gbf4LqjRa$)\p%*9CG,o% + &'ntJg_SeB=jPMnmNNmXoHs$+FAZV_php+O-fuGh_XTP/9G)ao=Se5\mjlNb + '6j0)N*AJ%DI.J!j3M#sg\86tDC6`X[j'jQQf6*TLFp(7^%u"uAT`9g%Hc8OmQj$ZGG)kk] + MO*N?;&UiH6b*+f>@7RLhdL3AbEeSgS\DGP7hfs>@&t@cHPn6K"N + LglR+6nImMI?SAiJ\ojbCpD,*,id5%7bo+M6]f`LZurinRld4=2kIR,>g'4EW"K?9XSP4O% + D$,\jV6Y0K&B2Yhh[k'Z?ZoA4E3q;4L%r>2=&5+g8LO%nnfB<1^^X4VaUA*a)[[J$3Lg&%C + 3SF`qt80Po5B8SpOFTi]4?QW$W[%j:9YKbF?5=p8FU%IuLebWWWl@!e6oY5OI!.\Rt-l@JoB1p"!L(SEjp:rr:QXf2)CEXaL1DJ7%]#!6V1kck'Y\P + hD-8A.>-2c(Nd&>lnlIXY$^Xk%`YNgB3GFgZmeR%E,ELo)t&qh(&:*&5I$.f`dAlYg)0FmH + h*>B&>*`#0lYpM_a2u3oFt%i3!FGCIiQm*46/U#gO^TY`e"$C::9&$-Ql>Mf?.c5mhCXjD_ + 8L:pRTM[(.eejTjMMBB6WX`oY89k-3.nL_,-af"80?k@c^8X2b]FK"Wdl%a3G:9@W2[p??A + Rl1"];.#R;'OMB0M+RB0q:9_NiXMF?EMh4M5$l\Ja?P8CGN:AO;DVDt1gl9D7N\9J3W93NL + 5jo3LO2AX"Y4fg&!q6V!O\oHSd:/7)D"05nnSrH1:E:6C5k8V[a?rAdYAj3]FVmu6(m0jrE + K_6ePo11#):=ol&XaP7T,M^E)Nh3AdBaar[i5pi)c=3e$+T_jI2mobp^`&V/O]N=594[[*R + BoDn%AP&k+8c_*m_)5&%*JKocGXRqiUn"9ecKC#9E(\r/oYU$9]Cb(B%%YrXp:Nnfjn`jW] + cS,0a_):\`NPq]fs&W,M[enj[")pCN%D;l + u5hpC,"Du;b0N?k);J_G#.*^K&%Hdp,ADX3\#-siXB,'7-G:JUZ#Ko7\#<`>F'XWcliNS@% + .p30r/I>0*$19SUYbJU%574X"$IH=9$]>D^]&aE(\W1),ZL\M5XQHRhk3d:E#Um=QI1C.E% + do/:DN><$h[bhH^+Q*7&HNG?#et?fM=D06&JXQ6_!BuDlq"l(:0F/No:Z<2` + 6EZ_sBSTo3s4d8JMh=2U#)7ZZ7ZL>o!iY2plC1&T#L/L@KmIa7G>^"$manI5upO(brcd&\U + 5&_`Dk63g6a.e$D*A[lY864&`aR'>HY.]f_Lr4L<'je'gGnesK=T*3mWr:P0gocTYo_*HDY + bdG[;f#h(O25LJ\d;>EISpHXl;9bIIl'GC5JpE6g]r,_lmo8-7Zt]ZuZ=!eH<*q8'jI&n4+knb + c^Mj.$TbrZB7^4?R\fa.C>](:iArR#mW'^#?%Z/Y^56UZR^.0/+U@4<5U7baXhJU$+/"0c(GN35I4,6X2@f)7EDA%s3H9C+b`),AeNEMo=d>UBQ(_@2UKWq\>1KHR'rtOr + Y?P#=>Eus-Fi.G6`EX.t4B('!msGTfl4E':5.097Q/=nanNiH+5F(Z-;u],upKpqt5umkjQ + 2b5m"$q)"?ITuQNm + )_.WBLf19X>IInPlW#W0@'[)FR1a_Qne9bPcg'%K^;ar>'X$qddr,sYD)YI7((UWc(!1Z"c$)*lG + B\CY(Op[2I<_*2GdiHQf9:IrQ[&7oGupM&!I + U1*gSCf\Z6'&-.3cV?1)D,TN,R$1;;o52k!:Um"-nC^VLh[T5` + D\2/k'+^!tnEBH_Mq8&/(rGPQ,r1er1A*;['f0i.3pY6Z1cHR7G:+%]8J?/B7bB/%\^K3A0qFV + S-[7X!c,jW96(XH4BY!R\(eh1o;/&!i.JB2#)TCs)+4&kE+Z!(pd&K[ck63DtOs%o,hpo+#8)_;R$UL0 + bT?U(5infENBsTptgsN7uhRFNjgZ!6:W;kE_ + SF?2f4`HM7Pn532&!O8T)+6V + )Icrg/h,Eq5)`WVAR.HQNsD5WA<1INAWGgR+X9_h2W.9E`GbmQRt'SmrMV%m[BQ)P@IWsV2 + tbtWfqS*g9F_j[rm#/Fnm%[[?5X9.[:qF`S0e"qY/!7A&b$I`nY4Hr+j=mGK3s8a#njlS:I + #2qldqhYT#O*><:;=0sKCa5+="+*2BcC,I.+`b1e;d>bLl)S]SOUUqDo8*0@!7T:+1jZrs- + #HV)Id9skfE[<]*dRCPb5?\+\XcIQ'K##1WcpI?&P[k4q!4*es9+<#cjnpF>PkO2>@]NuF=EQ! + LB>@lBdNO=p:Fq$4hs;XiDJgtK<$S563YM8`Z;XLru&4lbB>kK$rphHfO*SaYHAoA?,/hqe + %arVPL]YK35k8(#W]>f4Zi(R\NriS;!&gpQ-HZf7L[inc'Bq/PC82p9]"iEeam;CB]#5Kqa + VbjAudHV9fl9BD]&jjm/g"qtPhB4Y:[k1RkbfHo]bFm#:r\%MkG]782ZFT:,4Q5&*Tm]AZ0U]R[`oF:ueK3$gd'9?(dtnp(D4P,') + roLRj_e&tf(B3u2Y4[ApFu=<^XiG#)J6`(#JdohV_Je:G8ZCJ4k]Kog,b+N*N<$T1"u@\_X + h-hG@FnZ + u%pX?P.^'mM^iE_tqjIR7M5!e.V+61,*q4F;>T:u)>+-Xot8$j=3qp4fo5Jrq + ==b(s.c2"j23P4FA^ZP@.cQ7'Pr:sr0s3KGDkT53-IY?K?M9u-Tkop,OC-UWQ8/rBW"(ejs + n@39i*>+)qiUL#k.A[!&7\_]H;arVeO2tjp3?';2."BC9P*pEoTeMli-8S0ig2PB-cC#?fo + ^77MSPYsN7QWYh8>FUIPe(';O\gPWR*=9T>,t`,G,o2gh/ + TmYhs<7$f'ddnZ>Xs<*V05Y@:(^,r%9"PL;^=$Ghc6rK8I\110IkZ6'%DX0Gr.6k'K1^d:d + Wj#Q"CWVpsuh>2KrdUQJ_0CIEn-fXZt5;&)NrUf/)%nVLp`` + _igO)&CWDG$2j_l2MjH1%B$%8;3Ri=\06's!5^L=_(]-P)3XNTCSe[.a%>3ZS"lUq-[>uo* + iGi9X?"j0U:aVs#!N)K\\U&o6fXZ'ubIu4&6O&o0,rknbuRSe--"@cu_,Q6r6<\=F\_?D&G + iA1kd9+Q"=5O9@D>/2bd5X\PuQNo`[c\Q$"ZS9%=Y6;cZn@F]DOD[>eNrO=F^/! + jom:(JQ5+mXfJY%:@p(]AQXj6bSf9j]E\\XE4`9h`,#9O\]+)$B-50.g/fYsnsW?)TCY);2kY4smlA^ + 87\R>AoR/AadrdfD=4?iU='aC"rHYs6Y0)hn/B)rtiB>Hp9:(BR>5#s7Q>fa4^3U+-5U[I_V67B:A#`o + DS\rj8XVU"oc:s!R`kL(M"P&`PiA8JF*/Yi(_.nleZEi@-)u-6U5^DG=Rp5UWfZT>[+[;2Q + >h.A\LC71uIs@^K,cAR[5[P0d?@l">q%Ge"UlknrG8CG/P\]U/uW?__=j0SU.Z(rIF9,I*O + iETTA$,;Lr_l'XA?RR"qM8o+rVZ&nN%gISYiD^9>;OlmG;*$^Xn:1TYOB12#VWAo&r]Uq;V + ISh."a=cR'm?Ip^oH!b9RLHjg;F]W%qX$j=5hZl4cSKAmlkYLkhi_-:F%D( + 8ApFar$FPgXkX95.rjV0'_D;^u3Ll*WCH'[iONH?W+ujZfZn#+j=*JPdm + 37B%"B@C!=Fc"_AsAq@pk3Vo]<>m$U;[:RP3[j'jIo8p$GG-G".`7&.\f + XjtD"`CL:T:IZN@=>8q8W+;>!g(>dk4BH[NGot@jH7L + S!21C#Nh*k:^&&OArf$1;r\\e)"]_X"1CaLjATOQPpCBQXjEp"tP#Z6D[bMg7n122QOS,ns + IS;50IR^%"dr7<5pSn#TcQ0nT?mo$J?8-3&pDW4ZLo"+as""u+HF"NOX-K1rq(_=?MLRAg' + ,N@]b"`gT8'\IjT+^)rJX4Po'X,:(P[P`kg;k/k.iMb_8FP\5Ud8i]XlAR)4"R,n#aCj@T% + nbYPl,$\Y(&MDFH^W@,b?VDH,L8I)P6jTAq9\Gagd:+o#8D>^jN4Cjp']$sS)KDS>aZ,HLB + :RGRF_8"o4*,N5S[6aicT1Ea1UQsRoCkAFq>[(#'`Xi[#)$YN*C@+j%9.F=V,V:L;KQf7dF + q)]Bg!Q7FI&6@RU8d'SN3Q(p7'P-G>N$;7B(D>Vpq01;ehk+WqCP8ef)#"lMY:m.V2H.)Wn + "TRg6-5(;I)=qdEL5Q!SVkGhEhtA*g=]ZJ'G>4;fraK0XTbKW(4OeP]2QI]1[)6tZIcj,CM`bjppYG/gMjk@ + s"4-]\3)q]gdj0=DFYLqpG7e6]9dTt?QS82.N)Mu7s*^dfd#*lmDFk"GHVJ9LmO_)?Ds98&-,i!CiRidpmP/NYjRO7slQ3E/#+n&d&O7]JG9rpK + th8aP:r!+k^i^-g_-S9,t+f-8B.OPISJ*8fm2@n[,15rT>2FR9k;]r3FBKiuuTnM(Q"i\jD + f8`1[$t0_0^SR3"d1j_lQ*VMrmFB\mP#]YWcOr8*prS'au)d$4-6C7R0$2u9?!SEH.3p'K2 + 2:X&S#-m!j2l7527.M&4dDMM/:,uEg-BCU,I[.td_jSXLmo6t>uq0totI4q;drjUGT+&hQT + J%,-#kMQ0oL[P.+_>-;3jo#P8>O.p6bsR#'&Gqf%DCr^&liu8F'm"5A]sI`^_uIL4!'g)U^ + a&rr1Meu8r3l4o5HG'u'DXnGF23]USRZh%>RS#nG+4%m!"0]fQT\0U]8NmelXWu=(3*(6"(1#'GhF212Zr]D/p + Z7,sqo-CtO$G:.$4G,%;PK>qf.PeWZOd^#?koMVP=JkC=-Nc*:&ott/e@e6!4^;*^SP>_<[+6<$Fp;+j(KD3@(?%]9Fqd-H]:76B!FVg< + /kXTab2b6;$o6!+Zbo/+9,dp?bd52sVaGM/6\DC3F?7=7X$aM$3pWq0oh*@856\4fi-@gY>O*JOX/*Pb_s*oAIWPi^[ + hHa+WSFPig8EkK]=T:8I+!XQe$;m*aE3BQpH(P8Yd6GOoibD*)T@e6.uc6o&RXiNC"?tapb"AZm7t9)`_1EU + "lQgCoG4@WCiC+9!TeICb89.SI0`t,31H#r%_0&U'gh(d\+0=<4()l[CUrM6]7p:NA&JBc- + *LLYfo:01U/Y97a(D/bQAP@Cr&ds33dL"]:;AiZq*9r]K=q4V+erVKZQ=bb06Sb`;@JHeBG + lOcE`+%178bD9?OQYd`$:P7>&o\9Nk/+1<$V*c&>q&RW_E4?RLffZ`R,hXPVJqho8@JPVh@ + =>iNHq=O&p$4r!S6*:q^g>h-f!e\,;>eR)>'!@b*tZel?>59kTgZoZpJ0 + ^O,b&?#.ecFAJG/f%g't'`chh!)M,E:ZZSs'`_<(DnoXrf4AhL[.dXUCfcM9aPD0hKmUo=O + LLd/_!MM2oTMjPJC*!@;#HVV%>3%Gqp@P?=CU,Y]bOY&*DpC)am>Tjr>A3:)G1tp;7rXNbE + PV1*Q!5e_Nc61V'.jhWku]U$61YG<%b`PqF^E-MR9V9P5\@\1S[@F3aB'RF>qY/\'jXsbCq + Xfe@O@&1X!kjbN1TC4f>PA^dL=EZ!B@X<5Zp[43Utm`19(RiuZF(5hcsg4=rkK/OIDbbd(L + !4ESnRE=jss8'Mc>bqU2BZd)9F92/r#4WR6[.l50#:Qk*B<>=5pUg_e8gJ.3#eR_c]FiT_W + j%_I8eobS*L!&2ibM:Xk>ij2bmEOGZd+nlQh#5[W("6G7e(n$Rh-uGrQ"dYJS(=u`f:CXIO + j[S%tc$i4tH.W!'=9 + X94oa\J(K&7:T?R6=e>nZ$`(#MpIajZ/>jnI/]T1TiX65>O.C".1`!PYs + (W;EQMY9p\afT@p(FfsR;Pc(*4p2VIT994ad6P6Y8:\M0hj>!8Y7Q:*iu?--9/P>#%fCh-JN?2DX;^11O?@j'FS&AiJan39+m:j + +]U)Q9+'G44[E`h1DS2Q0BF;`tALZO_Vd;AVTH]-Oiq-TtWY)W>$1Qg + S>*D0V";>hTp"d@jRQ"F#@?o4th1LQ4LYXP3GL'uH%\7ZQddhSZ + dkg&,a]d-&Yi>rT[D)b^r'O%p[,mCDSee_bSY7Fo'_Q>@$tq&D4)%_Xl!?MeDO9U"R + -Ad^Bni&?[[QI#RVC?Bl/%rpj5sk65,ra?[uO2J2G<*XV!:ABjJGN6G"HG'VWs2>lB\=.`h + dF`k[`6.[r(Eqp$:]LYb#H)?K;`15-s,F<7j*/fL"$=:+A3jjFW8T@R4*GnuS46]-f\5@)B@[p_jr2_ZJ'=c0gK&mLh6tgF\`'**58^ + CgoNW]`s?nh7r0lg#@tFgbt3n2NYVDOhJbt`bq")2O(.hTBZ(>l0e-2(@uGkoC#`Pmq231G + 4kD8kc&6%UmcQ_'. + sXAkX^k7'#ZKQJasH?qm';G0&gJ.,]Pj_bSBQc3`Y,c="Z<=tXi4(=?bhVN#odANU1gW$>V##nF'uYk-[Ke[W + [lP=DnhoT+gq<\k1a_fXDZ4hf%/u^=_QFENP==#%@&OVt&iIfhTHO4dJ+R`TDWh\b@=bL<2 + Iu-1%3/_d3X1Ijj6`c17X1,NgRo%tj7%dP1DIm7]h5="i$P0]>dZF!fRQa#&%\g[e[lo59' + Fk;ajnK%*gC161>4k*<_nW.[0QG + ;t1)s/aP0,5W"m32$"GO/+N6Jm)s=!R9#Hu`!)Eb-bAGCP$1btDqn3i3>Ps+=rCKFIl3_t1 + 0,mtQi-LZa^PB9q/SLPk&/1M=p'psso4f8fi#3+rha72M)"5J6t-`fHr_O/MFHQ&R,h^>)ir$M&igYl6RS$h,gr'CKg>,-9P.3`U + t0BAR2aG-G*?eC4eL'!'KQs(VadIHkk(lL/tG_8;^tcCSMdrMX0h1M]#Aj#?ERA`Q$XJLb@ + e+aCt>XUY2*s)>:rtRS]9o<]3]Ba]>a@6IDB4Z=6q7=$\Q?+DtX$>LE0dD@qrP8VH0IXb%9 + eD*9@hqRqXdYqCN2<9r6?R2-PE7V>ekYlh3CLQIfi[g$X%%B$8X+^G*lq*6)HUL]#OW9f$&12U2tHjY`E[=oUP.oAsjlOcnQj!sNn!h/m=-brJ!`t[,rsQ\_geSJ_($GT7a)SbA--5Y4q-$j + ?grD(lo:4]$c#X%s=F]sK-V)VX*C!MSQ$m:M.`HQM(Xmee-h`UE_S&'=G@hbVGgr?#\&pAQ.`j + nF4rO6cW@hp:ikrr;HZs.A?OJ5?FO5R7f!i&h0o&2",OfFnAFA.X==7LDF@/Hh`b'n\2H%7 + /U_98S3ZKM[VsLB;X7Tg>?*7Xc*jLg4PNbV=h59ZNPLD%0pi>qloW_o'q<&#&&KLf"@#6jS + b`i]1S)k!g?/\8;WtS5G.U9W8onXU='mXc#ZS/jbB&CQ7[h2j''V`^n&d:*IZ;0s'CU.,8o + SJ^091\F"Y)P,p];9TTeKC=\@pV?6s-jQ]g^M9 + Z`0$-I4]T/lfkVQQkXe98EYem-I6K/9AUlC'O*Cs]/n88,+?VGl' + =/K0%&C9:s=e5ia'l9f!Z9oZEdHCWb0D,I;J9te%$X&[p?J8eM*`)YD"=58Lm4n_Nht3*g=][NQ#Uu-cFQ)pm0>/[)m<(a^"8_K?gIB9YI4qV=A&g+Cdj,ai'F + cisF:<2$l-"X0SQa9_)c<6&gB'fJeZke\L>$F@.m3S^RC1>CI + N0u#'ai@NF3__/R1\(4Km;6AI4Am7!BHH-[1)KBLf!8!nINUXYHhkZUBqF@ + @Nf'WX]$`a,A(QB>e/d@@P3DU:5Z3N$^8Si",D:pgHpW;;LTe=)cW[7*]em@h'j4^4t%`E$ + og`iX#X#?WJIM6)$kZKp5p=nW#5XmWB$f%T4k2Eo0amcN,)Xds6NIa-Rc:MFZOUUBfmf\5( + mDJd)#[^QIsgcL"*DI(AppE&SSgT,F6^"p;EL@!c$^No8gkjiSEZJS(0g08@3^[D[627Id] + 6JJbgpdYS!(K6&o4rncjD\1n*7uD:M/]>08@Gt:'NUWFDGI[q^i*6?Rnj?,t\OG`D2l,89f + \(V37IoS)lH12>15<'EGi/&Bt,\@V(dS$HG:7[*MJeQ$b9bL&L2q + @Ec+R;q'&k^A@fC;XFQFOA.bM6OZV=$++(uH9UeA71m=)l9%2^%j"J_fl8LWL3B^1=&eUh: + 3hag7VVAJs$`ElM2(3En.]gq'_U8!2#:mkQHYrL'T+RE[Q1c=Yrp\pX_hU&L*5YDOXjW5a2 + W/,MObMEdc4re/Ytb*'%0D+acHNFfilQI?Udn+Fk7F5G%Xu1>VWZ&/$L@u\Y))<.'^8H,l)>fj/tTq]-L-ODlHmnqKN?#D[_sf + 0`,&3Z/54$#`0Mimm#KH3nFGsg&k:>kPNjI.^i&BUf4K`"na+!\8ac/38Baj^>0PLf9\f6EKpodIdRGq&_?g+*%6%1'&M;rue + TN+OUQ=neE<')'KV5+`\YJZ6:E4,pKW>,3UZ^0K0)NqZpl_"Hl%Un3[G$8HfZB,k,e"&9T9 + S=XA)o,uS.tU8%sVB]N&[-LcPp&j7U]M^OUJjq*X]Ya$VPisHEXk=b=PZBYcTr9-fYk\Lb; + DNM\4VWi-'$hCD.;RHpd0+>cl%5+mp:tW\Va@?`QQ#Pm(XApidAIdsC/agpq9O/0cK+ID*` + t8J.0aPh)%dq^QmkGOhdff,Z+Rff1&l/rk0eT"C:D8d(1%@C^.H\hd7e1Ombj#3D;`+*p3: + n[<(/mL7O_lpn<22*P(GU1."sXrU=O?Sid8+f1cZl;VNm]L-pedF,m54VNT_U'e)N@<,YFa + 7iWAq"[)iq0An$Msh`>hatf&mbfk6i;+b&Xc@r@6D?&)/-[lT;55rb\&IdO?CS`B:KK4S-T + V1X/']d62R,4gX*1PN=t!k<:]d54e/+1[@8HnNVp,,>pGWE]YYU#mesH-SuE#j&%)-C:fX' + -<)-7oHP\>,snseL(kRo0R"lY'gmo$5dGf+1^aUFJA(H=7.Q0A1fYsr4o"%26%oY!C//H"0b!X'u^I%eO-\W6e0LKe2:N2(q4T=6?V6ml + aL/Bl!:N?>rBl60RlJja]rua7$<"YP(d:c;!-2g@.[Yl;O9j0?B@!UOL/banu6)?G@Ik_$o + 5@`Yf/-BK1M0B78h0:/3sa5]1GS"8X<1DZJB)6ULgg4`c2#k,gT+aBZF)_0=Gi@;Ze^1_e6 + ?u&i@iGP8-eH0eopjB_*A]'],E;.%`:I::0I)'_'@e>8=OCoM8+q/F+'o#rIMg:t6.o2&0q + GD`.'A;A3[:Oc_Q00,\mdpsG\^[6#:*IlLFpe?>WHQM"5I[lJMp)tTP!L@7.qANEbjE9b[G + + KBB@(&<#qrBRd`?PUt3oG:f"H![t,?IsUM2h?3tIs2]gG?8fA=(u$NR<`:h?l,MEPZ)mO%: + M(rHK2VGGI:k`Ysrm\^pY[^(+i$SZ"$fjI-`RY/uoF^53juI8FYSe0#8$@;b>P?8b1t8(2n_>InTA<9G&>fCmQje#AA#bBW9BR[ZI'rQ%HC%BssWJYtm/4o+a+T1Puc + Zo`5gX,4s8uCUd5X'3-n:(Mo?PA!Iuk3+YudBi;HX)-&7e2W%GVjS;e?)VBS>(@qaODNO[r,puRLaq/uF>KL">$[K1[*CO*Ge";d8Hq#bm["aKctj;A2b3=Cq:)(/PdOnemF1AlnpBH%bTf$"l4*B6Y#=oYlA^K1rZ$BQt\P<7oAr/ST,H:0*p#'.ni[V_`L + s:RFRd;aY>_DhSt@UN(Em04BRb'o"WoD&?tbQIfu[F.Tme2q:'/[dQ2!o8%N^Lt;B?fT>uT + R5rf+*7kR!32LThX)Wn^EV$YtG9V4g1E>R9;O[K;Guq3"l(VPAL#\JLB3Ff2frVttV90S?8!M5K!cN;4p"GqF>GR?KWkWV + JIZffc?W?X`MtP`hkj2CXn-a'uS\X5lEl=4BFb/$]17AE7JT@ + (oO(^M/g&3U(qmHF1kZ(_kh^NV-BXi4/KLbVn+sh])/VA40,u?Z+HL5]=YnkmGk%S>UI$jW + OcT=>LYWYEN$M(W9_j/g4cIEjhA@j21+5q45IYuf;;MAP5G?[\M_0KTKF-lPS^m1qQ36:CT + CT6PrcM@>/?J\09su#XI%\CqTTK!Of`e&Xd2nfm`U)CrUb>I"i(p+r + ZHWDG8.)cmu@\GMW=-Y1nW_n4_& + cb0@l>lW/V"42H*d(L%J#-en$M]g'to8 + ^lKgP'd/X2?f9a"4`a=^m_%L*"UV@S!7.mHgD3Xd]9F>Gt44K_JCg@f@Ro&1W+FqXZS_b)V + `1hnUFe7Qe1uMq1E:jmsnBL`N^)qp3jq#AV$@e9c+mFFOeE6-PO%kh"9W%GV_,D0kssJo6c + I2glI,,7o3;`Zh4R13RlE'Vk(K$(g(>C>>oBc4P-6-"3l_ + 6T_j:t4Qj5%l\aCp^m@ + gB689kP['I3g6O+8(?CTke<2/rfu^HQg$2`k^?FDSqHVSd?NPe.kfbnQ8=`;u=F4n.bg&+RIZL?M\WNSIg`L?[l2LX>l#A@DZnF + Y89*Q`"W)r2GRRhXti4k[n\..]U^S/pe70k4$cn_YA[KmQ=Dqb,iBhmFH1L;!s32(`\%%oZ + dJ)E-p?SB5[(c*mPtM#JcY4tBXi0;1MgTl839Za#DtMr],nU:tX0hJ)dMRJD?'q-Tik^SCW + )V>@s+qZ!Jj5HH0*?fu)okFiDTSq5GLaQB(hr:#d3hppu+g$,-brI<]1^7j"+ho(EbAW0m!I"FnU8)8f[kK\!sO9gi%XM6:+m3=`BW + "Vb_Ab8PKRNY),/Ug,fF9/o@aWPLIn8iBXJ`o)_(fff'>3;2GU_oZJ'0hgL_5IG5pFffc0> + e$\u^[ES*Ap9E1@'j`C6VIZI3R`ddZ-rGKYo[GC3Dq[<*Js2F'hq0a<6=.@jV@R'hK%sn&, + nG<)Ihk.sJHhIB1n+h9<5S+n=!^Q63$sl2<=oS]Bn73AD_9eTq$(#6)rsV7kbVp&"Yj$WH6 + q,m!>t!(+rtE@K9NN#u7EKq,`\Eup)Aa^dB,nr(e9`5Q"l]J(85]kO,=o+.Ljt`]%%/E`V; + .CcQL^QH.3IZVFY$0`bck?qdfF;=bYnOX0?hK7crubL9\D;JZ\71=9p;"F3gPK4m6&].Z#) + \Njdc97TCa-35u:"P#t1)$8S(B=kPZDldKPbI8CH"er`GJ.eN$@Q[08cl;q0c6=Wd;o;,c" + &AQMqTj`t?<<`P,e=B$"ib?'T#)p3!-AQ%`ZHC0K2j + YAIK%iVliBG4i[igc$P"F'McI<)k%7PM.5,709'I$jtVp,GlBddA9eq`OQ?3a;3PQOAlk=1=b^.Jjn + Y3XRV)AeF.6fF`cn%f\d!GRBDN[%U1jB]PG7;f7[^)e\qZiVC"fGZWp4o=Z`Iqg^:UL_g?- + /TCc`u[ZYXtPbHu,5_a5IRKge[b4;(31X'of^H9\]gugoLF?LeRan3T.;:TdeB2dCSICX?gW2(k@a2T*='L0r*:\lo6br3- + 1u9X?R,8ms**Msrd6a7q]LC"%K1c@kOJR:\,QD7d17DI'T3P_&HTu;*.`c\#84=>"CQlr-' + 1Z`PB(dM@)O!3E-.e<-pUU$;,/R%NY<.ZWsT3H.4QWgXhsY?=^dG'L4uk@_g$#.9;FX%E:g + j\pjit!iIcr@'ZV'.*ti$KLGL8t-UcU[B$Dd(2"=j*8P&(qIi]:FiAG-"]+4=?<4>"_.nc["lc-dpbACNb.ZFiq9D$KPL"'tI.alTuP]$_^ + NXK[BQ56n!0iU6JE2;(=:hB[%?g<-F:!qDUrYocg1GNg!@E;+W3:-`(p^Q;[Qb9PJ?"VA.I + H;d+08l.%\iPieRGG4+3l%7@(S!&h>Wkkq',8Z@_5o"L!R<*qddWXk6\4kIGLC;jD(cX#j: + pT%D2ojY!;NjCke\Q5`DCTUTOWRgpH>\^*VEj[g%=*j7?C0c1:(%qZ0p]Fo>on%ZI&;Jha$ + q9)KMLeS^"i0[h"^n,AZBe5l_5KG-/CYXd0RaU:kY#St-)2nCWma;UWH)lE;i@qF + CUEPUj09>MY;%![hQk4;nXgE9:VKtW906V*FD,7mT?Oci?68G"He0@fl^(\J>2`(;AR%Khq + .Jl3gr;h@m-X-M,>#@Q&H/C$e[ + D[+;Ar>3KU7m?ZKYYkHgglZ9RF%cOmdQ6k<,cZ-*?laV[Qji(NaEh^KV\[P4Y>oTcWDS0W> + Ofs7?Xo<@9Q/9.#f7RBk_]Nee$BJ'douHk7H`q;U^),3eh`2p137[nh\,&egq)cZ:('WcD$ + Nm/L?K8`uS3"c2;iIqUFQGe#%oIl\Q3Z\.`mm4" + &(9`0*Vc3GA:$QjkX4-p'f6Z=_V>Y&,ocDH6HXBA4I1Np]Dmrp94`0[P(9uI,,@'QX)=hqK + Nj:_ZbA9D8Z$&m^qO8hYCsABS^=4O<)cQ1AgK"#la5bp?cgu#RdC?=1laP7[,&VaWr*j:)h + -7>JmiC;uY"nZHS)WV*nh?'ol8S"p0!g'n4+T$L3B0(i5\`?i@e7BfLTaX55c]^NT`>VpM` + AV50>!1YqY\/9$a9Gg&G#n+!s1J6'X0rg\Pi[B;jTe.#I'@n\YL0)YqA>c%(XgrV=U8l;kU + u63$pGU3\`dpCa:?kV6OO.-F9YfH>U6]9/4X?3lA)2b8lj0Y,>JH]*fh"qm$u)Zbm]/4&e! + kHagmc.D(g&4KLJ(`&G?93CA`d6\INgS3+Wi4@@me6gQqQr'!LO5=>oH6l\Q^'LY>?7)V2V + 6tq!1e':@<^-)#F[>WkZ]ACKLhAh1%7-iUGo@ub;;+1=[71<)5)>$>89uU(4++rIiHqMA9i + O-:&`N$ql6H-ZD>s=3R5(m8L1[;DJ@'%93[T_"kgLmsAml$['[^5KUHtY+,o>Zm#2[!n\@r + ]N3pI?#F;_V/f3GaU-JK_k(=h1Vo&rW6Ue/kjC%nR` + a3YN+:,83+#,Gn]la_M]iUaS9V)8_TI`1/mD/]-kG6O]SK1F5X$Zkf,Y_`P9D;n+5$ + 2OA5Kb%sR?F@Jl]W(NSXb,hR7[&Qa2X\-g?9#Ro*gonleYY,t@9.[bnaT8b]1a3UbUZdaPdnq2`'omLb\2c#`*9Wk9e+^`En86ffLDC^7]Z1$]CemVEORIm5D7c"C\l<"E50 + WR7k=QkF*kr*kso.<7skBR/9sKBJ\YN;5\('hbD!`h5_^(qAI%sN[C?gP'N&-N; + /I#pI25\6(K$/1;4SY'PoNZ$R7gen4QK+-Pf6N%_+YQ\9I1l\4427M,tm:I;FMeXI4%pLa3 + kDH9O/elm)M:I/NukL5#fYCgriV)bUg-G7MO-;,gSq2ABbd$bp6l/X74@qCt"p)^FO1kZZ! + k&4[3dI5C@BD]\Q'kG0TAp:%WO&"92+)iO@=sc8+ahjQ+TkH;8SE\4l-^ND)jt$:jIKO5p^ + Ee:CBUmm."Ia?8t&"S*hno141)V(Q$cZ8I+Q&[=6mIqjW:c`_G[KdfHhZgPpfNfKih'(=@J!I?& + .'LLbrt$;RMD7/6Ys,>;apOdbp$Pgr_MXbLmmj/A3jm4Ynur2,d4-9bfV9EYQbcCX#dp7\u + S+SNJL*fi-V\`oTELPs6Re0UkKSDZn378uuN5T>.*I,re1"LoT=<2=i\]h?Y'LD:IH#F9t/GS\N,Te:P9\;bk]*:]g;U'Yn^Wf7/eo/ + ,gZCT6=g2K9k;U.B3\S8es%\MI0W+3#LJHHf#;N-gnIjJbr]a;d-RU\jj7)bd9$"gh$qe<4 + k!\2dbS3Y?"MPlD:i-Bfj/aS?&dR*rI@uWU4[!cd`m^+rJOtj\_*I0?7gui4lTd:j'GEr?3 + Q9oI>cOukm0F)dd<,p"Yn[^m66f"dk1"pI?hf6BN]-=9f8:rSXCXJn_("pft>p>eC[7@SVu + jg/bIfO"E2d)U.64jhs08spt4;2iEC$u>(&l#7B&e72H5sB?u8P9`N;Y\Y#Oel:;p#Sh6Br + uqc,/,gHDmn9SV;5!^6"C7QhOTWW7I&OhM; + Gp!fd6"GI54qon)!I$;b`Qb*_]LkH9,?-I>e_+6#H*f"NHJ%Y%a'-gb&PkS(Gh-B.0&MX?GXD4]L!QJ?>[ + h!Mi3D=67trnZuPhej1WB\nagn2:B'35/^[kl.%=I[K@hl#)OHBpS#O[d*:pm;C5;==0JN% + *.F0nHck!hSu/JL7]U&T]DF49`Cc$5,/I.V!jfB;jB@M>rl_T;/2QSlDsVi7,$qUDWPL4%. + &$"r`IZ6>%OS:c7W>TL:#Ha8S.>Ao!%Vf3k_Vm + ZnEGAT[VJk#!E8/BQi%<\;2TeR^dq`GGMs>^k&Z0%%Pud]s(+:mTVX^49[ + I.Is(_D?f(j^FO?Mic5k3j.DmY:2u + a#n7s4RVZ$F72;6s#oV=4*.kkl+N/LL.6)VoQ4E3l5'*B/b$2d9^dqfl;pVsZtF0ui#c$>D[5St?#Yh3n?6%L-O`th%60+Q30t.f::sRBB! + 95elg;&M>SM0J'O,au8?Wh*D\tL#`^.UaCjm5k]jEK(b&"Ha_G5;kc?O@>GueJYgh(lq^E[ + C;-0O.6ia4(F:FT?GMdmomsoWFJTq-Fp^_t$3P'9=2B17,=YDc + ,7*5'RkALT_A2cdFpP\YKN-O6>GjF@2+,[cs3dDg&q'lXAVF`cR7LKSbbW9)S7E49'@^0tf4? + 03tmp&2,b"q>Pdn8U&kp.iGD:;U"\(,0>7cX%B,QH&X$)"t\bp.'Q]cEnOSte5pudV7^Y=1_O*Lruknhuo2(sArf)8rFIO@VWJ)kln8A(Vqoe3'476p?4V%!B)n6WD.-`G(8WQnRKlZ`)L`jl&. + Xm;MBl_e)t^TXGO&Ci,A5p$^F"9!DYJO()fp_4n^s'7r7YfLoU+VTXM@1/@lr\5=MY#hm,r + -CXRO[tfHa8DN-.$[G:P5/A%D^_$gk,WX[g2f6E:M5Sse,FnbT[;95RM@$-8Z#D:[#050U: + "6*WjPI,B!AYBf,s\O!oC\$>$2Wkq`<0NZaJOpp<.lpB!o,(/oUEJddq^s?Z29bV?6t+=hj + HnCb?eEM8j!HlNNS=,EU#YEg_eQWR^:ARf.EM>5qX([[V- + :hfZKdDDoYVA,h+si:-Ns?:PV,X&\`d]Zt^kU!r/(Pu%CgA$2m"pN_2TkC:J0np!:R5hrnfBLfCSb8It*OVpAhtT'EJ6Mcn#b15u9Z["Mp1I*=0KcS085+:olAZ_ + bd^#%)]=)4V5OD*'Y(S;)CiX`DK<3'LgDS?P^WnS7NVY0t_3?7a]G**QQRWJKDler.Q?9P! + "LFaO4%M--=SOTdEB0I&AC8P.[nJb-Kg-/k]+/_(W[e#s`_8'0OZ!bu7m#2U0]uiAf+]SEW + )[e%G35cVrWp50q^V!C5"2*=5'Ze3+C3d8XBh7a]_6+\:$\SLI1Ze@dS1do>-`:=I_k5u?' + 2*D'/YeNHc/eQ#mX)gK*.@DVNb@_/S12*Q`?Wf0.N'h,dn8G2:buiHX3\S`tIWf=g^%hcJY0I + c&cV!J'*1*XRGVfKKn#iE0D(L>gd6+c,,[SgfQVfY0)!j&k.uNoSdk6'1/1*_DOUffi8tj] + PnmQK?eK@@61[SnXYUftMHrk?6YeT'+f+JY;41*f6WTg-1XpkuqD]VWlf`Tr@6[SuJaTg:j + hnlWW/UY3Xg@_6E91*m(_SgHO#lm9Q +q 0 0 409 407 rectclip +% Fallback Image: x=1, y=285, w=404, h=50 res=300dpi size=1055868 +[ 0.24 0 0 0.24 1 71.726261 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1684 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J,g]g3$]7K#D>EP:q1$o*=mro@So+\<\5,H7Uo<*jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/mioWjnAY&^gM+`4=1jRLW!YA=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&t\ctIN)4XQLiVpoI(>.nOW?*DmsG$@,,f58"PDKfeXi0S^6MAH=;fBr>1IXb_>k + P+oS^^pnX!PjdJ%0OEX9GI`IODGpB_@VYP$,Ve*/ITH-bV]jE@"9@*_pJTO&B'\*,88$.9N + ;hHO_d`.`:8LH(rh].D&GhUXBuR[OVE3Va78MI+imP4K-SI#7#`J5&T"!caq80P--DBMXXV + E`XKa@t;XA9^t9nP[,:n;AJ`$41%=6f[D9[b8\7 + Qb8iYZPXLZ2F=.?!21NWjpmR,-6r@9ERTO^sji9p+N4(+kL,H#>@'AXS!YR,hXeCR]mC[W@ + sO=mf.ZjLPOoU$,Z4K9^%oL0'r1RJ*,AC)VPtVeVZ\X/YC>`UeJL5LoE)T1@`1n\ + >D3!@6_,"@JbNZ2ab%J(OHXLV*G60E?T`3!02S/-g`&-3c_k&3U96s+1eS#f/JP"^o7_K1" + 2q_86-VT`cBh:jdZ-85)0T./&?KXFJ*c>72Tr0np7b(Q\+S5,#>CL@F=H6b%OSU'*W80`E4 + `=G[XL*(bt6.O7ElSJ,Ic9SlTg)T>7L%3iGBKb0gg`OJ8?i>c3Wcs@/N8Bh?HS.:*iWWpJQ + (BGb6"+S_Crl7mqrhI"'O"p&b7tKtIU\OA?j2++';?%db4GJt2+Erl4s!)'%Nfk#M8P)eY4 + /?tcP4E./aa#p/V#6o.j0h_*8Ok4@7&Q8Zb#OMmfde@rY*K^G=\Dn,3ld'VQP/RgbIN$SAc + ViO6c*8*8V]$47)t\TVIE68s"fF$n!Wc>B[&Q=3[U*D+60#<9t@kp-M67:o6,,7q9(e],jC + iJFE7r>s#Xk@m[b*;Gu!>@3sY]XT/5JT:YGP.a8"_f'FKAN\dLgE,mL%fr,Ias)+1?>&#*# + mLlNr*7>LgYUJtK?d@**UW9E391k]r]n\[q44XR!>P]0OC>X.Ii0VsDnQr?;c9rYr.VY'/C + %Vg,^L@mg16iNb.8r$8Y.,>lp-UJ(HaI>q<=.Y8,E#=3C:t#PcrU((.k03rh/)4bk!:r+jrs)e + 1FAM]0KXJ(Zn6ALt6WZPHAPfulKB`i._#(5gJm4%qL9i;^2#Wmob7?#+:Je$_0QD3+4bD/H + tp[hdJ->K_7LL\T<-(CK*U4+K.WaT&de.cfBsSTA[>d(2/0IuOK6F`+nD]*%tDNI/3[Y3k, + Z=,-9OiLDc\rTCjQU@hjIfm3j>n@f!(IuN.G1Ie56^Gk)#N6CgBYO2A*=9em9Z)0bT2@.+n + -O*tk%Pid#s2#UDB]2W919dnu+6+^UiQO69E<[@#(l3[#N(8`0/ghP3QPGI<>DgEa0ZHKX@ + uTbgNNoWFa$UdbA&8.!L2K5s3d5%YZMX#:A;f:.Qj_9I8K39NEkS45\e+6*?2B>",61\QCH)?=-';?* + >35).H'01BP_Hnbm*O4$'S?OT(BV6mCldmEe%Bu0d%7M_WG3eL:E;0f*_3<.MiXC1=q=t_a + H],1YM=_g`EX[UmWesbo0O7@@q[Jmnm)\6@n;0"=Frl<\9pLW^=F0dcLrtbb.Bl-SB[_")D8H3'`STM-4+hNj;/@k?ro`*!TN_epe%<),miUQ@GAg4=]g]s4h]g[#mu_>!\,cOEK + &;,u6"hj[(C1e(.u"T6(W3aZ[Gfn3=`I1f5PVE!ie#XK]$N1$0I[=,R[jc$?h'kmOn'Pha8''gjh.LXo3Q(TH&k,Nr;W>[*fn]p?UO)V + -`H[oVq-1/?0]+YXnFrnfm@(?mJWZfp>5,jHbY!tIL!O\?N9!^+,TOe2p(j00B@N,Q1KH[c + h_!1l?sbTo_u>Qq]BbBrSP$W^?=DO?\e3$&'anL5NVhcYP!,`67<(komZB/3e'[(rSSr;s& + $?c^rXf[W6kSn2N0l!jEsPn0nGN/H[E!!Uq$0&3u + .%]`C[;!3!j:YQ"b["H>)0s)7ab#ibjplNV%hO/V^UaS:i.FHB'-#H8K$:k4]_KT_FOP0f# + K:lpo55a0Q_$#**?ku.oQ=<4ASQ8#EJYcsIU[$U9jQmm3H8W=--P71$KRAlXDOO0HWUujG"JBKE&?6;7as`tr'V]R_UM;QYnK=US, + c"%+UhWmBl;J?G?D]G:VJ9l[l?3j]8Y['@ + 9NMaXu==WDG#*(shNb@n@^e@-rK\#Y+il\cf7Lq&A7\WQ8#X-U=iWk'^e+02i^9?On__'!O + 3^!#"MndN3)ikDJS^8p\9lh;SS#f%'$^iCp@;D>qm1;V0[_CR%O;F&,T7)SRd_pV5rX@='Y + :F0gT`KFKFdTjdCBZ=k0-EM1,Z>dkNHH:,OaK13>ba0_5JB:Khb-I!TXJj=jPg#;XbY6D5m + +T_TUs4k_.]i(gXR[2Zo$D4Q/$/t*bo"h``@NSPdSe<;NA3":2Uetae?):om85Jf@$T)`2i@Z91mN4;=,3!Jt26J_mmPa+.9/^Zb2NC]oDIhl#FZ1pXk7`7+dt$V + AFQj9l33J<%cD'RPPrUIclY`sfo:5+6b3W'+3c;h%DV"cIdlVWY40RC#PJ93._)ueX4L;pf + o>s-/brml94`fFB;rKmGj$!"q5-sK;oB/=om6=P>5BHcfZiNCU"[DVa5Vrd4oF4)C$UJkH6 + 'N*MYB'U1iZu>=Id$A%e1gm##=EH;"ggDm'L;n%6Nkn1!9cKIO;.li<tB'%"t8F7PVWO9Pm9.5O>NL5d";SqTa.fY9!F8;d#a + HQRgDYT$40^*d&<5:_[>XF$T,s5PgKl\EsrW#9siQA&BtY\d[IDkRkWY.0]/p18]W=;isRKl@2bf9bgN% + W="CqohcH]RhiD7(/\i`9$J/0@i(DE(b4P_&W3c7F'!@D=0\#.WsoqdT3,kR)KgrW.lSC^P + 8eY4*",I[n]8-RUKVZ`>-ZNsMg$1=h-4ZS*`]fo$_\X7_]Mn#+3V&sl`VEgeKBdf+Y2Fn(# + L0WjM#XS^K'YFp$j6M#:!7E,E*9\ni\7=6Gh/H,cj?Z9M!,<'ZKWM@?P21lsV7-FN5q\@^: + 2*dX*BYT.!`!.#bJ\p-0t2Pfo$/A?qZJXOC`9VTor[.e[,mXS")A^LhMrcn`\iZH$SjcI-P + ddl)V+XZ^>L7qca6/s_JLc!],RPqNo=f9VrZZNFqoM(k+u0bXuKXgKm,ZS@X`gTr*ZQGD93 + .,C(>hDWU7[a?nUqe]7tD6=+#NXXk[9/TXEiLslCY'f_D,3Ys;-cHSDZ1HQ9kF0m)VpH`lP6Z_ujFLOh`pIB?b9m'N7Fa + %,6[paEGATd`[FuO,YpMG+5CNjuBGF*Gr[t/btKm2.*GVd<*6;KqA%gOqTH*fR21d=Nlqa` + P6HF/*Hkefh'G=L2p7hf/s1hV1p^koP47eb/TW2W9ge;L\,HP``/O8jN-jGio*N_&J]Y]5. + LQB0C:8iQ]@Bc0P4Mh#Lb9.;emGS(8/UOdO+$?XODoZIWP,u$)$9pdL@OMss/jb%[?Rrn$n + (dUqolVG#\:cM0FnFKJd<1nJZ.J=V[2@R*f(SeJ + pHC*4X_2ERBe(YoRV.*Vf"CFh8][_d!qq*nA@SOoJ\:dp8@J+B@B^p"^dZ'jac[?H`.O1+- + F,2j/=\,8L9rp&u^b$^5U0PHnD$FrQZ(+_#U@-IR%$m"pu_HRJMKQ*r\`).=s&[:G\>.G8Q + L&trmBUFMR:.i)VFEkQ(qK[7)FB/traG(4/1eS$jlBTh=,H(`].kgf'1034Il(8#>BTeI7[ + 0QsJLeu[Pj\"%WKgUYQa%+8=A#9$5VRgK=qIc]eD5UQWVgl + rbpYUD1V#^7t7p")bf?^K9YH'^pX*VI[<*OcmGCA2eWYaUo>+)KRPt:b6Xf(tdQh6>\T*LU + WY.+F]3!k+3NfuO"JMao1p_caB*J-,iYc2>gRh1rH02:(QRIoN7Pio>34gJemCuOFOrO>63@hlr+!]PIP8k"K=Z9aa4^f + *?`E>PT&n.#M9u:pl@_#RWq-RW7?^=i)A+iC8[Rk$#Rq4U$AUI3a'!9+cZc.*%QonZAfo%C + AOY'_!dlEgJfpufEV4'EB0);Wio(ecC\&`E=C5nFHp9/6,aYB*jCUqj7o-W:Zadns$Sb_B= + )iX*qTNo`[q@1\%Bq[I!ToO>k(EK4',GLVQ3Ee)]m^mO$2*,qAV + tt\br;Fu'J^>DBcs6t)r=..;$djA7d@D$"]ckf%-.1NtdQK,/I4`o21"1>LW'X.URc/aBh+ + 8NJrthH[qO=S_UI6$D8%5h+GI=LJ]1KUV@BFrLe9sd83ot + k$!NtpE9%rleGRXs?r<\_)YUN5LFN8tP%$nL=JooVL],+3=*t7p)%5?PBRkKF61YlcuZ^op + *l0Yhp=,#Jp&6hlcqc2N5CULZj[FHRYC.V=["h(fp[b-:i;+ok0Iu)j3M--%QrZN%]PDSsF + (YbLk[H+I4\\solA412\)\W!q_)ll!3'qN!dl + 0!'n^AI;(1)muWKd%9#P$@sLQ2Tr&46^cMP;W_Trh.3")'t\:_A4$+122ue[?VL\Pu]WW3F + +$Q3UD$(`#d06fknZdbfq,&c5]dMm,u?RAXIjLB%CtA2K[B1d@">:`qc7qp5d77lY/A_a:3 + JL]S2h[MjrL_S0&cnIj$OoSSUE^CglX_iHh1\4',q<`%j9:m!coX=@`SAh,5a`&6 + G2S@S5][rhVA^0#dc6$oKG9ciEld$J$cT3l.=#ZnF2:DM]pj\:PT9&c^OnjKlq7i-hs-`^* + SbbRMqL>XD?a9IBZhj^0qmokX_FP3r"/rnoW83A!)VO89"eK0uD2Bb0r\#+%rZ4R#iNF\8W1J%KG1MS3 + W4=*[lK%a+a,s(&HHP@2Q,`%fFZ_;.MnS9KHbWl-3",o&roq:.Ydf'*UDJo + >\fKH$N^G+XF6%B1r6B48S]t\McI`&N8#"FGo\(ZCZ2^lUlS[E;L<]_Yenqju^!?#e]&(3W + DQX3PK>Z_hGoS1J/"$@uo=7VfG=f^%lTa/WNlPqAg[@*`?3gLgal.=9r9aM[XoI\[qHVn7M + gOX[J,MS2Do^hJjaY%Kqu$L[E"i.e*/FG&"MkXK)[8S4fuANY0UsEr6L_?;J"'9!3=VKTr' + G6g&K1RaS1uit'?)\:pb;Y7@BB,\hh2bqhdog;PTBDJ#l<7NFu + <7IeA=]ZK+D.%kEZXqMG@+FZl',m(p(`Bsf[`?RA(aBJP@N:sD4bNFVM1suR + 22p0CYL1aYATaDrOQLo=!@QQRFa=DF58c`NHAIHq2AqB2kB&V?KN](J)<3@omU9Bp%2]2BD + r-fi0ZD9KOBNPP^hZ%H'='DAf`7UTB6n8N8kc&Oc3ei(.!uu3?CeBjI&2$A*!GgJ7B\sG%$ + a"3MpGY+6EOZe)]$Q#m(5Q,?gM!"Fgg]#rnY*P)j,/J+8`M:]9ai3n\4I]nbuc0S/2LJn@V7%_j:3J + (k;(p\Tc*b>`U8U(<+]$b]?5!3R8Jrmm)Ag]^JG>QQ8:g>4in=r`XFbIp?4O?#a`8p>qo`g + n4Q&HefINkC?][?6T@-ptcTsj*=(MAq0WjJ">eLI[?R,(.\D.m/%jU]C("LH"):$59IgFQ6 + .3roDL'4>Z^m=J)0mKJ!]4sQ,ipnr;Rkjqu/g2==XXS!J#+r!^Lu>!_<$d'lYc0^rRN!@"] + (+6E#CM=;M-p>T/6VAe.2;56kU",D8mZ&AiZ`$6_5TKoiGR6E"<#@>ljo:t?OPG\7LpZoWs + RmN4gKJI`q=5DX:`,"-[8&n3CbM%MKh7+e1Hifm2qO[^h>33p># + 1Ia@M).Q'MND[,@`q\4,,BRpcOj6$Y..Jl+XC$BO`Dd<8 + GO8OA8IHm&lM:m=YV5]GbGfR^]6t%5q$:YUQgq-:^?Ll+ifEq-02G#b0j!88M%1o"I+s?E^ + 3)b\Jpl5p0J<9*AD(._]bg(HB9S95NYY_R1hJi9^.s#LWn28K@Xb]Bra,RR.d"jFFVQW^Gi + %nj.'&6E6`aN3[ZcKSWg4#:9!21-^<>S1Y-6cH(dE6I/B;;p0gb]Ld7fm"..LKN=m+K/>]H + V,r32i9'RQLae]UtAGV3]=tiXm3CKX,DN0n75Z>h1;l!u(X\s2O8]phqV.HI/QE"@db9:pZ + '\8^O6N1=beN9Zu'Z!`.'2V@k6SZS7b*bk`AEm$aWJ5GjRH%+XC6_Yr-HOfhHE:nA`EP7_q + QCjUFE1,:`2n_2kLO^D=jE,gX_1o(S`@=e:>t07LQ8s;6d@YAam;1@p:_U\^4*-qq65YE!h + ^JDJLjCt60KrA+R:#%&?V/%85Tn#F?570dNV'b\J89>>A+VC0h3"E*@$esC/P+"[>>JC,eI + K#m?1DVM.53cUp?B:"hV68I5<47d$q0ND,+.I3[Z3CE`:D3:hg'p>h&bHPZ!<7f2Y;=eM*h + ;e_*iXM,2t)]Vh#`j&G]*\(,'IFcs,_L&8VX<+N,$!4LJP + "'cZPENWlq]'rV_?2-:%Y4_+6=1\*5$hk$;q<#%NiCZDGA&QKR'_%in_? + 4.!:Ls*_9e5b6TeW_nqE:OHETh/4\ZpjT&,[Z:MKB6GL`QtA*\TIa%noo56%83UXW*)"@-6 + iWSf$8Jble]C?R)MTZ!7]g=KMDGr;_3cFDJ0pKhY6GHc5`_Uoe<@r,G5Yr5Y'fm>)YD#Kp3 + G&Gd_)cLHf?*Ug2f%/TFB5[+M]sr7jj4jHtEGfX8\`5N:PL>qEDYZpT,ugu&PX^GO$#)`8e + @(]*p4I;[$SSF`"/#K,@o')o557n]Ss.HHE7sJ;B87,ZPJ<"F4s@i=f+-be,EHfCS\Z]b,N + N)2?h.Yd6>0-ijchAuo(cY+BV@Sa1gc[bnrlSSa[r9S3g0+28P(?s>Nh"=K`lVcioO%Q8Zfe<&NeLg)[LXF3e"T + 6g/q%UU9`_*86'mC@-5G8BF'JqWhaX]p6+9l"8_J96oqWe\CjF&NAK,O76)o_mri'6f\sH\mh-7CdDVrmT.[0.t5>WqQDgg%\Q^^qO0=q5b'sV + fs0r#T$lQ!_;)]A5g)C$Xc^uKFgM-_ML_X<]@["-tARPe!2pTJAsug3>?'b14V@MO@G1B7[o@HjD) + ]HNQ/nP7a,a>RubS]Bh!2a3?s/:6tEh`KK&4F`ZUJUK6p+9?jWm'\[6Z2X.:[)S]AX?3U<> + qXF?&5Tu[S*WGA@[PGP7*Vn-\u$[\kPm"W*e_7J-Q$pp^[X09UUVo[&e.UoD>7#rg][Aq-Q + %B/%'/%@)+\@\Q%.iZ,O9!kX7]dnJu47]olqRak]_Q2/&]L8>%JGD<Ndg)6G.3b1bAtBd5cMffb"n4dt*#r&.Vc!1<87+X-WL-T;&0c\n4K+bFWgd%> + C5d]Ac;3jK.i%^E6Z,@4!.@iaD*PjNS_,DM_4Qf"XkV?B\Z?n%:X;oNu*R;kN)qhpA%.>tl + ,>S\'6;/]39I:aD-QLVa*;_\3UH#TZet$V/*HWm>bV-jJr%oBL6SpXGlU=VI2UDjkT!ULFN + &dL^"@L@,[+%7cCJVk?5Z!D*6mW0t8c_=K$oNV@`**PfXUdjc&&k@V;&p6G/J-;m&lD(2;, + %g'>o-@<(f>,ed$/d,F%0 + .X`b]ZnXCoQO/4U.$7g"K8ltXtn/lMp.7ZAlr/9-JnF43i47Qbe@FCN[&Hb#>d\ZIn_S9r_ + tI)k6/8,YV#9g:ZeXN2Z>81]CO>Gt(u6ca+5.HH:G]c=,4j@k;6JtoAhS&ir/i:*k>e3S@]0 + 8==9BC=L`4oB$^e>[95.&0[*5fMUoZU<`9Ou2Y3r)I+@SE9aQ=KX@.;-DSc+fLhfNSJ\Csl + #s#=%k1fUUO.'\:N^f>'KPR!]*'HP&N%gWe925f4=2o,#[LiBZuP:,EM:diQ=SUs;pRc>47 + kHR)b,l+scu15$WMllO")TGrP+cKW-]&\q0Vn1tb(:H\3S<:s$uW'Zp#_R0*aK02r!4\rCK + _[A`#lnYq:YX:hZ6Y-;F'1U>+Zl]>?6a[*PD$NGo:rEgR:lI`qXUEM*6a_%H24Ydmm2t*U+ + C#)d>Q)ED%Dpm@_d#pS>[>8&D8fb&`a!r6>`Hm2L!*_lbM95D7,9"a--mkH,?(V!\Wo-EI5 + b&>IRd*\h-=L>2(MTd@8dUediI+IXkI'og"h)4?#kI$AMqJ-2S!,C3H`c\I5sg!H/0%%;oF + )S]feCZIjm6>a:k>u/OEdVK\je]e6Ic.@\:6N8)F]W?Q=tR4H*7PNB8^deC1eSm#ffqP$a2 + G8N`;=1V1\MQFkSR5"W^?-V@bf%7"I?g&;=7Z2]Q%@/TbZH^G^QjLHoD@0[EEIkl=H(_E"OpPTehF4b@i + X.Rjtf8Eg0"H:5c!2g-9"36hq:/Wc3]unAiiaNQe@B^)J%u*Fdg-hS5+\a=kr;Y]H(/Qm2N + .p1>M?8'r5AfnXP:(A=]X)D5bctWPZ?K_SrRmjfL?2r7^3dgC4(Q9Jp$,<*MKej]qu+XgZS + T:/HCB6fV)!SI*ZO>W"YQgWW'nQ=!nB?sMO4Afb@B-=LL=A7e#:Ai=75:1iWpB4d0;AtEYt + Y%_LVC1b1s;&.C3rQj:/jN!f:`XI)]*Rj172H*W53AoZ!L#'6rgKi*LkFLBT:3PfLHmUIgk + >GYn%!>KrG"6_,$!lT%S`FHGRbhY1$>,#_h<<^L5\Q+,aPHY[pT)7i!idr*1l(7>@9P4l5ZPKmKV + U(#@NLf@%,3g/do>iWbRI=]ce;%m:R%1X@ic"&e5N=m':%`'U%7Z@:ZdK%<:?])-8`(=%9W + ,`Z.0k*F\&S0$SZ*^*-RamG]tAle,M%?GB0g-VUrr^O@T`4bU$QqVnm/0MX1YfllB;0B2WS + @g:(c-&gkINm4\efa]o*SDN:R%:c9_=^9s'/.^#pR6a##j89\R%RB(plXVLq6-Z(`7b_)f7 + Tq;qjFsD9-:m3oVGBl76B4`+Y"`*,qJK$/mT_BuhGDqt6KKF[:^0nR2$;T.0rE4:p%?@k$>BS%d)VaDdN*TDJ\cjp2>][DiX[4T)3Glr,8b9n%Sa=mf%/(rU:%Uh>VS1G'i_n$OBLR?-`plcOB=4KlEXjkOgb&IXlBTJR1M + $B+0*"?09V]L8bB4e*`'!SNu&6lfbpIB[2S#j/R+rX2p8k?P\c<,t]XnP<0NPBgkb\?2#h\ + [jp7$hk$4mXo)n1Rn8@)?hU&K"orB2!iu9)C*j'72PT,>am)YZ/<6RuD@5ZP$W^$%<`T[Pb + cU0Be8!1WJ`(VU(jlVldVgZ8!S]%:0Jl\"BZm^^'/+4lgjf= + iHg5/(Jq->WR1oc*R?(;XIDpgTi59^]jc9[N-CL[9BeipDV*[p)*=b?2`Cj%!EM\W6%C$J@ + &Y=[eU!/XH)jT]=8Wj4rbLVUjG-)!uUQm>[[ip=f5N[I(mTD:B`G05j0@8A\B(D@@oR]r4_ + MjCI)JAG5-2]rq*Y;8Ylf6X*#t[Bf5>"(cOIDV_gHlojeJJ8ZK6mj5U727ucK!H88Epg8`" + DhRn8L3SK/G`R`,T8Rr\N-MObpqMf5muDYj6"E>F$!GEuPWPt-T5q*1'L>^Z'^O7A2mnX\oaY)-hglZ(@@e3>!\h]Qq5+O + kRr.M!`\0-e(o!c%;0.]\Crc6e_qk#N(*Ebt`SSA"nC)-sLm\= + MS?tr,=o6"MMV.=s;%!/;FC8@tQ`FrZYXl0LLi>rYS%;kD@Z<$V+FAB17?#oRU7dFq1oTr0 + OB1m$++D[rSFPa[^a&$mW-!0P4=9ha`L;JAR<6Wk:9]j;rB34GD=NrbFor^"J0$Eq519h&. + (nNTpDs)9Zce1KBFuDA@^LPPXQpEo"=ed0No-7"h(uuYTD3X@0[R@FID.IP?pAWoP?ZBVRi + D3e=pHD:3htpTiqY+^YjTg$+`NP("rFE8 + _];5U@Y:\qI[Y,e\VCK:5mK"YTu=R=Th7@)D8[9IL6qd%4tEk#BBf3?5Boln++DjU@p#5a% + g@5(eg7NWA"`aWqt]QQdgPK2d*=_ssB24;a%1YKG?Gb1f0go"W`K>OL(rame@2[)4XDo.K;7q)ma-$:J+NLaG9-V + ;P1eAq1:ol#kac_#l.)E3Z+Tana]WD6";VV!C^2LZt0['dthKhkZ1s7]";bYk$9ZM8431oA + Rk;ghoV!U:j'>`Eac_p^@5gRpLjZlY?AIh2$oL0Qb;3ZJ9Fe0uZ,t_'WXYEJt<3cFF*Ye!+ + :K/&L78hUt9i"`\k=OQ2e[8so=Ig1DAq/C06/>aH28s]ESFt-UJ(QY"JR[83ainq#p.^lb= + dFK,BV4H:fkK0KV6+Rg(>Z!2>INeIJ"2i7a)P@N3W=#h=&%:h?'fuqG8s[ik'u6s^$'Rpf? + qQc*&mQgFX,j,]019T.S9-r6Hp.F1gbEJ!sXR8'uFL$)27.rD-MFFe+"ff + Uq;]QRHD84i-5/2M;e5AENFZc*`nQeq;-%QB)4]ZraqV5YHDK+T!,Wj+6 + I4Fn9b0Gamo;K=f-]I+'6qarSMPAAK")=jn]l[\+!sXs3^gVrpT[^^\@s37YCmdj!"Q?L^s + PVHj>4t+pYprr\<>I'S9gU$c.sd"sDRu(sl_&_;UjhTch@.E0u-6;4.c">UkGh^)',pLbd$ + K1PW-^)ag!4%0dZl+7StG_r6>*@JiNLE>3n].#AS29M668-7=a#UD1cD_27\c.`HY81#f9> + (bou;$t9/Q@iSs:Oe+?u+au.9r+h:sZ8Y8ji>Vsi`#nO#43(cW0]TfnO)afAMqIhJU^Vkm; + ?fLHfb5OJjG]5=r^P!9(bfKRPS]KZ?&Sga-=ko#PCi..agjbe#Pblp17D+`GuZ>*@(@)kr` + ;_nHRu"d\X%9gIuAl'/nNhLQ]h>T9AseAjr"+6ipL;MM2sE(@($k=rb"q3Sh5Z?d$h:=CCf + rN2.kXtRtBX::'#,iV]c!TPCdl$@;e3)0XdOY]NNPEi]lUrocSk3H]Vt66>+sY+bWI<&R#6 + )LuedboGEZ&C(s;h@4S%ie)SWok.7`nFF.:l>S;A,lu9$(3t7p_EXH;o(P + 9e<,_[[9thAP9#!0rD>'Ro>bq:?fGTVc*2_J%#WJrR^Pf/p7lVP*/OfYB + BL\,Wc*9dM7G>H;&#XU0lkg(`>2m2B\6$-b + IX[-qbYdXt@3\NsEl%;a("Njd(ggaNVMVGuZi\&rPd.Rc^DmLlR#8tf@f$^@^BV9N&`d9YQ + CHES).a1Bl'lE(5ZG4*o7>Dm32hAa8?QMolLf:?s=Q,:18D=(XG\Toi$jLqacrEj@MJ7"$A + Ib6OV@MeuuYri0-=OmXc3$"!`ekLbL/e)@XNL2KV-%oE23bJ.o$XmY)cuA#5_lYMd2#Kbk> + (^!npn&4/B"HV5j(--RRBj9&AD8&H2/Wlf]AoE$Nnb"kE4Gka\7[#Ggh&upc:uKm7lQUdek + L5RM9n;"V!2/8Y][K`.lt^fQJJUSbKfA-4D0A8hC`>amkZe4GF%lmX3l8S^$]ML`LPgRo0.6UsFZ,m@]=Q%Mnc9L;iWVVrdTKt;^ + MC'\qkod6Ggd0!?9D,h2m4A=;Q3[eb\V&"3L=Otl;0'enP)0P)RN027AllCWrUW196gE&UM + q$F@f>_q(].S4Ip,IHO%Q'8gd:W_ia./)`'PoG0PmKm;Pjc$_)dbf.?$%'8X<54g4^1D4#R + 53obopYd7Cs@gK^?5@Php[U,:6#K>"f1H"PIlIn8`dha.]NFUpA1Rn@Yhf>oGSG;WHfp(OaD/h@D\LoR@T'Nr50P,jfRE]fQ*]&! + dWqH>Brt!G[?diDgFHdte0VK1p?U44H[fs5hlF7N^>`B:OmLU]bdfpj==\`lFRG=)\r.KnH + j,LR[`&d``C@S\B$13K?H+>?`f+g;#39 + ]=FTQB(DpU,X/:_]`KZMAm"3d"O&41GsR*"MRapb.hn5.:5ABQ?2bDa7A#C=Tfb6E`^H487 + ,m*p??fQtTC#KOK(bj>[0l?d@A#gDddD#Vc)r6T9^Igln7XUp$-'[`indL`R59d%l$0+3Rs + KJ:,n%60%I:t*ZV%+*/HaEH(;eC=hXeS(!R#aZ#qA;Z@Yeno8)Dg's_/NMM^$0&"RUgC:NS;)6p(2V]`Jgo\ZQNRHVHBUCC)'Wp*Fl% + n1fQPG@=hPmC0$*b7;\JC2(S?;kM$.B\^d,<1Xi=6]rZ%/E@+s?E5T:u`;DCV&4&^MIm\K,\=*Kf5"dGl7Pk8k6 + "*`;5F&apf=m2qJ^+0kP^dK:U)#9WLE+ArXkOq-gG_]aZXlu'u;:<`)u4S$eOXg0MK0&Im, + )X_AlnS[X]mgAN(6R7gb,go+-&kaN+:.Z'^nOk;UZ=<5T+X9C_,/h#9Z?!;b,kH&$o;b^[0 + 0q!Z@471)oW)`8Dc4%t:/Oeg.F20qDd^)\V"T_*p@>AsCS*+QI.sqX]'k>'o"t1M``c0$]Q + 26lP1!VHSGCL+/^*(dZJt`Bo"OS'0#dtilhpYh!;nLkrA+BN&(^^JgG"R/rf[ikCbYl&'et + dB1!D]YVNh,M"TTm<`@q`W$r6mT>/h*k!QN_J&0f-@,m'9'!lk;]cndOU50/pkENhcMYXp` + L9fA)2b)fO*YZEbW[!&O_"t[LnF+VVtD$L`3bj]l_CuGX)]*O2:#]/qTPG6BBP^\^]Ik:m4 + '=/$BW="`gJW1`Z-n@DV]$eso\g&PV#C;V + lCohGIV:0k0#Lq%hIa(G5pMXpGodi>><1(iCJUEK6[Ur>DtgTTPp=%QpG&6V*(^)X^A^1m4 + U'\Jss=UQNGd&]cI7b98^C+V_+&oWq0>[7_4C9@URI'Y-n:^J&ae9U*jseC.*uf1ct49iTk + B']2Y\X!)L&:;b#OZ?mcN-VomU,4tm[12-(g4]%1$m]p(hP#Y@W=](>`;:QI[oc$aC%jhoL + ;UkkJ%c6bsE?^Z/9Q]r,&gXjW7e>&[oBTNNP)T$%$Rl^N.90^I;P<]JB"*r"S!H1'&NJ*i@]8Qq[;i+ddJhB]O9()05nZ[bQ + Rk:`ka=XrK@Il',(:%e7MVprp29mn+e^P-H%S=?ILP-[IZ1j=YmJ_!OG+1;aeXp.q"Q7Dic + [UE&>WL=^dCX";"&Eba15HUGhUA2`<.\.67o!rf$b9*es#L5'9k:j'K4aR4VX,Kh6BaDN + T!:6pI,[,74GQ3O`;0$b8582i/rLYf+O\)E>[2H7rBmrN\Mjf2Hf.=*41@F]VjkQX=o;@s42IG1UAMpM4shGB[,8G;k"qQ[p6#lV@ + *@:GR>oCkG0<-:u9eQ#U&7oboA.^>#2+M:TD.]2SV,W03CFlP5/??aY5TrA(B$^YMEKOK3*&V0%+dKt:sLbIAXLeU(t1NbQTFoqJ!Oq52K9)1^C(g$$Z.2'0Y%T-M*2;04h1M#e)? + BZG_fK1C!bjr\J!HDV*>`of!G;+oe2^[tITU4(i\c[AA1K9Ng.!OZHP)Fd5m8_T&Ii;UBRU[g#A#>Um.b/TAP-Xg>I.qc9=?% + $tP/W!@LOn!DW5Z#EeY.SQc[tpC^">2p0=?G\nUq;E>M?5g28]:?!F0kFKih9-2:np*jbE]Ye4)D8GPS>9X<%DgR9g]drQ+ + 9s1Ha?*5m?QpiQ?d8Y&R:cOa-SVa(9R=tP[<90f9c,AiAlT][hdruNCR-4))i_,C'@-gS!k + %HRSHAAQT7Wb#BT^I^tH2d]G$e1l+reV_7B0Rg`sZU-c,^2_H\\0S1hcb1W,_;_q[2gr&_h + /kF[%ploFtTqFdVf7AK]DH.k?T=3I?7'r)ZrHho7e4EBq]D9%nP`dE923]G&VD8krm;qtTQ + 2j)'p8#3WOV&/*>H;uBfm=.7!.C]&p(]1)=r)?[;J6I)GRX"Rs`qT=^W!.g)\/U#:*kY[?/ + R"Ek[AV;Cj''Xkqc0tT[COU`^T/*c>["Z93k\ep^P)kPXE[gJ\6N[8e#aALL:Z,GqVbcuGr + (oRY*adCpkQ%Vo8+C]@8g50]gFZh"U&$7@Sf_%p+N>bBk7fGe_g"J[0O:AC,T^<`N + _@d:L[ef@-L,:n-*6UY(P8N`3Q&1W,7(I?-::K@A/Sq"na\VM0A4[^MSf[\($5H"qN'\+q1 + _*Q(-f/SgIP5oZ)*4-pQ8f+MBCg3]TZS`:AD:V+7*](A"p=]rAK\\%B3QBY1XQMo`Gcb0/( + 7MK8IWSbE-LuHW(^6]iX<-`LeQm+Wd)B8Qp49*)qO.,^+_"kCS1o!Zl8&S_,`'-u8ISJof; + <;+gjdoi@IUhKrA`b%Hk2'n:*bje);oH=BXD5r#Uuf^E7SQ"-U`3->4gH*1sjdh!U/3q8#nM6E+AYLo?<%#""E@Z + `XW6#RU!0&Pu>jE`m0N\-Snq';AL.$ZB\1<97B;_XQ@-?E1gdVf(g9db"]inefC=tQXO7@b + ?Ys.e#@b?@n+APf?jl8;1cd]$>Oq^YZU6ljatVSB=-AEN< + HC\Z_?*VuIp)?_lf5\:UD7?"*mdGS?i,JGg?5FQ;a5q>eS;qeHT?ZSAqH[Eg?`D]DG'fdop + M7V$nFuie?RMJdl1957*kup.iU$AerdQ#fhsL79rr4(de,',0n.Ya%5VO_o"$nV/(B_LX<# + l0/n<>.)64gLO$c9-d2[r-YbWcS*&G/=&6ub%P'KTe35^38@FC,kS0pH;h7H6_28cruF + UbUjX@+dJ;ZCaJ\rm,KS;$QR,2CJa.a,nr07k9!Y7r.EaKR[4!(Om(RtmdgWg79J\-X1X.^ + 4f/-3c<80B+Vj-:(+7l#i3mR?Ho0,q`o_1SrZip\#:p^ZB6;\FS'1Pi@1ugMm1kd;9bjttV + 9M6#S2bd5VjZb(`2&/JV8[T7d+NO.CHWEXZCjB)PB5M3`+8)1A>1VkNj]bfOg&?.rQ48CR= + LKN:@G*Nsep*h\AY!Gdp6DCf=pOlQGZsgcZ#*^RjhGZe-V_R#+6Q5QF'TSFdro#07F?=OpP + I7@?<=8nHXA_Jc[+K(rV_7R\/#$iSB#@DK4,T\'8Bq?EW1!LR"coCicqm^N+,:(23&dA+kM + cc)#cmc*+r.KP@YUq;jM!?EW0rgq2SmOB"=dHFQWo)_jonXhKj9VH3qK_kbn6mT]m@YPGW$ + V5+SVkqL55ilC,/YF)&;.^TnpInsaI=0q2Lh2$#EW0X!s,Z)SI\3m=qYpfrSnn@Ulhl7Z"op + mhS2H*""b?(\"@0I3"%W.2JC$UfNE@P1`hXB&"#:8#U&P??oG`Gcr+1jI70t+W-%uo%'9&W + \5N--*Kqg&0_WYV@TqjUb0[:Z!pid6Wd*RQ1!+\JGKJU[DaGA4f,K,t.1#d"F+6+>R72Vp: + U7hN'd7u73LpSsI`*O;.Jh[f+MDHJ&ArO!d2+Et]0a:b[NR>U%`u+%\j*8HZ6iK2AI5VXd-W25;N&TRIFXj]JPk#ccETLK&#V;V7HUW)VcKZ1^U^.?qkaCnd.l8lj0h[+_9SfR+iZl9T&d(7RkJPCF[+9rr9;e#gKRu3?2-ET&@&2K3BT3B[&%61u'A*`rf8X%9EtGG + .!*0(*@@[KsA(f8DA,7Df75`\mi`U46Q3*-[M*+h-Lc8EeU9R2ZggR<2dA^KKs7d + -C,7s*%_,s(3mmG4X8aj//fjYqGRo!?/Vmks1f\!`R(S'ZKWL$ZL.]:!!cilr!rn\3W*Rr` + *(IX/p.5Fgs9hlHp0q;q]jW#5YXp3hK'90ki\-o5rr(E'H3&&I':UL?hCdMb:`6N&>BFL3u + ]d/"eNXZ6r7nB$KK*[8P98J7as:5Uba.dFOI'r'QgMm7b77Ms_q27BpE&_8sX]lb<)hS5.6 + #!p[$I8j0($-!&W_]4+i79t;mboXW`j"*%!0M_Ol`jdCImB + $AAj]4R;[h@jX?c,"V;-SG.`S*"c'EXt3eB)a#1[t.A=u9'?-NTH$@inN + $qN8adHD>\'T!\g'Sb8@'c#Jr5gS01JRi*T[uSYYm768@`R`/l\:(o/]rG\ZpiCeB=B2$?0 + T+^HSQmO.au0Io#CN8$9NL]C_\_de\/[l5_g^o_t];HDaj0I:oU'Y-HRSpKd,+F0Tlea4P_ + dBC+HPZM)#[gH/JRW>4^5Gke$F7TS]5_=tc[Nk*[YO5Jq\N[uBh&DrnIA5'!I + cfqmX0_84`S=b.H$/3sm-1=Ikl1_R+k0s_JY1El0B4#Jf + e24o4S_3t&(P<2(HrA2>S_;YM=F$N%f#;d>+Znrs`6Urt<,bV^`1lNbBMl7GIMpE2i;F\RVoU`Hm + ]n2V[@LSl?0T-J@Hk)VfI(%EdTi=HDfMP\:]SDPZt(!K#Q2sM>Dn(A7)k=@(?t[\KkX_W3$ + EdO#^0N$T"*ZA![RmR)Y>XMqG_RXD=eNR]2dI`oE]2o:DrATgo`d\gt9PHuloAFN64\7mqL + ?FEY]XG"7d@a..CpUjL.hI)i=:a2EEse:,IAJ&i=R80i`,/:kkrg,kM087!bXiJkaWnes*0WrFPK?dga^gLGg=XL,ccM;#8Apt!e;G&Ge + &[YQ]i*iQ=^K5O?bLO64bDf31Y]UMUqNeo4liKCdd944W36$W(Rjn2`2g'tjtBE1(\?i_oB + 3iOlH9h:R&fR,_sL)amP#%;b?Fs2)ZqcM"W@6K^H&&CI(QVY]YALq9B5iPgC:p(%mR_CYoE + ]t4N"rJ(,2aOP$++a5^oGk3=*Ac1cc6<2("$aX0#G@%;_BVF@_aLkB + ke^<`6oCO*&TUl2c#Sm6_$-^Z + X:VN@Df]c2C_,4j(#N65u7n^Ute>a1MXAK"N45VV5d_;I0[5$8[g8XVE@OKZ=c+4:/KG9\# + `EQoMRWl;[>3Z;'L0P@tCP8H:W@q)W$G[Unq<;*_QpKA:,c\sd.UcDUHqp3JK + cdG,*l9nGHW6c5DUpF`2/HeHtBi&*;V0rQVm0G#f8N.V]gE]J2]aRk=OC[r-ejF2o8,oZMFH6OW)Xfsg4m-)`2Jl^3A&7L`?A,*aLPS%7XC4eS8 + ,#lbYil6\]$#GP+#:Nd*e_/h'8WAeN?d<4P``^h*ooN0a(h:: + BZKYAc=l7b64n3MaPMfHhn5]/_eeS>$Cp6qe + ";jjCi*uO`;s^^TB$41ei2(/=S@LjACJ2)FY\e8YgqMA4E'L/6RcF=t+g>$1_m)&:iFR/`F + r.qe)V:4q*.5r]X9dFkI))k59`3:>omkZteCYdo5nqr';<>VgL/"HZ=G&B]SD2]!.)E:Jf[ + Sh]r[YhWjC6X!_AjcZoom6^0[>ZD_L7bGe10.09[s + Z[g._@NeGNKeVD]\__tBt:7B+3?!a]153*!Z2SJYT0)-dq[ga$4]/mH=!`C:N&`\kds2'KtFa[U)[>c>/; + ^!,V-c:6&,>kKM(IF1OdSAuX$3Zum42;mW9iTA\LB'*plmVV+[E9@FFk5KDa(3=+8F6?SGk + @SgKG'2toXkkYi88[EYKi>,hk3(6a/4K2<429Z*JDR8$hSbao2M0G$m?G&3h[Y<2IXtiQJZ + OoqH:,UX-A56_Zb]2F1-pW + &?P$6i@#PFfT%ZfYg0HmAQl"ae\P+k>LCTJgdN8h:0pg&?H,HC>`N:U,F@OU-4@L7Dd^=brb#LXBT15Nn"s + j"%X;p-qh7:6LX8nP&:*a=!,ql;n0\8+2Z[kf!:SQg3p-\90*Lc/#On&^n6<%!+Amol$1N] + ZnCIYo:fN5DGjuRInK)lD-PQ#/'_*gknOF@7bh(Dd^qLk*-6H\$Zsjdo@O6Wc5.=7Q<8prhCR2P@I_0CA=P1 + @$ij@Z[r[ZY;_+n';iqt#Dc7rI&1A2D1=b\bV2T]d"/G<$q2'&7@h"5*Rc"uGtA'8%m%R"Y + UdHrPOD+1@5,l_C-6KCS8`#Yo4c:73(7mq3tA=X!1(>ee>ZgIFmAF0i\Cf"[aj1!n+;(P]. + 9bZ:$gZ@MAO>#E$#\CU(ZcL5"En7D:4miGm3[UJV[OA]#F>h!P$?=aN\pZ?Agk + (`]f0)F#IC[n#6NfYiEcpR??^B22$KH%(>GF3Z5^3Q?>]!)BhBF4LW^'*(_#=euZGt/St@iUi[>hbO'6?2A5sXVfZ98iE+TPIPA(t5]bkh%qeCnK3pC.++TR]8Z/;OuQ#6JDP+\ + W(rrYZ3#_r=N1CAj%iMaeo\R,i-;^1'=f&geT]9YQ\4+7%T2Z<&Vd9'.YWSCRVOR*I&MREG + eQ5$h$OC^9nKMaX=<=7f["lC1S!e#G84P3%)kX&^_9Q0i%m>d$BD$d09Ja(;V\m8cZhZIld + =XmQuUNR$Ij>sIcoV9ST/[_CZAlW`cj#.VS1T$8j. + g*N>IAZ!sAo_PD]ZWsb<^\/TuNmDRMfW5iarRURa^#3i'CA!MbM8nPUef8R7/"$?"-na'm78Wiq?--=SoTde+SXJIF>P.\1RbEBfW/^*_ + O_(j/4/Aj9fP=3YPc')Dg2,4g$j#>7^XQ_glF3O#7:D;O^50su)"[C@U%1,DKe4g<>d1g., + 7a`!!,tCjtN@RTKeBK]cPU-@h,1I5JqEPVT@H;pfa8+IgeTFkph0@eCSaC]0(Tku*Sd3Z7Bq'g5C0hN`YrFk,A,t7@%V$?u1lER)^*ed^W/M + u.@Rue4$3H/F<`YT)/CCS>Zf,O5A*Q+h$&&tj1+7TOY`$gHSi\Wt?Ypq(2 + nZ"NWFARha7Oej5\3Ju[=GlW\/k*:Q^^'dFN'hJ:`^-L?UHnht;3EfVTsYi7JC9GIaFB0]Z + Q3PgG)m7'4\+/m1aDpJ)[$"O83%&$,W(,kj'7)T\Jlb=FHM_!9Ri#[XX.MjQBMr!9hD%J-0 + 4k$PTr#"asfq=7\lcD18',Ua\s^dCp0.&```eUVEa1L\;CHVD9L2f,C!f"tpr42 + X`d,lZkrm$7bTHH=IF"ng5Nh\8(kXAhcoMP#-oD=)'G+]3U&RJnI1@*(-ATu@-Uh^a_P39. + IuGQX8#YesUdPu]&:.#9Le,?*,YX"lg0%&Sko-Eu`/Kj?CeAEX-RH.dR8_OJ:Pn$lW'opF' + FKdrl9Ruj"YH&T_P^>TPu0#b/?IB"^K&G`?nGrcZW5H#>>'fj0"$YL<`8?l9A+p!7L$:<1C + @PaAT9Fo6Fn%;Y8mN)OFM%N)t:^R6MF05P + e3sS[C*IEZg:A)`>#B]Y\Mk#+]H0mf9b3C'QW.*(HQb,k)mA[K5NE$GH7#8)a?,Xj00+stk + QZXi)'Sh!7!_5g@RdXTVfmU'CotOSbrSO+?J+*(3^UOdS?q^`TE(1)K=;qHPS/[(LFqMS.: + C#i`-&&S#(W0?f%Uk;ZL@G$>;lY(]OL\?2O57Y?WhEk,hmc(JQ9EjFBHgLdL!*b + 2j2Nj8`_a@kD6YFLH3nhs(\\Lhq6J_E8F(rNW<-mjW:U^'0K*WT>]''7W#"TY21#Jg6\#(Y + 8E"MgNA@oR:T9QJEuY&S*t#8jgQ@*l/o7qoO&'^+&Ce/I(JcoMW=kSDItZp'=`/WI7uf%Sq + :K6P`qn[W@(#iKg:K@:;j`TbD^Vd&bZF'=K"3L<;#SM$kk:7]qHQX?fLG-Q0bmG,bpf)ajCK^Z"N@M%LPUE'=L>icIeM>V + 7Kp34fs9LC`A6MD)u:cP^uGD<"L!3Utb^)rpRq]TToBBS<"ZR;VD\Wo!`G>XeB3bn=j\Me(*=e#A-Em?\ + u3r9'FSc+RncM?eZkIgM]'DJqY\bSRj5B6**)MiT@,t5Gb)5DWJOS@HphoIa-UqR2$&@*`X + ]KIqneBc+_)JhrY]fP^P]?1e%J*#r)7.V\rT@B$dkNYc8Wc^\VeKVEcb6P#I'l'pP^KjsB# + I,o3P-^LTd98&;CG3[HhH(p@n*?s8Y8,p,R@&EXkEt'` + t%Q!.YUaQbNS\!PgZ%K&BlFJnRjg!PCB!0fUON-,[75>32>?lZ>A1bro-4r`qUe]*pa=&-W + %ZI_?9/74ZsiKG=L6%S'17`$Bk(:`;bQjT'6C!miY&:br1B2$:'C"7S5P0KfP85m2.$"L(` + +OAuGQg#B8&l?q:En:(0D/%3\C[+_8^2o + *Hu1=0RSn0Sn%%P0_L'&+'5@N79 + 3LFr:UXodM.f&4W4i3D.t&`hnG)W?\rMpa^m\K+XVOEZQRguVK0 + "C4=_OSo_lqY;C$/m7Ld%8[5So3>V0&%dLhP"iK.*gc=atTR\R-uLb.I&geDZK9IW2.R7.d + ms".UF=&)_8*/,YoR70-hi1<:Yoko*U#GY;E1r18sK&o?Tb&cV6$17]N2k.rSKB._.3N?E> + p[XNtR]C=;hXrq@o!Xj[jV$Ka*B)#-HLpo=%kDj)qd82NUn'B-Z95o'V(;?n^#lp%r+S611A/.C2+!\)jdq$!V[f99B\g,H!X/c3;mQWB+#4mBM`@h7Rd/bq2308>D + co/Sd7K6LaaZOXh4B'WiZdV&.e53Al3 + M]EgnGrmaI0_Xr#a=bsnEiGVC<%"c`pfTc0Vq$2Va-IU5\6Pq0W`>p&6iaU*fB883m=\3dE>@+diB%R + ;!<CjAeJh:/L&8)W`^sjt\/*Y&@aX?N)m1P%"Fe + nGGO]nb0>41\E17IZ(4";l].Sg@U.SD9)[jt5f/!@WDNu%9V`htT8/E1(i.Xs$mkjsCXoutnQ?>?$-TM_c3D_kOcl6X0 + ;)sh13VI_][WcFdPA,32Bss^LQDJ/S@\8ECt>Sf9EHSPd8q=D&5-j9G`gaoYfFiaV_.X9bc1sEKs8'Rn`P:)H>lpfU^BmIW_i38c%rdX>r'*!P0%"PF" + Gl&o$96HMMEe06!l+-WQL\_O%G7Mm9hodoof+C[L2^D%knb3p@]@Xdh?0]'k0'Jpm*F>iVj + JK1`G;4#lr'U^^kEeq.(Tt!l.&/i4oqU6kj(drq9oc1NPL=GEK4]CY9jl(52b^M#u?Wr&H8 + .SPkdh73DGcA;3cE4Q+:=gR:VZZB#&T36OZ_)oNQAsGAV;Q + D:^1:e+jLIi`LBeR.fiaQM#1LQ'o1NRGiZ5JXX!^R/32)ZdrlG:a?9H + #jY^F0oML-GOXZCRq^NFHh'B2c15&JY&*aT(cM^2d^`*9:r\,F>n,0d8ciZ=a-3[esCr]-[ + A?$M0PAJ(N#.W/X1V9.87<(g,.-%1t1KU,ZB\X? + q1k>_>RpXe3sCFY$LkIHYk?D"plC8-+c27-/GPnj2=$-"mp>dk/de)+2>rG"X'cH?*BGHX[?D@T_R`F!5rK^.'kVCYOd + 9is)jQ`$R(uiWr#%9mqju!cU;Rp.%66M9S$ePD\%0,]]DK:S41i.P_7Z8V]XuS(qpi@6ftB + K%"hAPGBo^^pr + _g(%Q"*!+\l]Bu-$3LpsHSOTfRf0@2j)4gT\FOYJP1ja@*m1U:=`VK%rH1]V>rH<$RTJp^[ + (Njr5%EGr-9!*9goX/qN]6O(89KF[@2k&G:T;,Y^HG#phpTpYD%A\3W)Z!4bH;RJG!IS'TI + +i"Voa@Y.B]*'6IIZ?q'8kj3H=HV0ZQ*Ju--2hfR\=Y?TU8Y7WZIl5133ff4rJR\XZi\.P;YskQ&[]l!XZ4i + )M;=b2rCMr_F*DHm41*!M`jT>80EaBQHIG!'K2r,#$#PKA,(9\5ta>b6DOhkS][b'rm'\k2@4n]NcbHl?/2W(j,9aimpqVH;6j[1CM#EoB5 + 'IU(H]\*qSe+fJS$o(n?Eg69gi5P)$LWNmD@qPDNg;lZAjd!Z,]Qme5g\+REL,_'6(3lNAX + *NYNUXS%4J>q&mc;6BdS=cU\.I6/hoA^gEgY<)9YDXZ*?0@_qneU%DKNsT'^@BghcYuTC,? + #TU_iR`+tZ?>DhRk]f.pTmu2Z\k`gde0sFQ&Jp=0E%Fq2ANWC(YX,gg9V`Xj#/!ped[0a+9 + 9c%9E>/g;6!X]`Nmo5.hXBJcuj+=eRe3Le?,Zl.[0Imp3^C%Q_B2QK]Uf5#OJc'H+WZ.a'ggRYM9EP1+GGT6Vtqh`/KnG(e17 + KGiKG[4dV_tkD)nUL4XrGKYo[G?>E*aSdVK>lsCH2N,`2lk$enGAjX&!t&foD&9T^Q7"(q> + ($)DtEmN50t2I!l4:N'*CNs6l-(oY_3:.5gYqT$Gu;.1CHQH`&A2oYlm'!6h)-_DOa4)La* + 3YKNf6]nUtA_Kje@o>aNGtd.PFt,Im*\naqG%84WL*GY#,&N?h)9<1>/Tnmmc&b#4r4FIi! + 0Yq+!QjL6_3'#_S8bYo\@1!I[+d5=Vh@F$`(a8FmV:%P1@3h46)s#a(UbkF=tZip1j*2d6F + 6;Z/`%n0>PAIs?]PV"%KdO/uYea2-jU/e + lAFCE1I`OEEh$Pj`YTE(&@l;fQIFKF4aMR^LZ#1rKV!JLAB)+g/a(0Dd$2TX__3be[oXeV^ + _Xb>ZVBTF'Q1?rb`^BD.&_p$>c4[rYL + =JR!'^1@V>MAL18f7_2RDd`\idKk.W^7j;AQEOCZAF;3`6cZ+ZH7fkt/bAiZ_nC?F3S\pp0 + Zep3hL\cMeF)k%P!U$0'WO/&.i-CB;I`gO<6l<94GWu>0-Y^`*2f!hgJReFU3Rj\3UZL"&= + e$Nt)q4Y*2gMYkaD7c40]'ebWSnmdVhY"U$qtBr#)TVj__XO'S'?"m'q4#6@ghEh1nn)U8` + q%,^1!Lgrbrpd + b.ABDB2*NT>@s5EFs!rcsN:H6/]Tl?_SOVsL&<0q7TXhXA5L(oCaAnpK=Jch2]N.>VQ-s** + Grr]d=Dq]Frf/@)/-N5N%J6Jcd",BLL&f4 + 8n88`Pa?9f;"<86<'S!u%:C3^_.tpfe?OT7#$fR$u(W-_"_e + RSCJ)rllrN`"@g7fdNL,LHH+ipK!#kkcIBppLu:%RS6."tjF[M\a_j7Z[(T,,C0Z(D9V=$X + "u,#PbZhZ>rZtEW.E+;!\C:eJ9AY;(?-VX!3(A%t`aM+iZem,\49I96k>G8XV@pB-(D"WIH + QmiFnL1Z=+W]M(7XE_(`jR7@Ss.HH:FYfsB?^+B;tT.@e"8,r5^a1K;'F;.JO0j`3h_>7LEjq_NfTUu + 5*Ss'+;=iG:]26-'Q",KoGL26"XBf7QB<SPV_<,.l5f6(5&6C$GBXoMcnUgBIs + CeA6/9V>;fO)/LA]Q/Cg/p/'W=AlTo18c(eX3Oaf$="_q-I#doU/Jh\SoCi+m1@i!UQZ0QA + L)&9bop@X[)k#T`0RkV@2ad]WFE6mrN5.QX:V)2t\dVat9Brs7m?$<=">1MAC/Um.H(;SXn + 4la1LRCHRHjP;IfG.gWS^>ASbHlNfYED@SP3+jC`>^MVO\+.X%="`[?';0F7D:H.5&V9F5Y + JH7N#&:ZID$nH$1 + CM8=:*b6qrO6Q7Kpjd1p-ACQ*7k2b"Y.0=-[[NLA?TkBQ0]A%$]Dna\ia*<@9>'Satt,"Lj + B*@/!kOHNLS7nItfUdt3=&]5Rq!D7'$1tJ/FKLc)n&-Nfdpl(N7\m/uaD(b#Dqph.:_b?Iu + 1?[Ukee4l`C=8"rV-MC!)Y[PEO6rV=/q:l + H#p(P?GY&]/ci#mp](/#1gT\bM%` + g/R'f;qU\lVMl3`2X$,*P%A'r?QIE&'<57g+Y3hP"MK\;qD4%H7[p@OHpNB2K44V4:QPSOb + IfRi5D9BE`9>c!TTlk7tfP7m0XPMXIGOGoU]Y77O?AmCk05$blGImSFqr`CrVk^uXl,mAVr + IAi6"2MXW"M1dDlu6I:_72ke@5iDuE3m@SD\M#:a-H.#GJe_DK+[)o8G9;P.`FBLp1!K_Mr + /=E`h--&j);2`1"6Hl+]e*J)inV9;h(\m)R@DSb(GVUJ_F6X-tr+-Dd1[YnE; + ;dMBS,sgT<_<`L8Hm_7>-Gl9$6DQ%sX=Q]N/OHfs6Hh(!gb2#'0LHg5_^mMO;fJ_REN2*W[ + mW.f,B^J;35_-V2iDoJ\rJ_]HGdrj6ed,Mo74af5XZIoKg"9:[iJ\W9,i%>*#0n + [h:KG[C9i)1hV@0Q^@L3R86G\i,%C^1eoLBr&-TPXoVA-R!h+fJuVOB[G>98(qpK;^m7n1" + LD/HLqNJZ&YN0E]6aL:CnI_e`gBUPq1=N5e^@39$fuPE2_bO?"8A`i?.$1i&:jPfDIC\Png + 6qj8&ORE$87'T@)n]!djPSOZOL\bpY3o"DURTXlRSX8I;q$`,C*I(F(2\oXf?ql7^E`k&b) + ]%hn'ql`O_c'M)./JNr+Ha>:\M(M#D]30g.]%36A\4G;14->TFqV7J8]GVs-/c:eM1VU"3P + o5Lq/ka%&HKQoNR63:DRX49^>b!G*Xh94h8a>cWWq&9W\B5`]][W]>!r5G[^TX`94X8E/bL + A3R!&+N*b,a*t;qO^["?i7/*ON"(N3GIo$Hj@sb:DO*lOGWGj3(ZfT&\orA@OgEkY%B\5-# + R8ZPbsCr]\iG54Se?$h,nZnin$"Z9eu79Z/]$&jLKX5AD([HRc>=q7jNn5Kq;8oDUNS!4+K + `9^Dj;Hj.b%00jUbZc!/8XOK&e$p_JV1`QrM$kC)Vfd(qW_(&/R]1E(F/]:%c#bV9M3!=e/ + '`m5:JhSp2fId:D3.m;j"=QWl&4_D0B8!3VL<*,Cn8X.!;MSXTK_TQjW(R?48d8Zi"=RX[J + 2i6".g!4F!Uq04ck6@-)Sorlc00jIS/T5^)aVQ4_:15-4TGln+d%^-[LE8"K`\1E,Ge'Zcj + V^+rM!IF(3B( + dm%r=?qZZj=R4]1#U;=$H-'L>+a5D*?&".]5o;Ome)*eF)0">E5sKEHs#1l'U'dGY)Q]dtS + 9EedbDKofLC!'CA95lpHsJfGO+,T&us(H4Hg;.O7J`+M'bBK6307[.Xd]PLNjDT:_%7eCZK + qj/!:Ek.ud7mqF=>F_aaGUpFNa-32rI!R=8HoaB<3^]fE]RHqdW4Ine<9pS#SPVAl;+Nal8 + 5l_'r.hm)`O/mH.1$c:IgRB + E?T8]/Q\A&l^GaOZj&api,Y"3`mfbcbiZ4SUZfF^]@^=)bL7G(lg;mm+27]29H,2 + .]YhrU`Crqt9O,V&HRLYEq+R':1>3hUj8<%RbtQ=^9]Iuq.P*Adcrli!5^Wg]24RB?$;FLN + ,K/2:qa.)@%o&Q*Zs;B\Z:]_).FL'uLd`S?E#4*;1'8pq1n\B5E**@l=G<,lg>/pci)g/EJ + (kF;gD."u4gS?(=+uH[gKh7AKt#fm=5/G>L\Oi&:j[j463[E@K$ZdaE#/Qm!Iu1I6%&T+h8 + BVIrm<;0cijN6I0?uIa,tMc[D`bneQko#>t* + @N?.YtX'WJJTds@b;>o6Fe);X*ed@uED`SaV,.pa5,MS.GsUIW!j<38j7iF0e2:+UdD80igqr + DCXB&QLi1eP(`M%#Ek8Zq.<$GGS>-&CrH6^b"'9TH0O8qGrHUQUYcs + @t4mdY]"une!3EPj]0bY3nNIoeP4Y_W&9=9MHjLq3"m@XoF9D$$PXMS)Wo.j%#f]+:2#"Us + iO]`q*U"mda]DYFK9gA<:bh\XO,QqZe#%\e%=fXSdau3DZ@lK&lm^me2H + 1OQ!O(=hVOT*($&]?j(Xn["1UJs"XDGt+U)O^"kDtecBmf#Q>'X]%o?mCQu<'D1BUlO!(p: + !>W1gmph;>Udu?EG[6$A^O]0g0?i<1GnMjndYnRZLe0RDkZujejS7$4=/%,]4F.u1,8RS'9 + l`6l3`XH8+%TN*t(9=N:-4gsT&n)c70Ki[/1dCW+T]4MlpJ@q"j\&J:DJao6]+X/KJn*ViZ#2d9lg*U8LZB/`j=J)KU[_I+7+f]SjE6 + HTp.3j5pe6C(jK;,.2GDdsrQN:X9\rQ=opB;+;X`DgfsK2rr`%-;%P$ + 1c:4="[AX-<5m/TA"cummrsNF6A8pC8gBM;&B+S9I[Ut:,k+0*R5&06;'kZ&5B$P4G\4d+r + ,Q^Lo`Vub]=<.Flr9%+&cht?TallO(20m[Y3@7Z^(@Kj,C + 9O)1]8aX/!-qn;@JBiZi.eik5D#Ac#Q,>#Toi]\Z*7OD0MeNCPoat7JiV/H$&g36`sb'e>J + `\:#^h,dY6]oFlhG2VCTH=L.d4m(gC;GZS!SMr"Dq1:f/CGb,<&;Pqg"ocSI@tOJU$ + th\n6?TY\30AsF$Y$[JD_)o+@?=r=Ttu;E5)hNcOOpG%d$Ouk&7;?-+<=rC0Q`8EF5m=D\[ + t>r5c`I'nhr(k=[J&cqt`r)A@EroB$)Q!)(1$Y'gr9C"u0/iGEd1P8$I4ah7XXMHh"?9>4 + !&'uJ!-=G.6A7mclp=k#,iH)P8?gQNiipP@1hc['S^&aXR_E[O7Cb?e>bA"`] + R-\]T-'?3QRSWji@kPo;jtjQlp4\^$dD,=bI3V*RWaom&ch9^j@C"PPp<7rNiVpl1DYLcfp + B?PiiP5/hAoAWPaQ7JFXN1?*UX87JHCY6b/cXMkp:HmppP&UaAU+-S2G]C_kk0:)?CuTCKZ + Nt;5(?qW#O2)7s$+i_NrHg@$j'fRa8?AL5k%Gb"i06qMLWJSPTpLOd&\Eu(l/#H%RXgtLCQ + ^,"@WriYmaBWQ_kHs(.E4Z?sZ9aOoH=hd@#V*a!KDhJ&d]DHQUSiRi^Fr7o*5s\Vjh/nqQY6,429iIIWA_CX/>^tHCQ(r'IAH + >,@#f8:@!.Z1,.<*\.cu^e'AQf%Sh)@dQG23=!<.Ea@:#]10\%V$RhUgH[M.mp!6r]^NJhrP7nB*rj+H=ueoG*Z:Rf8 + WRBO%Wn9Is!>g.I&YZB8QZ;ofYfFf(GS`=4m3%QXSS@B49kNpD + qk,h=oaoJ_kWWs+fP-Sj/4qpqC*tk':9OLi&Xe?n_uk4E%KDJtT>#>*h]+U$)>[GKK@P^E7 + [SG/%BupAIi#S/V\6hlnL96GH%goO$KKbQ#jk!'*p)45?V7J8c8,5^4"ETHNHLYWRENkTW* + ]S/!%U;@CD7)T2[V&>C<5LiF8P7#7*?a8"[8;)Tf?kb/^MPZ^qb^T,+0F>DpmP/<6+8(rm%oN + 2-A.NY0YW,@"uCOhNaa#hL&Or,dtHc8go0lQ1faH5lDN5S*Yi+J\GgOAZp6aC2b+UgX4bE] + Q\2q'@2=kiEoW<_LPK0dbu2SKHm2E_6'o-m]S(P`hV8aq;'Aa8E!'ZEdP)).>):ptD!rg02 + [>`@YZRg6],CIuTT.0P2`'+K3_$"-gRD`6E"X? + n36=0,-=ju8:3k,ZM9.V&Zb2ooid=Tq&P8S5/>X(JUH[kjqC>MBXHj1r=,PsP/L?j=(4taT + BOqRFAa5'fP5$hHR=HsQ"N40$'Oj`bP;3i$O&=pn8Bh#Y1a[b&)Il?2c!;n=-G7p!@r"c_T + t.&?m+e*,FDstUa#Ji,TH)FJFcN"]W0Iq`*SQcV:>t7reNEP*'A$40OmkO>g8lBKmXNRIql + @dFYTVF1=R-]26Kh=S + A'd*VZJ/8eA/7L(ZFJpe>U_ZRb=/seNE7JF-\/>(eg8RC9*&ZVX/*(>RatZO?#!Z1 + VBWr\c343AqDd$H\X-mh?$Di^0/_nN*'N]&QbC9jehkE!P2^!\MYQ(ugu4ZJ0ts]BR^7A6H + nX=[^mH2Ei1(7.YXT#:fJa(DbU%o2Se8i,?OL;4.u*Q5*5bm&&7PK*LTr0L`41jA@[p;QnN + JPD9UWi/]Dg%0H]`l@"ol@cq#6oR4i`rgahI\+q_B,YaH"+.A=(],h`acV=ZPDo&>g34S[& + GBa;U$mYn/[&:WqF6n`3roOn(DAG&Ci?#Or!Be5-?ZYM&h`]]G2oei81>h?C]bnH9u#IF"! + m%;6S7#%9&,d+T:(6>0pSC>k(.n*"6W-&MReBrbE + `W=$q[9R7$g:fO9h.6HFS/,ij^!fUEpS8a/5^G%Sp_&t2ee% + AbVPIb!/A;I"(,FY>Z6^>"i$A5$:J\4@=ZR?KgKDMbF*aE6&YHb>//i]k)QjP_?tMK80Re4 + h;cr$Cs@J55@h4=?O`i7$\4Pc=4RVsb$E4?T[k)@=@G2\i#C)M],H10r)L*A>o;MU@;?7]> + JqU]sb]nsW$[bhXU&*57_:Sn)T8H*F.MZC95pEc/GL4#e5XicGin:[/Z\W+TG3FGc0)Wb5! + fSUKqW + %`"kcJs!-7L[V3..sN!VA('1qZF3EE=dq'WCt`l([XZ@GX%@!,14rDur#Z%=WKgf2,m9mj: + Pob+DUf3/\!Hp@>VB)r-F(f4KurmJThUq#;/lr'-hEIZMbMIqS*GhlHO$hrF + V6QhC?Mhtd;EmJal@qu>">!"\l*7hBZ9]Nd_s&+",Y:_q?\)Ucu>)o9bPhBQ@nQIdBKAQ#ZfBU-IU8DL[?jQE4"*]3n%q2e8W:S:r\Ae; + [goIeWA4=%:G/IArY0p%F&_@0]nEJFP%%MO=bpD&GFbu,7i/tfWCJO9pCjdT+K8B&FHQbNN + Wq.D\:*3gSo_Xc+MgD.bo"pgp&W'/_(MD*1G!Th8unXO[^S_lm5M(S<6Y*Yu]argD/!6i/q + IsNXdJ?',J^biR)W]8c[j[_\^H"ilVcK:,a#Am%Q*r0?Pja:/'_gRP5(8jUn/\mT-.O>boY + Ujs*d#c>MfB\i-^9VXAu4MKYoCM]C$&kqUVbj07djQQ<6%4QF`P;Zd0]`;H?Si"!u8E%j:TL:e#%^;?\1b'k(8eDb2=;:/nE!\<;BPY?pnJ?rsODp1 + p1#ZBi'0VY9SP=V#d-:NlKYJG8%&.dOS^^bc-iE`-h?][)nW0:seW!rq`o_9]51J,oI%cRm + /Jqm+eg1=7_;h_)4Z0,!_,;XH-#o.RED0@L"W'*g2^$SYKg0U!#%;\LmL&M_`N0[+#YAL.? + g'aQK1cO=kN:a7cXGlgZd##Hm.:h)=pD[!@H2$\.@+hSg\-3A]J11_j<5\LA<^)Wj,2)&/i + PBSi>Gg)@71T)Q>!R)U8W!^>M;D<9DOF7/6Kei7kc]b+0o;;mC.g_EC$-`ndD&k^"Us^s%$ + F@U)XYC3&]faZQdu?p-;q2:qcIGbDgh_A(V;-Q + JKpu!b07LA/lMlF?7e(_ql3A-b],3ARTeVFuZ&I_c+QNk`U?XBLd?s""V+JQSjb#E + /[&0Z3ZDCO'k,rHr1q)QZ]]X:OkL@B4FN-<.ND.aLku?+PeCNlaPo^%]l>EL@cDr#sY!4Z7 + lYaBY&cV,,`uqbeXfg1t.cB^>gEG)WmV;I!DXQsk/FR?^,?5rVjG:'oXRMK.,]=2l;G?UV* + @2=tZk"N`:Fbqo9GJ`#!T5%X'4Oj)9Ea9i'gYkA+_LAm930JcoeM5&7:@U0HRQjHp.oHQKn + CicEk8QTpU-27op7/YRNLK\'VX^q^jS3H+Y4.l>7[8['t5opF"L:(/[`,Djpnt + =a;9\!-N:11r + SHr&KB*]F_;lJ%VYmIjgB@F=OeqVS9`u?"889"Zl7!SbP2s + !:8T^Qc.Rcr^@7lcd)\KNT[(/iW],V/t*-ZG$Y(Sg%HVEt]9bGiK[tZtDhWcrV*gGh_eEZ? + Sq!2Gk+/B8e2l>q>#uAjm+R[]%;AE$2iHrur;,n8RMZ"`a)gp0X,;ghnPpl,p)^Ge5,a[N\ + 7+Cb9lEh'GnsAoD(\[FXN!T,9@tbs++>a4P:)+r0dD="-/U8^",525>OY-f3+"bL\Q4P(l*oLMhj?RLmX<\fTV/ZQ\j@Z@b(coQ4+>uEsT3iM\(FqD!PY7]4)=!@^]-EfY` + Z,ar'*))ZU"=fE)$Q&-3md(]fL4(r_N&b@J,rAXL4gLFNqQ1_bJA$NRk#Q>MO2Di2d9B61, + 9ZJ+Nequ-c$1-;*L,4tWfq\TktY4AWqVg[2kPBITc_eYUD3TOg,F@6%o@9_Ha4 + _j&g_p,aaBH$,0p!^(Y)D]4akWa;]"Oo.kO]/lLC50-'^tHlUhg63K6#KNJ/&I+X@\OTn,0 + >i]YLS@] + %>K2[s7gF2fPuceiN6;PY.F/+g@MM:pTNZP`=Vf^6#4a0Wfu$,E2@\CaZp-5,9%Nk7N^XGaDEo#OGQuP!%Zor?W6^_)B7!(p5fqFi0WGtZ4Rb&WN)7:7 + U\!u'a7nt@#[!Ql'4.S;rSQ(MSl)6D)F!?=tU*4cNs + 6^+2[JiZtiTh;""2j)+4fm-Ye+$(pm::Y[-q8^DeHMTXK&eRrFMki3AmJ9Bu0(5V(o?#E8V + 4=sHhe6e%Z&0j>MqN,+Pm"gTO"[bYDCfXJ0`2:gMhO'>Q79ZTU&S6Nc2NIZr + o!1DGc<fHeR5*BORbW+k.;OIMoss'nLB'`i-S_4hjA\/i+1\M1RX)jumm41Y'[PP_,dNEqd9dP+S$&:-4Snq&E)a_+W:lb";:eG&V3Id_+5CDE#7KK/7J@j)8gNC)4]J + #/Y"!R=%,Zjftrl<8Qk"p4>h82)4ljmT%'f6(L`q'U)BZu'qo:[JL2$RFqA'n']AToiA_<4 + lk1Gj%A=7@^uQ)XljlB7&)dd.5nXdG?5*E6)8UJ56/fd$#9PRC9!eBU-*Ejr + -%C*om3&^XF,Xrj>-*@4ik+C:tNEMJSZ-2l4Uok)1BpJi9h+A64JJ'&n&chi=6:se.JA3%U + h%%!;7+X)A3($'VSDE!H8jKq'+eu]3i>2:\E3I%gI\RiDA(Iq>Ut]`"srVN*7_JN@l$.puH + `_+-B*(X*H7]G5kdhATFb\V/3_mb[toBZK>dZS'GZ)=iC.8P=i031f+>sO\)BIl'0mee8.f + ]Xe4o:X]DW+J#o[Cb2%6P[&quH<[EXJs(A^m/QUV;TjY0Ughq2\N5o5OqrNSl[Y5af?/;r8r>fT@%=nWjQ(29rQP+Fdg!brD]fLCmB?A[ + \IcKghujjh9Q%G^u'*US3Ak43h1=:OY#POm;d,(.86U,Z%0QL/XFd)ZpBA\O>E;g-IZF[oQ + aXc&T&6YH/$C!n@kMZ\'+=7r;HY&hrAl3MaTUs!^NbNYUbfRg_ToUO8L"(6*QRq$,S;;)?t + e41cD4pAC8^26nbN=<1h+6.W>oniH.'i,uE#H'99u#c#!8N\\c,`DdlMo+]q1lWdEp,s2PJKAS@`@:=):,6YJ + q\:DCq%K8#q#4V1,Cr]D'@F'3/dl2a$:Kh_)(7A%7/5n)'kB<5A#lBGB3Wrp`HPTh[EX2-=;CL]@T[e2Kk+;o9oijg[U + l(Bg,=)MBJr9c5^5Y^e-J3u`&Z1g+85rXE85XpbB-uZIFWK](L>gGSMsj9Gi!YPl[3$*ea@ + ON(QJBBhtRrhJ=Td8%"I4QrQFR,0odA,-m'P%6R*7uiF + 8BVTG+W`Tf8JL5*,De.P0;4M1mekM$2q=]0.@i?,4U$&p4!Mj+J"o5$&3c4V=?5f*dWa)"# + c`>k_`PuuS3pH[JloOj\Y\]dEbHr/l\^"$9`L--XOJ6uo.to-C=p4q7AmN,">K@X"L>ge%1 + (AnV,Yo:)J\lFJL]8=6SU?JFEcPEg.ENQB;?+m^SDQETF8Zn\@OS6 + _1^E;#j-rc"DKa*$23IJ8&,G?tUUiV:,'KQHp[8\sNa-NBY#+9$bdkaU`oVadH'mJ'B-s1WS^S'dGh + %#D"36!]X6`?u-E>J-R(@i!oe'8iq[p^_m74NtR+b@?2Rj1IqB9T(CZ`$rNBVkN)W7Zi5MP + )@/RI+NpG-,(n72)+*Oh_\ki@Lf#WG0ZHg"U@_WJ;6bgDkii$`6R%Ui.RQbk0YsPaTRYCi= + \;.H/#D81Q](s(5O#"&VR53N;jqGS`[LkbeH2Am)Mq;U0`Ms,&u8L#HnahS:8tWbW=.FN<< + rJ'(#mS`#B9;`!VeqfTPP)>;MkIP0h_S_(^'oU=q:\==:W1n7LQLq56su-+dLu@&OIm8/+F + N&,`Ige3s8'q0e+I0]#><:(eb7@2CDNnSeJD\AP'Zt#>DG0Tutdb7,X[V`D!@:1;.Bp!FIC + $Uph,O=_d>,GV6GEfG?mQD+l7b0ZFgIR*0bmmFdm_f*:.N;atFQ28qB6[+/EnP\NpLZapNA + :Ee]B*WrpY$R'%IK:m<$_!iBKTHF@r]/I+d&.A^BS/m95O;el>QP.2jkq+c_QnKm^5mSt:/ + R/$uca"BT=59WX7:;44aF+Snlm2drg^*uu28&\iUOY$&!@J.$#f.?7LN)T272Va?GP_tZ[A + YGUf]bmMKH>cm[s,Z%"TY2?#lk:Q'7sn>`+npD$?>b`6pSshU-p"(OK8W$\0Jk@(E&IG@KS + ne.0lP3-jP[3/B&0o*]W=7@SF=^Epd/TfnV7n4MR;*(1tdYT/C5NfpX^?CX)[a3+kA8STBg + 8cC*]SB:SdhdW3rD3@q$4V7bF6gKMu6AZN4>VRmb$O(nGZI'f3"d^6>HBUWR-BsrqtFOm$n + /8T$[J]\!N2M*BCE'UaX5HAngY1+B1=DHYBX`UG)dTSr7CUNE%Fn5"K3ZgdJLjN`6Q-&/G%.cG5c!`FRk4"_-624I+@:V%T*<2^"=D82 + ;gcF[2jr;60K@=H%;?n?2+n?,?%_AiFlCDRgFfql%L2cR=O_QE?33sEr$t'tuA:B?n-n?8C + 6(FI1QN83U"bP*-%DpV$%I"hhA7ULZnlA>,56WE^1na@/LVl_E9dli>O-S.r=a<#CjX%RAQ + kK/YRg9'K.'VB9=j"9>5+_4X0m_$h30s2XoL-g1EbVss + ZtA1e+07B(dVUt$rirG%$](Pd=S;[2g+/'s,BN0/>=u5T=J=n.("W-j"WTKQ'D8UH^QOq_Sr`_iW],J2`0A(4g-B_rO$Llp&E&5f + =p56o7,\E&NCq4,q\h2+%E[;!%t$*s(RX12JbJhu`sM+9DNs"?8c'<(7XB)hGLJ6l\S4/5s + Me6A5D8LN-)n+Amoe1'8@cJJ\uMTV?rF9F=<5Jd='L&D`PSIM#C^!Y?LW(^5Fl#fYd$0n9B + -YT!Y"3.m#dPAklg$uE9"D6rSH7au=r"E2tH2d[J>!QA:HKOQ*s)/a`4Tk0Qr6@=bXY#K1Im&TG%>c'h3#2MJsL8^` + J059nbmX#O?G'6ZU=JF15t_[pdt!K6aqqH_'N'b+jDEe?#6,J&nAbZ<>-O4,Z`FN#tq&LZh + /0GY`kr&V3':.m+PuJ16*@1C#.n+On"`gRV!dS=qtXbOQKD!Z/F)_4cd`3]t;@AAk22#QTg + e0pl>Q3-2\?ah&`J4"t?&d!gWj7"k=H!>MucE]LA&!Wm$`!q9J"#]_2+^s-B+4:B)%K>fr? + `_E5K,tl=UCWS]?h7U='4Q9b]gu(If:<.QG2!IW8&WP&:N0*;LQ"lOO:o + ^&M,iBAg\^,h-I!f]thjh>Jj5(i+6PNk?LlD\6[cZ"AaE!OU.#mG!F#a'(lHNU;03aC_UUs6)8Y)MdBk$Sr>_rFgqkTf6<#5I>URr6-Gl;tY!a^*`?^F + */On\3#Ok%_(h0bS3WS(J8`^#sF!lP'56X>$a/NC_UF!gbGR_>?Z(3ViR,CAR5t$9$":.8I + $pW1W3RZDQ`=4R@#X]OaQD3Jopo!sC^)BQl,N#Cq%,e0>9!?jdVM/$dD^; + Z08<[7o%D:"7-W<7Oao"]*Y`:K8];eLaYK2,r*pjh,a6g0>=i + 0[!'g9P$[)pqD9AHY7fKjYDl"Thim<%edd!miQPX8IaCm8!95>B5HHS[cul\Z#`'>HII$`: + Z=%^!C6`WP&#ZHMc$iql0MkWa_i$g8K\o;22"a!F;:$)?!$$#6Sp)KWp5eoE`g&$4+$Hd%h + %7g;H8Dd>;]Ld-];u/J&6(=p;]jZ8H2VGUrpC5=(+*6(AlkHlLIu)Qe-#;8qG0'cg0=*`'^90U4I^^%3om + oM`^l0qCK=oN$`p@tPfi+4OQsn/h_qjG-b+%NY9tQ1tmg-P:l(<#+gg&2,i)7+.P1hm4VZ: + ^$iE)9*]s?ic0cTUkDdO*EDeR@Ei'.jt)t/E+, + =j"Fs*;:_<^\#NHp,!^J%5LhR(/G#t/S=2T2%FJuZqH=NZ&9"f_`]n+>EXk4saSkQ<>5\Ne + !Z8f(+#9\LCfL%/["(NhOJk.3iY\=bM/WA';;uE(>1O\bVbYgmD>9J + p(52(^g>R%a.+?^%C`)`OFT[j?@.d:G'2mFS+`2^/&#,g[^fg"&rMDT\MC'._&mln1k)"6T + $bkAqE%CpaZN0>DKQk>ic5rp41Ms%T(CYh)#\ip!_:o6%gj84aI#,:f@N_G#;_rMUWR.^p) + YNhVI,"63`Lu^#7;n,Mja,;>_b-L6U+,j\dtu/ktP9I[JPmkc`Z/K_Pt+c8"LqT#QR7Pd#= + >:u,JVqqks_C)Z[..m;em!FQK7'A-Y_TFC)@V[L&O#@KmW4sI:Jgrj/+oin_dp5JSndY==NL+Sj5+IK]FIJM*bNVf7Ek]\a50^jgQ??!3G$I)-"i+H"^``b+rG]n>c=e+"BNPLYN:J1-kS;FF* + RO"%$nQK3BM9#o="&q+,Q^`f4a8.P0JQk9Md=T_d>5*lmkBZ"i+WW@665'",2Hrriorpto5 + u;BDJUeV_Q\hUo(4g]M"t4)=Sm1\2>pV-+jm#iX"f_!&Wc30Vg:C^)4QD4d0nV/D$9VViPIU + ]Z'`k78"gE#e"R]mWnE0?(^4($6)Nrk,(#.klWPnROMa:ShG/'DGKp["`B:/OP^72>H"b9b + ]T-B*H%"%(BnlC?_AIVK!."!F/&h:q/c&YtLWj2U3J>gl24-rWo;Ud\HTRirB5ZuT("#F + EdA[hkA[^E5\6NoKO9c*pLINUq + :8e'Yqg?.TC)RUP=MJ!5JeQTS + ^-$4BK=hmBE$0TdVQRM@aULRID=]Z5QgQ`.Bq`[2I%p7=#6/pkO`-ZI[btjfBRJsrGLBgY2 + A\m48pEL<=1?lOJ$j\*%;-#1^gjq/NQ;(dracuqOAZh&YI"`@_#`JA,oPjUr+? + qgr#lA50Us^%_[$",#fCW/Wt.T&BrMP0E=r1^6ubUj'ZH2>>\0@:r*^I40q<)!`t/!B*Cki + =RXL!$0WP=IOt;QPJmb6,;AD[+Lh34LI%r)^7N%G?b0n+i/PCg<[kAic'Xoej1DucSnS)I-!8Ro;(_\(ruRDIt>C^>9alZOj7;NL]%L.QA*HA&qnU_O%7V(k\M2p7DX4u( + FA>G92fE`4&==j%6[H%o^th+QK?q"[&"7rF&i0-*_Mo6C0P8+Aq/Re"i#$3QQNF^6q3h0.W[q-I + 1[=.rr&?<>,7I,BWt?@Ukb"oj,2QlZ0,Q,ik9KjcrI*JWPYq,g[,=JdI'iu#K_,RH#,/'G@V;+pJ,laI36Y(Y\Xm`6ko)aNUM6/l + S#,>CfX+#eikV]_l.XFVXm,]MSZ-@e2Qa&rRb#0Xm2;!(#1)ih2GG1f.+U6hq=a2#L]RRl< + >?nTc+iX.#1(-J4'pYcD>T>u6BJ'($3Gbt]T+"f@`JYqm3;nP81q\"--u#m?*@bmp796@h"RAsIGK,gmt9Nm?8%/qpjQ"uj`qnSS`]biB;OefIZL + JoqVKQ!mJ@sf[I3r6s2FGST+B-8r81;tp&+Z+6;S^@kjgp!lRL$RXFJC(XT+1@qYoCE!@]] + 2VEdJ=!FS2g-q+%=5dH&;TRf'Ln4j8*0_QTX$H*@HTJs\h4TqGU'SI$VR)%.$$/m]LJM7uX + 6F^8t?j&36nBM$L#OMFu]K%MHSXm=tI1O*oFT=aU;@>8^@:DNU0l\b`)&"G4U4JMb`aUD8\ + >j19$s>FWCsti%;JOuJ#W;dTDN7Z45!d7O#Z1X])W9s'KNZP[Va0L;,I2He\_'!+m.-_rr? + "g?JN_Eep`?3kHr$1'8B9Ze+2f']6+Aq7EOJITnDG;i=r0MgDG;924"&D7*uVA@'$=0YMPW + WkPpjD;Ra##pCR$/rR0/EN>-esb8qe4ja2Z"6@ilK2>ND0"%3hRb3?+'i[,H + I>gf8_;KA?rX7[d`)^5.>&/./4GOQC2Cn(],P:>Jt=_YGfj/^m:gD!*mV?UE1-b.%ftHge0 + io#)Zs)Tof;/7(<'81;p94&*Q58qrZ?jX!ru^EUXi85'>q4WI8t,/0^9?4(dj + PTsPSqce;SlBrr\Z[8s0U1frQ3=l]+&YMpQiMeN)5PVn2]k1mi+%r^k._18aN'O41-"5tLK + KHTJ""9s0$77ahs=!m3!-ms$&/_R`P$K+8J\H/ho]=_20nlo>CD=+S-#iR,)_tMX`JIj%/d + R5.0&;Cfa5Y;@a4=Go6QkP(M70qj1joc5d#sgfc:USgYJ?TkC`W4F@`Yp1kaIa0N_3BiPDC + ZTLZtPF5?AjQ8/t5DIOlIEm+5nn$bIE!%^c2T`J4p2un06o.20Nut'Nkg+V&_;lc]8qR4WS + UUOlQ8ta\i%'jOjp)j@3"dBc]_ci0"a.F?[\]a9Vf'qi/BD3NSkb!WZ(s/-_?06S`osB%00 + rV'71LnuFfa3FOFZ`cjq9dTQdHRMW$/'EXq<(]t=q%`.@%#_T^WRGD)ePfOM-PYr+'=rIYC%4D5H,2Iqpd,7SA^CEb$j%rH' + 5_,I11i"sV8Tm4)G5m5CrJrEVo8J);t4&Y=p%#)eH/gppfn@!=pqKS4:gES>0ttG[$JGjU7 + 80@Xi`XnW:'1\lc,'*7FOge`@@\lp6X4`rCcCA!FdT4Ra[9+:9!VBVptR9=t5@T!([Cs^gm + k*?r@=tkV38b,T?h^T]H6j3J3XA@Ji#q+.o>nRZ:/SWhP`: + mUtI>8Bs!B*\-[^=3:LSmB;Va>!oR5o!Y@X+5q"#`n=UHt^e"hUZlfJ?^W"%TCBBb<158(H + cQ2aIE=K]]$8LkhJOP&@@,N".8t6UtTRO&u'FOqL64/974pF9n-1W1Y%Y;u.YTY\7!cW=ot?GG1j(KkP,k^ + %jtT(Mp'CNsbF_"^?)n(mGlG@4G)\DJt$I+!+(ME(S9NNGb65R9;=&G!DV>NgIX\+dW2a8! + ConbbV(GlK_:]cX<3_n4Q`^h;+^5mTB(&G.6d^n_a_oO\Q+_B*.,*J+4j0ErP:$NT/-!72A + [&/>2]`\2n/)F;L[Ti;gNg^YJ##%2*L0-l>R:'CW")hH!N/1QL815[V81TWYA8?ao^>f9HH + `.LZ?J\K(E'M,1B&7H%]jGqj-$jV<3#g"\15s?!Z//Vg&4Q5iM@E'DYR0h60$/q6i@G2I4B + Gk4Y&:l;;!1!tP@K@m-#9\6sCkBa,nrG>^O,TBd"3emrSgF[-c\Z7(Ds@p+-s489$D=U4JJ]"CAf"m1*W_9=iVj\QSJ\V + m(t"H`@>u,_L)FFo#bd[qi78I4:^'*tP(6(+^gIt0L*"Hse4GuHD,*dHUE&Nse^;g6c!jIC + AKC/OS&>`dkqr"eXE?LPR@,Op1*WYWD$)!^sDTWU-p('H,&>9@H_&*bmUH"sA&7Gb[PC=;*a:)K,# + f)B.+@lWcD$-Lj#68WKB?go._@e:GrZEHkJ9VGkjqH\l&YZ':+]'89FLC&t%j>ZoJ=$g8N! + !aD$fJ`2dp4^Bq`jZg5L]d#'Epb/#se+f5r9$:<"VE<*C7%U6'NZ]oHusQf(TP]IZN/LDq@ + @[cU70G6ncMIDokD*^AJM3+CU-F@BsOFc3EaP75SRF-ri[BD#ABo.?m7h&(*nNgILlr8-eW + (YQVXTg+E`N!,Wnb@.4\8$5Xk5):0!*Z5a(()M6KcNE*1#BlGMhn"tW6%OD.Lc7g@B%5/B]'+aF`s$j?W/"i1`^Tfa4OX + <%DP*.e,f0VMCKT2P0R;`--5Ts[g\RMk(g*_5?)WI=p35SE$:(/W;tOO4M)\B-U:%M7MuA' + kcdCK@\(%`[9<<=:$^$j=@B"i1Mb_-WJ,M&`G2T;+?cQ1mB^kc;@QdYuoSaHQ'1"WIVK`7%:'U+0U%3N(E/Ae*aBr@0S(i:=&02[Lui]!ZRo/n[68TQU\cZ4N]D&L!Z_JbnaDN#Y + n=&?d3u!3-C!s$F_80r,=7mosm%%mde?514J*s]Gu1OA&28d<(I$7EIsT_K='mW7;o"lPCN + _!2[KffdLgDh7fri9h@G8d(f)DWa%l5lqm)5si'N6C7@op\0.rq1&/;J2d@UGQE>^"b=)pJ + G9k0fGQo\0Na6E8:gem==$Kt/ORTt7sHm]a8ndfDa`5C\)B"SJPKjNe.&):K@j1W3*1b)D1 + cqUL+h#.(hlNCCb/[;!*F9T:Ym\=@Kp4Y#=0UR@BpnUG=p3e9(];[1qQD*R7l&I9XMit32q + d[VAm@m+!CIm5of`/*+u@i!6!A+OAUt3-Vi=@_pDZ,0P+%!@8G6b2PXqDbYC^E1'9d&3$:6 + a^dJhZ4;Ke'O>S=^Pqc3_%?Rfd;ka5R=Vr,](QlX4*T3Y!JA,pZA-RR.#h8_25aWgHlq=?)]=3o;4\fdR + L0o^DSTWa5^&>kbnSCZ>ufRHFkmfNXHiJ?)o:50+=%$.mJtI?%H-@\Un/Z/"(4"nSMg^]2@ + Z4C)dub^@MTuUH0&KGn/'aN"K+M6@6J,Rr7'-=A9LH`G"S5"%3=.7ALY.?Ul=cQ=uY@r"$f + 6cJIWG=HY6M8BD-VGG(1YnJK_IYB\Gmh!I&5YF_Lk](1H(8=d!F5mC47rmLM9LB7Qcb@-]1oUT!n@-gEE7QK"[fO`$>GDYEZN[eVBGq^PYGVH3l#Ej7-= + $tJ]E.uQW3,2%D&D@AeI&j\PlJ^m#D#sU]qi]+YbNJak>\O+nA51`Xs](.gap@O]+3(G$1, + Cn6.4qJk=(b8Dm!8\FXP&B30#U%$07bC9"QoHRY#XF))!@So1/UZLm&Rh>YTdCC<[_\U\DX + RQ1_$gmKORll6%A?7oC?fGuX;%@!*nV-W\/2W$ICHh>\!r)\qhM[CM7EGB\N>_[H]bk.PkN + `N;BYU\R!6-OFT>MsLA5NXpc5srA\5gUKtG;lQqe-gH4kc_'RRlSgXj,`@D2QRL7>N`G\an + C-bB3D^GLloOM2*1]F:VB)OQSB=HgP-Z&.6JM7B[F>T&F3)b^>80G#?F@EcNaH4%Hl$olJK + :fiW9f2!7>NN>cKr(2nnD8bd8>KVm5+NP#nWZ-NVOB!ZiFX<.b29+;YOd/(lr-0/`*QQr[p + H]f]"c1X$Q'5?1aVd9)Q"a8mTp1Z$4ulD?3E2>q\-rAq).BHbfkjks]igbIbW0fL;8?"BL7ABD/ + !)ASq]'#O3^'8SMkiLuf_1BZH:f:U:YFGrB`QKhI"sB[GS2/L + )-3eQnk.VV;/:2TVc$mWG/5K,\dEYMB;";qJOcZ],2JgK-p!]oZA5FmN@@Io.CSm+^%j,*V + r-VMX-Ws!)S`>m8n@e9XCcQ!NI1aLL^7D2&]#A(Rs-K9biI]6B=fCoGSdQYK6k9S`$a"P7H,]6A:]bQgU``W-f6YAN:N(h]M3$,G + l;^6uP2S[mUNl+WqWrh\noWTsJ\lTVH:?ErgF]Oq&8;B)JEqsEa^nV;JU]3`u<>N*3nhbqC5h#n12oQg[jp%G)).%k/! + @ef]A]@tppcNIn>]l0O3QM/^a1mctT]cn0&neIk%$b%\^SqZlX?7Jlq_`;V[;j>r]p0=SL^ + _-#i=Za]:)ZF-bP?/$]Z$50gC`5cc$[q.`b*\6o]h-*Ur?q1lICd*=o;/IR\f]s'o\)B/hks3qaIO5_RA3% + &,$aXkAn.==>u/erptNM1%1J6%KV+"J)PAACdp&d%L7[RGoW*.$jHg*cg#Z=$3-L?k;1<*k + )',6dqIAp>tX]d]3mkrgC8VPBYW2:QWoHus)Yj-)%=TVT+5*s,afGVa8[NRb$4JJbI@?*V: + =c&H:qhus0^YLXd;2s%thj_>53L6or@'&lkp4fgeX06,*QXjf!#+olgh@\^ZYLm!5GA+!s4 + c7RK!CU:eVZ16*Pq>=;F_t.1*kq`%ML`jba!I6cF^E&JagE8:QoDHqtHq&QDsM7I`rQ=!-s + sBc4pq`,?NYKjk!&8($[Q+\4?P<`Pd_1m'$)11Z_!+6?@"-qZp5W@LOQ]W3SVEn%]PbOX`0 + )MXu2&d3c[R&3%\jRLtX9J_@Z3D:*Vj#Ts,[.?>D77m>$#if.OiW*@'#YL87o]nqA'O:<#; + =`AJ/J.Of+tO)^*?.^N'\sch;H0JBYu:5=3E&l+m2?1[XG')E-,Hg%.$Xrroi0H6]hp;E"S + pgbKOL]`ak,LIpjZ@'7<(juF_uhF!!.UX,SWZRj(-bB`NO,N6MS8b=mHnS$A7DR)JRrLc'< + CCoqD!t#3)2hQN08^#"]FSc-^rF=5DJWhtQ78Ipc@_#D6Eo7L=cVpg+Vg@JG!jL>mH4.>_N + D`\3<\f]FuMj*:8bOCWV899Ec<-;Tn<3G)-TAU/`/QtCW0CPifP!TA;T,KT^KS>TfqTP.L= + LSJOo7Z!sTqGGI[TDE`)Vs6=?VlOQ9`iH3+qUW9ZSEFnlY\[MRa0TTo7`i&Sqd.a[D>SM:[ + dJKsl+(V=&4R=GBj^uBJ4B@WHQ=%9#_2@!2XA>"r+>?:EI<6TaDuZoe$!P7a"P\QSED]+l( + &$Bd-5roP"uc\JZ*FVLF.Z)TDa7Lf,]9pBQA:Pa(sLSrSM:.SO`^0i:n?0MrK!\+-Y`1-Pq + ?rTDj>8kB_T4S94TK\Ag<6qr1X4%3q:VF7d\q&fWtl1t=Uq9Ab + c?dJFqNT]s7+8c.iO:6Vi&1"Sgl&d`o*);AGCc;SAs+4'F$,KKI361q7K5bQ8623SK@-f*W + :hgW(peE`&'I\ZSWYRqr>W,(Xs+X@u)2+ZM'qe\Y!WYIlND[8N`jjqKnI?b&pl?%W]L[m!, + \mkJrtFCN:l8+N.ReTZ5K9d$MfEJa%G8'*+p;>5J3G&BZQoQFYa,rSfP(f"r\TAZEK5%]3_ + +G]1e,RdO&>I2a4UJ8j:M>0&ai!,6*VUZ:sb=[1.;WAY!;!#P*-t&,X\NX)1\*r)FNk'NFE + /Ya:SZAKHu;pEZln"?[@+WnLe'8$S;8q@g)"bQ)KNoG188`;n$K`n$4Bol4 + "3D>ip/?$e`W)Lj_djoK=XUjoq'a%.?-%]N"N,mKRMihp1e!.GhEqWjQ<9Z)+&B.E9'0Jrr + s"VkMDA(S?+bWt+1Xl!C:\.1lkS4,kL9T2I.M-G+RBnfgmY74;HUMOu[Kkaq9LH$("+Zg&+ + (m:jSMQp]kn2LBLLfkEo>aI!.HEWK[.GEWej$t&*,fhj^AoE\/-iHW5A1Cr+:`Lb)h5%dMAg0,7dL35Z4l + T]KoP4q"!iHn`IRrd34qHnG-esrLt^R+Bg3EI2gXc-oqi.)DHqtkmt7LR%72[&#Ro7V(;'> + G?tq-eY&L0A)mFq2+p@Foj^?03AU0(J&_GEIb(<[i;)8a>=5?&W+"il65np5dRP&Ah*K>X2 + \7N`#TJ?T(9"n$aNP=7H1Z;tb^ep&;`XT`K1m#(TMoHWhUk,fIKuK"6%4_)O^do+#8`:o4m + mBNpp6L2)Gsc5Pj?Aho*sm>ShQ>eolP&Ya/iM4+&b7>S&]F:Nk[\eZs=8B7H7!7Vs?r_6sU + =sF03Wf?Q.3pD&E+10OKdrGp^aF->tUi)riE[FBCGp=TS!6EH95c.OPDLUXkTaKM%eT. + eV;q,J*GI9.bZb"DEo52Db[?&1T3LYhtg9mq3egO!bG&$T^VJf + TgU!9b_()9Foe0m>;*[WV$&g0eQXn=F^HHKbK5++t3*T>lA1)B&fokUERjPU;+R@rK4tOhT + eE/Dc93A33dtYD6R(7#S'.880Y#3sU+n*Ri-OBiQ]I'YO/1HXD<^fGF]5LfoXGq93RL"6Pj + P"&t_Kc?[B(kERV-<@XbCC:.#/8A_KNA6E!]eR%K_OWmgiT35'?C$KCR[FVt5enu[Z+/$Oh + j0uW25fKY/2j&gHG55Tc1@+<9n$QJ+6bWjt(BWNS?4QD1Sj\Br)ZV8i!;UX>JC#ZGf5nIr+ + 92e#E((K#R!eXnq@8q?&LZ_ui7_"3W>? + ZkYAA0d.5H1GTY#6T)]L2PIJs1P?^@QofrY7O?,1"#)dBi[$DO-F=^W69@IWj1kU@n.b*\H<[*"3X<3Zn4,Q/[Xjt1uh=g$:6T`;St"a,c!7a3&lPt*httR!t[N$W`$7:>p + WH3#_QJOaH!\7Ns:DiJP[*_ZGqQR9*N4F`&hY@MeYXaCIDoL-@i'`MJ4EDDT&8b"gD;3.X*R3_VZsK.&QDC)qo).g)f4`d$^WjCpUUPUTh!\L@,?_O.gSMJ[VE0B[3c_SgXFSA2X1e^h4rGdPfM)T1" + b9h_T'/:haBH9([[32ea>l3#WGkD@KG*%WZGChe(dis1AA039")'TH)TIE>3sCP['k>/^Qj + J<&\Z;fj))+O5?th($"g*"QKQ'D8UluJ58VE1BN2QlP=<[eh1J/Xb8\C)+I&P2@WCj7l9"_ + .Gbc=@cX@h9O9'icSjKV>TZ-*GK^,#?`i'Xagq+GK75@aH2KXa66l:\4i0;J&%.CmEbBWPk + oJp8cgbeZLg"A?B"9Jj@8Pds##`N[P\Yljg:[)k$Gh*9*]bd>PXr67Iu!5Kl+5WmCY9[O(O + n]`2+KD7`cHGP3c1-i/j^r4kje.4h*&Mfm25mdWIpj]_6/#C<$!D""2^i,"X'Z">?6$"ff4 + ;bo02D\;P:4"opN#MtOCPSPe!,)T[=;V'p*'tA.,"*YB9^sqb-+#3NZrAG?PUSG9/-.F=!H + ;ZU\:$^1qD?,E2"^]&e1if_;C$Aa2)A4Y$oHt=Jrh`U6YE87@o4akd5u-^F)47.D+(^J;/iUk+D$?,)$fq(KO@n5BH[F + ".n(+odJQqPFArYg<5iBJ-\u,a.XDMM8/oVd"it&+dsf`7JC:KLW&fQ"jXhljKgl`27MK/VV),*-9N> + ?o%8UTBm\0_8b3D/&03s$FR:_\KR=?Ji5KD8eH+ZFtV)M3QGK2=CXojJKC07r[_TNO&NF3] + Q;A]lZ@-7j3(EE=+%JNI)ad.`\>?L=1lH + &bf#serIlY""gZ.>co0)f]c4)$=5AM]8ZUI=\C[eUb^G:3X`rE[gs!\7=L?8qj8`<;bqZTO + boG*G7+59Yd[q@mLB)Z$QjNhmSUfo7!=[RFgt9Etg!.L*9rnoNeD3jZ8-N[0L.pUP6l+Jq0 + YrI<:2K&Yc)"0hV$=dSK$Z.Y!UbIiW[*+A:AjY!7.?q76UG>U>5Tbo;b#)<.P230KjmeDjg + ?>Gqni:/ceL9Or%b'i1Y]#5:_5+.c)DZ?!WX>EMQhF59aJY=#t)Z>6doDhc,E'6ItCcM!r? + 0I^^,YH@>B7C2JAd23b:]gA^O3f>l71@IF1Hb)H!%Dh)uX,kT>D&>bb*tKrA8^FAb&/+Pj) + CdQQA5Kd>)P=J=A>K[=8KS6FbgJ1*!"-oEQc4Y=YLLW1>f`Z6biN+5NZ]p[]jJ1+NRY^8id + NmhI$;ggX9'fj:a3k748KH5SCO=#Ts5<5;/&0m[o-2CLsGgo*Qe5N)KaUlsSUT2Oe?hk_RN + WDZK9Lp0u.o@`V`H0X..0V+t#I9d2amB2s>X8G[3==:,h:GtQb:L< + PaUbbBjGl/;\KJ^>D=]kslZDO^-[*p=@?=^6\#$4P'i]\Sr:*S\(,[t + @3@W!T'IOS`L.UnDfig\gu6>etUMG0Gm=,aUjAhdD-VH?iO=4T/Gcq4GV;ClKD"5#s#:,HI + P1ZPVu=Dg62*J`YXa;pj*A,OJO`^*_=(>.-]f]A0)9ormkP!CU^jE='g0JESG2P=0Z#5+BL + e-oKZs%U1sAGk5<."Bq$;-DNWh3tiIKs3n'Z0#q96Cq-mP8o1%/DFD^gMUgQ*Oo=Drj=&a:`aR$QtI]F9S + E1IV8uPg=D$>/$0Q&(>X!C;8m0NhEJLU0!+K2c=>tn%bL%W[d5B2r8nQ"`88'U&d=iBB7FL + C%;2%PU(SCdTa>nm_9AQ%Wh5YWWPp0MWH.#,-?//L.i#RC,-f,Rf?6!5,2(U^=jjM]AhL:h + $h<>1>>_VBkKLeRF3]$u,987q4QT72i3B0'P^,Dn!0*:o8a#-*HNBo$?6L(SI + ?`c]+oQf?E7n:S&.b4T!UScri9Ah[$a;%HZ]@VX*,ikrmPW;.]rXK\Z%nQ-!Q%$='CmZun/ + qW"M:)]'rEij!V.82LJ/"fuqnCKAYg`m + JW-dHqFlD*HEc:=AN9e88gJD/S!CGq7n&>_C">@dT$5"u#$)hEFsqD>lZMG!#+4I0$j#EhO]`-TjF0Se:.Lp)n:/'cC9r)@#(ZiW4jilt#h[(Hp + +:$]fdkRDH:e-3b,3CbacPC%J5%W&/Z"AIucXLbq3*-CbCJV>8k0,JShJVKi.)G*(RH^(0r:@-F + SVL%F\1A,(_o!,L + g(Bq=JPYjD\O18%`2"3=ASqi0'JU!najWGV4k3&^qgjJME[WrpeQg=*p[\25B + n!*5JHQ'rpSo4P"b:45UQ'_B(U@T8GQd'*uYk!_?QF]mt7M6AE,'+f:>2C;K)T,q"6G+IAWBm^?)2F`VBMQ8$!%VKc7Z8?J6+C+X + AFS`Aj(jg!bH[crP'Eds9ackK,P(Y`Jjh/ + X8nQM^`/klH$-[&2psC(AC7QrR[G/3&jG[*]['o'_;EO`Z)7j4):j`4L?V + I)3Kc=Slc]`*+@UXa3k@[l5jRN%n??\&Xdf9";I9uqd_qjkZI/`"Va4KqWrBK + )XC]+5tn-fU(^sEc-!l+p&!(Al)nL.'FO?=6b_N;DD$pr=b3=Wcbj?W[^:[CF]6uaaPniZG + A=S"tAj1cGN&Y$Gu3Y#tG)ok^4Eumhaq8DAc1'E:TaHAZ@,fiJLJL2@7FI7$i17A.k9!X<$ + .a'1J[k>j%r4+JN'$S@Fbd0FR/;6Al@0`Rdn:2aQk;iQ&cDNMf4IagVD4G8;2,7(lm6OOt8,9Pee?pSrEE&3#[8p;X?!qK=-i + IU[Lul[rbPVXom:&9(%$`l=LK'h1li?N?7(O2iN-r>p5Pta>*ck(CnKjlV/BL\l+: + ff-EY=/TB;iXF58cDeTTc8IF8@P'W%1XN^L&eBcBS@omU7ll(W!dp^RO[@$j+eK]&d(Tn-X + ^AfPVqR$")"@`S;9CZ?8_1QCGImQlgM`j,Q+jLHU3P7/ZfQ[5F$8=2"IL8"X8k..@+SECc` + Q>)uemXLjB)BOU,TB9"IUZjg/NbI[gC&A6g5djpaQ">fZA/XS0[B=:kl&Vt5-iV+qRG0(7Z + Y^XaNO/#Mrn_-&]8#LZDO\1oAfpoqpUtL7rr'X)jrp)-n@U+X`:4gS$5%rbG,gUb"!-fT3E[];[23DJ7KR``epNtfljl_ + !M+9"3]iqTA]O6.6j!WV?Z5)6!"Qi<&K?2E[7\*fmfkJ-J5"j).Ar1>pj>aN2afC^6F\?hL + phuJERMhS]bJ,^VBoDOulk^UAB!\Rk#"W#Gt"-;tf!?bVSJV)kb5lka(TV2;n6'2C:1-?Po + W2(1qe:5=06O4hL,o2Ss'8mH75N,QmKlFI;6MP(U`IP7KOP1VjG]aL)aKH@(rZ=N6IOiBj5 + _uZX,=IZ!/'%Q\M6Sf`72V^3"_b%\nQ%0ZBX&?4e6O0UYZ=3f_\aU4AW2b50mMP:=qL\u6Qk41W)EuYGd!c;s#^__ZKoOS>F)?9nnOdjfo+^ifUOChIoaC71S6qGRbnm$ + a#d+BbBOVnS$O@r/s9-e'3'`t%[;+U28&VEc3"$gN5b*`rT9B`gG_([2@.:jQ3S@b-9p0^P + 8KeMR._3gE-@n`;K1?M!KR8Z"W9_kM@VPMV.ZVG:1RB\[4]\T&H<8a9,3_#,aa@F^M*mU!f + N014/SWg`':D?iTO[:FeaI7KP=n=f>dH_9ngd'.?1a[nNX$]i,%\]m!5:C(>U!t]Md*pR(B + :Td3PJ%l3M;4)g69h6pG*!r%P`cY:,G`F5-IS-N8lE&K8sH_:-5pGh;Y"KpaekQVRV\BY84 + s;-g^F4F3&f=*6r,ZX6I_8,;Uc^];UB=A'::/8F)dKNOk.^uugB"M$'%iEco&FkE/p]2t7uG2g/ + =%9<:9f@pR>G$<*DVB9F&M\EG@!S1+VG$%Y2o3P4A8YFD4&/qMP6HhDi+bHSb,ak&UI*99NOU)L3Ai8BrPn_ + R:T_9X:e$d9gdSu*6KG[9]"LVnl("s&(eH1U#3gPF`ps+?WNq1Bf%&RXBHIkV6I5R8$^i'o + STf\QqN*-I9]aL$O"CbF'Q"98P>H51Cb&nIA,P/WY7L2b-KAJeV^+i[N;H!29-3/8+:j@P1 + ?8gia@_3cG-=j5Qb/4Om)Rc?;BNY/TW7f;9dA";>Dq:qHlJ8\6=L_BhtF$(%40-R-7FtF=] + q<1to@-&Ys!e=)XBG3WZ"ShBb?jm*-?uc`DXMiMl(afI5l(j:qV?@MK.YU_!W?t`E'VjFb! + 'gt[b&BSqRbpJG]lgJ8hRV7,s'KR_Ha*2J`7S91A8H[ZZ*n8dft/nMD&o97k70l#LE;N%W^ + F3iAK'=QArP%c0=[sHje=i.La#[L*(/<.'G,V5mVV!q2jX-nLXU7jD959Ak25'cF)CCe[5r + "b,iIGm,c(iJ9DDst9GGs2nJ&l9l,OL98Wc%D"R$VQk\#(s$]kh6`]kqA9W;#NPGd-,b3G$ + =W^C`G&,N=MJAQlg`(m5^LJrj>GqP)g2 + bC7>$RgELS5@(O"G^k]1ik4>+(BWN4atN'5_Q;(!@"$hCINI/l->GH- + ">-5KNJ\"7MW;`$!$D=C5uJEups:T(G3p!5n76PnB\Ho4e*&Up!0.M*Ubfeg"b;8fa(PP!4 + =?!*%tO?i0RtRGW:hZ$&A\\k&=bT'bQ_]t!FQZE&?Q)>Esp@4$kUGenB25:bm]][%8bL_OO + 4Naf+%6(%M7e5:rq@lL(s&GY0Tf%^`3&1tQPD=ZR9k7J0i7%iTNT`3Z]2A?0r&M< + GaE@VVgo+9:Vgee:iOT_5Zq\0V@Ub34SYtEs>:o//0iC[A6d9*8`,iTDBimDAtXtpj3bnfa + r%S7(F+j/n!9c+57jfsRX72Y.frtt9dk%HRW@#>99E3#g0)GYVB&ZVq8MQKa[=)O'L.E('E?*%V!m]*\X).:KFR`-:0H7+7/+E;]Nq]UH^[HjN$j<`kGR; + ][.SlTbN#oiP0`#^bHSW&,1:m>nYksAOr"$Mi@6>6%,(6!e[ac)b4:^o+_'[%.&LId%PO! + .dTR60C0.$4O_m'+#'J5SO7X<#$SR39(2KJ;+K3$kgB+)tTsa;n5!=6TanO4;5at;p.9ZcT + IE=4O_bBPLhtHeMe'-TMY1jnOX8?mh@SI3`.rqe+uNG1uD7678YWLKCTNqqBa8e&tq&pe-S + Fg0&H+S5Z@bKd60gkXZnWu'M\rKe*TNJ'bs>CiNZZ(1eQa^B]AY^9_!#\;/f:+kYB(_%05# + jkmS=nE#FdejU.C:e9`$iT,B7k8264g0p[9*edp0$kB*%/c@W-[ZUt^`XbD6sNgC_"XB9J\ + +7uoX6)L/Bk&u8$*AQ-Kn_Hmdi>ubN*`;oPoI.<@^na\dG!*'CT1]Y<$!=R6r0.uihT_"F8oC?$`O% + 1N-8LA@M-BD5.P&,*CE4mEM-WO\!i.qZS[%(PU'4HkKDfk_Hq'd&0.@#Z;Eh%bI\dST;.Vo + mrQ'uu:T_\&U>">PBP.pfnXF1hh`)=Q*LoqL.%!!I/!Pg!5YLtNCc@Ib5r\*SCP4!o7n-e0 + l5q0e`JF"!!!ecA*!)O%Wp%\V's"RFhb=oT(n09\B.5Grr15sC41GN=e3Zg#_:-\^ + fUbJK4UdFA>SQ%T_K=(K4u)SBiZg"QBQ$kQYG$GC)3^]p8_q/X_O0*CIfl;t!'Eq/$T<6C-4g0d[GT$M$lF&]#_+e/pr_,AiYkDaVQ^2U=_-\SS_W6XuNOf#:#E(NC + $liM!h&Jb4)YZ4m=3(81X<@3E6kjpHX?$fH[DJN7G(J&b685tIEBabS>*Y^5L)J[)[ + \#UHLY=^F0)mlj6FL?JFZ5_oD+1%=85SgQKY=u+,9_?qbOm.;Dg)Fp@:']nmJJ8eTmsc&C: + GN;RDX/H&rI=J@J4iiZVFVS5WI/A9!<<`5E[t#80nL,_\$[N6MkD/B%KPE"OMG0,+F-o6;" + JTg-#9V^[8+>6V`1g^X?J?Pi$&4F13S1$Q8rOXq.5l&B + pRFnQPk"5q0E,;0mDh=P.!X&Q:D.clo;!9Q7G8u)qFa/d(Sqg + )Tc-t-ZpCP<72;Vs)Bl=*JKtle9FN&b%A7)5+D_%N%+]9tW)OHE@97\anTa\UWDn_RQ^+PZ + QKD8;K0_m?!I77\Vg?X@HTJVTFK9b1YH/GEXAlZV'XrH5YC&-ZG4UAjPfU'`hl[=r9fLd3Z + 0SR?l$aeRIPfrOKH:dm5SodY:=o.]GPP$9rI>h"A%2b?;ANf1$6T-feh&Xt4!qE,1U@I1Jl + 4U,E]P<$P%i)^)p-),+K,_d5?&Ig<(j-BPt^g3?\L@9KiMEeLCmQ(@CHtb&qMeT8]D*fYRV + '&LK/h+4,YOiK=M_P=NBP9@3-5MQW_Y(=r'*Oj,nWl/>50>U"Pu&9F2i)%A65pJ8EdtK;a/aK0\Rc=c#"m'4dMn#'lIlT\J'!2'mb^rd(Z0:ouQ%mb-$]MY=!T>k-2_H`q[*2gs7X3 + 2K^b?V6oI'_"[7A-VA5*SDL2\"!b'qr2Vbu=5.)GR#tg,n7;'$3-5@/pmc5GVQUc5b`fSD6 + 3;;pB^<7f$8]2\4-$?ZAg\)8W:V.LuRigA=+r[0Tf3*]SFW%MF+1$;WZb&<+!WT@^L7DZ0%Y5]Iqbr/'= + &aNNKZ@MN<=bUY7[e4@lQsq/rAeuW?S8n0.gP(E-Haq^Z=IYMbB5SB65T3FbM--%SS# + n2CT.A[#HLetcQ+MqKZb-+LMnQWDo%f7)d4SYRN:Q:o45F3>i!r$:cAD`5+N>"?o)O%&k=h + Y35'ht7"i+7;O.6UALoHJ`'$W!0/+PUYh=p:RROMG*)^l-6(1_!jD'GJ#=+hqVUrLUh0&7*Y88QH9_ + ^%!)SnQ`j!58j!@)tG'mnm,_'#CQ!`:D + 39')5h>:l`PQqXA53OI@W1k2g";n;3CUH>-q-ra@p-A+j=Ti"FMFZuKp7\l?/G'j4j=KA^[ + eV`a7]jYDPCiB2$)4P6Fg+?@V.l%'B\D/]<>G^0T`+@eDGH9j)rIMYdfh_Mm)[aJniN[X/A + ipt@#ZPi0!GK,j_.17O=j5V$N;q7Rl]'T.JrN>U1^#!"tTDh&)pWr)/qE9:m_X[Zpq"t + -mM*CCt!*_(Js1df=s1fqf!a8'1I`Go]s(Ig-4?s"17r+d7kdDU5L8#Q/h=NBt=?!g,nE@?1b@oXOCfVS*"%35"p2]l[4b3P1t%sP%-64Z6rA(97o30l(q)"-o\ + *P$$KN4%;H-YMB3A!;79*?aQ#0l3ei!,7?dDE(F'PPO#f9-b(0,]mfJ[lA6O9D7cXN_Yn2_ + =g(1jsBfQO8o4c`%F[:e6\--n^cA.aT8V1@2V'Eg]5B6dg7#\4NRpW-kVKHZ?PJ)A'B5T!m + R-pAH#(e,2UKAcN1LLM*F4:_V*VCcUfiFXnVK:uU5O5n%63#,t4Kc+P\%0ZKW + :(2au2J\fEfr.Q7k?u(Z&cuI.3Hh5"n9Q]m(G;&NBt$!mVJ7#mUT/'T[\^'?]$@L]CV&ZCX + mA+B`NX0*2TrIDRo[kCm5nq;[r+9FkI>E!QKu2$qpt/KD`dC]^Ml:?#J]c@(Cdhf,luu!V5 + (Qo6_#Mqs2f;OT.4i$I*)E5o=W[3F`X)[`R4=k(JiJ%*B-pJF=//9fH:'AA_?^)[qjcBS_% + f9pH@\hhfpOEaE@9,08\_75"Ct"q7]q$j;,_fQc>kROJjc,Sq"6:HbP9MmX-)M_t&'o#CH) + js&XpIrBFrPeUBZ"jjj@D7mG:K*;;&aEkh44EoWhq$aH35!Y@qLYU0N=Z;r(N.$P[GP^gLd + -Np*<(P:"G$Gj!W3=#I&KCF0u69%C?@14N:KD]SH3)p=dJ.YnA[)*;=ZoEj=22:VS*(.5P2 + Fe&QLX?/F`#+=Bi\WuY"(E@jU&Pc1^u1'S679_hW>+?Zf8:/JJ&S+a2r`V!Mm7^c7Ms!6:V + ,p3UND"*R+E7OA8dGp:i6g=jW,S9FcR-k4\&r[1uk*TO0SV"a4UYRj=H(JKDYuU#\(9^fYS + V\6u7q[,pah,Q'.n/8]d\3-HIa1+EE!b&HWq=jYWeYKP"2G\Pu!h"Ks'2Lh;em&-??&h^7F + L>g'E^:C6[..$Fe-bVB)pVD1@H+k0#tTfsnp@9K$&IVBQ1^fei(A1_k5[[+es>it\7S#-J, + (YQnibXCVJjuRW%g"8O/&W6bLKG:rI?jM_USM]JlI8?:%ClNQ:2DbEeRo=5M7fd5U>BRW`&J*^'4"f"GFp\5TbX + >1-UoBl*1EZF:V#gtR0uDf+UN[s&&6>UtAEmPVto(D>h7H^AbjA^@p=7crMgTlh%gMK+_34 + ,9^Y!/D$?J.MAC\VKj'?d&)T&'ji4CY&BdF7$[P_YE!bjaCIfcPiRM^4WY9&>(!CHrpRC]^ + +a3DmfkaQmW"ubM.YfAe2R6RJj\*s*1cSrk$M_D%70G)?X)a!$DOJ>0KQp9V?nIJkc_?]N: + #!PUXT`Ri'"u9a]is2oa4YK.KpW3T9$Za:T15A1WQLne=iUE@r\N\>tf;jB9V;N\G$,Ud)l + 'hTFM\1H=%G)fkReO"p5[juE)X1Gj]G1)i'kJYaLM?9;44oO;k)@fYA + V+[AH+,\iB!3dBJ@oYH]f`?bI3? + +@^JM*5Vn<.^MD7bQTO9AUi<^6H92%.TPms==Z$HXHfZheuhQ + 4':!Lsl)M8a];4280VoiL3%&/D6]&@_2^KW+0aTqAW%;->Z+!kKF7m;pH0W6I\\V:$nGFqc + 4k&R\:'CuU:-S'qT3L;G8<'44gOeWp&U`WDFurS1JVZqa]XP:j0%8Q:?Z#3O^U\OUl.gWhF + 7.lI]Q_*Yhg5q"&`5&\bn^K'TIs0)*EB?Y]b,NX(j*9Hgh8?70Mr2?4m%+=eFF!>M#6VqYfMML@C).]6jq+Kr`Ta+pA + hI'Y%Y/.We4DMQ"IgME%jaOrK?Jd]^ULNk#M$`;6aL/!37FPUgg>X)""`0r5#?8I!k34u?B + D;lI=7U)'f)4=NJ5Z!t\jc\u^um+Abo=#^JJCUmu*k"K69/.A[)r-j.Cj + 7`9,-\/#*I+NBpV`roa>'Q;#3E5s5Fa#%nY/9T!$G"5MTa+SZWP]oWnGfQkq]L)!*KAOZ/h + FI1p4k,"c,MPa6$(V1Aa@"WO6e=J2fLTB[!9at::]c.q!s\OA#Mb&)e;$TUL:rN!85t\SA) + "1CaN+toa96t68c]*'=G@Xu^;Ok[Op&Kd'g?/tZ)!o(I%;DOlFrLTan$N:aC`0@n?OU?56; + tB)CI:MoKPG0^J":COtf1[Wj9RD5Ekp^.k\.1rQ=a19*>1"1.4A9Z6UCjbEWj + =_4ofH$,ctQM,Hc",i>e8Ce8"%-pYW@m*"GG]-LJ2=)iCnpO9SYHO('#Y4>Sg4 + 2(=?e1!Djlc71@S>HdT5=dCgIG`?!@V*mr4SdH)KJocV[/+jr4k;F+DTap!F&a9Mita]0b; + Kd]+.P;'(j81P!<"YIei/kFcY0L-TYa<)qreV:ajdmT5Er/jnE2TZAj;Tt?G'e.713ka`XK + ofaSW!(Lj5*a;*8[8giN1JHDT"_S)<'6dF%%*As*m-W9b"F"p,tZ7%9$1tK,2+TjS+f-hXA + nhmZGZL^+]Z\OYWpB2U]"0,oWl772G627^`[J><4//C?H[-gbCg/Oe-J=1?DTct5eOeGdTF + ]b%nmOH1i#Ut$VF)PT@RtV9N#=nAB-#FBF";Z!dHC=BEJ5o"AH4A_7U`'do\p.cU%3M_?dp + $)Ka4S?Hl0mVf6/Opa6,n1E]LuL#@5NN7c"Sg9uC`_T^f[fuD;/0Xg$t[e)Do6o6hB2U56C + _hEoG'^$njkejLd&$pU3N9\^"RSM\"`!eoVr'O6_n,Zo4;^)DKYY0_`p;DT=c[D1CZqIF3: + !^/*fu-WS"eE1*ZUH/=`BBm@!kBh@!YfSU!&Zr=oqdnl"ijmSPhcYY25h5>W@k:[Q!@ulPp + ;bIE.tk,"Lq)0%1sRn9n^l7dO@PLO_S'2[_!h&;3e:gPpG+T[+i""gVpa.e[sSl]\DP_g\& + A:mD7Q]_A*e3TN*=l4XZ$``a!XI;O'=KK()^'P>]BCda^9SjYgB*2''akK+KmjkY-[!<`;^ + H>kQ?Il]*TH+(n,m&lPK"*MB!&fI+ + 2M9(2"0b(DW'#_e49Oe9"5lFtII,hHm9\^7"9n2M%Id^97t^3r?Tn!f7I;bd+e3C7<%OVJO + q:Sm7,PC?8c58#/Ord!U`rN)8mP*4H7D-W/l^[0^P,Kl:'K/[`mmLV9)PqM793fm=$S'6#* + i>!burF39NA<*58dd(8[82Zu'=YrY\a6U;7j_bY;bZd%eg%Bnf + >t@JG>(0I27t`dh.q!-N5g'l_CQE`XQ.=#G)$7Q%%a + =/.J_39up[D'JA#`hq1'MfM.(F/V_/KLfetafT\A@D!'BY5*)P!_o7VQjc;@^OA=o":A"&: + :g(^j-%]^U+CI;6ba-24QT0tm:Q:dgSGL1Zr']oLg#W+B*`:VfP?JZ".tn"8mqJ + 63*!S;I;7nUVMjJN+YNYT!Y"+G'kQJk./>BIJIg4I+PN!m!_1kUm!70EV#>k&tEMIV@l@C$ + )j6E5470*`h%J*u_&?7L"mQ7Pctuu1U>%Q4B5p4gp`G + +\[qG<&>D9rX(4=mqK'48.BI8Aq&GqNm6M=%DX]mL#f]$%MDdm'C@WhiJuR7*G()W`&,IS_*/ + rLmN?B)ItC3YPnV=(Kc]FOY3*/E4JW"%i_#TNm7q0g_\ + i@jou>7,2],JXS0Bc3uL(/icZj3b,jY2c>`IdrsBEH"6N*[!"]0VEkborm&_P&<-%e"@?6= + !lR2g")'XXM?X.'/Hc8NA>p@*`:qr[MpJ7qg!3B)%h6Jn;Ha6tg)U4Ao9Y6F=?4*"B@njQ3 + (S].5R%3^KYVU3:fN5@l@OO6l]uJ"gfft8_j,Ee'uQ\ar,lXL9a&7)LE;bFncKV + 36rWHRI<_L^Vd@P30$dh6=(M/rk2KM`skcsE*ZL2HkO)q + Ibo=g4L/u!FKfP*4pF9B\Mesp#B>D;QZ/*J%+!$_%_7e@cmaA5(coA"E[.s(/BkncJR[nVY + Q0`s9K/Pm/_=;YC;GD>U*LH_m6:K2*?&/5M+J:NCp&Yon + h>?mcTBlMelgbf_L(9H^_:402?j=O,(;t#i`ceXAm"P[ebP1@cG3l(3ePrg?]iJsnZs_L,m + ,_Adq0)-8&7HQhbJ/;;XM";&6Z$CD@@$Z],OVWY=-bCH^a0%q0d':u"os#,?lA1%A'9j+Y-qZ]i8eXlXb;Yr5WECUj#s+a + `mA*&37#m2S4K;$;g@\:X@N.8fDA6*')Vu0s@7PUEM)TN*YHt#TCjEDU]EWh&&80Djk + _*k@B<*1.%iIl]IL('b)8gjFX?qSkcY:RkNfmbMFo'u]b9CjFL1Te-Z+YcTj`,hT78+Cg13&br`W4,jYor#o\ + Di;(=_/5;BP"k93!(qH";='TI:7;\)94U"VmkZnlV2jrHVaO[J=35+8-_5@T_2UO_*(>NLO + RGjb6oQ=uMqQDbf790G?'mM&7Hsl_%&V]1GDA.faR"cW;ig?,V8"/?I"9I)7&53`B,P_7F@X0RPPN',B + 9pFb-?5+anUS^)SN+3GIQXs=F>FfC3ppMacq3nZA$KN7TUZ\(@GD55V%YTmVRPpG,BmQY&# + (a'LSrJlnEN?-THOd*]C`6F<3L-&eZqqG!G0@AZ>/mtX!!Lt3F7'tgot9a\J<2"8H_NGD!k + OF%U@@4c'?0J]D\^<4S:;j+nskIean#A51!T;pmk^\tUL?U*ms?EVde+V.S0/pCM@r5m9aK]?-%uo5'ggPJ$\VS5%mej=#9ngqK2=hXM%6X5<0%pF>"qTQ<@XiVba,:r@'*"W1KN#S>E_rs:%calf2V1/PHo^7[I1^U`<`- + d.YiuJ3CbMK%&T9+QEa&.`[V-\A(D' + ge^Fgl&8LS,AP!bgi2rf5/oG4&NTWlL7/&`HRlrEfAul64@YMHeT*G%0CRp!!,bB8%nD#GH + )OiB(:0gqdKlJ3g)ia*Ws3kqS?89"lL@@K5cPR_H9#/i>a;#n?s4O\8MdabVb/q"s:ccHP0 + d-98N*r.E*.-'B7eIMm6cIOdFGkT4Dr^=QT@n%"41VlsYh*%j>IbmhRZH=34d[U=qPO"k2cWL9Bh56an3W@]'a0Gfq5IFM,i1s:i;P-Yo2]7@@ + LIPZVCh?3+jSc0(YBND,U0jL]5qsZ(?1Wq<'qS`o3N\qL.d7qIjOHkqqP;FrNIPD('#dTk" + #VbuX's2oQQ,LGk.\g+c*rm2r,K+D6c)dMi9MCJq`KI-H+m4k.N0;bCOpO?XS['_)A + Mb'o%)!Z0ogAtrak,#h07SM&KX + `EOjB8ZjM>\Yg/%?)tqfYGqu0f=Luhe2I#;:YF8mQ0g`9.Ks(bQ + GU.6&(Z;>>#gu)]=Pqg?1-4rlW;uGRc(V\$J[8K$Z3#r^%#/&iS4qaE3:e3`[t"[$!d:uTq + 6cEU!Qkd2hV`o%[r,rF3c\_JTt?OV.!Gjo3QfNl7_2[%cQ&BqQ)(1*"W;S^aBM:(Bo<%Zci + d8=?slRq>c4?W#qW:k$npmli(hLq>WtMI6Xk+^;ne$5@ + ^/V!if*jNY(a.LQOA&%>k1WT:2g"ZL=5PiY:!6q_Lc0UnACt-]8SeY[C(f/igJ4p,#%g"Eo + !cS_QXO@/G0`[:m!,)_bYPeRm4O3Fas%he#T`Dg"rX`K^d)b_(m2FAk>m;l'dZ#g_Y\==/. + ZpB[#iM<'D*$1.JCJ:.eEopQbuiLf>6s4<,3[;hJje0ACaSOaQjn#%8Wq\%YaE[b$Yc%Z/V + ]]\UtDBA$r)VJNK>]*\=8JK%:\8LD5<]]q$o+:0.jF=WRfg;B%sH%geGT>C%U3N!8NG-h+@ + `+d0u[_&DdlY2_F*aY-_t]ni=tehsi+i6a#>s5i>#:i1ToX+[3W6.MH>lV2ch[l)h7/* + RaN:2aMd`FR.WAXpQ?3[@RP + 2Rbo/PA"KM)R,R#r?gl!_t$.>_[3f*:Td30W,9)XOiQ;$LkRX':fO&Ta[%#3cN;tD4$f6Yh + AWTS.OI.nE/HOkac\ZgG?eYir[tf%PeUk4`/3od02]'#i.)e&J5`iOV\V0'],i3&of31e,8 + *uYi#uki,/\UXp?-.,SK'V^7b64+[HgL+?jZn'i@lh<%SjQ3u0G9(3+1,d;\0?rte(r(d[Y + $AiPNQE!JXp(f"i`Zs+C;QPhNG7Uc;ZWkfTGb37>sX`:ZdNg3,QiCGH_)t\sO[#H'tOK\lm + (N&CW-d8g?]^.B,Z#Sb=NlnX;d2qqebq;'oc=o34!u"7mZjQ+\ofS-f8 + #rLh@D+R*+:+'\Ec?#5j3Fkng3k;(Sk/iBP5LY3!M_S2^G(1E1,;b]*4:BqP7?rdPr1L--p + &Rb-R"#FPQ'5]Z;:t65'#o4p5F+0G7:mNf'3'iljPE+52HTH$1ZI51%hUDN51Z,d%H]W%kb6(0he + i)Ee,L'Q"VX`gU_F8RU&GbB=;ku?$c'Ui'I+=A-Z(>I6Le>uZ!aK:3>PJ-6kO?*'LWW99l* + HZ$)!jub,%P3L8sZ]7TTm6U&T)]Ckcr))KDlLDN@<^+WF:l88b1nZ,]E'X[s[S8XDA-Yo[W + OTh9F&YqF&$Pch;KZQ@^tE + <;1Y!mir4$fY,guBgOC[PoD#f:;"nO.J5*&<@n%_0Pb'<#9^-O7X>0P3X(>b# + 68]MPt`Z]moU_Y&g=QpTY_*<+ahn>=Vpm`=8RJkrI?j.J9V$D\-7E8$%VdcJN+Nu(`[:UR( + f6YrWrs8m1EM:m=R:^?FAsKVr(eqhcj2K?!'W>i?f3=:Aph4!e;g@6,F]9fj-RK>o5q`\46 + Z:jbGfQ>*sMd+WM,'7#^&%"[HF%fSq_R#;oNiM)Gm$XZp4r=<5ac@F0\WZZBSSHX^hp@eMU + 9PC[Ae"@(I#37+^bZ^53SMe)FLR<,%R1SnZMiJm5^!B-cd%@^sKfk.[4B(g.UF3A2us(A`7 + A"e&_6-j4@r%_<24mD]SnIIg,Q"eg=55(R-Fm#;%Y3 + r#/@I54//:HQ*qsXs7?)LmHQa2TGP?`72".+!h0B_0\2TZuLI6kGAcr)t"ne*sR&%&a8UHf + 0os_D-qDN:o^FL,;C#D5Z!+CFj!4mkn!=6sX+JKB*hS6P[JYjd.iU47tc3OE!$i3_u\nS); + +/g*+W&fd6HB"k,Of5jTW;;dY\s]P[%tZ;hJmf1Gkm`6co7H2nLR:$$R!F>50.sbQL=eqr[ + C1Uq>TWTb!`.M]JIiI(@a5n6Y'S-k[H">L_5k(mY>qF"62h98a:7@Y%=m@I\uf>lPHd*5d[ + 6S631mIT3Y\8g(^oXfQ6?O99oE,ZQ'f6p)`qO`n9im-34;25f[5ZQf*f2g.@Ak$]3UFQVOI + ;EF680gN<:oVA/:%GN/Igr! + ,1B;rmJI2CCf?n;Ap6`M-fc[>E3\sLb@q9U,[coh]^I1=P/&^\3)WLBWYZIc>S!O(4^K,XQ + dg@mBa_:(9HY+!VrC(/@FDRJdTpOI!.kJaQAjcj + '`iO,(mW_M("`g3Z5Xn<],fPg3Y(*C&R-cCRk]ft2e'+=Zi-l0ehONk_dfpM[(B2i[S+#1e + W#kn$l`X1\E'+uA\)L<[aVaVF1mWKe$kn0\(c8>&FqON&J+[T&31]?)'X\4JL(Xq'u=Q')-mt=e!;oH]lZ,tY!;"88T&_;ja6 + =TZpWrj=9ZK^^9OCPHiFq1"dr,.RCXM,-9!R($S\.[flR/9/;oP8.9;%^[h*eu^GQ]0p('46Nq>X02h06F8a2PN\[-4t] + L[R8?:Jrhj2tdG@A_/>C'\n67T`>m`KiB1dX + >lW]e.fBO]oG(q0MS]SN%EoJf:iH,=*"UiB,Rq48U^!q41j;s-)"uO"LBif".pc?q:G4RMf + PPOW,+h?emR[M:io/r\b6/^ifsUMqOdOg)[$iZZ[H#QOEcr"G+Bt&9q.m)$teG!WatX:+6p + 97H4tl)_K@eE'NC2#5j?Lf9+1NZ?[Bl]`49jV6]o]`b@,r9A6])bPl\8DU0^%l3.ZO6>AG$ + oQj4UPtD126#i?Y:+k"4^Zs@,]1TP + O$a8^q8ipqBmhOc=sLs,L=]tXN5a6E##ZCh/Hcf'W#r4gSDrrEs4oE]r^`Y"=8_,*fA#8f- + o+Fh7)O`4[!?ml?a4-%r5#6I?iMLWQ!*(:K.nMEX@(l/#j(79)h@UDS:S75gE&\7mIQr2@Y + *M;iloaWX/8R->'Ac_oW8Q&-'?cc2nS:?l'I&SR;ZG`pu9#\o"J'5.nHSEZTbf)?0jhZ4!9 + iFa;2bgW3e6C3sU&J8_F0tI!cR8K!"=c/Fpfa!hr,^=r-aQ+\0r![4$NjFK + I8jdkp/F99us\r`N28*CWd;Uq*NkeR/C02\W$=A:N/fSRkt>jba"B=&m@K=BAN$I:-/gEF+ + .I[QTU\=a!f(;krO33- + '5.SGpqgQStmr<#l4%ir1u + !^KEdQ3#kB?ec$?:c88CSM?i)E,f\3k+6.$S7TdtSUI8mW;78=$EZH,j>]M!`/8PkWmR,'S0dC4NO-5 + >"8r6+b-=ko#aB9'K8a/8`70Z6r8KL`dA6s\-?6btIXpBBBf,2%pa79QO5K%N)(=@A`KCFI,:I4<;F0tHZ;s&HTPjptZeGUL-n:2n7# + =,Wg#9__#K.%?G68TI0&+KWC782hD!8nk#Ueln]Pn?I7<>Hf_0T@L/3_'m,*msV/OC=#88E + huYV&E0rd`PUePcZ=1$=K3a4Yod]eMjAJED>]GF&'j!4]D"JT%!>#D9=.m<-91U.T2D.K$d + 3?jXd:/P+AUFJ6AmEYc4YNV$0,e!DpW[LOc>"']Zl1Q/XW'+%KD)Q;&5W\Lh5XqmQ3"E@a_L4l-5#5tD!g&c\5\/2i?8]11T;B,6Wgt_;Y/ + T7O[.51/)SFERQ:*4J5%dc;Tc,%#?e7UbIO#*KX/'6-,H'Qu`9fObE<]PZ]6"8fX1\dSi%< + j)"eWp2cH2N_mki:F`^%H-_Y\\eeeZ5NR>rBX5^r(jU1sA]BXpVI!XJZTBk<%*4gUVb9_:Q + >H38Y*nbhB^,>oEMgIFRjUR%:!HHmNiCR!,9Cf^iJKc0ar>kOB35[Yh)c-HnCNlLGY`DUIj + a%I5Y)%]+Bf"8BDi$$RkY'L+G^d]?u$525)ATJIScS'Yd>@[5:Qk`gq$bm8#[k5,^=?TdTVF;3X48i/0B`n9$$UlDG"geQl#@j\*O,b,#W + brs)s&-5&XAC97j7O&ijJ>AJQ0Y$@.$/W0)@(<(U]Q1hmn*arI.4b[28)Y,k<$j!V;@#JDB + oX`E')fd7lZQ.eYg';P0Y<+r4%D/HXp%*$pn,`ST$r72Wf@1fXK+MN*L'99.+"l_SD:[aL3 + m]Pb!@0B"4g[ff0tH!7QU^Ck0=uF7n_H:YGOKtC^3BSTi-XpFn/`7ZG[D'Ar)\XCSgVP6bL + _BaW-Q)ob8=]99o;'ZS+@./raIuWkOA0aoU_,m@K/so!N7rXomd&85uK5Q&#](af@kS$;=6 + RP]C\ldbscWl55kN!`6d.d6P'q5J7"Z=Sm!5r]Q9)3LXV+U"7'IsZ1&"n3X:3L,34->pg]% + Uamn!S&F?>[s%(ces0=e$7WXm$jF\`rN^Nho`?MWNNKPCX1c&qJ1OD&+UJ@Al3]"Z8^e.M2 + g/PE<:JdmabI%lAT?%0R8"X:d@a@PlP-5thtX.CV//i=7+M2 + (EE"6lHPmDm`be2/F9R + +c/XKjJ^n54q0CabI)&Sd64lnT0YO#Iu=2^':DF8-V_F=B3u6]6+TR[5sl4"5f-_omD+bBt + b^IAuO]`!JS\Y[%q.J05e^'N1d;6Dh7jW46<9\9j(NR]V:;;+.W&JQ(-uA%$n!6A.=+X&32 + gl]FUdP/TJm\Kd?>]9Oo]E8EB + Ld/h7r3H@I!NbkH:PVp8'H;"gjDWQI7NXS8,Rp.n\;_op:Qm;Z(pJQ;BS.JcH(*`0f.\"j3 + Y!agrR+^8AXbK:fmo8N!PdXJ?TFd:hYVGh+9_g8P9')XGT0rLp+m$"e]B-pb@DCQcXOn\tB + 'CFIaSD)TZ)*5,7>a*2,QKlb:lDauEQ.fP*E\!3g]3^B5ds)tUfiX%KGc19@S"g\4>+p5&S + rTo8_%oD65>q`i/k,[3&ho//c/8O*[bbCW?@RS5Mr!t*dE9?=ESCq`/F#da.1^mr57$k&^F + ?%ro3^uWHCMajQ'83mVT5o^Op]1P@Wb;@pb##K"=r"^4R:dK_26'G24X&P+L*%(k/btD+Cp + bW(\6RZ=W!FQk`,'UHegHmrH_Fn?4g3pCjIPtHh&8Wf@)M8,_2?Zp4!14)ne1[bqD79*#., + /^'`&0nM1cI/D[mH=BPWCaF3k'`f:C"Ro4m4-13m#=UIK`C*]pCfRO;=4"T^`M8g>URE! + ^jb,r0rH2+RRs.g1MhCkblb4!D8+]]D8sHd50Q5=oENTgY/;5_"@c+bECKJ,gO>a<)T[IF + KQ60E".8SR*ppDSVLP5c&Ho*"+'DgWVdu^tj,iFN2OLc:j+(bb]SN,g4#'bRK6M#Q9%ol)?#*cmH[K + VX2aU4O_.%e^up%Kj\&ms/QDp[=Q/0,4k83)D=\_.Yh;]loM"K69<3#r%E>Y+f24$=Q$AK=6oadDb + DY:Uebe8A2F\0t`,JbUm'e8MJ"Z.!]MJD^n<;MIZN1gNQ&;gm=tA_Gn!g0MDH?-V*Zh:u;t + h-KF+?2`_fo>4F`L/XV8?;3K;n4*J$M + X)mrYq=>V%Q12u72It;h]7+q\%!$2XsMV+`Zc-p*'`\prRA6A($[WRZCTOWcP2$7M]0R-?H + &8=K/0W;'.[e/gn"e*=tS(Y3>;JCgg9qBSkT4E4!:BPlG*_KTunrmlS\(((SA4#!d6I1$I6 + sZdlD#1/bqo")d:ZI:-mT*-m9A9?KgAl_VYY0_`/C[33Jab6,."9o\OkYj$Q:2/-UQrB5@k + 75bd+,fu[1=8X/csl'd=@)/'uDOC"5hN-K=F!sPqPu7A5PM27\puj;U-/-A7m+5k;1-5g>F + QJ0*Nb%#I\EH?p0.h>MUVp'kJcKpbIIM9;l@#!#S--87u6Li8,f5kY?bD?A*!(KkP@[BRYe + OG)BsnMWgXi/KNk!1k\&Hqd*lr?=&b$:6JqLP@%RO?Ta0ta?G8_7#7KbK!6oc%Ir[]&Etg3 + C!N4i5h:]hBED;8?]Zf%rfL2#*eDHNK,?BHQ2g,diu*FT6[OPn-+Q!OpXmLRlIrQA<7i>lT?4__\9("Th+,iG`_H'VL^)=.(.9ERO]-G + j34_R(&!)g-72nm0'/a$Bc7m#E<,Sg7COb<\lMf@8'e,gK&i(8eId2([0b[=Vl5dnO.%D+\ + c"^chnM#D=3fKk<-c_"hJ@2GN8if\@m`<8h>OiT$k="gE*1n-LLN&qM_Rj7MNP*LPn%1X;d + QmYN%H5"!W3[q''A:A4'Y"?Y<_4_1uoABmp'?;F@7:5-nZjQYP.r!Y@$HjgJb1\7QLc>Foi,o'^)/9/1k+UH/0 + GA1f!J4]+"-jUT85oiP&-3!;C>HtNAop(/F_;S^EH.R@#'MKs3s2E`;-hA5a9R6?$cU%i3] + @u*FjoQrJ^PA=J63(*@=s,'aTnu,nrG#9QAfjf.WW9Qo$8h,f.:t"066M8Eoek9g+NQ$49R + jG,)2h5?BFbhL?Ummo7o>?e`\22IY!k)J_$rR!%3A[/Vu0.o8bqS+$iJC:k^T.J=m7"^qK" + L2fs?bJp$OUL^X:)%O3ZVo1YooO%sG^:LG6Doa3HhW&0-G4nI<`63p0Joj>l'?u$Q\@'W#i + Su[":ZrTB!CND2g%(:cHZ)8I1>&YCZ1=K%Op$O.cSGufLF?Lms56q(=g%$%k3C + XCF4oBAjFJpe'Kfp$H>`pM>!tVZ?^`GKnqFOEH,fKMNqPkNKC=OoDj,r[elLMe=7=F:qG(- + -W.':g'_BnXJaab5fMr)WHgiq$(3]oD*sQ=;DfBf$\XmCQ\3:#I>_n/)$E"=b(`i_9/KooV + J=$gD.9HH+rKo11f\Hg,(E-u0/ + idqL3,(/rWb`Ck[Fb>'m&"CJTr(;0P6(^iI6:7hlYuL#^-9j/d"93%Cd_6s4ura,jf4DU@: + ^,YZ,[=WII--b!/`J02_aa22'UY"g@:c7oa='#k`CEJ'P?-Y-W8KreL[TY2=dr!W`k"!Yl) + t6Xe@:CC-6gCH%mU=M.fl(i%$bYPA$L]-*6DA?pJ+V5W1!b2oqNFN.`FQd'*d3Fq+K3hA7m + F69"pK`;1,6#d6#-61EL^Zh!JD*FdnW`1Im$>j5O>%ffEFmA@C`F6!Wg@+nrUB]YcQVD'q's!5Oj!bDEfG$A[L@j>eXDsS'>Se0D8=;6) + )`i)EM_((6J\p@7ptSn/$^1POZ'i8MIl[L>1a' + 3>G_#F8:LZ*:h:M[Z`63Y7fj8#MS'&kGqcJ@scO-egZqYkqNjpU\Z5O]'es-W: + 1r]6)cgT^sVJA+ir(_Jm=Y_r08U#A]]9G8hu'r.[L=ts!u8J`3h5@P55)]b + 7bT-0pcqicM9r6(rIt7SeLIKtR@L:\4*n6;YXl$Uc*/<>e2jZs + =_U;DagA8Q)phA6Mm#6p?fV5'mCD7C>?9=c#hb + X5s.o5JgcL4J2`s$*="pEf5ohN2EWs + $%`Rq&LWEDq#3Fg.O. + -pLZL5=gmC_m,hXA2I>P@m-n6?J1^i8'N&&RiV?PUTN4F\6iESQU(an"5R1!9)M^GX=.SGt + OpoA(3KdIc6J;:9Sm5?HA-SUXqtot?u+Nt`l@iO>ct;%@I`L];e\GQ&,0iV;7GOlH1#5Ep& + UVuN=OH2`l(l2'8'Z0M3M^U:YGAghd>:7rZ@coqYi^ol1^>d + Yts1;!)*]M@5K@NKD'dK*WnZonH,@Npj@$Dn3*JR36167&E6dt!<=C:JlkbTn3QS.#F,SO3 + 2mYQ&cWEX3XCL9T)F6^Vj'iOh=/:t0>M#_YG%$q,mRMBYH>[1WXFYo + *Ni"b=ZlK1"jS&n>HkO8,qD`l)#J^e7 + _MQPA^pkR&U6'^`7o1@^.*,0R9"#NO2TKX?NFm.+S(]4jCZ"fi[B9P5>+8L3/X)b3i>a^ZT + TBc'?+k0"jo743_engOKFM@k:g9Y0'uIJ4\CY$+#Jg$c6$P$gf4!?;gVJ.'($3(eT(63Gc$ + F;>lb1oMpg2_8Hgd6m^6d^Phja"^YJpCRfCo8PbQWIO]=G:*1#%71p"E: + .[_U*oD@9bUBn>m&9cn>qqfFAl'TRaUB-mF=c\2))?+2h9lq-gpIL9[O4+/2k4$K+ehJU96 + \OEqIH/0g855ntIa1^?cic5"4CAVZBI-tN7P&#"qNb0^_M7'*d/Eo=#7@jJL6*)9a+jAq37 + ShS0_d2MUgCQ/q@3(FhQ*:p%KNj7RXa0I]J8I6H"g&==ZXT]qk*/ft85"b%,.AFrRda=le9 + L%c'-H`;`PN?[Yb"X]q$LqiZd\BU\U>6/fEIH?Ur`U.1D_IsDXd@KH@;?a4BBCqD2%q?Abe + 54gVM*FMdo>3Dl*,/N,he`C12bA>V6]7YBFTpKkm#Mo,>SI34VQ:\*u!ZtO1b=]#PNWi4iO + pE&Hu=rI!n:\$E_D7Y9g5,B!54R0j$GIB'%R/ZAS`o5JkKF + `7^**"(5pY`6atnAOMl/k9e&TFQU00]jlIZQBQ`Cd + _L%M??9)RiNObFEcL1ed\98W1QkG%'(90W,U@m.DfD! + $km!Ye9p>4-Mqd4@2rR\aW56'Je%t3qr-c>`:$fl\9)1cYU#S?f-#^m\HoWfjn_>)=dI@me + ]c[I%nHj,6Yc5_QebT)+ZpjNagr8*84+!Mgs$,Ak5_)s0ei3)q&WrPqi + $6Z8TbB+5%mLZRq!KiC[eBl>[XJ + FA-\/ic0jS%PXhRNoO[:j/;h9"UqC\U/A'eWlhPE&[(4f`2kS!XR9F7Y%/XE_&NH$#LCQqY + >tY]((%dH%ShbXpVuHb[@pg@1r8h6>/ + #$,4!Gci@Gs$f%k.;lG-^k'B.dUi4KZ&3!;R0oob"XdVASUUFGc1.MAX/d]4l + 2W@LlM.rqsHP.rNq_^i%5//$$Fc^;N7'*52/L9LaUP2@k>ilI2?rf?.nP4($LjQOpnqbuc`\AIP\Oeu=OpE7[>7:CX#t5]GRp:up6tlC-)jfPpQqE;Ms._%8FM)1hc + "iA_Z`'OqPH#/G0`iFWl,dh1uR&l#nLNP?,@;VWFE(kMGj+NP*:"\)2*#iLjRd7BugEto/R + 7<);aO]N9;>i5'_(,9Es`\QJ0<2H`/V=AmKOb2R,TM2_iVf!qU`8O?$P8S6pj$4+mVf^te7 + &j+`9TDmedB_j`]&g3+)mQp7dDY0hi*5/B*oL3*df25r7K?g'#U"d/!=BCSL_YJE+49*iNf + R$LnEG'2YjS&Z`aB@O#T1@=#lrV[dkJ0b#eg<:&$nU@i/@r7d'N%)^`c)[+lWc8LGmZQ.2" + 3`E]Rkh5:]`AB!l1=Z=R$dQjI0[Bn>ojHl%cLq'5]R4I$36_126r]&;Ti>\cWsPE>rC;@Fk^4;hd0==?BsPW[HClH + o3W7+pla"+O.A->*A*5D1Nj-^[LY0Q)_mWp0/FVWVmS0cIL&3&HsScoOIb["%I%m?lB=oQ3sQh2`#d`PCE`I>AI/m"ga(22SLCLNd!kl#&nKse"m;5TfQ + Y,3fno0aXK;JMf)J(#]mUqPRM4ZQTS*64I5k-(B6NadP!E1G85tFle;Y6D;\odJ( + N(%9`1n.C_W!;,9!O=LOcZN+[i%JH:6`Eu#(oT+`u90b9^Yas'[ocf3DI!W)puPu2ueB[!Y + n\u#%2(QQ7=&6!!@b?$,GO3+TrUt9GHIM*dB0=&ab2S\P_dd+7b4.cUnrqQjJ<)Bm*P[[5> + DAHH]bV"+X>_!=fu__,m;P% T+["4>5=Hm;QT3k(p*HtckU5B!%XqDm#)\6F,J_jeN:^=d` + $b4#XZ."YVo3[mX?MODYR/`4m"-;+=O;S:bfaEo[$'A@$?$6(=*fUJYX1bXr[iq+07@>+i#1#8!Uob&tq,sJF + !e4&.64Y$t)g6+Bi7M7:j"]AiV*(aDqo%1)4fT0;*n1!B_,Gg)lW7S!.-I[X].pHlENb%ot + <:+V#*84:In2%ech41N`hDZYL>eSrE&Pg$XpP_ZfXn)&CCUJl-Ace"BapTTFR_g('9Hj#]0 + dTpG#IF0)#WgpC2*OtG95TK:fH9kCj4)$d:.]nl[7rUJX6RYugE.C0!DZiGk$_@,_=@g2F-J[Q+nA/,+@694-k/ + GY$m?j"JRT5-*#_+2'LCY\@Jb[-";Lr6+$i.+!Se9\)B(73'orh:%lt,,49O8u?C2=Y$^qQ + 5jdL-L"b9VZ3'\3%:`kHONFB$u5b\ck'SR[Z%3[h@!2#glI^b]&LedPtQ!_2028&BHNl>06 + i>`c9g]HWpC#F8s2+DUghhH\8M?$]tnl-/RLV9NeN*-p90.mLFH4-!?o`/q=+KuE.'G2LZ\ + iZdsY+cP[-6&?3#saX_^d\H7BdAL5>hL"5fb;=%%E2RN`LKfEgfMJm>K.Xq`a!IbRE/McaE]PWR6-UF^klLqQsdt@L8?(:%^!Mj!l-Dj4JsAG%E[^7R;"$ea + HbB+$*O:"OnSU84t5l7,9f:GolCNG-Vg$rkro@$aTCe!;uPFZP26I:gB3k!q>)ALT1)fD3J + ePj)W-8Pss.6Oi,UE+*7)E9AOmr3,5J&-.B'B*qW8_?UUV#?0+g/W/p.6mu4V=u^te-g=lG + sPCEVUn*E=%T;DrS(0MVnK]b4h[c0AeuW>b+LgrJ1:W92@(F[$IiAmi:[]?6kI0K(',Hh@< + `AM*YuQl&:m^gJD_$tS-f8@R)!A8)iHW4ndhE_7hJGOhB#)4<(.LNigS3Qe:pGTAZ]D@j), + !L_,D,p%st[SY$14qg@2#Oo<1+#I8Q?8>6V6nSP3,JYnu`2PgOt>qm%%@ZCZ?I:1b7"@TWY + ;++T.Q]Ud5X;-ZuHm>*M"0%L(]X6D,]?L-#CJ6C+%7?%\d#i#Hk+]]-3r"m!k);HtdJK,8q + W9jVK%\W)%joQ-8$k'j!!hBh/21u<*dC]>sD>8YW3/h?,OX`Qq@%RSgg]gPIPj2[^TiK#3! + +6!2O:%Y\&)cFW+]8lF$5ich)P52$iMV!TiR^jc]`s + .`/TOJ*b&$qEC!?au;TPb$#Jdc!5&)beCiA\6#-l0P6(SLU8JTY1JAQh@I%3[eV5mdo@Oq" + ('#6:\)i'%>%*NM0i`h.&Ehoksc]FrEn=XTATTsj8IlsrZC9F)'#18SMbokbWNZ?_>rV + G[bstQ*#9+kJ%$DZ\83o@&Vrj6nQ;#Z.2Rt5A!)T>DEf/'8E)u\"9ee@E&7q*5_o0"BgH]S)$QH_N$`+&:ol!S_GJAS%E#F"52j#TC1^tDkPRsd`5Xg. + '?-)=>8Tfk1g(]gnYqIZ`mN"M*CmBnMImt$"GIu>E\r\"+6F`5,tWeN>&8dUQn]2qZF$d,% + :aZ)?^RbeHt#b?$%-1)o.8!>9f#(^1gl[:r`g8)"[u8;FYufk<(4K#h9_XH!aZ"Xa=KKp*s>-9')a_J!P7hSS$RKQ'tn1QEb"9gB[\bDHeE`[r7R + 5q-mL!pEe()h"OgtFs#gsATqGsIH,W[GI?@1Q7)Y&I?k-5e2QU/.dM< + <.Em%NZj6g\>Cp9j#FJ)N+7*p,Y4D",-?Q0\M@b-"2S\::S%9BTI6iS\Ag69GkO4hA!tndS + ``#q2D'hT4Kp@Dj;rfCBfYUJUhQ3NK;$Ha/ruiC]!eZ@BjbTn7S9K01iqYC5`R__q_f&sK: + uF7:`^OhS[`P6QBeTM+(hQXAG>4f<5QG*iiW+9WgP`P=G=EPV>l"JUiSMq?Va?ZYUd"<$b9nL?!G//Kf:C^pLIK + JLS%fIcTCItg58b`]5H/Z&*Q6DF8_bK5Zs"sOnOW+99?WIQ_*f_17)N]G>bUPKK\+W>DoC( + 2>fM$C[>RhKUT9tX7\YsdKg1P.jNXAq6<,qBu\T,1tqc9$f10ZGo]Fq6n#N=h]O*iO1#cZn + V;a70$t(ug+n<-F<.,ls\Fpk-r#ImT;!%jAkW+N?t!6]rWEV1_\j;07jg1-lkS6thVAZ9FH + 9*?n(c2("Ji9p?9<;@8Pd"9GOdm["\V,F;C,hJL-\Q5`U;0/&JWVQ2LCH1g,Q=;]H@\jAKi_^bW)Si9Q4h)h8?<4;`mosA=Xu@ + 1XVI//ep>kNCLqFdFiLml.cFXu[<8Yfd_/_.pKhXsJ$7E-^=YLJ@4W.IYnd*!]iVB2/9BOM + mr$3L#K$9f[?Z+n3XEOu2.paeT79mqr7X+(Bl+T^+pTZE"gGWX5S^K(2T73[i"23,l'3>+H + Hk3S-"\5>^('&J6!G`k"TYJ<4l^8h>dE3hWH'5;&@Ugi_YjpPV:UHGAfG$\rsd1\N_9rS*u + ZP_&5JQ`6qL%@\e:-A@aus13XSq3C8o + Tr\qj=0N/=pYQmG_-Ff<`!WYIm*(-ZkQ1E*E-95h013C$#q*@('3Hp`8'Yp'a%kT1u%5H?p + aAHYZ + Z:09cj^c$BGQZ%ZjMK5M@i2IZMbm)QrB%gNY2Fb!<=5M+R1`1^q`37$=WR*$;p>%f[s74pC + C.]\P*2F!8;(M[4G5c>gfUof$Hh/ok(*2c:IJCUJ=HT + np>:B'sf*/Il\-4hSI_,'H'&'0A$Gm1E+iG,t"Z4*4NjnR3"UHi&>?[K,b$>6Nl5q;1.D%) + LRd#N73cK^bQMO-0STGc:k%j$-RT@B?72STY=!g''/sD[O)1!9YY#^Co>(4me<$V@^V>*OdmK,]1P%r5am_giU)Ck3E8JYX/@)g&n1*l + :!j_GhetA0aK`-,P$V_LsATPUaP//&J)56F8;\j=S7@/KGd`Qq?TH3,FKtM)^-[_aHB";i8 + J6Mn-bc\El49bW7<(OuX0)#\\epn3r"\:k7I6JHR/FZi^AK."]fO"pf%_URjKq2oB/_`0<( + ]!($ghQ[g&B$4%i-5Ucn^CBpW33afIk,3P##4p1p$\so`hA#c5lXY/qAN3LI-lrgpbn.N>& + 3t`c,iq14Tp,b7c07rr%Mmc)/h_32a]8O.o>IlKZ]XtcVUGE$BUV=aHArdXB#Q)9!]*>$__ + %5#44=kKVbJZMQ%D6`m4Fmr(Rh7c`aum!.OW2NWbK^^3(WX?5]c;I*UXR9T*5+'R"rLnVpr + Nd)1kTI0M.d%1d,T\m*XQi-WBIu!*Z:2T-n:T8!*GZ,'Z=5HqHcuQLdK\)tu1!pb[r + .^QEn#U4^4nX1u"?9%3_BBNGH.2Zn;$J>`iD*/::`!s.=$!n8=/cnY\?9!*J7b:oN=\/U$] + ;?[n`)gi*XZX,to&e7#;6b\3.1hs*s1f>M9m8ofITipqik>pi-+USl`,tgnK^$F6qp(_nKd]rU'L73XoneJ2J=m8Q84Ngt;%&otMj4Ib/#Ym + 1=$!up]#1YsUfu-P>#<.A0)O^egg)QpZ7eUR;&!G#8gm.iqMj!94,"Es;Z+OK.n540YT(:' + UhYr9D,J1S:uJHFo>-L#EA443UU.l`oQ2e:F](VN]UX2oA9b@0bt#'3]_m/tj3g(1J&i'Ma + ;BETh(FFQIt^Ep#2gC#Zl%-iS,eWW!4Y5r&6's'MR$KG;j<7s[5u#(iCO&68JI>nm69"X6^ + 3aEZP,)c>sbEaQbJS\8Wu*TX6_XbP@h\WN&:d_@'&T2dFUc9[9)?21o"-!+6!2Pdu<0"9;gd"#s$#0oX]AXk*/Q + \,];-k]N@328D(T&u_+H;8WIF4bMBl.aWZHD1^B(i`J?r=@Iqr!NsL#`&2OEU!rt"W_3';!0J*@i2%X0#:f+ + [V-*`Aq]$P.A`B[#AKaBBE=BgIg:sBC&Pmp4A.k_TD>La>*#hI!9^X5EVg^rC5>o$Zs(625 + M;0aY=P\@9HCKuM*dE$gmQtD)SD6R8_]F$Ys'QQ;r#MfN'a#h8%`,s$o,tE[=dU;NoI5TcDL_gQE!s?.=BMr@Z/;02gmbL)o5NpdbOd?3,j"JctasQjJ;a2$7M]Ks4kgV + )OS133lI0hU5U`PMaIK4@oE6-7qGR11M#[?.9:I'!P==>%XXrQ./Mre6D4ENV\p1S(*Ce() + -/Z"od#YTKa#m'U4Q3"K4n,9-rL;XM7.)CJnQK`R=X)A#r3>"!]VEW;3i)>cIROYbOoZ%U7G> + kH<.h`f$f.NGcb&(\-1_aLHlm]fIXE4;)^ZhJe0&W(+7B+-Ap)%=N\P3paH9+4b.JV,t8LR + ai8n9)M3JuK6UN + EHpjG$7fhHnogUk]<."'>b=92WkL%"i4QKF&b+9sW,PB3QU[$cTK9YW^?]#eI.J:p%:jjgs + 3@+BuB+L&)kFgToSq&u'Nak>`5O5'G^>GG;%sNbu`t]uEO-HeaT"B@8VAXDH\*b?Umek6Va + Jkl9H3K_Vs#?Eeq.KA8513Bc/WPD/Hrp$c;l4 + h`N=C6_&!V=[>B*K&,8?ZS'R7]rW5q:'5Lho"k]NCgJ4s%2N?eAFN#gHo:":J+:R<da^[(S%lk!6+f"qSU9(+bM* + \.g4=l&fq(uY? + ;NZ)&-pii"gDTkpd,+V-\@F4!:UMM)VG,;%=n.QJn!ZeBNKp:[g1X1E$lC^c:`*ds)Ef1nU + 7Bi3(=&G+p'_PMP(.r+;4c8(5!/RK:#Df(gRTAhEtuO=5'CRnG?dQOkEe']8hVjh\t]IW>\Yt?Y'dkDIb^fMBFd7eQ-'W + 054e.`7g718re+/N4m5lik\P=g7Yg2m-0V6tkdO.>Db=][7ZgO_0h]c)gWmjHI,1reFVQF_ + k>LOaJR/:TkuO$_?WPVSQA>KB"qYE>5m(VKMY-@85X>YA + 4==CDB@;(&Z2(rbF[se?E+(B7:*cIg)KLp]#eh%tBmRA\TdjVj*t72ho(Z"lN_"CV6Z>[8^ + P(X6]M=eUG@$095hjNWhsQLY,%K=D!1&0nc@CK&@%;`t#5-$36_0CnN+4J3a;\6&$r1#N#@ + tL)>PF2$EpEJrOjAiW8K@)2";?KA^$0#G-6bq'ci#)QW-0$[BK?-.YVB:.,^\]\"HRHZ-(W-(\_ + u(n#HoK"AB>s`hr=ZV#\6uSQDZBXD)#V,dI(oa\k8%66"8rD!r>,V8+40s6e,9a!#Con[\7 + YrC9ndOL"8G@fn1b#a,6CCbFtb+kQl:P.'`h\ZJIiFp64Eqi%KPu0JKPU,=5X+GhLX<&![o + CIa=[Q[3!6$["DCTWfO0c=GPpnFrsA+@f?A_'o)CAcIo*FuT[JVBO8AUmL-#VlLD,Q_' + tJUeU=a:a%`J-uIqnJGdn*YA[s!l8j&CcEOKLnsVCOQ(gY-57qPo+@ASCUbJW)'"N*-Z@)f + /U:U0eZ0Fre + TS9+Y#m0u3usUdA@&`32R]0VgMF?&RTD#7ro\HA?8##'c1:UW]X1lrr"]qNKu@Ei=DVRqrm + 8LB1&2fI@iq"MAi!S;j#sRFcY,-2*@?hAU=HHFmFrNSnf3HSODBJbQCKG_ohXC + 2U:*14=`8eW.4MFmrsK[`CbH5b0\ef$R&4oN`D*;:LRK7O+i0.nc7O[5))fFR:o7QaG"):fKn!5uST-79" + c'Pr2cHaMmQX(kh + hc*1FhL]+c%>IOLLE866EDf4A)2:,Xaf3rTA?3g:B?!^U/kBRk&`UmAA2rXa>h`(f>J%2'O(Q0&R`\`2*39E,%QFQ'D=_)e,s,ppg0k=Huq-d`8TD+g>9HECY + K?8Z0S5p=F]\l7aJI/RlNl6*X#U]B+C]9>VD#rE0MXX_74A3"i5/8'85>s5S+,BfJXEFTVt + $^!l5.%%L#-065]qH-tjKTU+hH]$NdjC22t$"!^N,!!Bt^mAPrKYp&0)Cg%J^TA)$^Ja)0W + +S_nkOGTH7*htUM2M<"ADq+t"^6r=S0%0JUK-4,f:Q=H8J+&L!TB#s%:^-td#Rpdl49^9I/d4Mm'*I0'&:r1t$ + :-X'"J*W3'Z9!"Lk1p4"'D6(!B`Gr?u-Na83d;CL`(c*1'sIa+*(bf&qWqt$itTC#lu#'L+ + pUn"9?`!:h.(^,rG2$2]>&HUNG$NVLuL=!@E`'A;P,,@S^EKoaDFAe5u<^jtf*@#* + JN07(&8]_i!-02G$P-St!8]`h;AH\Wi;L255)+uC>1oZ`QY#EKr<@anVYEi)+>"]0=/EOOP + QP/^l9A+A>jlBDU,(On<$&p\%7*1iq/nRdrjrOXbfGl.8Bh_0s2.jeYRU]rHc-cPM-N)_j' + 97'-=i38_,j:cIVKP^CZoEj=n")>PGu%T-4m/@2T$utP:V$EgBE\7+;uV?o6,u*$381&_/q + d'T'gnp2&#*#mMGX^47-M0V("3CK" + DN:!!t!V_mgV;p'5YC'?i#<:H*7C5b`!5r#?,\ttT?SPr,a;(,FAM9ba!`7L2bAh6T8Fi + .A\mKT#Fg\:2Gop['CH@g)IqNA^1n(ZUYR=fulL!m/bG<-UQ6%_m=';h5@XCL=RD%cYLj,Y + W[ccpfg,HIBC?\5H"#N+Bs5E/cl)?(C^NjKHG3\Q,l;u<=5GCS;6NY-q/>Pl6<$;NUFL57` + b;k,K,@]dLG,+(QB^e'V0N4Jl>950(iQ*fmdgn`j'3GI).OuANRqA$a%O/f078/\QgR.HC^2.<2@Yf$7S$r]4!l[ESAk&LZt&o+O"r8f + a;I`UAJo?IZGoqg=e9&6fnl4T-A9_`mV0oT,;qCFgs^=s:hrd6WomXNfc(T3cV9,%[]KG"F + 6KY>DSJL$/IA-C2ModiWRkh"s.cF__9uLW8k][EV"D*`di*-tl.n4p[-""1>)-!j%8Eeo5, + HdJY>o>n;lfS'YgqIJ>&-9p"mO:gC%D*4cQ^i7`9dt%V!ZieEo@j^`q?;oC2Eo6)>9hP?]pQ+VADhRYM + e>dBV6jN'db:a#ea5ZY4FM&9>9:G'mC-N4]o;`YIa)d-7D62hro%*ggk+rOamM\pjro)@F? + @>^79b?sEqfn4j + BjfJuad'hO)o?5:>eCXupa46uD@,5*Mlg+5?^MQo[\`G81^iq9SFnVZGJ^Bnr@L"248N]b@ + LrHdNhSA1E^!r>S@,lLu6_cVO(qcQQ?j[M?+fY\lU).P:1VH&tt7V)I#O"odHh3tZ38,c41 + 39L/#S[4sacFN!7B>!`\#1ii+rr>b-d1$H`n-m_3)VFGB!K\;S0HU>"+TcU/!jEmNn0nGN/ + H[[e")pC*:dG2f6NcgH"L(H#n4*X99a+?j"`R`NY[I]sAHhR9"u'_IXdIfRFU)=:g279'mK + hOl!8B2`VQKY"mILVnpB`;(hQ>qs0V&P)TaGb2$F$?6d$G+oPm_Q!$VA"gd(nBiK&H.=#S% + :%NY`-W,7SN2%;ma,mOJbsWoGesj).'@Wl-BI7-(L@%q<2R@a='&<94\tY"J!@mUYGl'X.$ + Dk-5i/$O5cHGj"TE&oNC9lPU?gOQak:'4Z4M6ZVG71cPk,l40c"ED[G1U?`;jlRQ$%McdDb + AF%EW(9;9k!Z]]FPSm` + /bij(@f>:)hasVCYnVTi:='"o1q'Tn]^U+#p!RsoE?r@;;f9/9Yq<=*i3KAZ0kW<@)L2Tp2 + Ts&cYW>2"SW([`K,8`&eJ8cLrPSn>ORNjmunqHH="gGqm68_[(4p0(>rHS12R+#Nd2b.uDD + 8=A&h\SFY'grnLH&dD'9eChcBAq5PW+^n'TScknPndPV%qB\3j,jUX'4iZB!uTulqqO7&U* + OrI-lsHmLJ+kY3-YLN>XP)mh))Yd@0OoV!p2(]()`2cOUs,[i82X5HE6Q0u3?QkeP+KG9f1U!FPT\KiD+LP::0+XeE + Ka#ohRF?:DEb'E$[+,j%sHOg-H6)o'nJX'i?H703'#GP93[Y),aBR;KXQh[4tRE2,a*+;\L + BQo-I7t.o]?k%9"n]XInK/@,HKT@!=ee%l;h[2.4'H]d3A-XYNgR.-90.-h'dK7a[D=oQZPG@)>d8--Y/rd6 + E>MQlmBanO(#_;5jV[&54gC6_1Z:Lgj$'g4)D;I:PP#jTJFQ:Q)n$1poDrAW"WWHd*>TY\c + SkCG,oq[f_,`-/1`mc<,p&:"*msKMPVXE23CA`u+>O6k&dq*GHUO^:75eEpFB82QAGL6oq6 + -J];DCX_CI[r7,IDL5js`f]]_g3/b1),90@M4hO(iWA8Q&sq1n.)-D+ZX_C9R@ZPcXoUIqs + (VD)-r@YRsOt'i24Sr;0>0EmJg+o,u^-Z;`?4IG:dkp0aUiE1(s8"[7PHm"]Y421Ug(Y + 8tf8Z<2.EglOTTebJMmkljgTXrpOQGsP;cL*=_e+Bdt(ZnM-:^_2 + &3)N'9?>e6X)'"3UWYHf]F16EG&PN`'0*#4>ketp"`q/K3hE268dupp$Wb5pK + pRi?e=YVA9iY\o7(oh]X.P+PQQFm#opAq5kZF3Qk8l_0-0LQJ[RLIEUUK2Gi+LbE0ussNjfC8oiEq.9+=rkEHqFZQQ^FPrH!09OLI*/fa3c9' + V$$EO>fCbpHarlJTR:P1H/0oq!oah?$3VX + .K%EpSBlkRsNAR'\0a]lTolCPj9.!>ctmYPJ*DDb?]>:4F+ + $qnUWh>g?t!gS$hR2=6"apo"*BKS?qHDp&ADUoma.a@.,1G=9W-!+eEcf@WW>*F;\R+oc@o + qJlXrT3WR%j6UQ)S+;d%K)A,A>5t%)/Tr"f$pd[bAd@tes,$7hhfNTRYGD8#&,M6c.AhAc2 + LPJ[GBXWsJ/AJ#@JOnKtLj#D)eCWOeLsqNn3CSZ5keWd:eI]!89([ArIsE*2EY!9J1O:Qm8rO*N7:]$Z[D!HZuJOg + 'h-)W?g)j,l8\OkSQmg=)3'ohd^uYG+aT)TSq)2m@Qh1g*+F3CU2*3L%,mjB$>&H!,?8GB_ + SmGWB4e3G5ZWLCu[/QQ&(#fm0q\LO&bfkh1Sm&P$&,X]rD>lEdU\F0D"BT=/s]RT$tiEH@S + Xdd=Bc\%/n0(Z1_tlpFppIWOUXRuondobt=6?s204p]#b(iIH(jSsTn9qThJbf>< + e171O.`9[0K,P&hcY8$C'8+k9KJ.bIpe.=o*lg6;Kc2UuS++L$?"]TdUY7kE)FM;$8SFK8V + #1ZmR"K+A?\kf6M"m0K=II#_WM_STVma.7)LlQaKr]O+X.2>j]KsH*7Do$n`&pW0r(Y-m9> + u9U`LKrI]OAeQe#V3;#=.8Y\FHdb!O*KeYM/`Q>_-nKn&Th2aKUT*RcgjoQ,oJ^<'5f:4J? + kEHZZoEXIQDV>76Xb[?K'2YnpupnF*Wm:!@g"Pq(O4RjF?Q?*`8=Z[CSRR;K;+F*W/tQa`[ + 6lQNY0VmSIaR(8?eYu.qM"?Lm@Hkjo7e%K$gcPN'pI2dM#3OcKn]fg^q6edO/\*pj^Vn$m= + ?sPo[PPLd#&%YWWSlc<1h,1dEm6Pt56+QX[RJFJg4[Sj.K%^UFek:XdFeNnI^%PU')s*fW+ + el7iB%U0t)tbF;q]hU/7s[SG(j"Cl9o$;WBf]=O>SX+U)5^X^VN:2Cp8u3!T8Hpqcoa+AXV + Zlm?/9m

&N&&G&3.>d);_IGoRGiOj + [!n:/Qo--;`-aPJH,I%B7<*QgG9iL)/A*Y[Tr"j8t6bBj]A]+5D+'pNc:Pl/Yb_S`YM?-O< + 5bu8?$].Pa>BsUrXZaZ"@>[S&-b>jGJBS&E=;,/I6\1OY_>CpY( + aIV&BqTgtf,-BYIh@>0);!M0fGI8qI>P\7fgYk%UGd6afOKYSZ_RJkg,o\;\l!/YJ#0dkgO + 'fnqIdYC_88[J&)q]7osSjgf:UZA'#%Xq@<*5U` + Uo?:S+Z/!T*^1he\.9K_]k8Q)t&,(on"U'Cm@1[Z_)Arj51a2B*r'DbACdd:M#g@0@*K<;: + m5NU?#I)]kVRnT^n=8Om_4Isf%ZapN&LI5fOkj7"t*LTU!5O#Q_I + 7C27UU&TlqSd3U>H_+E(!@>/PgY'7D"_I>7aGnYgMRf(HR@EfJ).:E>LhQ'I(qEi\-N!>OO + (c%Ydf']-PFJ;A"WQ$4$+[OalRR]I(L=HS:B^hbhW2U6_i+EFKAMQs&H/NlZO3FQF["5AaO + [NolLd3oT#1f6gjf(2*T$*(^Z8eu(^t' + WVq:5-Hi;hDC]/_$qL-)mJ(NlZq"klI+o_O-:^MMX^ed-&"@7!(HNV+)r#f=5:lHh0_Q^s2 + ?:'b,rsls9iI_Y/7H?jBQj8A%F9OM/G"k76*+7ZFP)oj\TEaKpYoRS*N-1$1,a + HABD,SpXCs!C=cCnE.K184\Oag/R]//N?6\6I/*"M0)K`RMp`QGq(g1sH\5gGThR9]1EG7B + (8ecI8l`4Vj6gpj^W/Tl30B<"/b1:t.dD6d\F&)+RP8XW'fsFGXR$dc^Q\9[]c61JX68l!d + efobC%qe?q_@h9.)@9hF7m=/OZ`HhJm=Oo'bAQ^p"r`5< + 8FeNI%2Pm:J(lm;[;K4Botl3DAR3>sgm9nIFBs)><)k%7Fl@P0GFe&>hUeUmI:"qp + nUFnirVM+QGSU[;i:nW2K4-`O(PcF$L)Ys-pm*Ff@ecNNMdol/2ihIZ#!%fUq&VndAGJ,^P + 3$sY=d6`-SCqE0Y[;>_He$a,jG2p%!H\UdqC4usZGQ8]FqnI(I#6Ve\UPS + 8(R`=mt`,T$X(WUO.LDe3Rr1>0KS*0f)bjuh+43hpFha>_3>mEmKFJf@"e=->C>LdlpB49D + 84f;P$FZm;8h"Q6;Heio%kC;H84stpAG^_l8jEZ2US)jDEB;+L75,Y=EHl4cI6YjDI(\&[ol<9(/,+J,LY;JFk-#Hprh$[kr-r=X]D=bTqZ#If!J$75"$egU2$W"=J + P\**^t9e4i04_'O@BSn3%QoE>T+^REXtI@[g-=IdtD\+&OL_6$6]]'KlFDK_T6'k`mcYr@n + IZWG\IXrZoWqP/;HUfI1Sq`#9o9EM%TNE7j:MG,D;kY7/3;e8Qe*3K\e2BP&='id6/l]$83 + *fOVhR/<@Uk31.H;c=N_"XND[,B(5UmPMfa"m1!mItZLV;]_0%E+-9fKA#%1sRM_Ph[65ZI + CA'B4aObRI;RBnt#9f]#4P"aRK)(oad`0`L'nDHmk3cUu.`As*Gj[s$OFO#Ha3qBZF9&md4 + cP3[NLRdqYF5VH-be$&/o_kEHIkX\=_a(.I@j&mRKTLKj6IRe#Tuis_VMZKaF&`jI\Zh!ha + tH4?FWMd<^lG<*6(Ec@,L1Cr3YIlXSWg4!V4$8-dh6#ZBn\;hC%.T>dVGJ3brF&AoU]_/GY + b[]5H!$<6<$4W;"QJ3W4F->k`V0.<)lXiFYQ>7r>S_cF]?cML')dbq$=M/Frbe@FiZ6!dP+]@;#k;Hp7rW>.1b<8+$ElBP,EPt%e + (3iuFt`ETA8G-+d0\QA)3@B>`GZW9-5=_gHAX];g:f)"').s5j7Q-R!C.e-e[c$-?kp:`1l + o%r.BK9WqJ"RUU1f&d9:5K[YXd-0"Q;Y]VHQ?uTICan$p&/+Xq#TD)#e632j$2`ZEaZl0K(h1b>hD7G@, + 9:c=&p$kGYN?)4>=C/\fKW%EHiYgj+pP6+l7Ri66\-!]oarA7'#8\$!^BB;*HMfGccEd`*4 + V`oV+eFM\q=VArSO?!ap9+Jn&*pNNpF/i:>tMY`Wmc3*&2/kknfK)2'\!(rg"K'P)1iP*,O + (bSgd5C*]r3P1"5B@ehcr,m9j[P'GtrGss2;dE\"i8leR^J9DFXZ^*Tco43,gI&5ss5'$NJ + 9FRMD16f\DkUS/WR?iMrh`iH5q``JDFJ'79Qa>k=gZd^;i5?d23g@1<dM_70.ShV=TT]@\&1\p%),&F\K3DYnLfD;Oi + lG$fT7?1DcKIBMIn[sr>0s1`d`"5mE#JFJ!nlo3N(hY]0W\fKbBpm2TPQN)k-c7a2*/]-P< + DWb9JT?)9_,J%-Gkjj),b&4[Sq#$A&ZB(3l#Q4`"H'. + 60^0:,a?dDF75;^%h?hAW$CZ\T!l=S0oopMh!k/kW)rK_Go)9F)c=kfLFApBmRLH^(G\Y&' + 1JbYH+j!RP5TsQS*TaD=n;"(*RI[(REIof<@T;m;_+CV(][XNf;_.6:9YXKG + eb&%ulT0CjC&YXAPnA+RV;JHM0&taEp\mDSrP/g@5SO2`*!8GW"@+495Wf4c9Emb0#=+4P^ + mkZqU^9;f$p_H75dV"dbR-9rF[6>M[pn8].K)6,M78]`5spFDCPP3&L;A_q3&?6(X;XC:KL + (8-6'NBUl[(p&'g`/1Q4X]hRMm26O"m4QkS6J<;;URA + Zm*')M'3QgtI>VeD41cb`4\dP5cX/!RZUE\^t6^+E/BVR6o0X*As3gckWZp-UeYe&7C3qKM + m.grrs$D[b4].h)V"19CA[BU?77%:YIN(Z6b(.?;;]=^m#YgpAG)S=EpZ,0C$$QZ'B+#45B + %a-'/;"-S\oK8$Y4G+unWaNK7.D3RC`S2r@;_C3@d(L]54Y[U5;q*s71VJ0I4``QgF5&jO, + e_-14k&7O"$O@)1WINq4t/Ft.6,%8E&:1>`u@Jr>=Z&bF>SpA1s>6ae*RkXmloe#[1mp93E + X$7PeTN(Bb(8A' + r'b`56lO%Bb?aW-C6,`tYLPY!rG5m.ckBtjb%`'R[eadN+E"

\sbD<`<=OeCf.I];`k1^4=o%-;^' + D%=%.dFc&GIe]N[hA@PXJ/c,AGNm,(80i'nF>-G.aP1ucMIDba8C`c?,iKP/T$>S>"]::oD + I'^W10D:T,5cLXES.6P=TE_dWDcRVOeBh(@n9$^F0:Ln],m.[InHI%Osc_?[pdo3YJIa?0' + cfG&(`.Ce]SPU!s:`K>aD%BJQGLqfc=-\#3SU4G?Hrm.m=1s%6c$iamIom//fH#KE-%T/MK + GjEJ;(N;V4V^jHLXB[i68]k4onu]7N)=^<;9b+u`JiH_MV.li3/o(`RbubOPYs9r;G=.ED3 + e(9.a$PM=e%':"Y%m-S4akS90Jjj,qcaq0A6jhbDi5p*4\_E1J=N(g0?\C"ZB$7^XIKcduC + A+SARDD4'Kcgb[!?C,7Q^s7RV@>UbtE5>Y7s96H$Q)gK`'/CsLOA\.-,A]DU,WMo!A,8lW/ + t`GJ9pX\DNT:6/r>eF2W>),m:fN*]?1sKc9[G*P:Cu*6ta2MuJoCQ)ni)^q>a8CKao + lIm=Fl#dgf5pmm2<^gPccj3=Xr?4%Js93r)k'36_2`5^O + 0Yg`G9gFUKO6qYD:6VPt?<)pD,>gZc3h:nL49e=du`)YRB^>%XO@[eWJ"\lt<_H\,!-8!/1 + e\td);q4mggl9G+Fim^CAmD[pgr3JmL!NM`hC"T9*CUf%&SWh"ojnIFf3R=!ciAS`AQ]D-'_[8587((/J9i:Z"',jW760/l?1<;I + r$lAPQ9;7TX/-=64=%AXs.b7J*Z5p"sWiTUl6smJ*=Y!SKA>?j6R;hIC.MA7d0"Aipc8(1B + K]?f4+^/fk2LG$X7<-s>L\*DD\gbdFd^4Nq7ML^d&(iZl+q5)WlE\j\3^ + kh[Z9SgW:J=&dDq`JuJ.[D>iAPU7)M@cUiXc,EE>0G)5Y>_M(kQ8:[?jPrtXeYY72L,[DCc + s6ciuAT"Ad(]PF[_lb:&Jm_1qE>@kC^d6;bPp(iaI2nUDErY8VDq)=c#lERu0g^;0(I_.Cp;m,i*M59k!q`Fa + n&CpDD-HDbT5XDK@ + ugY#VZLTk4?bhncMn&Xm:AdFAmKsYC6dJu>^,\P$Yn-fnC7>*f!',e9(<4(E?0,j1kXlP*d + 8,8pE(IN06)1pEd'`jG>EC+7sB/aGP?$#GFT(]ru`9d+`cH`pY+bM^OL8l-#C=FGTVK]/=P + 5+Jo[cAGYS$Z#:t+JM"g'bpXTOc#E(T$0^TmKi0J?R4po`Y2/Qgpo58\lJ!4`A3FNEPF/I` + 6]r'aPhG\;,oAm].s-m[!61->+mQe#m + i)osVJBY?L8#YPl&_EOaim\"!&fmt1SuXVUh059#EejmuN#c?oSde*(lh]e6n>ZeL!']=l1 + He%=W/^Xao"2l"^`p3Q\oKn@GHnbN*sT7=?3ri]H's0(JV=?nPX?NEEr)-g78n4XW%6'-I7 + #bU3m>m\BPUb*"m&DSa9'o2]M&4>sr8J/n]q,UM7&QD[D`R/L1'bs/7ru&h]TiW4(;+,UJa + -$:J+NLc-JKdW_/2V#=-Y#4Ujg+Zs=85;,hBU8((@Z.7hmoSLm + Oa'\tE;do?!#:XiAR8PiC:*D]S`'jXg?eMVbX=B3n2Bj'$;Q#U!Z28+,j'Ihq.8sIUH7V#Cta"4q32'\6]c%@9@CjLZ2EZ.>Zo+Ftn1#=&q8=j-]g/C + >0UA8SOZ;L/)IE3F5RKj]Qb0Qf_G2:2+=N\c8.33PhM.B0#[4TBKGgM4eP9*fap'-iTC.Bm + OZjVs7H/WMo*nSucU>HI;bqCYIXRChpWpTsALh*mq:\3t\'"TDNN`[r*.VjgskA^?N:J`c8 + )0n!j%J^[PJ=$,CN8*t2cM]M\^KESO]t`ptHp-cqgID_>Yn4Lpm*F3";Bcu]-r8^[1Nq3Ga + f*EQ1ip"aOpeok>K<+Qt5b1oB97=_Y]Lm2'N1efSK\gTu;ls1g/J1n + 06.n,X'@H-7+0l_Xq)5Ai%N6GceApQ9P"%=-Q,\o9"][`K"jD6'/\MhI[hT(h/DiQ9`3(G; + \*78G@,T5#kZ5Q+O6a7i\oSks/4sUQg4]dKS)bBdE + kRbg)Eug/1K*lT1ldnsD7kAC5+\8#D7LX&G=h:>3BEVc<>m[FW/jWZu8Wc(:]=\s2&d@s4i + RlDQY.&QIO;<,hM1_RT=YinT0LX3OVH\[O:qh(,5$eX!R'HHPS[V/T^B#H4[qdA4L&k[=q#Cgd9G'$t4Rr-'6h!6[OJ`DBq8uf,U\&k^ + ZXl'LfROE[8SMUfm,];(_NKlU=@B']RVE0@tTh?5!ZU0-jr6']T(@\bc2%B!k)BO0.9;/H\f3BPp338'2N"-*l?9#(/af]?I8s:TAR)9^^9(EFAE@SGRb"m([+9QRY3RraXj57"C[1 + !K[Ni_nq\m+Kf:0(Hja_Z##/XJ$WF@D9M0p'no0-73[d8IuI9'Ck=c4kD?e(dTS"FaeX%U% + k(@8EH)gFZl>J/nt+G=pYHs:8]^?s,p\"!BS'5NAB/d97M0_XSsLjotRA^?0dB';XpUNCCT + 9Sun\.2Q;JGIZbjMV!k@hL7m103.8MAnj0ZmT"XQV=VFkc^1+lms_J'28@cDilE)E+Z\V?^ + teqn*IJZUY\"D.I3/ef?BTXh[nXaQFT;"O7F][J+dCW:;W:h`er[m#Q9BX-ip9f"d;C\*=` + Z.fN47MfB5?njX13?0j4jHtFmbgX\bD]_hRggTn(m`RGL$HMr#^LZSfbjpT*sZAU;4to5'r + %WR@*oLM"9&f1\mQhaKt#Zce`lmH2g=r]dKNG^5p`hT2FhE+.Mk.=$uj,@7i@n1%r1O881; + -d'Uj,df08?[7rYY>7F3Rru4f"9i(?ZLlMoY(#E/fK03)C\lei + s!GeagF$S3Rea8_Fb&1($i"RGjg@"uos,N`r)lhg\MkW/Ia-)jX:>_dFi%>#=Xou6g2W01+i5g)P%M[m&QJrAr"N%OW:*tg8.P38-"j!FuCU + h"e4M2.h]M$KS!ug7)Fu(aKN=4VL#i%:VL;(r@NiltQ/,KW?JEIDa3A,W>X:XR:''N/lk`D + t2_ofhKPm`NtP-1=lnA<(1r9B5#5YeEj%^>8X9b!,u`GW?T:u]ITB^F%ZnIHKId.7bZpC?] + i%oE38OT,kh!t^Rq&CC^onI#p@(D0L`&MY@>OWb=a,8,sHn]r?l"cDk:%ef9(p2;Stmpou_ + ?PJQ+o^P]L;,Hq*<>CWp("$(tnQKF@>8E2)'N&c_04>NtChJfM(XI:!8oXP9I/#FPCFqUc$ + B!=dNu<\tq5m@/n!Itf$CqZ;8X@Y6Mp5j*3.ZhMWi_*f)4]grC + rZ):YOM3V#E%2g<1&^&b=\]p9H@FZE;(g%ESSZd/Zfu!0RiJa;G!a1>YhC+j_.8n0%hl2$- + aP,2Zk`q"-*Tg_Mog;6R7aa\Ze]bc6Xc0VMV7=im;#kla5\kB]H=3jc"#,oD0 + [g1['+6NN%l$i3bL&+$!!&X+D\nBN19km7'/LF:9,=VX(,J/j'13O67l1lN'luGo&T'<9EA + L<&19l#MOae3(AJalr(isU4XYgtLL252'2p[T8;5#sHV)X4g)R#nr&"spO@/A?@q_>b8&%= + Ss`,kIor%kG,Z.0jlgH1;a*V7"L/Y>.&kl'u5*pXI].iSEWptO'/Z>]O]/]SuTk5\Bl!7DL + f/`MBQ*`LRujt`%5Eg&sfE0'RYOA:-P18-18MWh@N$e0XiEk:N"1 + FqfN/2!tSFH\8[f.=ir/Pt5fdb.5\AIan?9%+_6Pd=!S[7_4C9@URI'Y-n:^J&ae9U*jseC + .*uf1ct49iThFdiU>"0f!f`VK6*?%2UmDpGK3A1R.nRaoaK.#;fEe1t<(?P>b:8!*YWa23f + 7"<:`!W*>fLX2JQe]drSGf124(?2fjHdZ[cmiUK!G3q9tNGduJD]QMRVr3RaYu0ul=tAK91 + &i?TOrCaq + m36WS5`d?<'Fc:O&Hboo,LdZ!("3:`9dMVH"]R**1c%L>@7.Cf-B:;;keec8*ourd-aZ_un + :Kbd=[gbs7M:tp&t$ITSOW+H7kTCUP^($oI4KDj$hT3F:rt6Wbq(Wl/#tN%^ + b1hp/=]:=(N.MAQE#I(11ZS?"TC4B!u>aeoPI>G@pPqB3'DM<96kn*Y%&j'9t.`'^mg.QVq + rH:R*);d77&WVbpKpo2Q68"jq)9WDStmo6-mD(;ghERq[q#Bf.P\%3\Z6EG]hU;pU;"P&+F[DN)(aL@GZ5tGWo'H^V&,p`+3" + SL_Zp#RM8kpBEt6hTt;2>'@Z9mMQ@?kcUe('s%]V[2jS7PL/X2AHPL)[\?o7ogc_\&0]`Ns + g+_I+C'j/u:*-4_(jaA+Irp'V,^-k3M_5lr)_Rp[`$aok50=J!^1bGQWHJ"b7EpJCk6\(^Y + aq%tSs=JX"u(p9DO>o5f(Eqr;/^XTLs`[-dCY6:$!);?QSs9BRbTcls49U\\C,pE8Nuq(FS2u#(KM^r>9\8u5`dL>FiP_nD + *_iT,]4'8993ADh'G"2-"=tBYZjIrLq@klOiX^1bK"mDcX56"7,pLq&qbKcLIJ\N^:"PADJ + NZ[CLMFtHe'1Q/r(pB-%%-f@_#f/(,;+SM1&?_I@5!^pYADA6%1^^8e/`)R=f&"WpKL"R+N + L3foqg!Q'iMVRG"'u)4?prT:1&#R[L@Jfs@-P$\C>`T,9#/fPYGk0q=VCdTASc(gDYReV0F + CS_;bh);1Tahk#,@T$q2(=nA/eH(7uR;gH?;2*Yt>#5ItAT[rLd3/NH4O&_7?Dssr3'k)'k + S?6uGU=c,^[<%4L)2afbUXaoXG:/;n];1Y>N#9N:GfOmXc#aajND7r_\Ce/JhfUf_5Lm;02 + 8*6(q0#XnO#h`Df_LSpF!R%$"EYr6bFOaC=XD`9??A&2[I97R,:=:lN&`D6P'qWo20r/YPM + &6K&sl(PD!,s\@I+LpEilH?[4/+ZS&LS)\\k':2hLkMT6id\=mqn94e=[u[+e6])=+\ + 7:DWgtZrghF)>1>\.A+4fZ^Yg)2W9FYGA_^;L`-jDG_NY2'lT@?U$ZEJH6o;TQIi]$E=]sE + ;n&NkV]FJUEY7Dk + dnXd$]C)A"%!k1GI?12qu`'0SToISGd(b(g_6]VXX@;W7(Tdi>.Ki_$^:(BHM/#RGtY6?$^ + D\nPu+T1>1]%nh8G.'XuU,c]'/F*m4>%NY;SbOGNjS0Ks1@uZCGdP)^iZgDoZ+0a..Y"I"T + JpKub6haP;]q'(rQb/m.qcTA6$9Rq'TFB:S\=b1g4Ek&If!\$\?/[;R_A*5etkh`j3UFZOFb27n"bG9ZqIVq5XDHh1M;s1`l;iHgR#)%45`R + (Bl#j#;mr>S8][(LM8UP1Q1KoM`UO9rkoT^CZO/3CUhE\&4QE_orRl4@@h-7srlF/*kY8tB]?,OZrEQ5YPaIUZ4h:'J/e_M7th;-iB*SK:ThqieAhOX+cI$_s/Mojc%hqZ + a!SBKhqa\pl#b3Loc!2Zik]s1+,1\bu]C*=9'nZ])J?`^ + pX@_o>CAr#]h(J6n^STEO1eL@)MtEHNKAIM,oW+p49sH_0rd5U-CoFFDFrIX*S<7PX)`mT\ + N?kI/L^R]HTd^sP+UG$*SNMUOq*Vm]&262_EO8E`DhuST+[D0_(?X:8fbs$q2*tc-f2Tgh] + FH@T.feC1\74noA;Lus#]ip8+]46hd,qX@,5MBLZ8:6ik''e?Xr#c-Q!mHiP$K/qa^@0AbM + a@VOq)f+'tle2<-c@j.D@2IQu+[J)l*K)W"/-gQ1GYpZ6f"qSt8Ns0AdkAYXYak'O6.*cKG + TF6@n\dMFdQnb_N5/%f.@khF5.?BL6?Z)4HYe/)9Q$Yd=LooC+h^;9lc@mAmJ8/r@n`IGa5 + E2[;2*>+%Ef??b_<\G4q0]MWDgibt*?H4=0q(/(j-Bt]FEnOCiGq#-)-<"'C_1O_<0%^^1j + SF3fThoDQq=PVL7U8okO8<-CRnp"\QRh7tV@2m4"ZW?Bs_(1n+FAWhe + oqVmFOe?g@9<8Xd<,$mIKt]^u3er/1qBIK#GFoCK8A1+-+EQnk2Z*=oXjm/b;!HhIUPsd4F + oN8,euTV:)JfqSf&2A['iPp,c!;a_A-;XB'9i'*XQcW=1?i<0 + g_lq`pp,d\fEkVM1nG#+P?RMJcs&Q26?I)ufiU?Sh'oL'3fC/G2IfEd,q,UM1n-f'6kM7G'?%.D/5#K_XY*7%m'eWA1B + b`Obd/#.1/.:=dl'o6FOrIrZ\7L%::#oJ3_pEro0,rko^tIFZjct#:p_MZ6.%MH'gto@FW3 + kKPa*=^_hAC^GQ8H.2bm;Yofl8O@rUQtRKgn6;cOPq;cZn@EW0Y$oo3B_O=F^/! + jom:(JQ5+mXfERJFJ(XHhO_%\(AY3Pf2Kbj0>'@aH2KFBnQ? + c$!ANeVeZSQ^*2KhD_;"HWQJLm/(^Zmei4'O5JmfKS'8Bo7H*2YUpl6MS@^r9gM + ;pS(22l$ip+U+ta8SGgjB3g*P@Za,=-R9aR?V1n0UX7`#nnA'PHs+EKS93*.K3QR/F-?J+DC:S^$c'5<7' + ]h$5oLFIb/dde*f_/j*l[(h9Y[Mjll2U5M-:boBQsVMmnpMhqa7Z))Dr:8;5B2b[rhqLYp + \]MSJJ3P&"Gm],0`ahm*e=HK"Hjs#"%dbhJ`OBJ5lt[&8[K6\6L^M]U4EPA*$-N)*YB*LTa + =>]-7U;uX?s5"=.sVBL#o8A_^K2&$]I"sAa$bDU@M\8nUE7?"N!PO)IeY&Obj9?,akD[A^' + dB1d]R9`?S,mU9%ER-FPKEo5,:G;R/b^8LYf.jKt29&lMFqGrI'\jI_Xa + L/,lO5q+)oV3M[b*nS$TtU:D + N=TkHrZF$n?ZZg%RGHSD%K;m,f)"?1'gH\\"iRr$LufV + I7rqZVbW6Z0khQtLFYuV:]*bM'SW-]pDZ/GBR^hfm6d0gUI^)#5UKLomdsC\`m8g-ppT&B" + I+?(*<$g:iV@qYG3$Q.G`#uT4A**f<[ + O6%S7c^r<4NTE/DRR5(!oU^'K.p=8_AGKD@-f= + qE39kkfOkV$]J#%ZefM_rI3HgKQ&0K9URBU*'i5)H4sm*"R^:0T:J'`/ELo).MOo!$\^umT + 7Qt%Diuqo[M2lU76WX!"-"?4DP@sSb`H2<;:hgZ/Ei)p3<&TPD]X=)>N>J=7Kq)#Aa[]MT+ + )'/]P>Y*)Rp)pDc4m`Id^I+fl'#n[Pi"W:RV>O'ofT-/q8-)/#>gpj6`ANNUZe_Bk%\0sWO + 4r]eL+3nlEsWlqI6VXXZ<98"_#mqK2GYFbk/;!gT?p@m(OCppoB^aY*J$M=++dhT<3LEqW> + V0WsN5d0#&Q+(l[fC+(J\QOX/LM#jf&KAdQ/;K/RTW=lP9N8k.F>G)"F3U:%II4fX55J?W5 + 5a_U:.34=P'g6YHN&#'fNh=2E@D_i]Q@SZX,S)hg_"$^g#*Qm:P@+T?W]lYIsdu8KSlJ6N@ + G;sa"3`gZb7m8>FD_Dp>kCW@smPfKg(E\_tcM5J&$%5Wn,/cUA&9>K^b;iWUjrD.(EpdYcH + *9$dguk2K^2M>Cf08Afs.ONUM9n:B6g4IbUi`uQM(pqb<&gX#AKIi#fOdhC(r$ktNk(9umR + QU!LTkqc^Y*rMB'c(ZH/u]IgNp;)mJU%?ANo@D]<59(fmbVuXM*KDN>B`@JrTOd0q(3^+O- + K*2>FaYSG[PNnep#GkA9Z_ZfdXPIV1n@XT_M-2kP'C-dcV=Qg&UXOS'*-8p\0(lZC=VX+&) + aq^k%2oaPF)g7GY%^Nlh:?EMbccf&X8o;q9&Ib,Lu?2V;=s0_UIYgt125^eXbG!RP0Q;S'+ + m@abKnN#h;7CMp9kHrU>E;mPk\[O/n/\@_rm]Yh\[qn==>K;&./b/jM>2csEj,EOX^ + `k<+0Ta!0NVE + .Bc_,M2]<#6DG=.F0)u`ZpN6L4%q9Dgb2"T0>eJX]3HmS1Uf]P.D(g%]>QV>]JmIEM*(=C_kXq + $qnU+e_C3VkRG5Wc1H.F+`C'gJ]Sjh:UW:$Bah55K]YV*?gde^R6,\u5.N:c=,YPLSdje#? + [#@b[S56hG:pe^*73gKmj4m'jgT)RT[4C-'F&b)+n/h2[[>S-N1g@Agj<(/Z2<8)P"6d`$@ + 7Ck5`ZU@,]#[(`b!,.Z/B__A'PG0)&jJ3e[a;$4oCX\.p-3qAXc)uubB:9dgc'$I2h8h2in + qNAi$)ZZ2rk"6N!%+PklMTF#"aOH6kjH%C;ON@'V8Hb0t1m5sKP"AUk6dLE283GBm]^%mPX$b'_78&il`61YDZq\4r]U]G\['W[nE + S*,?;ToI)78)F[/69`"oKFd/^,0+als<-A?d-lR7MG&F/C1aca>Qt/^< + 4U<@qWesmtofhc!h2A`+_]Ao=m*RTknPI6uG5@)+O7;`rb!'r85O/E^/"/N,*dc0Zh@?!4V + <=2tH)r1jM>bK/b]73*baY<.7*,2F+X!Y8`?i*:LDcp!4>)3*s3MJ\cm8Zg10Ve9QTBee8p(?:7&81_UUmh,_S$;u + e=Y++Thrt+6KFk;[0e:h#uMD4;&om/N2Fui.a"9[_lXZcq_1XK0Z2DAZTT559Wq(LYHq35; + <1)#N3YRAZp\Fo93!1+"Lfi`\%MXM;IRU&D'tjT:/[;+709F2r@=FL_:$h2bUkHT%)b"c`D + [Jk4p5(g1:kHCahCHl9Z-##gs*h?`(]K+7P)nU1so#KdQZI9;rYtYPOM:^n;7q3c&?Bh'\Z + 9fEAnTe5CD;3lh\luEBhB^e>./pUuTKhjK:&qa*W<]I-2^8:sP;NNJ-Rebu:d_Lec2pcJ%b + Y",f\b=NDcXa=-f(9j<[coXQ4\YI.]`/HO3]?e,s"eg4^)2.>'!@b*tZel?>59kW$gBNB7h + etm*3ZOY>V"Yhh]ac."^2/el$#gjr(LI + *%Wi;/Ld0AJeMP4NbPsb1.EXc%FE\P#:SMdIFG="@Y^q+Z&IUfV?_i6qPR],uSQiW^KQZN4 + i,Z^!`n.;Su0s*)fbj;c<8*;Z'Qh9A\,9[aQNY`MCUS9ZjcU?.3qebe%31NM!:(3RbC0e&1 + NNe7@%pe"Eg-`e?\-,hZ-Hd*tKQ5@W0fbP,2XF2mT<5HF[rc*,Dg??9!"hmMJ^K^*?cRp97-,J-]Lij2h2Bq-M,%=Rtia*ni + AgcS].,o+nieAplQ7Cn4pg"'MVF>B"?/8fS0Ms^_?*5sC-8XJd1ugJO@n&>nA_po43+J63h + >TIqV1@DKJC0p>;72#.7HS+`6=,A#9*L*o/aLh(bL/97dQNOqjX!=&Nb",Yq=7o1 + SJ7=Y&--7);d_p=W[*?\Olr*Vqr/RQ`$ebb^ikFoL:c3+\:]XBG\`,O8MbUfopWjlN>YFp: + EiVr3Q5@%LicXpX++X4l$Dg>m5,oh*sB91M]M^]h%s4g\Wl[GbE"UCJ)KNc1[e]&s\b31TF + "28)9B]q`hUB4=)1$]ea*Ku$M8ijO0rVL.=:I-7qdB?#A_p3u30IV9LHkT59<-A0Bo/`P_c + ")'ZI4lAqe39(K/k`LkbDN!OeN8o)?B\n^rk)/B"OQ`5MA%%2-/3;W/4C137hG-8I7R5gJR + ,fS[fNppC%RaX37Hr@ijFrQ2`UFk59%&/]]hH.Neb!D6/EI(3fiCsbF55Z)Zrr-_j[@-r[) + h?Gr+aJ7?fnN!c<9'%l0B(YA]&'E2I5W;V*="<2P(,]Z4YfN@P'-Hi2*HnedLf`A\XfG^U= + X1m,9PA&E]MCeCG9lmVGl%'\d48iF4P.FF30S\_FMl>K1aC^3+'Zo\n;OkBmnO#1)'N?YD@ + FkUtat@ + iU-p3mM4Sm2W&B=iFC/thDXZHVO$Ia5OR*jBr1%]p-\)PlXWSsl.(Z\p7>/WT2K!fmq/7qr + _.6^VeS(>C.0[\?D]3t;G9q9b-M!3Fns:isV^q>1-uHuZ_K'8VIkU5 + HQ#9Xbo-Z9a2Wf'F1@n!no2di^a"VCd38Ol)DP!/.AV0?qR;UlKBqFeS*h-8dnamUgFim+,Cd2RpbCNECD + F^6r>Fukt*ZAZE*VS?3G3f`PDB3p,s(@A=E]8==c[5Q`_#+GRqu=P2(W1VJ_u*TSr+ErqF< + 5Y`IWt9KpnPY"J'7VI4*X9?pg;-Z,^_qq`ec(+Fl#/ + YP8eE#6fcHerWF+aqh6b^Z#hRN(tr>N("iS"dEmd*03X$&iff3?Eo"%jYDO9_G`(QbN;_-'3=:\tli`i + HH"C=]FSY2*s)>:rtRS]9o<]3]Ba]>a@6IDB4Z=6q7=$\Q?+E>Z>P2\h@mA+EbBVF)r!bN3 + d_][h9_T?_++g)VN$9qp-OFZOqO[ooobluh&7L1r-j*Ldu#07 + C7jtm!rtSUBrqBNn2(^\_,+q]#G:.#%gbQVS1P)c&>V/k_f3g[&&Zd'3t]CC,Xi?b&AarRT + CH/j(.HW0@2[&SXCW>?Oh>fVa-";g*QR^:JK`*4/;#1gP!k9Ta:U8"'Zol-V'sKLKWQ]?n8 + oNR92b*i!_a.F`<+7;.B*ab9hKOe1C-&5s_((lU8fk:8QB:7=nU1;o#XefgeQGoS.>B]92DHl7? + _f;'q(!CW&f1GsNR?q46G$W^ZiHa8RXlXmHL0=P(hfmWHJ)AlfqgmZ1q1`HgfL?C)i5SM2L + R<=0,D]f2EJ9<\#Q?B9)&VKu@:QhP5E;*k-;8pM\OKAZjb>rrQP_`;>[SDhrb7j6\\<3VkF + '>aNM2+j"aNaa;- + Mr3nE*o3B[^?6B+F6dTp\01jXbI7\A(F_C_X>F0b^A(Ys1Y@q,V8,mSbG9sM8h08hlJ`6B1 + &Sp;!kPp]KQQB4tk'>gETNZWTU&XEoKtjhYHTnuWmsG;2^[WEG-dD2ZBGkIU+TH0cpRs!Jj + G+sN^ZpdWaI5Y^,erO?8tZ29-4gTR^d<9Ai&h;n`?_4CW#ANjR8AK37LBjX^/mr1(kY8C%N + _Q2HXQ[;KJ8Dt6:o5!pLg:AHHnC'ac5AuO@h%Cm3/5fB*^dGc%\a(hLbSfO6s, + 9XiZ(OGd4-D0\=F:BKM78#4t4Gf:4*S7qMS3er.`#FJ!3K9N(K$aTN&H>dbJP&8Q@<2f\%4 + l]PrkX`]?UH<0H:Y8rlP!4NGP9*2E^[O>5HTaB8Y';Y4^`&g];&8KB$dJo"0P:.ZeNq'FMf + Qr"E1:.^]:Uf<#@;=]`D8kDFE<77+u;Pd8`eO(jP/?r03``al!Coc[7\Q-\#g5M;"qB5IO!krV1bGP#QDW0#P?4JQC5K + @<]g^ZX?>QucZr+imgm`q6U^b[W;G'X>ksKor/cEL`f*LC#K8uf]17C3qCujC;RAWf#S[rH + 4:T=MqFmcNc4$&>fW]R\6[4UB#)P'f5;okt78a"W-W:XE6eU"E>Y!)l"pZhY;I2.XF5'P0H + .m*6KcbtJJg>)YmKDC!"7K(h>qd#%aH%6G;R+Kt!:S:YmYQWYa=BHRG+KQ5+[O)C7fJ@#q2 + 3BGp;T_FD/JW*@Q*WATburd]BC+HJZdt#\>!#GmD+18.oASl;'\$ + oDlhHQIqH6U:4\5s.n7KhNnDENu=[k0>U]:C.@9SSXl*pMKbV024&fXf[HmTmHD`2.kXs>o8T?@TgO(k5+V<\?9nuCgB)hAEfYk_\qK8 + 2*E=_pn#st*RDk%5FH?gAlKEf:je0f-uMVM=Xo2(#,@C9h%e(,A['R:dmp^]B;@P*O>b6W8 + SML>V+pX!k#uDcl'?%'mT2;FH?W+ujZdl&#fbH?e5b:f'$X,OF[^b`>->ZVHOU)MI>Q + NU/dke[D$HZG*O7U#?tAgQck62Xs*ML@@\%pf_8"e + Y.`eef:-kcO]"t7EsuO-H/+5ShAf$Mn/cI`Z5R/@nGR<0u$<#I4lU:N.'1G#",%?#:"@p$e + =fT=eVC#V,7)`6tiK,au!b:2B9&gkj9S]=`_"4k3JBj#%OL#b*Q^VeX@qh18c#!3-X07jn# + C$c?0JgB8lR_C-0&t:A7$Kg7b2iNE=Lg-EQFU+'34gRF7[F8_.0#>5\YodtBq^C$e'^ht;5 + E2T:;r + ,7)Is6o%IcifL!')bVUr=0t/n.bs3jPnH4!Uq(nXt[W[.-%N_^lCJ:$kWS\1r589_@Su!mS + q&B7Tdr="Qc$50N@0EC;\/pk0-0T/5YN4G6Vr4`NFgu"pF.(f#B>##Nd)!DNnVUR`CMIl3r + .K/uIo'B'Tr.aR"`KE2:U9]#hshb&55cCoZj%2!lpBm1.eX0&5k!kjFPVE";1%&Cg.9o^@P + bn*K%TcN_[u"EdKIFmfn2kA'O'rpe59Gg-TBXUu+ZYgI*fo*;eR9#B#[37>stdgFb"/N/&r + 8CR)[Ia:k)/P<3;"P#/1p((]aD-muj*7f%MpCDT%%t#pFFSi=Jr"3950k1u=@2?+Z(=?h3n + T>X&Go%'@(NFp@Z%3a3Kc%(I)"EG#&W@h#`r**35%)/7@.,!gV]&F3)Y'U2nZs0Bmi,-9)j + .fR0tA58WuSCd*:^-O1!L_9d2d5_*O4?=;84/ljW:\C*qAJ:&a^KEaWHZC$k`@Z&cWt/#p= + 6t+ArdoZ4eAO'd9k#+n?En1*%Q:+X1qY,-ipHEVC\tocpa5,OtLL&_SFLh^"@R,dL8g1-Hk + F5:#f*-$!E>&n;uU`>c*t$hA3`12SCZ=W_%b-F/(1nq/?2JLIgX.+3r#nrsNt_^JEP.?^5O + 18-11:&VhdkNV!)` + &-j1K-Rq1IX+k7l3"U$WAW7!:MdDB/d/3$Quqho78 + l"K/dpt2cF3GF-S_kR5gD$3HLfQZ_9?dYR=k-3jZ"N^p?+$\N!O"4*/(t1U0&`^HAj*$WGA + 71W_^7fF&A?4k%qEoAr+XXpZB@5-s]A^oom7pHJ3E5Ekb*1\3kQn-[Y35euhS'G3PD)*m,b + 5r9Vie0@;%+[WOT5]e;=Znk'k.79=W6aS6KTWSds77oi5!L=]H2B=S7GP22ta= + O]cgt=gar+Q).XVVcksW>4o"%260r(Z!3L$>ID:Oou1.ca]p^H>]n:s2:5]PcX!s/?.IV6p + #TL;l!>+l??P^C[IIUHoj>,u?hO5&(&_Mu$".oc?rdkI[M*'B'k+Ng@J1LH2B?7-+_#UH@^ + \"#Q8N.F2e+a+A+i&r2EPGm6"H9MA@>?GotC^IM@C/iAbKPE2J$KDBkFejB(g4W2K`YUH"X + R6BD-mi2MGgfM.j>WB_IR&2O/!"R;'+#C%e682Pk/3WG8lDCA+oJ2RR=D\SJXeC\GS\2T9K + Ua_\E1D"c7n2UuYffkn1RD>)q+2W\h"l#*rsDYEU=2YD!3q/<_?Dta9O2[+/E$#m?_E;'ra + 2\g=V)0*,+EVCVs2^NKg.<;mLEq_;02`5Z#3HMYmF8%tB2aqh48T_F9FSAXT2cY!E=`q2ZF + n]G2jJZ4R#fmU?cIJ;]L2s#L4l$g,/IeWA^2t_ + ZEq1#mPJ+s%p3!FhW$%TMpJG9_-3#.!h)1f:#&]K(q'Q3&Q>53J4h)KD7` + c3(8LF8S^Wo~>Q +q 0 0 409 407 rectclip +% Fallback Image: x=1, y=356, w=404, h=50 res=300dpi size=1055868 +[ 0.24 0 0 0.24 1 0.726261 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 1684 def + /Height 209 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 209 ] def +end +image +J,g]g3$]7K#D>EP:q1$o*=mro@So+\<\5,H7Uo<*jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/mioWjnAY&^gM+`4=1jRLW!YA=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&t\ctIN)4XQLiVpoI(>.nOW?*DmsG$@,,f58"PDKfeXi0S^6MAH=;fBr>1IXb_>k + P+oS^^pnX!PjdJ%0OEX9GI`IODGpB_@VYP$,Ve*/ITH-bV]jIOR,+@`"rKE2B'8eWJ7Bc@^?S9#T=;8dl(8.jX2+3(RZ-;'IW4Hh$';Il=Eaq@[M,>+,XOXW + o`[$*'SEoab]bS#IU/PK^!b;.6R[)FdpP;M#LP-WTs9[7^\jZCbOI.]EoP)'b + 8Wu$&CKa`G[<5YSFhN30fG3M$9TeD$M.9YqrJ+q:,]P#%g"(kLAm#m(*G\ie4getW,]0/l' + /p**BA9lnc#DJ4"k6(J2dmYL?3J@fG$]BXj*0A3c0@`[jN[LK?C,7'J!$)c$[H*h*Vr2:`6 + K6U@CT>YL#SJk/Vi@'DDR)YR!B<6P;rP'NFAVX988QH##\.VGnre9&Xd6@QXgt_A!doT5%C + -r3PJLTB:9#JI()8]o=X;qan0*S9@K(M#=mT5$W7IHP\/p7q@pi + HQBpL-_"JpQ=H\SmS&LOC!^<5C,Me1-YOua-5]`?LOUP&1#TO( + 8LSq0a=qj8&]WhsL;i?b#eOhh1uZn-/q!VuZpPbQr^Eaojt[nfNP/%tZK%#/CKj"G>2Z"J-:M%9'B4+1Ma?>M`Q5G#K;;b2,%`Qb=M5QH6tD4,c8EV/ + `Z2n+R>qm%3/:&j*+PL[%RJ`@a12!g".Xn&OnqC83=R&tXGE!)b!a=m(Eu5&F;$E!8'%YY, + m/g`P;5m)8U3-4`5ln^&pdBs#t6:2aDVPdVH(ca>pq!uXHlc&2hr8L/Z#PXQVusNL?WP.KZ + q5uP5\nsOP1[$1ru'U#%Rr&PT/aAcjdV6Zh7UZ]8o-ahp-9aDI + J(X>WhQg*T0llCKf;G>B[S-::UGT+gEGccQ,L"'knu'Ds^a)@Jf_888ep-@o!4dm.Sp$_e> + 1L!Zh:)FI4,UGQG>;:*][B_\!k1ieT\JC-oe3+o'EY)uTh1)[thRAL[mRSmm39PKl+%)QVI + ;gNjeC#(qN<9uEj8rZNE4[l=b/u;P$P>gaX:oVu7W`;*^<3!o;X#44cs;j=.9"mlh,h5((S+3>6SH:-&8a+-H9/G*F + eA`NNs=iPr*N?EsWmk4a?#+;2XL;0Dg"RH[D=+ + aA[^PVZ>K_/-D?[ebSgc]R-n/qDIqHk3/I.)me^3AdehokXTYPnPEp]CQT/.$ms2@71/ + /:3GOs+>9>%`0&Y$"7'`L%(rV_UrK2@D"aXYm)5e3.DNL>Y0eA?3nM0.huOK=b!X30u(3X) + IH`bIICJ0a#NM\hL!4cnch>"=*!Y5ClNQ)Q:\YJ/9ot:RQp@l;9@KNHK>R84>OWR+8KCujf + H"Znu2!uKE!Z<9ZNQB7VBt5r-)a8gRC!9h[Tj/uTlPUJgC%=0"i + 7X4AMj=mJra3VAEBf)"Q9CV>*ULM6_?)[g'?4cQ^i5lVh!ZXmstQa:jice7?BAo0j6[>,mj + gA>Z%mCg.la&)fk)iJ\'4jCN]#iaZ%XFKZ?gU]mMmG9(CH1+W03(HI4h`KkHDpcD8GPM3r4 + :crPIKdQBrs7RNs+Kme&3775&0'CtM@]\6`ON)fio>-$O8_/[\F1O#>_e?!_8nk:2^mbbVj + ;A;:,^-0s5k-!k;H2g(l"8ltq*f8:U01Y84-`1uoZ.$:'i#guhWHC + t6eU!>/lTJcsO7BF*HN.=<*HZ'+iW%SX5A':'YI/O^X4t7:ru.YV\T^g3h+.7i)kH0*pTF> + LqtG[1@o'r90t'eA:PL`U5JnD3/*78dQh-"4d + /%*6l4Y9*G6h#t#]Pb + /+ccp1rYPZq$#l@?OIZ_4TaCgL(nps:YceD\YmYNY$Gh*,Jc?,I_[HQn(i[A$d)ujVrYMf` + %?T0NTnNqpiYSi^%\`kLo^(Qj(\dT`Mo,Eau1Z9]`+2ALdF+7_uKdRtkV + <@'Cm'k(,bnm*RT?R@OB-?o*7!+%5Ccn0ln?iZQV&e,r,!=1,C@7o.;aS.l2 + 6$1Qs[PZU$D=75NCa1c%c]F%nMJ;)NDj27$:?doeR"AMu>Y2A9pcF)NtCEAqr].R?B8!T56 + ]rjduPaUFKHN/kOaPfPYHa6\&k9RAfNhS/>n3jYd)$tdLq>fCcp4*/'Rlpj/h9fu*H4EJb0 + ZdCn!_`RJ%4;6Ad;mSP!Yr\*4`p@D"oC4k8I6"Y03K/L0ZiI]/Ma2*Gb&!b:bbWD2'[-ut6 + *qLqm)%"`d`g2i6>YOGm,H<41sWHbcZD:qe3)(YgI+aMd1$OQZr#1F+%:9O7BJW"<+/5_1= + UDZe4ik2m7u3=FOjE9es(L4X`[/kJOjX78?IqDc'$dTSOm[n8WBV$XfC-?Uta/*h!N3UoW: + a1h+MV<95WZ.:$g_rqVROYheiHgmKH;p#2m>69o^"J[,FaI+<0`):9HE9[oe>[]2!]%ll]/q_m_\&SZ\eZehPO;YI7q-fasc`6h[UKfIPqp2ZTn%2j9a'EW4?*DQQ0A + Tqli;R/Jrq5XXQ14h(o)N_3!0@3^('+G4*FNh0!`fa3O?E\h'k'!<"V==8p)O9t?".be@^' + DK(,6&uIq*GC#aRUZp,r^*K*a60$2.(#<_j*bZXn;[$P:9gnAu+TAIVJlB!C5(>/+C2m/ep9>C![2./*(O!J/2Sg4" + MemBuCpBCEdA%TLV&@[_)UYf.n\K)t^M\&/*-'MHn^DD2f,_-^DmAhNdIV:^"X!IFE,kh"( + D.hB'd8_T,8)GFnh5"r6R123EcO5fE`$m:0dQ=*-8&G;k_,FLOh`o6YT2;frmVFg + kLrpK)Ms?$9:XG'@eH[rHSXFa!M'G;jekpO.9FH['acGaF,/[ukq0Q$CoKGrM4C3`jirXFEJdO$K&P5dV&RoFf:tWZE_)KU_bAS#N/e3=371H<%c + Q'n:,q!'Gi;F4m,Lhk:Uodf:-VQM>u3NT;!r="f`+eYGj&9:;7.KDFWll)02f=tlP23SNjV + fn5jWbD<3Fu=feHJE2-#\*PHO.Mq*A%VrDo=WnC;LJ[<\#j+'7ncQ'I0t=^RIPO-X]g=a=d + d05k#3F'?nDQ]Hmr\UaK5\G;E)>^;<6=ciH%c!<>tR<@*Y0@cLKh#176rV);emW<<\[;@eoBr3UpU@JY.*OT" + _=j4A[&dXRLa!AeI=PWAp/##p2^_pF_B9N&!hbX0b:$;KqhKaUTlCbH9i1PSS>O/C(WTDG, + qGTX_SMSCN4-dnRZL`Eu8(pCb^V\g2S"=d;68AVmb=uZ*UAel#'A0(3.4Jc,o<.r_YIaP5qY.3TrNKK,Y^6h`]+M9:'sRQOYr`i.q]2t()mXf6FdKVK7gFZ+ + .C4)ZI/"kkm"'SS764NcJJ[?sfB7[o#Cd[m4dZ8Xlp%ni=giZYIlHQ!lhQ)nF["PMJ\->]p + X;@:,sWD0386GF=W$/.>62(KfcD(ZpD;U",7Y$L6PKGqn7b!=hI^DLO + 7:YX[(B?eVFoZLbsKe;^6FbopmI!rS\K$7gD$E?]E(Yb\%m*h99%iEoY$1T`PH + ESN0MKBeC@8mh2Q[8_QFs>39A%;l,,LL:RLY4\G)%dq2cRfO4=u`ob3Fp*Ds?^;ES6INh_a + 7AWf/i;q46uPtdG>F&\PbYB3@*[QF33K + 3H:BcX-^K0>*ZF]L\lMj!2S$i![p%b+_opk^\S + NQjZRG6(B1S.b.d'b:1)fck+gp/5Hg;D`-bM@KbXZbL + AnoZ<=8$]hmZ,t-"lMQNi#W?cILkAN&`J0RiP#uarYa]:*TB73 + idMWJi_6n,_*JbEK"aBqfHn$K7(\q8jK%%B^/2R(&%+K5[TdP/hI5N[A``o(4r[?_HVNTXm + t%'(^"u8h5(U4c"ZZB>\GhJuGWE=E60W`kl+EXN&\_kR*4tNq2G=Zi4. + B5\D'aU02A#l_g`\%gcP'`ou(2cjnT#Lr(4jf#E[_DOWQa*cBH6fAQ*_+`jmV>ofpKH<:2k + qa8]FFgkV+OB3*fKp9f+d=^0"UIEFd!oX0hUr2Z.=[EC\=Qf!di26k@cKj5V@q72]W][Q^1 + `Qa\$c.q9Hr8Z(MkF6D_cM[("(&n;;a^A8r@3h5D:,S-T2mu#5 + Of#&7K7Z=dbjsF&,#f8"U'Cm@1[ZEq2#'*6^Vj!P56C;J$3<9Lq=!Q*F?c2n+Ga^7j=*fj@ + gFarH>uB9;SriP3m`MR]`OZQi,ie3h@s*ch[ePK"547W:XglrG]EH;lR/#Pt%`J>Lfas\,+ + sh>clUn94cTO`FN6,XUAo-WW'<3gT@N7NQl6Z]"cg_/'[$kI(q6Kf8L'_#DIZd0_dN.*u;0 + %j59XuN\tO5b,eW^r5NM.S'P_g.g;.:7AS>qFW,ZH]jh#mCUL28L:_*(g-=-1rPn/E]AIOO + )l7;[s23jd38C)q&Ql,ao6L9%LJOblS-IoE,(YDu9tD5gUbe"[OhQ&I1U@HlE>XE0MjY_XO?U$UZW/2\/8Q"? + R14nJ"V/7.D6q'1GbdW_,F9N%<*qVT.H-m=/s$kE.AKs]C<.Y$p*u)+;9@G`Z2c!@P"ZoC![2hkheuroFEF2dirHhpSFqopCf_-VR@p(^$QY0A7NG3([Fe)Z7g + @hq\;cE?(]5CjG%4cErQR+##*`PSU7*q/5%EPoofs=dA*ZC"@ + O$q3G`_k./KMSKI#liLX]8mXLpD3[T5rkh7cJJ"8W$RAK/ZJc9b*>+VO6C>-MOX6W:Oc`?B + cm_?#C4!qaa*N/n_[-\f5gU:r(.G0.<4.bQFDYpm5J"3!oq7Z6p?MD7*S0&)fS7bn6`:4gK + )oclQ(DFl^r1>L]*B^5RE:[>nrc"DJpHd-t4X%YgFN=DYeTBt=;q@H:Iq!p$*M6YZG($p9g + RI$VH@P[[s*`fEV^_Z*Ght*7j`s$=\8i\jGG43r5,Y=ARqEW=m!E'b\*n[Y!WHq_59Ig`I, + @5toDM2TZ>fcWJ)0mKJ!]54IK-kXr0morp&;_Hr;]=!rIADUCQ8)u!['T3JV.'C&,7.J+FF + cQOA6%?=>'itkl2R*D@XM(4U/p!g`Q$G'#I16$=Q=J)K;tu&+QMTiIit@0[:UH-tNknpsL* + XmN09eI1Sl(fcT_o,/d$3&n46s)A+_O%>"L8,(rjO0hs,*nZG+9`*/'RDD+H2Z876>s,9g< + 0ud>C)<4Qn)6o2[`ke\q,@K#]ER\Sj8FA_&b^5B_oRDh)V]Xt.J<3YE6:aD,1l)!cO^gjMa + RPN?Uko$g1-/%L8M)9"T[8m4r+&C@5:<9-c]ce=A4gQ45K5g]Q#`g99'K_/E.E;XEf;Hn3CXGBSV-oacKWh5kB-83!uQO6=mnMO`;?0*oRr<@!C+Xk&"nSVK+Tjp6:gV + hTnINNd.1%$a\]Vr7)k,u$8>2Qd6+1WqMu=_5Z#U',:OoYWg,?58kP4I.qN+lddk(F9*o)Z + V6ZR+\o:kHH#G=boXYj=J7$LaJ1st;`)Lde;9W.K1'U@K<7<0eQF`+^ + 98IhCKO1@ + )O'2ikZQIRsMnH,8Jb4ApieTY6jgMg+1l*uY>ESP-KERt9K<&ZGkXei8%Y*IC<931@/7?r$ + sD;&.eDXUlc_06Y%e@N,SC#(kEHrTmJFANk5R+n!l6Q# + "(DZt!jMS2_HGRk8G4H/4ho7PS>=k>\t(V]W"ep2U\QTm + Z(f:ZJDkC0VT6U+9TrnkWnd?"P45[>N6+BF\WO=gmV/MX*[N#72/C(P+S56p.H-e+TF'11> + [$fS7hL61S"6b%1Jooqau&Lo$kI;KRjCf-&Z='nBeW>FRV_q7h(1raBpZ/[BRfo`j*N8:*H + #&_kMdIKG;=T'E(*nue2E^jpA-+IK=RZdQ7E_4*2-oY,"Pe4A&mb(gWARr.(P/:IBH#G@Fe + @_,H,%>KdO2**$cJY3&B$Gc:2gA<)H`i0.#Vis&/@b>JKgEO5'Kf2s7;]Q2#f&mJ+G]Er7HRIQpoH\"C.c0;\>lYQ + se!9G[h]Hk63q>8(Ij3K:fN$8@D>)&isfdgRqc".>":Ylk5ugCZ\t'!W%DYtGk;=f@dj$o* + YY5hd6I.i^hG*ZKJ<_&FO:e.T%h+rfd^1.*;&Bu%#Q-GbTfZBQ7IZRQ1lbX',s_:d5PlRam + P0J_nC*G[C#XbYG-6 + rRA8F'kZK8%'Tq7$L;%;DlCu2C!<<,EldcX(f#T:c>[X2Totrdr(s3Dkp9&`ER=-Rr]jn=> + qaP2d0%eHs7miGIN`82k!jXX*c4oI(/*@\+>-i/7LRoBfIYS\3ar-oOg-eD^Q65-VK:UH]" + _SCcJ2J\BBoBe8I>\O@KS33@&W*0T/F^KfMbZa'+9ZHAOY6MRf4EM6!++Z^f(IO1F;C\auI + T)O3h`QH0H.\k8AO<.cX#V^]]s86VT6r.d>;]fO$)]%Wt$X0B2nG.kA>*CV;h5QojoV8XmO,)oKe0f=dE>8^*bhN1S/HT1TNb8fXJG/>5ZmU + .RPE8kc*S7&NX^VoihS8t;kQXJj:SW_1418qYh\['(.)5Ei/C0DkN/ErJ6"Zp"$Q/h?NW1q + ,[_ji`Ku55XeAWGFcYm663:9?b+C`4nt#^WWD?bSo\Mi(9a@d`W^T. + E0Tn+mjr(Oq_5M:k-lNbZ3!O/X+))Tc,6j$.F&&<#2c!1N/1tKI4fG]\U^oRs[fre&])8PU + p5l2V=']-+M'@$ot5ti%L0uH;1j>(d'6+#c0S/jAlkVC%-_>GG$X"cpc1/?%=_H\HTAFE:[ + .'nj]*c*@Q/1WO3/^HiX1]/pKAG7o*6c0W6+2n!tdm_/:r%s?jceLIsN;l86%(,=s6c2bgg + c\$b;FC!j6j,e!X?s=Q=uDdQ`+99D>@(..t+8l@T#jL-;7si6tJ>'AiO + ?N7Z3$]N@RJnLrR+[-]32hm4UHRO%0kF\I*pY/9Em7FGS,*-u=(UBTad73'!6a8$%"br-UN + QOLa_+e(fIPlBb$\Uf7\i\mqYLI"j\?W[[Rr$cIjZqlNhEYK4)0.U&_!XH'Lfi\#Xc@kZ+k[mn`),=Bac>4H9aj/1bl;!g%=r\\+AF%)pm2a\&g/RLoCGelsW&&fN:HLU(eFbWAp+u;+ + :OFkBNNY\h1IFhm1i3+IKK!*ts21,0:_Yre-+?]>"19-Q2,$9^A3`t>#i!T1d'Q3K]aV=9% + T)6t;!^cl`PWp)B#N_Cd7"dTCf//7(I^J([f^WGHro+:*$jil[q&[iocD*.cm>O92m"7"]O + 9/E@8^'73#*drD(!biK[Q?D`fb@3XXhE7M:1FC;XH/-qi0&F0dmJda"i:'Z]msS25kS_;f+ + M)V<8/=3iLjr?Ca4-gRnmR5<9ND\ibSc/O?sbn-`epaB_2lSOL-'7k-Af.SWMs/PA/_9=mV + e<7Oc9,-KH";+Wb%<@.VS'>;p_[8gL44:P@`X2@N;"BKYVi*HRpD.m=##%2ld@#bS%L'qC\ + $fJ/r@,;?#mL8%Q%UfPP@1EoB/SS7^C0%^b=<+VK'DG.!a&hMJ=4M<6I<8o2F0B_0=.UIAh + B,h%+5uRu=2l632B$#"`]m1IiIgi5Bg0b+KAqj5=B#.AeV6cjLZ6R89aQp+/W*ZLMm1`k@i + P_=N\aCPOP()*^p=kfC^`%hP8`PIj-q%ACu;MjQqeBb:*^WYZkq"fSAC2V:1^%`je(68+'K + rU65qVq7RnK,V8@+A1L6s*'04ArWS4HGg9Wu[mB'0#pd6@2_^tZ='`:6e[=L + ][R7I3cn2b(1d8_O!L + PZ:SOb/Ip2B&$Nde65`1>MT-^>ob(o1gicfe5A^]k?MKEG'>:'B'50,-Ipft*SX&[/`"Auh + ;-oCm4YC1i(!j"dl$.K.#ZqMFh)%,?,m(7RlQ:uBceeLZutc2[&+QJ`IENj]#S/d, + 1/&aE+'C0hPQ#Vm]C6tY%mgX@6PAtrY8NiIplq:hXFC]7#qaC<\T + @c#70SC6F%bSa_B=PVG/p,![&OCMja0iaCsL=7'\3a#=\j1[T@(-Q0Y5#*u(fuD`Ke/I#qk + @?.-g(m)n#":m3nA[ + Ln&:!a?XclVa^Na#=QJrn`dN702pdA.kjt''Y*r@/lAT,k+d3TIEr(l*t!(A5LE#m + 'JY&.F`DGeY!guZjNboes)dVt4f\%O$UXjupS-;o;:VRCb>IX1+XC$33YB?krqk'qA3HY

'f$mHnojU/UuAEZB_2VN=b4S]Gu;3i\k;6D6r@ + p$$&]aOU%+k*]3*Re"V&]8iF^*hMs#QYHT/l4fXo2PY"VY]ERECC`=E=#TYW2fk$>F.YD_Y + ;p[53ci&!F3d$ka$4Y%',+-HiC:05P6,N"JDtITiJD^/%_1j8\81P)C\mbj^:ijUn8tPBFP + \3X(Iu1\,d4r!fEN-TjsX=Z6Zl'!@][f+?H:]9a]O!E@brb47a/*teSNtOj$(d-%a`I\d6P + C.5h7p>c'3B3Z>F+aj2IDZCE_B#,3/8?j9F>.DF)t4h-'[?D3KX? + $iNe-:r4IIGZ>$Q;j2"hg25#cC/deqT0g3C!*\P4oX^/0`0#$i#\SmIks + Zhn5YN5oEW6\+2`1"_u)1+Hsaj?DoDR'`dEQ^I#lEtT?DVKbRhLoF;rs+0#ch26nR95^$`d + 2gcg:C5RSR>Y-Xk[j%X(=kp/bgBIQ[=W($"^Gp9/6)]Gfi^kN"h`G4%f9%raif8 + ,!eYpGPV;7ca;FF_'ZZAG5;4Q;NG?L&TbV5p1,K'4MU]E'N(gfR,_'%;Wp,NVEbnU%rV?gQOU!rg` + dhqFH66Sf71J)`99]S$0Ke&c/VH!J?r0$7lmo9nt2.ANK^kd6e%:6/P'-!.WB#!!RhL@V\c!n"$rEd6CZ.nYgR#JNSJ&C47H$l3-q + [JhTos!+5T7=K3o3m"FYP&VSnbW1_s2GH>!$C\E#lp@^*"H?na8?YV6#^W`#Jk!IMLWL)Zm + uO%:s:Cr(l/#P&4>RLLCckY,Y\u>Yq/q*Q_kI&(e*tRBO4.?Z2Ye67LX>Z(Qm["?m"qJLET + 3O3I`3o-.J8.TCQNb-V=QaI"0;%Z$S#p7Ei(j+6C=a0?c3T`A5Z$+`B'Z-iDP"QZH]p2bjV + RCgBf"[-Kah1Xu2(:NQ(*>0h:*!aJR/3GUuZ-i%ID;7'bM85Y)K-VB\HV(R8H1u0ihdo>jK + :fKs`6W-keV+FW%-(4d5e[8so=On3q?s+TEX`%EF(#=":($1;P?e@7;?]orN0m>puaFJ(thT_f=J`/](MQBDpA(?$C/EGMQh-<.EbkAa3:Q2r + 4"QhcKLHJ)FE=$%QF9*Y3k](g[r3iAc9(Lgeqr.>d':Q8+9W35.81j-]g)OQ5"06':6]/l@ + dC=[#oOjdCR'R,tlM@@Ce2Y&Tro=giUGkIKGtT]d:8J"l.12Mn3n>#(K]C"cmcVWo(kV5if + A^8\2Kg#nD2$VuILOEI;[Ja7sDA8*rS<1f + 6.DpkL]F@Vet5`*T(elpKdN=ht?0+n:15)2iH_l?;I!MH/;o#k5$-kV<[n@^T$RGhTrb"qq + fr@j7j]fdg@G3s+<,@P6[Q$bOj?L[5m#I4^3lcL<*b48S*0L(_&+R_%j:m9/E + Y_%Q1"8GKEgBZ7#tZV%r2c13XE + 2P;@Q3D=[3+tf>K\bG/`kXBQXb>KbRsk/-60miK62-pq2mG09ZrjQ2Jj@lD]Rrum&4,KQ"sHAoKomhV6tbHo,:$NSck8< + .#rs9(e.e%.g([0dSIHnk]Uk=U,YohV6RW*ZN"hG:;eY,c.PuOPQ.!'2knPKq<1L;CP1!MA + O`.)G['pjgo1\D2GVE+P^mIEW:5h0WVkul&R+d($9[T0)`b)!6qNP]/@Abs%[+/.i=DEhP[ + 5Sh&h:7^8D[mZ#3;%[Z9Rh13Kg).*G2%-,t,`'i2Psn6LbTfMgB$n;_7lGW5UYA2!dP[jmq]Cr]55qOA%'t=18`RNRZi`E:2/kK-\VM=e)H%japgdEPn9^n@ZhLa6s;$ + 4U8=*nePf(_:Yh:/G1@Vf<.+b]?6'uF'W$=S<>L2c8l_q8lM#+V!-]gWI@UMotK&/qD.*mbHIft4kIRFo+ko]R@c4rSB[LoDI:ZAHT5UBDjI#-=j" + ;2pqN?lPi&oFJ!hZ2BFOtDi-'e#co)4s*>Je:`f8B-Je@5V,m^2&Q%]da9LCBf$mB.CLDkY + A5Lh(?7Nor\ZFf_?&PtR1i&Y%Xs8mCf@ot*a4U,-2C*U0nd7Y' + I?btaA<,?k7q]pqSL-7jRZ_]3:!)'W-tPTuPhUXfb.Yd(G9Gh04.9;FS^f>OA@e+k(O/VJj + Yj6`#V6naeF9;GLH6J + !?Ag3jH/sT7ZAONKi(8Mh*P\=fGVWu](W)Z$gClO-Mlc.AU7?Mi^U5J@'2fGb-)r#ig@rYi\J.i%'F7?Bm&5KDG6;MmT5Q:8Mn + (*bIhNGSokZq1->qAW#iP"hp_ksNs(^J^?!IBU)FaXnidDsXUF\kZt_gfs)rS^ZuE:_t-;g + l%Jle:5G7f0\\,09p\Z@[t%Sbti`k:%[l5WVY4unm3iiH#HTo]g$i:XJM-UmtL?1f@=PAV7 + h>-?2n1HqtK#;jadB&"^\\\*e0].O+6?hs0).rciX7Z#640#!)O+)O:M@g'*41,!RMV`n/D + E?-N[*p!\c8/O>-g`1BW^t"4/n.&3@Z:JB3gF"Mk!J9TF\*OND>C"fE]ha83pep<#((b0Y% + P#>iStG'7]Yc:m7'd!X)WL\$RODrA3VTR8"$HMY^a86%b!sRj9 + d-V2EE5I1n(4qMcK.tU[bp(SteqB#6du?9r%5`d`6AMf/e*TX9%8@?b;%JXieSSWM.'V8SC + =\A\N9tg#nIjI;k1C7_NGX@^%>re4NtRG@fkD"Zm@b1fUlhdZP;,8TNNj0l[hOZ_PmtD@mD + bFYb*ELbh6#7oaaDq7ADV1_hMpIN.AI\2mb_m[(X<$=D?AaV#iO*fiG%0-:)t3I+Q?-AiiX + UP/hn8>1n\_Cj3BY($5+YB)!":/jH70N@XKR9`oSqtUS=9S!ikAi9'28ik3>e+&`TYtI3+8 + NkY^d3Wc"_3p?;p=l#HVUNhIRjSuEr$lAD+sY-B-F[]6LE2U91?@s74%-9*X)X5fH3CX/o6Jrf[ik7I:$F(Ba + .q1".'s&483/+#6bF16X@HF#,T#2)>n)1XeEB'0.mJ5;[FK1m:]ldo/*0=#CXo2,d]^OA.W + '$O%)A$kmOeF)oBA@lKAB2pcCH1P3oYLW=gS^EiCC$)#ZL*;R*T2DDVdAl + 5A#ii:])\,-,8fT1BVP4[?9$U&j%T_[`1E>[?*DU)+FMf,]hCL:#9[7uZ&a#sNK2%T]k]G\ + Fo\ue/'-J"Sl7rS//uCH<&PnFn:WfX%Y.3g5#9l#4m*[BMma!*m(F)]/XY/^5&j7KQ7oG6% + ,d-]%DY?7hr3*D-;pnqtWs\A)(FF&=na^NDdW2YAF@?]p-hUe8Ee:XjB..;@[*jo@&sW'-= + "1=U.J8-_d\aJaB_Kbn.gpf%$\\8^QM;*5/2GKb8De\ + a$N__6/T=O5;I$fs'bem%V#0['Or[Grt-%5F&N?IN7g'4"sFmpC5/?^:XuecT9f#@J;R?rd + YD((XhS%:PP9@C?t\eg"W>-Sc?F2Kg]l%#mA*DZu?<4/`*;N1W0i7gbH^#;`>=F/(`2=#q+ + "AQE#I(10+';*&knB%*qoPI/ZB[gQ$EA*fOlG(S*QaUFBZ%1"g#PM";s:MGpN4uT#sd-YR= + CCbTh5;W)b1]J!5"P+.kCX$c_f#'cc*C4kj6+5(kZm+080g_!Y':.NBe2%cc6JG5@6d\hN' + L`a;p=udr(,l0JBmXerD/b_Eh[t!N(Cq>SCDYK=(cNM*nVWjDI<(R)Em>UpP`6RqP=[L.)U + t;rl3g2uV0%_l9(#IpJqm!=biQ+YFim&leAa"r:EB)Qk=Vf<\@V[]RtGm`O\\$LK,LpJ[>>n,r\i0+1` + W@j[I4-FHWu&o^6_k9kK>u0'XMt(*Gh#91m0 + e_oK=E@qP+,?5-LXmo0pj7f6LPT&jLcCHOR$!3WPDPZnM:e)N(n6CBT8HaO1fc2<(,9;t`J + ]q"B@sr?Yd69mCM-.)B,5Dro8.(F?&b.[NOEDXpu>s%CQ=UZN>V+G(s-DEZ"JhHBD+K%(qX + H[D`j-)4Vj#?G*7M:Rm$J=5#!nLG,%70XZojheL6Pq9j_\#"`*L>5l!VV<"kKekA:nTD)

qnDH&Q4c+-48o%XpN'og[1f)\;pB1858E0OjP:%`LjBlUM$ERuDn(FM_`QUi!` + )6*.I;4hooQZLQ\F:LMS@T=A3W8o\LFM`5p.PDdE=WJ3iFnELn=(?E*a%WbtkB,/oY((Y`k + BG42GSK=cRIW'+OR:Nr(s(Ro]ZSuWJFlr#+0dO+??XWoS],BY)TPp:YM]d/r^,h4ZR=3 + C![=]>(C5a#OF2qq<--[mpL6au9b*J_2 + 1tT0fj0"j6Y1i!@)XFRfn#oplYF>KpHNUJ+"gApM`]15]1M.0E + ;il>EJ@PW?p-T$dpQ&"INttFsR+I="h6WL-Zaf+/HT-ts0=7,$b;ZqCor,M\P$d:/!Whu6M + mq>=^=BqlP+O26o72JR;J9F?'eDuE-,0#\UBh_JXDq\R[K5(c3&)iQPhW/iVW(af/%JiNCa + U5R^A?%3NnoW#Fh4k9"D^+dDRG9`Cj98S??-jq9r#4Bq9HdS_%kQ4>T^`HDX;ST$dF1OoN? + lK:eCuGqoo',>k+6"/([hH:kYmRMC.XYBorN,B?#C2(PDYiDTM,a43*p=uUi_4ep^s<-h'1 + ;K&AS*E?2.V0-'n>$k%(TC2mK + 9FVcc_%3uVRrnD/lfN:R^(13f;jh_Kl['E]`S-nabDk$0\Cj<,-d + l9NdQK83SMARK1XhmRe(ln2*BQ)>&[=#aZ!e/*RpV8(>EB(?N)iBT)^p0&cdnR?[!citRm# + A-5^-LRZr!IIh1Es9AS4/5B>%O"SQ1bPM*/J@B`4Pcqe^%Gh3"1NC!bhJgNnXeK=D@bCCpB + ,1]_C&3=sr/\U0[RgRYo1-'\,T\reWYqluEK4-f>%'R=1&\S&P=;nOhbDp`"O'N&gn%H*63 + EBDj@IM/+hiGog"7s?I$3M!oFT>,7k^U09d!^FJ*"2L>6FKS,Ur^4MN7s.tT9:&j'ISqX3A + *&rsSX_>S>W#5\3DI.L+&7ejDNg\t8"Xf.T<>"5g&E3X<9ni8`KD5c*g<9-c(TTUTt2=m>] + K%(iM(^AmehP\)E":VcVt_1YhY;]2r([,9C.=#m+#78>$:5tUK-tumFV3AI'"g,#KpH=8_g + qU=;NHUHFGqbb\qX&>gJ"eS'=Tkc'U;dEmD7BSLlfccN4MNJkQ.g)F`h4 + f>aXgsWfWg@OF\3@-lb/,l#l&%OF)\M^noPMEU_\UjhEG1 + qRpdQ?*S0(TDLdSdquYND&U%2oDP]Fi,H1g4u6'Gs5\424MH"uP1/Xe>As5-=0L3AF]r(ro + DPK=n9DT05;Pa6s7CWN5/27^F_fC/G2I9Gr]qtg?-ciaRS5c=FJCI%A_&HtJ)A/ + Jpp0T#d@R]rS>$GqOn1k1pTgcZ.bnJ">'6j?ekJ&LkN;&)O)A5HhU8,Qj7RPCm]AOb(hraa + $rm!0[D1#\fLTB8Ou,KS;!T-Hp^7%G_Jjuf%\b!pKT//[rj[""Ik]Y,qcZOFi`9N*D>@[WE + JeMPNlFPA^@L0=O::6V464APL9L1MIujS_90Ak`.hcr;;$@8M&;ra?ZYAK=7foT=br;MY3& + 9N%`N027f<1&k$K'`CBs<-7qb@\<<^/C/Iqr8Be\,oDpAMsLgh"TTJ)'cmcs2pH:#1!KpQ + 9E$+8;W!HJ\DHo6Q_smJ:+\jh:hJi)h<->8Kg/'8;c4DB<"jfSTT2iRk:]>[nVuCQ.;/q.Y + fK)#cj+j7t-;>@[J#B9hSVr`PTLAZfC.Qg`qNdnRQ0!l&X(qln[B:8@hRn?'`I>_0lrtg_ZY[Tp>^u-fDV;L:4,2Bg'o2eS]C+Fn + pUtI`=]U%@r!rhEE4dP.J!EH('?-5aD]cdc*-WXMEdY>Bb]:AB0?nRNkGMDn@T9nI2r]d=LqgUi6p\]N5e,=e-rW, + 7I#ln?a"+\`F"@0I-"'r^9'Ec,N5r^:s@'g`&&7u7Si;t1CE:Y1@ILqQ27gV\5,_Y4W'Z.@ + W$Xm!eKoirsNrePTiN+kjd,l<8\8Mmde38Wi`Yuc=rt-G36QXVj,``e/'4Op:M31+P72Va] + U.eT0nR=(=Ge"T!ls#CqL+l(V^DUMmpPUc!Gt+ac)Iml)NUaG:7l]TMUGQM?;;OH+R/%dI` + )MV7"t<)'!>H^tLPW5[*^f`@,33i2LX=%46O>1MTnr,iP%#'GW'r(B"J19%ILm"B7k$t+Um + 1G=<"@=;.HPBI+60Y-b:-pPjg;F^'%0qnZ/S"0jM$tdraAJ+Fpun2'JoApI#!++_kF;(9KFiF]6)Shj+k+AccQX`!aQXWg'BXZ + T,!nlanKsn3Um[DZ>sW-I2k^B>KG2bP6OF85.+CY\;'M`5W3m?hV+bhCH8nr=kuoU4IZTk1 + 86KX]B.Kgo[aM`d>79,.V?Q7PQbs5?$k5-L"tH/\P9E^qEJu\cABRq.0P=)dSi!i#dT!PPC + (6$(WWp,eS"jn%C8FlgVZ>Qn9"8l$.F1#>L9O\\Uih]kg8tTjm%)V@G7Q!<4NE]\T$$;&l^ + @<-a3acdZgrQA+p"n_r;[u4(L^-_L-c/;#pG3DLB_K<6l&85+uEP^m'W+aW7&p8X#Q+P&jN + `=I3K)aO_I^1Vb-TU.U'3TPnMoV9.>>C-:H*q;]9ULQK:'[V"1(Yot=,"ro#7?$ZT-4MRa' + c8DLTqV/C=?\`3ifh'G%ZY-On8lBt[Y[9n+mK!rG-EVT_H70aodkfWZdrm7=pmsu + /Z#e%PPeuo'0O@"aDZ]p:`4="i.27JM79;K30W-@@0K?@5]7IYd59Y\7LXT/ + 3(c@o.%Ze_\"Zj7*NW>3+l?k'JoH7d[E'IBfNr*[76jL>+RTe39@@([^t&E94c0RUEI#mhi + Vq(DJtNt-"N>2iLCrbaj(4336\?(+B/infk2jI]W[ZBem?OfIkbOTad=kBA'Fb81hODNRr_ + >Cc'cI.B,p0+oGQ\>_gT"VQ]4\h6qVobO<-L6$@1\&g'>-`",EPZ#B[96k5dV=_s!;bD%(H*oT/FT7mL)M? + @N,seKc"Og>QLJtc=VlE`]Ut'O1)nYQVqQ%8$hX[`%aa--+_-$^ap"o/*Hq!&Bq=ibBI3lBUriQ.6CQn-kSU`7.?*5T!&6,Xe[oH(jP*^UP__Aj]8,U:/_,f2)Y_F,_;<$np27l6GZ_P9^[ + ZV7fkBf:<42"WE/A1o*IAA]'k6R,RJ1L`4?@'t>h6WttIj'=O\2oCk>_k]7Z<&R+^4//KY_ + rFZ_)cB$R;Sr>t[VRP61b;$^C,WtF-!Wntg/p9)DFB^[`/AZF)fnEbFJat=`6r?YEbi.AD` + \W=tQ-<,8I+QapXDWH+EHo9kN:Rn"-]\]UHlnArX:TK2pmWY2) + IF.kW?Ge,u58"eKqA#Di4gH(7la5a22]RQt$XXbm/3p_G[!:RN4\>t4n4$880;S4OG[Rc=c + 8>E[G"1^t>`&kL9ON``H_%"$)aZJjOaV)f['U+ZK_E9U"8Ro_2E?@;g,fu/pX]1b%ZI^[eS + @mf6]_%f/)VmO_35%,r]fP%69@c7)euhX-4cr=LThN/):;,G^Y0-&no']2gi4^Gp^+87t9( + joAp90)Bb9:L^JSD7/RVq7i-j9Hf + PB0Np1-r_-8jYo/qE!u&Hp'JWa_5XQ>Q9,Y"A#0bJr5_RFfm*EJB+Mb)+Ri + &llS5VA@F@B1A7NVUMHJDef\MM=jcA'eOP&r_f`dO + A'p@6=PM&s"6:C-7`L!7!m8-LEcoXqHr;b.PnBWrkcg!sFh#1#Rr(jGg:VEIU258enU-m1u + :K0P]mAJXs*8/T,2$F%Jh#gfS&Pu3(`,u9\KLepnYf)u2d%j=h1N>hE[*Zc$[sV(B%1gE@& + B.?V7=>j(XV=3`'Sr8_>N:@6ls2S^)+En";6,N#]8BJ9*]V[kggk"p#c^@IBK7ZdMWH'\:! + 8G0-="p.;M9!LPpouB.h"Sg7nkO4gib^[Udp+m;ZqBGNA=#K1L60Da4TD-[5LPb2TPbU](J + ))7"eLWK[\c9]1kK$D*U12jN+Ghe29QSXH"i9]ZC?AaWnPOUm+i,8P)mGe>5ehrU5n!dT"Z + D4IhkQWn(/boXrr'an*iB'VNuLdRkPUat0Ch^&uO7 + "Np>reiVto7L-Yf#g5YK@&16SD.uXim8ft^9;Ct!`Xfg'o@E7#$NPf@D"3XNs.sJ(PM[^r:q..jS4d,hP61@Up)'D3`dKQ=>k'ie + ArBQ7HFA/)o,2ioVe$p+>;'0&m-jitaE1%Q!,l1h/Apfl`ZbJ3L?BXu)kf-C)Z-X8>NAg,i1T8XfY!'9X_l0gD("Tnlsj + $:o3Qe:sBCq?+*sc[p*p"gPrdA)gg>J=PLe0>LaNol@$Xr^ua%"ga6@I%E$11Md04k`_L!_ + DI^3\aAO>,;EL)t`?crjc-gIT>jId5%4VcCElU@\h*,`49R"&gel*;;h1aJ04k\:NX[hd6; + gmG]?/A*lK'/ANaB!W!,s=e?Zq,S$BBA7l$p+b]r=\5"5I9"R**BTY2*L.k*s + (J@UO%`l/]/$>Nj71B1lhFi5Q3im;$+qnmMV&@2&6@-E#f[E+)%fbSCr;/,WIIrRYT"@@R> + LQ5dUsHg;=]@J,&;I=C/c['B8NCLdf^4b=WM,u%Sjc#n`bH,[TX2!`^MlfL'?p:]ek^?c'? + lk[J)%`@W[`,%?Mlt46'G/\9P`pAUBj)-eN`]+)b3Fe@9AA1-9VFb:I0B@<(j7MtS?9mmI6 + t@q#6f"X,7Rc\k8*uI.m0;:t^,t]B7;UOL:h?ikmbcb>Zd>l6VERpr2H%Kt%FFfK;$@MBFh + 2.L];KgU>Kg6tDHj$Q>fGg@#pmI9V8H0Mn>KH]36ku1n>0B=oXP`LDeVb"`StOKLYaY + 4hF<"Ob,^0!mm95iO-!i\%^m2S&.9^MC#A_' + Zg2QUgCl88OnioTr2BO%],C9Q[a;o]Js@pJ#c8:A#,nobUNd@L2>/2aXpUmH.,[c)bcpn62 + ^9oq'h/[aLs`fBmb1p#fWRpK^Qj>OA%f2GW"LV"%ui:1UfYD;>Al<_cJ7i+2_(mNte82H"_ + 8CA-6>gMZ,Mp/YC`=8*FBpEo-BVWr'=leD(KjkUelp?tHdnPRS?7Qn`Q2'23\+^C%=k$Ido + L1ftHq)O+HgurPYpjE@.CknBkL<##CDh`L\M"la^ph9P[T8NoXDMQgBN,:.kntL(5ECZ=7n + 37")T*8jl"hL$*E-DS,IgFluH./e&E3Srsc^WDH%Gj1T<#6'E0;%*KNU\;`kYDmSrtP<"(. + hhgEINbH(%Caao5Upm8]Hb?,L5NDgY\'oatFmfDNeGB,1W17Bj@Oca!F?6ge9r#``YJ6I`#_u*&/HsamILW4^dH%gKGLRF$ + ThPHRQVWriJo2_hTIMBPfF"l1HF.II4L9=U$)U*pTlMe$3\(AD[5>9q@5_hX7%=nDqdd)W4 + CQ9^n:I\f;7l%8GIOA"l2cG/iir2I0r`A7]:Zq^ik^Ia>rho#[\*8@^lCM&!oh6h-?YFV$B + (^UGooE8L2UQ[qo_'C"p)!H&=8.2UW..&'A=rGZn,)1dB7JLhp4mA^n:3Wnm`6(Srp+gFi@ + p'e2Am$Z`*_r1KMF$3Mah1g\@ct`l&*P,neZ\&9'MP^AC(!)pD2?)]\;sH4IPuakcWRP169eGljY/G>QEY2*i + B\?,ETNR$p2FP\ZG)pRpPiH/Gr3"-Q=#iNMX+8DpL--qLi\2!CKSlPfTbN.I3I+s-/hTtCm + q@+L(8Yg77?i$="X')XXC7C5Rrm,AegMV3S6`K/W!8qg@>WsMOL2]0rgJ0-r0:U2K2I%p7Q + W'g4lui/ANo,!YfPl'44qAHpYMo)fg?noAZe+U>24MqlQdb-jr-eT@p92g=r]eEgq#MD<,R + f0kL?&pM5qlOa#/R1,'aYuOTr)\DYf&1'_hOC1%j0qr[1L8r'M/t,-HZ9%`Gnj[(WFd!Tc$ + =Tq4MOndDTrCa+elBJ&ft?Iiugq*.DN=a8D&G8MXPA-HZShRj?-sOfL!*#&1%c94h_H0;%h + -__=bR=f-\mdmVM=c')DfJ!icjiAhcFN8ZinZb5p@cI:bP5>W$XTg-n9712JTk!>V-d<&A+ + 8!rpS,(H!BFX]s][(S;+;Uj\)@i;ZP6Vp`^.>h^ZofZ5Q"c>rQ'FG24J7Lk"8_V<2,%GGXnY?Usp=I,F\l!+B6e+a\iM#PiXs@)d'q3H&NQK?tKT4b_6KNf6 + _re[Bd3_#?3S)&Sm6#VWGAMKkP8hlCO4RA>O,JoBNo7V+_S,u#QE&WCrNR-[ha=1 + A[u]6ro^m/S&Y)&Q=p3m^Mgaa"i0T'JRj9"gd_mnnXNqr=-b66+j"aNH""g8r3nA0Q,S55c + Z?c!7F:[snQK!9rBF61F^8oge.m[Gs4I$nQYG4Jh2t`^I32hPpAQ.HjnJb(O6QQ@hp: + Weru^Frs'Ps/J5?"C5Ssk/i%tak&2"(#fG+L]A.O8g7LDEe/Hk"E'n['8%7/W&#N4YcK@#> + c69%[FTgU2\0V0"GfNAU@>VUrZar!K*D%0pl1l"oa*Q+7i&)m.HLTq=36qE"D@RN_:@Y@Pg + #$DW!J\_kk!(2iVg-DCBs,/UJH,Z_n4(A+rMp[P=&+SHl@m"@_&[!u\f[Cf"5kL#_aEL-Fl + p&O`-Y + 3FQPYug*8f:8MjQqPb;AqmEfiK]#A9EoQc<3eBfc25Y[uHN9?:HAj)("oWQr.OKbYe[#Lc! + 5A'.enN+sAaSK[1lrBTGkJX@Qs>,:-lpD3Cm.9BQ=2-R@DcPCeNc-TOMa"O*_WRGBr;JOV- + n=KKh?lrM:!>qC'`Ytrg+>A*bMTEV.&:f?aJ9??+d-2*'0o(lbXJVGm99Z>CH(h@cV(VXtBL + ]=,Qf_gf92NelBgplSdm1]9]k-K%.@>D/QLf`HBcafNdaYG$WYUG?:p*?[%KYYJpZifGb"- + 9HAmpef/tgK+bmlEK/jV5CLN*,@n:NO"tlW9^/$!AdmDq1KJbGYX:`j3%^dZk)Y/KD@pT"7 + g_+cZ%^o4?,KBp%Q7QSg@58Z`!sPo[sUo,>KF+h/`jgY$secqqi,StcP\^/-]Qp,3HK(Pd^ + hA'O51JPFa4K@3i?raE^iR?mg7n4gmIQ%6cb*=)_,D]XV.qB=N^GP/nM]/QEpd$bW1/-AmE + HGo,_VifrHrA]\T&uL2>.<^H(Hsl(.FSH-Wth4D2n2T6(9ecrp64BH7JRoJAUcOZNPVfVK< + 0<6(Ap:+aGAIAQ'1R?nh0>t^R@*TaAHQ26MAC.Vj5jcWNk'9s+(@ + \^_>5:i"lMV`"IEQhq:eCcR9IFI&'B;_r"0)4e,-E=JB.;eg/)7CD:%gtPbPPths#3HkVeS + YR9CCcc.s,LTmE2'k + h6U5g.A=U/ZTCVYa\@qS$;s_iaU[0`$Z-(-Sp1DQN-+ZQ63Z*U<8fm_Z0Tk3Qjbm&Lc($P;\;q!Ci3m?.ITK]l0kjWLThm]PjD.nq0u + $Lc::;@`]qE[4TP%.Pt^[]^OW:E$\q+![h<g + i"Q3"(HHQ+3Kp!p1SHjNT/s\c9jJZKK1Y\)"(6o.Y161=QU=2)771 + BoYfmef]NjG9[7i$'\)=Y]1oGc6H[%,']c@#bj"1XlB0N[DQ9j3lQ+C5*Y,e';<^nlq&[rQ + Yd*cd;>TZjb3cSW+:d?QAG#;-k3gbN:dkZYEZ61iq!\n?[Lspq76;^^%jP(K,;MCP1-Y8O= + Ru,j,gS==Q"9L:BNl*T-5CLlMmk$5GkFsPp%!'/Z?EnLB3;?U=RUB815>OdLgt@Q.5-?bZB + n1HSG@3#.PdmtFgPg8XXd2SBi;HTk+aHq^h]4Zr.`.+P05EicGXbOrNb(p#59`-kl+KG/s> + RA[Il0fq(Hmga:eGZ-Xd#h!m,C%@.Og^#EL_8))YG623o7l2BcOQ+(DZ)@b)u;p,-><3F`g + f@s1(H[R"GI7:`hoAG/T+&8*lNW+C$[%Q#BX;elZ'(M:"!5neH$e-Tc0#A:AoB@DNo;k7L. + M.j>U416P'X:4DO]+dBK&P<$UM + _3M'U';sR24;@j0p`Z,ABb,UDNW_3"^p=(I]2D]h>VrW+qAe1s$X-c#p17)U<\(Ph>2$BcE + ;o:+heO2!D$*m2WM'GP(U32#DN8[87ZulkKgJUo:M8a\ao5;+/5BpS@3L,uTm8;Xt>s.n6m + d),l\<;:Rg/(W>g+.=AEQ<3I$9ni!tB=&gn",Vhl=-&V:?QW@!^oFb3s&mH\Aq,_K3=0&k( + Fb3Kr(nHODWJYr&MHI-@\?h(f[>VQ*Mtjc?1J;;G4ucSM62"`H2J$ZcNGCN/N)k"7m5I + ;=WFu"nBK5Mj2M[7$q2_n]!h_"E^rd)dfG?i")#3-'nBm6h[#CC)dWWBb)'g?U_ih-qCmd! + gQI-5m3tLib&GE;(#9Xpl*^)&7?; + 8=(K?;.Z,:O`K-6V,oHEnTPeY3>].fF6[NX\YS>292"t[ + 8%3k:02I'Z,T2bflsoV:Fb$9m3To+b\_\*ne=CO],J>Es?-023d[r`saO]N> + Cd=:Kfc'.=KB^+XUW[@4iFTr52\pt3cAbR.u"0jARm>HAuPbU$LI]nBD//2G7O0?mdi;Z0V + S/[F8g3jclYBjV)oX8$V70E$ZKs"F.paOl,QYSDt?fne!"N#96e>2cW1jfZ2TY57Cd)XX^h + q6,,CY?M%2]*#86"gGTFYknXNR)tDnJ=63XZ2#JRR2_9EbE4k<2d#<0[Xkc0mZ2VeO4ut-1 + L:7C?j8nZ'Hp?pJ`7c^'=*p@%?qg?4!Bc+j-&!46FP-+4*2](2f1QGXfo4dN?:i$ogg'ROhFGngZBQlEk^3K(Ob7rl'hR9>fCU&T2@Vj[ + ^ORWh1<2bfb5^RI/`Ro,rMb+j+LYFI)GS=.Efn\:V%_fp!fS[X#C=l?FVK'O<;T%WC"2j(s + p`HcZsT:,YN'c-0T"X!RKHIXC:2mK'!1OH;#`:!Y(qB_k97AA=*,biH3"IC*Cq\I=V`K#M[@[/@WIeBSJsMCp:Re;gXimoIK + :Kc1otc;"SLaJ,/6GT`cc]V*X"@6(Wqs&V*9F64].Ws!?b4A*3mEGmq(Q"%aV]l:\;RKd&[ + :"'d+o;\]bSom(XYA`dJY*64WDgt+juo-d_.B`rAE$P/[9QrBqZ&R4(l!FB%ZufeEFrYq%^ + 3i1M:suO#oCfRm48cC"BJL"COb'_,_<0E/cqbZ=q"Y0\q^o7h0lc[arL.Gr"*CHYlu9-$Ir + QRu#i;:"l=m\N*KXq$da& + 3e"&;c*B3OR.Iqs?nm=-B^GGH\2=nRCoD^cA5<\XG61eh@e(Djn*KVINMmeg`fMcY>+(_af + jn?AgN]g[u]jLM^AN?;up]#d>!1jN?W#GcK*?BX<*D;P9M>=\.om:T14dZ"j2aa.,6bT7ZJ + $lfBXgmmAgB*MJFB@5M?/-Bsgf\:$];q7PYnVJ*U-7>!^#.[$?/C"q]U=i<0l+H?PB2#:+: + ^@-D?U/o^lF2'3!'s,LI:&5(jWp\K5A&)nT`8e2'%W6,OtL1rNRT1aX#<$qd)cDQ+,lH(;. + Lrs(aQjdtta"GrqWn)RKhbrMn\J;5gjJ;c1()VGjJ$qNXO=e#LsUm5Ed4[p#q9g/Y`mrl3B + '=Y08^7BN<;J(ni6l\3XGDJ3=Z&,(WbomFI1E")HPrrn(liGf]^)`b#"`ac9`r0Bi$s(:;3 + q.?dF36c2LZg>pHNW5a$kqq++>>>-uf'>3J*JUh`dXUY-qdrbsDnOopGRU/+$3.s(1cW.&: + EbkEjtko]Ne^9&e*&O_rKmC%S_NT,45L`3GK0Ym1OS&?5H!/QAj?7O%tDXaoCoL-Du[lDoD + APPYQ=`3R8=S'@@8e`&Hfj'@>+hMa8@&g)%_`U$,WV9K*jtBbVp&"-9OR+(^;_p&k(+u:DH + ;Q.=P`unT81U`\F9%(RN,bru=O+aa@O;;>bhb87h.;,+mu]Kd95f9U@+CP#S*(b#5%0@*3N + #G$lE_e@@0SZN5P.9L[aT>ti^5s"hnu8<>,+BU%O;+p5p];jR[Hs#_Cb]^n>5POWi\:dr&( + 6QbEpF[T(qZ#`<1e86:!(:834J!QqM020h/eN-l42#7,S(A/D6;:J%B:k+dR9j(KH[=qXbe + joOI=k;-*Dd?D?V0QD`-(4d@=B5ld@p$/G?s+TFbQ0!l&C0K2f?ZVBTF'Q1C`)`/DrRZ5KQZ_>5?%l]hJ)7ZJmYF5%aaa*;e%h?SDdGQ + &\:ictF\h;nBEs-;aBSp-snH@C@El$D3aU3_+!g"l)nro_2fHsYG>"bD')e+J-=BA<0/r1G + _P"&p_-jEQ*^oA"h$^LdF+p\sO;'=)^4OoiFC7fj\Rl5g',#!hAk"5oCUJri;,5k3;*@*&A + #E--\0-(7FYqb>E@61\Q8n_X:`gZ$r@#!5Fnb%74_s#Vp!YLJZG@An[#RI`OS%6YXf-D8d+,@%(<@;IBHmjjK?<^k0AG;B;?'F)Kp.KJIQ!K(8+.X+\W(Ll"bN*\CuGd + u<$]mOemERWR\R-,ON]R9OI7NTX=(F*I7g];FlOqOL5(dG*!MXm'1J=)ZR@SF$-@%8$b\Sj + -4fJT%(k$j!?QSR]J8X^f%.*[k!(hVuCS+D"gLUl"tB6\h;+uDm;H1i@L.''!t'NBuO(+mN + S^S2q]ClM<$7d0X1UgpKKO#g<+l$3.M,Sllu>&$K?q&?7XLu=F7P"SA@;5s4fSWeB.;bC>( + .5d?nQ($<.CO&i2jik0bRdOS,m=-f:*_7HJ]K+2mQ:IfqbF,\3BTf)qZt?u)g66Sn:&2=ZG + 1>]FV'r.bXhd(+I.#dR_KjHVkrkroq(5gXZd84.5IZUQhNSM&:g+?1s8e8)?[Y;P1 + M=5L0HHHh!+Q5[Ue)\HNFAW!al[5[G=(`l'#PYXmJ[op=#gf-G-mZr]$=rU,k<3i%_QD2p0 + "[Qq+Ks.pK'j%G/Cp&.c\aak2hQ2Wdj=V(n\@tl$/N'iBDHbogMf.^'qkCW=ZLbZAP3oI-[ + H]CI#J9u@n?At6L-:h@_`l]*qep39rR\UST&\^T^OP^$#K-=D(Z>4@M=Nhhan_P^kBsJ,o6 + ubHqHm=hI=J0gI^U-3SY1do_#>M8Hj]e>?h3;@apLA#RN5(/>r_F1LIschHEW/ + )A?[Mf;qu@g`!5KFAKTJnm;tN=I!5CW#5;CQkq^t-m#XEtOZQh=[J?&,n!s8Z<4N0>sd:P[F8MMqHsRb@Cnm:2FD$]dq4FMB>[i8[ + ZbG4X>hQ]CdXY/VZLjq")^KKFi2sQVeE.=oKY?&FXO54`%$2EMg.1DI^cA)`+sb0gdihq`O + Gu)`1ql3PCb42N2XpP`9W)U)hpc_OLTV$\<<(9ZrAK$NB#+A`G:O6\sb(l=Ur'+WSm:qqir + WTTt+["7I-?kCRM24Rh0)"\]G.-b-!bjTW7m_3[A>g,GQbWBT[)-7]WAJjD6>m\[ms8&j[a + '];h$0X[a6Z'#UnC40U7tYraJA7r$)Yob]G-:;XYSD.6Ie1!;*5._ + ]Hb=rS:d)Ag;mL;XgqZKEF6ACM*-^=aK*LM`/H<@juhDE4UdB(Hd^"DdCi5:4Zh6:44T^&f + L]<652;JrCm[E9R)s`D8Z\@P]U?mcRn;,"8_fq0m%?r2Th50Uapb":4JPMBU<6`:8n=bjBq + .BR!.Y1@58,^nZj*h[#;X4#9'c1+M^Q8UYs5;ab:EE^1pS/9r'/uET0"4=S@D4N\Hl^HJ-\ + gOi)32>"YiuXbNg[oS?'j:#r[.^Ur + _6(0LDU+SaUd6YZA_KUgMcdit.DiGCc0Qu=X=cC& + i55Y)c7C[U;t!()1T#OV_dJ`sC2127Cr;'r[_+ZHZY`(IDaW# + +gS,mY"`P=ata42P!e:0RHJ&iW['g4&^0iYe + *KMpTZ'r/cHr.gR.hFOC!aIJ1;43U7>N_%re4U!a1;pn5Ul,beD(\?6W]T]N5Q:ch.e*Nu2 + rB=0a5?&(Y<#n>*'gu"Q7+=@g<,G*(I7;YF7oYaE<1QZ]X\;]iSB@&rb,iE7gqQJ3;)'o2e + L\Da4L!@-r_#[\90H?L9is-G[m0p*!>lFM5SScT>ZKKa38QB9SI[V*I( + 8[GsOb?9[":(\gL236HoCW6'Td(HlTuaJMa)`==bT%`7I=$,gC[4c'qX+ZmJ+K://uj:"rg + qoIIf.0%et<-0-c4r8S%oD$WPj6R-*L<9CW*kLj$S=`!=iH>Lm7R3qjuV\JOO]5P.nSO;I& + [kp@r'LE0XG$5kS:K0bN@)(DhpV`AXd/ICM`M\OHF)`[q`^g8n-sAO^MM3ZSp^>ntb0_)bR57chNKS8 + Z!H`CD3o7l%m#2',7V+07mS7r,A`aBo2MGIp9g;FMm%Wm*W$I(R'>a6UE0"k6GOJl9!-h/% + c#HJ(i#0M$%84FJtUS&raXb<2N[4Ol>Pm5Nl0d8Q%a8I[`&/(IS:PeDI0<7OXuF\LY?l!BA + 7?A7]OSa5.7lsAN8?L@+9rU+"rmp?Op?QJ`F(%bI*:-\tYqhkTmF<3a2` + gsp7DeU@E<%9^I@@KoA;hsLSRqtZ!!E!Kf6"u&LLGY8+".>#>A!0@N]LH'RbN + G`*eoA>!09"f4Q4`fRS'3@lI='Wc(I(n/N)nS@dT7[)IZc!@O=7&:1]_LFQckLl/Q + %7_kcWd]pqjZS^-jl:CWrV\p7HgU,R4h`'usZ>kU+aV*o0tA1U/j&l4g%u + @g04*(gAp0d$]:pCZGgsm7?+[nZs-@qTNc5;W\(a8IC[?)V;&&?d/I*D';+%0^1Eb;g[u:N + SJF]._8aZr;3"ur'cL=VE7)qMX8U+:mE%F!+D+fqgr7N"4jH,oH'AKYXNTP!dm`(F-/RJV2:H_p;hU9>.dOc:j-RaHhG`!(eb%`^o& + pR8BHDe>(5-?RK]kURBPrQ*Q23 + _oiu6ku@_*:m\&5$Bd2g."A(Hq36TVV6bm>+X>6`N&!8rb\%KjGScHm/br?h.4E^78o/aAH + c:"OAL=-7BYZ^)jBA/?0+94.Qn7&8uYY[]+Ahl\`R$l/m;+4&`q!I3]:9kFi)Ol?Y0\G;7i + j`An,(oa4u^K')fr-Y4rZ*B3a7:k*=7u"FI6KdDt"R>rHV!So@TAE8>\X;Vg"sFkTb9"^SQ + Zk?`@'^3I`Li%Ai,kGmcZ`sc9E%;4gQ8O8#$A(^"ZNH#aIB[7=]*trWi'QIInnS].^G8>1J + )0(]UEJGKQT,YnlN^*09T\^=HIirK4P"OSq?ac0h1'$RaQKc.2#QC+)n6uSk!qL7UEj$`g0 + eET3)ZdGhC*88::]cA"AHo=]9$\UN4qgUAEm-ufC95B>m]?#?Y%liT@AS9k^)QAK4]fQ=lR + !\=1`G?a5r=>(CK@mg?%c<\-,MPec$b@_[n(;QgfV!7c/ub"`8BS2^f^8ko]UpD/Wg;M3J4 + nPCd:;>:<$d-a5^0e:9HNuI`g?HbZV[)A"B4;*m\""d,Wl)j8RJ;$qFK+q/&sFm:Op[>l=: + iX6lcSmB0#t^.I_;9'tJ.mI+h-r<7'K:6)42DE^n9_P$PF`Xd:jQ]mDJB5)Wh^6mmS7%Ekt78pL[FKAmmRZG5:9TqLq+In&6Sb_n,!]DJ&"[d + ['sH'eC)$!j/:PE(4rjNcoAUADQ*dXujkjPr3\Y^E`U$,5TV9EcL]d#:T + /8-2Q5pHJ-f@@"*at5_MB'HZ#PZGUW^'Wh<[rHB\bX?(r?(/iMf<+SR@7LiI1G3S+B^#b$u48MT"NE + f\B*8AH0b_13m-E?SqdaXE0fkHG_b;d-1,Eja7#)Wmp>,g>,B/>lWJo2LA`Lhe&rj5PLZ7m + f/!Wi]kt.^,I9tal[X>C8Fl/TuQ;rqRq?UBLd4A--78Z0K'ZH2^>o:PcS7*=*;64aVa&03h*65>>I3?TD*.K0R;Da4Ta\kg#,Y?E + hT-h\nS>@);,luqqb>R-*!"WT0%Wn9)q9jL]P;LH=bstaX2BVO4iASt[I-3K7PJ$7)SgXI/ + 5#67e!C+q0oa*n1ZlKTDcY$\IJ'jL')b8=UN@@Cre=A6bdhLUq:=D&u3D`VH>tHZudc$!mR + WT'B:K=e;@9;#ZWCg/+?n_ukeFhWk!^h0@bbS`+a^a(bR#h`&OIIc$LjpOJd3@K1 + o:\0_Z>l3L7FVX*e30/RFd:)V^='UilCl7F^pg44-/H*%S;ama8*]8AN<4[QK=N#e!O` + ;%V(qLq6@eYB)1#!S`e\j_4>6ereI63=qT)TBdX+JDs=iYs/0TmJ\H2?3ju&M&RSO(a$3]l.iD)n + jjJCA3/NG\S1Qs+THWIP6iDph$.bk+Ce(\**TOLSa:)?B;7=q^.H5mC[p2^Z9R@$`Yl%%0?*.k(rduJ+ + 7YXQ`b=P]`"Kgjb8;PoaGf_\k2k5KE!h+N>+FOJDeBN$4<-,[?4hP! + !-n\KX9!S<]7)*_K"TJ2)iWS!1-u:J)Kj-O':a+7b^rb%X^&j]&?(iqQtTq-lUcGls@qgk/ + 7_#XO8_]cgocM+eO+7E+?+,34&\&_WC>k$]^,`3O$=dS`bqOYeE16!]]FJl'T=9Uj!PUQjc + 336JVi.^PYJ'r&';-XgI8-(qQ0alH[hTllsGgq+B%oNr"Hr@h1J_M]lF@Ea:]TZ00J)3ZX= + kY[U$VLF=>=sRf1Trb!.^5JH_D+;dp*&g@hO;p;JEDCMrUrPBWSF`Isd\9U8e@(_j>$jt&N + AIH]esoZ2F\V,V3N&,@T2Z_U;!J3PW)XV/<-jg,Wb"c$[4\@W)PL-qI9!:I>,kSK[8q&(=K + BtiYLV@C:XZgWRh_JI-3^]_QHEmCb;j09'pi1VC<]kGh*b>0Md@piH<+e"='-CB@-dZM<.H + U./&?rq'jZEq[Q;K39-U.\0V]?A`ErU,'4O="C^s)N]%8H">o$>bXp[;!>*Z@-QMTCEbFru + 0Rq6D;XgMJ9fm6kG!l(kl!4S9mHn_E:E3:Do@*D?$0a9JS(uV^oe-\\9o"M>[@jLJ`4Z&['o%cUR#tg?-V>p7d+gOWc\#ae@RYJM")6)G;u/K48`(H?/ + *JGfY853kEtkt>LUU<Ta + C.d._Q'r(%uN&i"7`[KLYFCpHYESDRh`O"&rZgrQ@o)ZL_2YE_B*eMar&O + T7Kr1YNiMU=4U7]=?2UJuGj;=eQo\7*?8oTk_])H:gX?")aCNj3Sq0?1NB)ro^tOE&Q7a8$ + f#jIDj+Eh5qHq+!!t>dp)@raj9`V_R>Hc^T!IER+Bp4m-(]ZL()(*s;@J[8jB[g3ZM^D')K + Z>s?!q)YqnKf2Pk\507r=Z!inN6;)JS`q?A[f_WopYCYIl#VNcn!72VdM26Y'lUfn!+%ra% + 5GRtEP.%K@XgT8Lg%YAbi9eR=?8hrBpn&,3Xt@Q$hE?X!Q6kUpIEf<=C?f(h20?"Of^m>0m + eRm0u-hOdenY@jtPT"aqUgZI(*^%D$Tf)FR8g[9"[]td_L"+2(#nD$OQ)bqpDi8 + 9`Nn;h?43)^5Glnmq?US5U9U[1$H9'q)a,K1/I']RHXN*]O=r3$nSrg[ba0to\q)"Sj0kJ< + 6%AjlX%^\g:NOcob,:&iG1afMLnO9E^BY_%h@bDa0Z9Z#"m(sM2D!Uq;X87I<)g4ms,c>;G + A8:]",N6^X?c]%Gu.%pd'"H(Q$cqOkp/J*k8'TK46"mm@l%4;?nnC"OYdqpk%NB5OJ6IZ;] + e4iWdBe#;U;UqmMe^0JDM-SVNR$>j2$41PV9lu^rGaF8:$OML=E4"9"MV/QT,GI`^ae]D[U + =oZX(SYNqDk&1,XIG3RS)TOaK!*kq^>)<0S^#=4im/-EdbR7K&.REe%GiaTk7Gc5TPa32:% + U1mq[psf1MS!E:'2,d1D7tAUX:jU.Q67c7ha/k'FI_u"/_oTIh^1R'^gAa&Q]]E>88l"'p2 + o>d:3rSB,8m+(D1F!0lIk*HP_fo(NG'Dd=iDLLD\Es)$CrC0\B#`p?8ZA)@lEY%dtmU + Po42>37C]de!?eQO[o*e]7DeS1I3HoN\1\upZ6j"c`UDDXO.(_#gCXBc-S#dYVcM2Ia5/^"B-MjD#Z8 + P:sa/ZMc2D8F#O9L@5H8@dSO^9/M<*_qZeoQ#H8@Ao2aA)>6npqeI>Lao4HP"Rat<*ehEb_ + E2M4rG7(I#3"WON8V3>H0anMMfeCT06X?s0c38ebD + N'NAb4>*Yo+*c)eC>fYg@Q"KoTDin%nuXL=\dCI + ]lII,<;+$]B(iV3-I6ZZDfnm7od@-W^f#_\:PgKY'e&O7;?n%YdZl8BUiWi5q_Ftc&%bc'Y + '1Do/!%Vn&'6@;8M!PUrO#QToiD'1jN#U6/_+8^%#i;+k'h@:6TutdElB.9c4/!7a_id1oU + ]b8Hb/0GmG'd#W#`l;dfsYPr=_d$$1#l9jotTD`)\N"Yg3:OZk8Xh_WRO0h + \RZPsddZiJ4iF9pM.P87BM9uh#_[f\X&$s&'OTOA1`nh:<4Q?_(apC4s>#[DAM3^HP?#T5Ro.UPXe+WMeJE;6oL6Bes0qbQYG$?C)34P#:EDsp\08W[215k(i\)eq,JLbiNdL + E)YpY+;R2h1[53,o2_)3rqpdn.'h'WN]2Z)M`^cQt2-!Wk=T0%>/P!ER(Y4#"B3)M!0%G"q + n-=E-GlY7%0:2821BCU%qTKT^J.NkY&2@=qT.uNNcBYG6o-4C'ZNSo%Mj#uU[D+M?__^dh> + duQoe^IfF;-gM,?5;X0;dB[=94%BgPLFB&8R5]0HXIDe?rM\cFq"h#M[RdjLD,j[R!2u\Vd + ^4c(H*QKF/12qj+tgp@l?9&6EOb"aZGL,&"'+&fV]94fjrJ84LS.[el=k-o8D1*&b]RD;sP + (&nRiV[N=%a3d6-C]JW8.aBQh(L(#E[UBOVnO + h/q?S2#U]8e75lDqG0o^/f5[,0ECADr(>j!i3_e2mDn/-u2Xs?soorTTPU-Q(!@ea+8!1Re + !Iu'hdGdmR1?j';8pMk+'X*ZgcZ7OCQk[)P=bfZ4!DGM6neB7;Pg:RRdr`-m,F;>O(KF3%j + `Rk),rr;,pLItPo60rDCH9:VFTH]QJ"4r4GZ>=5ce6"q+P8L2'63I"gIFBcpqP1LXi;6,m@3O[U"DA + c;in[XmK9^E^@[p*d7Tsbpah>k0KQ/R`@TsL'.MG + *?&HN^]_#t=c*pA.e]_fb_!>H.)1(n"L'LcaP_?q2)X;c`FF#f-J3K\C;qjC>8RC>U0G>31 + B5FQs0oLlldfs,\q[<=R>S$-+f%q1u-rLA6gGJ#^*RFB'Af(bZdGnh9D=lT6;kFQmg;MQT? + q=@N_UO*mYHW%1sRKn3;CAf%]/@AKOB=>jsSf5H=(;Y57JM\&AFqGl;']B$'Tf1JMI + bP"r'?C&n,Sj5u8I^rZ/Rg1Uo#gg39H@H\8`V?.Iba3f/N9N7@]fK_h%7G["PbEJ=pLWe66 + G!R;:.+rA23'\s`B+OCR+(_"4.&O>1_5qWZjp_88^&>9p9M--6/HJU2r6TaPsMU;Ub<_nX%qOeU*A\.[L[UHB,s0+ZtYa0/aHOl)T' + kc*MNV8(5gIL7M9B)NrincKN^,P'n3TFF-j;pPH?9RA@6fbsOjM[Us'MX`0VrcTY\co^S5% + bdD8::;G`j0.9h;b`;A_hF6KFf#%20N)."%$>SfS<[T$Jn1'tN\bSjoo*:# + -kIB!jOSdHUC"TVL*TG@QY)3s3$m!ooW^hsBb_@1tI&`G&M_3Pu__b7W;mh\4((IPu#"2MP + To6I2%='&-09m.KsS"(Rif5&6n./&$CSJuJI2[UNXF]8"JAp!+;O8"c2[nG#mCD7_XZ;UY&$6Ce&p49Ko4*(`+!^7UeurkJ&Yu&q<\**a.H1q1aW;Bs$rm5.9,#4+b%C + [e7s+UV-^9%?SNYq]'XKb_lhXn2EN\f`Fsb)=ASSKJYZm7U4u#j(bcj>!+,,\g\(RIHNTBf6:4.FQ#o6P1@[l:MAJ#rMFcJNiW[I*aTIO1>Md/ + W_6]ZX)P\RMmpr-j,KVr-n;J#jDm^[([p5VN<@=-b63$Nl\+9GU^G:hfo1(%?l&#f:Pf.gh + Q#mM_B3SFp`HPP<[=LY@*XuD1@P^mXch*bM8q3?/-WS[hIp`JY?_u"p*^[cl7A%7;-b7$=b0a9MU!<7Sr+E,& + o(cC5V%Y977D\)Uh"X'u&+Q\'i9VG9_fcMV+fbnm0`N&DEYLj=;[VK*/VXh8Bu;?4 + 5K9)FL="p!_iapJ"`VWmTnGCV:b2gD$qFjd'EWemJ.TSBM%RUt@uTsC1WI&OMXak$7Br@?L + \tG9nTllY35l\gChAY/r+:pjeJe^+e;[>B2imKs*$_P,NmZuO8%@tB`gHdedK2YS)$qGGpr + XLBr^9;V&i,3#MNRHj84bP",NPg'P4E3TM_T63_r-%YTdV7Ai&1fY*(Z>-TFh5Z=;M&6G8, + Y-C(Z%a7qJqJQK3jd&qX7>8T?=/;ZrJOH&,3e_*BBdnmrLqQrAlIc^E6AX;+m;1s2LuRg"> + J9sW&HKp]@k/M'Hoi&h>m9]n(%N"Q3mg`/_hh]BdFb0Mk'B;`#1T*Btt)MugJ9kgdhZio0R + K69IT"U@!,&qq)9r\plWDr/^<, + /O6Wmt^_jJe]`'nENdl)@eN-"X14).M:)fNQiia0R[4AJXkh?*(ca=gc2\D"@5)le-Qh)bA#O.j24 + CH&&XD%UYU((UZ/qfii`:Cfi9`&HXXYg=$*C_m"TrMm8[Q>DZlC22`KMqJVG!'ggKN + V>9kn+33f%Z/RoC^BAJAd!LY + 7MkF7juaU&%!d=T5RHrI=Q:!PheJ">R7o`t1]L_3cg8ETA4_&;CeX3(o@lN%]mQ-R;&mCCK + 1_3uT>SIYbfa.R>a:"o*^%)P@iJ!,uEV_;nr3@Fl9he7=LuV_i"AcPiQ8DU&j73CeJ]*j_q + qNY3*nB>!o/UgAP63X/0t.4$XdM5t6>:Y#<]CDE8W?"ar%#r,+To=IDZWP5cB'"U=>'[;JJp!YSIb/ELo + 0u\F1BsgkUu35!R/7(FJ45T9XGg=iraK/*6]@/k+(J$F.R[&q=cr6*;>t*>pWr + rC3&1LY)P`^!uAJZoO`LlJ[P2#RS#X;;4@<%]friOkaNYhl!$H7rjuo + ,_8`2Z)Ot>A`mI__c;hJ-C&4?uQX#pAhcH=mGJ\"a'MifjLOnJXbDRleDa4!W3sKdV]0N5_VH1!2pAn$P3qKMYp^OO&m_C8]6jVrH:9dXa=IJZ)6pN + XKk;,BH3`keeU#[p\R?6[6(bn>P)g.._a6)O*2A3CBQk6aE$hlBo()"un%83)i,P8I7@kT" + &g43.sZ!+VQiAMSrpE6uo.c$_P!E8]6ic`/I@-UeF1`9Z6!d`:Qbm"AZoE:W4#G`?\C$*)s + m6fGa"456[7PGc%AWOg9R7OtPV;m*`CA]@ZR7WF/ + 7>I'X&[75MN`bW%Q]<+OWD2"WfL+>hR=>N%\QpqKkKRcYtqGi(t,faBFJk.2>LhVUjFrLeY + Ro@L#]R-i'c+M3o4T']4P^kmcItA4U!a=i=YR?!&DMK?_]lO3;CY1$qEB3!^7,q\HPerPf"T&hag3'Qr1$@-n?N/e8b4/k9V + A[p(;F<""k[5g(a+DI-V&!MMu4lOkX^B:=hjk7ZZN7`)`XTt"2JD/9(P-[F9kMP5erRL^oB + nWn3I3P%flNMb&k+c#Tj*R.#"U+#@NQX;I--s(:>D)$,jTd6WHeo)RXOY[>Z+QP?U^[*jrX + '2;#/;W*KY$,D&H?97p^U5U?LPaG7thbjp0?_VRW#g-XjP[hLI4!9$9PL.u-5%[2&GX>ID> + fu%9Z2lns:oJifa3]GhT_o%!RA3Dmd5#*7+_ukXk`'M[ILVuiB:2EA/V!,>,>#(kqcE(0o[ + -Z)VmEWB4cID;XjR:D/nBWBL:GhUf4S$fdp!6V2cV4)ZAGAr?-:P6m`P\Okls7rUrQiu#ch + )7DKOM#7D8o!G`]XFDbll&*"ib(d4!P:u0nE/<$.)ZZ(Sq103HPG_@"r_o!gkJ>jT!4'&XJ + 7%!dr2=66hF;%>@ndL&?>2XVaRi)9:N^8.?NZ*?9i*It[l#]9tfu + oUR,+Sk8hMTf_m=*2,tT-UB.\R",Cg9gF4/-N])X#!ci,]W/am"%qB=UQn?lr3"3G#KmO[b + :>hN1.d&d+Ak9f<:OFJW!\(-8VFldbKRZM7)#N4),(GWbSsS0C`Lui_hs,d_;rVPpp,itDP + n\"<;T4;GSgJ^Rmp2U01pW<7OcSDL + 5%43dj,c4ZM^klo?qFjMI>6C-pWoll/4ageBRe[Qr_Le!f]A1*AH5RRO[gFTXJH?P2%3VfPa?4o + fl2G\M\$'u@g8J3=gHE-?'JL@X&.U^["p2/"VF3$rCkI2ebu8&:]^V>!edLH"[*_ne:-,:) + FtUCdDUT5fPt*>*JXoALZf3JkR]A6++abD;Bsea6eJV;[BMIA;Iq6(FXL(H\\8+,;RDL>J8 + >IK@=V8?e0$?l"I<:-1!,-d^69@f_>I4p1oK^udqe>LPrN$4nO`':gp?TE'g+]Z-ALH."#r + /ji5&Sc5_3#Q!N97X!-0:f7EWkE<.kD(Q.Vkf9%9XB^m[_lbR7Cs+PJ^#h=/,Y(]f.;%Y[" + [eOj1K>ej?N='fH?9JjX\79PLJ>Lg&`$%U"P[$8!":f@NgEl+b["lq17;Lhea + d^KDRbuOMhQ2%gf1BjUjc'<-?RsCs_DaSX&c/!A@$\$A!E]-7M9-1'B*73\UB@?!'Gnt6eL66fZ + kF="clI1*T>P3@J,c0*[',nQG + SrMr;F%&4D1b9\PK+@l53&c)X^iq-)Iog>s?-\38VVF?Lb*LBY%*5!7S2,op:#;Jn_RJ_=8 + (.Bq(/A2KBY3tACI&-Xag"qqX)*AG3]_c&#`>Y'A*]u@.Fa$.a-jRetaiuq@/b?WTAZ;dRQ + aF/Xo,_E0P<;6e#TF?N^=pDCc9%#XtKE]kaY?ds/h6l$DemPj!\R"W)>oNWk&3>Hb9XPU7? + ;"4'XAE`K\0jdBEj!I@l?"R!@Se=Lg#S,dU+m(9bh6Jl@N&Ye' + 1$hc!C,nIJMI,f[e;ahp),Wlo6rCs54=B?km_6-$-uYK4%)4i)'p7G)5(*M;XW^:7`6b`YN + 7=(MMrikjFGop5nMCOm)e-kn]Is-Bm[pPj)eEBm"r7K'Hd1)I^@Kf[cO#`Jrm.S`/uVceY: + 'rLm%'+ibWPi\mR7%OEa!-=V.o@[Vqt0Jil_B^+ZO3^,0/-8+<^*eg.' + g7GWS^9loq2JZm+@s5>:%QC-WC'R?WJ26AABK1Y,4WR!U;GKEs^-M#sgZ0DAgV1S>(_qURl + ).>ljFoMZ^1(qrmq,$D"eK>V(*C1"MV)hkkj'`7eksrEnZ(_i=_116^WC!Q3* + 3c7O.r:'B0r6/Zqgi.U=XXa7p/Q0Ea`n7N!qG6m\V$1N]WE0gq$`s_Cg$ZP8;nF$hUpCNUm + R'*!:l<0^Ul83#9QQ'rWR,Rg."C=O&f + JnMV54Ng"3_[\./.gtM#:E=]0!HD<-m[$X-j"WAK"r]7E%6Gf,D'UI!Y08P7Wj)2>AG.?jlXrPJGL]8uYb_,u+%0ka1aEB`=7_mc,\E!`!8G9p]/jsfGOG. + @NL/!R.pdaD28BH$IFS8?L`ldAZ"73`^V`^F7mE*hKcs3_NjW;hBL!Bqu-A$b$AU"Q4a;G# + O*eEMt!a$\!:b.,'+p*A^!Z2K"(_k\#!n7Q$!8(G.cu7EUY%?JF5]5Chm'9p2@8tBD#QE[S + CG(-Z[K/'!!K\@oQmBk:-itM9K#E7mW#^HY-M>_.RKsT-?j:[X:De<\"UJ$2QK66_jeaZm_ + 6mCqVhEouCr,0SKnrrgE"8csbM#bp!<<[^?ljmo.KX!*K0c+3W%*D/3W`D1K/ap'n1&[m(3 + llO!l-Aif($un/.H^MDbXn(*dd?eq9=?a_`iPH`+L + ?[jM"((YJI"l4:YW-QDt"+hF8A/`Cf1=/^JpfHeQfjj`loP<:D-g\(?`,qSLG+?41[-Ho&U!bD=1go"1OXZPW&dTkNR0n=cuJ.O[c((CM%6=E]0OA%3./fPEpKpr + gEn7X^enX/*GT\,7F,OuZJ25pCN.p8aeJeV08)FERb3M[Ke-l=[AYA#hYW;gI*kVNV](5YP + TZJ'6nX\L+W&@8U]ME)TMi*4 + bX\-CSH$?30C3Kr%@[<,@IZ=9<]0l]>WG['7EKqqHPQuZ0JEs:4_YK6`L"'W$'P>;C)Iql@ + gcA@J$='B/WS.oZ"*2\9EmEL<;XJtup\UTKYBn`@pd:e-,m'49)"'I6=9eki7&cIsM'EuAR?%I':0S + N)r#l@_0Vg7(6En<*>/IN)2I&W>$#:)?BDH6Q7.E*1>K"otBNmmPKO0.oEJ/*G7a]Fn>/t: + OAKFA3oRS1+`MAD:Q6i"B,tTjsSL2ITJRj8Q;P^3?b7_Ib=-b63\M$UPH!r@"Etl_PQ,S%K + 1sL)@ff.1);-I$Co:5,-:=IGW3Yd?Ws$"d#o`7A*<%7fHQ#o/&6r?p#)+G?0;?'26oT<3J; + \Z>k9@J"-3D\,S%6IG:'dZ:HeJ15I;cT)W>?05(NF>u?orVe0h>7[I9kItm=A]u<\ + 8r6fTnscAD*8SSRkt?D51at + ZZQ]:8rWRi1Qj+"Ni:no:L#H-O'8PP6L)l*0R%>m,PU-@h"gc+fqEpd + N@K2F]k:t_SjS:]2Q/u.d;jZU6L0^1=k;h@ek4uH*S`a/DF._Wa#(<0.RN@3*B)4gnV.lAn + SYbTIDP=&p>-=X$lC,_pV4IJF]Q@r$!WE6'5[HF=2hmpC[Hotm^Td$.NjH:^H`lJBDIKJ[* + Gk%5:0&/L2MK!2R@[C]/Iou/!Ui>0)FBc[!M,=E\XlC9jBT\pbk%@^4jA%<-:Q<(r?k.>*( + .706A!"_1VUIGM!@3pB2S]_pAM88h04YSGMdVXs+GLghBBkt*'QlWJ,)9:dH>f\&"i*45'h + )^FpL?`+UKLkc@>`K#J]btk;U6KI(q]@Dg^GTfCo1cJ*$EQ`p(Wqrnkcbr;WDHr;N>hg`1t + G!C00\!FS;#JP[25N:F&55Y)2tcX_Y3!2^Q%d,H/:AduST3&X84s+Cr@&AgD$$"64I+6-fA + _T5dd7g(>s,@VLF0^^+BT`ttBa?beo]KbBI5_nk6,"+D>2.A[>+6-=<`429M,'6\>"&NkGp + o,"&XB142au#2/"T53_kq+2lWEGER@'@]#\le%p@4PoW`1;_.!5W + V.#@X8`#Z3u=`c6hWUE!p6$#3jZ_1.du+Mu=>))'3=_(aqPJ':KC1F=Zo(#m__#&r-:5aW7 + .ng'Y.!6?8,9\D;pCt>(sBfG2E\JQ[SiLC5!3G0ec3E*)'*0[+=6Z@#XP>I\Ji#Dm;?uHZJ + fQrIlJj8Ggl_$bTp)_-2HdADWTbK,5,=-316We4=?oSCB2mN\)!%\Ca&2)9omN.I9&OL/GO + s-8fL=#3+7$Oec,)TW`Bn3QZaN5GIH>QARi3T=1&KoQVG`d]C7S3tBU*TSmPZ#q1WC^]u-2 + O."C4^(lah]mc>,bL8dW_`lANM;/"'=dYJ5PSaZ7,"\U_JuV@]W212r + oSV0K/nk.Xp.rEcQ/IBYU._,snJ@I,"kYgt;B=M#S`Ck[hR<^+XY)_#@b[@+>/DTl8,2.pb + 0IUapHH4XKVa5$igA<%Oq>1RD'/Uad5:'mZ)95/81[CN`J&\M?!mr-sW^h[eM$//7,0j@Yp + \Q9!t>O.a5Xi).u,K'Y6:^q,00s2Gh7@dWj]ZJ#M8sH-q)$<-A+N-7&)Im<#>74?lm8`\j( + X:=<47)3M\E"ZJQMo^=c9=VjK+Z0%#@2ohFau0a)+/E&4<,@10S@Dj\4u,E=-iM5_pcM!`D=bR^J%jihin"h(sc1;JL$BD6-%(FU#ZE6OEM^ + DGoII"p#-,F^:hdT9BM$?96.%>N9"L;ImK?*-'K(>55@'5d-ig2luclT-5;;@RI-!d'P.#b + `@o$Lqdk7Gu%U'E3O/8d@XXLA=#4-[nn:qa3ecYoUXp,DHH0Faun%U_JiQdSqcQ\:lE/]M0 + +1<*dJjNu=hYj,R&QG*'6<53+R'P#QP[[TpU + S%K44StVGS#'iRJP[j$1XHa9?rrL6mRr6o2uQ(ETS;a0L2[:<9WXhEZHLT+bRD; + 2LnOjNaNY8=bt,FjLqnn@U'cFhh'*-Pi(+SK@N#[S+7MUWW:8/.#mB6Qd83fi0NaD[L)E;: + #[iOP&2X@o-iu\Z(h(qBkp@hG3="Q@e&aG1_SHMW6I]^;/>CG8J.;T&SdIa*ftjW<&?+.KX + Oa@gQK`N"@g*7BEsq":f20E;nAs69bh&pg+7R\o!H&n#b4lDk#p>TD/Ynl2=PC"K2Jogl%C + m"+bhOoQ39f9\?t9ILL>!XE+[%LhJ\TR@uE549j15N!Fi!9OfQWZ@s:+S#s7&no#mD"^bEp< + RjT&p5S"F!p.2+/:@rC^gt!#fNFLpnrk`Dj#C,QRU7Pq6Hgm8"TQtX"*\=+5jL6T['-Z/cs + rf)hH!NB@?]o@4]aAr3FiN$uZ#Ah#3;brQj76TkG<;$kK$7&l8#sP5,&j>;,IGb*)u/EsEC + -DZIk(!A(b)1Dkr;)):?M!\DeUCtA`e]S`CL']F^$NRB03?5N^'*Df5P&4H`K9TP93O>'`n + :g/prS/T6D2=k8q_>O'neJ-Bk$]oUr:k4_r2CWTuPDqhkd#g;^[Kkg;/V"`J5fsUgF;R(CBo*9TbSbBM4TeA"TVMq#Lq\W1 + 29*2bD"JE;Ml%]\F2$SV*Lp/'o%D&-.4h&4$^?0cr]d,2SG1h[qEs8e.Q"2+#\N'@B*5@(="uX&TpRJGo("OW%i];1jKp[I&S`())R# + HDJL,IYig+j)@h$2FI9,VY39l0kd70.EW&M:WCl^\9/NbD<3])<]h>`K99dCgoZ'W^a\;?O + 9f1$fFOD9i&cBgjriOJ[QS`o-STG-;nh_B1F>tG?"$IG84)#eQ4-]EB/R+^qe/'5j + 'S:R_h`M.L[gWDk-Y:Yq"2.=:_,A_0?k,$kjdF(%TaTEmB'\f)4'k`_Y2iEo/KCI9OJd$D= + Uh8Z;93&h"p[KC\s?ZMQ=3!ALq/!+E$b+QY!1]n:6UcflQ+(-u/J4RJk")I6)'pK^O%HhQ0lU"Y"6Bj3AF=s7D2a^P*(H + 1;@T`,Kg@;Fmb7"Q"=d8q&^UP8X"Z)HT>j'Ve7fM&TFMFYr-$<9JCJ?$3VbFdHZ^pLeY.Bm05fG;j;]GB%h + nFa(S`@;-0_nf<'C-E!IM4'Z"#i + @=`^p'mXphH&"O^P&BuVH%#?_IJ$fnF]j1$>^&K\+/"2k@$7+3/-R?= + 83M]0;NsR_1BY@]V@BRLUILHR/BJ7=njh'or0PVhLN#=>66u080YRBdljA?>ZJmNDtsA!T. + lX?b#R"7ph*m6Y)tCJbB;u&P9F8RVZKdGL_)!jke%jCrZYc[h&mmC5UmQ.HlMbk"?%$V(*, + 7j,;m!92L)W=R'\?:p'/*mcY)jiBTpi*PpCh`'6SUem3)a&T^G9* + ChT5(`+O[f..\[K'$,l)75BE[m>)rq&5Si8f>#"2c4fYM4<27G^72rq8Jl7adi1ISN4rc=j + `dch4;+8SXJT1q<+>0ko@0k])_C4JId0qPC6N[TI**Tg'N'ac$OaK+uc`tQaM1O*IF4tmkb + ^k&PSW9.WUe-TNBU;C[6D[4ET+JUc75HGNjSQ9r,?mpOUTji#VqKkS,16(:X_IP,"nPWE<7 + ==BH_t\p/TG*"mNTRY]/f9M-9a0)UmcWAUE!pcGIVH!:';rVE9T5q]sh>e7=Jp!8@M + dtWZgL3R;?+ROpIdt]gFLNn/$(%[54c%pN9K9r$6Nj@Lub%R + gEQ9$on?Ic2]6p%FU>HROL-\N>$gm)_$bcNFb1*e6(]]39Olm4ZaTKNq9eo/TBA,]s1#[!ii#2X->ag3V&]&6sYjgCM-^(UDC6Sn3C"3c'rWHV#?n8Gb?s3e$Fr2 + D"PS>3Ue0C3B,so&pPn!b1q-e/F]9V>m(Ld4Fi*dm6CR4$'QS_X-PZgI6#3+Q^78#Bb(X/,ZqIaR)1&-KhW#;X7X9^%QP/?KFh[BmE]KEFDH"=%/",g4^b08@]E + an<%nX=:)OqV5=hDW-8]:U:jo/iQhJ)+9CZ^(_k<1rlt!\E+IrCbZ8Vi%ScM`k+&*F&?O&(Vpt2BST=CW;g6g&U)X(nod;.5S9CIuS^-#l + ?Q@(1fP?N=,WHNt]!`?(#>P:YHaf4M+u"HX1;jX<6DU2!s9D^`W\"nfRo<'FkEQK0d9ROO/ + ]5aFD*9nE>K.Nt7,e+7'1gAiCWg8)r"Yq\B,UP0"utR7+1V^1'`Hj?bS9&N-%P8qh>LnV$q + 6:T(Plis'p"Ttf2q$:QG.9;aDooSSOkVcA5s.eRPq@<0)3f5'^,%?5F-M-op=kS@kBX\s5Lo'@#74_@i,Q9>IT!^_s5nC5]`+6fR] + TXUqV@1HB=Zdc5LkY;#E-L[o6G_`[9N[]m.L7)*rhH&<3#eCQ%+kHZhO:p5O\4GrkNIU#m! + ;q6k9Gdct!do_/OK@#XWKX.g\PhUb<0E0]Xr!6ZDrb&4CLP9+]&3,Z,4D0k=?%78\_B(rc$ + 0CDo\4S9#W>;8dZ#8$W8C+\2)CK-EkW1m9-*E^ZK)2bl0)k;uF;`9Rqu,lur8:[,+S5*4qNepl&R4UjeVFB'8Jd + H;\+6deqn-VI@EWQRk38,YGD;fr7*:t,(P92\a^*E,k+a(bQSqjk4NYQ_R%.".N`bOYT^u#3j9[kILS?TP/We + MkK;1!P!.lqGs#\l+2VAWG1([[W.r9[]un!3p*oT+6'N111pp'&d3bTBrH9G-OKg]D;/sc\ + S\4`l+8&"_83d&>L)uXDl18A^n:NBY&Eg"57POQj,iUpm"qt%3=of%]RQBImk;oV*7m9t)M + [5Ncu`P(8(2Rn7nA,uI1KK2RsfcqAXZ,kM?\)/OPW![#)TX8pV$TTi-5H]IH$mu3M:26*ab + 2pH00CTJ,2@$VVi9EfuWrW^15(c3@W&fEAoA3jl[1WT:3K2?Rt_:V";`]P/m + E/8-nBM^GB4=Z$e)AC!Hr\9.D0nsYi)FJM%1C*hTL&Ab'isdJ36&.1X[)X$d.4e0BZSSo2c\jf$C!@p8QU[cR)O"cL-5=:`-M6+fH&QW + 9$,`0d->+Mu='D.nLU$tFQp"`5AN@J6"Wg>/gd^b\b1T".%F43QW[2+qOVj-VA;l+H<,J>?FKX + k\ng4<-Ok-.^r + I4i5!B+"g2=9=:?+H!!IP1'J*sWZ8P%k//<_H%&1Wq$P:OMNJhAcR`="LHk6?BGmFEZ,rA) + QdB(3fA!QB'EOGEb4\[^]Rl:/T9kG + M+82[e=&%V"l)Gjh>Yeo8ie;IU>prI6ei!"e2kaJS6\A0CetuGOYZP[@JE7t+^QCI0Y\8g(gd_l<&]QZ!ZpA!?&PFf8r38n,fLaC']Q2nMSo + ?a$&\7Q"42N30n>0mE\SGA7SA_<\i&RDgDhGgILm]u!.Y'F'gm@u-lI-%jmn6O8plDV;CY/ + WaW6fWr0!4@>"TPcgRLoKF!'@G/;K1e6)>!c0J]+^8tuoi9O<@;!mo#V'.YYV;4BBTi6pib + H))b[PF,t29'LVaDtM8oRp[8aZ8NI*S-I->1)_WX + nC8Rf:(l/lb._NFp>k9q\N[WD2GOk?bl\D7b;,NYA_.Y0h1b8H0/Cf&>+Dg)OufamPKVjuT+$F<,5K\o,R&KV4fb0F1tn6-eu4* + t\!]2Wr-9"m&V>.tt4k7,o + =Ya^93J`d$23Ee[Ib9`oQ5dNs;F%F+!dBoWT[(9^'Yh-8;&e2HJNZd=< + r"UQ[(,[:Q6$lP-]2_.cE"FU`!/uK/TG7K0*5?MZZNX + f5]je,K0qng$8bur%*Dh7":d8A%ZO^*9RIB\X%c9VA3i$d%T?1]";abd;RJp=6$sS.A/_jt + dQt`l6.@SToI`j3ekKE$9iTb>CtR![fhJR%9t]0(bhGkAgeHS]:$ge4jP`i2iQ_kk:-:6ea + ?2C,1W8qK(Sjc#MfcjY9.Kj#cFc25bW3u&Xi&<^JT)G,TH&O0+3tqtcQr*X9_;+co?V7G:N + Z;+0H#G?*._K5JVXa8fFGDN!^pbt!$)%C865u]lHZ-.`8'TR5snCp:XEoR72(_YMRZ),$:S + M679p2$\6#%\M/I`j/>G + +B?$0N\u]Wu,](KA(%#W.0I68H%uJ + rh`A1BjEbe^SBd8R'fgCC@AYg/P/@4gWm7UYN+"S"XU2Ts[]\',A54T@G3[eN,%OSQ/9V<\ + `N@,,cT8'b;Sb$NRC@#fU$n,cX&tO35.C$"NNNLee0cC,3:SK1JDR1G:tF1"L)+5HSTT;uA + FF\2.q"-3VCU$=Q4Ob1E^.bet6 + V9[5q;lHK/CVTdE>$l_'WdjH-b:,R]"/VQq_K\0jifPQFHI>M'KMHGjqfU[q&c&>c\MqIEV + =S7\VrK14CjKcG[_^u*gD3[NN"PIOTcnnD@[-^W,mTH*M.@fj&opo7?4G2QW"B>NM!%[>Kr%3X;d+#Fd*2sFd>V7sMh9S7"$2SF$25>$ISh(i\YTtM]s/H=T2AT*;/NYeIC + (,qF1=12X2mtfnX^Wk*a>`g;?\/j%3`a1^)I&'K.=Na=9X4`mK\p.3s@"e9dq&5G"5]Sd`o + j2`-7_l]WNh>;["jR)X,X4Ig>a_*Ip3RfLRd26%f?X]N$s_Ckb&oLf;6J]X?B_S'9@Ic,e) + RaOH1RA'V[)P7#gGg7\sL0a19NjlnKgl94f;8_$^FbN$<: + mReL!U6Sd%6g'7'=,6Pu!fa9,qkFp'IPOW'hgJh:KjV-EXbgksK5`f'UFX"PCXuIl$9Y`>< + G9sSBi^ueL&b`hSOMBnG#XI^4alEuAP1g&H8,nV<8-nB + o#6E6\ZKpZo]Yo%'ht:Sh"LU$3BYUtWkrie>6iQ"C]4l"?$W(& + c:^qs5'/I3&Kg#do*XYt"_H0?4@+ngY0ZNCEBhJ0!oD#[;0Zel[H9NmO>)(7)P.tN;c0X7PK=\_:-t + [!q'N%=i`VrG57b)?M(Rb>%Kih]n.JG$*EFdeKjSc_PXhD7t:urX[#Q(QTb:A9c=pAS>)l? + [u)mS1'F_-cEjgOAaUj,et\RjkVSK5sR/n*Pt@rC^D`Gc]qa__#Hh$;3S*_pG*HO"FG)V/kj5l= + G@?Ni0^*(@%,%9cg=nVQ7s(*UAHhCQCGrIlVRo3cg?Co:J*ccTH:+47ho(F8%k>s$:`6MJJ + Znl`NGhJdT[o^`1a9('+sYJMrpHp^7)3%"OuXJA5T;7q;6gAp.NoG#/Qs + Y@I9mB_J'6G->^3'"inm;?a3mgDQ8n4C;r38o83]`cB9(+;06#l3$`SmDjH_%c4`Khfl#KJ + QN[Wii3#](4i(;geQsBKuMhb=`07(pc_o"R]8lLlRlN1mcNoKmUGsZfH*k<(=32ba;BJq*c + F5Umo;F;?>=D-pJ^2,aM.K4m6KhEqtXWLc(%>W@aL<)geLg$@r8-6jb".1?c?n[)P*J.6l!P_tBA`&%sEIdfk!2'bA#RgMGZ2g5rq_th + X5IGi6[K)r)qeui<0E_AKI3eSUKP4QZ/rFX*8EqO`e5;9JSI + XS0Xo.1a;LG\U-dq[2poEX<&s5A&Ii;P+_r\*>]5NR>Qk'g+gra4i;O6D%\&Tfe.@]h5l%n + -ER46sXYrr5huB1W%E8,jfV=cZ1>[nLnqp,GS\!MCDed[[:ZIDD,dR>qYc,e@'R/A + [P`*QR7"C_&86aG@D`&T>oXYr_d`(\]J]-OkPbLbhn\2)G[*][%b>P_nX/Z\5:)H75L(WU4 + glt[32"uba8UQUT[@Iq%6ZnOrmP`Lot&Cka'd?Hm2u4;PA1$)?FR7?,,o,"ctpJlS7]edPg + JL_3DB.Ys.Rm)o_RBM_u6+Vm<:X?hZnYMhX31I^3BH3X6K;`>+V@9\c0hIrB>^g8_Ic1h%%lZJT+/]Bj?4'AnE`Vo`25Pc'1FW + D<>LtL9O<4g-8Cs=QNcp35`ir=W%3k$:fr@D&,_8.aO2456Bu-`N.8s4FIOZ#;JCShTCT(e + .n^N1[4\*5nVJpC1BbF#bbt83N_&B%#XfG>iIl^+'9Ro.+6D1,43o_$ofe^Rr9lmHkP96c: + p_MX7*u]S%U>@@r>eEF[$<%EdWDj4?;i]6ED9B=jZQ#pp9r`U*1m=/JEV6u&-Q?FR8@p'RQN*=oCH"CVl-iKrXg-qj;C_e=bs)$N!jh5 + Wg(E*3S(0<*6F'TSfeTDVgM>qeV2h>:;?8o:XHf%83?@Uk!p%s6s=:P+-Q,ik5KOFRM)27% + iq,g[,(mZD'in1C`CPbFm[LrX]^EDgn\/pGmfC2m>PAH>n@ThP`:4gS&'"-EG8\Hl>`U>TEn + m9?b4?V129teUs#JGIh%**)o\C<\ddCZQ5^6:BT^Ft)k7Q-lp78[.g\43>D;H7 + ibK.c:f3B26qE/+-A9=]QpR0;$H](m-`$#E6MOmj#C$OU+=[5FJNPJUEEF`1qB-NOHrs#2? + 7b3$,Sk'T'uJ=0IkT.QSmgbg,,'@@`T4eQP6,+81gMgjGfc?Z`>E).Pn"tibeJciB8n0R1i + ^1X)RECZ,GD<$`l-XO;eg30aSuquS707\kT3-;a#VQfk/`aXG##TtOjZ)NShn6/,amRQa/& + >KUY.!?On(]aI/M+hoIgCcl@YcA,6_TlJn8C%6*e4+9,&M&:sp1JBO).lPX:0<3[@+LY$`I + '=H(Wu[25U62?s6u$UK#!!'gej_:bYAb@#Z-if,C`$+2Zhi`Vc"G&F0K`0l>Oj@mEY9#qi/ + -j;$P:1E`ScNNU,-_bsZap*5AH1[i/g5Ji87Xuo[0gO7%"]Q0V:XG@t5Lgb_<29@hMLj"W< + GJ^b&Y+\U@ + _e,rI$>b#/D]76`=fYKiFM&_H=\iWQ\\HZ'Il#=S5[(XHf""QVQhGj]ee2VQ\P(_-$"&.\bXjsG];AG0gU_SeIGA!C)"DGhUU8B.j?SW'!j,u>[FVJ`^Dg_\XdeggQJM;M + 2sW=Qjh3'MBFTO>rZK-ufGikrf@ntfEpclU[.]g84;-'s!`2SuJ.Mb^>CC\_i=Z_(1ej-(9 + M-Hj/VT:do+P#GHJskML0(mSKj_2'hoJ(h@4i\?pk=1>j@(U9qB]MdO+<8So84(>.tt4R'k + 5/l0MuN"7H5,9N$MtnAlf?L;]CIMZtOqDXOBZ173$:jViL;sfghfUMPKSk2aT&dMO6FM`5o + W9-?et,pi+*jZU-opgfd]P3!0J+5egWJHta360,(4'$=<").bBG^0R& + GK.os"_MP,]_O)aX1a83@5hdCJ;/`_l$][S$'0\ZK_$JW"fVj<*c7rfR6EHX?NN3%C + K08[8Yi1M"7@IJ(:_k5!oHmsf9t!Pn + I0"lNW)JoH/R_K]Q@ZBJ*HLT<.A\>>_T[W^##lUAg2NR6_;51//W7<:I0c/c=Qgfn)apL1e + :cYCh+6!PnM3p>-7Wo4.fL#pQ8AF$N[sc.2D + nln]3#i@H+#CE^&][".ML8j=`KBNAa3Q"q_m"J"n8qS9d@$!liQ9,bqp7mhK-bpe7?\@*,Y + ?JFPHnWnPX@!9O75IAJP5e@?uQ]hCpi^%c/'.Bq7?`kVf(4g4`>f3Z"c + oAu?a/0EV;AIj"/Zc!I.sA65uH=ATd/9l?bG*a.c)fBQJ7NJ+.qN2CPL>E+gKMKYmLR+]hQ + g'sk]MMtrFY,P5ogFu[..\fIHODtNPG6dVa#S;as:l>T5N + sLi0#gebAOI$:#PmS(l$8A(Z:oaqbY&-oTZHa^8+jdQW]aM'Yim>]OE6@1C2%:$NG:nRYN] + uL+HK:H9Y,)f7VX$n5lS1dT,+TFbQmogW"s@aO_#3QboY9p*/5F_2N!SguDD#< + t"e]IW.@:=Bm_R3NrHocNl*LO_h"g,U&OP;dXLOJnOVa2XA9bY33'm%>t<_OTWjh":^[Mo",%,V^rBX/5<4F0Nd(K>heaUgT%RaOKAk1h_!::$PMPR''Cm + qlRrn802h6lEq:rIs"Dh'0=(m;'+$?k$S`;*0iJN9L0Y0YUpBL-1/PeBOMrtoaPueI$ujYT + F#stqh)#d0&JIYEYnI]\miog=])W+cJ/A>q-3Bi2"JAWt+Ee"jS-gCD&jY^oTct/dA/=-d& + ?ua&Yn8e`NtJ:L%80#)6'*$n9Ea9R2)D+^OYmf74Za6A'U-!?DQso8T0*e$J&+OhmX@j + Ij;*kQVO"ms[F=S<(U,\gU$fKrqTp-H]nJok>6qhqJO%C@k1<@d8F-rMfAdeB65I"X^G:e'IPYP'$^r*<3D+-cJa`^hsIAL8*$*!AsMFEg##'r + rNh&!\c&)D1a="0.QW."*1^MZF$O674Z_Z/+?+]Pq$24bu58&/TT@@n7P\K)_lCI<#%o&!`0G#WZJIBQ,?k7a'1k!G(KNo'qncqhc\, + s=]OTn!G!$T+9EZ58N$3je#a;VrZna9415;,Yu+oX_dN)M1Z@]uJ0Kal4%PC\4cB\VNnHTs + /hUP9_+4(=!$$RkkeWJZ_DD4&PQbUFPsFdA5o*KVe/'#[)NJg@68>dbPV!i1qa1DaB-=668 + 2pT+6QJ(:oFsqdZ/%ht2/*Z`PLNkZZst0g@ijQq$/iOMTo'_]o_4)^E)5Lg%5fKa%T= + !]ED8epo]JtFr!.au$C(&\dZ720OXX94:bnY>P,2"?@k+rSVUZQ+L$*R+\P`V*/2\\[odq_ + 3Ym%GY@b!Ij-:\8?GBXdAG;XMe=*o.XIs6*hGLqOn(Pd7eMg6+qGup&PGE[<=T6\e5=/\+L + Tai%R]mAZ;HSpL4eVSX>^KB+*1NhqbDE1k/cX%F6?+$P[FfKp:i#6gk=uYSp2q*6+ijBTEI + oVT:@9OFTLt*U]D-.!5ebqQR?XT).JMn*kPJ;\"&h/@J%%uU0+Fq3"-U$$24nIegFtnW:5! + *Qh!FQgq+LDNLe1[+Uml!a&1^Slc#4PYjAQWS23,9D,0,b)^NGM0u;6N<+[2ZPlnof)1K8K + unU^RM!k!0KtoLBo;L<*Jl7*eXLdG[N6jRp$g5;o'jO(&G]WeneV(ZC;oCrZ+G:,H.4EOX:WbnC/Tl,7#>hlm!r5o>'boG1f66`>H5E'70]UR8TSR/)r6R?s>pQ'rS[RQ7"/=f\!ZYF9aC"PN99;MX + kUmb6n\lkl`=/Gt@#G?g$3"5)<"+aWs?t+&*p`"f'I67S0P_$4QBC(6n"rKXj**>(tLl[2B + Sh=R-'1b/"+`82X@tp:PL=g)J!9o7d?\`lPnX+dr(kmbjFbmJJ34oNP + S:'JZ8ad*F[Eq+Wq?ii's,bF9fU3$7i+b+JoL]g'`GH%j9"(!%[!?\e?Hk& + 5h\4Mhqqh_hPJ%,<7\"I79RV70fnO],B\k0db)#Y^\kE&gL#[iLlkO8_,HjWpS]N52/fc_] + /&LR<,9t;uR%7ub%r"d+j+5i"^i5:cnq34Th!OA&Vm<=g5<[JS3b*K8&Yhp\\[_]QAXjXY` + a!#nRDjMuc^_Pp`t6(7=p8%)!MB,2T:+l`F$H8:mMB;b)LRI8]UR!-j2a7;ZlFM7XtNn.Zl + C7W"JAI[mU;en>RS@H&<('5iIF8ra\chBY5CKO!SkI-Jr%U:b/3%,f\HqJ$ + S*Y(bSPJSqZh]aLb7HPPI\#CqX"_#LC=BrW"7dPSH0F#/HRbuZC\ZS))31u]Hi4=Ci;?9tuYDdt7rHaX]")/9EBi^Y+pp[M1]jG'-VlA!Gia,L`'Y"h#[Q3@>5Xcs + 4uMih;O)+4lP82l/(7fh[[XJrV8IeqAC=Ti*HB?iIg&P$m57=cb#eo2W'R[V7O+ui`QBCJc + Cg9l.3oE#=*G=qo$oc*PAc(]_NuT]@Z,_2fZ-Mj[47+>N-a_KF!#^IddY>2_C8>92JNSF.# + sJYUu-GXOt/n^p:Gc.Y5F+(W!Y)Fr>YD-7-NJ@oN2W`EoI]5-)YoXm2L]lX$p`T!@V<\*O% + *llO46?H_\!c>9"UIi:=ngu68cB?&fgmN1Q2r,qG_F71)oTRQX.>_1%(s2ge'1]S5=BKO;[KSos"p05crS/@IC$jeFc.qd5]]\qE8q``+oh3A[* + ;AE)mLUBn*_SCko(d^EG'?6!HIXc7i]&K3]_sQoZjGWYt"]&A]1sp.*ST9@MV,X7 + ;P+HGfU'6e'0JeM_'j\:Ld<@BP*tY$]4^4'W2lnc3I1f3"ZL(oM&suifWY/uR`f?O>&B>no+Kku?SLm!9:aZ']t.OBpVlG)r_"T+C\V$de8@f_YTNYQD + DN9E?fQ0P-i3-VR*4W5RCr6-Ek[%eFDK\bSTC[X^XikKu%!J@ + 8`JpeUGLr3(k:8<2ku3mhpHk]FBd7DDKuZs.XaIAcqf\)?`MI%Br6RJsM`qRo)-GIQ#-pqP + B15H!9%p\fPjT4NRaJ%tI]!&A0e/6ZBS>=!-r + 871dCW1eSN)j^D:,7?LhZ(e*t?<`Pd^Uhq$in^MV$+6?@"+%QQ?Kd'"7/;GHA1.7HRag+m: + -;!cmUFJkK[&5FsZGa4(9X0'#=)+M'amE+oMl>i;)E,+7oD0t-V)UlPq>U+o[P`m;\\m`:Xc]T7o<6n%8Tl%'f + Aoj;l/`Dqh`)a(u_S/^Ak$f1jY8?+4D&Fl#'"jE03:WPa)?2mrC-?^K=>EF--]s+0+ + <((Z"*(cE%^@o?Orp.2agW'!$DnF.MhEG + !nDJ+#)C\307a?[X\9hq5*q;+2@A`IfJW[J+*XA&+QAP?mGr,YTt?Q/T/p,@gXN$K8,fJ. + Mg>`Y90icuLDM^qLY\0Rq+UD&l3lFsghMSs/sE;.K,aO9I>782@&A,V`1Hng(7Mi3!jMHta + j;KX$ikelj?(B.`5b9c&(7?)7"=04jLc(\.J=!h9gb"GdkH&;"FdKWd.o`6Cq24"Um:P;JH + `d2KQ?C8LI,2A6:&9J^R(NNp3c#6sY@UuiZWV_q5nf/o1,nP-@52U=@hS*.HX + :-$7?Kp:^`8+cbPM:d9=1'=*]^3?6lk_,k>'LHJU%8XP-5*7lOTG.89UEj,2;2kcLB_RTW8IFXj7&[[>M>AP + Hm82l:]ZRo?>,:?hF7W"efr;u28GC*cBKart7GM53_/@#&NWBr.!7t_5qf?\8 + rn,s`m,MrhMRMq.gc-Z4jR)WR\.2rh561^nJd=aNNks=//!Y0]PI,sPcins()e4KX?k':_i + n_5RoNnBN]FRa!]r/%+h[A=h=/,@Na3f6@4n!epZH#07jSj)(qg`Ver4jiU"(5@>^_ctR"o + u^bCiLU8!uuJMQoBY+,U^G'7MA,$.19R5(E'I'%)Z1o%cR+'_pIBf`(WaYnJKCe6i7r,d$8 + *k0EqOI:kD,F'9,PA>/q4sMW;!"O(.lO#duYoJ-a@!_cUghAFt>k8Eh/cFEo6RmjSqW"!>Z + ?9?RJg6^8Y%,0Io3O[K$pKhGWTjHPqS"h%+eZK3Ipl$8LKJA0/^!#>X_G-SiE&q_Wo,2l/ruCk.V-c0LGK+Trrp3J+B[Rg*Y*JG3jW5_C,ro*:DrAd# + JtLrUXqO;SEeZ=[WP@k%m5!9oKgPZH_ETIKI?%+T:5n5,+XKS-m],O'IJR]XdMO)I1N9g7\ + jlNPI3D^IFl&SB(+R1a*-o7iOq19p"p_GI4$ + M2ONA&W%oks@g$:S/3&Oa;4=74MWjPjn0+Y<-6Glhc!QAjiJ`@KB\F8_K?.k8Vir!#BE]un + )pl?f^HqM9Fb-F]qp]9Lm3!+.U1+.cuR,F9SJb-=XBNl9;[.]]WF<69R>52,++c=lU#5rBj + f2&k=7btT?L"1&?o;H@:g@2&Ans']Kq=$GHJdXRO%b]8W!cQ[KQZ0);*jm"G"3A`jjJ:r&DBMaLkp$hK3 + \!!!?`C!/;(p]-^$!S!5/o&ZiJ7U,$Vi,>:%`d5XRssA-JhAfX-6?1mA9QT].f/9!l)=!;I9c + j%h#+lp1/?2*,V@#k4_toJKke[;/jOPSL*"G;ZF)26qHa_L+5pHaW`Y_8-Pg/00i\A&7_h- + o-Ln'5"[u+?"k!6:6.;97=ZV-Gj:@_H\M+F=BP0.DiGA_Sdoje18Dk/AgI$_XoP!lnQB\1. + )a2_aH;u;-:+a3JS5jNWlG-r)1g8J + Sh]<lFZWY49TW8qnbiJ_68L/^Y3Bk0L1jE1Z + V+O0Ybh+1)n,aMs7"*3APsJ5-sUNlgDLI/#4E[ahgE[1GB0?@u'h#!VBF!0K>ktpI[7:1OQ + !oqte(=;G(oD2ulc0E!$).!'h5"^`,S!)agIF7*>*jb6(4!Gi-[h8WD_A^nB[D\6Cf"#Cq% + +^t@hX&F>GD]"oAuVXtNL'YTQrF2:fk[>Ym;KY`a;>``b?9L=-8"<491@SCfmO\?k*9H^c[ + +gtR169I*B1sO7ocGNo59]XOE'[Y)McqQ@EbrjG!7+KO]f$-n?c",I$FP+j6g!-nW9u/m.C + c=_6TGo.Pc3-.r`&g^>50buXc9eVNUe*t-jj%*uYW0V?>is>-k"`;H7"e3.eDnj,It7k;!@ + l+jYT*]9:ergK`>mc0oMidkU6]G("@,G1$bJmnnBgFaY9Ud/'>Mp>^q;4>;*>Vc5WCE + [\]7nP;1s/+I!NB+Gs&JRa0GdM\5%b$Ifp&c;M9l] + 9Ss.\Hnm1l!SAHF>`UVHON@PU8K';3'-C-iIt6Gf:C=X`J/JC[Q:YVRdt9'_gFH3tmn(,C5 + 89Y[j7:=UX@_mq!DN>>BK&O.p8#L7<'\)g;`I":qau54KLeS[lQLsYTr".[m?^jak]OJkf;1cG]%87-H1Dku^Z8 + fL(lum>9IQMSlu="u'Wo$\&=n=b\e_Rb`BD'N._LP0\!/cXWk)L^O3e%'=J7KUhs/XdP\)r + ^GWA]!VLVopsGUT$SA(*C,)T!"dUOVM)`[Kp(Oe>\?luVU1urS\hq.fEd4^C^7Wa>/Y;?h% + @[2'#t[%;,n<#.J/@_o$LuiV+hcRPo.oV[aCokgOsn,W>,SuBt*Rd""69&%3j4@,I"P-a@k + )TD8DqDLf01f]tGlM;qm%_J!s*D1)a'n2(h'=0>Lgnt?UAb?FMCiS?Oug<9I(KL1 + 4%M3$J&1rln"cM"JK&Lj0@==&9K!AEe\\q2+fN'!%Q5F&lMdb + s8fFA[+&4Zkj;(QZ#JP[0#V3,13pXqGP=[LTj<*5Xd%Yo+T"9"TiPZh4u0%n48:^rsuo`8P + L1!h%*"6`4W0Hl$ZV8d?Yj+XC#G\;c8H2q=WJ9&[3kV.X;XO-r2d1/Fh26SZ%Z/ddI+"adj + Xg:l>VV>'o>BLk)NO_p*\`Bu2#fq;*@`JNM+bGE]gYKV-73u1HfN]^Mgbr-e74Lj!`eIL5K + K)F)>CQ`O2Zu8rgpE2K]TV's@gKk7"(tUT\UX]3E&iQL:,EDShITI)jKH@!l.$3I<@(RM]Et + sQ10S:c4YF%9Om\*c)TAk=+iel5pRb;/Ef + S)"["p9?e"dmWYguA!rlbm+>%_^mSTBkp[clpsE=D8"Z"9:[i]+L_@#\8]2VGO/1j,FBK+@ + LnC\:r"=j7^^:r=S*3cJ&iTj??$"(.B=PF@IZ<7qEQBIb!M^9'!uba0sU\^^YhP&?KlsJDO + +,m"/,);6\p7dWSQo(0+U#+m!8aj`]^?pauXE8$FsC"JAr[cCYUc1 + E"78&',rh[e3Tb0?56]U"KCS)hfMhM+g%O3WR=K + Ykqr@rZT0?b[H8hMnH3s&OT0bcPVH2Ois_*qAD8l-CJlpE9#S+n@Q8E^q>E:F#F3-Ltdsnm + rC.=u`[]/P`Vl#cYL7^5B+#W=j0QVE>o'W7Xp`T\]-*2 + &`"\;qF?F4af5.WASoEMann2l9\7:O*DioVo[]piLI0^JEgFFQin8_F0,.Y@rSKEfM-QVE4 + IB?uYY/8uJXW#k%ooffdUZ*@T;L&L7apa"8G0:gA>Qg'QItlmfOX?B0K<6i + i,E6;hHTq(mK3r12W3@B`,7.U>Yf%pIE$H.C!Vk]2t0rR^iAe0M!hEP8LEfu&<'N54"H[hK + WnarY%6?8A[8/^8YssCCn0XYfA2L7L\P6]0Rn^?2=rg'8hJk;c/,ddhs]sjE!M&T!$-XR?m + ^sIJKPeBNd0E)#=(b*kI"1&L4.T0<'[mA93Y&?=;Cr*J\^ANBLKLHP4L3#+Lo1(ZQ]9[e>]),X/o9[,e2i%K$a!6C[ + HN1e62aH\A_YbXV8cU(-5.WeP]_#5XoPL1AQUkorTIsm#SS2#WLh*T2!D'H(MuXmFhZH"f3\.,fH@CS"%YVbn3 + GD(de5TV84R4Y(M7i6i^egap9*M#R&Yq/!e;[$/8tgoE!^^Q/9_$YjlhYI*pj/FIt7;*Hg8 + .qGZokj"'G:WSK!F6QV@4o(`dbn`j$BXY!>g\a@o9bsFPLn + .YF(CH'Ttr"@EBfNM,^m0.@7g3/(PEWCG_MU=-HeArd-d\(XKA?,o/B@Uat#W"k*AL9rHI? + 'fpBsI/g6J.dNAe@_5mK=B>mNrJo'3ZW_2]\,+t'DRQ_92`8:klZ68_TD+i/k50X4?^;'Y\ + /5V\ULJ45A%J4E[X[>plTBo#-^Dndf"%mAb;Z#BkVQH8VM@dK=hjVJX\Q'*Xk5kh$S@[nB= + 6N3pKL(4`W*X/X>i="Ig_/6="F(dY"p^Ac0025WosCXX46HYHQBTlZ!#oOpUhP3(Pu1nJ,0 + %,O&rgDmsh@&?QY`?lhC&N52W!qn,3,L&*sAt^ecQg"2JkL%0]$/>TF$c&7d'l_JkG_$c:9 + 7.h0B-j>@Fc&H"s-7$.mN&]E'j;&.%>CfM9?OaL^W`fYpk)923JE?3(sluO8hOp$1UaH@O& + +\<:tP9\1ICmcZmEf?P<8eRYr.a&I#[4BF@bdfCMdgWiCbS)8@16ohi!TB>[g6I>?^&Ok)HD%fFeP_C,KdZg::Dp\-ms%QFVbrneMk5 + !=te9)Kt;q0>l93'LWAQ;d^'n'Xo*#0K/J(F;;D-ba.j`On^(&`hm!C0Ht5?S9>EaP8Y*J7 + ^&(4E#l>*b/K?rhE0?#A=smB^`cPjLbVJfp.(AD%1GaF;ReQ/,'goUQ29hKQ(.HX<&PomGY + ``Vk5AG>\Bk)J7[f"+];ud<#o;)('3jBDs'2@Q9r2MW5PN22gKaeg$4tq$'9KS66=oD.NH; + 9pOfFq6Fu$fnNk$k*_fXR:cd"<#,[?>l%*WV4"Q,+8QlSCZq3VO.m"+i7";Q5_($bRKgUtC + E(=nZ)qe&hR71b3s#Ad)Q)rCZY[6;e[4]jTt#U>gP4Uqm[Kg8\SiFDVml,-rqh<9`npm7n, + n4)6g>(NfJjFSM6_X$gkQumnsl='J!q@,1X'o0a"bnT]`1FIoHnjdJ+UeW-qtK49_olfJr- + "5+8qW,B(he9Dqpep^NsMdk,3.V)86]Nj*:n0O6(XN?_--nAsr\_qL4d1lh]JaWdgF+QauR + 3^>YjWI"k_Co,m$J(0'tN:im`b7RQn7E.r\3*!q(s7LCLgc705ma59-\YBo$'4jc$cimrL+pmh& + :tE%a>>n#,V4b6![KB%nF\KZ.L;8:5"J7*-REa`QBK*e9Z_iEMF?Nq'7t[/,/F3j!]40#;M + lNJn>Igj7PY\[ZPgp=?]@L-YSNtZ)rl$X/eQ=:QY>_k$^#gt7HhOS!rX3/#_NnTGf5ue*+h + mOM3/!Ac&4AB,L$XY1e*^48OH6h9mNh(;InUQPAM\l_8):)puBLqN@E.ub`u)'X"^bjC&4DAdPXWFp6OBkE[PX;+l%c2F!J!5;J,mA%_inDB[I(6-)Z=# + I>A16A"gp9io%iXiM/S\3^KAMWA)%f\E+-Gtf2iaJ>e?8AtDkf& + b"<95HCmc+4L>':_!Lk-kcWS>RH%7rIe)u#Qauk$A5Qtp0PA_#[nbDKf@9k?W^S?Tmc]jON + JL!fQ@ZIZoK:_3Zha,rZ8uM8\,%82MW-3'4PKIM31+S76%=^ijh72,kP8uU$;XY-*=9-#U+ + o#a,.s<>E(AC23nO%>9f.^/WnID@oDb:bM7K+q!L?Wn;\,K/dimZ(s,=D09P0C4;Q+]hu?GKKCQ$Ej:rEX*^`* + N5#AlJXsZ^;1jg/Sf$<%Y.I_:UmVRW+1?p)R*Lh,7]uNiR%i[8lrjk@M&UYAW1L]*?/,F!K + 0=JC#B-b!6%FiuE'i_7n=krF?SP#[3/&Z!OL6A?YC+_=5VS@]Vd+iXP1n2p3Gbgan0[r1X,a:6bi*THEQ2e1EcciWZX+72#0&jf7YI + 3DVd^A%@#G?AR/qo/ZPE:A^.dNCt:hV>rW57(a">YI7L(%G.*%J\A!0RS-F"b.NBG]"Rb@0E?O`7M0H1&[:nWeO + fJ"`UD1)KQG1N`1(En;IE%on*5m-U-fMG+DE$58$h>gAOUMs047FYUs%fn@pg(7rI/Zf6EV + [![604B)D5Q_8[Ctm+$gb=Q>b,SuE[bWX#h.jmad$ieQEo&.#Ae:dGVI0eI_@=[>%KQPJmJ + dR>!$C-("orPDTPOi5JDgtPi@Z&YWsERC&IpE>%S$HWXtpRR,iaIU%oDoYb3J%?7^G`KF&< + 5)91J#Y8ELCd&SFkTTa]"-=9ZKX[Vg>ukD9i>K+fkf[qgf`d5pLs_%\Mh\PAT@@?Q"S)A,Y + r!e;a&:9]5KY3W-ulc9Y(%`[ic_LZZ1"cu]8BI`CHJF/9UmI?##DWL62keCp9)/+!X@N($, + THZZa'Odd;&:j7D*:U5?nLQ**mhYH;1E8%j(QT[I+NP+qKFQl#'*uU;%m9J8>UumboTA;.a + Qgddm`,V*p$qf!i8bB!6MAYb0s>]@%sI]B7eb,sP0BToN2[uHOWl#pq.W7?E\,E)0[%e@Q4 + L"m%*p0E[`pQqQ`n(%n-(*.9dMIu-#uj/P#Wg6@3tJU-.6KS12A4WD'q)Y-ZX,QZ?7PCGpi + 0:-o-TI0FH9j%gHth#-\aDnsoh;T`kc,"E5joZDc<2,m-f6"->XsZFq`n_(,#WTsC:?;T_m + KM?/V"JmlJE;V`dU#mXAi#;YNKD6+%/RL7IG,3kQN&<,DA8I8;uh!h!DdgY(EWA-*(BVpT! + `f/iLXKJ3M"!BXA_>+K??l%^qa?^RS!DO#E5n2*ZiQ`e\;bF=$;%Bi"j&9(kB*Ag0?5l30E + 6^YH;#q5*$KU^h2etnqNak#n=r(Og)knqb/;+[f7Moch'$bmgEAoo$JEZMf'CM$nd7HC^aV + ,-T_p>0>L*]TP#oc!"Q:?O_g-$6TXu+,pAEl87Q-R9WXHeZHJJs + Pn0Q5,G`O_Td,N<,LlN&k:Y3K<&I4FF5e&LG;!^!0C@!jF^3`9$W\sR$*^NdE2t_$EA<`#B[G + $1$L/8_3#6JL4>o.>Wm7p?#3?M + @&G=-W1Og!:d9Q*KG/.PkD^rS2@Ko)B[h\-g&N*n8QTB+KkgQQB&P"o;9jf.D%D_*-V(I>55M=J*menc^2^r(7I*.e)*@=`("IK]^L#0_`2+Slf"h"1/1NH+,qXCjt?(:=f5:(B%#&!H5FLDtaES[g@$J#B;[3EE<`lG9(\4+`WhpEVCh-eF^S!q,DD#;q1nIns*, + '%l`4obadoJ/WW#Fa$K$QUc2cMI2Y&#:f!I5g0^>3+!Cn<*+?hE-,TEL*(qD"^kbS + Eqj(n>;12,0U69)pR)> + '!nOV[$Dpq-/()%s)7ji9*"u2R;prZkI]re^ZVodWHpSJ7iO+T`Pm'C_8rS'omcN(`bY;pO + Wm?3jZ(*[K%E@[6%*#*?ZC(i$?B/9EbE+@;`P;&SSfM,<^2s+8.4=Q7VTNBWU-p$UEddmb; + 7^iJ0<_(t"U5ekq4p"Mg62)?N_WP\YNQNBr.`6,I2"'I=^J.)T/`B:,Xg(4Bj5YRNkO&hJ\ + <5VrUpBGVi^BiIepet#4mh]C9Loi,3d&c*7dC*,])"5m7*e8n-#%/6(*+YSB@BppA=S5P'8.6QRe6p/X&1FPBemHR5L2=6'M+5PM&Nl3D5T^9 + pI_-EpKA.JFF-EIIpRO/0m"um&7g(/Gp,67WQ5pkDhQO,N\H.g8NF_dN:WHsu_HI[,>ZWnSL=9B$%.1@ + ++9'Z`?!Z&Uu)cK7Sd,BC/9qXGs2)j9_[Bhae&Up/`>FHFY\*jjq79k'8&JtOup]>R4#^3+ + ZG1J,aJ0"al-HJFt!amVsJVY>1T3gi/?W\S`Ysg_(s+8lZ?rOG7+Je_Hp^eT3KU*R1nSGiO + fbX?eW-0]t^lb$7c2sXi"-J"AoBdC)FVrbTAFo\02Gn%%%:t,6$_Y>*=ge;1g^Y0enG&KSZ + +[74F,G-_ni3hT[XX20i#X2'Y)F,(!437rb4I[>N;1q<<*O*u=Wb"\7Uc9(@(Hn1^@pLTCT + .n9dLhOM8KkVF8[p*j]D$cJblZ5(_\9Xj8R;]UbrKs".\RcSDau>\,RCQIS\pKR*_!hk + c(c+!iu1^!i8)1hO8[N29s,H)G[]NU/Ggl1;UZG + BrL&>;t#g7^`7Ve!;a5PTct&Y4_][#73g(9su$g:s^M`uDB1XVPoY>g+ShGDb,3c$6LNi2R + .Bkf?)P!.c7*@?%H7`C1d>NAdI&4T$iO!G^tJY^mu2JX?Fq=g9+:Z##VbJ7[4JHoD[\G:!oFTo@%K=T,!(?'[a>GKmMOTmt,g3nfRiD^QnLr_hB"2I;CTsu0"J + )!-)]T@u_Ai(62A/PQ14o5il!;i8=^h%f3hKPH[(Se^4f=oqlXi?^bYef5arYOOP*)6%\V3 + 7e9;o!\Y^tJg^F&dYF+i0Zd_>Yn"c:MDOjhb^<_R7R:\guh;0uM^&jEs&M@>#Lai:_$UgQu + ^eHrE'g=b8bAK&11O>[tbs1)&W"_uL/<_!DS?0ur!oJgC`:>g3Xk_[UJT`KY:).3W`!f&F!$hV2 + 1[ZF2N8402QD#Arr5S[;YYk62ThlaA=9G-(Z!/*nf_LSMKsGt)db?:6SMP-Y&^u.i8kETV] + g("%7C:lhf3_&?]lWi'8bE$_oflUP+'nPh?hM0Bp4$ZI^LGY[p@d0E&EZ".?%]d/MqX_]pg + 8m3]oNXgDY=kbA.Ff>j:7kO9Z^TEjc!n(dY#jq4(\t;q3hY0n(7F."lA]C5+85Sk_3WQ"h/;t&` + Ur]q1"%#,"fC!m$3"ffeL4?um)2/.-jm3EZ^uaVd#+tC + 4,6o&>KIC.nL"V!+__Bk'%R]@U2\;Pl4;?nFC4FG`*5kuRdm=!ib + u6?22,2&SgfTkTr9$,dPI0pD:5+70J'YJ!rBO2$'afGUjc'd=d1eTL7a^=G*CugaEF2=9e? + (Au;`*Se]iE'h_G("^tM_eMUW4<`Sg8@XjYS?"[?lQ*k[<=*:KA?8_PDd0[S\T(jJ*Q)o&pGLE?h02<@F^;J1hg&M + ZV;lmYGI@$i?['3\I:(Ufs++Q;-38a,GWlLg@E+,/^!r\p&#q0ki"Cj993s60<.J\gS + s`lG@OHN**6mMc)6^jl8h+i!RF`kTpoI/dj.p(J_IDr,qX#fnf^kX$PN,+mKarXjAT7S('5 + ,NO^`^iDb6#q7/HjKt:>kTEKSX:lq":k+[\bl%k$6R\cZUm3.*DTL&q#<#R>SO>I3jl9C3cItr"8^DL('@16C.0ej6])#01W`0cr/?n;J3k\+-fW1+UCW7 + /_,9PY)gXYu;`fu?sk#'eQ + OJV_I9`Ki>!O=U^RtdC\Rlnt']NYYQ_54XZq4?L-?mei(NMpVuuEQ'$KuXnPtF/GdAjhB2H-K8n:V + #=.c&YK,u0g2V!`L?&8_G.,s5i\^^,g]i:pe<@\[DZep7]=12^ha(p-"s[@m.+!k365u"c@ + 8&)[OIWJjgS"uZW;dN_N+%pS")1?Z3`8^4&#&&K0UJ(LhK'Q\@SB9KUHEu0@+6;a(% + ]%&!$m65]bRa#>`2\UgI;I]CZ?CH^mGPe#luP[Y0]rO6(nLDA$I$!mLtWU;Em[mocN\Nm#[ + "@Iu@0I4@3.FYIE$raQ*>OOBsH#BIsN^Hsqr[\eUF7Jkntf7YkUms2TBN3/9d[I6`T$!s1# + D$jgR0;=7)DBc:;goRegZ*`G[?,Xr1^L'DWPqO+ZQ2J)]^QkO4QRsC<:9rZ"'ZV98HPG:&c + 2((GkPjMC*a#d'Wj$"3DHCiUO5>Tu*5\\1pd$bJ4;lpS(F@M29=uSk5r>DCfG*14A:J5CR0 + r9!;Qd],^:20YcW-%a-e/FBHC0a]92--X*FR>l*l@E5`Q$9C]'!kQnENSoa:=g5<)hW[-I;LqF#pCiTMP437jgIjKq)d1RY@0)]&lTM(W-*cTPSq + .RMZCO0"9Pf0%qApORPRn&Ajk[I1HaIgF+Z>ts.#gtPiJ;0_K4m#h=`Y;o)"<&5(3*(TBGt + .cr9sFIXP/k2/hgZ?ao\,s4R<7GOb[)chi2Fnbms`p]"Upr]g;Y^^L0-'ECB<"$eC?5U)Yr + j&d48DDe3p[bFtDC^N(J$T<)827mG8fseC.tKV2"m>D&TM0.cD6%>]/?kQAY7TS.P^Kk#ouOG^0'fe'Og\@[aX]6d.YQ1R& + uVPrWYG/lLiBN'Y"Kkdeo*3Q+>oTP4Yh)1e_/6SJK?L^8O007t*("[H.DS"l\[V*t*uU.AM + n!-JCr5Qp!YJO3p80E\+I;$B3t]$`.5i7BXk>7I(,`QPLqbHs_(ZNM+56"DbPL`(dM6\JQD + L&b\Jkn&PF=,0CI,uZCG8-FHN-3A-D"L(T)MHhoQK-fP,4cq+FYY57W-3@j<8)ORocn + PVF705.fK,`dHF"k]$0tpbju&V+PR&8pmU1FK.rlWlgj49$FA/fp+g3[Aj? + M9+2)!*!o7h)lakl5s2P@1q$%N[_TNd,\.HE"Lh%*[EGqdbK?\#O;n;J+[hE`%N#EOi#`"S + S2Y[d"^G,_kZEPKB*I4!RVTN25`A;%0^"aE+]i'i6J.>%$u!8%f4n08!*.39TeK1p9YU + ;.gd-`f_g6DQ%h`%&7_gHsA>c-"9tr%>kihbEC^!b`t`jM2)S$37"5#S;DO=oC=Ukr80J=2%/S?<^'3F)(]CdL.86Fj0b/sJc7\@;`Zpm&4M8&=9PQs"lf^hYX8OqIs`;7G@mk&DfkIZb&(.!LOcur8OGi#6CbB!\c>2-pWFk6NpR^KV2Y)i)'\t;h\n&"TVI* + TM1%&A:u@!Km5G,?q#Z,,_GLnJ@s:1=<<%g.0]KUL\ND%gsr(H`lBmV0oU4eTaXG4b<4!n8 + #1FCjY]Le>lY]BJ0b#BYR:I#,D&08K0V9sLdD5(>_JbCL1j;7phYW1:Bnm3"K5p2kZe3L1k + ^iMK,jg;RRT_:)j<718ApK3LcPXB;onPb5!_1mAPt<=5!FX`1LW_M/2p`S=< + NZ5"WrfVsEore>q-e?<-p+[$>g98YU,h0.D(HWR=s5eV7Ci*hAeiIGbQ + fEhG,0GFP$\qb[_9i*O!'\S=c>LHOIFJQUX&5pU51Z/E'_ei5u0A23IF3J)k5P3Ra6Z]$_=+%OB + 2UaCqLSn=;@eWTK!%H8'p?.3opn+gg*brZ;F&p;*D0G5Z@o+D*0t\gIS[P&.sJ,=bXq@L%& + PLeLi(P0>)[S;\XAMgWQk&*(PuG>R`S'#[E)S$\OO7gJu3!i!07$$3]!W#2iYg\:<#V2:V" + h^,g#Chs`lal7kP]kUiTg0MC.?+&JD`SFhe + hHg*9h@8@K(#WC=j'F=u?7"]>4lt8-Y:9-^jf-`2;- + fW`!l4qR'Bg7ogDfT>#$(GJ@T+V*b"kP"c-oDKM(.!,7?K)KMAOOdf3M*!*\e=7HLc$2C_o + ?!FMECg70;e!*08bF'F`Uirq$ll1s)G(Iq(k;^K1_kn%)'_:)^M1#9^%r],S?/;)5!c6raE + -j-b/@U,:Mj8^D+2,0d@bZ+e--G@`1oi0I8PWl6@>t*5KXI.gBmp0g)1[@;:h:L35jPFKFA + H#G4DGk$U;WSruAPQ39?;os'SAU[cnN`p"K>8M/>VHg?3VI!T#?XC0JjmDG:-1"son) + &S)Aj,V?D=oAAnto^b?Y3p4SnVPdCM$:_M82DebeQt5$jG!cL=@R-j<`^eg/sH((sB@R+\?i@7CuF?(:%m&&[Jt@?$o + gtA!gMTmQ,+FV + $#;A/M#5$YWRV/5\iO*A0+(1ejH=/CWR%2l]'6)N^f6+8(DJ*8T%,AIFZ'ff3NN(7LNi0Na97d^O9KF=`(J6;]PW(7&q=((?PBGM,rSe:0EpK<6)DiF)IAeh!4 + q[,43$t?CZ!gLM>I%EAPB5Wql=3Ii;<:Jrj1?i-KC/UQ?91L]8=JYlF + 7M1u$ld@pZ0M^Qqf",%IMqE_ds+Sur2%63Wq"J=ha\cj,)O$A<(`#"L1Z5:pp%/aHR91@u;>o6"8%%Sh(to5#dAHOiMk+%,oYZ$SF8HU"ob:UYU?[sMJk + qerukT=Gis73c//2j["CT1S[1^O,FbFK`Mt:JpdD_f[%s,@=8p5?%Ju`a=3pr$TTZ%a]n`< + X'2a!=t`[cj>5i$Ns9rKb<V(dj!HQWh^fE8.UA:\o&IL4?P9G^g>NNtZbs'aOmI1)^^S+OG@$-5?o + C6Xe@:KKpXl$Q`*D-2SGV^1'RlZ,kZ06R+.JcLDc?.,&W3Y8C(2FM_B>?8,t)Brh2U^n"7K + F4p]_Ila:MTBiKt,*,gHc`WQTBVU]Ij6&.h5P + )W6"=tUo_7D7RKZs-i)\&\:]NaoN9:8`4qa_:^n@kha6S,XXpaQ2pF>f6MJZH[#*F'n4??U(=4l#h + 5?cV'spATMjYM9!U^^qIb!l.V + h#m8"?9Gmt!cs.Xs_@W4`$GnL0.1APibW--Md*t>k`%_*X'#]np7hinh<)FCLd;35,7T"PG + (rh]ND&gR#gi4B*;=&iUaAMSd+NUi.N?lUY>`U5R;KSnptDk'=+TN'f#U!s$,"ASg*l:^R8S_U,kO)+L%NCe`M9s)_O + J3PfgnVT%7E%1,2+Kb&hC->VGHdJdpNp[/!1?l0pgK&AUKrbEJ*:)AUmR!p'3iOGI&MIK*O + X+fRn`\ie:#4=^;R^iVmO^sk?rb!G9qAtuul2'8?Z0EFn+d_Tn^?<3!r#[#qm[.T`s7?1R6&&qK5BklVra1M9pLa_uYUtlY + os*0L`1R/35R\=COD5P?jEH`KrrS#[%4"::#Jh#O8QV[l-"OBN_3/^@aiQc"`;1#Z846=4< + $`utR&?0(W=$eQ>(sq7Xd-30%*JBS%mf\a!FRMbhu#V\J5cl,d./7^=`l@YfXr$!P[Fn"ZS + /0]_E[&j@SBpa1?MIc71)$H>f,_;&_%RWdT&L-9uNKI+"7S*>$,\3^4P1!)ua]U>FjOB`a14Dt + j#sB\J,bpUPMH!2N;^pg-Xd,'`\$+QH:F.VKdJSdo(!WalZu(q1P?:,f,k+0kIHN + OYZ&ubukDlABc!bZ'Ga2RaY8CWqBj4KrVPM$OU[p$,8H3[*kB6So[Yl+#Bd)adpng6js*V3 + 'u8)%:CjoJE_7D!3d4@/]^29;4K[K>3T=N\qD^D'"cb/AIU=nfIqC<_AG'3U5U7Rd?Z@RfW + ?0h9QH4L\jX[Y/L@3WSX6YI<6Juc9-9r<%mTQ2&->csVTf:?1jX8kR[$$Mo_GGmVRY3Gog> + \&H*T=HPhR-O9Pno1W&A8D<83&6DNFQLL=YqQlJm>oc5s2L-PA4N*G'7D6-)(,2!-;gD0)k + Nd^k=r>ho+s)FOM9=,QDnKc%i:_*B^Tcl*$7g\tg##T/1JINF.:S:76lhq8fuADT,\ZHkPm + fpa\hT"UY@2Ke8u8&Y4?*Mn0bSb>8U<;c,focNn=fF3Cm/4TtC$A#8^J@Y>]=/Zc6?t:sG5 + lrRUT]q#/*0!5356RX1,It$Us+\Bm9aR4I]#=jb_>$Js@6NG#2ghP=BNklgHnd,_d',lAeT + sa`:*/Au2[N;E#JjEQ>9JS1K&Gc371c[=KBa[h')-q"p'$<)[O7G$rZ+A5g!NK*al/o"->lViQ=DkIjrsqAT*V,5. + ,0h$I>D8Y"TYna#%6R@73K/[@rQiKWYn.r,mr/UO&ODZq5W''X^A-sMpZBQ7;/eFf$nbFdH + 3S_[G)Ur'Ub/$)HTOFDSp&&?quW*FsMaVZ)6miS^X$Q:\LVNC)%eaD4Rq.l:YW'2*Z%Zk1\ + T?UMnrBQZR+7i8AADm+p37^=F)/iG0ZrXDU7M3?$^WpZD@UQ--UU(K"L`-$L4o7_E]#%eka + oM@nj\-j;bj>#! + Zs;J94pt3`m:SS20@BMu%YJSP(I[K+9OEi?L*ucc;-!HgN0',f#_?Yi$R*__M#h]N`X)FO& + 1if+_EW)/Fj,G(oT=2J + .HljRO3(1,bNHW(sQijKK9rqsms=MaL2`+SktFuA8$POe5cc&P9f'M!OL*O0.$qSuMNS_UR + Fo#KS0?[qKm&&%af-fb-^MtDI*e+s#FmJWZlp&?,ir4e$Yrnl?*Io#b9s53Xnm's:$]Z^Q* + !/_3cbn/,2&BumD!JKgdD%-OX*s,.>cg:MT&1[ho0*>$&"->:b0I"lJ-NjuN!O*@@/C5<;5 + S`6o)8QRj+Z9`Oapp\"$"0b;!+Za>VW\)1%Wg_`qcJ;>D&r,2tmFpD/1D:5 + \'8jT,c0H&QnuXCp'TMu(XoB+G"EZ7'Y>JK^EF.U$@c8L0)(lL:U5g'Vg(GP](BE_2+Xd^` + ]FI2W#XG-hi-GOa;?fBM"9;7#!&=Yl2?SJ!"Fs)J^fUcUE!G6@#f)r;+NUT,%0aRE'`hPX_ + 2]Dlr>c,j)=CD.N`>,&ij3N1ZYj=9Z0\hMF!".n[#T=:lW33>!9c0B[I/dWmZ-M:%j=5j[S + dqe_=SCl6lEf*%m`,=^uPbDL'jSS"b:Y"+BS_22?[D?!oQ3HTMX(PBP&B;#@Miai2R.biXT + A_'*0-e@=/g5MX0hVlnWl3dXJa4K'^?Dm>fW^0')EjP3u*4.>t3YJo&%sU:W&Sn.,IJ_Q,0 + m&T%cHsn^pFK"q?MOs$@l4^TR-qe?iqN,"E,am + +@$&0=p6$J#=)l*J;soNO:5O>%,i0;@1!eO7MN\_oQ)MS8"B4Uh1[bHfm6SnWI*@LshVuE_>rb$3Lg+``aYmD!VEnbRE + &Wmja0ck1O,=kZZ&hu%1d59[=j[i<5hWiDD/b_jL8oh17i"W2o0j*@rAJPkj96"lCd;;PhB + bnOY(6llX1rpOKd7lq,9f1NDToi(Aj%\%d*H^TfZ/H_K:jDD1jja-PZ1-n@!?[R2k72bZdK + Ja]#j>/$+Z12^&fX^Lruoo?+earp<@etE//SDH+8%Qk[5h196W=dj;sqJ$dWng_I-aa]m#m + %gY1G)P@.&'#%)JU[B::qX]mZc(:C8KU&S?g/R/rB>]37k02B"75n.a`6Y>?XAm0I')M3O44D54(*,M<+(A7q@[80HegFr#.q9>R@ob[$2 + Et];6"AJ5A^BdF$N\*5Lt$kepD'GH"^6,B+ + tqk[Z[H + n:J20+i7,/Td:Z,E`H,*:9@V4-eCdQB;< + q;b.0(W/0Fstt_.e9UOmfb4;#_hr6/+nlQFe=*tSQXLBI[-a6GOJa;o3<$DJ!J2^pau)1YD1i-kqSR;R+dOG9BFq%%Bq]oc/0Dq=d?G6O-DLN(b*P*%\C%IK>dd + 7Nu,PF!:^EE/5;XKK#]("Y*3FLt4*3L)Nf9_?MM)..Je:R4uE:AJ[JESm*!?VcgI:XoW8a. + mhN#p6X#+8:%'1(0KdIo4gK+a8^0f8W]HUNpuRH+BhP\"Qe^ZW1e+'K@`=7KL]qcl##J+_-<8=+jleQoZ + s1bWF6e`24AD-iSM?M3ZC(%Ycg@`*LM2@l(*,ACPO#*'H0KC>dJ;h8A`LPON7V4(J^fR]mq + >G`4H`/2'LhM3!i=?<`ab]e[C"Sc1l3iN[T;l#(Wd:G?R + ]iaFZ`:UGWuALl\t>tE&#iaQN)ld6e15:df<%$gXd6AsR,Z`+_2aM+Nr&(%f$7iOp1KibAR + Rb:Tn&J<-5.P8OLFHd[dgU3Yt)>@OgK\%q'EBEq6Q?Q&FR;I(*=dLu>QW\UBfmb(+Z^O.*\IR[#n&)kVFV=du^PS.^l`]<:PsbBpXOSkfT>Q&`P'moakrT3(3) + gYZ3Qs*n%dTNVZ9e]WJ(&;b=)M7OR1P=JpR'quN`pUVr.(b)3^8RIi:UOupKqE-Z>6\DS9K + GmKBg/#Tlqh3[QW*DF%S3Ns"6c7\J`#M:cge5U+8]=q1`I(V'S6s7j@lX=BLUrPJqTZ+QRE + %,4X>n%QHF9fdK?,$fX5&WS>`HgnYH/^ladU>l>b7WoDiGG[X]X,UAd,9_H\C_RR + s=o;#<@Zhje-4]Og!W"5k4*hu4#A?4Gha&)gh.iLUY?hA>/81WG4L%Nt/<'J[WJV[L/.j.( + 9ur-QP3Tue?GaSniS5#:72;*dB3jcgSAgnEj-V<2$`k*joF>2.5840AoXbP&i6]Z'(S_I_N + EYF>1=%A"BpN#_IpJ)Jlei<-:\Uu!YJ(7O2GMQJkSR9r,NRh0let$[/\ + Z<:^FdmMXf@#N-qlSLNBtIZ,])0*e428Em6eT)p]Y1#1]qrE5Vk:_*gAS@N't:oJ\b9=B>i + !62S,K%/aeA[Z^j7Ba=tAEt1VnGXURWF_II!\6/],3m_`dbR+.qN,U#J;7qHp<"5HjpUZ23 + @aqk(@phmNA(]DO*>ib/0!?7j=WdU56pb$I0l+5a'1S)q;Lj^7?85O[[i8`W,!WSV"\_>r5 + -5N#@/*#9+kKg4;\3,C>j&Vrj6m<8C_0,BL9j&+IQIQ8O37bZ,Mr0C,*PY1%8fhB+_0UGIF + /Qjp`s#?QDk3^jGq5ZcS:["#]/IeZ6J'r%QWb"Ab;5Vk.'hXZbPurOeX-K",DSFT(f^'AP0 + rXjI[9Ds`gNC6a!s/K>X@Fd4[/[d'g+AfUqYPcY?uVJA2@g;E^LEi1b]@85OD&11R2iY0rm + ,BBF*XkLIm'LNkk#b$F'8O@eESnph.FE'Z.1XQ)b9n\Rap0\[l_:8;h)Vqn/7`k1]NBSj77 + JcG$@]@cfk!^?!aF9VqL;iRSWqGHb.@5pUkr.-_JZGAGH+As)5[*nb4k(Io](gmf33j)?6o + +q#U4g9OKk8^_ZhqT%RVW>MIo6G$pbInYp<*W`$QXn + J&QDD=VjO^KP)+@VAYVJ`pq8l@@8joI3-H"'S9a`dOG2@QpsK+--9%qT-kI2>blklP-h,9R + .@>c@*;"?N.9%B%)FYlZR!jOTB5m,2,09>gGhq!Phe56`Zb@D:Bhg34r;]'rB;%(h#r5SL$ + =:Xd1e_W7T!aY&P&JcN@.7p"q;kOQY$9u9iI;44&Al;rAd`AFW#=#eL1;m6V9QCKm+\]5V! + 9XgrGDpF0i^>h;]tF^:i?ds%GYM.g,M=0:_4(>$\-=l3sL1)C* + (Pe;YH"'pKpnfX#iqV(\NZ[A6AWL@=r^2iZGr@ohQ7)Z1Q'N;*?d%kPf7dk1RauS + S+kr>$#A5%Y:t!"2p_*/#=m0`]amoeFWED>eocCb(OtE + =4suEOGs3mJjE[=uS*5.sQ_!Df5-LmMHToKZlheEJ^$^7I(W5fk+#d_,PQug!p3j\p!'(P- + 3_0#l?a<&s,H-bUrVrgi$R$c6!C.J.!$F*#JFF.OMVf$(hj:!bT)B,jpf5,E=UoS$&B-F"[k>&&K + k*>\oCQSWj>og`'BeDiO+]3!TlN*4]0lV/abFk;_fKA$mlLZ/CrRIU+!tG^:\#l97_R + pIl7b*fmDB5A/pP+8M:SVJ6E&dmXNbfr6gTWY:j-Gn&#o4#Z6T-q).RE6^:NNFsg*7Yp>P, + K+hg-ZVSFkp7Q"A=G_[K_A%&<@gqFpm4n6s%LP3JC$."SC<@;T]s9Td'W56-usg3$3``C2& + `3f[1uS3a6UYY0*-Cf7d",9>Pu*2>/UPo3gC%D* + 9o8DSGKXO`[?\'6;U&?k2eHmfT&.8UD/H>]f#H?H4'uiAQ+",eh + VDD*H#>5,]=7M-i*8?tEGc6+\nD + 3jW%rX9e]b(&3hQ,+Omre$3(UEjCpiguO[Gnq;P5Ps(\TY1K9Y35n!F9_3(2u*4_%CGd'3M + l!@g$;Y&Xs'G8CrQ*<$6amCj@kNP"q!j;fcXe + cN]3,U>#!,&8WMlETp+.7+<*=6.-31raV(]5b7'dhEbqnER7:EB,I9"`$/q6HAm\4o\M1>Z + m$P>:rCj<51UAh=RI`uQbu>^QF(ErjaA-X91d*fS[,TBpQ?5l4BqFKk4.[>o+J_TkOu+X`d + 1b8&ce8%V;R0QU'F?Tm9dH0@2OC*O)MTVs)an@cIA2T:6.'fkWG&sceJ)b[;>NXOBc`qn>cns]G]*uKgdd0?4s.agQPSSN?]B&s3Q=&Q)rpj?OE*NRaa"CEjIE!/Eh6FV + q0+=N*+%c:SnM;[Mt4[Y/CpI4(n7/ud?=% + K5T5$NP7n\^.!o3U@S1Yk+Sr(V;M0_L8d:H*FPg79LQT8 + P-Ssaqlo'F3:'-Ar&SBI?W4O$-ZW<6c.XFtYLE6aYm%d0I@SgP;#q&/i@BDL(>oH2B*qB&. + S1nUPMDN3(#qau:M-C?X/$2`u%:!gR:q.PqFe2ifd.*Y[\2,]dkHEC$/l:\4X2RWQ'd8at> + @8-^k?T%tT^^?Tt>+RW,C-XhRQIBC]$Y?bnB==";2M$.ROaB@"CmWkXWlhJ&nuoM2&$$pq2 + `l&r+cEg5\A:7N/!@P2@sP-8Hbk(H\'i+/RsutO)lu\`_:$@BbC[9_QapoS_A"-0,ToE1+i + 6>c_F,]e<$oIU-c0CB6?FWmUaa0f.71s&_TXOIe1SW!0>cKu_XXtCS%u9rS$&6c6W:9Ol^u + (1Xub&l\f7W[ZoUR\Wl)J1_o+^Ue3:dnVE2p]`!`]YoKpH9.(^He_d#Va;mW5$Zo_GH7$G# + '3t+3(916\,4.?\SZqe63Z9-!$]@4ENd(Ft?/V@"Y,R?a?=Ac^m(U91C0r)b-UE$pnaZP3t + Z2;_WX3;$2,=Vb\V)[CDMUY)G!A2>Ia4g](48H_3*h + S%t,-G4?4&oL2pDn[uH^+2B;A)t[?jsOq15(c3mTd7@I7?WSpZsnKuaY,jD9"s?T1sCNL9C + ]CT:e3l;2&#U1_c%u$p;hm]8<`m7Roh+$qnLTa5KR*Mj'9@r?GI8:[JH@N'ElN5TKl\S^iT + d\7$a0%#g%XA.YDNFZE)*S%EZJh5jGXHj)>iUZaW=8ap\qBA>6n+PW*t*[u.f,Zl$r%)9RQ + I6+!W$"O6QE5Ne?6\'Y=dlZb7q2(,d9ouZ-,88b=&b]<49tD\'&_'ZD7q_T]qcbgaI&" + @^Q*.`?GU`.U_#N(OEK9=oRFbs8-ogUT`D]1mZK72/Wg#eZ)J]nGlQ"ou%2%)*fk%KP])#& + n=4gfL:5)9tUi:/`0%`9,%fa[PR[`T,2P`*l+L@_D<[7RZViCh:o_>K2$?7[3GVCY1dto>\ + Bs7`3rW>MIJEBL_W5:R#iTN,u]>$qFS#^,tFHD#P#gGVfNca,qMmP^:B^l9;S'[!I59e:5O + HJA2ME55^%(9Ck\Q:ph9t^KcRP;+icWMD]ZV8A'o&gl9)0Nj[COaV4d'@q!J1"AsgU8Q;!6 + @<"PbB[OY>5bi'cWiEM_%8m:H5m3f='!j)>&^it*_*'*FKG?#=V&?'G8nu+"h($77Upp7A; + Tu2iil8h*a\/_JbIoUY,r*k+0iLLrdnMXI_* + )P[?lG?ns;!10!Vch.k7$dme1j%biMI9@_%@o$9_q%t+%>c#V-PDGQ5d1L0aQXSUOjqo:Pl + 7oXTNFaCc*G#/TmckQRX/:/bXJ&fe;8()lh,_&>g#=n^ua:s6)D2-E>LWEUc5?rsXFUl0,N + _#[td0ofu*0!f>Q-h^T2<4^.*Kn2HP/&pl[QKe_7$Z0$#LUb_dF=T"XI<#YSC..^8_kRg)R + A&dM96j3X%ap53Kd9]GZ'*\d[_gG`@FQ;U<9;a/0upbjY&SKa4&9s>1K_.jZ_DkXkN`VgBF + k:'scR'ZeHe5>;`eBA[U98[9J?ngPO6TrEfqc1fXu\e1JA`ng[U-9iTBPYSBdc,t_E"93a' + e6l!MmA\_PlYM80P>OBP_!s_g3SP:t>gs"=!:!Otoet[rGeW%u$LpGR-=g8]GeZ;[)?r@/1 + A,sscJH`XnJ.7%e&Ilg0:-E'F?";^_kM?ARHat*I^23J5A>mfH#O`m=X<&8m#*kPH + BU)-&8l+LsXD(:u.%#MN:li&6S:X=S;p+XcU@jT9-/il'H0\j@ceM]Pt7F'rdUhjAfE-1jeg+D'[:;Y(b2*n1UugT:D.h]ec;)#^?>XI@*GoA0AmU_m?E@j1Xm=Rap- + #J3A^`G:;`.&QhH+jia"CfH`,slnrSL?A:]9t,NIAjGKB1Wd8U*RSpKS5(Kj_%B@W>]m?g/' + R,J7#^q?/:`e./!'B)t,@:k7&HkG=tLsg0tb$?E!?At;\NRGP%;TcmcmZ?ZSST%&gn5c#t? + Nfk2#OlpCn8AX&LC/!e$Ll(DnCJ%ej,WBdT$-i1j6!%M`hEdZWUWHYEDEFsDQ*)<(TtdnnW + su1VQ9".''eWlnJ8?=rikGm=%(n3ne8TM79U)?2?SIqln9ZCJ@PY&<<\1lAF8:8p;_3Xdqk + =J]Y;N`Y05[$@c_5VNWeh;k#cI!cebO%D&mV-]_0:qe8pApF-Z69V09QME`[p7k!%K2-=q& + !)eN^<1_+1q2JG!SC@2:AmN4l](2daG$>ro+=H/0q*ck^7M8,A@m[eT7G>>,\m,sZ?d;JD\ + XHU(>$tnE+2J.FT-4ac"&2ps/ftR2Gk4i*Apul%>@9D>+!IY!3HXYW)`^TQ/S*lK8gq-Q23IGP + ?<+nZR4^LO&X'HM;n.pXaP-hgWg7*r#s]>lPpe#9hnN,%Nn^h,R\M7jW1gLPo7gK.&hJAl$ + ]aa8!SYhCgHe2Tkge@.NsVEsBm#B/4`ge+5cMhQJfu[I!9=>h;/FjmL]3#QBcmn"U! + mgi@MB(B?do6#2DkS=bK?Wl>ESTVAog#i,'f=LFaU>_]%6"aWb[pmdo*VMl@g98?/mAost_ + tO\[pBS\88&fpX`r(V4r0PHOVo\e>ao&Wl\USX/hfAo_e9CX,r>%'Va*fXcH@8;1rD.fh?e + 50T2>oJ^gp_`\V,D=-JUP8;GU:aQ-\MBifm$;Gpg-Af+,9QG-9N+_%XkP3)XGip + \qZsgW1OYU:,?>l&!0$dSd=apToH"H><:$lN3eCR3YhYplDIb,.Ff<_AZYP;'P(Cc0fi_\h7q2#iE`M0Fngi\#bO2k>,aW(4O]jXL/ + 15Su==M0mB+7gqW`aZcPc52[ugB%=;T?jGM3YMFA7U=E.<2sI-QVOF,l;V?;q>XHqeX%f?] + lLSfYV$`Qq\>:7A[GV-)i + VgJEXPQY;?!q,jmtKk]%]<==m*.Fr>q:oU0?e=.kP)8s/ai!MAIN<E+Bsm_@UN.#Js<=.1="3bUj8k&DTDY`":uu + &&`Gr8JB%i9M6,>&S+lW`Y!T0(IjOGCDk.>b]+ZCna(B?8!3_'+NT]KN?QC6/`H,:N4k4Z1QUT_Qh=kckA_C.ePC)/P4aDJ-GO9g.?^L^g<`N6C + %>*OU-5D0lFV1O,@L)lSM./H4VJYoSNLY^blj7ZXA0-@)bDl.UlaW9ThCXf6(SY-;OWa8WWQCV + 'MaYj@h`c*?WTDS*:uqe"VumCRq=\Sam6gUe?8`p@;UHg1REDu980_=1\!Q>)uh581tT>]1 + M#nU-B;J!icm.EcbE^F3deI-HpAF8YblAFt:!s3c#f2c+%R*C!cZFiX$A'N%$ + ,K^i"GT#QK-LR/OD$aC#Ws8_n;)a[1*T.14=;@MXGi;J>XtK5-R=A,Q,?L*%>h&?-ID$!'= + )Ziars',;$<\ZG`5VEKLq"4/?0G`S6--r;(KV)[$Je6(!Ba!Mab_"QYnC#cjO1rcOZ)[Q@XNKLZ27f;+6=U?7s;9+es-:FCR*-=r + oOZLo]nX^E9-'&)o4]6/l8Ybj)c_R8!#Q@UD.8#.%nd:XS2+U?m=Y&!LYa*YV>"^km/>^:m + QSRE'9A+SDjkR\=dgBJV3K5b*7)YKl*bEQ`R5K_5lncVRBB:nbW6*tkc;V%F]IhYf@OG=*nAH + o>In0)ZeZCZ7VC[^O+u4?Aphg171/j),+:L1)K@:?c_&Qbd!\R;:b + k>W::qR.B+)-.R4pm*?V'p[l?,b$/ml]F"k7aOlo&&iR>)UA?I)=nlAMWr(ABtr[F(0IT'_ + /:k9or#b4hC)IkmH3AJ6*`u+7cA))-dn`E*Wf`r^34H5H.7RbEZ+!kIQR?R2J;9@Ka-tPNC + Q#alDb?81(c.U?.Xr3bLJg61`*?cV(<"k:HS(F + 9ZU/=i3Pfl91SW5)7NZ-BQYZ7`D?5OeisgPqgEdb&kXiVHRl+l,mX6o`[D+<8sGQYhr6_#- + #fH6jS?@6n/Bs9r^'<>#bH:e*=BPWteuJ2>5j-)d@EbmFJPOWm=kL:<2$_!k7OMh6YmrF)t + @(]=K#b[;4lMYM3X?mFPh7G6>bt6B^H2uh[d$ + A5FY`hi"`?$clUfn/e)YL[gBM`/e.=)HO)5%]XTm6MK(_;$K6EUj"Sd@n^:R\iO9qb[nDlO + pjE[I-/"*.,F2X7?$E#3j==<'o%mU$c0n5Bk?#7iQ]?AuDjc&-f@Q`dIZ.b)mn#8o"uhsQS + K5:r#O5D05%(ntK4F_blW?bH@:$PhYg6aq#87R*q'F^SXs0&MWtY3Uhhn6PE5g7t\k6dKQT + N2iml>U+GJ=%4]E`lS4o`?C?O>]h+,TLD/&Z\I*TMO&N:L:>an[#-jnuTUo"JrQAc5Ui6\: + rX\h?Ht?V0KQ.@3 + @Fb=9=DK8)s'%+`D!KsYn,1m9"PX:Qg?1NO:_;B:@^$4GYY,Q/mE0Y_d'64e!j0o,cnO<11 + p[QNhU"njf>u*/mp-2A@ueo$DPE#/(WknGirso$`Ll)8Ar7+bV7640b0 + G__^=WL!fg$M1_-D!"&S9cYj2]A2NGGeNkUWn`t)#A36o[0$p9]37hBRh4KM6gkrA(s2 + Ie1n$B9,@jmuYrEFSIMG(U85X%sJjDHPfP.(ibNPZ&olcPo0%X)(G"jmu&)=I/%]&)Q#"[N + @Pr&Ykh5,dk@`Mn#4_(Y8hJg*07Q)l?a;Oe,TJN"i+pd_'g&ef)8J0erSQ,n(uZ7iu`,kX: + b=.m>T^R"TN5Zs3_>0#8I52kl@po!67G-/[loX)$,`7!NjBdn0$NLL9Z`jh,#[*#GD6:jPE + ]shQfcWO@(<%8@7kY[1H0llWXO3>_01o[P2+J95`_3G6Z>7-a"gEl\=E_>6iN*j,er*&;[n + M7k&D=jMH0KN`*K,>7)ui$:pc$OK@TCW='9EMQ>Hh:44ZGJIDqN$u>AAW7qaef]e=q%?(XS + d,=2J'+R\X%W8_:E:5G6o'B3L3KX7P/3*6%@30oTR-;-u(5AMR+k1 + :&Dbo-Z=M/Mg<[1NP]8ZU$C275OO*1c%][o1_(u9/UcebJbhLAp2BqE>oFP(K#5l&Z7QDE7 + "-G2mJi2nX2?mI5k/V3+[4kEMYZ/FIk4h2U*<_Dk/ou.aq8+V6RcT1U(GQbr]pg40d*ndF0 + u[ci]q'"CS)6i6E%Q=rFU-rA35E;=OP5jJpW$X-*9$m>F1a#60n_+L2SDn,V/T'\[RH5d1a + Q1_?k8]QOo<6*MS"nhi`h4$msOh6KB'njIf0:IB!b,r@b2Ea'jZ:B^5u"ca)sdVhQ&0a;ZF + -]TE"&9?cnKd_s<,Z9T3c:(5WO%45\L_?C2&=j.BKds.D.e9nQd^q#cZO;T"$d7Dj0Z*%X` + 4mpc%.9B4OOi[V2ScWY9<8uqo'8soDbP-;%l3d>&GIi>r&Q[f0FlJaE=.mC$S\n%&Yn_+', + m+!+#T6^273*\a1Xk2<@piCAt + gYBFDGM;@n-$OB<[@EJo=/bUM+b%87YA@+d:&6!.^:OmUpiKRJ6en%;>Fb+XG#Hf]bp2o?CN@?Q + Yo[^pGiD%]'DK,qDp?s^/nI_)X5>rGeSkN`U"Xn"3[fX + *6LJOftm33?`Vb,l77rGhFZo=.=K'0p?HcH5Skkn(E#t7='^;HLpNN9o44@[sQ`#ce,5:+!Qfg]D2$5f)"-u0$90WQ'q*Z%tAFa(q[T-n!%=klm-V- + =M3O'sa).q'QJb3X]n:O4J.1!;*\sA%?pbc`nO+YNCKG(\G3(Z\7f7__OT!Y1"=n+'2NssZ + I^XTN(bQW`AiG;1"LA5g"P4&o"'Z/GU09Z>%)BjpFkn'5F`;#fsfV)(i;\E3YN1)o)\j;<: + 6_jKHUq0-nqF@AC9r2#jV0ZFD\m_Ft0o^G22HOm\Iq>5K9=c04'>G2d(>I=SX;L0es2foD4Xe!Ss5c.qr#.NOjEd&K3<@=J*2^oqR]oVE/(] + >JIg4]tA^.]UQmQ]@":ge\'Yi]fXu^HelCe\Yb]LVV7rJ)K'+)S\KK>@p4A];k'a)O2-($^ + q9eMHi_"T5JOe%_5^5E47T=u-*"A,X#qu7)R6?520dioX;O%"=[B-qgT;>arYj=6(%mhqgQ + e5dQ.k?=q-_p%AUN5#@9INgp)=$L]g8kCYM"=KFTdEto-JL7&%MbGe3IqH'Ze;ds\/cuB,=]5][(%WVY`H1MLI`7BSU4]NEj;2TB:_6M[b+G + .E5T.aV4Uep9$#bZqh:\B\Y,&DlI^*e'ZIU`eY-3sHfO6.C*%$ + ,H(VfH#t'h0%C6H$8"`W[:6QI;N(K0rnP4Out\(g:+u5[DVf!`&br&ge$9`:R(U[gq)7*H! + A7iQ*,LCY+0+,]P@M9o3lt>,1IWB4n/SYJX@3+hn'39gC474PCGJXZ+60LS=d)lV.EC0ZJH + VEgp)CB[EG)NbLC%/)8@T0--XQ)[.9X0>?9idM[B8f.\,J+3(7PnC#sN5[dqDH3*,7TH_7a + WL)gB+RKp@.kKqond5hrgE:;2c*RYd\m5U0jkqd56Pq^sr&Y1^4gUYj@V:E;Xec)Q5riPKM + X6Y5>l^kNM^;9.7`Tu0_ehb:rI;@OXe\[h"g"LN#I + lGiuH*PIQ&\P8E=)?SBao_4P'nPgU5m4BftKK*a&kUm@Lq0C;+2eQs7nkJ`bYfl13`YM"S: + Oc-[6ID&\mu^4j,sM7Q[DknN%u$Is4s"U'Cm@1[Z_)?-^<6XfKJKMEg.LT8mCpr$o')imL" + &+ri#73n#8Ul)]VY#-d99;RgIPf@DkRnVu9=iB`N4e0j]PR7!eM*n%7W>&N1*gKHs;sAECF + \8?+X&WpM9%@7W=ftilef*q9_.9fRD&.IuRb;D`g?mK2L!JRu\QuA)He%9SGe[SRpY5r`"b + jbNnL]aW#bpJQHs9QgMQ + [(1NCMHd;6Slg^X8gK$nL#ptUlo-^i0@Ek37IO$BJkD=TaT0$eI@hX3p8-H[[$^QbV_RqL/A#T`/Zp8iPf[K>89TSA7$3D0r/4nRLT16J&bFIF!!na`.9'M&e'U?a + Ke3p,oPjUs!C=cCnE.K18LTo8ohSF//cI4s"6smm(Y8K1F0dm9QN>>0:B-4fRM/pK\C1co9 + l(/cLZ=\4O7VUoff#1AG&47'GV!B;"PbA6W$ALQ>+aRI2tjVFHn@Ide'@iAK");2bqhdog3 + ?@[2G6-eFb+d;]k-r7on0*F^nNSFa9-`T`)sDdM%\omCE(Q5Pfof_-VT@ishrLM#5 + *Fe!)IQC3eKg@hAK@p4'/s)-TRD7snUf,`%S>imK&J)*%heTP6mFlRZ%G@?;=TD,b`Hf$dD + pO?OP"nH)i8,`aFi.`,5K&Kg,(PcG/Bn_7M8,aoF)&TM2MrQeZ2iX1a`^#%7=XHM"A7e#&P + o'][s-@6LDI8fD3Mp5S(Zm1HS7c!MGFrNZ#'lmY`ZbAVB\Fn_@HcEOQ)89lEI4PI8,fGnQs + -YtX6Vh/\#l!c`k[)j>:.7lQocA9?>Tl!J:HsuIbhMOH]`fem`"&L?#Ns+s1RmUmf1+@`Q< + LWn@Uso>aN)f)9)-YLE9\:L]8aEnsm0Fb]A0Br`er`s#n^FI.p09o\E#0J!S(*=4[&8=``6 + hA[c7)p1:F=gn4PuC0YtRs*`gMIM$;OprEDr=r.*5RH=5j\"W%%IZLFWqVK8m>5J6OBC2!k + s2'9`+!8F((7u\ro:P-$BD-c2Dr:A>5H!:=(.\Dnr;Rkjqu/hSp-6N@Z%,X`!Pfje![(=CJ + M8d-MW%nEi/A5!:f[ohGUEVb8IU5jFFfQ`-q%JX+X<)5O2>?m7`dbC2+G"j6J,[]+cJ(/:t + ?OPG\7LpZoWsRmN4gKJI`q=5DX:`,"-[8&n3CbM%MKh7+e1Hifm2qO[^h>33oqKf:*Fp)IlXS%H5OfUJtibOa8C8LmgCLM")Jsl5N+\mQVkO!/4qj(Q_N16:cA + ,,%NNfOV09:8CSY%,a"99#S%e + ;T,n7;V"uDAC3#.<4bj[p0B$;gG!4B_bS>%@n^$eT;GM6;0+T_9][-K<&0b<1KJ6bFYKVV$ + D#&(=3`XnKSVmY_/;9EQB7eN7'@g`tA[=QJdb(0"I;VjRcFG>1k+ + GZ[GV;MI60M5;?`XE\d+TVWfJO=N$o3_pqC]]gSJu%>9oW$D4%L4c-Y0<=Otr/P8h![sAot + ;HdahaNg(cB?Nq;9D*)JHnYiiVJCduT6`<_=ZW(\$fkO/pWK"oq2bK[t>J3sSD??k + =>-Qs*(u*TpMu + Ub*XiK6m5'm(LRRpRa$qI$QY]5-#h(hh@9"<`T/b)_Yf2":)8p)@"mi(BS!_(5"N.dm<1W6 + K$WA[?-C[6@3,JD36-QOd7`A$ZU%Ne[DrQk2+k)I;j'-_mf@UiEU/TEUHiK\X+N'>e?VLDM + ?QN='"P@H`;0@[Fr0YDXUl3^fFr-CV6""2TGP[H=\,_7?V9W8W2OScD6]=P$1K"=W\kAebQ + tC6N,g-qg\(BotL24m,&c>I'?S<]pG`r@/M$u:(4(cpf:O*eeq"6dTb].4c![T:RCj + B?=IVs0U%I6:/`/q9UV<-AsCW]R*Q^!WNe=Xc9''=:2R!.JN.986c_m&F(BR>3YgA[=&ZM7 + AGP=Kns3.Eo?r!3bcs?4`;WuaEe7m!#.c3XA"ad+K@(SXRWCn3@/73d;4QR^R'doDe;HCCb + "J=?,9`n*VO(rl@8#:U25YKM6Sp]bU1s%RC"bJ<[:Z=6)YT>mod`ZnG13='L7<'aG,_s>W8 + Me5=4D+4;`Lekj7QDZXL46n<]NfE:+U]pm2TPS3r3S1>R3D0,lhs + mdsoD6%h6*/\sQ9"EJl+I./4.^q?uUEkDLuDupp=r3r/:??+,5f-]ZoMimW17F6">(;cGF< + +4!*dW!o,`)qX(o_tAGYt%a2rDm7a^9NT.7P@NC/O/q(U"F@g@`EZUZX_;,O/D>0aKpdK:- + =_#qi\@X=Yb23l.J2iT6eYdfkG$$!C#)b!*gCrK'!CLR4)BRbaG6H;#_].Qphab*`k;Jh_C + Yc&"CkTj#th/5fkhL/'p+X;1rBTT]bPIS5^\ + AS_>S-qTH$p^m&^q:+Aljc;Y%D`G`5njkr*"tUh'LWY391teTJn*8*B + F:r_;$%k)kK@mPX5JDOX$C,]9biH,=5'J_H\>s$mBAF1/@n,WhL*];$[ + M;nP3(]eYO81:0M5$S8q'I_6oB4@De(A4o$h1VAXdZVlWBS&me!34ulJ(pc<:lcuicd(K6? + 79Pkg/6=tqR'\s*1b1sWEh,@H>Wn4M1o&d3XC"-5V6nG&1umICP>F+=il4VgS,;6R1K;A>a + ?FZH^1+H,P?LGdeLQ"451R*3@#Dk9k6&ZT#C`#]bL.H]K8;rEsKSrBkMo_uY8DK + 1qm#9$`N_'A78IUbR*0Wr-Ld@OL_5nT/4=rTN.`mW"adak+S1$^gS&rl&ajchPgn.1)TLn4 + <,h/:)PbQ9n)TdB'&^2o><2QLF0tN=Bb*aTo9<1[l1V1aF6Z]CW40M9S3kHZB6_lh!m'cl.:;1jiUmZ*2.Dgca`K.VrQgCZ*3#A^-.l`b + SO;9P>E1C!Fh'7]T\+S8:[Dlc/6r7f-R`$Xg$qA3VVd:0_p36hRm/pd]+6T1nN7[-K92?3:ni7,mR"pUem#Fd$p_$%%kZ_$W,(V:u)N+knDF?#td&e8XuX( + K;6S3'LMDeb+u"(,o4(VX3lk_;3Wq1goWql*&2iF6]=%Qm'2dcZbt"n'\'M%A@=l&+[cbc1 + /keQ.k"&\]0o5$9>`8Qgdj3U8PA\JbQGf%]Y_l4:/_S>7/DbaCWi[+a#qRDdp,l$Crs@1bf + ,8r9\`cLXBNj?>J5GJ1jm_?m6=<=e32Kn2"]AK,hL;*APJVr7WdBK>hql3cHYD5^84FZ]B^ + u#iBGHQc7AB`lg`"7nt'sc5=F[0D!3ELG>=Cf5EpIoCM_%um)R6Nl_[O%(0Cu#dA;FlSV'eP2 + Gq[W;;ABQoWZtr+ArpsdMoSL$anDGN'nD?;H10(>pqeR.8l)==WK$2F20QmPZ$:/dfl%"Ce + \&\0c.LA7.i@b1rn'EaP"e[;fp1+]0B!>TJRZN9];Yn`*?6rd8T-k9f1@'CgbLm@bQcN^(! + KaWP6WR7_VjW5&Ef[j[i2>YYG?q<7S9DoP[_$j%`!_5;c:h%8t5?G!BhD7t"CXNE8W]n1oNQU%Ib?W<9>i!TYmEJo/ + cmQbIrGWueEEDgR;,)I92$_a8fN4p&=2!;CNS7[OgKi(Xh48rLFVA%\i)f"VfG3AmF + KAe3Js/WV-7#bH<&i`\A_;.i9-&lGX?BH5Mn=E);G=RJ=^"qn-J8-&=VO;j9e%Nu.ne]_7% + D'MD(aLY02+6j;]:DDRl=KVb/G;\;e/fq2k3*HbNRD#

Snic02<_g + qlm$c7FN5e5#(M?&Wj"Pfs;i.&o8;6DZNh@^n\&m]6K"qVcnYLr6H*(e8!#I(`8-p%r3P&7 + I)KI:7.?gn+(>)3GHn4A@/X9*7Z)8WD,p+'?H-q-$+r=,UjLSnN$!rWttnW?QA4&.k-bl'! + =]e.\"=e+*+=AL$PPm=`TO#IKsl19/;=l^PG^_L7X_Dg`uTeH\E;AB4,F@i9Zl)Ra7#?/pl + +Umjs:=LI3,uh^`a@@_3f4n=)2P)/9Sos7gXZs^'Bm72(GPf8R4Yb[_c]pT>SJtiCPm*HmB + 9lD-H?_pqiNj=Gd%#kO(SVV>f=j[IA9AK?'iBq_B4c=bDKcG3I;4'A5p?kk\.X%9hSn6)%hhHb[e`3S<0V_M]q + %lX/3iddipp:<<15)]MH[.3!Z,fkA5Nlh#$)2ojTXJ0W'9!!%mF&?R^sHa3E)9'Q^F%frV% + eNV`;.>f70na\')idY/Gi---C^%$#G-.%K.BHDtLcmQ_3!PmFRY*;Vrare-sh + jtm">#2]0\mC)u/&$MH.$#X+W)*I&eNT35IBe8SM=,b'b(UITnG?W,Jh/4@ + 4U9+,Wc?d"$pB1JK:+K+#0b!;Q7#>R?ACa$lLEQFlooD=(H*lj;^N.m]e(-&Le\poqS$9'7 + HkJhC1=3*Vgs0dnrj1ICjLpflB15Sb_9io7.d):S.06)8e\2F-&5Y;4llN27`rOiCt0sBqX + Z*732*LJQrg1an-\:i5*>F5YA$7WSr.^4[#RPeSlWAa5ck""b/hs&=?Dq)%D?giXm#l:+6p + 97H4uIe8@8eO%87D)&/*&-'Lp^&t)_%=Y5t5jqAVsc=o/*:$31XeF%TCK"e/kq@E^b7AE`E + 'Ui#SR8'SXl4k5&:8q6ob.G*&b'ePDFAQW*e(7#E3QYgMBg_Uc,c/Wqn?0Dft1 + `h!lRmCFXbIcHbKk]M^R$ukb),UfPa>jo?9e2L&0AMt^G&??3HtVn,Q\LKjV,hp+F=KFdiZ*PmlC + :V58E>Sro\S**hQCrT`i]U$(i9InLsQ?rRjE$fBq&cnK)Tm#kb*/6NZKo0DX!!$a=.(#;KU + S+rA+*`%"&>m5\9a_e3L@d@Lac6D-L(lq1R%jMXqW`F>'8%)q^_GQC9DHtomM"3bg'X$m_K + >t=$VJo/SERFHORn-Ssma`:p]-VUlKar-`l+t_!r$01nShM"Z";c(!A8cpI3PR?$Bb#Em.T(!H?f + em+BY/f#=aP>&Hd%G1jo@'QdJJ2#qX!-Nm%Z^LktRCRUWATnsYsU[hr_fYN9$<[IBe>rtKc + B9?0THo"Ta$Q2SYPXPLS:1`gt1.u4iVJ8\7juFb1;\\=NSnIlmK:_:Q#)T$eZrtCK$&;s?F + \_H(kl9BG*OO/IM?Ub6TQc7iGK`!`_m+$)-Hi`3qaT&BD0omET;X4ZGI_U[/pEA$S*>/IKBc+ + Djj#H4=e8NAK)*KF$ZE7j^*mTE^IP9r.\J0CY(2"+X=es=_*aHNhoF`Pj`pELALo*a[?=2@ + J<1NK5^3kB?rRMbO>[>/oVU6qX'KLVd1J$?n=1/LqD5'uI]WeS^mH>N=Mof2$@nSn)E]N`d + 'aZG0ru\;LoFqhPZ8lh>n0;^3u3qNO>5^@&4QdiE5N:J5N`S7@T*gVN&cVHV@^S-nE"%L'g + /l+7^Z,U<%dQ?Ho6VjRTeHOCX>Yp5mYjV@oR4j;468n3%CAS";jk[BR6R93b[!RGt>"D.Cd + 4/+@bFEEC49GNFNGTe^2V[Ou6GPLl-/G#ebQlXBMWkE\!ClWtb,H;G$WJrWM_[P_c,/1GLF + +j\V\lqaPCo56,_@Z&.j>@T3[A)UQ>A$NF[ACbY,;a5)P;[&Iq4TWW + Pgc(Sh7&H?0)h`tgRFCKlN'#]).sWUAKn!m1-ob<9Y?EfOpl4s2>hsMHJ^ik<04g>deO=+# + WG#kDbp30<$$nj5+Td`MhOPJpOf+GLT2Y&E-%gZ4b7lf9Y22[9OM?bB?\;KKSbf](3`:]^>gE+I#?!g/.Vo] + eC0=U3E$W>Q^Y`-VFkF#LRP>KqdF;:9s'E`cICDgo2u`PGHBtb[#XeI>7^+=1GG)'%HW8I( + %BaTNZhXi@>Aqu]nmP_[N:>tFt@<#2K`o6)i<=b-Ee*6187fL#r,C)K3`]7Q.kt*S(c6mYT + "T-A7/'j$YW[TF=J@!iQsINR;c6%[bBaslBRLJeFB4=IKZ;_363V+>%!/LchQiJjZ + !%O8Fu+.-\YmOJEJF8=(k[K7Jc"K-Dg\r0U3%umG\ocM,,paJ[9'WPB!#(DD38T(jk4>QET + &Cg:Q$rX?\.*`LRn@$[[!o;B![#)>3**%01P>WYuGgknZ2!-8rn\r#1>R00!KbRXQW'XQno + "V9JM;jb9ZnN[>qMNEEBFN#@Pp'Iq:15QAP06 + FY]L@:5I..+?Aodq't)e#[WrZ6jIDR#Ed#i_RuVONS@:J.DI=+QPbrRYFAp;nZH*Hd`ed&O + L+m3>[*]1Fj8!=V:P&7(LNuqP?FQR$"`tIdUNdc[#OVk^=8B-LmN[D;?XYFLA_%bNElcOl7A.m!V/s:N_Ze]Jp + 'C7cBs5LU4h$-,FQ"'Z,kL6\ojq+n=9B&IK^ABTA0`9M8h&!F]5XU7`>J!ncg,_TW&B0Wc4 + :^Z^Pc-@(l-4X]RoNpa&rHYoS!e9'.<[5f9Rl),38ZS$7n59p:qQr68H3^fcJ&YscpUlljl + _S[Y9*2M>Y0\T?q_GUNj;^Fd%KW%Fp:ht`O'#!mm^&/I.$)*+s>(]EtN8*hd&:1PE"1[XH5 + =:Ms;te&/)aV+V6-BBk:g^k'+$o_SW#,Yh/0ZP$8=`?&TsGgEZ:V]co._^[PMnU$OO-$5,m + [.5Z#Kl,q')t%q1@ZM6O9_5#o\GBK9Ze[R.;Gn1FTioLV!7p*O:O6)n;5%Z\QZ[WF/$0gU8 + l<1T;=/6.'el]=PVblat?RZSIrT63b"gV&1u(T0NUu'M8^qI^%!WNB0]@\Vi#&)CF3'1]tt + %F3[/f;aifJ78-]M=g^0Ag64Q*%1E";]@R:_EX.MD/eE,l,.EjlDh[CA5m1U^)+4J?=p5a1 + X85Ph9#7SYFK@gJ$53uPEl])laXj*oU3RB=6Hp27c3p-VLNDr:'Ge,ls#,2P[Y\r]R;obG< + _%O^$+*bI.5WWge?.0*&j&emMu?)miWgY?XMT06;-tqhd]Ri_0a](Y!pVDD*j3ZQM^n.ao' + `ahnW0^@Y;fG`O>t6"&kMYV)A$upGZ-LfAO0gd2Ls/h4/0c3)FoaR8W9'/S=0cRJd]a,`2o + M$fW)S6KnB0k"Odgs8@"X-9.ltNQ,4jqEJ"lQBpQR;\h#nN];-*[&""MJ%u;Q(jK8J^Y63: + Y6TA%+\GN!M7@!pA92%9*nqamd3JY0#ZI:-*n]O;;[&5YI?1P*A9h!e@Th + m'@gO*ot'cJd0*>bE!6c/AW9U4N^:WSfNY,`5ktIfV'%sjd$TVVUSGB=Da]=c,=/.btQY^> + h2Vc;b*4(Wr,uKEUe&[gi^2>P3B\j3\@bdJr!^?Ba+OLgMMAd>]-a%A%d!s(LT93dRbt/MF:_=^PQKk\+^m2J=28[3%KW8n4<[ + li_]R5:+8a>&5<0RFb^NC\BONIfd!]do$:R,(D?hXXSG_q)c=9g4R=+_[2qs!p4+8*;@#G6 + )3pU]PGHpKUp4"&@K.)$@2r^,6C@9_KI[u$UH=Up$d!3$n\u=%eq*30_=._/(d_.PYtH3EU + +48_GiLhu7S?id*M]i#'Y$K?YsZ/dW!p>^gqLDd(/`0E_mh)D*()0&`(X/q.B3XiOF@j_Bp + 7La3'$`^$cG&e-)r-(`6hSD\QhIODjEWleI0NR&cU$#dg)TJe-/iP>ujsB=!0'H<+L + aQtW4(lOk.F(,:G*f`b.cneRs-U=hNP9)qauG"_[CeU;^36dmlF=AQ>.^U3r&GrjL)rKG_0 + ]rbXs'6Xs:L-Qm!XHWgPZil:j8XT,SNWMnMfos45ga*XA^q#l:3(a=07Y[R-JPBY-*>#9^V + &Ed?/GZ7U'90G@0?I32d^5?:I/r_3<,! + qtP4r<`M$ll4nP:XkGPIpnA_Vhu+_\%IEY?C01!55X9Et,2J1m._o(m9(eR3I<$[o7u@6]3 + 1fUU!8r6*b:514"ILf^5X0^,p>rbQXMnKgAZ`_Rmb(]@/OQr5Kk-/;aP+(-u=24j"cSYV:t + uK$DP>^f*@<:!Dh0QuWhRiFbIl;:R2@9e/M06UG#uf3NgW?&dMCoiRD6ppTeE2ZVr.XBWZf + s-OaJ:_[7?a?E`QA2ng>"TUV^3DpSCAsj:L7Y"eR]P3bOC5-P?N&id.228%`raRQ; + CpEkCVLk]=`bKN>T"?:G?pd72U0t#Mb(mqqq]CUeZq9=o1dj,4O)N"c0J4P&jdR9jiB,g83 + g\+;Ba+p[%t'b'e8Vk!q-L)%*AoU8BOp9&4lu8]R\8e@1ABn@U5hrml!_JU=Jk$c1OY.G0b + #nf8]RaSf2%?)[UAPgqFdUg%U%u+%#`m@L:Q40i8Z'OrPA+%(jTG_-SB9KN`Y78pNCoS/B` + Pp)W+1e8W!d&VV2i.q8JuW!Esp)hOJM)_T6n4H#f/EIo"s&)mjb>D"-SPd`D:3npdQ>J.NQ + ,:=pO15$pdQkdVE[Mt%h40s/[<%LFB>fEIK`s9_%'?S6>3t.Z.$"F<[T6mQJW_TKb_amuDQ + uU+Y.US/TaoFTR]XpGAe^d/A6KNYuAWBnZeG;Cgg!@S4ceSMn!G>dC?AAf)L59)i#YKX(&R + IM'L58NSo];dVe';o80"Y="4'YOU>m<^_Q.Y(IXR2f%bT(,%%fp94#nOProN,RG5"k2R_\IFT*\kT,q'"I2n.X(r#H7BTbc+gR**d`:$lJ&)JK^gnX\o(c*:4j/6;\Um9 + pJ:OXDqP`;*GIX(1W(Z6\II0[(+Su]M#ZG-iQ?k#mFi\VeL+,j?#>+4@@mt7,26a<+,H]q1 + ';K-5B&alqXXQGS+b05Hrh)R3@Y%&b?b=K+l?f1s#2LSlE&p;dE[.lc.=h*n"mTHXE=Hjh: + RVY0BtafM0INNrr46'k%//?s<"9,)OZ\?W=LPd)LFtT>;M + `T_:8QCAnS>ehWpMAOLu]bJ@3f#U!8f5F;&Fj3.g.d^ADgCJb3Pjl8#/c;YrKV*9@`>pD5_ + Qb;k1>_C\M>lhV62=-n%EfRR6'L`T/Ap6b"Q9hF25R7T<+%UL#Jb9>H=F,4%%?86`[3h^T, + E;hEUlEKII6.dWbE&=c'RQ9dWoqC`W'b=)NVD"Z<.51&gY;ft<(F\+.O7eFZ`Dsq7d+SL2< + CYE(qJs6PNLA4sTT*ju=T`uW7K'm6YQQ)2e3r'-)` + b>6Ka%pg((?/%o7ZU)"5n)#VIO<.?KO?`"&!/j?Z!P'FC/Bnkt28++-(U7O".UQFDdKXL]h + O-&HXnU1'Ah2(pTW`72&9-&8H]no5h + SP"D3X.^@$V$GV`js%*AE*gJobU)Et`=bIV(&)*85&3Vs!i#hhF_:t!XgS[.TI^deKJ9-iR + Si8^rY<2;EnoNDHCZRog<5cE3;R5=?'2C+aO$eh_jd<8Br(.]Tc_u`B(OUY?"E,kJ_,n:/2 + 3Jl&(4g7I#eZ*:8Z(1jh*tD`,h.)=dsXI1I7T>UWijm;qWF)i]j+ac^KJP*?7md9#&%-$-4 + kEGh`-.&KWl0)+Am-We(0`E"eh#QOC'ld@a;8KcG4.+i,$]ub`4T#LH]_8*,uE/F%7hSmdH + *K7jFK&eUKTdkD%L<``tZ=>rOFjjll^B]RUbQj/trABGr0=2M2ja&;UDe"M97;i6+YX_A', + ,.UgJ5Jr1hGMC0UiW5;jtP`@#ab9W3q_NYGLjK_?6VGSk-b@OT9AA2V0RS5'W3PHV$Leb]N)`;d,GCtA_sgK + uW4q\m94*Hc>.?HbNj*fDGgX"bX[#E7fP--P_QZ@r^0!L.f0t,:@ZgP5*Ys5P/J&g^3@''7iB;s\eSBr.a!h(f + 'NN_\AY=>LIjeb+c2CWe=>c + $-(3aIiQ"qS#Tm8;>J0RW+UJ2XK2r]$Eu%AGge"Znp<\j8k$[sA4jkcYk"=l3Td=lb5@AUb + YoJR,qEnYV)6=.><7`VnHkC=]B7eq3m3]W[k^`PCs7@B05K#OSP_tqCJ`,Q7Y2Fu7-? + j!F2@`:fN'#L@bB6pO_!-G?(#j?F$pNso#81,"`DAq[=4;oT(c1ueFajpKAV1l`P4h,&h=+m9$C(sc?FTiIaQaiH7MO9dC + Y%,i`Fu]Yn0'TBPA5eo0P0fPT;g+^W>L+J+1?6th4gtgUWnX_B?Ypmn`NO%>g&?PqkZT$`[ + qComF]Y3$nu/PC$@QIb]koe`s0@YS#4cY':SSCVX9fVB%^%K]6T%=\n + 1M$:e,Wt?\F\h4Z$"(`:j7i'84[<"d6sc.,C9OR!X>@>^md8i&m>Laeab)1_UmB&Z\psfm8 + \Yk"9ZeW@E2-E>[kB4gCaSh1&kcp0F;)g@j@;ESRgX\u*^&h>`&a[3.tq$2D=1O;)t+T9p\ + q_FL:h$=`b/-B07JYWr"^TbYID%L[muPk[C%]gJqo?&$C2YLtBA("VOh_'Un%qW!=Ba6'pjlR&Ie30ROMM(PN.O# + UtGCaqQ@YJu9R)TPeRWO!cgF!8=+:4f?8i42LQG7VH=f^kNsu6Um)%q$7alN+'PbW7QeC6X + 2&X0Jl2oHc&f_Le'Wq`K];N>9MWm8_g;AoBcXT,0YpU75T]HY%q8bR9*#Mn#PH2YTkTG:T= + 8h_cBh#dL'(q0AVPF8&Q&d+9dkSY!aEj-j,'[NGuo0;D<80Nn)X1LQ.`%-)?9r8r<-iV.B: + U3CNIikQ[:",gRp5Gq.QVb^YKUs2R[r/EN\8QI=Vp9:9oTjlF+AY->.JP.&f-@/=U'Lo.Qm + QBrbqf9p[EmB&P]eib0LL6]?$>_B0OhYZed*<\Fm)nj+ac*]BnBf + qReAJ'g"O-)CbFh"'Z`Vle>jk%O"JC%5n6/mq./I7!./%AD#RKU)@d5??f'>NJ:mXu'C5k$ + qCs-jB^N-n>96RGhJN(-a3UXSt\;/PugVA4ROu`5V$SSHg]J^4eQAUk#,9c2^WH\qDC[+I/ + nB:8A2)+sKLlP6A4,DU#bd<9f`K-)6E!"OYj_a&N!Y'E"[@[miOE)#7dhVGC$FJOAa7D6;@ + &\5sU1>jHZ?4tp5.f9#l9hS=4BQ2Vj06Z*_Bm=bpk*%b9^'k6N;=g?OAaB9H.A&?3MZIW@+ + =dq/]m,aeEeoJr9E45E@n6r]T">ec!WZ$M,XUZ,Z3SP,4CsC%H&!l!9>EK),dZB1q2Z4#>l + A-c1;rE$6M=E9C%RYqBS:%Ca%\_GhqA[<]SBn>2&7KIp?\-[D,6gEnbfR]?E\@R\'0$6i-<:ekSk)$j4&-l>*MsA/3Q0#4Mc"auPK21SXq + n+O?bO;&(-Umbm=Z+\+A(5%6Y'?S8WQ4rXUUu9BEl/HC-t.8b0Akg^N_CQci+tf3R_qb*Q> + P_-KX%;T6)V6,;<0PYB:OaI*n+Ge;?pW4Xgg2a0jX$=,MYMj"+1C0JQQWNs4HpQu@gX3%K\ + 2Q+;n=oM^R*G**/R]C!7?dg96(+3c9rcgm8Q2<+jAD#?OM=rO6[!^2s@V6%t4X0\4J>5(fM + k1nfsDRPb@W3]6o"[7nVA]dmE\O57*YC3',qMJ[lF(@XHO>5qe#2%-j1FhY`6tR@SmUEB6PldVc##<8EEd+rpmgPi-[gKD,l;_7Q*p*5`%M1=CtUCB3ukr3Ui-Dl-`-IlT'1IGEpjB + >U5tCYg>K%I6e`&X)Y&<:U(*mI6EVjIaorV(;'c2kr5'iSYI+I3TsZlV&_\D?j[Rf'^57OH + 4Xi?4/T1Hf-m*#2nt^kJ"u;`_N,Jn9DbHW&bF\=O=5X6e + keiSId=_[8oS3L3qX"Z"$p,@KW%Dhg(s.B,982)LLj4QK5jMks##Jq(Y> + foVb[N"&A&nLtD7V46\D2$;>UUpIA@-j0raUsBVML1`i;s9]dK+=g2.K%Z? + 3(6\a-$Q;e3)IV8)4#/J,E!_\J6$S:9h"8FAKhM.i5:2"makM6=RR.I0g!q7mkcT#?FnGf8 + Xis5-OK?dLl]a->_'Tl$&d8(Jh;\Y76QqAi4Xl/&%mTb@,>f)Adb!96cl@PklUnP48J\VqL + >ja6/(lFt'5roEG`"b`WI7'Mbjs$[Y3f+%rK4jU^c^\NU^4PKB4D%JdW:o[GhK/e\q[Q_MN + rku0:ou83nI\bAdGGuHp6Z16R"DF/BoM66nKYFm]oJ&U3%$`2B.[$h^d]Y/ + 3Ijsr[@PYMl6piL<_H0XT+Gn[?DhpGa14uXA8#VN2/hh`d18Y4E;$gE]Mo]DJct!IHClb5. + n[KQp.\C;(-14_]Oj:'!DEb@W"N@YsA)^`Y:l,$d8mo9l6AX&FYFq@@ceQ?E%h503O + ^opFi1Dk7?4Hu\22NGS8&S>gQm#&J6nMY9`mj89r=C.-97trL+o%!_f-j))4X@CN8+SM@ig + j[*IESus%t,p*SG4>'SSPs:#100b"H3BS3%&%8_Q/LiXW2pD+6hR4:+_@ZHkV4rqd47:ruDL$"ht?joq),kUq?@B^C&=qq'5'SSM6WQ + ITmd!aEF+A(?*al#RdjA`G[)bKC4$1M_eoL3Xk*R7JrM?'_RZQ0N^TWVbndW$;7b>hPCgbd + =>A7Cq*Np%rQ:(dDll9nqC84Y4(Iu7-#b#kL>>,DT-]BX(,a4Qp&cu2O$2ltH+$A9(.L2b: + s&n*$u+WqYr-3gjQ>EV]bV]HgD63b9Y*kGq + qC3`A8EfnC#P*Y+2Pp[7@$V1.edl1)'4FoX@?qj?WaRMk7q,LEK4m/?(,'/(>;!idIcp-97 + auo7d"t4WV+/\nkkp@7k'r1>Ur + eh\Kcp#@9@FTE1I1WDJ5,2B%1Y#]YRIWFIW(*XV + S/3>B'A-TtP1_rO)]`b:1?Qua7Jr32n9VkD';$BHtFrb"W+A1)G4%$IfiP8`tCBSi?Bg+kC>+3=I#mOuPX#HhXp4U/I=Q*L2SKDTin2&0Z0/3sX*tX;>W;d-\RHOs;e0=SR_ + )"1K;[jeLm1YGk$=d;_h+L>o=?O;+`kMl:aB*t[Ji\i!e.UR)#"q72A:*I=;bdsIjk5$B_G + )bs)s`EKpZ(ufO'@M@PS\W"Sa9D%XT9,j%Y*mhLXnI_M]N;g5fdP+GRPd_2o@R^PPt',_9b + G+TUK`'&k.%iaN?^TmF?8!i?@Fq1O4W`3Cu@6p,?X::=i.`i1ZBe4%[`n4s?re:@FNH\PYc + Ne^tAabJu@?K]s_R1WsucA!BXMB2,F*G*tAA&_?*RQKr/l6Hl)BBncX7PcLFBn9t2+^.g)] + L+N,W#[V23?75eR-nAmSb$ChHO(\_[nm(Wlj';]Jj`oH!38\ZjG;4An=gif8!hMX=XH.# + pUr`LB"A3/LHDZG^3TP@]&18)V=TYfY\XGUTbF&G%/esL(;p'&fj.;f5 + !0:`oJYDUbpAutQKKm>?H=Z;I20_BsPY2LXCt;=LH(1:s[q0/D??:9QGZB$DMb&P5VkMOqR + 1pTc[B!K?WeUgb0h$`l_jWYY"P3eOG]AIr@o<.B + Va91;m3g@#%VKJ$H*7DmQ(APu?Z`(k"ER[T5)=gtHL^J`,OCQs&b-uV;\c)get\=8DdgQ-W + [,9DPILD=ApY([XOM22Y#-nY*.7H'@3;B+T#)?I\g?r`I2_Q(G@'0qZt>"BCmb[L_iST1Gn + EN"?+3^Dnnl2&+5dfCE(di8FCT/'jsQ.JuXg3"km-TdI+g^Z'dk7tMu*rDL`JsRRQjcEUOL + kH!UTtkJB&4H('4)u05-g)Z5V]m;n7qNZ,X-cg`0I!5BNMPAT_E8P6,+BHM]NJ=UU3m[F_] + 3i<*;J9$2,B5ZH,1JUaIJ10bpNY_`$qLl89F&97((/WUWQi>b+,NT_onVkZccU_bFGu_r2a + 2:_G6p2bacl#aS2:)#$lNB5U;(TB0JI2q2GNe.0J/&,QF'>gRm"fa7=,,=pN.1SS.\i.HAu + oa9I6m#_.TRU#DG>HpB`f18]bJ3J=>1$.,4RGLKhOI>'r]"Vg`D-Cpp3ismqkUUH"Mr+oQ* + ?0DNjJs/.JbIkO2RH?qOp/Q&=^&jpJ>,+13nRrq][A&OYH`S5M^ + Y2tj91DllAQFPsajs9[fc:,c(i?0Zl9.;%klg>+S^5IM"s&APgH/6R4hIn"CGFhNYCZ)E_u + a5UIQT%W_h"IX"u<&F.W/g>6o:DLr@WMh2;pi7pU$Y;a4s'3f(c*n@frnQVDs&PbYDgKJ-? + ,2#,t,0U>+NXU#d'^k%c5CUic',C.oA8[DoqX?m3/qTKEs_8eVT"I,gKLJe:jh#_"p-D>/i(Pp@8j + .CXJ-Am!9Xofpb"pE'-KTKN3rSpg&Ko8`'"_4HL=IZ7CBO\T2*RU2LjK>UcdYbah9]Tn.Be + VJ*#N)<#0=X0,5A2]ScS8]4:>!s1Gi?T5U,RJq!`nY;#_HM!c1`U-hjS7"A=OY&'ChDrCQT + PZE5B$9hiIRTPPJRPcJP,efk&JOU?,rt^]hWAWX0aGkOr + MQQO%WoT6JXU6.oR>5Q4V&`hehsW/Zhf6JnKLt/b@K=l + .2.2E21NF&bKfJ%OG]U#1mYO'#O'&aJ0`$bn,ELLKqEZ9SaQ/Le1nIPkON3uua5cSqq3aD. + O_:`CE:]VqkAa)bb'?:H:?N-ZZeCNYHq'`+l"#ZM;5mO[Q!1S%UFVBuWKaDMg209Pf^+o.* + >X]-N5jn_q#t6dEOf?BQWplSnThfSl?0(BEk<0lc)fQ&23Q?&YL9D7lZ;puOi_/)Pq2L<@/ + H?UC\,#aIG(<9B]4t2;h;EWChh[merIG7Z"@h*>?45LO1A`ffUL^js+.dkZ+-9h52D@/ASg + %h(3[BdX_K`iR[2]TYi>%1]8iKS\%qM':@%_gc8kbub2r+)Wq@RR3M=^XR!Id0'(q\Vr6ks + neB5Q\'F+\)YVZjn5ql7S#!oJ8*sb1ePU?f)Yd?%l6SR"K%R[8-6P)X?&m1GGbedbb.?)uF + rOsYRI!EX^q;1T]Vc7Q]SIXW&d^80,MGBZeTJ%HZOrTB*8Ff`>-:ot/QR>B\KVp3?ZB-\H' + >lh-/erEs>;-g+&SEj7dkPu%(UHtk2E/oIQT,lSOek)]L8kYuTa'Jj5!)7HaZq#i41o[B-X + n?F4@b'6Gc,=Km/Y<:'h;.f<7Oc<0C^hRbf*/<=ILc+&p6 + Z5EN1.(d$,7FEZ?t/pi!ag=g:%`L6J/f)/_0aAf);'H"\!0O_\\I2RL8#Puc:,DA79a6#[L + H&N+SB(GRF^&pI2"8<$n/k=k,o;8b;MVdO<']NDd#= + ;Q&%>PtR!D\@+&L@lW$uNaoTm5EKD4le%QSLYPgmAN`S + nG-R,lhIZ(5Gf+C>7Hj4m0,/7Sso=EcUG"l4iS(e'YS)=Djh8CMU8b^T^Jel)h)6)SrgEC9 + 9VsW1:-:DKSFRRXI]2Me+P8XmeN6g;5qVGW+K3Y$t[`%``%Wj/f+H/GX-3 + G#jLbF6c=X9Ng@Ws + \PVj%B4H!+(L@b;_6HE-J"]N0Ir"$!V@F%A$dJLZP^[QV8(WZ)o^GjL2I4MaW`Fk\7)LY%f + _tR:t8"FD@f'%g0LKC$shA[\)E!`18^DG>sJIik+5[g`?_18Huig"?fTihOfHl!PpASMTQU + b3F5]GL'kAdgJO0of)TKa0m-/2OBX?%JhBC(X]0,5t7T^Pa + :*1R[S'L!KN4WeM%T#;J"&g]B4PnKro9"A(TATAc/N;?[M3GC'V['uA7k$:V/8M=X+YF#s! + d^[4lH]/R+,/imH,=B94&](5=8dQNHfr6i5C-,6_j[(m0%40u;o7mi._+H(#oMC_aQZK!Z0 + fY4FB2n>dNf%5j1b1YcK_L9.JpL19g5-0q)Kb8_U_H^SU6J!!Y\!7:=@Bi1r#(0e'`E)0f#H0c*fI#N5JS+j9V4Y.-L947*q8NDapH0\-^,:^Qbs2@4DBd + p[Pu5Dk8bFSHM&gfRu,NZ.1o.;NGQ704,lpQ%H3Bt/ksR4)*KL/TemE7S.f*=^,F]]F-g`1 + c56c\j"-CaZ'2K2:J1^Q4@1%XN=,nP;gh0oCUWr-"Smj5mlbe;rl=eK-eRDGrC<[+7 + Psa]-ti<8e`B.oM6\h7CH=K96L$&X.%.5`m='Gfb*$,'HOKZ1\=bH + +8#0o.*,%T9jKk:#Yq*&rWP2'$2"E9@8\J>h$HcBt`rZ\uKQDcV;?)b4UZ4fPi4[b'7DNcZ + _+R)tb+f?e(ZEFQ&cb8I`?cON`em_.;cG(h/SlE8+3S\\%0*oc%cDGXtYT=S[dmbf&iJT*d + Z_0@aUchR=\BIiO:hmPt2cg:*F\,S#5fZ#_(#ie'SK=,Ar7eO#cUT3/bHD,h\#iluM/R"V^Gg.]0m8kN&2`#juY2iO$@]!)=2t_aqT?$a_%rCBSKoFeDilH71 + ?V`5IT.I9\LI&AS;U-2sXfVB+/J>@L'!XX3eLb5gXb&O7RbEHF4,";0K_jN,kY/Uoi.Ri`K + a:5%\8R%n%),CL]GqY&aE*)$9.'8&Tt:sKdd3e,a0o`*Y64U:j;u/0+2S.'_@c:BN#fbT,J + lhoOGkBP"=6BV-c1Nb_NZ.UZYm&MJ2#`WW$&b'@c@]$tP1UYSEKYSM6+B( + 9]$R!:q(8FWtq_001isnoK%YHQs=r*.7iKO^.W1\s]5t/VOH*a"[3\!tBbP/5W.E$m1MrZ@ + p$H9^-6E/-r#E)#-@.,QPbe>SuLd=-B).PM.A<'#O(37n]l4'k::o^9g"ZoWd,27eZ&%bH9.`#pDS%"^ZE0hG_QT9/S;d"d0AH\A + ?8*j[,UgXK>:k?E*#=*:;,@a,0B1VUr\h``_I$72[ZcbbDb>bmmA@B['\''H7bETQ\Um";? + ^8<.1a^e(0gCM6`!3-.[`;jm-+BjDVWQ[[3L-SIbN*.pP0U!A0P;tI`=Z29(2j9(YPG@T&3 + .s(dnX>;#5;>OD._H_$U.0bN8J1/L'Y;P1aih=I,''/+_u;gh6o$SQ1/'D.NJpXCAn7EeU/AaNW*=ZXggc^HD + Z87]b4fAG4>kpWnNfc]n7OV#&EEqp34Yb@F4._hKum+bu8;6MP[S^iH_WD[(/a#7*r<7.3r + oWWqBRJJ_Aj-r.[)<]Ni6+Q7#WqD4faQjp<hs=6@+:li?PVQ;`#*X",f'2 + .R39io).";Zqn8cK-e-H34C3AH^g)aP@ZW6[d9JeZ_`udBu!`V)m)544D/2e($V1jZ_/d5L + ][u1$Z]e>a<(Ha04"o8!%i&U"?fYD?e1$"TTOk,jqsg?%hu41:_6cOB=]@286B#502Q^)RH + J8JLb8f*X%?*)tF5"\5+(:6)=*1>6I'I-I`K0WGIeWj@[VG6d+RZab-ZOCmpC#4Veu/(-'s + R^tbV&9pUWMQDnXW$W4;^;p%@#`QVViqcPeD?%e:ee?*`>.4>>/%u3u"##qb(bccf(2G!d* + 6tI69]D,CVHe65af@><$jaPhSJ(OpTfG/tX77!;ofgfQIKjEY4e[4Cq]!][bJi,?a-@gXX!ljhBLB^YB".g_IZ\,Cn,$Pu[] + )8TXmb+F(qZ,JY1r:#dOSJ6buR%GOU8&tj203*6>$7`ZLJWjun93GCKg&BIs`88FVD6^eLe + '1;S&+b9".>ros<9^XaO5d.bZ_I<+L;SFF+5UD?g/=Jd(mRd40l):'. + fC<92+(K.Y<7>qJCl'6)^?iAj*lY.k+c=^>*3I+$Q0Na&<@f;5"ph?2a-=BIBV;9_I9ARSt + kGJ45iPQ`:05]"Ci8s>K=%D5[F"BI;E=!5^h'(f=)_V?`e>ghWi?RSqXLbmuO1UYSO#&$iQ + A4*N6*4e#a?e'L3r]9)J46+=Cj46BRk!%i!5I9J19?_QVh;Z-8+2.j9#n.X(%i/\<%BM]%Hp.bQ56gaF#9i)eo")J27nX"&>HT!Cp-<2o"( + W"g=p8;1"[tD:$-B%Oafs;)=Z]4(Z&PiVL)ci:Lg3#@Tp#VRg>f]oc#(U*9%NK&L'U?P2G! + !$7YJPh#>f>nD4"mgRB]4a@FdG1*?&M'GNX^*kE^DVVL%F6HfrCrM(QX)*Q6;&`O`J`>_Su + 9n7`(,E.kXm="['%cs28!3n2e^<:*J8]hjFRYq%$)%@YWo>b'\TFQ"B2iXR4=.g4oX@5:S]$JO2q,JlRsq/T6$Y + lp^jKOANtQka64L1'4bKchJXLi_=/o\ZI)3YTPFSp"Q(heAC`lW1FZB"%#K\*JdYl]`K!95 + e3CG.;T\Q)15)>t1+FM[&a]Kd\22]G:^%9EFK_=QDHonM<#f0A-onZQ.2DH"0P,4Q2B?Rq0 + m%FrIEf@c=uKf8@<"_q!kO\a+Ef<%`Z2/55Bb:"X3QfS!)L4AjNuY,bPL%@nkQ[3u\g.4o9 + Cd]"m+oF'qh%GqfU@h]KM(f,g@4U1CJ>fMKa/I.%Z>Q]E/&'_[[$u3u8:hU3A.sShNU + :B_jAsj!=OsHg@cLBbc7cS>F&MG)[?t)"O=p"b,Ec/N#'a'VDNnXZqc0aWb9jT8s!mTK/+U + _3o%,Jak:YqHeR2>R>ZBHEjs/-qjUp"ug/)_mk\:;UE.l"]*cmqP#7sR[(j1T6BP02jE7BtZN:a;d8TmU7p + /bi!B4gj]BD."cCT1?F,Q2(1:#M>P3p:L95UU]t_1p$(!D;&L%S]J"?:3#QkFTAX>5$?KeO[jLid>nnn15dlQc- + AkCNCUTDAecqA5k%-_gGrUTYIF_RZJO*^`Z8+QQQm'B!d5#YFi=NeOu>"4i:Xl8G[%Y=MPH + kf@;b0PFa2C\0Bkkmn-j\f0UOghC);EgNBPa/!*-V.m&"E@B/m9MZbu-1.QVico0+3B0Guh + Q8[d"j5CoU'oDR<+>2unaK95#YRVO61>.?gu&Te^?RT&EeVk$DUnUJACUA`r9%uhqmY,SB@ + d$*fDUKWrKl3!%tf&'&]nrH>]4F^7l;jMCBdm[KZFf(l]26h9S*mkFcV7%a>4=J=XM + :-[MW.@c\E<)LK7Jl#=a+nH!/1[IAR$kS@]>F8`aS#BkQRD:Y)8 + @o`,7^YO@LQnq&G,\-]C)i + .^sN-i@fH"??8',ea5;BcioWjnAY&^gM+`4=1jRLW!YA=M/6)*K + S9PE`kN%="Tc_Aoh+fk'&t\ctIN)4XQLiVpoI(>.nMXSZep=G#VWs$@_iC^1VA@bMI.1H>m + J73K(l6a*.#ZI()m/c-LEP>Wk">lj.I^OC-A+*TK=!'< + WeUR=%ITYL9=7r$^RP5&I!LjWL[fa4=o;6K<-R54(3N#N9Fh&l7??eH4)JXT-mH\rN[d"fF + IPs^^@2$&R9N;hHO_e;>X\h/g&ucH\ptI1`bBDaOnCrP@*`s)S8DOUiU^eFtp\%5hObPV(+j; + SAb9/e2N-VsCC58&Sq1K5t;Be%a"^oi*O&>=/8(db^j91$q;@Z#a=UcP87#@%(/k/Cb*?S\ + L/VD@]*5#d+:/1X5->+'2%cesi@;4.NLKIU]pr+]B.u"h6J3Uu[n1'dQr(TbU>GbhW/^kA> + 9>`&.22&3!L:\-c/7FM\oHJtK;^*P^OPm[**up\HV.HaB8'P2=29R8-7ZpL*\Yam?Sk;7]K + >H]eU"8iGHQ#1`,m0.9rg"*$d>&7g0L%:HR3-slIEAG^M\Yn8e!&feWSK\7U'N(i\ptqj6s + g6CPq""!&Y]o2$kADs/)X#:R:FLbWT!;%d0i%)8CBUpMG6\,74G'4Y*Mu>>taF90r@pqRT! + C,:$KVW.ar)MZ`$'q)=[E*"4a*+.6>l)8kVa80rGK.N[ZBHWN&ocAdOQ[5Z<34D1]%#$M]Y + 7U):FW;#jukiJ?)/d*#k84rZ@MAH6*Vk$3jg"/KZej\?.%NXVWf4?>r0Sc(URR>GZ,SY:\j:n23]PP'PGQbKW#q,@Z3 + so;qQGXJau![f_chdpj\A,!^MPZ"bYo08%!\bE(H]QCBJj%7O^2rS>># + 1IsUHq^c55.R+-5;-l3ms>jJ+$[5K;\X'8bCNHq`d-HOl74^EH[\[VuN=_k-m%9-cK673bM^bSb'jHcWT^#BAEHXo + EKln9(Nfsf7f.D[,5P!d!4SZfo?;"/sKjjDi51SQaF_1(ZKNm7(T^*fAgO\c&o7h#/&P+do + ^A0;2R)BLm+4c8soo31Ck.Ra_:>iAPRD1k:HqBp&T*Y\"]Kd8qE^C5rFir')084isNh!&(3 + h"Fs)#/%3`)jW+c1ONs:ST#?)XWGjK@. + d?H-%GSo;-X`ob5/drg5UqkboQ74jn:m\2b)0F*!1@&phtP(^qd8j+qds1PhdmON3no + !is-!,G:ukD^Ilr_$QV6bF65L+hir%aB"C3JArY1.lCZQ)>J\kV5t#sH[maEhkJqiDhan8) + OcCfeB"cJS@LZbJ=p>PF8T&@&9OGrR[3X\PXIa=,m']Q#:Z;ouTct(7X:eo9p[8[MkenH,3 + !DV!*.b?),rW[))Z.gMYKt/P8Z;R6<<[>E"gD89n6lMTAHm*f#-_oH7UGC4%bT!5KSdMCaZ + -RoMT4j^>YV)0_7UP6i<%An,6=#B5a\STi2^V(X6'D`.`R8ObcM.mX_QR!9&=:iiZ(SQ8P8 + 7gNG?]FoOR;XUejf7l:V?NjJ?cm`R&k_&:E/E;"eVIdKCJfN-K(gHlC(BX_Q:"MTj/=#coS + =I8#kgCm])Ld+qBiD[kK?VYh,O+qDH'&qDhGYV-\<$ljh!$0NKQE73uco#(WVc*&J4KLjB@ + V[d]\(9q]lnSo@"FVf%C(U8B)lb)6F[,WSdl`%h$d"JA.6E*Dr>fJH:_429l_XUJh!l;G\T + L'AZ8/!UfI0oaE8)-eYjF1XTIcS#1/qCfi[QuGs3[U$-9'jHHi.5i8\Vg1tm\%M$#o7N3+/ + o8NZ%gop7.naW820c2J_!.>Um+TP3iUiekPCc%["i;ZT$=os#Me)ZhV;]3go-a##c&S;g*" + Ys&j]t>5eS)?0dUQY*5R(80=KHRR*@&:%?:Mf0.b&hq&e%1-aJ.Jnpr2EKd^tu.'eg\ioM9 + cDQub`+DJS$:Lt[^'V-M>SRK#I+i"clF!X[O?/^#A^t@,_VJV;Z?fhDtAdVr.&n,AheOC[K + %9c@u?=RK:A$'>`X_QGORb'4.nr7o1:fAeV"EfdP2Jn>b;"1DuMUB_g)38\8%amUVipDBp9 + E`<%"6u2*eF&"H:*etpDYB`s2"0oR[fR6\@3c,!\M\6jU7n@tcQ;tHpB6ZV"O_?5Es"+;FZ + 4Ae2m[oko78kWKfF.0R]pgq;?I01'[_m\&&Y.R"je%co/2]B4(5'#i+c"Q9,k$KI:q7tDK: + OCG(-I>kK!@IYekocL9oN8D`olC]ob[W+3)]:7nretr + 5YlcKG#.(,4s.m27^R#%oPmAXF[pP!8$mY5-[u"LQM$E0Z&`5E:;%MT4L/ne)/CcpX6LZ*] + G^fn)IPKle#FTX^_2/Q(#_Ik08c&+N_P@B9b3O+`5G=[Sj2Ie5\Yb>Z.M*J)OSF30(STTZs + =-7p>BU4=nI2>j_$f]eL?;A8C(\kMr9fgMUF6sL(OQCBDW7;*>%?IgSf`:BSR1uO?K\24Y= + nkE^GYG/L6H4&j\>e+TQIo$RM%AhLWZ\J:&B2e/Y_l5#GAaBqJ1#q#qK2:_eC[omp4&KiiJ + S=LF,jm,p@m[nq1':p.<]XCg.PAX%(bLVpqdR + LC5TSLXQ + Mt_JB9,nod5[u-I4&'dr/W9B40;IBXWn6p5s&IQ"bE0Br"[1eks6'9s?,F^k*3s7#n'o^ei[XdW + si:@_TE2+:)XBRCG&O6nTd<;Q^:ZF1:SfG'XpBrktjKPu.rg.[W5:]41Use[AgG>O$mdi^4 + Nr1#\!$NO#(?$G\enK:c^7ldr(uZi#11ZBNB(Ao0?o2G6QOS0uKm7fuGdiZWpQ9_WLDWj3C + .Li;[$5)UPiPf:*-%Pg6%Bpu/-FJ9bJA34i9=LK5Y[5,/E(]XD+n@EG1@kFC=;$4NEf'dXr + PmVqb:a`F(?>Sm8$XZr-!j@=9'u)&H-^/=mTZ?O(1FsQO%FM;/bbte.`iKW5o/_a@6n2!J< + ^."DlR;D0.CIk2Sg%?>eos6bt71C).[1-3nb,G"O(o^c!3!YSWO,:D7GLo)3d]EEBXPLU_b + fpjn5\Knsu1Lo\X4'+IKVSFsu[=u$U!URbTIa^V,"'VLM@Z`4oE0F+\KD!CfYo>#ebFFKFp + LJuD88814@`os8<%.HPH@I371"!:QtLR=']imE9kIf57i@CVjX32'Nlbh?p6S^oMn-hefnQ + J74F;W2rW#M=jrP!*hJh"!_d@K_Rn04gh&&tpE[&17Oh?'KPF4$gduTNW@ear=>rNY,K7&o + hkmlMR%>QFUjuq/M`]FdIB!Q_ebKO&qC/)ZA"@C@rARmRS;5HkBJb\'[5=@9O@2+\X=p9TP + B2_s4UNdQ_j))6)Wl6*F#oX)>iqq\8@Fdd_f'Eq2,:NckSMj'e;-j=<4_%eghXc8rZU,efQ + mdW-BoY1+@G-lGVAIVJpLp@euJV + K9,2^tY'+L@.&96W9ej7`N]8M_X=[,fm?f1Rpfnt;$?6_Q/UF%d2i=:ub"W&fMM_0-=?EG' + Lhc-Io+Q@.)m_'*M4//T2sgqi1CIl?@+VuN8T'i%ft*:_r-QtpWE8!WdtQ9)p&H;1bknXd=)5WJdI + E;8,W':AB_jG\G;:\5QAJmjUm@1a2iGW&q-,P$Y_Q<:)2A:VNRB9C(5a7a'd@3E&^)On?&psbT!GW;+q5 + D,4/>6\*Ad*Sqe/5[H/[sIdn#(UIjaKp40!/]U'^p6IohRY$=*q3%r%3^BlAfh1H5N9e;Q# + V!Z!uAp)\''7-h?#)lVbQLgZXKW-e4Ag=-BsWP8-KCel[i=,SC+t)8RZo\hhVo"=%*!*FZS + u=^+r(.j#\GBKJ#OI$;o?"jhZ(d(Kn8r=F,l0n>O[+I[&YgVE/+.^Jnm)Sq^Z+Yj25:;@^1'?1m\b(!@8)S"<\E618Qg?A(dkr/7mg:`8(qj((=d5ZPo%1M8;) + e#6uJU28U)>O4*//qM3)NuL4mb/N/4A;O8mQkPtADGsom:?)#AP*QZ-N"MX+:s+0&n"5YI- + +;B`Y9P5*J)QW4S,V^I90A9cf@sTh=9Acp"t2nhnB!Gj!D(GK3=UrM7Zhm<[#,$"d"A<3uu + [p9E;jo6k[&09OV@'WG@tVQl&fP9@;&%6)dTF/%KrI:4spa">96\rP:VlaOB\9rW.Ng\[<7 + `ER9?je-iD`RFt?_Y(j-_`4J>(s)Z;#F9-<`_-sPa9sA\+Yd6iB;bEa/U^hB*"$RgP6aRha + ?jgFI6Gd/\4SFe_(Ot*)k!%&VrmC*sea7h=_OVM*S2U]Lf5^C!rdK!X+bgX/Y.qf%'7e3DQ + -^:He>uiAWPO[3>,,CiXG0oY@o1.Mm]_eM;OmTTJk'?>jg1u7''5J8T]] + Q1k6(`bK]=l2maP5]jn-Qgum/]19'9&c@cN4_&A.g#omrYcRiJ%pa&*7b'%G$-rs#Km>Q2, + otq7iKrp5A>2S&r>c+P?e.:Mm!qX/DXGh#=SUi\DFg/-Z,K'W9(&/no/AICK<%*+=V>qhc9 + GO1?MH$u@Z/f(7J!j$gK<5.dm=&'+#H#l]HBi=M@R/f>+R6fJ[pPYNYBMacYH5uX:6)2nCFW*tTT3&#CC\*BZbGhMKWB8 + rURWHW=oVF]b%qiWeN;0,D?W6r&mX%*f5A'j3Vba;BlZN(FT%@nkh=#Rc*Zl2ZalW,>r>qS + 4YM&=;Hi>*/Qd=phqgJJdT@`Q-rVo+O"9SZiE$Pen5k%/T"[R*<)Y(Bf]:OU%c<9l%.&o(X + HakZlk79$IMf,O8Wj1-V*@EsIXrN>h_J^BjiP&4s%>.k(7^9j:*CfSR/drLa!W%M#;SjGsf+G7M["Tq%eF%lIKD.7'WAi4=3f&cjN?.WZ,HK`"Q\J)i0^JC@d&]9%sLZA2ZJ#DW0IfII[rI9=[qu8nU!eq@sMEfJHr#Xq:Z'.Y&$XAMsah5IPZim + HD:G@'T@=Me+6):PVuR]FGcXVunTC_e2&=9e=J7;8S3U/pJq;h?&sAKgWR[ISBCqhTe8FtZ + ;Z^e5L(,>V!uoHAeTL7jZ&:,#YH_/r@O)6,3.D!eTh`m-JIqdUN6orj=2!7oSc_&'Bl-]=d + h/Y/>%($Q]=1q&Pr721q!nl3O6a+bq"@$c,tQGI2 + VI,c`37*WDsjN<".#1'dQ'#LSV=!ldU&.,RXd-?(2`3GJ8p+;d9@@%ppX'aOEB8NUinmu;J4AkQ'9'8.I7Ao;;H[0E;Wd)-ZPI/O)g + \8YY&uFu\;:ck"f$j]K$Q<$2[sT@:PKFj_$WVsZW;=?0g]GcM.N]p`/'Kej![KORi?fmAc$ + fU=:U@r[P4Tm%6=Gb88+4g`&@/M1$E0_0SQraZ(Hng@pef2MoG7P_KLBB.[%^33 + Z2319W9#l6&V5^U,.R_EmE)mRDV%rWJslW[ + mK<7/#S0,mFJlDD[g-E[s'IlhRi;Cn3l[0GdkC_X`5m;"?=sF@%/*LHhjN=\De1[`-)W!]( + d?\GC5fqHWuDN7]#1Zb^-a"CAhSBFdBAbRb(bZI;uE4f"8%%Q3QK+:E":TClR)M3$NV19Et + "=)<66AJupo;DrASJ@9uL3].@;-Mc4Am?*(HB]%?-j@rq2&eMFYfgZWLX;jCag4u)AS[7(r + U+u2D[Vr9N($PD8fV_E[$@a3?tK[Nq)6pQAPt4BFCf$DRMpRR21rrF1IGh + 9MnAY5GFss`KRTV:l4]d*_,X`"+*Z\BqA^ILENZ*,IhoW`A*Z)VHT_&h=O_SG8B+;qWOIdU + NCsIdX"tfl('c2FT.u`AhO!/G`RAk.Cq[,Xit@c3Qj30im"fsib`1Q[ + 3b03G%BUj6c_%3.l1Bgl^D,t*QKC"jS8C[H8H[@-mAA4od^NAaU2Z + QC?>I2`f=JT+*VpcMO)l6F6^TAGHS"1ITPi`J$J]rfA$1pGOKt[_>&KpD"@8i1[Gt/^p-qWsJmYc + SeGY45c#!FD4`nYuRhY;2f;@)FX$p9/f;W_c4ipdl'mR(pi)0A$[<88*]ZgUonm.&4erM_; + Ypl5Tp<_]1F!/2KXU1daqo6X2N:Cc=QD?U6+K4PJl&>CjHiA#C_r]dU(=TOET]97[3p6l=F + .3)'I,CQ&@3KlYBURa%;4cRf6_RgA"Rqk'M6L0g34HeD@1Z;N2gW!sjAWkdIS%`)a7NECr)]PD'?-a + ?]aJ@1980liZ^gj<\;;c-4,OZ?M\:=TSXLG+fcnFUh];gW*[GHQZ]cG49S@p^L + .$l?%Kbtj9@!%/L$Dj-I2(T0V<"6/*%^#)g9AI6Sm(Z3k^e>0Q9H:oC.h,)/$:C_CP4b;`[YpcZPeV@2sBMplPOZ@97["/H$=,O9@h0sj?j[RAf<[p + 2]CjDp7@F17@R,0>83jMO^h>g,I)2?A.lWtVa3Tu5($WHf6dqO%QBe>]B\-mCmO#e2-!fSd + )hMpb'?F5*LR8gLXZ,g5&96f3QQ;1sHZZ-Wp'gpG6j)[@co!t&-jeN`A=2\eDO. + $!)O+*:`TU6/H\g4"AhY_Y[eA8CuQu;=\9CLOXUsW,+&T0B*FZAKW#>UFSH;SWrc2j]8:!f + ]$*78fXn`@Q+O^ES%]Vb98b%1$Nn3M,Jjf+:Qs/(+spM846aOcu' + &lE!5YJe!1H'!,[Cf&VW2A"O$g1!*>_[7&2pHP+aA@5qN`.0,"@@14X`(,`ZXMg40)Z33c6IWD#fJcBk`9eP"K* + "5UHV_ff8[L3S/Xn)(K>BEU%NM.>!8H"f[mST]]-9M["pm@;.<7hF`C\1H_rGnu/0@tb)l=KlB?aE*#botK7."5E[8MB(Y@<9-lFK@%!fLC(B269Q8;4sTBM,Qt$"% + &fsf-Fh2fOul!n)ahcp::V6U^%+u@=AB!T59>C?:OU3eY]5@&*!^/!#7Xt`b>U"2]>nd!=f + p=`D>.Ccpg9$V?_.%#X&qsR"BjPNa:gL1EDB+W4c84CL;7C+#Yd.`mU/]IL>io$85TMt;Na + Lt=N?r"dJcIc8_Wh>&5Nm@u,UqH7,Aa1QI<.p&ahX1P% + Z^JA'&in1'5dR$;<17/g?cR9g@?*6UDDu'S3+It/A"G^uNS-i+rQK?UfO^^XH(3$^E!8c[i + oKkhKZuXW#X8J?q?4Yo?sedC=;OC95hBMAtD/2-)S?Fun_FM; + .tS`J^ih'kPHDB%VcCZb.4lEEEs@/W4Yr;Y=lmpq0YlVSXPL.NhMMg7s>3C^`TaD#_cfKKq+0We]\43OG6GkP1q>6ODQC=.#/hk#>JI\/GmuaRaM[DQgSnXWIa4qrEL%> + VZSm8rLlbRS^ra-'('EKN+)uJ@hmBt(%c&@M;-VV;qDLg69rLV<,/V(7tt,;Vf"-Om>Dk_F + 9Y9/6HpNF`Y@%+RTZ5=B%=HU[\mkhW+q?lC7_G?XF&onS@/>85sq`^,8D)E$:E6&):3)Or' + 4TFTBYm=#4P68n5TVt8_D\1)LH=Z`X;t:e/IG2Mdpab6ZUFs(rXh29qDs+@!4_=:l7]0B#[ + _`AbKlIa:L7Na&(dJsEQ5pX(qn!TscTB6P]A:aIHbWF>Yt73"X_AKTT + QTCiH*An9dKT;LDMi>0":qZ:2shl*gr;kp4[EZ74Vcd92!Y=*]3IpW(;UJ8;"EjKV]1E(3LeE<@A;(eHUh*Lj(gU-D@%C%%*?!5G2M9P + V>,- + i?Zl!-p>R!@\Mc0?=a:b8:q."EP^rG(f=WGFGC>\*tLGJ7T"+OCsj#AoClF2Y?Q.a>]n@uF + kYe#m0<&u61B?+g`F]*$\Vmsh1k]:"`4^pD]@3h)1^XFK`FVYOf&TKNeOTrR_ZIEQ')MS9+ + *VAC^et_)7U8M\Mn9Ul7V'lc;F2b=`Q0%GF4`YD2tG*p$rlH?i;OH!ck + _DjP`tp[5M!DT5cPlT%qFZ3JrqLB('MGE"V=?Nf"9mnLo/VYiO":HR0)RbS/:\WB+Amh'1#SG4-f!0Y\LFHY5X7qnP4$)>c@f2s1Z8meG=Z + mp*V4U$Fkp]5M7:e0BSpm6bWe#.$9uni>WciR!(5l)=t2XO&%;b7dQ!XE\>^nD;;#epg<-\ + V??Xu.9Wa!"^b:@UKYU+]-u9$R&q*hoQR]"ga5[F-dH^m]6!j&)`$P!_I7%@\pk/,`'7Ro0 + d:`)`2&hQ?j3dZ*L.*>M8S$7p7&Es7Q:kf;q1Qb!1R_$Mds8moFZZ0jB@H\\#),F=;/"I*7 + 4+9mVGh3YqLqD8;lWi4eU#2H[9Ke8CXr:cL;dZ.=_uZ97AeUN[TdV"qh<&OF1PXpf6bH=3I + ?b)n'O+b66Qed6JjJ``#\:KHG)&Z4G1["n&d_4cFh&m2!3(D1X7!CI>F;1V#pL!5+>EMeSB + :grFWrQZ.a$fgF^FsF/ElP"%q)F/PEu\EE.C/G9pbB>4Yf8p#ebndp_oOrH;7RS_IiP>.sX + MKt$)\q/d)do9%AbbrT.%pNOU8o6Gb)%m4A;m>AhjY_\2;IP8b,q#[`R4:q_4:g=kB_9dLl + !j@\(/HDE%&g"nDbX3*C0ors3Gs5kn.!Mu;CG9n%&R.594YJh/(qaJ8_JDNf#7Em(gba`&LcdeN`"/q=pmo`Vf_3! + k448+O7es,r*$iTq3DR#RV/"<,]5V/A@d-I%PkOoOE0[mM@<.ZX3H'#+='9rpQreQkGaULY + RsLM>\Z'tFpXus'S%&40J=Lj(>8<\@9M[jXoFYjSZ;Y'0?'&9?ZCt_F_s^M,Hm((:C,XD4?"1\F$DD`(bpJ@DlitF7p + Gs@E:kEI'A\1'*.)r!]uH_>[JAgn_AU;aRWT..E_5:^G3t(>i.QeoA'@3d.CT^>JqW:(.ha + SYhi5`AOmgDQG.MX@@9Q0U!&oF#5PoN'Z*W(\bJ?0jOfosY[C?CAH\nZlEY,2W9O.pMI,No + "G1)Qk!XN,RK!,)Bc>kHZgsTX43DKp7i-/tAg^T8@75)E!rS^pm17%K$j6gaFH?D$5SsXc* + A\j9DPb1\[<1_)\b],Mh*3)bBRAW_a*/&rS27#<2OE(JB?T(D02\1b`\\uTk=f6iZ^rcfH- + 6sT9N=Fp^+%XsU_GN<:IC7'%bg]GCa)b"#q1J[`NYI3cOo+u=HsM.@r*,oL`HuQdQ!kep+d + 02rO<%Sr`gTC;+t!lO6?fcj!82pd@N0aR+3,8A8R=R^,SV"jW*U$!lMTP:s`L9@6bH+uEoS,&4>j7d=1([2HV[$Va3bJlS + Jtf[bAXHTen;lM#eGL`0`9Z[iVPSR] + JN'fQOI+rBUrpr]T/VJmqXW_Suih@d`@6]@VN$?5pk.mj7).f:MV@S(2iR@4718/APBorlT + &W^1E3'fs:B$m>"(G=[R^X%ACQc!QE-[pF5$[!A4k[kb<@#(uu['UiT^T97$&;0it2X.2)Rf%TN%Cb:XP[W*oF"=Vn8@$^t-S.V4=Gj$#3C + ?M3jl\_bYf#>j-_d06qNO.bqce8=`BH78LoHo:=g)i^rSJ9,L`BCpk28S01!phJNKUT>aCS + IV-OdI(I*!4JFAulNAgL/eLTS-:%&LS#ue1m/mIScR1isI/MVeC%+TJPh9Gia=X1W[".)28 + c8-,B+K2bt2D.S0SU5)\+K54n'n*M_FkOiNKlHW3[gIPMB/aER104/N)aQg>`*blOLYQAX) + H]1Zk?4fGbHX-QYX<_Ea2*p*%0R$/AY&cprJ!kka9snS14\;,#0$fOi^+r,;V#q\RJod,^/O\fF[ + Zu$>eeb#_.lD!d[B=Yc]&-b*,iYu.!J)p`g5=d:_]AAn3KIG2(O1taA/pZ7Fr97LEQp\d]V + `j_80Oi#'hTJ?qPKiiaTiX;+("GoFNSm`$7Cc_T!5dC6RBJV@CW#(1_4u1jM+%94)#-dj+7 + "J+Pf=NGT'g#.qYEr!/F*g[qg.939\:=n7L*V&*,7P5/#;crP(-9]].-FbDs + %=$B*OH5#1.8n&9A(QG6a4UVU&csW">$,(< + q$Kt`ab3hGU$So&(#D/+a+DI0Gc?Ua[R`+%?7aOgSOY@6/DD_tn,Y&VCotLDZ/3(t"B^& + X+)!5nD=qg;ZmnDURoeN1/$t&L0@!+hedZ,o)N;\O1[%iB@h@I\1M08a_= + +C&VbNm,];EHS4BZR81^Q-`/mPYQq>5h32Wq:`jeZ0[^U(ghgluFJJoEB-3d>Z-IH!U!R)@ + 1nBN3]mHLt)G.+4/*&sFbBQRS)e.FOh<"ubPF:Ea]\J]Xc1AGg#e,6-[cp`ZXS + J\i&X5pl-7Wkiup@)i#[Lr'0nqF$W`kZ5R?P5>?aH`skJ.9J\`55YSKr'AUkX@aMZ0Mh4S- + kUKXXd6s^#q@_M0u?W`Xemd(+SNu+.dkfbZF4q^-?eNde#gVskVh58nn8:sBPl<]XkGffhD + q^t3A'D>m'S,d](%,che()+CrR$[Y0?I[2puXc!L=C^HRJ=I0ak/gc6Xm"iA'qn3RaT]';I + TeV`L$B3miF#/ZCM0MB8sB1J`=/Dh,UcGT6`TU,$^Ik2mQ?/dJ2P'JQg,n?uSUb6u;;SgiP + *6&_?_7"bblY(c`(UHl!b37oD*lHP4XEGls&0`BH:.'p*D&sG('9^L*q&5f7%WfJZj?L@@& + mNK<6dmRPXjFe$4?>fC@meE#ar0.]l3@Qk@D[;+(F#JcgnOCVHmJKZ:-j-g)8'DOE6.QtuG + 7H3a+3q7lC.1->)F00S68Y<,'Ve9"Vb32S9%:%;#QHkA-JGf7dV5N`m4B690L[`FaQ#T'+S + SCsbZ[<@&th].iBS$\aK*:skX:m'KTbQ;MQLNskt02B/t.0Cb^APl`$T/G`a^]"qRRpE"d9 + T0ncqJQZVlnZ6=_Z@L.qUp?VCh]dd[gRPW8^O6C[/]!/\etaj=@5&mauBP].P*&0>(u?sDr + 1n?3=WL;*g&9G0h?'`fKf8LW`md9)XcM.Z#V+kRcCV_El&0^3p^&d:SFAQ#q:>1Kf\'sh+E + [p,-0>Jb#NneFgC<@\G4=Ac/h;6h(UAfMsD"'!l'Tk4t2n7s\KqCEpF$n+[;jqn`r)"R)VD + n%n3`7L+#1_TgB0?Z%`DY#DX(='B!eYFD^WE]'GoouoYL.[lVK-@A++n5uIE[[&Y_Nj[G7a + W>K.nk;j;I1kS?*Q(T0f0XX3#+/1TCG1Tl3A#]I92Lg(82oZp#0#V+<4f\#GKil6-0n.*Eh + U:@PjKN<[hM/VeVNuC=]S((:.dW[c:CB6Lb-r'=g3NG;F/d*3Xr5@B:FL49ft_=N'&5TeZs + %ZT]&`'sP96&AFBMn6P9S5@s1g&C)3^Sf=]t@"UFHd,.BsZXOQ$3A95iG.DWg6Yk$9Z8TSS + %"/Ek]a7!O7Bi7lXHb"T^E[97,GVEa-BcMl6l+"K,b\>sN4)@K6PO8E'WMl^i?8pYLMbXhD + F)D[i3=%`gM(CfrGK/cmm@UE-[D&JH.S[7(Sc:XVg=+=EjI3)@M8]8.j=#-7/&cao0.A,@L + T_oDpKu:ks*,aYOR81/kq3;o3`;`9q\9BB-T\c."fAfP+8b8F>>,D<*\N@lA@.'2A- + i'6,?"+XU*$I+W=3J]noqk;\h<37O30G+_]sSUn;/LtIcF(mBeYQ\h)u-F^.UpTkblN>ct* + djmL!&Jd2HNYE=MI,fGaJ2S-N4:sI:/WZRA+^Fk^WY^`!3n!Bl/( + L`L8Bpa@RGEY$t%>iaF]\";31b3V7d>F/E)R0ljc[tR`6JSq;S?D9dY4@7`WeC^X3TsX,GZ + 5E"@U$#CbDl$]dqsgj`"E([2iQ-tH"2coVB#\p&X[oB\H*LELN3P;OP,,N)2";ZLR=KfR*q + V2npGUIi[_Z,lbs4'OgagjeI79%=J7"t(0oi>BQ + 'O@p[>qZ+:E$,lu8;>W+TN-#fe8EJU`Y5$g%q2s\J3B@sA+?1*lR + JWmcGGgmVVqQX!)M5U]Tj2b&F)iLHXhusR^)R/M$+a2TiL1D;JFKSL*45)Qo^Nq\jqgnIXd + AJso`0@/j\TBl08J).#&(qt'k()ZM]-=R00bP!UG83kY/p-j)?XK73G`4tOmP#IfWudr7:b + J!AMGI#p/i0?>SkRe>rH5WX(7ia-5726'52_oGB$][`Y@$h%U^9;Jf17f?+WJ#S:Y + UN0GYfm%sZ;4X]gpV^c?,oPY"FT3R!`jZI]2j5K3F`"Cn"5hf2f]YLj5j2ifsg_&,8(=)&eD3pN(F?OL0>:LEgQ7S13?l5s[ + .o23"e@"'6&$$qX/`#-kSg\,7EqlJcT&`Kg6"<,CtY/UQ?iQG(GiN91VUXKYCQ( + _pi8\I-4eNLWe*7^0H++eShK%.e.jb]Dt4G,XBm[WbbPY,8\8X!S`-htuZc:hKT^f0RdBXQs!Un>Y)q9*I5s,aLOEoL-S33F6K;^i5D9@s`b1YCi3:7bC"ru + 5.ShT-Hh[rF#3I)Ytl9*>^hi^p0YJtup#gj$`pp'HN[,t@[!,'=b.o0/:eY71jU:"@&ojV1 + ;25?FT!kU,Rdr'!\-kZOn)IY#Err;t#AfRLR$^ZYV'2$&(_) + OP"Y=+VBQ!ifR?M?83=W7cTW`oR9(LO\"Pr=Yu^I.@!NeP1ark@540Dk*&rThJkQH:?Rhoo + _sD-Thss;=u@@`8Yo>LPhL7DTi%dglBPP\?EmY8;d\)/omX^TYuonL>;P]pBmj1B2V5p]mr + <\ZhR?RWh;`d*?JX5bAc:)/F5Yi`cs* + nX0I.t?X;9mK?M)TH/2f92mpWnD=p)0DlZU$c"$QOX<1XEDt3Z%IfH%toD\eU.MM1N/'j^G + paiI[-M0cE`2:IJC;hNa[Cr1-7!3\( + bO]oh%rf%DVGqDTjZOgV+cVB8>R;d:e;r)Q;,JsbaqI",c%;/8u.EK9G.I7Z:T^d,ehXL%4 + oOS9X>/R1//3$cd@(Ih9/FWX + .P1mKF\;REhb9N*du2bm;Yofl8P2&/VZ<2`4gYg;Vlda%l;;s6BJMN^V_b[W=PM + 3!OQO1,iDl_9f=keA5-*'6t;D^BgZQ)jW'.V87b@qC_cZkASr:7nT/Xc[h4137V3oTA4>Wc + >%bfdPYu7c2j(Xs)u$\7EaL@8cG5tu?2j7Rdumkj6S4B%5WG=)kZG/Dk6J\^FfYpm-qRhpXNS/L#1e77[N299017I5)6(= + DNV=5*miX_M$;8O-&912/X?C9i[J.?]jn,qG-(q&hd=UnBe[f_/XpZu#V?bflU^+VfP]3$I + MV4W$TYKgBeP%udS,^Akuf4;S7>E4e+?`,Rn@(WLI-G9=nH4I7GbB\""?`Vn\\_E>cG$S2KbBIeB7/gXJtPVFeAWl[BJSc\LBNq=@k3p + \oZWoD_$-B`fi_3^2@#lW+7`nhN[.?dRM@/=JpuGbUnjMGmLCdS!FNU1t]+;)$PSb?GSk"B,U,g,K5.(j@Ugh*ec<'37 + X@*@!O?2\b=Yfcp\BmG.CqoA%4+.iX"-FoXukaVeSFB8mM#2+G+/)e3u.NY08k7l]`RA$gG + *mV)d=u-g + r)u]LFtVl32],Ggi!`P;1/8t935\#B%6-1%i579\lLa?bZncfl$%%*be@M5b[/D37pH^Nb^ + ;l(u1M1aT<&*Y>1"?""i(SZPC!kC#7oJX(r.m%I.-WBn^:aa3(m_JQ=M:u/#[Y$\KWe'V_M + =^8*b_s8Y9[13EE1r95/N%VA.%<;`\VeBsN\d80Vdk\ZEkX)"gq-JWqp`eHF) + NL`+(N`Ydte6n"U%1Le^*LGb_QAD5O:bpfG5er#Jk&iEq;Tli!,giA.Tnf'BRE\E.=@+XbZ + 0G!oma1]"GkNopJ0c7TiO]PHFd%jl/XDP*Gb?"aJ[3A-T,S=e + #;;!q-,rB8A^rrQsJ$@7P,Z`Z(Ftc:D83(1Le + NCDfJeOB)E]>&\fQ(V4si(2Ub2-kfINqDDRJ*.(M<&?]=E-'p#!CuL@JS.-".=S"fJX(O]\ + Y$KhLF8*K'(H3qcf:`fe1Bf(u=M#Ac*t^f%258SUU1psL=%pOuG;TL)aN#[mPc?4V`&^,O4 + ZT0\$V:9!rr.'51YX`h8*ia\SUP5]7ajM_t6'f`cok:AE\6)3RXhQcFL&=l/$j17T2pKWJi + U*(p4(F-#=3#d$))H!LX@"f7;/u2H)]3\A( + \g>c&q6Ka,?n:G!s.!2=g*h!3-B/T)Iq4au"dU2&%W/)T+ZCnfC`Q['S?2:FeXkj_X:L0QL + @?F.RIq3ZXQBAA>^7j.+FEF/.-(&ne6?T/:0+g)EGZ@bIZT0nZ*ZegIapR8G:VjI_b/mU'b(84X$6RO#_@U>ZueV[bFL + %S(L&*"TRKChc%;inbnU\<`7dIAOu/pE/H9LQQU@8]c6:-fl>GPqe$j@*2Wj)VS'uh*(IYH4P0UC7MU]>`3R + hDVoY8T@^N**kFLdNHM`72nT%J6;mN'?Y&Da:X019("--&VCdGn']MAeOunINHGS[oD".>, + k3(W&&RNEQu`>MINY1o?Y&P!Vdu@W*gZ[^3a^\<[a/*,5d6;p]qO6`K+-h[N]/mQ&g,if'[ + UAnQe)S53"LI\i$R6I\1DlnXT=L_d%cTj7&AfnVEBG@]Va:iaqo#0/m(?aERK9**;AL&-(R + Dt]Ke]JK\Z$3VK+a?#)W<3"cq"96D$64;^=44;6"5Tt=9?inZ-e-,l+/rD&103f37@%SbQU + =d.)M2t_PAZuZn'#3/AfmgMSo],'>'NL4LW^25(Il7_::3$%Q>P<#2!XF#Yoqu+$S^AL'UJ + )_5Ek:iZ9mW"`i(i\pC&:Gp+Y]TaGralWkX\RAIf.&Gaq>gC0]_3"*l:_F"j?ZOngC-G@=+ + _W4J1nn5S8QO>ih\Vg&FP&2abDD]P-3T+m.Z$J\!CS9[0T%\B0\e:\skb1n$TkU;MidW!f) + Obu'U"QJ4RB7+,.tn7""/p,@U[5hpBZ$?SN0&)aa>b]7,Hb3ZYnSNM.6t&+:WF/ha!>]G1i + p]*FN7@'a't:MnW#VUO1o-[78$:TG]pm,FL9tH+"2(3==Q\&Ja7Op] + [?.T3*&hCSM'V.s).MldIkk-aDl%[4+*)mKn,\>BA7/mK\lliJ#6NP^'da^g^-FAT/'#;8b + *SKq/moJ=P)DnruUQca<7ZQTOqb`5(p#&;GT]$I:;H6.BZXZFt'4CB>*eL1]!`Zg#S0s![8 + K`t"$&d9rN!,]EURm2/6V@j,N50:`W"K7^:BZlXUu9i?Z;A,_=fU]A.eqmO#oK]\(98kF3o + PCrn8T^NI@j)]$g[d?j8_)2N([n=`7p;FjP.qq8kFY*`>b13$Q1sg)N^0GZ,:L`J^uR>F#! + H/T,aLr&8AF(i[&@cV+T+21=@HgVOgF/NH3TBP1M:;*B3QCXh'1[#[)h:/^.b1:LUPa87mP + P36cfWc&B)(FP=iAgs=LKcfG)Oo`*8A!72G]UV(fqlGLmM;_1jb]CU!S!-C=8?Nu$a^;T]f + $6oPl=9RPG^Cb1C.]erZo7`A2(BXgW,6f%O#8ttr&PEE7gBnN#?\^JgLKj_qOEe^;pqR8!W + <>p\1tthQ+OUp;UC.l.MnCT-5YMj*bU!Kh8_CL._\o]o&RcHOH#ufS@5;"b12QC-NPHo + KM)/UPaESFQfOg]W.6"HY!-['3JZdAX!d#,k\NP3##(9=VW.`r,a&jWW+W5h$N7e0RH22+, + k^7#=V?.G=lb<15@CRh#(\:.'#lJIi%DC.!4_;ge;)=C23P0#PLM[:DFS>(&d.k8ntQSsro + T_b:guaO"h$aUb6#la@LhNpMnAN(UdR--8R/8QA")NPB@I@&!p.84Df'Hh(c4K1G\&K1C>7Z&4lpL:=kYc#7sQ0$K?dl4 + 1dRac_InV\N(h + Wu]*th+?a@K4M>GhX>;'dHd"jn9OW`?X?i]>0AboqAW0Q5%r_TbomX+al8KHjRS@*(FB<[9 + %n229gRUkj/)bP^8l2qH\6;nOgh"L\FiW9k + aP<'&PpM4446>%=OgaA/!Ah^4g*lf"*Qh]BK3YYb73pcqS5ZerHISa]#> + M6jn8I%/%)DEDRDO+j$5(7nEZmgaQLnLLe[,KOVS'I<6%dDpKO_[a8;*@$"CPjFQl00V!0] + ZfKa8WQE-)<__*+(#?<#T@]u7]Lkhqa5i2-k>[L;$5%,fN3dO^crI0DEJW!9O;anfn.]mFBh[Oq`= + [/.,K#KqAoi8,s5/a8&Lqam5m:)ci4gJ)#>E67=^8bmZ`^u[,oBON6jB#MJR0.'0[)[V,J@c3i)O/mARSag52_.XijMJ?[S,]$9] + Xc!jQ#8pg]lD^4GV0P05I%6bpSVRD3bH&YK]&cE*\.D1"@+NaQN/%gCL?:: + H[PXmmUBWQN@sR:9XuQZMV^L`gVu6#+Tr=n:4e_daKS_75&8Qrd'')4(ZV2KnbVKK@j5P0g + :UM9VPl0[HM!1.]kba7$[Ys@dB'-^@in/V1S!q;oa@0T6LB)5t!)nJMJ[H&#KX`0DI?N8E: + c`i'&a:[5tX*(!ArSm$;H-rNf<7VG3I]dfSp$`Ka + (Fk1rn;?f!,kN1NeG!,DJoDcEbihHRBk@H"4:01;8.Y^4#&2:'?)2LjVd"UN4Xg&eQq;L9@aY]6=_eGR`[[%E>aYNA+9CBM+l)cP86hY + .,lV:]u;1)\s4Tc@TuN0b[qTb<'3,"RoI4>%S<#`iE_&XDHq]4: + W$in7%'bdBCo8`ctL:kX+71&++5dd1eg:Ja"5Q+43eb#&HZWRLM.9>=;k=e];i("0uejIVq + "ajHbS\VZlVnic&eXeoqC5or't6WU0""E2[fFFf`5.mF%Tof#u#:?#C7ND[k`PD^nW9(fo6 + ?F-_G.f1US]F"c!?+bOtK!7)*m\_@?K?N\mYb=t-=#39_=jmipFoB/=/pHS9T5gU9B_Ylsd + bN@N504ses<3b)1OBi(s97Zaq7[RB_co5O->33@tKo9'`?=$WL?QQg`eW+&BR^7!oTamWa8 + Gui+-(njm*SMROlo@@R(L+M3WSmk[$[qA`m(De\9j`(a,oM + 4^$o,/kG.kPg)o!f;TKj[H(01oYlF9W":f@Ar#s&nh!B'OkNG@u*nGB`e:Gk[DboH\Z^D'X + -YH3]a7Bb[,>VH$!23:hWNRp]Y-a=CtLeKLNObeR=OI?nT_@+g)J]#;u.?D"8mS%l1J=l/( + 6Y2O0nrqA<`hHtBcT3HN3PdpHnE./AD'hI3-IH_01irtAe?8>jfk=(B5RE;",5@m,0EF3.L + %C?ig?*qFbq:f\6G/\U0osg0rKa!Q/K'.)t,khKk7V6L"op%]"NFm'(PM/6dHRH,r2m`BO[ + /3`]q.@ffMf%h&PL=6;+J<\6J5_&,X$50JKX`,39p,riVBkZ`)GqUDGZu=lf`hW8)=.$be+ + i=Theh3-+#HHq!:*%*rc-(i>V9\rDm:8U[J^b*HZ"kCrs<02aYEKD`HF$".=!:7ToM#gTb3 + mR875qZ(#NmhRS4Im6l-"3V;JbZgOh2+MBIVo8KG"8rau(eJ96N:8p]A8GpU@1#&*F+Fo*-2*273s4`*"0:A'd_jDQIT_;C;sjB[$jbG!1IEb8 + Q:[f1E[T5<^pg,>OfTr3OL=ZX&3)2GD&lrTZ6cIH^>=`.7:[:])1^JaQ#Ho2-D43((d;]N0 + R2(?jCoMRh/LK/qh,IgX&Ib/YMh=,Xd?10.rbN&2?I3--f\(0p?Hi@?U_l9#n`o?MJHnGp2 + ^TXW"BY5u+Z?3ZD$0pOS_2rIR#Jq%2,mhGMX=Xgt&Bm!@_iX4J&&]%g71mJ#/56es&PQ1>` + K=tB(WI&GAJrLMXDJos&^5AYkU["PB(''b& + [$AkACZ[-!!5Bs_KY6>WAClT/sS<6?h6SFl))FX*epUrJu`/,#2R8Rbql5Z0Fq"%XmEIp(? + fX_h:o(!Ul,f8siY]"lG+M'Kek8m,5DkEajasfPpt_3fh^^-Hb]5_qRoFV]n1^bl^`BfjdC!lQf]0GAXV\KY&0Zk):!AqkF + (adTBI1'Kq[_!/rcXj$VjTljQGobRE5j5?GO[>4dKL*_Q5f2g=r.#BFhsF".>,O*]Le7)gW"^_BlXoI-Yu]Ifu(& + CV`qb8b0m/pNA3=iqc+^L"9XfPRVdPMopp#,fCg5GAI.OD!d0['[NbTY0M,IVAMH0oU6""6 + h5)\I]X=hZ)h)M+kF0,G+DGi8ID&&=-8!2':ds3dFO,Z#gNVL5Sa'&cs%W&YI@'@$J63UofeWi3U/Ht(N)1tb'%R + J`1#N5e/KM[[A6gOW.?4,%2kmP4MY5Q39rPU-Z'8W7nV8#P3<^9iVDjUAW'a%b%i\,A;^Jh + 0AQ8N"<+LfJ&o"r[*@uo7:8GKmRIsMDKi+jM9W?@X:CNfC]NGFW9ohcK%cmQp*K#:g:spjC + PcUj1%nXi3d@D2q>d&K,*+6rL__%_%I#B^c.Z/O5S-9>9':'+$$)cT6"Jj%FU0L3E6*)?J4 + Vjd#:5W1R`nAC7fG>37Gtl-?kh-(J09T:_c8cIs8+fih097,U*YB(JJ4=M3DoW>]@UiW>q; + K6W("Y!%9]Z-W]`o%V(/=kaPp-9AtY:G5Y,/]-$2h/OMKs^+c35'Bm/qH?sF.lfj2:SA`D- + 7#r?QE`Ejg1H**JZ-KaVBjh&55E2?`#d0ab]"&;b%O!M3#@Q7%eoK)?$i=W&>S=F8QNUl)Xd/Is=j'rd/nNhOQ + h(DibZTsKaVjr;BDe++VmQ;mV6 + f!DFj]QupDW)IM5,*'IeY@Y0W/12Hl]KfNQnsG8a8_%aqg;?AN*\Y2+FRq>-+N3C9Kjb2@c + BS6VAjK1n-(%]2TsmE!FTY2YB+=JW+b2t$)nDXuN#p@"\1n*^>IBB + &ES8eM-\)rW1I:PT*l*3t.6'7a%&_8I5[/CWoKhTn%.C]GQ4F#k3 + r_0U.3K'_VqPbq"D>CtL$%qKQq#Tg@0`WCW`A_A*'?"cnQ%6%BUk[$U!PMJKj`n";US:,_4 + Z>bq_&bKq+We268.V[M9(/LnJ$0#!Cf<(lX#&8E8FSDN\!#B)a(&=3uK)%iYarl]bFDt=<3 + p)_L-f$F9).`@QERN0>aGt"km"%<5VVGX*d>QP,+`Q0riCc;H<2hQM\"R&TLmdEt5n'/AsY + XB+"$5DGA%<,,G0YnTF>&?RD'>Rin_8mO7#5q5eh[Ys6nIfCf*?)2b)Y('O0sqs?\Jqfe)s + p]5YO;eb@a.au_@WZUm^W;W*qs<-\`%E9ObjJs.N)YI#Y3;VblUXNke$3h(!"10lc;moAm@ + j3;(Wh,le'WQ6?7]>^+CsY7_Iqp*uV1GS*!,gD`[-fW>lO6,P!u1%:H4c#f5jOLI'r-dG4e + S^-f(*QTY+\#f"o+pg[(G>nP0*jV4-@(u[J)EThmQO>8[*35\XkEp + P;ef(MiERJY@.:Q-$p\'g7Gpq[k8&]/33"&JDr<22\0_tc#C3j30]Eco@gOq6T%`7,BN + )+P^uYR#uJ#=;"YDt2&/u2(diCuC"*G/bT;!"E`B>E$P.P&O+-j\j"NsZ"$p0*JGT(`1lBV + N2m8s6aM.M'Au1UM+G("F'Mt[q\u0J\*"1c?AN^RhL)q'&8^RnN2#fFtGu.K[=S7cJ2'4`a + ,h?F&c*Hp@')9?n8g8$!8lVa8LE+6F(HGoWQ?9W5$M&RD^mB/]?A7[2nqoG!L0t+W9lC@ST + csjZ4$jS`*+;GF!T"h_`E2R9#7ck$;T-@"a]R)W@.k1@2AKYD).Bro@II2SFGDK5MZ6Y:$E + =[sPa\;M\.p-W?F8ig_/^2ScM;CIMQ0#QC.pQ:`_[6)2Cm&qEY)*0lnkUd+g!1:m4:%@1Gg + 9+8kh9e1Z&(N10(^^rUN$Z(6rTI93HB^$8j26lFP6jUt9O2;_0mIY0U<(N35%LE@U3X"U)" + 3rCc8.>fGeuB@)&oNj,Pp?,d%S"0*.<,RS]\DX<0!JPZWghAq0l?e'd:22=A=OY!r/E;'ra + 2\g=V)0*,+ET^J6;lG:H/V(a5*lHtQ`$jAEJWRgY_JlSUQK7tt!F"2Lr/,3:D1Occe_4CX#%4VA(P*lBp$I + 6Q6@V;B'gS.%@F9$0!Dh.])E%lNe9cJ.)1f:X:h10'S/@Z\69TDn8D8eMj7 + 6Wc=W#kb`)nVq_V%d.W#\,]Gn4]*,9s#AO-1'NCMM$WEC6*Bd8gEWLBVK/pOC?=Kcc80acD + Cc>c"*iPbfbc&?PfN3:\3U'fQZcL'srE,O`p\(,ndW5ah&`lHr'Xhd_aWYe#I#anbe-6aG' + ?jeKJ*L#4qfbp4j&U>30RbApZ3[LMH.AFC*U?NhQm9b/-ASNqO;R05206[)`TGE*q'#6T%< + 9+ua.KlG?'>YbiN`f?23/^a!QShk]Q!?3,>,G-])B"<2X5@Pdpf!/+CW_;Tp4Fm's,hGWA9 + X"<\KdjfWN)]9E`JM?H9Do,OL[c\Gk!sRNlramZHJCp6n3;1Nc9IR$<0+(nnh;3s?[V\e:Z"'gXejp.U@0;%6N&%@1-+em(+KbY\"D2.aG6 + G&8CRZQ[:BB48npZ6qJ"q_X9nZaPX6RA"A52)%iNqr1b)>=qu*#TnH#CBOf,IST.C^fe4YrIpl)]OUaS36)2n@4lYuGe51j>C90?Lf-__K=A-[Cq<78hi.$PD8 + B_8:J:&LLCiXTB"J"'rUE,8GcN^+t-PNpKN1m+Uf@'2JS2/92@NkRiB3&n#Xr]2fp1E"0Gi + r>HCA=W='H+i4)Id/=R(4Vc@m):>^mWt^DSdd;?]0JV6PX7BC,_87.b_A'Q^OBk,$`nge@A + eSS:`G.Vknc+=k!JY$)d:X'2iK5n_+ieX]LU0!0O2)`W?Uco"_&8l%I$KPp>"Cdsf_MSj'' + l=I^I_Tr2f_t5Z\-G4XH'Lu-@3uu0HN+bF;LD&?$t,G@8-+miP+U_4&V0pl&If;kRh6n&n# + @u>saBQ$,FQifGG"4GRqBNMn7`,hu3Z74pBkn$/hk$Ri+<@Fe+/Q7>&l;MjLV5C)e3:>@*l + `io\d%@*s)7i])?ad",6ZqJI'i,)f(5L;A^7\8iJ5[oSB-R(89G1gsIJ!t]m5Z+]tjh@8Xq + 0r%JaEgaC_<"DQJ4ZC4)8\rE2e=mmr<3;FmHGp]E<40EuF#jn>(%kF\:KB;cCR)14P0`.g-dSr + /T,/NT%80?3@VEmEIp@fZ^<\)UI(rBT"01;e8IhapWSs92S>99>-`P+3P]fEI3g_!u>;.\O + 3\.fNl9_+82+Y2A<$51/t&E0K!?s3%!0PquU(#Z7*3>9aZOV$AhOo)1C^N'7&l2JHbrr;Ng + 0d#T20se#*+:jg^iX#:24^ejl;pH6MLHHajk``;#lAI[_%Tq2:JrFZF!qH'pJjguK8`Xo[1 + oaD'QV1p'acsMP+C_2SanaA>Gb^l[BF9%uWeddP/1CrV;VIMKV_9M-qNDDf`iCk1%,0/s\C + 7W=X::X4Eld.E33Xi&>u#c[G,Qm)kGRKH+i*Rp`(= + k%uJ9cEV2=e<_ja\`F?(?]J!"PK"s[DpZJsToltafY)`CC[c$J\3c1"W/!&?@pG[O1^QW0k + eb_2<%0+d4prNIPYQ9FY.Rj/W!uDM7CBZ.UO^A>.OF^78'[WS%;3VX.4Ui'DiA1uWnGt-P& + of'@',!#c/k?PNW?r=VOb.'[VQlq0)b*]CCE1?,.9Ish#AMQR+buN]m5k^Y"R7\dM\,'DNb + ]Wrb&!9(.n]BNZRngjjL['=_:imuk6Vh`tQRK,EH2-UFDQ3j*T[)!Y+tgrfi/&JqU"G8D7n#ac<+cf/;B-s\1E4A + 7AQMr'Fb3M&P2['=:QroZ6;^g!!D9%"j\:@5'p*EB#!k\Z&6qK>g^*uu:]ZHT))'s9r>Fnj + VQ^u^&[J\]L@rE&ghTd/;4Q/Cd.4hf%5RB*;DS1r+Hf]l-IK2))+;WS4o9_9p]^hO@!F"+K + 4+9Kp<$_im2JR;DR'XB0@&PkmlUuQ`q$e28iF%>`rk7;^.-l@Cd,k>n7c(g?<[@H(6+pf=d + E'M"r5h-=.MZH6mtu#d(r!W'@>\s%hgYu0+ei%+X[p9;*cC"Ooub.'NOgCA#sHlf"8#\7j1 + bU*X?,q+eb6Zm?E-s#@_f5ND'?+s=B,em + 2r.cLImVJ1,LG8@T,()H^deP(KeBKpIe+ZS^ELK>4_FoOXk.fL?qXR$f'#DCN:Qp)6G#8sE + jaU['2B@]c_,aR'?[29mUR0]C^/^D.fd4LR>\=GsESU:_5>K[OEq(>j4'h-"8[H.CM3'J9[/sJe-lOcrB0c/o1>."/5Y + iA,1IMk-Ko?s`!kFj/[WnuZ!Z2TTQ(kX/P1&'":MMIUWeiX:lsZ*\L2OjH6ZL0rJdCfc,CQ + no;@nu31.c>\:%Ah'ihG5%P^Y>F-Uda''Om;*MNLpi7gEK7#]-K`9&;RndD9n447$OGB, + 94=^P4i6Uq1-8U./o1g4sRV',fG_dq];s5GsD!Y*8hEH/W5k#%O0+Ui'[kio5n_Aq*QOfN" + \GhA735e0J4W4[#tuD&&Js0m[=P1''AR0eL8B[n&sP]i*bqE[Yd'dKN=QBP3m;+bP&.l_S[@\!eQ@:NQcNMG" + kQ\I2=VNL#0KOAg0iN,"-!&gLX(4nn.Z6_drV%]Ri6p%l&c^EjSB+dORa['hkL![N6K*&5J + Z%t.#&B_);ISe;ZWAd;Z#ocd0pJ#_Y>7Wcc?%pd`9*,f7*t8jKd30mOGfr`2'n``V(;i+H> + eXIS9pl+oANADu4QD$^,#AK8*f>>KVS(TO@qrDPM8KH6EW3$QRWKZeYr_&`3G3"s?I(#k$' + =NJBiF,k:U)-/BDO6Ze/I2uq?/ds#W3X?P\X+ZK"S%7CbTiI>M&^'7I31d\sHI`?$ii"DUm + LRQZhh'qn[Mq'e?O=0#bSOB4NHt(8h9r3?CbiAd:?^)rlTMKStD"W]W3ZJlE;=OZ4P"H5#GNSZb_>L>86%;n#XArBZ&qFq%M892=(/$&,V#e&.PuCot+WLs%U + (>cXCdY.R^qhWXN:(r!],=H>'EDE$#`QZI%md,=HacI8,T+j@&WW<\?Ta\$f93`S[-Z@QUl + lZXff90tG-N`jPHS`Ahr.;GO;XFqA#GiidO4`-ZZT+*S72%_Gc`$TF,>/QD6?"?W!&=iu7\ + 8[D>q^eS=inQI[6X;:mb^#O2k+(%mF+/,SH.P.@SG/Rl[Mui!$B[#f'6oVe+-KU^(#J5/oa + W-c*Ou+ml@c6X#s^6sK\1Gq6@W:)nq']\.7SPN.:d7(U/[^V!*X:prA(LJ=ItE0>]3d3.MY + -lhVf%-3WBt14iESST0Db9(W1K=J']:=`hkm4)".M=6Ma1Ckt[;:Nf9O0(ED,cV2KiG*'&b + `,b(=V98!\%'J+2m^RbLlh0%E84h.>rimumV#nM$qGhA5?]S!3\qt<"&fI%]7G[gEueT0D^ + i48X5qFj*coR<.UqO^jTI=JNqIb3?OT2W]Gb84Z72j)U=:O7,rm&1X3U_mQ2i,rCS8-QMl% + =NH(MD]^`p%uQkcOFEH6/s"P!?P);4)ei(E;L],q&Y[`40_YZ:j"'lG9q:ALgjJuo0AOk\2 + eS7X:+I3r").%8j<-F]OO^LElY%e,6sW14KG>D;p72A[2u*g$R'DRfkdnad&SD=L*I$g1.s + Ct=tin*MBQ?5-rE-[1a,5DTj$4SZ]-s)pVNEpTt#(Fa:\_kD2%O&^q8[7!p2LQk-QVR,$Y^ + I4>]E!2+(V,6Yn\KHoOKP3=[;I57*,m92IAoaXiW$/>MhX,P?gGE<]fM'gYhdX=>VP?NeG' + 5Hp`k!FH0GhnS,[$;u#<.0Cj49Y_c],k;2:$;lPp;NHE.O(,VT_Ge'TZYYX/,g4Zd)h+J + Ugl,Ii+HM::+eRM;m0((j+QL6tP`>XZV+6kRL["MbQR?u-"Z0#`2H0oRh']FH[&_8XoQ8*k + ^qr.%[B]PnNfd9VK;@1Yk+D>C'EO7KYS/>;tqap@KaIp`,JP7Z:,f(@1\# + \T\?HBW_8pEF"7>D`:?_IC:#]&"KQK\.tEU63-;3rCrL(5[d-.4K0[Ji=i2D25kK]O4JYS@ + ^.:k`5s"YS<'@5(;nktQ#;PO68T;[SAqlggL2:OX3t"NN?E2HZ7_#6\-rh#Y76Q$:1I+W,G + Y:\f28->T"Frj01iu5mN6&ieKWkrA=DA"]H&&1t8$IP!S>"!H_*t`-9PhaCI)o!?a\85I9W + ZE"4JiNYqkI+Q+4Q%N3bP4FBWm$2VPn?d?uUe[]Z]*D/a-aK\0@3C>RBBF,Z7)I$E=8MX!F + 4".b_&*qG"9=%5qn&8W%Dd@?E8e[#8%F2S0ZYB^gqbGHl7^[f"&lTZ"gME_O.6:@.E_*6aX + g`l`UrWOVE).!Nh(5R0RI9"Ar]<#Z2ZXig"6b6kF>!*r5J#"HC/`mCDP=^AmCM$2VCKZ(BR + [(43o8g9:IOJ544e<5ND@BadS0Tui%:od>.Xb + u>GM[6SG.qt=m[5P?*#?n^Ie%RPM]sdOo9KU$`:rgW4(/nKs;eXQUF.ph<:X&JL05,Tk%.u + `qp;TMpQO/g@\?J9?Y!L]O0ZZE(e0 + SbeZeMRgsD%G0.2-%>9=00Hmb_SO(8IZ;`NX9,0l&A? + uGmZqF9Q94^[eraX]!HH18V*PePR_beAbRtR9?)L=fn + /r]pr6MSKD]R[rj^]iq8)'?WZfh#AntsaD_@!E.o,Nfb?j%n/`UTg^qFK,[G(;0qC3.Uf(K + 6"j:DAE<.t"X?\TF#-7t4g]fj=p,fd+77_Sj>f"W9<#;mVdnQ4_h7Qt9&@=K-1u*dP]q;:# + 3nNa;09(LRZ&i'9q2_A*#(mC$SV@=hLC//me8-@*rJJr1[Y4%,f\H[4lSaEP93Ct'"IKtYF + _"VlR"n4AKNt*?osn!Y%`9:QfnEJ]e_FHTh-Jjn?1$NTrSS+WT"5p43]n0R76JD:Eeap;#7 + +4Z*Rn@kR0IV'JaKF;FWuMq?TT20g=lfn4_qJn[6P\>>8[>\B`(:,\VB`SLDj.D4:'P4$&F + ;ecD,4G + qs3)Y?TK@T=53<&43C#<(S)so#3u'e4qugDAq[Z9hS#p7:$g`k?ooV4].r;1"AHTCBFk5?R + ;,OP/[NQpRg1@M>BMUXEO<8^fiNl-iP$FE^-"%i!)1p&d=luqA;Xa6P@?Q\S#F + S]mO6-b'2@>=r$fK6Fgq?4KiEX_EcJ\rQi>%D^-qSu5.,L0^LH&K?76?96]O?6@fjutkNOr + ]/4QW/p?Ep;j*R*1h#64dYAZXLCdDd?;\;*.=q^GhSlO%d0V]-"?S#7(.:h + "ge@t[Y[](mX#-1jr0]'O;Cd"!76%a(mPfrDl/cH%aqX2FIN&tr?0@f*.6_(n"CPaCcmFK5 + gbqLO(iW_\7,W'LO3U4LAk0)1!arFY=G6Xr5"a!n%HPdYjP9oH$n#, + _pnlLA#bgIMfF)^*OXB^d>jHbPY`eW34LtJ@uBVgXBjq$[i:?]f + ui?2gq%]!eBkqfgd\MKIK(6i1\GO&:qf:LU_54fga\NX>ILUPr)m^uR1]c]5I)'6P'?GPWF_;LudCa`V#T#BR<9C->`AC + eiO^!I[R8q_^_#'`^jQJ^p&hT':@LL=kNHfSPJ[-]/>lX>Kc:CqLV&TJm*EA%thcIfU;6O6m`.S#'R0XQZh1_T);Jb6pW2`T.ku*u]f)FuiibZ' + "/_;kgLpOcr:j?s(Q,(spmk54A5;7-bJo=k;j8Sd?h&4HY68_S[VRclqmXa^'K*He@#"o@B + OJuo$>hl9R(*,mj5r4:N[aWFY?[93BZh&a'!NS@N@IIC!UGK5un!X#<+PpJ1pH(r69WAe<_5k_m"hVrU + L7"EZZL$[3mHsD,QQ-d\1MUZ#:!r[%=D-ntifa?5I0k/n(T$l$$*Ms)8X,1--t@Dg]pt^GT + Zu2KO'cW5@h5gE%[%7O$`3,'KrrqL-S<2k?W1UK&n[C-?s^ + q>n%:`PkO!8Eq+SP%l!YeNHj?@&uU\5s,_L:Oa.eeoi&iI[^k0'](MD3BC5l?&6T)_KZMBh + Ji=_<04;_gOt*PsDI4VBT4JA.X5>UC0_;d^"gdom#8]i(5a2T\O,+'3%;d2Em5Idc-`hX8j + EDYP^F$R4K[OYY!qUYSX'u8$`e3?%H[!Jb"drih(OhpaJ+OROGRKFuLe%Yf@>'^cH(VhX#* + B.HFU&/MmhAm[T1nQXX&0D0A^?D7J&m"uaS]u!I':_@^)qVVn6'kXj7WMNCsXWW=pW'I"e[ + ^Lql0ik9EI_Rl>p9Wb`RTj,fr#MY_Qc[6OW`.C0s'3hb40L,R'MDp>:?Lhi5+$Pj#oaT$F6 + NAUb3^hbedAl$f#TG8nT?c/WZPb41I!\(:Bn$Rm3"GO,RAn(;,s?GgjA[ekNmkb!?@H(rY= + ]1\h34hsSlpdm-ZKn%uN#lnt$!e=hME3e>>GGHPN$[Y6InFsjio+QO@@aDR&Abd:*5@`+:" + F9qjP"B348,T5a/!`h>deSR9AM]cLB%)%Dk&ibW:M8/Q"U9ZJU/>URH:90G9;YX%eG=iiV, + OBoC=RX+L:(6g='r@n"bsupW**rfHUXg^CURGaf)(0@j`A"=DV'6Sk4O)8?Z4Qo"pYLj;UH"gSGGCHCCX**`63qkhW`j[n:;Ls8 + )j);KbDYMng#5!dc*M6P6OU8?WSo%ldQ + AT%ST%gHE_4:cT`LYp$+atGcfq\4R]ji/qjHoPR/*.'M2_1r:MG:NQ]F^Yo9R]OAlqesD!4 + kocIkrj:JRlW:X?uH!):g.bm6th`2sSBd6s6:8C>A0+C1-sH^@@8'XO&tdo=RP9N0Hs53/5 + %a@(1iV0b]?_^KOJM)-QS.if^O$G;KPEM[Y[f2^XP?J!eif.+X7d9qPLoMV + TkN.\SZ-AXf"J>)gK*.@DVNb@_/S12*ONngOU=(k1?[+B+//&u6Qrh]6WDVoc%BP3NjT>l: + g_`>s*fR^5qjUr%[3jW3tB`LmlV*oaR_P*;qUh-jt8AC9G7!./P?_][44dHJ]._]YY5iZ61 + Bg\$Iq[Y=F7rNJb'6GBR6PB#W.S=YZ@3nD + V1k?cR&gFaf`o&t.!QafdQ3'[2-W,gljiCJ1HUDpu?OoNu68oM-qB&Saoj(kHB%aUo@9W;o + T5!#t?&rs,FitS(hY5k+J`-<0+,T"PhER.`q-IErC1sRWB6?!(C*Ls[#[B"i_JkGU#+^NN_ + $$9>f_'f9[-cRR?3LK@Us16.dZ4P3X@iFFNod(U>ZLRqPo$V2 + JF;5P5j)UjJ::c.Q2W\n=Q^'?\o*/p"/2:SdY>64P98(B=ZB$>U+60W5VQ*rU5i7sOLeS`X + j]f3t/i#E;mE/kgS$P=R!?VG1< + /iRsaS]aKGdQU2n)'pQaS;f%>/?;=02(%DFSWhKN:eDTo.-2_NPgXG='rRddf3C:]d-^02Q + aFF2HegukJ9;?YF?%8/JWan<:YpG5(Pa-"+YZ;[]=R.8EXOVV_e]!kUM(Mi3e\8Op)Wb5iV0/rkhRlpQK0Y7c?]Yo2G + KU$Pp/dXOH\SZk98RaO#9^eno_QX.'gaX.aA82,`VHF0lM]@VqdL*7M$mu"`C9\.@hWaq!`Q=Q"2ZkaUUf;i + :m9nR0P1*=m%h"Qt0$b!EB-rG:H7aKgtUX;@&`@=:k.3qrid:/=uqGa%ql@#MRk*S*]ADg] + 2NMaQX<='5d2oM@-Z8E-6@%7g*LD^.6j+q:sQT7tf%DSKsrXu!*mr8]RbJVt\S^tZU-*'C' + XOW=r@[pGUnRaAlf*;u?/5#=b'lCl+h]--rFcQ"S4ic4T+A^Zg\`b15YP1X/]mHhD-(TcsU + UJ+T$Pk`,(llE-?NhDmMC(ZJmh?OHA6GmrrZBVeD3IH:0gU'c(qlhu0J3Q5]3>E$[BLU7>] + AknNGWK:m'fP%%hO=+Dkm"[FEL3'@GHL0oOa*1g-$/*gkglLr+#6S4!C]_kO7U + MaP1OBKWp_o6e3Zmi4bnr\@CdkJ`%etY3!ft90;T[r8CB:NUNnXckZ'Tu4"A1mlGh*7i\*OT66%Rk\E"&)On25`(^\&qV7:dfZJ3NjD7UQIc@%%Gb7mp^(GahI + O.g/?^KX)s9OhD"8oaP`:ZPDoo7;$dft2[#cpffWW[G]ifCLJZ#'h%sd6D9^LQO%V"Z2lV( + oUQ&;TiPbW(V!crgC,LI + pgY%gsBd,@BIfZc?:/ah`Qo5$97Y<]''88kY?r\9qoLc8?)!Xo#Gj^YBp;3)Ze;,H&mEXYC + aj=UA0`Wdk2:!NZ,0?_P5>se:apQbKNWFcqJ:Y]kq$Ac!]rKAF<+OmX\,^V"i-S^qV;;f7> + @=WbN.i4q]"/)aPkT0;@tiV$P0)(?!^WijLOo;NkP/_o0C&AJQs(de.a'b*<@K'iQ;_JDB*'R('28]@8Ip$]G)O#$6_5F5_p%= + YQo_!)W6bkTs>%L4<&h$%tO9f+iFsYF<#ig(h(])@@/'3dN,$I)o:AMJ^FmWm.Ch?`QH?s- + 'G<*XVK5j((k$l;.gZ,C3:(5+0GbKVl&CQW<4f8]-aE(YF*I@O:spsEmTX\6]e3R1`$0P%B + lr3,kU-u9GJ/BD@FZM/kd$kg_UR:jWSg/@=8!HK)G)c(u^`#:Y6Pgg_p^]r_MbWcg]76pA^ + i5s5DW2,>Re<.-U"OO=^sPC8TBUD'77;+VGDUd;]stY8=@Ia_nQ!:cReK.+Bh<9!C_MbKA? + H9'n)pm>t;CYZcr;k"cP#3jN"W#cMk)8X$-iD(7VdL[R8*'gnD^h+0%WWF=0) + ?:Ta@@o,Lfe%7!/e[%o;V`sgj"@Y#0%0>E'%R-"nerX,&n/'UTj/Qm\/c`s*22HcnPI2%<0 + @EG'sa/];^X>a.l,R,1D13AO=8Q'MW!g((_&DJEJWT?Wqm9f-Ug?@Tdg0G@M>F3)FL\);6: + c?m2Jj8((AS0iGfhl`uMA0r@ue>;:uu;i9'HH5N0'MiOo1Dp12HtHk(a!k#idf#)!TfnqC] + '&/?=o1HX(#4;5RYo/>$UNrnj!lVKGqB>3]jd0%\6UC]be;E!%i4!Vbe"VO.ZYZR^j?Ik3K + 4"t>rn8-],oFg_"'%1/bEb#hL+:o]r&n"Qo1JpK%GW+n,+D`2CYaiSt%4K87o',a.<(:+*R + TQkeXHK=6%ok*Z&2W;!*[0__iMIW'qA)B6$DBEu?sXD\WWD&D0(acL<.uuIM+H-O8JI(S1k + Gh&qAMVh'*5HO!GEH]A/YL"*/O27D9g+A*-RMgP8$S^<6%[sb=pF39a?kEL!nmsYfT\$Ts9 + ::Ze5)IaHs7]6HXs#+Y3ct=;0X.2BbP=Z)\f%;&_,Z)j.fR&'#ZNbiR7"3)bY3mV=f@ZT0h + .3M['L"(NE"<])4-1pEGt97U%sUEFNQP;L`gSJYd! + Q5WO5TPEK'N@"jDck`$9Z(b96rdGt6r8)HZ?DD(`DWN(Zs#9G4ruE!(ZDM;JHQa^`!F1+)\ + UIK1mML6O(#D^@MU%p&gKP=jW]t/*6Z0" + F5p?+7jG#u"SIOPJUqd(pK8,%"r3Ok1/`PnTG,.%EWJ%</XD_&stbT(&)%6$^gV)B3b0F0s8'EL0hOerhYNAd%22U4m.EOqT>SPcZ:;>-h + GG,ZMT./n:L5P(4W*.@j<\Q(to\X8ijVW/\ + ,.@(eolt8nR3Rp_E!49bJVS2qJ/>_V,]ig!adNrg'GR]3=nSJ/!BG0d20+omE"ZZ%+Xds(' + H&&@#?USW=%38Ma:s/N*T>!V(K?uC$H"dJ..fi[3*hPrB10^qG3Zt67:17ZX*:9)?fC2aJY + F/M`Y;jm"+^.b=.OY=Tok`bOkCtA\L0#F4\ITRFe!tg08FpTQA8@Z6+gh + AS0S4Yul'c;DEs&Z\Rtr=BL?7\;hB!FeHQhTbI9PR$if`TlIT=E]5@LZ<`AI0Q>&B&`IgE5 + bjZL3sbN$>h.5-='&@h6]MmWSN,f'f%kMV:3O*d4O)+BQ[)r4'GnTe*IucW+hRp8rK2@OOr + 5^I)Mb7pMl8WQ#9ap&_'Bi::C3FP?'54Ug!8=I8PD0HWuA=F>.:Q$\h@2lSo`5,$"U9JD.` + ml*$N68J>]0qG,J_%(SN_qV"pBQ4d_6uBd$ie=;HEFRo,TGC,r&@H)H8&X;]coK/U##QoLX + .fRo.aS5@;t38LC\hBWnIX4oG:>;e1GiXPK=)_8uE1.*+ + _0)FPDAJ^ZJ(b[+\f*X)UN-,TMV9Big+-A?A8r"Z%5!;:]%acS'HI3n%@9qOREBV/9;M:/Z + KeEqPgQ>;nYQ$R?Skglq#,E36[0d0%`)Bi!)*qiVl9HK^&!`&)s=q>lUbJ<%mcp#E@)[(o; + WV@h>'K^nr^Og\%pc0_&O-)=\e7>rN&"f1[LL5Ua(>)U`=rs*'mC->FSD3]Fpks(83c!qWM + ?klkGS^jB.ddg@Q-CrO1[!YGnB^F+%d<+du>fK%gkt>8/%m[tD-#ST_`Yk'PT`@qahs9aN50`\!Qg=G93lln4$,$j/[NS5-bH9f]B[2eXf7Ac_ + 2mVC)^3?F'W)j8?4c(r^-+itSOL9K8(??f82C#[+RU!@dppbDFdYNW1dASm(LB6Df,\qa?$ + Fn^*QhV-(Ri=7XII(pn9cCt)ZdkkTS(SY//r"I"G'kmi=2rhOqbQ*qj^V\S6qh];=G?%:"`h6^*Db8>*>8&2aeEet" + 15VnVJ&4q[&j33mMbh@9S?$7L,M8'BfYQ&WiP$4=`"ZNXce + e55+g)Hn>STOi&j<:_PmJAnR$kTgY[Q7O8['1OY^CGpgi\6?.S:Sfa;Z+igEAP?7!&7ET4X^RD(B9#3Y# + ^mu"DQf7$)F=tUEg*`>7sjaX"F)kX\biH79maSZ&>*U?]@eb&8,dTTq`)o6_[`(/1ndc)#_ + e7'(WKH.dD&R@]f?DILkU&Ns4rqks^@."p5hA&?WhekIr:O"]:nHjn5@?B*j4s9`Z7,O,/Z + s!8DNCMrhkk'8p6(q-=PZfpcO\ + )BIl'0nE5+tiQ<9f`P[9g+@X]FbQg@]p2@Bir$Q\'*Y4GGG!3lMfQ=es44&#<;= + hkJ)mU-mD7dg(X!AT];m9pr`bVMa3^u3(@Mgl*HRot2du7M@KqW!13!FS11,hWpCf<5\+[\ + .$o5QiO'6B9l3YM%h.m+H.d3h*j;t2(t&ekpY8;u]q-k8*+;b9Ebe7B^l;D`5<85b\9-?9f + Ol/6P!O4*fL^Q7q"HpB3`#(=:l$9&LGYjGe"<]-.T$WXG0]r`OBX;[ai'/>PAlMEa0D'`+#4<#&TDaAt7=Bjn/(7?kJ<91@ + Ht)nin[*9V8"7_R+WI^4N?fFSKUF2[&sT@G(a\p2?218:N`u6>oYmn*g6#/g'OI$Z:X0+0j + A!pQbj7uchXpG4:P>898PfPL"@s3eM"GHq@6TC?BV1(Cq0p)>m`Z!iN,)WaIX>Y5WXl.lF` + g4/VXYi(rH&Z%:Q%kL2c8o6`=n:=5*Tm_mF^*neU4^//_GA.>C,D56PG;:Po`R.7H4q'k4O + OM\0PQ7C]lE@$D("DM;E4B1hFAOn`OC%)SBTE]St(aqLP(rBEgArM-.W(d`0p)[;f]%A9Ol + %m?pM@8te]FGFX`_65t`C=lG5A8KMa[1#op-!P*84""k4Sb'^CcRJ$?-`m$5oAIl`&V+hSq + 83_fMLZg9">21+a$o6,/S2;uQP/Fd9=]:>i1+4a8tjYP.!WDu!Ydd)AK5IN[i#DkJouE!\cd/k#2JO + /]-Da;1S>Ua1\W%?0DDNcZjmL>X6h.6$54N!/las#3jV(%(^kM + 6!%jZod+D%`fo^'W>j=_hksYI54dfeQa@Xs4W!X;-VW,/`p8c_IU+%_VKs'@?*l,BfMc@VV + I3G]fSIdWc(%DU&!p36i2]9Tg$1)^hF0Ka$?If7iPtX3!HA`:9F;JpU:9![G\BOpT[UK&8Z + OI;=\/*("$ENkq/2k\?fAN!e%PFqYLM4WLnT-Nt5g39TN')->0&'@K?XZ(Va%5=3k5NhD%N + 7_Lj\'_;lF4@P*H%O]2^UD!e#0K+[&bG^FAK$*Ecip\e1U=_+>A%[)JEV;W<)$_ojNuJ`K4`:cGs>o;#j6-3OEScVlRFJ'oX)-O!KX'SC.R\"aLZ + "YBtZO)FS&hZolQ,BqF'Q`*$D>-@br^2:& + ]79$kLrP]%)c9.mNcb9p[J2Y@9H513@j;-+oU?5\Tk6+$83de)IhQ:_)8c/l.cMF,M"(7W" + [g)P&VB4e%l>q*Qh]*JSef&0jRbLs`"5KYV;S?r;ZO5m;4B"Me7HW)*ZUC4 + rNi#,"BgJ8'Z:?71OI],Z>dDN/2O543tPhT1d&+ea#cAi + L59S4"1Bb\::)4h,lhtTj12e%cnoLb39&@7F6YTP*!jHt*:+lf`(\,!9uQ$=]Mg(5)^R?kh + &Shf_Lp"Aj3+<8bm,b&XuPqUjB0(qCIA/;6hA_WF42(,e.-!=%R`9>H*/(7X$)j(%=pD@$0 + iS:llORB-].=?X5GABI0O(NJ[@!u`%FO#dYBecK-5GpE,qTa!NIYpU-hR-4*sB^FcbAI3Le + #;$ON@Sa7lV"c-amBMX@j,D:#JJI"FDFFQS]J'"LSQZrafW+UaE + A#C(O1U52"FGs9Go&4CY2A>I@IV93>Gb&jQ(K>GDd`Ni(_2t::d]?%B`hZneW#AG+^5 + ]$]JE3'CG`VX-@]]KCp&ABZ:)1Z + UbhX?d'7=GGeH#7SYbK1!"=\h*W#XU8%7JA*3PGV):ta%T.KN6of`dENrbMHir>'WRoJX?S + dG6>TReMk7mj*\>OULC(RdA6HmQ>I3A9d>Cko*fLrbtO8?>'I\Ksenlq$_3& + p04,$U#,U_?NWP/7Bu$&kReU?Nr2R"^#8dE(C18$A6!YafPTk]c=P4'8-p2^L21S*;$tG8^ + -Pq52'/cOt%qe-.6?OQ$"A#KdbB7.FP%BQ&M]]!Fq_I@GKtpLe`4-II^'-X0ZUe*&>"$Q\i_/4j9C^u4%)Bd*&kN@@6gM+uXGI.n5g3cZ9S>d + O9$XeB]0!r$bfG+42"e5N!k4uOdR>Zd"]3_a;7oCF.XbbhYd0gU+r8A^Md>6bGI]3O'4U02=Pq]!4%)6?InqALCpQVfI#'o4ss+CsKBFKRd*VLb^mh% + F\;Fdp6l:W!inr?%[rYLNmo=$]dT[OHnn%3?m#(-$Y8Ge5\T!2s@j!b-=I4Bkd^*9*V%c"o)pk!,g/7+uR8'd$X9$+C=ChJkSC,9E=m_iTYV?:uDTVOD5,<50"\lIr9$_l + *f9(m;Ff=#ZWS4$L>(*I*`N7eDXia@a7tRs!SM% + Xgfe7oZh.(aHcEjeaV/pfce-h)3[;6B'GM$WTR7.hg#be5Sc"0tHem#=[CtOE&OLDr'Ko88 + rEG`'?]^NmoXe_p)PpX^I=Ej&Rtk'">h$_#n]JGIZ=?j8gj:>4:50U+JJ^WfQ%Pd + E(2`"rJA60P(6'3a).[@P/n!BGUe*C'HY7LM\0kKquY`qH&*n? + b!@EXt5\:$KC2#Qu4Kc!fQ'P1#jT9j#/^$!L,9\f%U>Y>7ga;.@V3k)R?;GK5L2:5tD2^9n + HbP"Xm3rSj:1]\cl*K"`(3'"0JXIiu8V%%V]4e;_[OV9U + &38NZ",igBm:[85gP?BA!f*$!Um4Xm5&OlLq + FHcDt]oZ0NaBlSc),[4G\7,i@j<>81!RW\oQO)@&fQL<+@g80qnT4H/3:KW#?6n5"[K5gu: + ;>M)nc+fn%==2T*^*_SL+d_i.W@9cQ!##QaYW?iS07:aVEjPEYM4kk`Y[H.S:<3B*H(!ku" + Hf(fXCIZqCL=kmTWb%0njeE/?7U*hq>htuX#?K5`pi?ZeiSE>H*Ne5]0dMo_A;4RVP+,i#' + 4E6j@Kia74b-SHOPE!lf>^6k:46bMM=/rRl5paU::OC/O>&To7-A1UlfL;7&57WlFp;>J&S + ?=:*DKE`G84YaD4?dWX783Q2SV(oYcK1aOE(S&GQSd[L?>6SQmIEQC'C8@#1UY+XU,3V$3n + 2'#-EmiSW@I_OD,/dBcW@N/X'm4'\Q4s=^^GrE9CAaR#YWniMe@I-C^1LSS?_]'')#>Jjq" + o0,!bMKihO\GXdE7U/A@onok@2W$qkrD$Lo#+:Lg.:/[`Ip*cO30Ll)oT!50]YO`qGo/AJs + 7l95'12>u,>.62,LH+ml3SJnbAXtGm4YJpd!%U5nLHo[1#fhmapM8Du3`&t5(e&6f_c*V9> + O+.>ZG<84iR?i<_i`rj!+7Um@[gcjUqgY7`m*S_LE3PDe9H!$%ace0)FRiR6tmG*`mo+cV+ + NA?<;$-0Vd/oH?1XZghqZc>o[]cq\19WiLLR[Z#%-d8EZ<:umQ?7F`oRDg?>afImYYp@(gK + fa=enaP%H4<^?LCR&lIK\:0/VpHXCuN-p&s;EKE,ZgQl,/HH1W;5"]-J"LiN]KVt/-3ar^N + Q>bgK6?8;_6Ls*eW)cGDuoOoJ>MuZi^:icXMTCRT*KCEQ.BOYqn=O;k@=LO.Oc40!o%d<qt%bXF4Jq1k@4`VAq^e8KX_QeVS37j9S*7EKeQ9oA2I5h<:kOQH + ap%9nM"T7S5-gQVYUM")uk]C\QAbL$%&fC+]M;9dnCT_XA7F5M_i*XIpeie-8'*iFZ?"=DP + l)sjJ?F'.Y_qoT4E1\B5+"0X5^e"U%\U`#Es0m<5TM#:;FTr[KO`5h4T!1/\(%2BT6=EZWA + *P'FIDfSl-fe;ZrJX3U;2=de4WfbV?h-3&Q;&`k8nCo2A@>bsQqV*j3Mb5C2k3WU;n:u_I6 + XB"j>8k"DN+OP;IZmjN'8C#iH#2NCiid*+<;9^Ldu4Nu/-oLuA + 3-VVV21%e4W@i&CgfJ*"m/Y`Pe^t^U;9C:a5I(\Aipe.b6kDZq3/br%WB/OJN>'*en24%&hj[_1k\#to1KmN+EmCnP + j?E`6l_e&]^!759IrZ%##kGOt;S;aV%:4--;-=EJB"*YeV3dM8*bpR0^a9l9-fWQ`]Y%h\XQ1IG^CYrI<`B%L%FAQnAf1?:lar0qr_=U8f@-;?o*2#F3JPS_ + 23$G$CkE[WG_$26Lt'V\-b/g*Z+dB3CO#O^hLZ'3o"TL(eZ9\-:u2=umt)Yu]C$AO]YJV(R + ^(Ba"^*:MGLES5!`$cD4+uKXRsSlNF)l8aD%q]L_B.#t.>pk,BaCdF=F)[-a>hh6h,>DWA4 + o>H[3XS:qf#GFO.h`,TX(SY+dg0dCbZ1%M1!G7qanLb+1!2W@GfCucBIUR^O/:8=l]].VW;n@S@"KD3nR5NV^&K^!([%n9VreU + N);;ZEBLs+!oKpB3c-Zc=ch>pJ"%TU".n:h]]EaJ!j_QQHCd>DZ$%ko?I?ClKF1&VNu@&jf + \p$/n9#KF#!s60L:g@+5sE&C)1]=D\N+`&WTLmN+aJI1PbCk!J@D4_?BN/loc7VPq]\V(nfb35I=pdP0W+/50<(`9O.YUcF'l'p5C<9X0Aob&G + U:s_?-"8E8/@(RNc<:TEPE#i>aOpqCA#sPs/iCej=$qLhMl_:QBHVqoGtN[mC*C^P4X]=oT + p/H8;G((d.HNllL*QL,Yeh[I87N2\c>Fti7OlC+Uf(]=FEG^RcS*rj-'4mJ@Mf>^ZOD*.1< + dP]/1#hMQ/gPEWmOs5G(?Sm.&ps_(fAQ*6T;s]i=iPV:!Qe8-:Gf:C;O,T$!+DZ)W6fpe'( + BA&L\nm@)3Ia+tppf_*;T9s.EF965rt2`]jX2MWFC$6j"s^^=?IbFf>0iTO8N=(^gTWJF]32P(@\P-UY=]eZW3A/V)4 + Z(;oQ1a\10RWDBu%9]ejk/>rB;f6JIftB?-"Q>kLNcH3Y[:cMUoGrReG7O634E_:Nos= + rThg6@_skTT128"YYCklFp1(rEegcCBL:"BaV\D3YQ'7U(.a4;)0W\+dEqN7&!\U-sfd0CQ + jHm^(e`s/SFGSTsRj\dTTjBB43*UZS5;#Th?khXIZW)A\6E`OWHYmbSTa2Gp;j9r% + 9u1f3lk4]?I3M?C_$/b.7JL03FL+PqXq=pK1ZfZOqZ5ZN\m7iJ.$6`oX=TW5?6G2GAE3Nn# + %Z^,M%*e\kscOrG[=d$*3R=LZWH:#\qYdTkWhHTP.3_8hX@Ut]5P.!-d+XAEbmHtS'=AK94 + >V1[!`Yl3_Y#a5Q)FLJ-`KkKtXd.(o4B%Nh@nV2lin8W=AbbF.\BJsQ=be^9juITs-=k?,3 + :S%`cSeDc>q:]H2,:.*B1^n@1YY[Cc@$s`a`l0n_'S@AV/<`oF#[shETuh_t,A=)&>W*XTW=s + bUQR0tU!A6%G(`]-U^=+?;U$,Mm[+J);:$8Bq2MriES$,; + #VKQpkKI?gbXh#rXiW`4f[Fb4+NIpiFPFbR7:#";C:L[^9!kPiqXC@fZ]@N$s*QB<"WU#^A&mo;gm&2Y\jdko+GE\>C]-**PDnIPL57e8^Kj4auqBgib[Ha1KKRO:="iH4NpuS0_umX0K$;NR2ZF!&gA<+M(j5%o:OG,+&AV#!QZ]Uqg93A0 + >$E6(0"t5qRfqE + 68kK4QTaqcJR($!*3NNs9n[M97_;f%t;d!-8.D[5c^'&c(j!-AbqK`@%ah/JJ_D:SpM()k# + `*!-7]n%*IV^lo9%E9nXO0@!4.4sR3k,:ceaM`S5*.fJc\i^)EB60]@s`#\qs)I_216&$B; + r"`_Z$pd?%TVrNT[L$@n)aJuf+g;1P(`L1p&Ab@LT`Q-?3cq#i&4*/t@1jL+"rdQ6'-X^jT + g0QIe.glU%cL_6+UT"D`Y(Rb*2,V7_9`q5S/l$])PPk&+hA]Q^)]N%_,/G9$T^b;2WK]K$f + ZpLZ&'=&N>V2J))7BoBhJ=X`;OI2+&##kn)B2D.`F#\*PpB(!Ql5;!$!.3"#$VL:TB>(U_? + "B(#_t&Nh%2iZ@5QE'r^%PW8pu(SrTS)lU-&$L2(=<^,1UuW5j/LK+N3E6GIG$*%s;=DVCG + \/s1lD`@ME_U:B'Y*rI^t.5/dNOdRH"4X9M`,Vhe6B--RreF@J0M(D(6Y&*ZBU15i-`Q_:fm&g)c8$ii>W1,\FH'_W0+s4Z$/-FBM&>>t+Bn[r,oSCcN^_3:u(h#`?77n:F`s0W<8`FB=0DAn? + X?kCctCGc-0,FtZ31&ecF)B-Etso&[`G;C#!?h)koE$IUUPN<\jWmm]/8:c/DQFU:mIs!5d + <_piM\n(c>DC=7[p)[,G$lrmnr5%H!1-KV+9-Xp6$G\+cHGXc."&r&L3dU;(^I4E;eQu,P6DD]X::\IJS;-DP'o + sN(!#*Y,u4a,4#k6>3<;]3F.16FV"\eN&V2bQ^g%DDM-]_XB+uA!@d"3Y65tL(*RNHT.N_G + ak>.AK[FM`jU/Mn(4$CMh4psXbmNQ^=$66n9*are%6.QSjBH0:-'*5HC!;priF):PqD$/]! + 5lM[0PS1g<%UiN\!B:pIc5<7`*T>YsO`]Pt:5%#BZS"=&@QS:j%<2%REAnVPFU).D_bkELn + ]i,aocYX*.UYBTKg_o>t\J0.tSoEL4([GI` + (0]mJh:;%,_j?-<;h?=u%XV)9%EQ,?UB:E-j-p3=Lu + b9U%YUQrFT4pBHdpAt!GrU2";@=%V3u/B5njj:WVV&n%_;Oi;E%4`hN,=XN&H@6+Rcg);,( + @Wni9DnTi0oYJ#>7*Q*e'a8U)TmX0F`)Rg3Cee5$i"3_@)uq;qiLsmY3W]?$>_6b5InV*(,HB> + CXe'3VF3CXdT4f^>--qQTT'l(1Y*.c``@68AB`t)Qc&E0ZUGor0:CQZ[=Q@N!IJZ'!.8/oB + 5(u`pZnT'eQ)/n_27Pmfs2[DbFBpndjRQ745H)P_J^DZ*/jmf7(ENIT^iu9_]*?iWC_#55i + 4+oRWk[6(^5s,MU=qs=t#.0tNG?GtDXqhW7<^WbGT + u)p#!$)l?`MLFVCf6Dt]oqsG+h!CT1?p)dB:?CtUcLr?H:^R"`EWr)JhCO3!XjdO]po8@;T + 6+D"g44^:;:M%0StlGj"0i_6%=HEoj_h2Tm%pDCfV1547?i^ANL.fTn.bXiYMCu-/*:^Fnq)rZQ)%e5`_Y!n"CZ5!>7Z&b@Eq1dl&V,&+ZmV)/ + KclO?l)aVsY5o:NNCt#%:%q2%D@NDjeD,F&Fu3]_NF417X=!EWgV]oOjqO*LM8)#o[X";C6NOGp92@0@4:9E]n<) + #Fi1)k!#TmN" + TR'!VhkhH_%m?loh"N&HSm@_,MB>$lq[T6l(]!bY/6;u=E. + S`)b2d30U3hWBl[],ef8,"*+;_O>03j:^`+FN"eg#Pi=h6>l,EJa9!RK#j1=NLA7r9Xi + bWNhLH;C"7)6ZilYDc%nUuA0T3*WRB-B)WXg2=q>F>/bH)NRQJ;LgTb<&PAF2c>V=,F']&Y + +"iG8q=j;gOu>+%1A=C + 7hVEBO!%b\g"h97sE"*MmP1k*fOagtGLh&f0HD\ep@9Y`K%)#T<_a)"8+FGr<;K+4]<8BQ]Eg_bqo"Sd0fd)W:0%!Q,Bjf+!![^l(EFKO0AcR1&t)kWX^aI + Ym9i/(U0ZLG'a=M/Aa6e6F0F)I[%!TMa299N!i0)'M6QTjAIb;$NOul6kP5_-H8XZo](7(7 + /\N(MjnXH<6kQ9LDZNK0:>E-QmuCm(p-t##;qLaH8"T*C`!1+lCFNja%%<+ETsInhJ=7\4< + -MninGkIjsra^!+(VRkGW@4Og0C:@aEeo).Le>itX]4XUka0LHLPj!,r34F$"!29'C",CGr + ,=ukclJ>$1tiDG.L8ZD0(JY>(RR])cKenWpDJt4N0_,:nRgs&OGe3:#2J##p"`"Lul*C52d + J`n,jajip(Xk7(A]X_a+/@ODto2WbM4OYU)FA818_dVIA_874>hAZAqe%h+Z!MC;RS0/7fN + "--?K0e2[QrG[U%>R=1&K$a,SJ@&=@hG#;DE,h#8(gcU9T>/\P;2/-Bi>dL`0W0T$lP61BY + U#iV[LVpmWc:7/gq*R'%VlGUF`AP)H+;:Y,O_-q=#Hr_1(M8nQFfc5`B(aD:&er9o + -dl@$H4gY`J'B;Z\I-KNlSQ"u[XaY'Z&)EPtBsN5*pD][SiW$UKDtk:n(CuAAj)Ol7Sl[S5 + c/NX;*?LNuWO1<#q\ou_Dcs+/GP(kV(7.[go6KsLQa=froj+t2LF$GX;M/^pZ2^H:mj\255 + Hk7i!C+pV!s68'M!Sd\`0mnsQH'l-D]S:.7e/[&'Jf-hcK%$7].V:-&N4Nk9'Eh56rOghEF + `8i7Ic%@)+JVACE#b5`,cqhETDHg8+He8+\6W!M^(d`7$AogEb(Xe8b.P/k![TJ7h@QIlo, + ?F0.TH_ZcB++H6.,NjYIr66/a[Adp^sR*Chi14e*dj0ffC-]^eZj'AW7E:\45]51"BT#shC + p7/Uj7+dm:KOp,\I8LbFN.n^VAPpE7Bb$eUY;sHYZ=k%10C7uF-nT%o8AaNZj([7'6QPbQV + N0>LMapr,eB!$ApfB1>0DHLNDMJu\\'p*sT0BOE1knp"H="i5 + &J<+VUa=Arss,Db&=OX,c5:h/(U@>e:NL)jcnMcjt5V8`TR&L&O>i'.bsmF\'jB*7(V&V?*0`W4HZ*!K0r*M^ + FDRJ.8TOCWpe..);E!u3=4%H7CKM.!H6ZZ*6@4X2ld)C(Co4\S0/1Z^B[Po1N + eJr(`F,lC63uc8u*b3]HO-0-#8,30.A/'WUE1q>5fPG9\b$K1+1sRfJ[`"KAmolKfE=Gdb. + KVtbPE#!+2lT'5`rMA,@iS>M@9tI<@[g:G78(eR18`Re6@I"1otH\dW`dg0co=Nj6#J3`roX>%=gPYpH]afQuD-.tqu3QqW;P6nO + LA[WbLBiB1b;o:D/sHV\qW5%i;-ND^V[3S-GY+l&&N1-IBI'\+J]W9p'i7!1'NcoJ%g*up1 + aE@c+Fa[YLKUPf7kZE^Jg.1l\+6ul^!AS/#H_rF`MV=hRa3i*![1OcGWY6m5a!"1MtJAPoj + 2/t>$l4B7sU/eG+3g!;D=Ha2h>Y5'C4"+Y<;6#mVX&UXSQN,]b!#Dh`%Mh)84Z + o:$C$tt[Mm^H6hE9([&+H['V73Qs^%qK]j@ItSi%fs[2#A@HY")9/bF92[LfZt1bA;7_n+_ + 5DO'd=/>BKVF^Y3a].Z\(k[g%JW_D%aIe"%7eR_Ce;K_&Xc=KOmh$:.cSNgLcLkRZhC$ptm + Sa<9fbAWYU8c<0KbJ@]B4=Ejf$kgJ<=KkYF&kg0:41EMqDf9[j'l,6^mZ%`9p;72VY@mh?K + @66L$%EO7e@']b14-4u<-d^`,k.#h6C)TQoA)@j,dN0/pE7Kg7N%rmFa-cTCA,Lm@j=?[\kA`'sQ5'oD&n5'e+F$_[.QRcaW#S-=W)\/OQ?'iq)T$;2$\ + ;>)eEWNgP/B'_fjn#-Th899!E-XtGkk\c3+gV7m(I1>7gMrpR.PuAbh=p2At7BkF'WD&RC> + NFqJ)NLRe=N+8UKV_@lnh8a5:-SVKCCYe!bJVC&L>rFV^`D1\W(hr/>kN^&rIWl@RX(k9O_ + 5f:d8/LJ\C(3N/biUC.cS9_plT`kWXbeAH3[IW)#M/X1-_^\I3;f-aLdpM_J + C6qMm228Ki)Y7?Pod6I@@4!5)o%okVHZ(XY-UCl$).HQk%jBh5Dff?>M + j$cQCZuj^-OYq1RE)kZB-L+jTYQd(dd%]E4BR]V)FL/3#U-+!n?Mcp_Dl1dd>g$>FfOPn\! + L:!,"#ZWV6dU^cg4DHc=Tj6kE@9`oC8g)XR"#SKJ(c5gK65uFg68RWnKVaAka/\iFbcZSl4 + R,qat!b;Ir2S[!Rl`G0+-ARr#E:#R@i34k6JO[B#+G-78heI2Dg>]4$0YaO!K)&E;Jb*l7) + f*=e6k3`?c-C"DE'r\_ZTDZ4+-pOBCMM`hOj0I?gJ22>:B'LQgGLN0$Dq&c@'S`eGt5>5CS + B[I54N,FO#RQ?4Ko#-f_'39q.j:M=sn[QbsHhcQ[4qr52^DGoj%u9_);CD5]2s5SgU%Ees4 + ii"6K5hgs`6T`TFHd`'%kO7t7ek1IK!6\BII"Q2Lu/ic<9C(qW3M@W2uHLU;@ZV^m?0'8& + D,f=4Jm5+E8n3Z:N1#KbR@0M#UT+X\X-rc\T7EhsKeP_n5OG(f,^5G(ss2LT=OCq$O#UJu:s20j2aOC`()oIN`MG3.`&+;ZOPc7 + Q#65^@N]W=E]I=4G@`m!-gOf6r=K1$3ReNKD8E:j-R>CkXB;h#i&FNo9gX2JiTjX3W#DI"C + 2mL@7BNi7JpN3gg)r+T;F]DL`4aQ:l<,)et/7>T!;J1HJ9=IS;GmiM=YBaWo&\%/1",kMO: + C^95B5;Z'o^q$PRD1LeYc*laq/Z^='Z]@opM!GpJSW#>fFSLcktK9*!a.6c;,/T[+U,AVK^k`g$i-Vlqm.QYPj + qPGW2)4gU,.=m1Q\.`TUI1s=d?!q'u5bCd9[RX?ks>`(,9>89F<22La[:]W*2c[P&V-\J\_ + A,4=h)(A;)7#3D,7Z9g$p"9[ujBekKn-b*^o*W:%TdKgEpJ*Ad18q2-#oN#9rS_A[1+.?YQ + "#Gt[d-97EKWfm`/BSgbp7]9W832%dJXbD$l/h:]q_W;.6F=GtOa'3J8`-9*[U:1t36X$:c + >,JTL/p.=9\iW7="*Qr=MmDO&5mh3G==>R`h9`bpGPX3T?,hqIS'BPc + 46o;8!rHbo>',)9)uJ`ZgO71=AWXh$?U8L1$(EKI;UYQ6,+$WE>]CfUsWPlKf_amH6`(\U + WM]>97a0A:Z,6Q(\b(:9,?j=,a!u'8dUm9D^P#I?5<38hnU@5bmQ:Z$q#$dROOF#$LgVbNN + 8^#sFA=cU@.*>FK8j-9Q4G[".>#h\/6q(5.bN%,ojOIgKaa&$M8TH5UH + =9p__c;nqA:"k==]3CSJ>d`6qW5=NZHXN-9@Q#onLuW$JOHqe6gbs. + P`li'7(oL9ZUdF!;deHY-N%i^+1=%/>6VR+TQqFhY>m-fM[rerPKksrKX]k%:#9Zoqm/)Js + 0YO1lcg24qF@/k+'@7@Q=PN%9231R%X4BpE8A)(L`30Dj8VQ)Aa]$R9\g@E!:kMS?L2p^oK + b>Gif3E13d4A>G6HTRBiiL7-\[,`l9c1k?A^SA,VJHLn$B?H]dTnHR-@O9I%FRh=0rK3M@L.ofQPdi-tkj]1;#O,!=TrY]MH%+#Q#`^m.*L"8=%K)BH(QmL9""Q4`(I + c0=>FqG'6.a>>)9,FO1!qV[Ib]34(0I.bsGfs6?$HVamL%OS/Kg+Ks`EQMm$GP<(@jr[ajP + 2;?KiL1X@WD6VR*O85@'E3!&.cAj*>07O"&+EJ4B1\)9n0'!b_'J_sK9,:*>`I9#?3\(d&_ + @CA^'3%+pFCg^*@!AH`mGYeRZB$hpEkIu=VaH4dp2i>MdIfkM.iktk])dA2:SL?esT7adJ7,%XWaf#K/u08)3^s)Ga + KQNT&'<[L!4A][4DJuU,rihcJef@"mB9W1/AW@g2`p?a@?C>&;.c.B6L(M\.T4[CLI+cHU1Z + HH:7Jf_ui.Q+KSpP#cIAP?;p^#]k^H3#J]5 + [!*n>?>@N"m!K("MtIc@#5lA#&M6_P@?Ea#:S^mVNHtm"uqB#$cLGMZrhQ_`i)EZRpTBPB$ + 97$50e(Qp2=jCDgqIiN^g[)ct<]9c*h[cm:\fZ9Ypqi(u]&C+d#M:DSe])).Aj;*SUkR@9q + 8_Q$S`c`9E.ADDn%:`rK)ekcWcNW+jURWT*2Wm.%P!o&^/j)YdsaQbiF&NhO/WTA#+e`@-tgP4/r!Dk"u08Wp/XqB.2hO3n>5.6[[IPfls + Bno8@$P6kdsjZl1?cO-=6nS7td7&!\clnZ4(Mu.s:.`!_>q<^h3l@G4-D;EV2NQCsg + n!(D7^[apn2VXh!dfaR!d@GQ32<%tSsIJp8r$GOL2Z( + !MMM@c1O$p+Qf8mT5ccZcJ-'."`j-A@<+_fBgQ`"::IoJHnMs1f!MTdmcU`c=>X]:\Ps`LL + lING'KpkHU"la:UPOR*UfVKLZBCk*_H.un+(a>fg9Pm$"hk.:>'o&fntfU$BSs5BdkHifuR + [3?BrBmT*QkY)!WFG8p + >LQHT^`uXk;$Z#KjEXo.s+1>]j7lB';c7C*YD79\)^XC\:KBh)>=p3_R`,A"u"13PdVM^ + r5Z=>KRI`HsCHbuZ]D]]Eoj=jL+^olUF@6WHu`'Ql8Z8P;q0<&F7G76OREj1C?#H!US`]";!W$%Vj# + \S_EX\j^-Vf_r!C?g0GLB6_EL^F1L)X7IQVfAU;['.r!XK;`Bh3`e`ldR4l3r'\)HrSI + ==D1VrR%t6o@f;Z,URLVi*=p*q"A7k.sekP2j(?,Db9p9lkXg[jV).%)Yos3=`sRr':n4;& + i.<_U1JC$Gpdf=Ppt-HsksMd@=mu6'8g"*4/OAGok#Rr.-!3;B1N;aDo^l,f_X@R3o<2N1h + Z-&T"Er[BWE1/@Q3B\Lu(Rr4t)2;]Nn7bgS]%1sJs(ff%+(I,R'1;k3)5cI9Gr2+pZVXO]i + QU_t4>#K*;$1@DF37%SL'j[PguH/V;bL=q)t'Z(a)8cnY02bd58rB4!+[2hP2<)kMI;q4l* + =Dnhg&X$*'A[uV\-Y98k>c4EdAR=!-NIbKiopAnjYFRQ!8cBqjaq.Vp:UdMd!Gk5#4"S9MNeI%8gAd=.Wfq:U:h\/F1UdO + 0_eGu84F&pcUc/.Vm"D%%KE`Klb;!O;(*L92.oG-eU9AJe+I!B=k%TM?XMCX'_*X^*RIdCG;,h*I85^c(W,m[R0rY%u:Wie^ObRjn@$ + sSNfOX@'1/muI?[q=HI?tK6ASd*6q/MhKrGo&\U=\!fe:>edXc7JW7)mC<+ldEaOKlCIf9l + eKX\a!jGVhD6PT:a%jq'ipTa9U&-%rLW'LClX#1pL)?DJEk&>C"/Lk-.DYU-Hf@Aqf4$KZ) + 7R$85X=A+L%0l@'X,V7>^gt"g.DW8@BMUqJNgZYUNnQ\u2QNek4i1Q"R,Yl>q)HK8TO)cjI + b.3,Dk/`Cb*1rS%V)FA+AN>\BG7$+_n!W5q,!8JtlG[(n,4=,grSdp + E=cNPKmB7Y$l.OSc)S^OB[:>+Yhk?pLe"'eD*iQ=-8=S#u3r<,4sE0FgCP!Y0h + 3T[Z.t;%`SeW1lmU#oOl-RPdRujW1,?oZbe[77cYgRA1IdI*l\.5,b;01t5-b)S."ak\W18 + Y:c>DRWVBijZTNkrh)#=Cm@nUJ2p4."A!%&clTe,*c23RNmY^"LG6+Eb%(oB)RSkeQ!JOj? + tbEOZ7a+pXDVe/A1WFXZ"mof=N^!%XG(QIaiLqonK;*r!bO)WLqdedW0N"5o@6?Ee/U%XTH^Cnj[3,I+!RjpF3&3sf>322B7V6Qus?b$q6IMkYNMC@,T7[FW(E>8q + -imA75!fdRrrVtb;VC64FOmVR3mr`59?m`Ks6jRB%f\?BoP0q%X:8DQ`BY/9>,MO[F5, + Jps+AA1%mgH[P8Q=_QUB8]L8T6Y0$IC-mH0pMSb6#U:@+>mKP9N.(grQ"C`kbBW`*0^u_*! + C5n@[ER+<0K1;mQF:E%1u-kX"YR5B\idKdWo@:iWOn@Ul + OlY(!cmdh2*?F-$I)`*s>,>'Hl_m$aiM_#@TmZC)9B"=XK]0o;.\s_l#P.SEHD`K2BgsFq$ + dt#!j&p=+X73GJM:=7Uae/,PQBM^P9td0-0.XirQV@'q\c3]"Yke^A-S@N[c]m9>^&-cq$ZSJW0:I1jG9&dkn7& + OZq2NoYsO$FF?[iPJ+tg#](!td!'&(iRf(Dp1-I]/4Q"O'gl5-*Kd3Vi-=#(,Z'B6Q.FPqb + Q_?)IK;>DH:Vr=j'6eVZ8P5BqZYWi)'W`sN1fkn7T`k-j"MM[OdZMBfZr:mIrk0$K?3ihli + H(pB8"Yb2MD.N* + f^"YR,EeL:77$McV%"0S?I`Z>1k&6.W@-1:(SIZcN>I.mhp'%QT@[V<0Q)C)2 + .,]+f2lN1ot0%2"\-Kpgu.%G2[oP]K\^8S=OVZq+<%WNj3N,/K7%:6i/$?HYg&qMYJTd1F_ + .LP.I&qDn:OP9PMhSSd@%FF*WJ]\\BJfUPV'>`lR+kE:O7bOQ*8St+i$r3KM8J)(m"N4RL# + GCi#@Shn>\BnK[muUr9L)RnT#([D7/k"cpj9?b5)8T8gB@\?ki5s!d'iA"K%%_pGmT_WsApFdHCMW-,S)A\nsph]]*P:-(8^_G6u?a$4*U<8?Qn + cX==:I.Y=`Om9F[*#7Lhmer=GgJHQSD2?eX)(bn5gdOO=<6ikM^4+Xs_6.?r#Y1ZTC[#kKa + Ecn?^mLF`,&gXUr'^+]%k0U=_!k`t?5 + UK+'4I%/!=Tiu)(2_802aATEq1j`-k??O$Xl44qGap/<<8Z;T + ma<_[\*TJOd+i59$A/YL"&qPKO_!DfP_@#6_)i7*(^bu^?bqIOd5ZThM@s@%$6_5F5en("\da3g)rU9&!Q#DaDB*(3(ZELN/mU0W/F]hu + o3pPsD34B$+#FFooZh5.X%N%Y8M`kl1C^*7b>U0VGnDk(5?%;@PPXuKHS[E]&bA-[8#[aBC + A?.^BS#"\@AFW(a;"]7%Wj'.7ogQ049ttD(*Or+k`kt@QE:\Ar00[R0?j@qD#Hem-L1)<#2 + HD^r["THY[UA3U./Cg&:haNbT\82U([LgW;n7>?cE$FVNA,2%fo*;feX>L;]&t9;RF?#66h + (T1]'eoY=fS992rAnPe0T<^J##H&T+]`VKD&6=A[Bn`o`.ljp)fYrb9BFK;^J=_@gW.,95g + nCb*)5LY1%/[iS)!>&*QUJp8Z[J1*a?.)M&p9h^JD][\FIeW@;"k9hDYePd2<'B(DPTop%= + I6s%[<&I04[7+&k8fU,P"C]^pJE@mra;M'F&/:`ke)]:nfqfO#XOqUW.GlP!n,&k#pFK@@WGT\4])5bMJ0$tBXB5*T"f6N+FuG0Pq(> + r^l=Kp+b:oiTVrEQ?H;Is0:%AP?'`!O)s*5T>nl*$H4 + :@EK:XOrY]l)St/j@G2bTAN_e:ku0Zh/oNp(:O(58G_@G.p_l&T_1Bm[=))`,3%?jh-;GC& + C?[.F`,;OhLMPBM=Ma&>!Se\=e/;[I*+;/:JhZ.82J"nK5Um'=V6@"o(f-A + Jkr)^Hq($3P$CD+[_NGdHq$7V.XF + a]J!Q=>3d%GXABo',%NWHU6R-!ONk'M^N_kSq)2#;+D>*:ju8E/G\J#O][&e?6o")b2Jf40 + l5$B3eph>7q&d0N63;'[N%#YMlb`A9]TQj_&:T-@0,%hM_bAA2kq9IUKTUO)D.`8$)2Ou*E + F$&Q5j7T,>O4(L*j07aDB`\\0G$0>kU)^L^`E#O)ur;9iMESbecLBi)??cN+fl;bEZ8GO)POm"!PZq.a!oU6.Pdk> + c22H8Xr:3D)?n<(!LgXp'H(r1EPbYG6<>a?-l@.]Ergo7@=YPYghk4.[R+SD)f)n$U(uX'( + P)c)JV"6&+rK65&']Bj>DoOdNN_@4"BtX2@8IoZDAls7*_%0q$P,5UlJU=ld/q5R./8];ZJ`3;J + F(cFu\Z\-rAOB%)tnL9R9ef5j](h![i5rk:"Kub-`]nJ#2;DKeGTj9g+HQU@fY%8ne"T;6U>b!$-5?Q<# + Bqe7NI3R[=;\GN:!am_cs6n'SIu]&RejZXIBq^]:PSri'LK[XIRFAP01GUb7SJog&;mn + ![BFE,%]WTJ\$H-#dNLZc(*T-3Zh_Aj'WR\UY7P4r_&3ILZKRUj^)!.*_]Hm25A"DU]pps> + "BYP9H;6R)o9J(U(;/\=rUTm)U[#0ES,c!_MCL9A1]O"n<=_L7:cL.Pd$m>aK]mD]mWV2%(;K^2(>bDKh;pm.[2Mk`f/\ifQg/;X'72FZuKrVnT + 0r>.%\A4%X1FhZgS\A,25>IE>XH+dPQQeV35!J/'pndduoRE]@8R&p"26(u\>oTr86T.?bg?S5O:Fe[2iY?#D'.Xa(2 + p>%lUS(FP9kJ?+>*3D^tm,[/!LYO=D^Ghe1k]HZ,?B8Hmf^!^-S\Fi6_9d"s#`No+?%(sDH + ImVrf=c7q>^Y-&?aAGVq-*dtT?k^[r'&GpE;5bQH$&bHDcq>WQc8kD@)=6)-=T<=1bN<+-< + )m86`+rhBLF237VY>gZqbGV-JVX>`MWQ>E(22hAo^A=1i@D*-WG83a/,7'A.71=Wcc9="3)0MF8:a"g>p1s\!Q1K;sa9s\s!3)28ok;lA + E[-p+Q1Xu._:UB]n5Yiplin,dHWhcGhEbg'\3&3MU,+^P-i?4]=q:cs]ES8k^SIrEW)OuqX + `,O5'ohKC)-Il.59$<`*49't/*`B + GEFf:MLTPYt&UE4Y#@3E$ + j82:O-gX3h8AEo"3f;h[`i1ms_K*C8p0CEtqR7ZhA_0I/^tj&%pNQprfo%%.j`Nsk6M21)D + AYcbIK$3Q:0`J*2>UT(`R[]=(PsU?J#&S6J.M]>#ba]lK^?NZ1>-?@5K?c&<[b_d\-q==[l7bG^CCW\iL( + PR2A;Y.>ab*"[g)PJl)^WORO;37X.E96W?U7J[RF*gO^Ctq;4.9r:!qNH<"Hhi[WOG2:Zs% + N"+%AU+rIud0S*9Xf^]cGFG[7b@G>nAZ<(`:c\bFW + ")6<6gT4t"HO>=p.7h3pa65I\I>37@"PW^185HpLiS5H8i]*bAJD"n!*D@C&?mNOfV9;DTQ + H4akm-1#/ftY0.)MdV$759fA9.r`9K@q`Ac0S"'(T@%WL5@b`%[Vkar-EE[0B'ud2c[a?B. + k;.Yd^i&u/tM#Q^hQ-T%`l&s@@^H.=uhAFH(1`.\4'lne`NKc6/*Cm,ID+[rp$'N2kW%>!4 + lLIj]Y'1,pIkX.-PeB3JD,%&Pt-sBb*(S_[T\$CbuqY=B5I75ZkeDD>!#;iD)J6!(8f + V"'a@n\H!Mp)hA;D&(&I"J'`$>7^7(;Q[Z2l'7+\(nIrlgfLVBllRNeG^7nmh3gj9&G1N + -A8Ua(SXkYNSu8jC2O9UofE=bN#.(bAe2_u3,o8?:ca=2HQFDjA^)P[;W2+-KbG5edin[)m + /`kk`M&CJ&tMe<]figS!CnDX,!/;6mh%nADui5m&1<_3,Cpt-%+/N;L/Us0D1SlJVR_n2DG + +aS^\uQV/X55FS7;_S046lb&<=OIu8p/iIpEG\5W^3l2&^<@,6uZ%)GL% + /i4_M3ASR!b7;JrVZ]$u2ph3'jCNnN2IGBWFPN)dBdXh7*VmbD;6CKB`^WO7G0+"G\*=NBB + 2$VRcVJm^/*_PLXA@^=i;FHRpjYr]s$.D&,".6R#9i_Fj:-s,p,)3ii/g3` + 0aoYS&E=cu`h,QLAVXr9%OompD/Htj<+o\7Ti4N0fV+*Z7qd>e@u)+\`e1Pi.*hq4Qlq4TW + $93ZgDV;N_4ru_WFD;LdK=YCU:@NF`l)/@nV%Uq1e/FG$Z1kOIPmhOg4nXB869fYY=E9p*f + u>.O2sLM/2S.d%rqoUK'#L[MT6t&StkEU,4"haC!Cq41_Sf@>QrM#0L6h/)10,lDXN@e3]B + DDo?_.Rnm3Z:aPn4-b775U^I,H_[%^i=X`_1&e`Kog2VW]t:oIm6LKO$eK,eb=l!QOSrojf + "Hu9%+(,^W<5Ns3OUA(gOhY[%qn/_(!!A&O)KJ:p_@T[H$Th[Pb34Kr;OCSiPKt2sD;1lQF + &9]7_fF!sO`#WgM;f>3l.As*:*iIP7^KKlND&gL9_\.:aC$g>V,/g)9h"uRo:#S4XgX*L"6 + 1(O4F6aY_,:IYm!;ngq!&VP02sG`+!&t+k+W>s.0aqK6.W1:kX#uL;EcH4GC@u*8&iZ*,A) + CH#>_,2Gn``Q&U4s6-*(/CiBLY\,M;B^5ZOp-,nPPPX`10uM1LPAGTurUV8$#;m$3J3[Gbd + Iua:3]a$nKuGe.'4S%`%]I_#t=c'G8/,'#?C<_*f!R;3hVH'T4,%)2mF^!nnNmj"7g3Ot"o + qX$[K`g(`dIPm^(e!P]T5/=n@aQ'#4V_%*j"?5;u:UYuEC./9)gAYIX1L#"C+QF=(D239&Xp + J)5oZB"g)P)RXn_0L4+;(X + 42tKP)5ueY.MXo,!G6aL7L0/PM(fCS%&;0al8SV3*cp`\^6A:MgcI\MZ9U7kM[YuII6BRi[-26\_NdKXbk$=B#M"70UbQ"[hB5:j3-mG))UJ4PZLLjPdPki4Tb[^ + $Pfs)r&^c!i`!s4XC1><(dY:O3fH]:k]1['a$V+8jpW*%=J7(45`1-fqF'q.JY7pJ8pmLEKFp`'D6-(;oobK]!^PLfac3@YPG#9EZcY$:2ED?-Go/3F*cU&k!7fZ]Ak.Q5fUb$i- + R.XdZ>!#"lEF9"Gb+]-@BiO\ll`EPRhjBa6(_X:ZKcoPH-AD\>E!/5p9rE214AW/As*5e+< + 44U+i]3n(Jue&=GueNMCC51B0he-/+$1=hAJ9a]Q@_3;1(q9bFX,*\Q`'Z>8/fhS[P7N(CY$nZ>E+9rG#d0DG/ti$3amCr9;mf:NQC2KR\SRSbDDdUO8D3gH[#V>aq + _,T@_g"t1\3u&g8qKlY"JV_iFV[@KA_)!9eq&Ll\-uE*D1n8d#67[(0QVt68I(?"#3&iFZ4 + RPPd%N4a7%F]'76:qsN$]65e3Xqto,'n*7MLZ&Or-`DfPpnt]MAl3=6O:LTM^GW@YQp'RZq + $FZ9#t\L9bmsR^4Gt6Rp(i@R&9r;QsoU:i&At<3+soS.l#:X#(&C2*aQG1M[pcKPh$J#+Y# + bP#QGA,lCf?Z:?`n1W;gP,=':h9/J*=1cTV79R;i3:rg@jXs)a\P2i%=,R0[G_HC+TgJ,P1 + ?ks>`%qu'*b3RSlAhNB;[;#6M$2'VKDjLS`+1#2gBmCfeO@]^DKQp2lBK_(Qn4rY+gUX&85 + \U<#;$PrW4IC1]XdM*_RSVJOfrQ)a)+Fk0BEnN]KRdA6+=/BCV-C53!>n%ULcL/[Cka?Q#8 + O"+j*jM@.8'qb9,MP>.P26(igq^.#.1eJ>dh.V\!2"cHkVTi'a0p.VIO1EedOr:RZfh + 4*1]@\;&q,]=!,qV>4N]%H-pTiO4!d,LT[']ZnRS- + PiXL870>5]R#N*nFpZep2mu^hj5l-&-ADKXKeNXW61p#Ms-#,V/F`-9\n9q;^Q\\Np?r487 + /ooU_c@.qo?kH!RJ-Z^&14OV&3<1]KpI331sji1Qee.GY+%#p+27,o&NSh@&"K`S*FVG"!>FYf6Jh7L3Q?]"+*+1YBNGX1Bpur_!tt^FS\(ZS3@@&G#OoL$+63V<5K:ljkh + @XV"UB#g*L4CKOQtum:>)*Ykd3rq,Zua$ATuS^WmPa.'S/V^3Ba52n]4a5/BQ.=gC]MHgD4 + .,e5\L5'9a8+,"t4DuGu08@7bo>nh(sRS&Jhko]a!5Ch.eP]Z\5BhCa3"!lN*3?uf0*g;ZFW*d(R5tjE`^GD0+!<8,AlRoil"dSIKc0kganXf>OnHTW59()3 + s'!C[82+U^N?rmI":5-J."@-CGTK?M8g@!9+nZ[R?(haf^CT$0/#;,q1?ujAljPdtql^oHn + <=a+6/k7?:LTkU?=BYobF^0`C"(^ZAWc9C6*+8ddo)CE7pG)7E1'lg$L3O<.+nCg6+@] + 6J;uk[FcV?3[WW-E[?I:F>D!*T?.C@XgZ%r[=Wk6$,%QP+;TX(sg#MG,]<]F@SmoQ612qP% + (KK@V`,)eh&$QE9jg%&ldMXS,*le-bmBp5KIJ,2(In1/"=4'L>@p=i,G7B!g1;=ZX&e + \Z71<8AA'HM=mqpW%[Uf7!DS%HZm,$SI_MesC0JTl+2L + 0r,fTfcTPE*4/l)<4Po\9hj.$C/jtA<=E%M,_L6]$(dkVT"+U5X&:Rp15 + FLDemr#B45CF_(cadk>e.Cn&mSO!VdKi(aJEs%hc.;>[e42<43eoS"fFKZ;J00<9?.JN"e:$8\-gL[gHVUV754ToFo!>mp'0 + /J)j15M-*#+-h_jo#DuU^'.k#,l[!?uH9hD9c763h>^uX;26MKL)1_2l!5)o5M:49W%-d>0 + V/b(QLkGZ+a44[`8Z^Y[I+N"ru=eR9Zap0:,@DZT3]eAMouPk>Pu;i=sd1Xdb;nl]R5tE]E + ;Y.V"4'i2BUZoeuJC]2rlNCJDD#C:r6(2CqRL-'bTFC>)jW[[jk^d^V,O9adfJT6FpS=gth + NpAE+L19aHB,9mYQA)^VC[OsKY`5jY'9IuqD/XKjW=8B8tFaKb-0/Q>8,3qq5e7r&'[D'%c + YLkFM922/,D7s>/L9WD-a;_*FQ&W.&Z: + FA=ThiH%DoQ`T+Ip],,n+V3X^H[M%A0sT!apFB]h-P%4`S&=/R)_4%BUra$\9AmI60*85LU + 8?6_N:iGHc$E=2%@Hblobls0XO7\S]?TPC`K@0KP[3U=dX;oV>87uQ=gcW;k(Xu66?g%\55 + J[@$'ei5-c^#,Cm?A>P'j)Sb*%F[.E_4_Yq=-sm'A&@,I*mW&>l+d9,fUr]L%q + I)*VN"jiOHE)J9Z@&tZ>?5nG+Om/9/q'FF@sd$-*k6dZT&ogR8mD&l-p'TFD1+*=V:9@FU" + 2,2Pam6+7ooUSEJeLsV+s'=6Amcm6,/g(Ce?'gm9V;F51s3X)(Wi/H\4)n?N[\*7 + L==8K=QlN,Xr1^%m.b\,HP]i0X]XJ`-;U&V]1GDA.faQglO7)eCFdBbr%?P[g(s3u2<\m"6 + ]p1CV_lcSOr(<*hDLBm.s;)3/65A^)Q$cnl0qI*J#E^.-3]Cj&"[Y_GdNAZo]\mUr[lMVPV + .M`IlD)[c,BXX^4=U8Ao&;9!YC#bbL>7%A#F=FrmY@\+K25"MrBo>( + `PH1/[U02Jn=2-@_sn^B'uf^5D]Qmh*02Dfnslm8bOZ_"29k`_mlf"h*=kTdoURX0e+F_W< + Rpc5DdCug*KOdan5`ls=54M7)+36l`$U0*a/7pCD?l3WGTHcYe97CSDTm6>LJGd/lj##$Xs + uKT[AtjZ8ZK76\m#$J),*+4TJIo!Fd6PJBl;T>Xh/i:3=^=5qiq-&9Dt*IksYQj$iq%2$r9 + ,c3s!:\#nq8/9gI)^"2r+iJjNcN6!,]Bi5??E!anET&M+U8n<=$67M7E=9#Ml0.>8>9'>iC + %$__)a@DdIb!JkCn#2#+AOQ6l\E@(qLn/iQX8.ico7Phu=-A__iAdK#J]toNS'k)o:N,^m? + LG7=HaDlF%e_6tQU?GTphD.Q#B'j(rBoPum2Fc?S5GL!Z>q0\_42h2U:oOYD/^SdU2i9[%E + :4EKoPjpTr\Tf/>WtDd4UgiX.L'U=)/U)+(oBK,$u(fe;Ji'udj6@&"%"_S,\+2K#l,B!IP + 2p+V6n\/1n'%pC5%1SQ,0n/`p6cCAMLAO=/J#*"4(fdXld\/f^_OZa*=ct5!0X5h@PoV:^a + Zt/\.sY(MmA!#]\a`M"]XLo5JsG+gqmDQSDfnd<];kG!0l5Ii!0j9G:6JLY + 8gFP$PR=,r=+MLZ1F,[L9CaR$;^uQVa^>YiUHf=pdA.(:#h,]H8.138U'&<82lTd/qUYCK-/r_J`b-mI?g, + X^WO5d#qqBe1C[HhY[Y]m%m\":\gRU;eeWi3B+/d)q';YgA*ekX=CWoYCfAnCrh7^;l#3"?AO)f,Dc+3RbtH6F"ScG[%=+s=-m&jdFp.IP>$u.RK + ct]9l1!*VYoGD]2C+="8NZ"lr&^)9ge-#TV?B>:Jk:K!lWn4UMrkp5o;E&i2dP)_I,kAi@G + ^ui04??(SYRqMkN0j8a.6J'>o'5%jC""LbTem7(AQs5u>W8!4T'3d>0$`LL15f*)6XaX=f/ + Sj;R%Ni=>gRL6NH;N!jth7Qdm1eW0(d?1'WFNSq?HjE4;ReIBdNlSH#oi,1ou\-bN\3GqC; + `"^Sf%.LhT7W?*Z__Xhbd?.f&#*b>S'#JCO6=Ysi+-_qUABKN)dWL+_Br/B/9-V=)[CKAj2 + /TK1H$n9!.q`]NY%,,8N]^o`FkK@J$DF'NU(jJ?qFXaba(53/,KU+Ynb'FT=iU#F0)oc.#% + 0SQWn49*fd`h5>!:<^>I\Zu0AJt6nM;T>%0J;i:Vb`T_h^3&FLjM=<9+[Y9C=QS`^k&, + !eJLpof6DRF'UsLM$Tcb!kFq;E2no-S,:ccLu5m-U?W<&Rsi@Gn`6l8X?CZ:F(b-Gf7ROR6 + hf.D$E$#hBrZBiM3C<:.(L8ZZ?d!NXZ/W/=%BjPMNRP)UG0+-t^b3gq"qIo'5o:_=$kpdP; + -M7`L1+>?6_1I(e0P'-iZ%c:jVI28S-F/8/2-t>(3\+gN- + gp%-!>$sh&9/!fE;UFo'GKG7$/lR7i%PZ+@0=n'">M8,;(E:L.2+Ef'2^ItEBOu?5nfAL'C + eR,0h=k93iC1s)Si'\6+"U>]F`:l`W:0"XCfJJn3g^Oa"_hlXPXiaJ;^MF(ibAU&VCqjONX + ?_DaD5\Ja+*r]H/5,)BmfJ@>GpI"r?1?%A?`@+KQ6M\d.1:)\L"-!CRe+efP;_0^2Ijl!_:>?re3i?k&g((kH*%ml7LZP4SrDP;'MNUFO^S9Gd"1B+6iLZ>Cr57ak((1 + G)?=:J;Ud<&#_WI#1fU.9\?"BItfN/-`1k!ihT!\g^F(*:89D:PKmlMKpY:DJta\M4k`*Hr + '",g'7ITW6!\Pfe"u0/TTMrZJ@,'i@ZnI/op2/ZL':8nLlZiFR5`\V4`$$*;[qHhZ;ru:V* + &cgmJJoH,e(!7`>5aQC[$.HU>$0@`3"O^b/4J05&@u@>phR/8'aj1s7.&%Q$*p5S_R,[1ND + 4@8-`>9`WI:j_,#&:1334*2@0ik,rZU%VcXX3YNY^(I9Y]TsPH*7jE)?,?'uoPFFRE%-biJ + l3MLs8:Ki[M!E'ElOLMlY/KZmJ.^^R4WhuSfH[1&V7%uj5d+(SdOF-)<#$A!L + jEo;$ufuoaaP\ZqQZT7$@">eVR$T6MtOEQI[8=.3Y]BVPu#=Z>2+:.F!)cc$XhMU3Z!\H"k + FrmueiTQTh;=RUm2WA\UHGcTnJ6;($ZW:CT"A%7/$o0di3&8W&mJU>SVs.*AnGc42>gCn?7 + (T#0GBBd,6$bV?$B1F@5'j&%YNBRXHC3=\76'3q66:ngJbU97nd;g&f[.fnT\!\bl4O=6p? + iXoTe"CUd;Zn3!"0iI[%'Z8?'5n4fA#So_9)F:fIk.oo4YtV1+X,pruMS:ReDB#UqQGL$8M + Td"nIOqMJ#V^q6Y_n_8NsPi^#uHrpYafDOq$JVB)/uKA0Wam1/<]Hj-)O0?d).2)2%4(p1h + LM3lQS3JeeZ??PL=[I%;g71`lu']Dq"i@l%?FqA + )<49;!OFYcs^%q.@*@S.bq2'js39_5%+,_PY-B(.#rFBZht:aW:`J*_ul'b^gShmN04.* + FZH!+hAIug_o'3*MJW._:BH3!ZaRS%\_;e!72_eA.)hi'nDeoThG_8aqULp?Qb.E_,^kNc5 + "-j*8t?7+j_!T^`=S9%,oVN@,GogllBMd&#%PkJI!9iO:e2?).=p6_420IhARP**T="56/i + i7F<4l5)8YSYTth`.l5k+sBF)9nO.d\?TUPD/*mA[A60K?*a;s6B('2o%!;IP-;@)6u'#6F + e(X4u7csX]*'-Ksf@9kmIk#Y[]%,n@p;%1=K-8J9VB)-?cX;Ial7d + BB_T8&qUtkLIK)W7KW"C_Vj!WVY9#^.nst4D0@@&HC^%GfLF6?O,B>PhJP*Z8J8\hBqm41_ + T@t`=sj>f')..UEJHMrLNn*"\:'/-JVZ:>Lj4c4XfX7TaPJ3qhIQpaZNg;+(jKu5*U1a?k0 + ZjOJDbBOi3^_"1sdb6g+8#liM,P$Drra1828C=:4kT=:,%+VA:?mhT]'(J+]C7u(k8ar2H9 + mSL_,,9D`8"\2bH(hq$iGRC(k#2&ts1Ui5Q]:.KpX73S>jEGqD%_Uk]4tl6q,HBSbrPP3V8 + %40H%Sm_M#LaZR@WPsK@$^iu':Fr#WI#Jkd$J?g'/F:nVm)ZbdLU#PJ.SB$i_<34VB"iET. + OdS$'m^/gIKb3aW6IpM3Q5_Z")i8Yd(dD!9b1G98ClO0pZEo5q)21B;ff + 312P/-K%RS^2^;b4lq3LGhmgtm_\q[N"_EshG:[,7k,D,((Ic%,`3_#4gT*!W_-,/F/t^+H + PJ)9s?YL-Ceg0/Q$aIA9tp%Q68k3]U")&gVugRYmeTr + e-f\bi;94/oggXh-Zq]5^54+eSre%L+'f)PDft6*L]*64BPY)kcO4_8-mQ`Y/B)*5PSl@FH + !cfGK1H*Q/X4_3Q%VhAO/EG9OgE$tp[tO<,RP*;8_b_BL$ea;oio*5U>M@K[a'g)_F$*Ys\ + XO`eS>6,YlR*F[kIJZKiBiYWiC*MHRQJ^b>0jq&\N*+7J8+he!ko9hB(Y>*5S3e_4hVEeeg[O*/]))p$ZFg3f*?h9FaRdG6t9h0mu4^"qNCiSH`Z/_lXm=Hl_:SXL&tqec%On&%fI + (jZD.V?a4sP9m)ef.s)69gPmUJYc)pH5SbmA5DVLDCdW3hl+G5s1qrhUReB%7Xp3kJ-Z68F + U*um/eaKX2`#T=*aF:W#SS[E'&dF9!8&2U.L(;ebeAAcF.ndRW/%oF3r)!%3C>lR7dG-.lk + @2t=]7HY=d@ZKQ9]l`JRB(?L$ITL'u:pt6$a5qg)4=B'&gD?@=0*SfGs^H5J,DA!`ArWq(`jHl5#P,r0CtUZM1FIK"_g\*NS]s$*^iq#N_L*!67-n2 + (dW!i*;t&eF,TAj[I\&KGJA,cRRn'8DV*;AqpU;J?Yq;5??C6buN3%e3B^;]8$!?9&^SLuT + B`"W=$n'Lmo4;&TOF6>-fs!MD7_nhcFa'?5RD:7;8)Sh:['J^XZou1uX+8+`B? + WZq)K4.l*-&Z*]<^i3U=_I,/B3!JfU4$3gV5Sdqp;O7`>B\ulGFXG6VGeK]^s[4dTjbf1!r + #^JFksN\hg&MZV;lmYGI@$i?['3\I:(Ufs++Q:_(6%cKa;@5'nUSL1`^0G#'UsGl/HQH#FW + kO*ljDqHfOo+6]kT@-;0P5q*n;JAU.l^O_+?C5^R*UU"jXZ-NSsG)FYh#@M!0?9g@C&%HK[#l8 + p?Kr1)ohK_D`b4T\aFg!grcol.j;:)$nDIp0.W4:Zn+i/ZDjh#ifKk4$.<^4F(\!(FS"[&6 + ?[H`Db>6GkD)r[ok0UpH1g?u@X@`nRI-)?QHcUJg3/>IBA&QHPMoRY2iH^@r^W16H%'CqkB + [&^UZm)RVlT-0rl;A4H[b.imsG'>_sr,(-d2+/s$tQ2I=GnapO3'sj8".RVsF4^hB2@Ih2eIS5$$\sG-9&8]+O8ma.'1P:8E+qES`dMPG9.YKQXiqdcX+fRiKB%SJ2-_2p,9".`HA + 3(4tCL9DO8rAjNmb2XkDal)Q]$i7X#uDCl=UZ8_p$igc-5F+mK=3p1`c6ubX.71;g'&N!;% + nN@RSjAUqfDCZ$lYnfaO>Dn)B01GZP(h3DJMihOL$'>-@`4/>Wi_U)l.,`TbP>dJrdQ,?Mg + =8Ta(`QpX%S+l*28kk(O?e2-M',;3_&1K2+IYb$@@/<+:rj7L[RpVKLa0`hR0E`n7tXAq%0 + b[M#4d]lMh)iXjZkmCJi"QAd!RAI?p^/F#Z_5HM_2doW"EG^?3GgM,/R`k'-WUP*Ep1$J"-M"bJB#&?;es#+0;]pk4/D^r_u3+ircSAscG] + @kF-l;G$>2)SUf\s5:QoDa=p%?WnYT^6&c%&^XMrB(; + jnQY/1C@P03K"aaUrYI!MabW1DJfp-c^GM9J74^h2$3-q)WP4ANM3e9-K*[sZ[=<4l,"1u' + ]U0cP]Gat697V5l()n"Gu#9RM"m;M@XM%1E@rIt6B"g"E:P=ZQpVG&":9i\Rqo-;WlJ69*b + 5+lOE't\a97JUN;uj!L57r(@n7o&0r,SE)KN08?F3(o(2A%+2 + .5#+a8TpL&_CYcIA%[iQI"937qs[(`$W;YaTr!==TtMK012])) + 'V1eNBpH)#2m*7,CEo10ubu(R-#;3cub7ul8qK9oc=4*HOhjd4iaf"RX>p(1EksDa/JDGS( + MNf0uu.pBYb_lBNgGdW[`dCckGHHAVi.o0n_gW(]j0"L,$h"_AM8G+HL=*DIKquQS2^_?kE + k<:_X,l]b+a^BFI5JG4+m8/qql16G)>HgI2\kc4^(_`o>dCqfHbi=/tpJK@E@(JhiR(a1-1 + #i_0`<0*TG+dUB.WglYJa'X4EDE0\4f@_affMZ8bMC+hGDesp]FB%e + p!8QdSi=]7rupHNQ]q!3=3Gl=1$3l8S+E4qc[#OVk^=)[nLE^.c6rPXf$s.uojS$RVdELG? + !`+_J^N$s0j/jk2^bF1UcCc=<_E6rk_6sff0^&%>U+,BfET#V6XL;]c%.hcr8bd@X'sS-RS'Be:*egqV$^-I10(/S==&Q&V5dfIdU/jI@"Mo"K`FOCGYSB4.D[PKKQ&P + tJ5V%#@Kkso#19RrG]kHm^mU%FW[`1h\okjSD'r""64>#()K3Ia*l6NFR;nV1Z[o$Df:Y^e + L6uq8E'jf5E<4-M#.%>A8.Pg\B9@.HL,`d$Zf;G1p',"$##b9#ZMV4JN5L$&%$HU>87SUaY + c.H(MqG\#Yd]NmZASF[$F$HKU!!cUQNnJ?T]-D'_@%K]]cBDh*F[P]n9QPu)lB3- + *"5%"ugibPsG-t1'JC$F`J1lTFCP@RTLAK^Xg>4F@h_"]_#6Nj:)WD*-j"h2Vcf*5&Z"u'TlE*e9$:5,WXKBQ6g?nI&AAqA#jL2[NQ`4Jbq]$"j5!bb73gO][IFfIcJ27(`'Uem>_e#*R\QE+DZfR!jrGQt*GLG2\Z1]L6drm,rkc + ]%ToPk[.U9*""k0K6ZS%*RC=,^W4Kqf;9Q(=W6; + -U_RfT($rW,j;8koOZAXXIM`a4]n>sP>Vi"_GLB*p&e3ohccQ*&6*?im"J9X0:8V`b-]E-b + (q3+4pD7h^l]L;2$*B$i52cG,a;iNe9SN9]O4&`gT;p@I(h*+d%pWhuP+5BbZ>Z6s2X$ou7 + Vj[`OOEdF?=J2)0`M(VsZ-BnL["f!rMGb&oWY85hZ\s(?$fJiEBZH(5@uT^RYUlEIlG(>Ph + _:k'^:<>iaA\fg,U-,5,'#(UN,1^6.mH,f"Om+r*F`%WoE=)HKZI8Kd"2D)*Di:7GRB5_;eIP#=Q><5>e@5f-[>dTf&s?GQ,?5n"@A6kfd>7"Vgn!jG + 9"5V6'i/HgSkK()P=\2a_+<.oL'"lN?S!+?2lDEWlN\3*GNbT0#<*^V`C=Nuh-g3-T'gmLc + K_IP#\H3=G:.>06&!Ph.@eE2i@&VKRd"7T:pLh2T(V3lhYJ`E!bVk24 + Qc#pK6bOZP$BU2"Oc,(:$TPCQ:omQ6D7ab*MB+.&bIP6acF^X6]?1F&Jd4C)0K]&U_I;3OX + YU`olmE5/Z72&3$-Eed.^fQ8[g'J(#* + ?Aq&AZg=@j-*A^2VP$=P1?ThCK5uX=39V..573`P3$7,+W)\ghs4!bJt(E/DP7KUi:sZ7YM + ZsKBQZ;KpLQ]kECE)"3>-WcqXeL;2,XMLB)>7k]',hhae0,8/`&B,3$ZtDQZ.h84[U=>33t + qX2oVN8;cWPV=IJ?MdJP)8B(6+Us1OQNirT2Y9Q4a9[ZKp1lOg[\mkS7UAi9=?lQua%4n)) + 1nQHe>Y5Y5Krh#QI&!p1Up-m-"a*q$A>c.AW.drL0aR0-%'7VF%q,AJ9%t$j#YkO?<.e"=" + gAnu#\$HGhHg*t@=B)"o$`FtB@^Zb_sO9pJsYTA*YDNBg87rg&aU*QWR + 4J9$EY@t[s(K^]c.PmZV!B]UjGANj+*2HFBapBP*cG,R\Ru;HR^&#cak$b + )/4&M>>APZ48B7ROP;M*@s^9I4khgTNajm;^JjmeMH9;B3U + &A;S*@3]ES4DNCD,CnClE3=Oi,PYglmrOR1N%;EjF8e^DK"9 + %AguQVHIoiXfCaVq!&!VG7Kb\)m!(4;5XPW#ao`bQKK(fAW(Vd,;[#]bL!X-0G + \2\\A-WB[LAJ#bc&#Q!1>)^3LiaSj\lk7&3IS*Jrip8#HO#.UBW)*s+=^MC0q(cP0=r+dkc)73.@W<,pC+Pt7G + UObZ,`LF/4?"5euOi/5AbeG-LeLh7&E[+-kSTnq,\Jf>/jT0BP'l53JiSQgd^CEe15l?-XP + C_q8j2fjAiha][Z[IpkZNQCefZcbKfmO`=QT&UQY?eMm0UT$;Ns2 + i3ML#W6=4J4B!>KRlh"f.msP6eC_V-oJq@&BW*[&Dp,Wp2#%@."Q>mo"9];a0U\@2SJ:\57 + lU0nb'^!GF\%52p^g-?ZdEP@tM[k\("t7a)-5L8[.();-uKDVC_/*e'1kDit"Cb2DaAgmIrc;=s%l8UA]:SGB\O\L%&K)+AW0.@ftpm + L.Ep"Y[rC46j@+O96XV8+'eRg>^/JcGY`pGGT_ZqC^0*F#F2]b,,8f>!$2"q4A#99#"o_U" + I?HVQJ6A2TQ(+E%dP0DN86#p12kQqIWoipO6cOT;jQqm:O>5B%<_,!5CTqb94+N + naP-`m3FB\`f`-&?5k'H4'9%+T5n+V_T:.Y0*8a)5NHk4&@+2WCY_gF\eHr%_/?cH%4M3_b + 2LY]W4?O#"TNC&/@g^Qc/FBP.D_]8!u.n"gk'O:q8?3\nd#.;OQOCnsr`'u.agq[=f:gV.? + k`)/;Kq)cc'#EM)l42kAnZJ\&%:+Y]*299?C;-_I?fl+59qn:W"Q2n+^eBA?B`tXYq1n>^T + !MJ'5jMEdlXar!F`-Mu#4%Onl_SUe[;tTPF6h@ulfE9Tolf[*GO-&hVA'VY//,iWI#%i4&l + qUK$W'aL5n71S(K$!0$p;C_6Q%)&6ms-1OX*;eabXKgqn;W`!T]=rh4_=eOAQY1JH_';VUd=]75Co6EtgP]N6[3g:;g4Znq7ff;6[;u&47P8"lf6Io?,ZFA3B5OsKOK5aNU9j(3m_nhuqP:!7i? + HFPY^?=*Jgkf@hea&]4fMXS_jdVQ!O_!,87@D%+TdWOMnZ'UlUh`&Q7nU.g*draA;9s\Zi2GJNddBr$g&7T'G\V=O$d`I + )K@R^8F/XOHE0OA6el#p?BdeRrPG>3+-MBNaFq.:cQBdlCf;k!aH-1mOnLEAa"mMdQU*>#A5uhs>I7CW + ?IcLO4+>kmD;1*8\aJ49!Y>m>J;?`7@lZkjO_'rbE1;aG$W+ij + !S0'h<`0h2q*#mhhHJtInF!,Rt*:HUGgeJ``$:7rZ@ + coqYi^ol26"fQs;usJtutsPX. + uCKOodg.j-^o.Z-nXBq!:J+aEJ[[hM[M*.RamWX[#bR#b_ABK89",6%gfnV<#=Vdf*WJ)20 + d,,fQ/"*b7s4PqqK4c'd$EBMBA@2!1UaRaYSZ9prCIk2b?fdsc72)9"Su,itQFSod_qeN2l + (m6[!#"[Spd"(:[A51#-!'SK.C;*la$Lq+?pUhM8T[%^'CSkkA='T`q=iE)ur,UB5#]2tHs + 8g[Zo)Y6MfQt$aG$sAPt`]Pl-`?f&Hfp"83@*F=O2J5_VTSeJaS6BE#:83G(M?,q:e&\+VF + Snn3d5WK<,t1W&*i)c(Pu9(`9rOWngsR9 + 83A3nkT&YT!D"6!W4!Yq116Eio^>[KEP[Y>1!g@5q]25$G`P3A$e%'d=)lepYR"kpH6<0gYW@3S:C`b#E@HOlZnC;Jf\j'p,dNRDGVui7o8 + d1hb_Ba^Tl#N!n?08O^ND(9FH;ZJf:d)8N>N%0`L`eEEH;fUo(L$DT!Q"JGEsLJf&*=DIL; + +iKb=#pKd^JT0s2hql%r>5o+L"[W@p\BkRl>\XS=FuDh*gK7ZEBIl$![,X6,=\_%p/E\1b` + mWGmgO;#j4iOq^k2>%LT6+tAcaa7sj]uA!bINBa**hn\NfK;=0GdpZ,Mk14EWLljGETlmp5 + `\:7Zc%V6)>G6-dp569JXjM9,XD*B%4A^-FS;kBo>>lFNpVh[[,K5)cu+UT9F:bS]HL08.e + 11Mq:@d^>lUC;!&u%>J3OX@825tkWZd(Pf+fM9ClHcB[W^6Vg`)/ + q3G%(0EB<`5UIieP2aZ_6*r'i[UDCG>nA(TAYr3HiLiqoFT:^FiJ + a.YrX7b>MfQuFbld\fE%`:OV#r/S*7@;eK*$UUjNZhpN7hE + QN6,FP6JiVq6!+^nE`Jdr;Wf9B+>6Va#bY3(F)KX3B`CT83cHtH>nh[EEmNY=].1$+^G4&d + t`oBhgqC + t/=-YXB#/c6A"YM>068kT3=t4D@V]KPX-]FJ.]Bac-9\n.&"LP+kCXU5VoU$8+q\1eON:dA + @nYb!9H2-D\XPV'O8Yl<*'NG%Zgr:'t[qD+:E+maS@2VlG3E9i.h6;Q4Dr(+P4WjSGe^Jed + 6&CnWNh@D!5G4T99YrQ(A-MTDnLd]L[IY%m+&$Lk5kpohj@YcA,99,+Ys*jU\PZR[f;b<3. + I7^WV#[B!K=lXI;3b9l\ZsGNk$[+\Z;=5/#KodYe*1#+CN8% + +U9.+"q1M4&CM=*^pjp6'dJZ^!/DAhEO'g[p$!?N$8YR + SE>)Bb=Y+d`F@r"sr>%^NPoTILG&OXpNp![-\EElo892T'HDIR$:3_4V6g7N-hs)$-oAmGo + u5-5nK(FO&>%NVMo(BZAL*25b-Tcqqc6Rg"Ei?R'r)A_K_0YXq6E=V,O4n8p^1GWP>A4)%5 + J"2^<+@$Qh6*besf60sn2\fWA9JT%Xcn>?XJVEkdO6`e'k-k;jnNh:%^4_amXkuA83_[lls + 7nGm7ar!Z[ME,4/>6L="$7mr#Uc(S,UP,f`jL1j25\*_Q(.M]kVK?J5L1q!0C+=8RZ!:)0A + 5FF)eW!a/`I+D."FWbWm(3i"Vlb'VU$r+f + `7NX[5u76>iaeNO_C],2H3W6^0Z"1d==V6?1L'g@Ref1be4VZuW"mNU>m1:;I!pptmW.")M + A+JW=.#&:"I$9.gQ\Upe\u38m9>iIo(DmN?(Ob0rs`AnGdP/NT`jG\?;O'S_EWVNV_6[cEH + F'M<::eA"Y2Uu[P=9*%X!+\3=*p_+f]$=O\dTMc;oOM_b=5W%^jFR)^)!Wq&,D!+:iFhood%E + 'Wj3Id[MtH;=D,cQ)Fm)"@e^o#$^`:=Zs\Q+p)e_ct[1>RsR2%nm5E*#bL@'u@HsJXd^rc5f + 4)b-6albO79O9_e+Cq'Z[Y`'F-nrAEraRrI58nD]"8h)>c#T2=l&+ljYP^`:0q$B^*Qd>Jj + -1Lg&8@hq"5eiRCX3/1io"6%04@!WeY?412[(I7O2@>kj#`Yp&H)hFfKC)WeBFBB!Dg8>kS + V^.7blo9)k;MFDmYUIJ0/ro&p0C3%FB4io[7g*l=`\:/1knaLsH8:Rc2kq@U,b1$/]TT+`# + /cfgdk40fG+hU"7uX)g`,([6BjRuu#g;`E$"3QB<;C\<26WO$o47\OgMg4!E0grOf+2j;'6 + .(iEL.Vaf,o#L,B?j5E>JX6KDc9YDF@EE%^3H3nFJT?l9+S2*U-CnK=[:GME:?h3d,QUj-n + >+T/i[$?3>R6N=n#\VDJBN&tt"?iI'T24OTIS9T_GL-=mlC4r"PkBWX\`VP9sZbbp-e(GTg + +EGKn;ZDmA4(Eki;TgTRQ1`#%.%DeVKi-64K`FJU0X)X0$;3J$NQE?:+_uHL$\0G%KHj-G,O::6MjGNDS + @W_@]$^_nEXO#\'-YdE,'#%U&hjZU2hiJ"$39u:Qtd7aQk,lC#ls`6G^:$2L_#&"Ld=-/!; + 7+/NYFa'%m`em\=*YqqA!Ck&0Y3_JHclTHk51D"sEIP!+Cf$=:YFFN'V%Z!:CWTc5+2f%cM + PiJTM#;lkc,cJT:j6s,^7&U4kQ-^uuZoAe]L3cCo7E[Q0J=L9P\V]f^k)I*pDBT;7G'),<[qZABUmPJr$[!!9 + ]#\&)1@jX*,["`*JN+Ae\9Pu)"c7)?)DJ(RYB=+JpL4i:h5s0$R95[U"qVOrq\E--A9SDD= + `f(Qs7MS2L'#AAj!>$(J4:a#hU:M^WHOfaDL`,,e&HSU6+YjsDp_@5;'DW=Z6(/`,2VjH5> + j^FILG)PE,'.j4lgDW2m`,C:1j#g%!s_eJRpT,Ih3/Oj`c+FJ#mF=TTQuf-n+C)X2.]m4Dc + eV'UcXLEgPfX3PIXna\XT%`FG+/YS%;h%\U0CIon!s1F;Nmo(#a`U@7hAeee;JN$@sB=+Ee + =TQ3:Ul)7hK+)[jA!hnE;(^74.eKeOD&D@o15%O"^h@8n*&`#Jg!*@,K>'0[GIDO"%TDO'a + Yp_@kcm`O8.T\(SjXPK=f1r]r(;Ht!<=`YG/J*+%Ob@QA"*0,jVDjW=I#OgKTiQ@-t0Y0N/93Gr + %/JY/b^l0j-h%E*UI"]Jp"I#"?4j2%hoMbT"oL84b_]QIm2A69Bh5_&O.0m&WX@o9Ya<$+! + ^#('6_iV)DC(eX]pph-\_RA(Fpuet$U-h/CmcF4X]3R*"HX$1/4M5BFB+`0m8e[miKdJfUr + b-d;@Z"0]pM><++P!6JU5^PHVn;1EH)dEHISW^'>Yk/_))Q$WY3*UaS_*Ib0$gUPc^(8XtcHLd + @d[enupH@Y]6hN&[J[H[^&cAZLPZ8Oi"I[`jF.9[;jNDb@_i)#.I\^*V_-+3%l=t1EO%b*r + %f=3'SL#P97;(\hX>udKbI=&KpjM+dcIX3+!l^B81YNLDY/hR!jc-Mhnm.M--,Z^:X[NoG' + R,'d.0#+^+0J=c=34N&\k:\CHR@hi7EkNd0lMq"'P+q(+N(/D1&.Hf>9=()fIO(.#)@_5LP + -h`9F[cBMmS;Z4p3?+**b<1bKq4Tu!L5ANW-P[+h=/:tg8K*HWpCn2ahZRd(00H>-k?6je$ + idG]#5g#T6b5$5f,)'Bo,g47DDl>\5m*TcXHMXUZ%T)E.,.fe)N()3G7WX9+"1ND=c'r.[T + rK.oIBGQE()iC0)=sX[PGq&NXC>$\(R)4"/M*_dcJkrfH!TDDL7NW9n_ZM)WEKCss6ot+&" + j(-W9T>?JcL-hWX4J]aE6po"ru=c\83o@&Vrj6nQ;#Z.2Rt5A!)T>DEf/'7E)2`JUof/8hF + 4_L3m*40l&Z/P?FqqEB0RbkDbo%rddA/;!=-I2'.lHD,TV_g3#-Z;5mO[/]tJ-YuYbnl]p2 + YHGO$o\*TDb23G^h[9Ds`gNC6hE3n?Sp+$;l]30#O)g'4:k%c\di:)_"1J\S1`ADRo%!+WU + cH.k_N6JUY0ZhQp1Q6_Q)aMBi0_C;U1C$aleM"=gh/`*p]@egFMiG%"E44RSGDeWTbCHkV/ + 3&Od1:rXMjXW?K^2g9IWTblnMLN$?:"`GO29fEW4\^/2j$Jlom6S.Ir^Idi9u'CS^ + -D0:"S^,'FNk\,[?,#+c$Jc`bIu*5q*3Y"@8,K)[Jb!PT:)t0WGroTn*LIBF"`LU+VXAOS2 + ZmAC5F4QY&gjC8P^P?Pl6]U1G64X:km[7gq4u*_1$7IipID'SdhdOu/3>8MX3f-:t;'SnBg + APbuVrZE]`I9.\#j/i-Dt^Y<:'h;/$[-]_M2O(=E? + .(QdFC#c[.:uqLKh6qYPNF:)8snTe(1dZF$()&0k&`1c6?Z$UAm';"Q"X=/NLsog[\^0AgG + ZII#/_d4<^E0@aYmpr`L4/;R:cQ3E1Eg`hK.O$7H\t;2mLn\*I$'-G$h.arc_`tIKk%)ViD + 74@4.W^H=<_S*d_?D^*ed[(pW[k@jfhcIH.j-q2f,7mRNFNL\=N.ANkbZp/U + .<%59]*Z\hON0)?:)q99A945P8^;=Ff7*./*4seqEc8S[a1uXA)8L,07`gm7)F_cdAebOWM + G'HcnI0B'"0'.>N/%^?.@_\4L2`?mOhscp-OD1/o_hQ_eTJQK6mt]hGSem=9P+^4@9n`fa65uG+:nDZmj(ZSCObYBW<]*+) + duE;dOle@DY2_QLU+ams\lV@gqN+#,Mp7S,$caI+l^1K(5U91a+UV>(m>dhE[io!)+,1I0].KijEW50TS#dB^p_I0#K5s.mJOJc'pR$\I/>-%m9-mA`C>m + ita6\X74(IVYd$oOP/Lk,sP.pum!Jf$;q&C)C$-u6!%X?HnTh-S!#E[R\KB8('JY58`q]m9$+Ft)I>X=)F!$SH%L'rS179U9;('F>k[)+XL#N6nB`/%5#_G]" + m5aWW]\4@-^DVPQ&L`Zcm]bA3^4c1bC."U<&'Xo&[K7NSDDIBFJJ`Kj;&QfGDaKW7m^_R0+ + B6M7]-k>7jn-`Vr*Q?Rj(I3X8$d^pTL(3Me+CmNl9B:X+TZ@2Dg^&[$Q[ZqX)&_'-Cb8IS+ + 3'+U'Z4$ZN5;l#TGPj:oLsS_M4Xh[e/Hpl6'S9G@00r8p"q7:G1'o6O6/rO_8V + &mTKe09j1J)0Ua0(fJO3B%"o1g\Z]&j^ELC+4H/cE+bk5<&n=$]M?0:n7>T;,_]WWg6 + )9SN69Q6JmJWejBT0Cnh^=nKEGdOX;G$sc.HU'5(S\'=$c.!;7F8CWpYEN,Zg6Kfjn6?Iq7 + h.IrSLi`ol"^4>@351.u!9o']ORL&Y&VNDtIb>;]90()Gr9"GD&(/r + hJtQ&"JPmZ0n?NI386pn"&26C`Ii/5W3imaXJGV9:m2H%cJ\^t8_9e#W!F/;-&F(^!OHLB3 + 86?9TW&$o'g@--g_0@!*iDK$onK'&mD+'HFE4S[$ctt-V"JGioVAt-]Gq;6L[tp]/@8+Ch1 + jV"1ku3)0:4B!IVf_9s]lJBol2.J`-5X + D&kn"c+ph!*+WDE!83HLBpcZ#]PJ'*?>D&LBpV*8^\V58q>'5W!#CS,(IN^6,YM3_uOb0ck + ?Wq:][M7KeO\U(fg^2EWqOZ#9[9(OE-'E5_gasKRe)t:eqh(t'_P5c*,dV<^JXdmQ3PiA=c-VIQ,"9;oDeT=P*Z( + =Qmo&(WR@=dqOac3U+/d2WcG!eJuL5oX")i*WrfLU.^H*Q[4?R4X.m0Ub.OeG^FU;>X=D,h + 9#[l`a=O@6XO>?EqTl8fdOe)@X`E#FRb=gCgTAWSXodN6.cRU/jXtlBY+k26b3Z;%mk5ULY + -5;15.6HGJ0X+k_OBoR9 + (7Ij,dT1@$;Y/;$M:In:8)dCGK40RgisJVVrM+s`Hm1m^MUVjL."#C7=U)1>)tF;qd*8ap[ + cYcA+@#`4*QOu/sCTe2H8>KUp7'JW)llf5NcQar8i%T>j(Gj>k'mn4R7VTNc!9XTQ69f1U( + K[Wjui(VVK<7A`YbA^4SCq6F\UQ6NlKa:S0+AE#Q2?]CNJs\u\kT,R"#3"tBtQ\B@"@'G>5"OupY0X(sgRCqLbr[c&/`q& + )+6SVfdV$?L=sX*+^-G=P%NMjV"@I!)q4J&#o;LEp'Sj_iZBDq>/0+$4s@bBkO+ZUt5#T;; + Ir@Jbo"@R`%d&c!SH/H`Z,AWlZ3%/A10"4`Ac4ujb:'QRL`eM<-*h2UKr%LD$R/")*3A>WR + #VJ9bTMl27kYE-#d\eClAjc5CCV)e^N"Y6K3H`3ffB%]H4"K53%Lckmn>QX[kL%$8h3+;o= + 5R'Ib"5;,V-te,C7L")fKX55J?pKEBBEYPC#(T^-ph5C1A\<(ue`EpAU + VtmZYLf>J>7pnhc*Fj[aBM$Q]T\8dUWU6VB0=(k:fR\3j3KjbNkGn7]brZ?/\\nY2n`'K]j + oVVHe5rf5G_;Bgss[)jb,X*ceScOh#5OS-.:S\kZk + (@'cZG';r8:.>6W)SK`G50!(6uM@0;$TL+"U+TO\*`7KuC%"<_9,&3bh^#_T2Vg'+,*:@Nf);_PFK8t&Enab:uYN1#HQ:7)HZ# + @o"`<@=Ud]>^iu#9@i?$shIQ.YdV0=&imAAes?E]jXi`[t&?#aDV:(BSQ:tL.H6;TOm&GmKt',J#[.=4E_b"ML/;pr!,J(FE^cR!j]2po"Bus(J'%%) + ="q'B33aQq?](T``XlquK*bV=!F$iO4G"$sCWfqA\QbY9k#?/i/R2,6BMbI=[^a4O]Q&\AT + 8JkSL?]ILokOdG*#Ebma,L_hX:/IGG;9SS1*-?4A.V$=UJZlA;48b%4W`#+("(KBPdXkZ3B"\WkM.PK?EA6)$s/Kbm=+VTFi*LQ2cJFq$6AHP`P[#:h + eVBXLa:DGn/lM5"(174jIOUq#&fb$\\T_i`)o6/7Zh8uc:J7''L5LlQV83SfUjOA_sOYn'7 + .;k10e5Yk%%Vs9#8bCXn1P'>D8oT(D!"UPiqn2U\d9S+[2"K2gS*(1gs_PeHlW'nS&CrN?; + `i-PjW=6lt,g%Q0b:.PW.@eX3`6KgucRKXU.Ql*.AC;rbX%bp^bu1`>@^40uZqZ.\c'#V3U + :\[U^.lJ[c-jK\>i.)Si%1DW/B$O!m,KZ3j=NLU/S*uqN9;duh9M2@:86^mZK+ctk0DE>:? + (Tbo'T9TnBVa=:EoJ8K_-pfoe3uNYOqRARfMklp]9kR#+.l6W,.3UCuJXu$g=GKh0:o*T(!4&lYu^BT4/\Q3X`/944'E@&#O`KHLM82Aa:VV(A2X"$*2WTSeV-kubdR + U0.nhV.l,XPA*(AjJ[dc::*&j%KPJlJE-lSAYs=%dr_fmN3^p0Vc0oq<"2(_@nou/9rBhWe + 1Rd*"[ilOq%!(\3gf19+B/MH8d-&6"DCVcQpZQ->6FXqKtog(i*Ctk9SM[f"Lqfk5YHh;2[ + %K8K;_P"#X*kA@"](h"r5ks&4_FN@0Hp>"peKZ$>Xe8Fr=>BZdtmuf";2g'lRYg$$F9nQ`"-^k?diU??OS%_F@O + +ok``JHpuDhZ_N0\h0&/o2&%@mk`hfDUKWrKl49BA2/HeD/??PL]fC&Y$mtQ++&D1XskXGc:8h[**:/JC@SZ-^^+Af54JP\3j3g'_O?K + C8GpDl9s_?aDbgdJZ"o:1mAujS"!dTMT<#g!pjZ25EoX1?in_odQh'AF`7 + 'MS)B7*S4&pD26OQTk'eEOJ0SIgQ=)9i;)^mpEHj,3cn^c4#M2Z=H) + LVu,k=LR?N*KG!tE`ja?="Tc_Aoh+fk&VoBa^asFls?"nLt$0Y]"M\d?%Tk.G$"(.f4fWfD + Kei;f(XU-i,>e=4spSpa5-1Wgp\'BSZd!J3a.jCRCf6@Am7F,a*52Jn9A-n59j7ReF\&jI_ + TAcgsPnE=s#u(5OJ2XIXb_%Dtj20)3>71!PjdJ%0Ac`9D8:tn"]+A]uH8goXun&_XBQub:M + #FXBS`A[On6G]IqC%i,l*E@aqb9b9FlL/.1sXXXaaaW2_-NM=RPnb$nPN8.%b$-KLN*+a:Kp5YAKl*>[g' + (KP<8*$hXA`=,/[SD[KcRg'>QkSUEKh7g5BGW`p!n3@AF82shA;15G$^M(d;n+J"ikRhKb6 + pSPNB>=4IJUiS8AnT6,H4qpg+no@G$S`M;e64*,h!j`\7f?A=,B;K8-o`&*-"OVF^o93(@P + D+ZL9d7gAf,DHmdihN/bDKP5e2LW+f]+_j=U=@NS- + \;F5t@RdjQ'SKSVY_e&7'IT)t8>ihPAAJm/D8NP+6jm4dZq(bfmdMikKK-l`N$L[t12oT(# + X]$u36jc#UL+iqO+\Y3aGn*R#WZqCOQ\ujUO`V3o>AZ%5nBM'pbWO,s%kq=KF%n7OCJ:k6Z + tjO\3H*]\J-S*jDT94MM(FDi0#:7r(;4PTU7p+i^!95q8CpA4qle@4

RmTY.ZYY%!D?eKN,6a46+U"Ebg0nKTsW5O5h&.41 + YOTS'<&IsFG#n4$V*6*(:)8IpdN;,CM50P9+S,!lc+& + NV?aaKUPaA6t3cfc;!:pH-lbZ]p6-SA#5JK$r\W/PJe4/\24Wfue!L/aia&r^(A$rrb40[?j_2?#;QO8& + =]-rP7&Lld/QiKb8hlT]a-:'WZ.RM;Sq8sUN.a + \0qtiMK+qmo*>gL2"LC1BrF]RR9bk$pi;\kRe.)8nNqLK%->:[\;Od7cH>1m%o4m/oWSjL.scO&Dh-2d&!';Ap\=j&O3/B=. + L-IuANqePSQXgUQVeLesAD-Qe):?gZ3p(L'\`A]*;,jfn8a!l,$CpoANq+6h&)5]U3UI?7\6J,sEKj=+:ibBi + BlY7>\ucY3k6'(Q6hoNQ+p)qCCTEWX%KGn2)l4H5KkAkC*Bf-Y%`SNZ + Ou;9E$;.V2k]PHr22a]WO-jF"TlZ6j#$Gm>I%l_TXshDr0Wg?W60">>IB[ho0_=,[_*.L%# + >'I+;J$'C;``/)M@+t.#SnH:WK#^dM_=?F?d6_iTEObA$R8814=V0'_Adop*Ql('i4o_,!- + \rT[&9gu$hp^dq14;&\,Ri7g)$> + +hE35[Yep]HC`#Va_:;6caj2N5^Z2%jYaWUc`K8kiEh`JmTORjF<4>ME[sb#9Zu,Laj#P)l + W`fu`h&h4l@i&pKg_l;39G8Wg2aoEb"[@50ZL#Q_Xg0=9ZG'(4B@gYC0M. + n74>>RQBCgn68U@_q7s[>!amKB'58F^V$EkH+_k`2OP,aNHT%Re!s!3N*n21Kd()Uher%P= + AKC<2N5h"BN?_th7hNC].&;21&cV!TjP+jRGk^1p7u?bB0?/7o*TR'QOn)gSb'@>Gjo!/eo + 3Q@\H/CdH]I\O$A+t'UaT]>>f(@`D?[Kkrnj]E_nDAl1J!SM#^kbJaP.VpfR)q*p<@Qt2np + "fI[9BW]MR-?`g;MXU*j>,KqmhpIjB8JDB;P1J>*b:dXd-?(X)>2b*+XpX5/-toJKCp*%q)G + #,<#aV6e#f,L*lD,1]W^VaTHlJkFm(pR+!X(oc&g;+pmG]<66sSK,lJ' + D0*e\:`0)"b0FH/oU:p3>ZO$Xr75#$$;+44uuY>.TIk3mo#hSQSk_RfFB0Lb(U,"49\iY4" + sn5J?IpS/EX6o$kQUd?XKWZn.;%/H`C=/C;cDl-XuOL&l0'o@D:JPM`975Th$[u7eJm$9S=\7nh$^S)tAsOsA\q,$iWtr + ojsm869NlG>/G]W6Y;.C,Hq;P>QqV(-Mb.^H*(d[I5:X!octXGWJ@6[C[$C9;!?k@:OT_78 + I_["`)%4AUXp-uqK9oY9U?5GSFcmuSCP"Sk?<`5QUq1!1;kE3]iqaD330`JD&410I7.Z_a1 + ujeDWFHQEPgqo_gEX&QNK2)B7X1CJ$-nd;bP%\3uTQfY$Q$$s*QG$0tH((q[Sl]fMlR(:%O + ,)[Yi$uhjjhSp0bYqe;NlpM\25/V1lU'rNgE^mnS)SPJO_,2&EDQ,ZQV)i[s3]!ACo:%fiN + B(en_@sSd'9P9`/NdTQ34$hnX>rEiAAdau3&?0)X@gG?:;q$K`N-Ge$sq3%)U45ja"5T]bG + *4>`A#NM(=O\!M1_.^9$TT+b0(1F!PJT;Yp:)CaB(L`E/&Lf%].jfBe)Qnurn + nR1,AL?gi-R0i`@CkfbT-tl%'EQ:5_9FDqi9eV,*Q#nPXhPUro+ib6gr+UVaRE"Xf\NM*h? + '&naTtb#%/#aC.rS`EnWk_'ROo^,)<'*>!p\]q#iNP>/8c*&P0E[<+"^=gicR'roeBA;.pR + 3G>W'E'nLmh#N7eP_j=Wj]DFubrF>^4@?F<9O(%CAYm9b"@?=CTT/pe,3Ib>sQV_u,aDMmZ + K@d-cr/kSUtj6FCqT"nAU&aT6=DOV39TL+fT)UU2O/j3/+hc)rGg+LgQD6$ZV9'u`6gJ6oH + 6/k@2=t:1k*mnHPQ"igOC@G"h(SYmcG(FjpKhcf$ + !>>9JX,>9RMk5nc]f`Yq,u#GJjETK)uV/n6RBA'b]rE"Z^n(1j]gAk$\?-?TB%IfcL(!p5[ + K-Ck6NVZWg$"6R2u-H-+"c>AY&VboE,]9+&8#pdC3!-gEq"*jLI2"N0WKKHY'b2oI1Wi8Ye#9[^4,RgIuq[#-k-UK' + +H?1%;7*uW%\=hp_i"q'4ImS!HoDJU`0Yl>>Y5QZIl%[guuZ$V*iu$KAc"Z57phoUPnX,l9 + pKHh!%SE'$e6*%QH5K2DGq96qsRj'iH2LX;t>EA^9OP0'r$-l:/kMNV7D]igBA?eS;Oi99u + UpcUV'RRe6`VoiAn@K!aE9b4\.RD$&=+6K$d1bV*#R*!n;@u+pEf]AVA9=JKiQb]-VSE;guA&M'NC"]a70Enk)oGTGaakLBJVDP$_^>@q2[">qS!F" + "u#h7+m2lg!MoM,Aei=HDsNZXD4E.C94IVYpuKqS@Qipg!Q?NeX_!=S[mP5\]p^$V(3:N;a + TKD=hHB8_-3,*iDomgQqo;O,o]ri)OOhL<[2'ol5?OXU?j)pZ.b5]oEV?HK0U)g6m6]Wbgt%-RmcSL`O"g/$r='XKT + tG=(]fF.!Ho<28f#7%'7ja4_)<&pNY&A_<`@0sU*";N)I&3JMTiD:DBr"S+Y%tRClT_6%Wne@de]tp- + L@9a\D//DZ_'L?aWTi`,IRMOk:)52^$#_eY(o3t=EH'dR03'k:j`Bbk/\MQI_lb_sJY5j?VTt!p'e@3B`Lu!Lbd_rBFX::X/R6ji;cVAd__ + gEHgbJL(luh=K[JeE6oj3%6jLjd:[G9g&39+n3;cXO-]Z" + CF\:9qQYKkEeMbuWJfT%T7<(.R(`gEV*r_(l52]Fo`RG,@H5ck'`,'P.gmA[!2!k%b#fFTR;I!l + g'!#*T-YSHFVE"3.SK#.k"-`<;O+D,=&Gn5O:e=DoluDpVm2E+Er"BQ:7mE_p^qn9'.,56o + 5##R=967pQCUB*cOt#mYe`7th8)NsXG5H17VUOJmL[XF&RFYdl;pu6RC36=.9psMltPtel3YpAf[F<`qZ5BSQ/]r>45Rnn'u954FbqL\_+iof + bhT^*L9PbOH'41OYr+9238kBWu91`)BH.f6-C0t_]$A')[!RBJZ;]Pi>]r;PY"?rA7[KMbV'/[&G9iRa>l7n1i>r7I$4%=U#,-Z8i9D^gNYr:(6e&@U1ega\<(5 + =lPuMU%*EG\\dM7.h@3o$tel%d/X_TLm9@i\L`6E?\?-UubQ0dmd4^ZOf'G_,eKgfFtfT_[ + R/WEJ7e*7@!P:!6NX"ruUQ=Xp&?*?&c%/Ud!Tk.ojm0tRGXf$#N>ODr)Q-HFR]/B$dMq.4$ + ID7\E(`(s;\V7p[E>Y#8/\d:c3&2?f%Sap-b;f)2QR6W1jj,ro)AbSu8EeN:h)_**X')QIl + h1h5QHs11gMPd$S_!7Mn)&'Y)s;teM8eLpq&Gu-.tbXoDYF#L[a/Bdf'@H^XZhq9U!G`?R@ + L][_i_uLYrHP=DZbI%hAZ,MKsdgoZEu0+#'FdN=fN>hOcZLjAh&sQ.-;G&@&PCRH=&%2l>g + 2I:nh]0VIdVR>G\0r`;Y7pa9 + iH/q4&8fJ"[`2d[3\I<;ADW!<)Jol(IV9rKaV$#oG).,ch56R>sS#/LHJZ0WeUd\C(00q_9 + d;ZS\e3ff4Rek7*M+;(8>FrVDA[eQc#-K>7Xs4"M8YYE>?Z<>k^Q`&m]]mgNQqi:#gM(GGW + 8t?KJ#qI299Aq_;:G-*&fT;a$gf=jprMAm&&TOb[&fSV]C01UV^=cW/d!>Yl0?NEOWYKb%4 + P2,t_s.hqinQ"=mb>F-mdL1:&BH)'f7B)0*GlC3sj7mp"J`h72(O;`ghPYG\0Aa + SCV0d,f]C.H2#K*FD5:=YGI/c^colcrDg@]gMLU['u?I,nBGuq3.Qt + DR_X.[!FSjIGYJA*]>NQ6*n*c/jqPcO_]%#p$'mBEI*`d19\'GGYr5)0hOGgFkhQW=F7+-r + 3O^d+>e=cgNOd.AV]QCRCccWUoR=<;JDqP*6\pj?,=Hd=&n4g5M30\D7/3Gi@['fKO]Eu$G + d/#ne6@inFtZ;Z_&-Yr@uQP91IcW:)Il`^NKLY.7ekp^A!D%pOk)TaGjuhb>X=F1mN4soG! + @.nO,!'sau7j&jkE.jF)\\9\edhU*@\O@k/l,5QW@c0q;O04`,D$,epE>&FtNs"ZkbF0Iu9 + s$:ad2CTZ#iC'2iL2$&&j8`'fHE67PFF_&4IcDN9k=59]$JL60gu1.;c\+N+n*&2?C+AsCc + 51HoHq_@\PZ>fRP"'d4g?PXWPfV\EUi;@&D6-j+/*'7i8ZM/[&a`0_Vb@Rq*W:e!7ZLaJ`Z + Qt?X9<&+T,/YGA9@L2I36,A)e&`D)l"$q/VJ\;X"io!)+, + 19;F.KijEV*Q5%#dB^i_I0#K5s.mJOJbgiR$\I/>+Pn+-mA`C5mp"E6\X6Q(IVYd$oKRiLk + ,sP.Y)%@Jf$;o:sL0d-u5kLX?HnTh,hL5GS#GKZ3MN9+N(-s&7O_0#E[R\KB7(`JY58`qWo + kYsL8s#N6n;`/%5#_G\M_5aWW]\4@-^DU&QmL`Zc + mTbG6B4c1a`."U<&'Xk)@K7NSDD1JSiJ`Kj9;-45/aKW-?^_R0+B5bbV-k>7jiX9-d*Q?RN + (I3X8$d^pTL'St6,7#QNnWk4b8C8.S#],JtZ8Ys6Z[As5pm;`%BD'aBUdf>>plNNKQdi.3R6L0QcPpKiM)^M-i]`UTYp:ToD%J#a>YGHd1JJ1T2Nu"*:^Re&.)d?h-oO?3P\A2i8mnig(1^R9 + a_HC'^$&hU,5'l`8nj?A!OVb3A4H[]4I@,4N\`[#7k*8Hk)e!K_m4F0@F[6tdLr*%+E!3W& + ihS*@r-Bhk]U"Mfpap5D#K^+2F5MGB/Jf<%=C5I[^nOiSC;!Lb]i`:S!/J\nk&#smB8me01 + WnQT0gnQj[!0:ME(XY_O?$%ECJ#c*1k=tDRE77g6\V$(6sdV:[3BdY + V\IPJief,N"L,pf=H-Bn4V;G)dFOWobZ//N2Q9?Vl?BI(U5dl)n_bU?dEp)^K+P8.0q22ip + 62Lu_0:1NXDA25t?a]1\U;Q>SjiGM'",QIu>K' + J95XW1jg[U5ZEt+R-\`UjVG,`277o7h)dElSd(?@7jSB)+Zncn@QO(3c'FZ/Z6!P0GX;&Ku!^S:& + _&;U3](J>l"@2M6^uPqXF9Wj"j$9QL_?:7Rlf"E)ks5WO2&Z02)cRs$a>I\2YX$I%X'2`2& + <0?I#?e/6CLJAOGWrDK)\Nr@_AJu9%C(dW^B57V)8/6a+L+[b, + ^3;fF#7bRC_s^^`d>C&Fg>ffTNaY(:*F + iV>-"U#p"hj@P]KD#\,\I=AYm%CduKtZWf>bgGgu=Dht0H&\/elOB2TV;$FI-KJIY%:h#+! + -\c#$!Kqr2$A#-*Kd]iSW-X=LU5Z%nO!sR]W>_!M6C+TcR44;f.C,Hmd\!N8U+.@_W_00=E + iH(-X=D)iWp6i>'!nW"[&[B=X*V?-U:dPL^FSlkX;]#.6H6*AaXiUuXLcWWg7`L + snO.**`*>Du;8kqN8A@ba,Wk]f`(_64:,0l=5GCaZZr/0mE/a;1O!+''6R*!>]o/A)7@:>8 + BP"JoVmk;e&7Gf!W\"T;p1Y16A_ZMY]2jJ=Mh<1[Bj)Sb?VI(?:*[Z2?;p#B]V.7YI8O-d+o0c[1? + +4&5`lK:urd2i+c + #d'#C,X%e5PL+r`k>CK$$Ig,@S\-WM!X1c.2^QA7&\4IhiVmp``X0=OX@U9B/m:V + ;dD:VKetcZEmlF:&reULA!5-8Z0d;K4f\Bj + s/LLDXO3Q?e*E]7H9Cb([IA"gc<5JIak_EBIZS]N&QECW+e$FOi`1eh`XSojQ>P]f`i?eaQ + hVd0`"snN?>r<'.TQ)q%\ae%+i;]i@Q61Y06d8/7WI3]U6SWD^FIW)Wor0>gN-9AqPbrQja + 9#2CR7=q)V8.ZF#4%9!KupijpNH,D2KLU=mfU[QDe]-I4CU=;.WA+W0YocumB^=sQ]W^kGr + G`F'bLM>6IA5c?_9hc=$N3(GJON#W&(o7d[03o\ZH(R_D#JcP[g)Znk.'Z%-+U3f.5fERCF + 5e:s*)GW7*R#Ef*#VqnXBNlN>/dMqXf7r3W1Ke*5iM^Yl/u5]Zs-84=8]JAj'K2p8M">EJP=BmF8gKq;c0-NVXlmHGn'c0[^k,;B*Ma`'eu3b]&&*A]n>lbZr + E;>i!\1mE]&9cs7+.>oh@!/^muge3mlj)s,18NK>-Dp":6P=Y'6%V3@G`*C#p8^SogS-'sB + M),2"$1*rEK6rq_-=,X/a,ri.?eKb>r!4%,a`RtEP%,-Q[2*8^-;T".kPD,,n0LeT6;ik!= + qI)jHj"i2E`iNr_D>,hsDR4`N`s>UB<,r8p3E)SO"c-.]n6:Sh63Z:CK4&#=cn,G-8qJa)" + FqH3ie^1rMoYs[8.q$*,*PPQQ,l:Z85bnUe;#L>Laj*q.QiSQSLlsq/S(J*N1_c0Te4n<"9;SVcq+ + E14bY:nK\0N6E*e9&n-7Ep+*8>cM>:?IY$2SN5Z-GmH/mUPZ9A$cYLTMWoFA\8\4_4L_,,# + RXL^)B.CFk_[@#;u`%Ajl@RCS5^:_4bZnkApm`[FPLYM=:I)fMb4^D0>=S!*JOiX&g92)bI + h7QOCq]:`Nhi/!e#8;2c0Yr\=G70YcL5k@'gfoZ!a=8T$7].UN@BS:?mVp+<]r$s)hh2_4N9Wee#b?fL%8/r + 1XeguCDO-1[IRq/-u.j*O]H:]`9]ThEu,e:\Tr\8NBoRmK4c,jQtr5T4S+r:7QL9.=*\!!O + ]WJ=P.G(!3jhm(IA8He3:)[ICnAdcWqgIWqUG\K']Ds?Bic!;=;@r@$;$/f@:6]e@loBM?H + $kFWIe0u%(r]Neh(W;Q1!4SFB3S@I3dD&?R]+OR7i#6Y#8 + ?Cek_"3U9ZTE1Y<"X5'MqeU0cfb#,k8./!/Ur:#;qqmI4^I^%6q4+\k2n`p5J=G_:/!1gcL0"X[mq6ufQ9C1#KU1Y>b4OB$u'=p6TL"gCgUcs$Y21kiG4 + !jGi2J0SHrOXU]jWAK7,>_K)0L#DNl8:$lKMP3S0UWBI48@kb@b,\(eXiTe38G]X6%N2<`P + ,jh2X1X[tjHZeTQE2p0XB_-oKUJpCRc6*Jai`"&g=*0C*ob6f">Eo0=A/n66\S'>KfEBC?t + 0CcC>.A:nb4-r$liIn,G4lB+;L^JY'Y/B[SP$Nj2W,!e[_?]J7oL5kVe)',e[lM^0DQjo+" + 7NHgUD\%p6B_o3VORV`pl*5Kn`qba$iMA_HQpb]&>+SKA0^pIGNj$cPOR1*2tTnA;Io+,70 + H#*A`DW+Kd&Uug86\+lNNchuofj8Q + QoUATleZ\Y4`uK#m?mK9c[4CN>kn`jrB9c[=7!>?dni\WV!id^kQ`T1f(n(H/OI+_A2OX+N + %VOJ_.69W5-njB(6`GY`8"GU6Ta]7Ds)S!kY/JITo\83*>5f?'PUgt%Y3fOp"pcgDa2L69a + r!G,BDJ9Ds>af6&uT6^6IljXL-o + !\ooqmT'3I\'nT_p^1p)d.5_AFKgVJ<\Y> + R5(p7s_dQ2<[3h.EVCg`LLa>K2!idl>F`leX[(WVD=4R!BL;^m5=]dS/6OM@Z@G>YC981CF + `O]>Wf6bCVom=BX0A#nm#6f]j@;sk2?7-JFAn,i5r+e5;PKC/:fmLeBT:=,=F3P-uLIItOl + #bX-7\ur^en]E^rJ&?pe(SY^gO7*)Fh?=FGM8VmQUer,B=Q1`c=8;daCFabS3@_oJ\U+2Q` + J*blZK_lXk(*iU=!PW=KDFDoldrNX]>>VqQ(%#"fM<#']6k[ogtqYJ'';BLF@*]#`'l?4:q + _4:g,-:-h(Xi#f1I2.%rm[\etH78ji=_^C^73q^M!(8?':3Tif%PZd*&d`OE(ss7]5l.CjN_VdVUW + V%bU19BCWiT^[VEuE\Z"5im7W2oZYiMA_AGb$@(4s/W4&i2?)6T;DmP`>uJ)Ho + h=f'`G*!9>M8mUDqk4p_!kSN$,Z2e57tj(>[JAgn_AU;aRWT..E_5:^G3t(>i.QeoA'@3d. + CTc8^d7e5>fr'?!gacp"b++f_/UCC"i::^N%9^rS:,\=-N=Cf:JMY7BjOZolM.g8h5'HBb1uWWIL + ;,:%)c&e[T(`CpFM&0+WaO\Go8@&8G$4AnH^,UhTH'nXe/;*gR,#2nP_K(+.b_'h_T@!iK9 + O>6sUO\Z"s6j8:]Um$u4A4Hs2)CHe1#8?W6.Ktp6'*?6Q#]X6#"6cs&Tu^7@JC5pcTO&>L4 + =^b)()'+:M@&9]7"mV)'Z@eD$INrG#sf+<-M;A;+r5mA>sJ:e@f1d$N/"F';9qFMGW4W`H[%M@b=<\nU;t + ,i5ZYO+D6ZWVDVL]eJdeJO\KPs]NN,CArKTE)n4+2V^1Am7d5dd($<7X=MZlSY`noJcdr]K0h;Ba6I&L?1YBMg/g=GrA@B)kKu0-u:G(Y+tX$qYFn,NR8k_fBuiaoV\o`$hIDJt@i,_-&;-J?Y + >KcAr/IF"W1-]bghQ@LPk*1.GNo)AK8gVb7jFe$1VS7FsW@gHo/Oc(hY&lF5@19hocgCo;" + >[G&3<:N'QgXVC^uhO@kTWX=;84&jlP.Y_'j+m,jfAO5NICMgN9?bD;>DWkT8@" + /`js\(?4&pMe?jtC;'tV^#\%tf,I(^l`G?E7)$?9\ + [=96K[(Gk*;^5>IP5>Q\/+,E=Wa$FI\Q2/5B76#+3=niW#5]enok&T[8SDpHO+%"-%sC#%] + g5m)\[d/:>s&"5q#k9+f%8)O0U$!kID?PiHgFdnh`h7i*`=eHX0MN6:cqK]VL1WP3_+%).t + gYY`goYQt`:'p'fg'.FC+1AkZgi5:C(_V3LF\:!1ZB,u-oN^K9sI8gcJCUpQ;-Nk.RpfAO2 + L1j=ljYe\#`ZP=\&;h,H?nig`EV'5qXLJ[7T5NlMV+^G4IOHtUIg)dQ`1b54U#(AeIo13Mi + 6_4l.,q<'IJS.+-M\0hV7JO0u,;F\;CR-Y:8FF'!1k&\m$/<\Si?KUooTD[99?5TTa=qZjP + o+M;9'4NYfP;$V-ZIdF1W_O%es*X[^u_2&k0T."!GVjf"L\EW@F1hmDl@E;U(-AE;-,PCPn + SM>U[d)U]-,e5u&'/QIN;So + nc9.KrlCH0'_87rq2`/fin:73ka&&pX=!A9fJb#^/VR:ha*pL-:l*T$g_2XWS]9tOKb=^Z[ + bRi9YdCS66OZbs/M"X'sIsD^dnnB+@HQiigLLqkXPoP#L3(kJ*$UCOstiB-\YAD(0-`091] + m1_Y@mUiRAp"nGor773#G;_.5'M(p=Z@A6'%7+t?"eLD43:5RING*X(,\%`'t7@,qrO+:]P + ?&]!*4Tb\)G'G1>lO!sR[aS_-6#*B3'O\$CPaWQbG.[*F\PNb7>iB16DP"6d=DZ6b\SeLXAU*^A`Aua`SaLjs[9kV=:jkB>mWgm + gM)9RC^We)kdi#$+f/0iHTli\+8DU9d+,)W$@$DbPbI_q4^X6QS"&&9P`EmZ(e6RpGI@[PT + ;ElAC@.tc-d_U:#&hC\lr0&l^\_[%rk(c!T?ipJ^6"'/%t[+Id^A2(7r:3Pg$,j4JA:.Cc_ + -DLu]VV0qT>%Y'UCIB1\R^B+jg5_j9Nhj5RVRA+W\LJ6%k1mJLYdeSW%WW('&ecEd(D)`:G + k)(/$]_bJQFX9on\O5AB#=*h9"PZX>(lH]X1B:iZL^Mt>RSR/d4V<7cL_:q9JDW9`3S?3KJ + Wq`3J/6aJWe6.Nm/)>(f+NUj3I?aGc;,<(F23[N".7U>UR`T((\3sN%le[V^dgM.M'EGb/P + gQ5!WW[`)2+N&V_bd:.8$=e)SeED+ba6^su+ne]_p^,H=bSAXQ8s\0ZHa>Aif&?T#n4.2D,h':)l&@^+p3s3\8;D@ee0l>836JW.W%5Uh7m]4;Y*[ + 74g29D(O+fma"TM5><,sN>Oo4'fM7k2UcRpZXhIi@+"RO/.ElQF&p`60/X1odm80+KNC!h< + n%O]e4iE^N*b#X@atL5/Zi%jNE[:$-+B&:+"pJ7%N:XoD@'a%94#>gZn:t6(R" + ]GrWrt#k_DX'LVX*g^_%TlR@\^)3>$<1/j)Y-=i:U3,/;FnGR2^JL=o>.s`ZqMCa8/aih1P + 7=p!L/#-YnV!EINm&gZ@j`kMAJtWcXZ_n:N@4E'Z#A`Wi-+IcaTV-3 + "%qP[6eop1<3[/5DTqoEHP+tkC^HRk5+r58H7,+BEiaZ1$"(us$oiW^2ke/#73-fJYP_-e$ + e?5>h?('/(accmlsD`';+(Qs@d_T=iiKJ!?#4=$&Du% + s8ZC[2Pbm#2M]s;Gn`+;%uj6.c.?V;s(GjYpAodk%R9$>]`CJ01*?_[_:+&)AQX7Y>cAImO + jZN-k_:.JTFo@Oqs=9LGH1O'!H4?EGlJt>_E[dZG%+%KHS^`37Z_!MVgGCTLS50).M=&_2? + eQB#gqLih;n^%$7]eX$,$28ZNc$2H_#AX%MNJ9/O)`[VJP.^pJ!I9Mt6fE\AMMRnXRr+81+ + ,'\Kon),8WjO\2Oe4d,(9CcLkMr,kY"X`2q0Z+&AEdB=D5/4CY=-6L#Fau]Y"'AHhDYEsX(]qU2F + )U+Rd;20U'7XtRlGUU#(o,a&9N]9^eT'$i8%0^)D.+=qjMu>\0$SDsEq^^&pL@f@mrKT7,asejn + LdF#Z\Ve433b7ajddXPuZiNm1P7Q/$TMT + %2&NlTo94/M8NF6r/Mj._Fr&(kB6"X?eZ@"WToR])*lkOb@TYtKFDNdnI2c3W)j`QTn\K;] + Th:4i*&_5;@F#tqeK1#u:peU[%CYRVlTiT)A;g(e97>jUuh6/B2 + jSU):P-Y=bjLoLNm`/mWG+#V.H\O>W54;2IU^qN?,Q!l#)mu(39q,b+PWZG1>$N3E2]:#u1 + ,HGSK=9QB>k7-_d%[U=q8q)De-l2kug'UY7r.)FL:.OUploguj9,fkfJa$^WL[&J#[Ul_Ab + <*E1RiHP%KL1.Q+ga$Z];]U-U*+h'jjYpL7/)DO@&A"aSm1A/-=/Q#5N$iDM"h%`_%.5>$# + :O!WEAJTi!Wn!952735QRp,9C>.W%^'s"F>XfQHG=`:Yn87`RrAgXd*J832F!Zrr?JW:.1J + XGhUb1hL_h^o2p7afkjNE;53SiZZ_YBW0$fKhX1+unqHaS@7l'@gVt2m\f=$&3>-<_h;-B6 + 3n/FB2[2emR^Pqb,ODB+aF_RL*VXkd`P$gR9!n3Ek2U?_U^lm^btbE>ahg,S[`lT=E*EpT@03'Sm="u*Ac/M2q+Xfbit6Z\ret\W9[UEEFMIrI9nh#EYDi4'*^\(cS_Ts'colOnUMdPhVfP7%m?g + I<,.f>:@r$hNn#RJn4k`b]r/G2$l!B''Uf`TUe+L0nGBaL#\B)7Sa7Amdf1YV^:(o@!tN'4dXMS@"3FIY22Fhjq + K>kh'+f'UdI!6U"d/SsMsaL6k*RYI."ZAlQ7IOl,(ol,4BIn_+A9$YrI[J%$:u:-),lQAMD + k9t0!6mG?ag*olZpkO+r-mb[F$*qShsqs@hj\2>9<>#f:_&#H/XC-#jM_;V8646*I2o,37[ + Tu5r20o%@gi=o?nl@!LoUbFrO'V)V1_RUC+l.I_.DUDf?lnti)J0__AL%Vg,RbIPjfCkhPX + e01,hdNZ&\g@cs_:#G9&U)(/,-9E.6^mn46D;[F\$/sYe2Sga==^X:%F8RCh/."]rSM(0\B + (7]k<0?^1AeDafB9B;ZnSU$qafl#=$Coq_NDuTOj7\=0>fE+i)%meeBR>B:SB.u5[j`hJNd + F;=>T@W$%>5"ON6]O+VBLEU6/Qb?7Pu77cTW`oR9(LO\"Pr=Yu^I.@!NeP,Q=5@1ntm@C'' + 0-4L01^*f,r]`%4`HI1N5=u4<_nYJ>sNr!h.T^A^>lAhmNVQf6c3NJ04o8^WFYrD$/lSES. + g9ihUNI,_Ii7#4nY)Ngq54Hh11@L1&o-47U_(fnf9J[%f_8S)_Mu7Vm+M>fKnr'TX?#uE=B + %He5o%QI(d&hDY>rJ1KuS9j_"''l@/T[C + &XcfiL$e/DDqF)^S>1/`V*52pMMYiAciBNoVn>>F/":-8V+Q\aGH[A@cZJWp8"hMmD]se8r + 4i]C]AEn_G^jb4<4>EN&NC1c=Fh4f`]1Hp/(#pA$mroBAMJ-JS!@@'f83KMSf.(PY.mmN76 + o0C/Xq@^rj"N+4iu2i_BmQ9ROk\7QCph\hE-1#F(PWe#@h=!K?F^4AC^@&VB + '=Vp62Ikdg>fW+4O\`L^PL1t@t%fh>,%@(7-()e,0-*ku*lus*bF + 'J%>9l%7V24j!BDS*@G\F2M6h7bcD?N7BO4"e^][i5hf?L.;+]41)Pf!/b#elJ_XlNg3FZ-Z'()$9S\Qu1oaC-goRPkjQSbAO:E] + mXpq/nL-A'gfE4>8^BnL(M>Y15KVZfebWP%$5A4-Z[8[\cRZoiSG\d^k*5p^ + -RHm#:BZ4n5-D,.Z(8:+k/bV&JA\D`@3O9`*/*,/':MABcSGJJ<\JC9m]8olpe99oqQVINY + 08k7l]`RA$gH@&`u5H\G74VoRDr8#!kd?#pEh9LQN2r77as,,@knWOqL5U8NA4GA@.\e&nX + j0Z:>iYA8*4O/LWdCISZ-Ui_.U[(8@U"M%L@E9h8tVQ#a+!&9;kf`ItK\6HK+[j7t:'\O/" + 0b5k8AN]S^a^R,Z=@8&Dr;lt"GWYJ'9%>h8Ek((m<8[p>6n?SPc`c2@ma=HF-Y+66%f3"6Y'Hg"A#Rcq,2 + RFcrGb*T,6?_n7tT8:n\L*Ml_)\pK;Y\p67>[F+e&Krlm7E&7OWdr)@%e?+U441RT:!"(lC + \d.Rdg:nY4E%7Ib[X%qroQ8.n9]mm^,FiaF2bom`!%n<_9&[R4UE<0XS<;j8QA4Wc*CQ)-= + 8Pm#`>9!l#W;W+`+K_/D?3GC'd"DaTognKi;5!*Ti^+FDtZrG8q36)V,)"tbtN1.FrmJXIP + eSf>bMe@!e!O/Xp]!9IWb;e#(_CZ[HF:OM.08k4k/i)q,SR`O!a.a)2(%/f&lMu[djrM@gr + .#.h69qdkYb8Lk=X%8;M:"Tt+df-^?6sEQoskc*[)3\qP$UeHdP2F-YYS0(AP-JJ-m]"cK5 + bsd+rEBJ@,)N[0M+=@p_LhV49Ck)XK"rKDTJPOcr=@OZG'hNp(!QG3RaE(QQqJP9>S?7&'B + AcYNY_q`b%)2^s\Uf5jkTRY;2P.8XYPqVmRIOeQTRfRS%tK3-hOWS.3'=R:e*1l[3O('eOC + %k2FbsMbKSWP$+nZS$_LM[AhnM>HX-^\qh#BQTQ"8/D:%i$b6Q[b:JB%f'otYFa2RQ+EpM$ + E't-QUMiV_dMb:nBg!oAFLIOb)I#bs\:p\e"!(n`n8C"H9`?2i3aWGTR6Tqm!+;rqB94I63 + bj/,NglY^UEQLFV535nZVkh(8]XUA[,Wp)_f6-@0T3sVDB'"]bGs_Wm6.GVG-rgR/d#ae]p + dmQ>DCff]u:rHN,RKkN7`=paV]d^RVl$LF&t*A;l-`p*>LeD3RI4UV9lf?D;&;Kb3k7EC0I + 1=qRhbbYFpZ#H\Y%&MC<0L`H%@:0C\V:D_Rb/=.W.Pd5/"(ik[dRSV=.3=%A=+`Xc8qLuj@ + =*Je,_TbQQh9)2l$P3i5/Ae0n9%A@@6k8l$g=]=7t9!m+hS^^u6ds?46FaYc%oS(am=(P1- + 'h7TRa@FifS0s!:[MD4_ak:bPB4+Qr[4k;^mPPUSLWjC,+?RuI&4B!Dj-CtIe/qgiRM77(V + YVXNeqc;[-$\[7rf8mGJ0PD)Y/pIP,]"E.//-O;*bW`C^H(J)pRh(!LXCE48a1X^VtD+/eU + "u5l[r0@Pgm$c\=(a.jLm\D\R*3@)8DftE>!Uf5s&ZGJc/S%ZZ,LVoA^ZO + ]4ho,g=5#AVD:>N@*B(bFA9$Q@>TJbu^n[M0WgRCKd_$R(rMla=>M2:0/TM5de#AMt^O'a& + !Tr(F#1A=QN:/htGVA.fAO',8%?QFUk8N#*KoB,sXDW`l;@JuLQjg*(Ni&$WE&F%!&#,J1P + []@4JUo(]7P.6K(]X(a@P8JSmA00$ki0FImnb7+]o",J00M9?6]gBj$I#Y*<\`oLu+_AhO#N33P,[gR27KMFHci3PNXQ=G-)K/PZWk;TG$6" + /3XH[S=d$9.MUSU3N"S/sK_Wu(Y!1a3qt&WdjY\])`e1s."2gnW"Z]ZO3Mb%.Bglb1GlpW@ + TWb+lVl7'"H\9tQgO[0_t_6W09a?l.L_2,X@2J_.Io!LGlMRHn"E$X/uCSi+@0[G,r3>OKF + _Lc8JX8I/]_.Bc;7MR'$NaO>%8gr%?&gl0Iqb[e`/gqo?K9J-ut_">F`qf9mGFMSW5[qnku + q_a\'?,9/mWT'M?)PBZJTrtGF6)iG3=7o0)qE(AX/)-YEU,_7'LBhMq[85&><@b2rG@MIkl9CTtd'4qZOb9jc""h;s>,*JoIEg\*maOMI(f-KGOD&D + &Mu>nuBR'cV-Xq8XJ,j@Le5724+-'[b$*D)]'e*AZm$K0#ZgUcP85T[]GRqPm-3!j;_L(c- + h,JVU8jo>T5f_Go?pKauB!_RQ-L:)+Hepn>t`!`<[p:.pW\<%u^j,M,bUdS)IBJL)j0E\'d + 5a$f5kSErV,iCA_60j>EpcuM.fE5$[k'GQ$K*4*AX\hX\HL26.?UEl)1--OH`Z@'AZlR7^7 + MnoagS=-BNqW5[9Pf5-99fmaA<7Sruf,]8=c'0<%Uj$aMR6N`sh'!r,15=Lt?A-g5W&NpjP]="NN#%,81R8PJt62'Md-9U9 + ,tAnliT"HGe*M;Yg.&F?`P\qUd6`R>cYu75P4(=E)#=gd\^53&5"4)P^^Z`4jFlA'D + @"_#;4(b`L&t1ed:t[tKu35eoZbQ58KE(BU9Q;`cr(Wl_#!3n\i+%0&+p#"U0i8,4-h>Q2:2(i1P]uk3G1q]qIq26)HL:&1V[5B__ + )kg&rAtA]%3j-oX<09E`O4V>JK%r86''h + rIJg]M2XAbM5gO8J>OA>`q$f&tHe^gp8oo_9]94>t$m*>jYn-gSip;@TG>=1^$u8$sSW8U" + QO':u)W/"eXB3``E!g`cD5972Q;5m7qCYc?Pc&Ln%@^JN_K=6]/rTF-jeF'JWKh#>:M@21O + @t0YVriL?7^D'_PQbZCq&mceS9@c7@no/E3b"]$l2UnTiI`J^>+acm8d%o`Vm$"hRb=h/*D + =r6%!)-A_DC30-je'Os+6R,g(Plc/q6!3rD_^'1P_ + 3f*l<%hFB7t]]>!4;GL,#j`\O2<6gSqj^^9i7T\3TYhL1HLLkqh89t<)/cSHNh+S`5& + JD8G_a>'>Y6R,h5]$%cHheOR_lZVRZ2-$^k`?kl023QM_PN[8'@*D[gQ.&PJX3J&>Y;$&Pc + B28L4k@P"Bfj40,V%Ln/$Jadl"[f]*gdS)CKeEOl)%BqG#N3bR4t25P+A_Z/Zt8uHSGVg^' + ::c/mBm5j4cr^hF=biZ+Qp4@'#OQ\bbO7&W^XjtrPtZt-JaVY2CR^"CY.O#FP)?]^kt$'E)iWihE^ + m1/6NQF[Ruo5]P%iD#7l>UoDU&KF''68=d`s$'(*_L4nCqK#[-f5Z5,PmY?dD)fX0b/D`"NA`SAhji8`tpd/7< + Tng_X&&]Q[A6F>_*2/l_fEA/u@jF))ilNh&[Rs!=gp#fZSpL!V!@.kCFp*X>C2e/Gn8``5` + dV`(:SfR1D1WO'Sjdr2X],$K$2'1=A'LD5CfeN\^D7F1pe*^.n]ab]ck)DKm8Q5l0> + 1;tEeL#(H;NPM@:M4E`k?(eu:OhOlJEI/ek>-Q*YC3@hGLRON<=Xb[`k'[glB$14:9QB$IlBFO#s3%Qm2S.8]Xb7N> + lR!\S:'AaCP4lnmb8Q#bi6L=\gLG"!YBKA+?NB!B1;=.joRja2&qaMT5OE> + rD23)K/\I"@eNUZ)E8A9959=eD\GMQsBLe.e-AC,0)#6.)`GF/o`XY_>AijR:B_@eh]=d]o + STQu)2cq[L't"6i>Q*o?#I'P?(g`iI:D[rC]FH]5RD?Q=MbjP6s!X*VVJf2bhBr3)Nr-I*q#?Mas/5_n[Qsl5r;Y + CI_O(.Ln:3SC:a]K`%0qPIThr70.FSf`6lck!Uf#GSN(+f7\E%YQ+-8K.;E#S]9]D:/>V`j + "JmC$Jb[sT6Ut*(9S2Vfo[bUS(g7V8H1ccjjNCRd4Bh`>R260NO2(DTaSQ;i?XB&L^]&Q,j + ?YA#-<[MsTbg!Lu=dH[00&:HTZE@7e:"J]TA6s1JHfatt`kYu$/6jM<$H0klEEIr:2][t#0 + X=hDVH5+\b[tp!]]ODr^sHb:>!3@B9rHHO[96ah[i^O\0eh?E(=rGn]l%aIH`bVBEk#>UQH + N""c"=65C\@PcEjJfI<9eT5D)RYLUX:JGl.k]H247,!P*n.e5obhP+qSE,\(pEH48r"dUW; + `lr&k!T:ZL()HU%hK"FNIibRFZH:eCBb_2%(l#G85Z,mb[?X=Xgt&@)6E_iX4J&&]%g1D.M + 9S4s7)n73C>T27W\!Zqscmf4ETn`p)pO4!+f*`''#qAp00hY8,;ES>u:aBBjGUIa**OX00` + :mRsBEb:c;M8Zcj];a5,0;kfY`1SNB/BmHT'2"@_9p9DL2pN(qjYfh3XOAt$'.h0ebYnZC0 + ?^^1].MBo/?qRi%g$<2d?J1B8(%]T,tYaiPq>WSeBKh4e!0,`:S[.@7#]WOX\=PEk\8P"+Q + sd!,P6:FB(fT4"fBnZm2YA-ZZHAbajHFo(<*KM"kL7jl[:!Uu-4hHmC^I,X_FXZf+[->[ + 'UNIa*oi3D=fiK2W1l3AVe[a]]=-`%6YSIN<=K<=$+GMk^(3:?8N*Qap;(a13#`s$I;p6-*3TqI7W:caog3\N"mgYW!:JUF=;+#a$'"'?;8J + CG1a/)_L'imV-U\.Y;ao"AfI'/ag]O]@f!&?M:Z!dFLA&oPj^kan+TZYW5nh"TB=WJa-o'M + SQ:'X>2)'W+/LI#qq"NWD/#N-SNJ-b!E+/n[i:UBjf-cmPfpZRcKB^',=SXPR>DPg!&.jbD + =)\(AE2TcV!9k.G+'_edAb,[rkV1>eh-^6Dr6qqp&dG`>QXYo7WGFE.C-H,YB1Yu?[MME`r + 4Vsq+?E@6i;/#8BeF*(R!e*rq&O\%t,,E`#kYj.6:_$Jn7pg@S<5_L1&J\9FQ"$hT1b=2.$ + 6;LtHS(sk:5TPCBgq7))5C/KN_uf,8(h_jUf9'M:m)2Pjc'G:F9WQog%tQ0d,S$h;+N+c&s + cGLJPXDT4=6P>j)RCK,`!<2#V]a?BYt21Eq7JL1Oh[@IlDb8JJCBL[=_[8cIj"bFL=cc-a+(:?j4-T680(be$5Z6 + !`6/GO.aWYr$#\N5G?j%]l'a#[&,l=4Yh&gM']MmE.u/K;g/WKUOoX">a[9aIL/)J5@R + 4`>Z-&+k37'>)QO'/k+Kq(ipO(>*-R8NEN>?VRYpFo3cAeNR5+X4>!%4R21fe4uko>jE(C, + ES:piBO)(@oF$f-g&!rnD"Qk[gRHLVIlb/1aQ]0%W6Pipf/:$Fm+T(Z1koPa)!]UZ.`m+=N + srS;W1\->O^bM47_Y-jO&1S5JNEK7GUM.ZpnJM`Yt@/ON[eOKVt-Ug2t"eLgFE0+*LY!Pnc + )bE",#/M+(Nl^"L4IQL89P^gfW/J0khE11$,N+aMLd.)+fqO1a\JGh]sIL=)Q&]$b+L&>9s + >D/k6:2b+ipJZgkAf=qs@RO&fh'g62';LCrNLb+t^UbPXEOHEa^_dfX!+W?.Hc%:jDfcs[dsK^c+>_Y@VZ^tm0YnHVRI]bGqf1`a/< + /=pAF]jf,4!?UX'.Gf=k'^og[8TJ-3an""en-O7sjm>*a,o!+QrY&,b]ggt_)KJfgR5d:I: + [_<<]Q`*!ET\DYipd/Q:7Z=CcEA6M#AGp=/V'.j+!n1=(4AZ1jZd[s/AGCCLf0&d8Y^tbTs + t:edL%QrB\ipc?ER:=?XW!g%<+X.]4fL + *``6OebNBl)1'8C`uYu3Qa?7^O)^=<"S2Y.pAY!7rITJN#Agq8Ei\Hh#JE0bk:?)%j + +o&b."W0dRHP.2&m:?B19S.2(nW=NA^c=GTB1k"abZFS:f0hXG@J-sXm:>.>!:-#Njr:'qq + 8&?%5>)Ms]HJY"F4PSn26)'\c-K*F#e`6AB01G%)nk@A`@@!/AJFY)Si/M%\Hl5&b6dNNAQ + k:k;c)UWQ<)tC`a/K#Le*?.L))\"-8Z)\d[0GcI:F\OdCVd=-j5^%p!G2khDLNG9VGW(0Sr + \*CmWOOo_p_g2Cs,"nLNsanpYji)&Sa3Ah/jcf.`h)K5;[u^s$R!2..fun<^^0*5.l>'PDS + _=9%o.FQnG6:,7[",q`t9sO-5ac81u/Hpa0):AlX9u*AJA^#lV*9Pm_4tqR2'P1(!Z_MEFA + SlE0neM\dEg60;RQ%ii&l7qT^fFdMSNq*92n3!2>4+la[$K7Z;75!RSF2%l5Gf$F5aGjshX + :V/3JS&q@2]%h$(Q-i0(t9VUcU + 'b$>a.shM!Nb6Q6VF<&YBmD/1#8V'#mA/JdEGj`UY)"ONq[^"k1M&I^/N)ao.hDW_d^[m"PC5VNM"ZOl + DnT)M:WspVDMG"KtjO,[hYTW%?gp0i5jpsqs:JMr"2*=D,ZdMZ8A.&Mq=PL&N>&-l?b>Mg] + <(1ZR7IXPOF!0<.ALK>caY!!^9U_8b`nul)1[B_-Yd4Tcd0cqr$?Y6%2!%JOM+u?t$Lk;pE + 4$(3h$_\u$kU;b;DgJ]b7)bEZ;B'\D`p.=(;MCM,ZD/Ee;ZS2mcR#lkEM5bAX(&B'U)Ao__ + .3$tU#nqRrVEts + OFhi>i*%aOGsT$%dDR#^&]K!Q7aN>JjL(a4qWC:qdLLXr8Q%7E$i82Z#rRMO2qP/$Uk1u4LD((- + 5C*)-/B4dc@JMF-PWge<4+*5hR7Dq19KjHW)t4'`>,^A\2'5%Mhl"E7S=Ce9G@c"7-aV*&S + nhg/&^:>cj_s+k"Ba=fTOV6kgJR99i22OTi?Xs.l.9*kmjk:O0\?>Q=V_8B]q5c0Ja,@ogN + gF9#EFu\ + u0)Y\l,7so2PR1;(GTb2U3<>/gd!n,4>(pJF<"s=;3`>&V6mUJ?f[EF(p.Y@h`3b1; + P.nD/?RpD"i28p=/2&Rj3-6D>$r&cn^)K95a5^8LOid&m$B]]`bIT8l/AtUetdleM5H<=;H + icGJ@5pKlUW95(uj^0R!qj"],p3C,Ga-2"\I\nVXiYMYOH6Gc-EQ[u'G.:_a"-<I[% + )3MHMOng/`2.kXOCM2*4E@F$+Q$6o%Z"WJ6ASZ8NfSq[bMcdG>QLdN3&t50XTIA$l>#2lFg`->>?f!8>R`t:XZKmph]8T7?RJEN3L*lMm?tBNR1KB'+mADR/X3[#n8WoX$"U8Z<2P11%kD-).6BB8A3Ab?:#R';(.NGM,8J3n:)lQ_['(sV + @EoJ([ej[m=-DEY2n]IV!>NaT*_R3Ct"B9;U8lpPb3#b:j,QXD`C`,?`6jfU);ui#jjd=eU + *_uHWlSsHSr-V3@++cZhm%q4%6M<41(tWZP%[KZde[^gnW"?pN,GPl46`OPs4:uM5<:^c]* + AVWi!pW5WA#37/;AK6)Z + ga2=eo!Fuk)D$;5XRJb_q$D:9[8+'lcRZ-CU3006lO8*4aU%4;tBNcf;8gXP$CK+e'H)$aT + 96c(^WDrUQ9/AWRM#nn2(sQ1Km>OhrN!na*gb_r3\\kU$_gFbf*(>ON/VLXUN4^OC=OFBue + W#knNR($a2&rD&;gFS=48A8ug6J@tooG-0U$]C/dfAU>6_`@qaS_1B4I+$9RE<+4ao%jT2, + Bt[CJ)/sBFA*Z;KoMF;j5\ahN`dN3mTUJSDUcJ*-kJ(ZV>4$Y_S$^bF)llMOJ6>CDY=7(`@ + ]l`C?$bg1l;LO,:>If4Mmf)bF3%aQc0CDEbCU[r[ + CSTtBDXmRE!I&alH26%"f>Q7oNP4?skNSq4=!W4^O$3*Kf_*!=qoBL5OWBQrpi!qs+j.,+" + a$rk4f>(RXZ_'Gl^:,p:;=X"AP?4V=M._S.U0jcf>PFPp]#g>/*ZeqanJC0`I6_=lhSNONK + )Ws`\X_f>9]h--fi/_]%Z&3$1$KC<2(a_\@T2P#3gEEn + aWZ`a$uYC"e`IXJ)aCf/;WKLlp%pne%tE3unbrJ00E_IXT26d?IDG>?&:QoaA/Kd3n:W"kT?sAa$UX@$.Y#? + Tj?!l>nH;2k6uaJF'1D@Y;\_`@A6Tj=nUtBi7.AE9)a[Em-^bklW:<9fsnKh)rV(-c=Df$ + ]k8i1V4.a%2_ZU;?FiWt00mZXH$ca2Y$D)hW??mJZ'j??`73HX:oEo64K(NR^, + $(H[mG:Teg>E8^@'$&)Tde[IqD2T2dg@Wnjo.:W"9,A:S"Q(;PjR0>6;jDmtYP@X=IO-O9p + eVfc*q1`CIApL7hRq?qXErBPejH5Ts3=VC;4o"-4N63!NWJ]E^SstP`3eeY,iKj0PDpV!D% + Wftj*XHfA\-aC/U6D\?I9m_jH*nlVdra@3qh&[p,Yalr]5HcSA%cf-j'TQ>quTXEE-s#@_f + 5ND$,H(Y#5Q;XHcGTd[bHQf[^TIFH[=R.j,#\>pH?[]cdNZ;K].+B+"ZD.D6#)F1o2]rA?X + 6@:Xh'UIoKKgST>4jd=6RMLhk,n)\>0"N + HCL8I4o6gR.e'i\OBA)bPoI%3F9:jc#Fo*9#U$I2H(TH[V[p7?`_aeD-'ooXWim:j*Zt/Oe + Ha4.^e"%p,YR3^/#DN(8U3$;AC5^dP#=i;koO4XBZ#sB&k%u;%Ut;e825f0a2eo@AVoZ + =1IcJURI`aY9d),H"L-Dp`r9dZOWcXN>i%,c17[.q'6GTRK"=hml-@2G4)\R,W5TgBO(-IG + kK/jlAWF$$`"O4hbLD7KZ6%gfGb62Ch]kkA2#S3Ze-3!WA?JF2"JC_qZ/\ + @9nT[.$Gb;@%poqiHAY_^],t5?o:SFrBWr(>f&W_/+8#\bFeSZH.[np12.<4Y3iUY0eE9nK + B3s`rbhDdCjkFjA[[,=_gO!pX8IK[#mFpB#qgdJhXi!(5aimrqNoo(XSR9LAWNK8.=C54]C + =S_>dC(=cg*5gX(=e1Dt + 5M]L[b86e+:A@JbWW#&V=?_"i67@!FYZjJ1q0+_No)ahn-@0./fHA3XA + ga$O'N^/>]_bR\>UON,b.>`gG?s@si9VERm4lR.h\4r.0rM(.602(F*I8H]-7I/u=ft-_N! + NOftGNW)qe&ch`UL'!g.ol"4G>19D8#DgRE,k+'sXF"5#M.hh(&GV9;&GOkb^li=@/Epg3U + -DdA8O5Uq=`^'D8W%:AmYe=,=Fa%oE]$^iL*I"RW32'\%FsQ,`gCWeB-NUe4nZs'?GM0J$B + HidC]gn@f^SoD\WIt."fJ(OStOPBE:!E?@FpbIDeVobPL!rFOCU,QT>]6Z'7i\%P,n087B + Wf_BZ1=`^'f,ImOZY41pW]6L=3JgpGUUrUo^sAj]B=$S]#&?G^Rm9H4CL9 + u5U+60Q5&b>,mZ*8la=F' + fSJ?l1\.Y+\1j1@.%@JHpg>510RhUM(@qE1)&f4]<)\lYIr,rkmLc2Y6ta6idPANjgM6[l] + \bg]an)/@T[!^k2X!Rg(QDpb6E:Th^QU!@4VqW\a@H]^$J/5,^4tDm:P62lrp8o!U`[kT=)!XMOq(R-%3YJj3,Mt&X + "$YK-XF'E:d`"9ML-"q,3o"XCr,>.7ECV!f`oE(@!Ll,sZ9?+30amB_b0)uS[QB*K5!sTL, + C1GHdCI_r)Z#7Ofeb2326+j748a_%4G67!K1I.Ha:UNrgR9ORkr7%K\)UsWo?Kb?f#(]$`7 + nnAH\7BZP.`qkY:9jr]5,)qCn]k^*TDB6JKQV)!kl665XCLPHOi2AU;!sW^7hC;Pb:2RkXl + fWapb%;"J7btSjn[0.td3+F7$^%&@uAKR7!m?ZggNODaebH`C&6G<3T"(YtH(B9.[DB4L@a + T[7ac595M'm$s"QNN>48'8X%ms/6]-,._-KK]4>3_F"OCc#`'.[#>"+r&u\=jF$&=@%P/$` + >RiPtD4@%,1P")n(i$I)4#=*T1Vd[_r"Q!7('\hY6$#8cr$3Ni5n&\rc"u!+I+T`ee\XGu_ + G&G'/CD=Ncj^(>6A&>coI6X<^GVbC-6]l.aiobGGI?Ag_[>TJ+QsLIbU/h(2EB3/'iA0:^/F$&4Z$)32+WEVQlRp1$[aRrT + !#,dRZqEVgbhjUl_ssS7-S;NNQBg6lbG94*PE/AU%iP8Nb)F&,H] + h!9!_Q`n:Xl2irD4"V:=U;XeC>Je`>_d^n%1[0;Me+<>sOh%X?*/Z3o)"&k^LmBMGU-UqrI@uZJ(O(>=4j%pb$3-G + cW?rg21-j4>Un#=.u)4*#l8$:g11[*IZq)#.e%I@XQ[PQAA^Op`4P6^dd@?tOO[TteU0AV' + U$rjC=G!H_od+&9q3C"A^J+[c\6#2lp=P9C!r5Md1#qAePAe2UX>=%^9hSm.Ki00Or<5E3* + \MD<X]iA/(qN$-G(!?a'5E?X\20<9tg/-5*Z&o6fbI + &8M^5t`\FSoL"'C&eQlJ1h)+@c6tK;rqU<%U:_BPS.tUo05?2P'[Z$@u4df(t_ca*0:JF8, + Kq;(c+'Tb!=pgn'F]V`m<]Lmp4&&[R`D^*fm9\UIhS&AML$I#Jl-oUp]e*>,X/EO[RRYcr` + sl8.?!TZpY2>fH?`j34^&26^rEnC[?g[kJrB%1qt[j:e#PB:#.^Zn9qHGs$@3:2Q$/&n_mF35[=/=Y#!7m?C0-UYceO?1^t>*,X1VPUAM6?8_c + BqO1UcpE7-q`"Z?RM4Phn\faGd?JS@_jkSRD)nK=Sb-J_-@_Y>V'h[Pihi9BG"p!FqYhY,P + `#]$81j2Zs&6->:a,HkF%S1,hT2d13>M_BjA+*Y"Cf05s;>nR@Fna[';.UeHAM(,t"9?Ig+ + 5E9Ra&U;\NQb_9Vp_uu5,HRhKBCR%V9rl%jmCJc7UR1:inZuJ6R=+&S_t8_YuJ<8nXNrWHf$tXh^dL>*-Ya"F_>$(B5Vj)7X/Q(9]m_oB,0 + =bjhXGDEFfSa?]>BPrd\YlJS6*VBJ+pN5(mTEKk(BBA/436-(iQFqI_ms7Ee1rgYo/3_CB. + 'ZN;j['/>\+W`LMURm:m3c#fG8&"C9ES,(!)IM+MeR9,Cfl)pLi^7"5OQqdt*V@ABaj=JS? + +IcAiPEmFaIO&%p,pn0,cC8:]XV\$4].VTn4U*@gAHZ?snE7KPdIo.MZdpS;DeNI_&Y"+g[UlhWL60n_ET=PA*pF7/IL]X5N!S?M + 6?pI`P><94"ajkF?=m2LI&CY@RW3@cgU#2LfQR5j\Kdgc`_i>]a5Io[8Ir?Cb(r7TrCF:eE + UVi,uTLr`sBdh/1t0D7o2W,bnsOgff$6k&SP9?H_#V/6]c&cS?hM[$@nnZ`e7%4eg98^moObJuZ`I$m!q0b( + Dc]O!7ppKN@\)^MJ)>D%R"Vn.+aIKE:F(? + KWAMn4X!_(C!-B#/k]kB]29sQ#GTS4dS0NnB$+qaa5X1(i]'*5kOd=+4Da-3gK + sEXO#@&Pfa>!?59:(MN,AE,if:'B9Y/mj`8lDf3/$k76 + &9-^\PLEEj$90he_8lr2s=P;_TgDPoDR,/UDdQZJG`mkKuGmo9-U4=Uma(MqSc%BjI.,C/s + 3Q0)dYB> + ZXq-0le`qFe0]_1GP]#8OMkiP"B34GiY`%(lHLJd.r@7)[`$.@1ZS[k&C6i:M8/Q"TeKfU/>URH:90: + rh`V.eG=kgV,OBoC=Ll6B!kjG=)6@`"bt!+Z<->bHUXg49"%r;oG`M(JNCAJB%M9$b4Q72? + Xe&d9V+rN_I7E9Hpc?tMoK7HcTHqP-'QJ_*a*m1B/Q1(B62a]Ne^&udV3gW*<)j9WljXMf^ + 4F6&[T>^Uf[l8qHnSshB>s&/^!$iUY;aA?:QB.bN;ndeNP9,KlZ*oB05P%AnepGGC.Z,f8% + ?*np,UGs$rr!g@e6%hYH&)jZ_o,?bHEXA"3ApIt,fB!O + JKUNS*/Jf0dQ-&-a[]m0-Co1)TdUpZS>^PPd^daVb==\*0$C.L_(_Cc*6BrUP<@5L*-5B<2 + U,kKiAf+]J\sOoe%G35cGp(`50q^V!C"h/*:l2;`t[>tS9%BM1==P[+YZR;jL$R3o":qqb& + WH5-HWPmPp=B@,`*Yb/Lrs+1!QEWgSHFcoXh];S*Ln?nTAhVf2^XOrRT,dm%MF5leqK1DcM + p1]f%VYpc^SVJtuK5SU5,*f"XJ_.VNGQ+:; + W=1Zjs>1^ahHJ=dVB6^l;:RK.IH_ZiUfk-ABkuqD]VWlf`T + r@6[SuJaTg:jhnlWW/UY3Xg>1OIh8%Vjljl#J\Fa)-KUbd?Pm9d!Du*CV2P)neB];j*+sF] + /,8/&9SRocq>De9;IEd@=P]`gi8p+[kkUSK#N;4>eA"bIe//^XE#IpF'r-B'sj1[2.nKmO* + bGc.,OP%[*YgJn8(E'rC;7C`VDYhSK%DR=AOh"iHsWhDu2,+*n]YqtL]RIH^N(G$UhU=f-V + J(7?Cm_Z^h)$fYoA)H2>rr:=B_S;H,rJ9J! + 4\XP%YQpWgha,P9:U$S-kTirrKG?PoH43*,&I"i7'3igLo"Lu^"(a(hF-7UbtNC<;SR?lqNs0/OqJX_m&jgB$pLVDGI%>2ciV7h&CZ^,H\\V=$5C\jAd31C+ + M>'0c&d/Ko7[68k!;C'\rU`*DnqA`711=>rn`uE>]lKK0!elC92SHC"aB4CgC3`7<_S^5Y) + X_#bGiFK9@8c_<<$JU_JfB00TY4BAj&jUm3:t+m$Q"Ka8&s,$4=qY=[p/-ONhA/oYgk4i/' + 7%ib_;d_3$',!@J'U?Ikq[@K,9B`t59lAuX7InE2N.AeN?([5R1.h4)lBE%QlbI::P]CrFF8(`7d>iXqA1c+Z.J#a#a0CeJ[YW@(/gT,FQdR + >d@/J5@-b5@ZPH;j63f3b[74,CFHQZ-:=oq!:QmRCeMJfiaq)U:o_!*M^[s?qO"Y8EhChQr + ='Id[$q)?Y(LN<5)nk;L^KgPgDG_WYSNCT1pU`177W5q>a5GZ6'mimAE>5/<9U + 8Zb+r7@PuXj7?E>ePVS-_qB;ZJnYZ;[]=R.8EXOWoL7SF]*!gOgL/<'\t8QI7@"t)del9crO=Q + <`TNGTFG=/H)f9-($pE:+qs\0nj]9Nl.*X + !P?AG4OipgLXB&NRbShhV;D+[I16pT@[:#K=`QEGc/V$3oP1O!qoaZXIEoF6>iK%:.>H5m[_i + >Bl's:gIBL+o_j<:pQ$c)DmJ?@_pqG\GdWK[a-5sL`V]75r7um-%4NB@0'BC-eORa_eaOqX + \A=SpJd;4oHlO@=KEqX`P_1H1+`Jb_A07bf(!mB8H!G0I=QA:Y@*c+E.i2'dmE,D(-nb$c` + I$pMc(-aB+P<0b#l(69dG/q/n#j7^PT.^D#`EE?TS1J6GoO.jL-dqW:_DcaijZn?E8`t%jk:b:[sFL4O%2R-FMFECK]=b@UbtgJi=N#mbm-,at + sPIe@Qb=l(+&B[RSM1)_Gd>]qHj_G+I?&DbHDP+1Cn'bg78@C7l$>\G%?3PM:k7DOo=hpL` + gKgV7uG\HolT)W3c6Io`_/A4N9q6.^J`J8bD_@@76$fEZBh5b-eGQcARSd'n(//g&%&D\;! + ^",_)\s2EBGg#Lk;O;,C3`gUfcjP,`YEh6:Rq.D;@gqT4t(6b`S'/@JL)"fLQC-D=K4m1Vu + On6/EGF:5EfU9_:F"Wa+qI`O_I%F\em\RG)Kfe?Y?KLiXU"&5.>*5@sY]_),f_[%-m+n:Qg + ?n.LR@?*>FW#7A^?$pr/)=M/.M&mW);718IIH"l^j$]7f=WJb[9a6ZGb5`1r+D#ZhET),*M + 4aUMsbjdB&]0&iR%!D\p0D0]lbDCkkum*o\Q4Gphs0']go3thS7]i+(ag;VmY3blcR@a%u_ + $9`qP2H6e'/u;n]ZZptm1SI%@>'n]/fJh`p=Q+/SW/Vq(Xl5-EW98#'+/$2^)RAp'Zjp&?E + $r84.=I\50es(C`thnSr9+6Cs*ot9%['CX&6[q[M(oDd/:!"]54:^6u^$NSNa!>#nF:_s.c + '#tG6rp$Z,XDJ!B.g"'N!t[6j:cAK<3s3ho";!p'95r?4Ylr0pcS.im:fdg^>6WA\"qY8K: + hKuoCBi-jn?>Z@/tiL=HO%oI#S;Uo:ko=)lnHi$a$4rs>:o=Y^Ws[4W$P + 9WP:pr`1Re:X1$kU;b:ra!+b7)bD%1ptt:tGR6]N3D4%M7Y1;!/=A/0H&~>Q +Q q +Q +showpage +%%Trailer +count op_count sub {pop} repeat +countdictstack dict_count sub {end} repeat +cairo_eps_state restore +%%EOF diff --git a/dune.module b/dune.module index b23c9c8fa16..4856d49cbdf 100644 --- a/dune.module +++ b/dune.module @@ -10,4 +10,4 @@ Label: 2024.10-pre Maintainer: atgeirr@sintef.no MaintainerName: Atgeirr F. Rasmussen Url: http://opm-project.org -Depends: dune-istl (>= 2.7) opm-common opm-grid opm-models +Depends: dune-istl (>= 2.7) opm-common opm-grid diff --git a/examples/art2dgf.cpp b/examples/art2dgf.cpp new file mode 100644 index 00000000000..a06b2825b79 --- /dev/null +++ b/examples/art2dgf.cpp @@ -0,0 +1,286 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ewoms { +/*! + * \brief Reads in mesh files in the ART format. + * + * This file format is used to specify grids with fractures. + */ + + struct Art2DGF + { + /*! + * \brief Create the Grid + */ + static void convert( const std::string& artFileName, + std::ostream& dgfFile, + const unsigned precision = 16 ) + { + using Scalar = double; + using GlobalPosition = Dune::FieldVector< Scalar, 2 >; + enum ParseMode { Vertex, Edge, Element, Finished }; + std::vector< std::pair > vertexPos; + std::vector > edges; + std::vector > fractureEdges; + std::vector > elements; + std::ifstream inStream(artFileName); + if (!inStream.is_open()) { + throw std::runtime_error("File '"+artFileName + +"' does not exist or is not readable"); + } + std::string curLine; + ParseMode curParseMode = Vertex; + while (inStream) { + std::getline(inStream, curLine); + + // remove comments + auto commentPos = curLine.find("%"); + if (commentPos != curLine.npos) { + curLine = curLine.substr(0, commentPos); + } + + // remove leading whitespace + unsigned numLeadingSpaces = 0; + while (curLine.size() > numLeadingSpaces + && std::isspace(curLine[numLeadingSpaces])) + ++numLeadingSpaces; + curLine = curLine.substr(numLeadingSpaces, + curLine.size() - numLeadingSpaces); + + // remove trailing whitespace + unsigned numTrailingSpaces = 0; + while (curLine.size() > numTrailingSpaces + && std::isspace(curLine[curLine.size() - numTrailingSpaces])) + ++numTrailingSpaces; + curLine = curLine.substr(0, curLine.size() - numTrailingSpaces); + + // a section of the file is finished, go to the next one + if (curLine == "$") { + if (curParseMode == Vertex) + curParseMode = Edge; + else if (curParseMode == Edge) + curParseMode = Element; + else if (curParseMode == Element) + curParseMode = Finished; + continue; + } + + // skip empty lines + if (curLine.empty()) + continue; + + if (curParseMode == Vertex) { + GlobalPosition coord; + std::istringstream iss(curLine); + // parse only the first two numbers as the vertex + // coordinate. the last number is the Z coordinate + // which we ignore (so far) + iss >> coord[0] >> coord[1]; + vertexPos.push_back( std::make_pair( coord, 0 ) ); + } + else if (curParseMode == Edge) { + // read an edge and update the fracture mapper + + // read the data attached to the edge + std::istringstream iss(curLine); + int dataVal; + std::string tmp; + iss >> dataVal; + iss >> tmp; + assert(tmp == ":"); + + // read the vertex indices of an edge + std::vector vertIndices; + while (iss) { + unsigned int tmp2; + iss >> tmp2; + if (!iss) + break; + vertIndices.push_back(tmp2); + assert(tmp2 < vertexPos.size()); + } + + // an edge always has two indices! + assert(vertIndices.size() == 2); + + std::pair edge(vertIndices[0], vertIndices[1]); + edges.push_back(edge); + + // add the edge to the fracture mapper if it is a fracture + if (dataVal < 0) { + fractureEdges.push_back(edge); + vertexPos[ edge.first ].second = 1; + vertexPos[ edge.second ].second = 1; + } + } + else if (curParseMode == Element) { + // skip the data attached to an element + std::istringstream iss(curLine); + int dataVal; + std::string tmp; + iss >> dataVal; + iss >> tmp; + assert(tmp == ":"); + + // read the edge indices of an element + std::vector edgeIndices; + while (iss) { + unsigned tmp2; + iss >> tmp2; + if (!iss) + break; + edgeIndices.push_back(tmp2); + assert(tmp2 < edges.size()); + } + + // so far, we only support triangles + assert(edgeIndices.size() == 3); + + // extract the vertex indices of the element + std::vector vertIndices; + for (unsigned i = 0; i < 3; ++i) { + bool haveFirstVertex = false; + for (unsigned j = 0; j < vertIndices.size(); ++j) { + assert(edgeIndices[i] < edges.size()); + if (vertIndices[j] == edges[edgeIndices[i]].first) { + haveFirstVertex = true; + break; + } + } + if (!haveFirstVertex) + vertIndices.push_back(edges[edgeIndices[i]].first); + + bool haveSecondVertex = false; + for (unsigned j = 0; j < vertIndices.size(); ++j) { + assert(edgeIndices[i] < edges.size()); + if (vertIndices[j] == edges[edgeIndices[i]].second) { + haveSecondVertex = true; + break; + } + } + if (!haveSecondVertex) + vertIndices.push_back(edges[edgeIndices[i]].second); + } + + // check whether the element's vertices are given in + // mathematically positive direction. if not, swap the + // first two. + Dune::FieldMatrix mat; + mat[0] = vertexPos[vertIndices[1]].first; + mat[0] -= vertexPos[vertIndices[0]].first; + mat[1] = vertexPos[vertIndices[2]].first; + mat[1] -= vertexPos[vertIndices[0]].first; + assert(std::abs(mat.determinant()) > 1e-50); + if (mat.determinant() < 0) + std::swap(vertIndices[2], vertIndices[1]); + + elements.push_back( vertIndices ); + } + else if (curParseMode == Finished) { + assert(curLine.size() == 0); + } + } + + dgfFile << "DGF" << std::endl << std::endl; + + dgfFile << "GridParameter" << std::endl + << "overlap 1" << std::endl + << "closure green" << std::endl + << "#" << std::endl << std::endl; + + dgfFile << "Vertex" << std::endl; + const bool hasFractures = fractureEdges.size() > 0; + if( hasFractures ) + { + dgfFile << "parameters 1" << std::endl; + } + dgfFile << std::scientific; + dgfFile.precision( precision ); + const size_t vxSize = vertexPos.size(); + for( size_t i=0; i. + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Box problem with two phases and multiple components. + * Solved with a PTFlash two phase solver. + */ +#include "config.h" + +#include +#include "problems/co2ptflashproblem.hh" + + +namespace Opm::Properties { + +namespace TTag { + struct CO2PTEcfvProblem { + using InheritsFrom = std::tuple; +}; +} + +template +struct SpatialDiscretizationSplice +{ + using type = TTag::EcfvDiscretization; +}; + +template +struct LocalLinearizerSplice +{ + using type = TTag::AutoDiffLocalLinearizer; +}; + + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using EcfvProblemTypeTag = Opm::Properties::TTag::CO2PTEcfvProblem; + return Opm::start(argc, argv); +} \ No newline at end of file diff --git a/examples/co2injection_flash_ecfv.cpp b/examples/co2injection_flash_ecfv.cpp new file mode 100644 index 00000000000..cce743128bb --- /dev/null +++ b/examples/co2injection_flash_ecfv.cpp @@ -0,0 +1,85 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the isothermal compositional model based on flash + * calculations. + */ +#include "config.h" + +#if HAVE_QUAD +#include +#endif + +#include +#include +#include +#include +#include "problems/co2injectionflash.hh" +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionFlashEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +// use automatic differentiation for this simulator +template +struct LocalLinearizerSplice +{ using type = TTag::AutoDiffLocalLinearizer; }; + +// use the flash solver adapted to the CO2 injection problem +template +struct FlashSolver +{ using type = Opm::Co2InjectionFlash, + GetPropType>; }; + +// the flash model has serious problems with the numerical +// precision. if quadruple precision math is available, we use it, +// else we increase the tolerance of the Newton solver +#if HAVE_QUAD +template +struct Scalar +{ using type = quad; }; +#endif + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using EcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionFlashEcfvProblem; +#if ! HAVE_QUAD + Opm::Co2InjectionTolerance = 1e-5; +#endif + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_flash_ni_ecfv.cpp b/examples/co2injection_flash_ni_ecfv.cpp new file mode 100644 index 00000000000..36f0a892134 --- /dev/null +++ b/examples/co2injection_flash_ni_ecfv.cpp @@ -0,0 +1,87 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the non-isothermal compositional model based on flash + * calculations. + */ +#include "config.h" + +// this must be included before the vanguard +#include + +#include +#include +#include +#include +#include "problems/co2injectionflash.hh" +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionFlashNiEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +template +struct EnableEnergy +{ static constexpr bool value = true; }; + +//! Use automatic differentiation to linearize the system of PDEs +template +struct LocalLinearizerSplice +{ using type = TTag::AutoDiffLocalLinearizer; }; + +// use the CO2 injection problem adapted flash solver +template +struct FlashSolver +{ using type = Opm::Co2InjectionFlash, + GetPropType>; }; + +// the flash model has serious problems with the numerical +// precision. if quadruple precision math is available, we use it, +// else we increase the tolerance of the Newton solver +#if HAVE_QUAD +template +struct Scalar +{ using type = quad; }; +#endif + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using EcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionFlashNiEcfvProblem; +#if ! HAVE_QUAD + Opm::Co2InjectionTolerance = 1e-5; +#endif + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_flash_ni_vcfv.cpp b/examples/co2injection_flash_ni_vcfv.cpp new file mode 100644 index 00000000000..f448a67fee4 --- /dev/null +++ b/examples/co2injection_flash_ni_vcfv.cpp @@ -0,0 +1,83 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the non-isothermal compositional model based on flash + * calculations. + */ +#include "config.h" + +// this must be included before the vanguard +#include + +#include +#include +#include +#include +#include "problems/co2injectionflash.hh" +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionFlashNiVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +template +struct EnableEnergy +{ static constexpr bool value = true; }; + +// use the CO2 injection problem adapted flash solver +template +struct FlashSolver +{ using type = Opm::Co2InjectionFlash, + GetPropType>; }; + +// the flash model has serious problems with the numerical +// precision. if quadruple precision math is available, we use it, +// else we increase the tolerance of the Newton solver +#if HAVE_QUAD +template +struct Scalar +{ using type = quad; }; +#endif + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using VcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionFlashNiVcfvProblem; +#if ! HAVE_QUAD + Opm::Co2InjectionTolerance = 1e-5; +#endif + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_flash_vcfv.cpp b/examples/co2injection_flash_vcfv.cpp new file mode 100644 index 00000000000..d4a100e34a3 --- /dev/null +++ b/examples/co2injection_flash_vcfv.cpp @@ -0,0 +1,79 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the isothermal compositional model based on flash + * calculations. + */ +#include "config.h" + +#if HAVE_QUAD +#include +#endif + +#include +#include +#include +#include +#include "problems/co2injectionflash.hh" +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionFlashVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +// use the flash solver adapted to the CO2 injection problem +template +struct FlashSolver +{ using type = Opm::Co2InjectionFlash, + GetPropType>; }; + +// the flash model has serious problems with the numerical +// precision. if quadruple precision math is available, we use it, +// else we increase the tolerance of the Newton solver +#if HAVE_QUAD +template +struct Scalar +{ using type = quad; }; +#endif + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using VcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionFlashVcfvProblem; +#if ! HAVE_QUAD + Opm::Co2InjectionTolerance = 1e-5; +#endif + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_immiscible_ecfv.cpp b/examples/co2injection_immiscible_ecfv.cpp new file mode 100644 index 00000000000..7e72e6867a9 --- /dev/null +++ b/examples/co2injection_immiscible_ecfv.cpp @@ -0,0 +1,60 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the isothermal immiscible model using the CO2 injection + * example problem + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +namespace TTag { + +struct Co2InjectionImmiscibleEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +} // namespace Opm::Properties + +//////////////////////// +// the main function +//////////////////////// +int main(int argc, char **argv) +{ + using EcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionImmiscibleEcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_immiscible_ni_ecfv.cpp b/examples/co2injection_immiscible_ni_ecfv.cpp new file mode 100644 index 00000000000..2c02575496a --- /dev/null +++ b/examples/co2injection_immiscible_ni_ecfv.cpp @@ -0,0 +1,68 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Simulation of the injection problem using the VCVF discretization + * assuming immisicibility and with energy enabled. + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct Co2InjectionImmiscibleNiEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +template +struct EnableEnergy +{ static constexpr bool value = true; }; + +//! Use automatic differentiation to linearize the system of PDEs +template +struct LocalLinearizerSplice +{ using type = TTag::AutoDiffLocalLinearizer; }; + +} // namespace Opm::Properties + +//////////////////////// +// the main function +//////////////////////// +int main(int argc, char **argv) +{ + using EcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionImmiscibleNiEcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_immiscible_ni_vcfv.cpp b/examples/co2injection_immiscible_ni_vcfv.cpp new file mode 100644 index 00000000000..a1bf11a65db --- /dev/null +++ b/examples/co2injection_immiscible_ni_vcfv.cpp @@ -0,0 +1,63 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Simulation of the injection problem using the VCVF discretization + * assuming immisicibility and with energy enabled. + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +namespace TTag { + +struct Co2InjectionImmiscibleNiVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +template +struct EnableEnergy +{ static constexpr bool value = true; }; + +} // namespace Opm::Properties + +//////////////////////// +// the main function +//////////////////////// +int main(int argc, char **argv) +{ + using VcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionImmiscibleNiVcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_immiscible_vcfv.cpp b/examples/co2injection_immiscible_vcfv.cpp new file mode 100644 index 00000000000..31b722210af --- /dev/null +++ b/examples/co2injection_immiscible_vcfv.cpp @@ -0,0 +1,60 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the isothermal immiscible model using the CO2 injection + * example problem + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +namespace TTag { + +struct Co2InjectionImmiscibleVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +} // namespace Opm::Properties + +//////////////////////// +// the main function +//////////////////////// +int main(int argc, char **argv) +{ + using VcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionImmiscibleVcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_ncp_ecfv.cpp b/examples/co2injection_ncp_ecfv.cpp new file mode 100644 index 00000000000..69c5179ddf7 --- /dev/null +++ b/examples/co2injection_ncp_ecfv.cpp @@ -0,0 +1,57 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the isothermal VCVF discretization based on non-linear + * complementarity problems. + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionNcpEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using EcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionNcpEcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_ncp_ni_ecfv.cpp b/examples/co2injection_ncp_ni_ecfv.cpp new file mode 100644 index 00000000000..0c44fcaa2c8 --- /dev/null +++ b/examples/co2injection_ncp_ni_ecfv.cpp @@ -0,0 +1,66 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the non-isothermal VCVF discretization based on non-linear + * complementarity problems. + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionNcpNiEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +template +struct EnableEnergy +{ static constexpr bool value = true; }; + +//! Use automatic differentiation to linearize the system of PDEs +template +struct LocalLinearizerSplice +{ using type = TTag::AutoDiffLocalLinearizer; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using EcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionNcpNiEcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_ncp_ni_vcfv.cpp b/examples/co2injection_ncp_ni_vcfv.cpp new file mode 100644 index 00000000000..3aeea9598d4 --- /dev/null +++ b/examples/co2injection_ncp_ni_vcfv.cpp @@ -0,0 +1,60 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the non-isothermal VCVF discretization based on non-linear + * complementarity problems. + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionNcpNiVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +template +struct EnableEnergy +{ static constexpr bool value = true; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using VcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionNcpNiVcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_ncp_vcfv.cpp b/examples/co2injection_ncp_vcfv.cpp new file mode 100644 index 00000000000..474ca007bcc --- /dev/null +++ b/examples/co2injection_ncp_vcfv.cpp @@ -0,0 +1,58 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the isothermal VCVF discretization based on non-linear + * complementarity problems. + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionNcpVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using VcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionNcpVcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_pvs_ecfv.cpp b/examples/co2injection_pvs_ecfv.cpp new file mode 100644 index 00000000000..d0e2f63e9cd --- /dev/null +++ b/examples/co2injection_pvs_ecfv.cpp @@ -0,0 +1,57 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the isothermal primary variable switching model + * using the ECVF discretization. + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionPvsEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using EcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionPvsEcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_pvs_ni_ecfv.cpp b/examples/co2injection_pvs_ni_ecfv.cpp new file mode 100644 index 00000000000..25d86bbf41a --- /dev/null +++ b/examples/co2injection_pvs_ni_ecfv.cpp @@ -0,0 +1,65 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the non-isothermal primary variable switching VCVF + * discretization. + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionPvsNiEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +template +struct EnableEnergy +{ static constexpr bool value = true; }; + +//! Use automatic differentiation to linearize the system of PDEs +template +struct LocalLinearizerSplice +{ using type = TTag::AutoDiffLocalLinearizer; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using EcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionPvsNiEcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_pvs_ni_vcfv.cpp b/examples/co2injection_pvs_ni_vcfv.cpp new file mode 100644 index 00000000000..a6c1f59eb3e --- /dev/null +++ b/examples/co2injection_pvs_ni_vcfv.cpp @@ -0,0 +1,61 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the non-isothermal primary variable switching model + * using the VCVF discretization. + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionPvsNiVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +template +struct EnableEnergy +{ static constexpr bool value = true; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using VcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionPvsNiVcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/co2injection_pvs_vcfv.cpp b/examples/co2injection_pvs_vcfv.cpp new file mode 100644 index 00000000000..e3696db39dd --- /dev/null +++ b/examples/co2injection_pvs_vcfv.cpp @@ -0,0 +1,58 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the isothermal primary variable switching model using the VCVF + * discretization. + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/co2injectionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct Co2InjectionPvsVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using VcfvProblemTypeTag = Opm::Properties::TTag::Co2InjectionPvsVcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/cuvette_pvs.cpp b/examples/cuvette_pvs.cpp new file mode 100644 index 00000000000..1ed50fa8740 --- /dev/null +++ b/examples/cuvette_pvs.cpp @@ -0,0 +1,52 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief test for the 3p3cni VCVF discretization + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/cuvetteproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct CuvetteProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::CuvetteProblem; + return Opm::start(argc, argv); +} diff --git a/examples/diffusion_flash.cpp b/examples/diffusion_flash.cpp new file mode 100644 index 00000000000..214680a7184 --- /dev/null +++ b/examples/diffusion_flash.cpp @@ -0,0 +1,48 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the Forchheimer velocity model + */ +#include "config.h" + +#include +#include +#include +#include "problems/diffusionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct DiffusionProblem { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::DiffusionProblem; + return Opm::start(argc, argv); +} diff --git a/examples/diffusion_ncp.cpp b/examples/diffusion_ncp.cpp new file mode 100644 index 00000000000..bbf1f4589ce --- /dev/null +++ b/examples/diffusion_ncp.cpp @@ -0,0 +1,49 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the Forchheimer velocity model + */ +#include "config.h" + +#include +#include +#include + +#include "problems/diffusionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct DiffusionProblem { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::DiffusionProblem; + return Opm::start(argc, argv); +} diff --git a/examples/diffusion_pvs.cpp b/examples/diffusion_pvs.cpp new file mode 100644 index 00000000000..0c7132ca068 --- /dev/null +++ b/examples/diffusion_pvs.cpp @@ -0,0 +1,48 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the Forchheimer velocity model + */ +#include "config.h" + +#include +#include +#include +#include "problems/diffusionproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct DiffusionProblem { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::DiffusionProblem; + return Opm::start(argc, argv); +} diff --git a/examples/finger_immiscible_ecfv.cpp b/examples/finger_immiscible_ecfv.cpp new file mode 100644 index 00000000000..399976cd0dc --- /dev/null +++ b/examples/finger_immiscible_ecfv.cpp @@ -0,0 +1,52 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Problem featuring a saturation overshoot. + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/fingerproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct FingerProblemEcfv { using InheritsFrom = std::tuple; }; +} // end namespace TTag +template +struct SpatialDiscretizationSplice { using type = TTag::EcfvDiscretization; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::FingerProblemEcfv; + return Opm::start(argc, argv); +} diff --git a/examples/finger_immiscible_vcfv.cpp b/examples/finger_immiscible_vcfv.cpp new file mode 100644 index 00000000000..d6a78c77d7f --- /dev/null +++ b/examples/finger_immiscible_vcfv.cpp @@ -0,0 +1,52 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Problem featuring a saturation overshoot. + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/fingerproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct FingerProblemVcfv { using InheritsFrom = std::tuple; }; +} // end namespace TTag +template +struct SpatialDiscretizationSplice { using type = TTag::VcfvDiscretization; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::FingerProblemVcfv; + return Opm::start(argc, argv); +} diff --git a/examples/fracture_discretefracture.cpp b/examples/fracture_discretefracture.cpp new file mode 100644 index 00000000000..6355e6a1c75 --- /dev/null +++ b/examples/fracture_discretefracture.cpp @@ -0,0 +1,39 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Two-phase problem test with fractures. + */ +#include "config.h" + +#include +#include + +#include "problems/fractureproblem.hh" + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::FractureProblem; + return Opm::start(argc, argv); +} diff --git a/examples/groundwater_immiscible.cpp b/examples/groundwater_immiscible.cpp new file mode 100644 index 00000000000..cb5e1b7f4c4 --- /dev/null +++ b/examples/groundwater_immiscible.cpp @@ -0,0 +1,51 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the immisicible VCVF discretization with only a single phase + */ +#include "config.h" + +#include +#include +#include +#include "problems/groundwaterproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct GroundWaterProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::GroundWaterProblem; + return Opm::start(argc, argv); +} diff --git a/examples/infiltration_pvs.cpp b/examples/infiltration_pvs.cpp new file mode 100644 index 00000000000..998aa1fc450 --- /dev/null +++ b/examples/infiltration_pvs.cpp @@ -0,0 +1,50 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief test for the primary variable switching VCVF discretization + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/infiltrationproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct InfiltrationProblem +{ using InheritsFrom = std::tuple; }; +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::InfiltrationProblem; + return Opm::start(argc, argv); +} diff --git a/examples/lens_immiscible_ecfv_ad.cpp b/examples/lens_immiscible_ecfv_ad.cpp new file mode 100644 index 00000000000..03e326e442c --- /dev/null +++ b/examples/lens_immiscible_ecfv_ad.cpp @@ -0,0 +1,40 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Two-phase test for the immiscible model which uses the element-centered finite + * volume discretization in conjunction with automatic differentiation + */ +#include "config.h" + +#include "lens_immiscible_ecfv_ad.hh" + +#include +#include + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::LensProblemEcfvAd; + return Opm::start(argc, argv); +} diff --git a/examples/lens_immiscible_ecfv_ad.hh b/examples/lens_immiscible_ecfv_ad.hh new file mode 100644 index 00000000000..609fcb4e7d4 --- /dev/null +++ b/examples/lens_immiscible_ecfv_ad.hh @@ -0,0 +1,57 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Two-phase test for the immiscible model which uses the element-centered finite + * volume discretization in conjunction with automatic differentiation + */ +#ifndef EWOMS_LENS_IMMISCIBLE_ECFV_AD_HH +#define EWOMS_LENS_IMMISCIBLE_ECFV_AD_HH + +#include +#include +#include "problems/lensproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct LensProblemEcfvAd { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +// use the element centered finite volume spatial discretization +template +struct SpatialDiscretizationSplice { using type = TTag::EcfvDiscretization; }; + +// use automatic differentiation for this simulator +template +struct LocalLinearizerSplice { using type = TTag::AutoDiffLocalLinearizer; }; + +// this problem works fine if the linear solver uses single precision scalars +template +struct LinearSolverScalar { using type = float; }; + +} // namespace Opm::Properties + +#endif // EWOMS_LENS_IMMISCIBLE_ECFV_AD_HH diff --git a/examples/lens_immiscible_ecfv_ad_23.cpp b/examples/lens_immiscible_ecfv_ad_23.cpp new file mode 100644 index 00000000000..8de51c32365 --- /dev/null +++ b/examples/lens_immiscible_ecfv_ad_23.cpp @@ -0,0 +1,93 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Two-phase test for the immiscible model which uses the element-centered finite + * volume discretization in conjunction with automatic differentiation + */ +#include "config.h" + +#include "lens_immiscible_ecfv_ad.hh" + +#include +#include + +#include + +namespace Opm::Properties { + +// Use Dune-grid's GeometryGrid< YaspGrid > +template +struct Grid +{ + template< class ctype, unsigned int dim, unsigned int dimworld > + class IdentityCoordFct + : public Dune::AnalyticalCoordFunction + < ctype, dim, dimworld, IdentityCoordFct< ctype, dim, dimworld > > + { + using This = IdentityCoordFct< ctype, dim, dimworld >; + using Base = Dune::AnalyticalCoordFunction< ctype, dim, dimworld, This >; + + public: + using DomainVector = typename Base :: DomainVector; + using RangeVector = typename Base :: RangeVector ; + + template< typename... Args > + IdentityCoordFct( Args&... ) + {} + + RangeVector operator()(const DomainVector& x) const + { + RangeVector y; + evaluate( x, y ); + return y; + } + + void evaluate( const DomainVector &x, RangeVector &y ) const + { + y = 0; + for( unsigned int i = 0; i; + +public: + using type = Dune::GeometryGrid< MyYaspGrid, + IdentityCoordFct< typename MyYaspGrid::ctype, + MyYaspGrid::dimension, + MyYaspGrid::dimensionworld+1> >; +}; + +} // namespace Opm::Properties + +#include + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::LensProblemEcfvAd; + return Opm::start(argc, argv); +} diff --git a/examples/lens_immiscible_ecfv_ad_cu1.cpp b/examples/lens_immiscible_ecfv_ad_cu1.cpp new file mode 100644 index 00000000000..9b695caaecd --- /dev/null +++ b/examples/lens_immiscible_ecfv_ad_cu1.cpp @@ -0,0 +1,49 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This test is identical to the simulation of the lens problem that uses the + * element centered finite volume discretization in conjunction with automatic + * differentiation (lens_immiscible_ecfv_ad). + * + * The only difference is that it uses multiple compile units in order to ensure that + * eWoms code can be used within libraries that use the same type tag within multiple + * compile units. This file represents the first compile unit and just defines an startup + * function for the simulator. + */ +#include "config.h" + +#include "lens_immiscible_ecfv_ad.hh" + +#include +#include + +// fake forward declaration to prevent esoteric compiler warning +int mainCU1(int argc, char **argv); + +int mainCU1(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::LensProblemEcfvAd; + return Opm::start(argc, argv); +} diff --git a/examples/lens_immiscible_ecfv_ad_cu2.cpp b/examples/lens_immiscible_ecfv_ad_cu2.cpp new file mode 100644 index 00000000000..6742f8bc809 --- /dev/null +++ b/examples/lens_immiscible_ecfv_ad_cu2.cpp @@ -0,0 +1,49 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This test is identical to the simulation of the lens problem that uses the + * element centered finite volume discretization in conjunction with automatic + * differentiation (lens_immiscible_ecfv_ad). + * + * The only difference is that it uses multiple compile units in order to ensure that + * eWoms code can be used within libraries that use the same type tag within multiple + * compile units. This file represents the second compile unit and just defines an + * startup function for the simulator. + */ +#include "config.h" + +#include "lens_immiscible_ecfv_ad.hh" + +#include +#include + +// fake forward declaration to prevent esoteric compiler warning +int mainCU2(int argc, char **argv); + +int mainCU2(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::LensProblemEcfvAd; + return Opm::start(argc, argv); +} diff --git a/examples/lens_immiscible_ecfv_ad_main.cpp b/examples/lens_immiscible_ecfv_ad_main.cpp new file mode 100644 index 00000000000..8e40ee737d4 --- /dev/null +++ b/examples/lens_immiscible_ecfv_ad_main.cpp @@ -0,0 +1,41 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This test is identical to the simulation of the lens problem that uses the + * element centered finite volume discretization in conjunction with automatic + * differentiation (lens_immiscible_ecfv_ad). + * + * The only difference is that it uses multiple compile units in order to ensure that + * eWoms code can be used within libraries that use the same type tag within multiple + * compile units. This file calls contains main() and just calls the main entry point + * defined by the first compile unit. + */ +int mainCU1(int argc, char **argv); +int mainCU2(int argc, char **argv); + +int main(int argc, char **argv) +{ + return mainCU1(argc, argv); +} diff --git a/examples/lens_immiscible_ecfv_ad_trans.cpp b/examples/lens_immiscible_ecfv_ad_trans.cpp new file mode 100644 index 00000000000..bf6de79af38 --- /dev/null +++ b/examples/lens_immiscible_ecfv_ad_trans.cpp @@ -0,0 +1,66 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Two-phase test for the immiscible model which uses the element-centered finite + * volume discretization with two-point-flux using the transmissibility module + * in conjunction with automatic differentiation + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/lensproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct LensProblemEcfvAdTrans { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +// use automatic differentiation for this simulator +template +struct LocalLinearizerSplice { using type = TTag::AutoDiffLocalLinearizer; }; + +// use the element centered finite volume spatial discretization +template +struct SpatialDiscretizationSplice { using type = TTag::EcfvDiscretization; }; + +// Set the problem property +template +struct FluxModule { + using type = TransFluxModule; +}; + +} + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::LensProblemEcfvAdTrans; + return Opm::start(argc, argv); +} diff --git a/examples/lens_immiscible_vcfv_ad.cpp b/examples/lens_immiscible_vcfv_ad.cpp new file mode 100644 index 00000000000..32ef6d9d30a --- /dev/null +++ b/examples/lens_immiscible_vcfv_ad.cpp @@ -0,0 +1,60 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Two-phase test for the immiscible model which uses the + * vertex-centered finite volume discretization + */ +#include "config.h" + +#include +#include +#include + +#include "problems/lensproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct LensProblemVcfvAd { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +// use automatic differentiation for this simulator +template +struct LocalLinearizerSplice { using type = TTag::AutoDiffLocalLinearizer; }; + +// use linear finite element gradients if dune-localfunctions is available +#if HAVE_DUNE_LOCALFUNCTIONS +template +struct UseP1FiniteElementGradients { static constexpr bool value = true; }; +#endif + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::LensProblemVcfvAd; + return Opm::start(argc, argv); +} diff --git a/examples/lens_immiscible_vcfv_fd.cpp b/examples/lens_immiscible_vcfv_fd.cpp new file mode 100644 index 00000000000..e3dab835553 --- /dev/null +++ b/examples/lens_immiscible_vcfv_fd.cpp @@ -0,0 +1,60 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Two-phase test for the immiscible model which uses the + * vertex-centered finite volume discretization + */ +#include "config.h" + +#include +#include +#include + +#include "problems/lensproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct LensProblemVcfvFd { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +// use the finite difference methodfor this simulator +template +struct LocalLinearizerSplice { using type = TTag::FiniteDifferenceLocalLinearizer; }; + +// use linear finite element gradients if dune-localfunctions is available +#if HAVE_DUNE_LOCALFUNCTIONS +template +struct UseP1FiniteElementGradients { static constexpr bool value = true; }; +#endif + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::LensProblemVcfvFd; + return Opm::start(argc, argv); +} diff --git a/examples/lens_richards_ecfv.cpp b/examples/lens_richards_ecfv.cpp new file mode 100644 index 00000000000..51c5976f329 --- /dev/null +++ b/examples/lens_richards_ecfv.cpp @@ -0,0 +1,61 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the Richards model using the ECFV discretization. + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/richardslensproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct RichardsLensEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +//! Use automatic differentiation to linearize the system of PDEs +template +struct LocalLinearizerSplice +{ using type = TTag::AutoDiffLocalLinearizer; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::RichardsLensEcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/lens_richards_vcfv.cpp b/examples/lens_richards_vcfv.cpp new file mode 100644 index 00000000000..bcded2002ad --- /dev/null +++ b/examples/lens_richards_vcfv.cpp @@ -0,0 +1,57 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the Richards model using the VCFV discretization. + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/richardslensproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct RichardsLensVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::RichardsLensVcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/obstacle_immiscible.cpp b/examples/obstacle_immiscible.cpp new file mode 100644 index 00000000000..3c11904f4b2 --- /dev/null +++ b/examples/obstacle_immiscible.cpp @@ -0,0 +1,52 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ + +/* + * \file + * + * \brief Test for the immiscible multi-phase VCVF discretization. + */ +#include "config.h" + +#include +#include +#include +#include +#include "problems/obstacleproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct ObstacleProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::ObstacleProblem; + return Opm::start(argc, argv); +} diff --git a/examples/obstacle_ncp.cpp b/examples/obstacle_ncp.cpp new file mode 100644 index 00000000000..28b08e29d38 --- /dev/null +++ b/examples/obstacle_ncp.cpp @@ -0,0 +1,53 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the compositional NCP VCVF discretization. + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/obstacleproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct ObstacleProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::ObstacleProblem; + return Opm::start(argc, argv); +} diff --git a/examples/obstacle_pvs.cpp b/examples/obstacle_pvs.cpp new file mode 100644 index 00000000000..c5d8a9f9498 --- /dev/null +++ b/examples/obstacle_pvs.cpp @@ -0,0 +1,55 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ + +/* + * \file + * + * \brief Test for the isothermal primary variable switching model + * using "obstacle" problem and the VCVF discretization. + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/obstacleproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct ObstacleProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::ObstacleProblem; + return Opm::start(argc, argv); +} diff --git a/examples/outflow_pvs.cpp b/examples/outflow_pvs.cpp new file mode 100644 index 00000000000..7122dc9cd44 --- /dev/null +++ b/examples/outflow_pvs.cpp @@ -0,0 +1,53 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief test for the compositional PVS VCVF discretization + */ +#include "config.h" + +#include +#include +#include +#include + +#include "problems/outflowproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct OutflowProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::OutflowProblem; + return Opm::start(argc, argv); +} diff --git a/examples/powerinjection_darcy_ad.cpp b/examples/powerinjection_darcy_ad.cpp new file mode 100644 index 00000000000..1880d0cc0f4 --- /dev/null +++ b/examples/powerinjection_darcy_ad.cpp @@ -0,0 +1,55 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the Forchheimer velocity model + */ +#include "config.h" + +#include +#include +#include +#include "problems/powerinjectionproblem.hh" + +namespace Opm::Properties { + +namespace TTag { + +struct PowerInjectionDarcyAdProblem +{ using InheritsFrom = std::tuple; }; + +} // namespace TTag + +template +struct FluxModule { using type = Opm::DarcyFluxModule; }; +template +struct LocalLinearizerSplice { using type = TTag::AutoDiffLocalLinearizer; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::PowerInjectionDarcyAdProblem; + return Opm::start(argc, argv); +} diff --git a/examples/powerinjection_darcy_fd.cpp b/examples/powerinjection_darcy_fd.cpp new file mode 100644 index 00000000000..77bfa9b6137 --- /dev/null +++ b/examples/powerinjection_darcy_fd.cpp @@ -0,0 +1,55 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the Forchheimer velocity model + */ +#include "config.h" + +#include +#include +#include +#include "problems/powerinjectionproblem.hh" + +namespace Opm::Properties { + +namespace TTag { + +struct PowerInjectionDarcyFdProblem +{ using InheritsFrom = std::tuple; }; + +} // namespace TTag + +template +struct FluxModule { using type = Opm::DarcyFluxModule; }; +template +struct LocalLinearizerSplice { using type = TTag::FiniteDifferenceLocalLinearizer; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::PowerInjectionDarcyFdProblem; + return Opm::start(argc, argv); +} diff --git a/examples/powerinjection_forchheimer_ad.cpp b/examples/powerinjection_forchheimer_ad.cpp new file mode 100644 index 00000000000..01da9c57895 --- /dev/null +++ b/examples/powerinjection_forchheimer_ad.cpp @@ -0,0 +1,55 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the Forchheimer velocity model + */ +#include "config.h" + +#include +#include +#include +#include "problems/powerinjectionproblem.hh" + +namespace Opm::Properties { + +namespace TTag { + +struct PowerInjectionForchheimerAdProblem +{ using InheritsFrom = std::tuple; }; + +} // namespace TTag + +template +struct FluxModule { using type = Opm::ForchheimerFluxModule; }; +template +struct LocalLinearizerSplice { using type = TTag::AutoDiffLocalLinearizer; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::PowerInjectionForchheimerAdProblem; + return Opm::start(argc, argv); +} diff --git a/examples/powerinjection_forchheimer_fd.cpp b/examples/powerinjection_forchheimer_fd.cpp new file mode 100644 index 00000000000..bbcc7e1c519 --- /dev/null +++ b/examples/powerinjection_forchheimer_fd.cpp @@ -0,0 +1,55 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the Forchheimer velocity model + */ +#include "config.h" + +#include +#include +#include +#include "problems/powerinjectionproblem.hh" + +namespace Opm::Properties { + +namespace TTag { + +struct PowerInjectionForchheimerFdProblem +{ using InheritsFrom = std::tuple; }; + +} // namespace TTag + +template +struct FluxModule { using type = Opm::ForchheimerFluxModule; }; +template +struct LocalLinearizerSplice { using type = TTag::FiniteDifferenceLocalLinearizer; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::PowerInjectionForchheimerFdProblem; + return Opm::start(argc, argv); +} diff --git a/examples/problems/co2injectionflash.hh b/examples/problems/co2injectionflash.hh new file mode 100644 index 00000000000..37824ab222d --- /dev/null +++ b/examples/problems/co2injectionflash.hh @@ -0,0 +1,66 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::Co2InjectionFlash + */ +#ifndef EWOMS_CO2_INJECTION_FLASH_HH +#define EWOMS_CO2_INJECTION_FLASH_HH + +#include + +namespace Opm { +/*! + * \brief Flash solver used by the CO2 injection problem. + * + * This class is just the NCP flash solver with the guessInitial() + * method that is adapted to the pressure regime of the CO2 injection + * problem. + */ +template +class Co2InjectionFlash : public Opm::NcpFlash +{ + using ParentType = Opm::NcpFlash; + + enum { numPhases = FluidSystem::numPhases }; + +public: + /*! + * \brief Guess initial values for all quantities. + */ + template + static void guessInitial(FluidState& fluidState, const ComponentVector& globalMolarities) + { + ParentType::guessInitial(fluidState, globalMolarities); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // pressure. use something close to the reservoir pressure as initial guess + fluidState.setPressure(phaseIdx, 100e5); + } + } +}; + +} // namespace Opm + +#endif // EWOMS_CO2_INJECTION_FLASH_HH diff --git a/examples/problems/co2injectionproblem.hh b/examples/problems/co2injectionproblem.hh new file mode 100644 index 00000000000..6a2ae04c451 --- /dev/null +++ b/examples/problems/co2injectionproblem.hh @@ -0,0 +1,648 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::Co2InjectionProblem + */ +#ifndef EWOMS_CO2_INJECTION_PROBLEM_HH +#define EWOMS_CO2_INJECTION_PROBLEM_HH + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace Opm { + +//! \cond SKIP_THIS +template +class Co2InjectionProblem; +//! \endcond + +} + +namespace Opm::Properties { + +namespace TTag { +struct Co2InjectionBaseProblem {}; +} + +// Set the grid type +template +struct Grid { using type = Dune::YaspGrid<2>; }; + +// Set the problem property +template +struct Problem +{ using type = Opm::Co2InjectionProblem; }; + +// Set fluid configuration +template +struct FluidSystem +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::BrineCO2FluidSystem; + //using type = Opm::H2ON2FluidSystem; +}; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using FluidSystem = GetPropType; + enum { liquidPhaseIdx = FluidSystem::liquidPhaseIdx }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + + using Scalar = GetPropType; + using Traits = Opm::TwoPhaseMaterialTraits; + + // define the material law which is parameterized by effective + // saturations + using EffMaterialLaw = Opm::RegularizedBrooksCorey; + +public: + // define the material law parameterized by absolute saturations + using type = Opm::EffToAbsLaw; +}; + +// Set the thermal conduction law +template +struct ThermalConductionLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + +public: + // define the material law parameterized by absolute saturations + using type = Opm::SomertonThermalConductionLaw; +}; + +// set the energy storage law for the solid phase +template +struct SolidEnergyLaw +{ using type = Opm::ConstantSolidHeatCapLaw>; }; + +// Use the algebraic multi-grid linear solver for this problem +template +struct LinearSolverSplice { using type = TTag::ParallelAmgLinearSolver; }; + +} // namespace Opm::Properties + +namespace Opm::Parameters { + +struct FluidSystemNumPressure { static constexpr unsigned value = 100; }; +struct FluidSystemNumTemperature { static constexpr unsigned value = 100; }; + +template +struct FluidSystemPressureHigh { static constexpr Scalar value = 4e7; }; + +template +struct FluidSystemPressureLow { static constexpr Scalar value = 3e7; }; + +template +struct FluidSystemTemperatureHigh { static constexpr Scalar value = 500.0; }; + +template +struct FluidSystemTemperatureLow { static constexpr Scalar value = 290.0; }; + +template +struct MaxDepth { static constexpr Scalar value = 2500.0; }; + +struct SimulationName { static constexpr auto value = "co2injection"; }; + +template +struct Temperature { static constexpr Scalar value = 293.15; }; + +} // namespace Opm::Parameters + +namespace Opm { + +double Co2InjectionTolerance = 1e-8; + +/*! + * \ingroup TestProblems + * + * \brief Problem where \f$CO_2\f$ is injected under a low permeable + * layer at a depth of 2700m. + * + * The domain is sized 60m times 40m and consists of two layers, one + * which is moderately permeable (\f$K = 10^{-12}\;m^2\f$) for \f$ y > + * 22\; m\f$ and one with a lower intrinsic permeablility (\f$ + * K=10^{-13}\;m^2\f$) in the rest of the domain. + * + * \f$CO_2\f$ gets injected by means of a forced-flow boundary + * condition into water-filled aquifer, which is situated 2700m below + * sea level, at the lower-right boundary (\f$5m +class Co2InjectionProblem : public GetPropType +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using GridView = GetPropType; + using FluidSystem = GetPropType; + + enum { dim = GridView::dimension }; + enum { dimWorld = GridView::dimensionworld }; + + // copy some indices for convenience + using Indices = GetPropType; + enum { numPhases = FluidSystem::numPhases }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { liquidPhaseIdx = FluidSystem::liquidPhaseIdx }; + enum { CO2Idx = FluidSystem::CO2Idx }; + enum { BrineIdx = FluidSystem::BrineIdx }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { contiCO2EqIdx = conti0EqIdx + CO2Idx }; + + using PrimaryVariables = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using MaterialLaw = GetPropType; + using Simulator = GetPropType; + using Model = GetPropType; + using MaterialLawParams = GetPropType; + using ThermalConductionLaw = GetPropType; + using SolidEnergyLawParams = GetPropType; + using ThermalConductionLawParams = typename ThermalConductionLaw::Params; + + using Toolbox = Opm::MathToolbox; + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + Co2InjectionProblem(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + eps_ = 1e-6; + + temperatureLow_ = Parameters::Get>(); + temperatureHigh_ = Parameters::Get>(); + nTemperature_ = Parameters::Get(); + + pressureLow_ = Parameters::Get>(); + pressureHigh_ = Parameters::Get>(); + nPressure_ = Parameters::Get(); + + maxDepth_ = Parameters::Get>(); + temperature_ = Parameters::Get>(); + + // initialize the tables of the fluid system + // FluidSystem::init(); + FluidSystem::init(/*Tmin=*/temperatureLow_, + /*Tmax=*/temperatureHigh_, + /*nT=*/nTemperature_, + /*pmin=*/pressureLow_, + /*pmax=*/pressureHigh_, + /*np=*/nPressure_); + + fineLayerBottom_ = 22.0; + + // intrinsic permeabilities + fineK_ = this->toDimMatrix_(1e-13); + coarseK_ = this->toDimMatrix_(1e-12); + + // porosities + finePorosity_ = 0.3; + coarsePorosity_ = 0.3; + + // residual saturations + fineMaterialParams_.setResidualSaturation(liquidPhaseIdx, 0.2); + fineMaterialParams_.setResidualSaturation(gasPhaseIdx, 0.0); + coarseMaterialParams_.setResidualSaturation(liquidPhaseIdx, 0.2); + coarseMaterialParams_.setResidualSaturation(gasPhaseIdx, 0.0); + + // parameters for the Brooks-Corey law + fineMaterialParams_.setEntryPressure(1e4); + coarseMaterialParams_.setEntryPressure(5e3); + fineMaterialParams_.setLambda(2.0); + coarseMaterialParams_.setLambda(2.0); + + fineMaterialParams_.finalize(); + coarseMaterialParams_.finalize(); + + // parameters for the somerton law thermal conduction + computeThermalCondParams_(fineThermalCondParams_, finePorosity_); + computeThermalCondParams_(coarseThermalCondParams_, coarsePorosity_); + + // assume constant heat capacity and granite + solidEnergyLawParams_.setSolidHeatCapacity(790.0 // specific heat capacity of granite [J / (kg K)] + * 2700.0); // density of granite [kg/m^3] + solidEnergyLawParams_.finalize(); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register> + ("The lower temperature [K] for tabulation of the fluid system"); + Parameters::Register> + ("The upper temperature [K] for tabulation of the fluid system"); + Parameters::Register + ("The number of intervals between the lower and upper temperature"); + Parameters::Register> + ("The lower pressure [Pa] for tabulation of the fluid system"); + Parameters::Register> + ("The upper pressure [Pa] for tabulation of the fluid system"); + Parameters::Register + ("The number of intervals between the lower and upper pressure"); + Parameters::Register> + ("The temperature [K] in the reservoir"); + Parameters::Register> + ("The maximum depth [m] of the reservoir"); + Parameters::Register + ("The name of the simulation used for the output files"); + + Parameters::SetDefault("data/co2injection.dgf"); + Parameters::SetDefault>(1e4); + Parameters::SetDefault>(250); + Parameters::SetDefault>(Scalar{Co2InjectionTolerance}); + Parameters::SetDefault(true); + } + + /*! + * \name Problem parameters + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + std::ostringstream oss; + oss << Parameters::Get() + << "_" << Model::name(); + if (getPropValue()) + oss << "_ni"; + oss << "_" << Model::discretizationName(); + return oss.str(); + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + Scalar tol = this->model().newtonMethod().tolerance()*1e5; + this->model().checkConservativeness(tol); + + // Calculate storage terms + PrimaryVariables storageL, storageG; + this->model().globalPhaseStorage(storageL, /*phaseIdx=*/0); + this->model().globalPhaseStorage(storageG, /*phaseIdx=*/1); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: liquid=[" << storageL << "]" + << " gas=[" << storageG << "]\n" << std::flush; + } +#endif // NDEBUG + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const auto& pos = context.pos(spaceIdx, timeIdx); + if (inHighTemperatureRegion_(pos)) + return temperature_ + 100; + return temperature_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability(const Context& context, unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineK_; + return coarseK_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return finePorosity_; + return coarsePorosity_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams(const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineMaterialParams_; + return coarseMaterialParams_; + } + + /*! + * \brief Return the parameters for the heat storage law of the rock + * + * In this case, we assume the rock-matrix to be granite. + */ + template + const SolidEnergyLawParams& + solidEnergyLawParams(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return solidEnergyLawParams_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::thermalConductionParams + */ + template + const ThermalConductionLawParams & + thermalConductionLawParams(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineThermalCondParams_; + return coarseThermalCondParams_; + } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const auto& pos = context.pos(spaceIdx, timeIdx); + if (onLeftBoundary_(pos)) { + Opm::CompositionalFluidState fs; + initialFluidState_(fs, context, spaceIdx, timeIdx); + fs.checkDefined(); + + // impose an freeflow boundary condition + values.setFreeFlow(context, spaceIdx, timeIdx, fs); + } + else if (onInlet_(pos)) { + RateVector massRate(0.0); + massRate[contiCO2EqIdx] = -1e-3; // [kg/(m^3 s)] + + using FluidState = Opm::ImmiscibleFluidState; + FluidState fs; + fs.setSaturation(gasPhaseIdx, 1.0); + const auto& pg = + context.intensiveQuantities(spaceIdx, timeIdx).fluidState().pressure(gasPhaseIdx); + fs.setPressure(gasPhaseIdx, Toolbox::value(pg)); + fs.setTemperature(temperature(context, spaceIdx, timeIdx)); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updatePhase(fs, gasPhaseIdx); + Scalar h = FluidSystem::template enthalpy(fs, paramCache, gasPhaseIdx); + + // impose an forced inflow boundary condition for pure CO2 + values.setMassRate(massRate); + values.setEnthalpyRate(massRate[contiCO2EqIdx] * h); + } + else + // no flow on top and bottom + values.setNoFlow(); + } + + // \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, const Context& context, unsigned spaceIdx, + unsigned timeIdx) const + { + Opm::CompositionalFluidState fs; + initialFluidState_(fs, context, spaceIdx, timeIdx); + + // const auto& matParams = this->materialLawParams(context, spaceIdx, + // timeIdx); + // values.assignMassConservative(fs, matParams, /*inEquilibrium=*/true); + values.assignNaive(fs); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + template + void initialFluidState_(FluidState& fs, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + + ////// + // set temperature + ////// + fs.setTemperature(temperature(context, spaceIdx, timeIdx)); + + ////// + // set saturations + ////// + fs.setSaturation(FluidSystem::liquidPhaseIdx, 1.0); + fs.setSaturation(FluidSystem::gasPhaseIdx, 0.0); + + ////// + // set pressures + ////// + Scalar densityL = FluidSystem::Brine::liquidDensity(temperature_, Scalar(1e5)); + Scalar depth = maxDepth_ - pos[dim - 1]; + Scalar pl = 1e5 - densityL * this->gravity()[dim - 1] * depth; + + Scalar pC[numPhases]; + const auto& matParams = this->materialLawParams(context, spaceIdx, timeIdx); + MaterialLaw::capillaryPressures(pC, matParams, fs); + + fs.setPressure(liquidPhaseIdx, pl + (pC[liquidPhaseIdx] - pC[liquidPhaseIdx])); + fs.setPressure(gasPhaseIdx, pl + (pC[gasPhaseIdx] - pC[liquidPhaseIdx])); + + ////// + // set composition of the liquid phase + ////// + fs.setMoleFraction(liquidPhaseIdx, CO2Idx, 0.005); + fs.setMoleFraction(liquidPhaseIdx, BrineIdx, + 1.0 - fs.moleFraction(liquidPhaseIdx, CO2Idx)); + + typename FluidSystem::template ParameterCache paramCache; + using CFRP = Opm::ComputeFromReferencePhase; + CFRP::solve(fs, paramCache, + /*refPhaseIdx=*/liquidPhaseIdx, + /*setViscosity=*/true, + /*setEnthalpy=*/true); + } + + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + bool onInlet_(const GlobalPosition& pos) const + { return onRightBoundary_(pos) && (5 < pos[1]) && (pos[1] < 15); } + + bool inHighTemperatureRegion_(const GlobalPosition& pos) const + { return (pos[0] > 20) && (pos[0] < 30) && (pos[1] > 5) && (pos[1] < 35); } + + void computeThermalCondParams_(ThermalConductionLawParams& params, Scalar poro) + { + Scalar lambdaWater = 0.6; + Scalar lambdaGranite = 2.8; + + Scalar lambdaWet = std::pow(lambdaGranite, (1 - poro)) + * std::pow(lambdaWater, poro); + Scalar lambdaDry = std::pow(lambdaGranite, (1 - poro)); + + params.setFullySaturatedLambda(gasPhaseIdx, lambdaDry); + params.setFullySaturatedLambda(liquidPhaseIdx, lambdaWet); + params.setVacuumLambda(lambdaDry); + } + + bool isFineMaterial_(const GlobalPosition& pos) const + { return pos[dim - 1] > fineLayerBottom_; } + + DimMatrix fineK_; + DimMatrix coarseK_; + Scalar fineLayerBottom_; + + Scalar finePorosity_; + Scalar coarsePorosity_; + + MaterialLawParams fineMaterialParams_; + MaterialLawParams coarseMaterialParams_; + + ThermalConductionLawParams fineThermalCondParams_; + ThermalConductionLawParams coarseThermalCondParams_; + SolidEnergyLawParams solidEnergyLawParams_; + + Scalar temperature_; + Scalar maxDepth_; + Scalar eps_; + + unsigned nTemperature_; + unsigned nPressure_; + + Scalar pressureLow_, pressureHigh_; + Scalar temperatureLow_, temperatureHigh_; +}; + +} // namespace Opm + +#endif diff --git a/examples/problems/co2ptflashproblem.hh b/examples/problems/co2ptflashproblem.hh new file mode 100644 index 00000000000..318b772b203 --- /dev/null +++ b/examples/problems/co2ptflashproblem.hh @@ -0,0 +1,520 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::co2ptflashproblem + */ +#ifndef OPM_CO2PTFLASH_PROBLEM_HH +#define OPM_CO2PTFLASH_PROBLEM_HH + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Opm { +template +class CO2PTProblem; +} // namespace Opm */ + +namespace Opm::Properties { + +namespace TTag { +struct CO2PTBaseProblem {}; +} // end namespace TTag + +template +struct NumComp { using type = UndefinedProperty; }; + +template +struct NumComp { + static constexpr int value = 3; +}; + +// Set the grid type: --->2D +template +struct Grid { using type = Dune::YaspGrid; }; + +// Set the problem property +template +struct Problem +{ using type = Opm::CO2PTProblem; }; + +// Set flash solver +template +struct FlashSolver { +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using Evaluation = GetPropType; + +public: + using type = Opm::PTFlash; +}; + +// Set fluid configuration +template +struct FluidSystem +{ +private: + using Scalar = GetPropType; + static constexpr int num_comp = getPropValue(); + +public: + using type = Opm::GenericOilGasFluidSystem; +}; + +// Set the material Law +template +struct MaterialLaw { +private: + using FluidSystem = GetPropType; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + + using Scalar = GetPropType; + using Traits = Opm::TwoPhaseMaterialTraits; + + // define the material law which is parameterized by effective saturation + using EffMaterialLaw = Opm::NullMaterial; + //using EffMaterialLaw = Opm::BrooksCorey; + +public: + using type = EffMaterialLaw; +}; + +// mesh grid +template +struct Vanguard { + using type = Opm::StructuredGridVanguard; +}; + +template +struct EnableEnergy { + static constexpr bool value = false; +}; + +} // namespace Opm::Properties + +namespace Opm::Parameters { + +// this is kinds of telling the report step length +template +struct EpisodeLength { static constexpr Scalar value = 0.1 * 60. * 60.; }; + +template +struct Initialpressure { static constexpr Scalar value = 75e5; }; + +struct SimulationName { static constexpr auto value = "co2_ptflash"; }; + +// set the defaults for the problem specific properties +template +struct Temperature { static constexpr Scalar value = 423.25; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup TestProblems + * + * \brief 3 component simple testproblem with ["CO2", "C1", "C10"] + * + */ +template +class CO2PTProblem : public GetPropType +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using GridView = GetPropType; + using FluidSystem = GetPropType; + enum { dim = GridView::dimension }; + enum { dimWorld = GridView::dimensionworld }; + using Indices = GetPropType; + using PrimaryVariables = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using MaterialLaw = GetPropType; + using Simulator = GetPropType; + using Model = GetPropType; + using MaterialLawParams = GetPropType; + + using Toolbox = Opm::MathToolbox; + using CoordScalar = typename GridView::ctype; + + enum { numPhases = FluidSystem::numPhases }; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { numComponents = getPropValue() }; + enum { enableEnergy = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + + using GlobalPosition = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + using DimVector = Dune::FieldVector; + using ComponentVector = Dune::FieldVector; + using FlashSolver = GetPropType; + +public: + using FluidState = Opm::CompositionalFluidState; + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + explicit CO2PTProblem(Simulator& simulator) + : ParentType(simulator) + { + const Scalar epi_len = Parameters::Get>(); + simulator.setEpisodeLength(epi_len); + FluidSystem::init(); + using CompParm = typename FluidSystem::ComponentParam; + using CO2 = Opm::SimpleCO2; + using C1 = Opm::C1; + using C10 = Opm::C10; + FluidSystem::addComponent(CompParm {CO2::name(), CO2::molarMass(), CO2::criticalTemperature(), + CO2::criticalPressure(), CO2::criticalVolume(), CO2::acentricFactor()}); + FluidSystem::addComponent(CompParm {C1::name(), C1::molarMass(), C1::criticalTemperature(), + C1::criticalPressure(), C1::criticalVolume(), C1::acentricFactor()}); + FluidSystem::addComponent(CompParm{C10::name(), C10::molarMass(), C10::criticalTemperature(), + C10::criticalPressure(), C10::criticalVolume(), C10::acentricFactor()}); + // FluidSystem::add + } + + void initPetrophysics() + { + temperature_ = Parameters::Get>(); + K_ = this->toDimMatrix_(9.869232667160131e-14); + + porosity_ = 0.1; + } + + template + const DimVector& + gravity([[maybe_unused]]const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { + return gravity(); + } + + const DimVector& gravity() const + { + return gravity_; + } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + // initialize fixed parameters; temperature, permeability, porosity + initPetrophysics(); + } + + /*! + * \copydoc co2ptflashproblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register> + ("The temperature [K] in the reservoir"); + Parameters::Register> + ("The initial pressure [Pa s] in the reservoir"); + Parameters::Register + ("The name of the simulation used for the output files"); + Parameters::Register> + ("Time interval [s] for episode length"); + + Parameters::SetDefault(30); + Parameters::SetDefault>(300.0); + + if constexpr (dim > 1) { + Parameters::SetDefault(1); + Parameters::SetDefault>(1.0); + } + if constexpr (dim == 3) { + Parameters::SetDefault(1); + Parameters::SetDefault>(1.0); + } + + Parameters::SetDefault>(60. * 60.); + Parameters::SetDefault>(0.1 * 60. * 60.); + Parameters::SetDefault(30); + Parameters::SetDefault(6); + Parameters::SetDefault>(1e-3); + Parameters::SetDefault(true); + Parameters::SetDefault(true); + Parameters::SetDefault(true); + Parameters::SetDefault(true); + Parameters::SetDefault(true); + Parameters::SetDefault(true); + Parameters::SetDefault(true); + + Parameters::SetDefault>(0.0); + Parameters::SetDefault>(1e-3); + } + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + std::ostringstream oss; + oss << Parameters::Get(); + return oss.str(); + } + + // This method must be overridden for the simulator to continue with + // a new episode. We just start a new episode with the same length as + // the old one. + void endEpisode() + { + Scalar epi_len = Parameters::Get>(); + this->simulator().startNextEpisode(epi_len); + } + + // only write output when episodes change, aka. report steps, and + // include the initial timestep too + bool shouldWriteOutput() + { + return this->simulator().episodeWillBeOver() || (this->simulator().timeStepIndex() == -1); + } + + // we don't care about doing restarts from every fifth timestep, it + // will just slow us down + bool shouldWriteRestartFile() + { + return false; + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { + Scalar tol = this->model().newtonMethod().tolerance() * 1e5; + this->model().checkConservativeness(tol); + + // Calculate storage terms + PrimaryVariables storageO, storageW; + this->model().globalPhaseStorage(storageO, oilPhaseIdx); + + // Calculate storage terms + PrimaryVariables storageL, storageG; + this->model().globalPhaseStorage(storageL, /*phaseIdx=*/0); + this->model().globalPhaseStorage(storageG, /*phaseIdx=*/1); + + // Write mass balance information for rank 0 + // if (this->gridView().comm().rank() == 0) { + // std::cout << "Storage: liquid=[" << storageL << "]" + // << " gas=[" << storageG << "]\n" << std::flush; + // } + } + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + Opm::CompositionalFluidState fs; + initialFluidState(fs, context, spaceIdx, timeIdx); + values.assignNaive(fs); + } + + // Constant temperature + template + Scalar temperature([[maybe_unused]] const Context& context, [[maybe_unused]] unsigned spaceIdx, [[maybe_unused]] unsigned timeIdx) const + { + return temperature_; + } + + + // Constant permeability + template + const DimMatrix& intrinsicPermeability([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { + return K_; + } + + // Constant porosity + template + Scalar porosity([[maybe_unused]] const Context& context, [[maybe_unused]] unsigned spaceIdx, [[maybe_unused]] unsigned timeIdx) const + { + int spatialIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + int inj = 0; + int prod = Parameters::Get() - 1; + if (spatialIdx == inj || spatialIdx == prod) { + return 1.0; + } else { + return porosity_; + } + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { + return this->mat_; + } + + + // No flow (introduce fake wells instead) + template + void boundary(BoundaryRateVector& values, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { values.setNoFlow(); } + + // No source terms + template + void source(RateVector& source_rate, + [[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { + source_rate = Scalar(0.0); + } + +private: + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initialFluidState(FluidState& fs, const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + // z0 = [0.5, 0.3, 0.2] + // zi = [0.99, 0.01-1e-3, 1e-3] + // p0 = 75e5 + // T0 = 423.25 + int inj = 0; + int prod = Parameters::Get() - 1; + int spatialIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + ComponentVector comp; + comp[0] = Evaluation::createVariable(0.5, 1); + comp[1] = Evaluation::createVariable(0.3, 2); + comp[2] = 1. - comp[0] - comp[1]; + if (spatialIdx == inj) { + comp[0] = Evaluation::createVariable(0.99, 1); + comp[1] = Evaluation::createVariable(0.01 - 1e-3, 2); + comp[2] = 1. - comp[0] - comp[1]; + } + ComponentVector sat; + sat[0] = 1.0; + sat[1] = 1.0 - sat[0]; + + Scalar p0 = Parameters::Get>(); + + //\Note, for an AD variable, if we multiply it with 2, the derivative will also be scalced with 2, + //\Note, so we should not do it. + if (spatialIdx == inj) { + p0 *= 2.0; + } + if (spatialIdx == prod) { + p0 *= 0.5; + } + Evaluation p_init = Evaluation::createVariable(p0, 0); + + fs.setPressure(FluidSystem::oilPhaseIdx, p_init); + fs.setPressure(FluidSystem::gasPhaseIdx, p_init); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + fs.setMoleFraction(FluidSystem::oilPhaseIdx, compIdx, comp[compIdx]); + fs.setMoleFraction(FluidSystem::gasPhaseIdx, compIdx, comp[compIdx]); + } + + // It is used here only for calculate the z + fs.setSaturation(FluidSystem::oilPhaseIdx, sat[0]); + fs.setSaturation(FluidSystem::gasPhaseIdx, sat[1]); + + fs.setTemperature(temperature_); + + // ParameterCache paramCache; + { + typename FluidSystem::template ParameterCache paramCache; + paramCache.updatePhase(fs, FluidSystem::oilPhaseIdx); + paramCache.updatePhase(fs, FluidSystem::gasPhaseIdx); + fs.setDensity(FluidSystem::oilPhaseIdx, FluidSystem::density(fs, paramCache, FluidSystem::oilPhaseIdx)); + fs.setDensity(FluidSystem::gasPhaseIdx, FluidSystem::density(fs, paramCache, FluidSystem::gasPhaseIdx)); + fs.setViscosity(FluidSystem::oilPhaseIdx, FluidSystem::viscosity(fs, paramCache, FluidSystem::oilPhaseIdx)); + fs.setViscosity(FluidSystem::gasPhaseIdx, FluidSystem::viscosity(fs, paramCache, FluidSystem::gasPhaseIdx)); + } + + // Set initial K and L + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + const Evaluation Ktmp = fs.wilsonK_(compIdx); + fs.setKvalue(compIdx, Ktmp); + } + + const Evaluation& Ltmp = -1.0; + fs.setLvalue(Ltmp); + } + + DimMatrix K_; + Scalar porosity_; + Scalar temperature_; + MaterialLawParams mat_; + DimVector gravity_; +}; + +} // namespace Opm + +#endif diff --git a/examples/problems/cuvetteproblem.hh b/examples/problems/cuvetteproblem.hh new file mode 100644 index 00000000000..b3af92b572b --- /dev/null +++ b/examples/problems/cuvetteproblem.hh @@ -0,0 +1,642 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::CuvetteProblem + */ +#ifndef EWOMS_CUVETTE_PROBLEM_HH +#define EWOMS_CUVETTE_PROBLEM_HH + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +namespace Opm { +template +class CuvetteProblem; +} + +namespace Opm::Properties { + + +// create a new type tag for the cuvette steam injection problem +namespace TTag { +struct CuvetteBaseProblem {}; +} + +// Set the grid type +template +struct Grid { using type = Dune::YaspGrid<2>; }; + +// Set the problem property +template +struct Problem { using type = Opm::CuvetteProblem; }; + +// Set the fluid system +template +struct FluidSystem +{ using type = Opm::H2OAirMesityleneFluidSystem>; }; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + + using Traits = Opm::ThreePhaseMaterialTraits< + Scalar, + /*wettingPhaseIdx=*/FluidSystem::waterPhaseIdx, + /*nonWettingPhaseIdx=*/FluidSystem::naplPhaseIdx, + /*gasPhaseIdx=*/FluidSystem::gasPhaseIdx>; + +public: + using type = Opm::ThreePhaseParkerVanGenuchten; +}; + +// set the energy storage law for the solid phase +template +struct SolidEnergyLaw +{ using type = Opm::ConstantSolidHeatCapLaw>; }; + +// Set the thermal conduction law +template +struct ThermalConductionLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + +public: + // define the material law parameterized by absolute saturations + using type = Opm::SomertonThermalConductionLaw; +}; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup TestProblems + * + * \brief Non-isothermal three-phase gas injection problem where a hot gas + * is injected into a unsaturated porous medium with a residually + * trapped NAPL contamination. + * + * The domain is a quasi-two-dimensional container (cuvette). Its + * dimensions are 1.5 m x 0.74 m. The top and bottom boundaries are + * closed, the right boundary is a free-flow boundary allowing fluids + * to escape. From the left, an injection of a hot water-air mixture + * is injected. The set-up is aimed at remediating an initial NAPL + * (Non-Aquoeus Phase Liquid) contamination in the domain. The + * contamination is initially placed partly into the ambient coarse + * sand and partly into a fine sand lens. + * + * This simulation can be varied through assigning different boundary conditions + * at the left boundary as described in Class (2001): + * Theorie und numerische Modellierung nichtisothermer Mehrphasenprozesse in + * NAPL-kontaminierten poroesen Medien, Dissertation, Eigenverlag des Instituts + * fuer Wasserbau + * + * To see the basic effect and the differences to scenarios with pure + * steam or pure air injection, it is sufficient to simulate this + * problem to about 2-3 hours simulation time. Complete remediation + * of the domain requires much longer (about 10 days simulated time). + */ +template +class CuvetteProblem : public GetPropType +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using GridView = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using ThermalConductionLawParams = GetPropType; + using SolidEnergyLawParams = GetPropType; + using EqVector = GetPropType; + using PrimaryVariables = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using Simulator = GetPropType; + using Model = GetPropType; + using FluidSystem = GetPropType; + + // copy some indices for convenience + using Indices = GetPropType; + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + enum { naplPhaseIdx = FluidSystem::naplPhaseIdx }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { H2OIdx = FluidSystem::H2OIdx }; + enum { airIdx = FluidSystem::airIdx }; + enum { NAPLIdx = FluidSystem::NAPLIdx }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + + // Grid and world dimension + enum { dimWorld = GridView::dimensionworld }; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + CuvetteProblem(Simulator& simulator) + : ParentType(simulator) + , eps_(1e-6) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + if (Opm::Valgrind::IsRunning()) + FluidSystem::init(/*minT=*/283.15, /*maxT=*/500.0, /*nT=*/20, + /*minp=*/0.8e5, /*maxp=*/2e5, /*np=*/10); + else + FluidSystem::init(/*minT=*/283.15, /*maxT=*/500.0, /*nT=*/200, + /*minp=*/0.8e5, /*maxp=*/2e5, /*np=*/100); + + // intrinsic permeabilities + fineK_ = this->toDimMatrix_(6.28e-12); + coarseK_ = this->toDimMatrix_(9.14e-10); + + // porosities + finePorosity_ = 0.42; + coarsePorosity_ = 0.42; + + // parameters for the capillary pressure law +#if 1 + // three-phase Parker -- van Genuchten law + fineMaterialParams_.setVgAlpha(0.0005); + coarseMaterialParams_.setVgAlpha(0.005); + fineMaterialParams_.setVgN(4.0); + coarseMaterialParams_.setVgN(4.0); + + coarseMaterialParams_.setkrRegardsSnr(true); + fineMaterialParams_.setkrRegardsSnr(true); + + // residual saturations + fineMaterialParams_.setSwr(0.1201); + fineMaterialParams_.setSwrx(0.1201); + fineMaterialParams_.setSnr(0.0701); + fineMaterialParams_.setSgr(0.0101); + coarseMaterialParams_.setSwr(0.1201); + coarseMaterialParams_.setSwrx(0.1201); + coarseMaterialParams_.setSnr(0.0701); + coarseMaterialParams_.setSgr(0.0101); +#else + // linear material law + fineMaterialParams_.setPcMinSat(gasPhaseIdx, 0); + fineMaterialParams_.setPcMaxSat(gasPhaseIdx, 0); + fineMaterialParams_.setPcMinSat(naplPhaseIdx, 0); + fineMaterialParams_.setPcMaxSat(naplPhaseIdx, -1000); + fineMaterialParams_.setPcMinSat(waterPhaseIdx, 0); + fineMaterialParams_.setPcMaxSat(waterPhaseIdx, -10000); + + coarseMaterialParams_.setPcMinSat(gasPhaseIdx, 0); + coarseMaterialParams_.setPcMaxSat(gasPhaseIdx, 0); + coarseMaterialParams_.setPcMinSat(naplPhaseIdx, 0); + coarseMaterialParams_.setPcMaxSat(naplPhaseIdx, -100); + coarseMaterialParams_.setPcMinSat(waterPhaseIdx, 0); + coarseMaterialParams_.setPcMaxSat(waterPhaseIdx, -1000); + + // residual saturations + fineMaterialParams_.setResidSat(waterPhaseIdx, 0.1201); + fineMaterialParams_.setResidSat(naplPhaseIdx, 0.0701); + fineMaterialParams_.setResidSat(gasPhaseIdx, 0.0101); + + coarseMaterialParams_.setResidSat(waterPhaseIdx, 0.1201); + coarseMaterialParams_.setResidSat(naplPhaseIdx, 0.0701); + coarseMaterialParams_.setResidSat(gasPhaseIdx, 0.0101); +#endif + + fineMaterialParams_.finalize(); + coarseMaterialParams_.finalize(); + + // initialize parameters for the thermal conduction law + computeThermalCondParams_(thermalCondParams_, finePorosity_); + + // assume constant volumetric heat capacity and granite + solidEnergyLawParams_.setSolidHeatCapacity(790.0 // specific heat capacity of granite [J / (kg K)] + * 2700.0); // density of granite [kg/m^3] + solidEnergyLawParams_.finalize(); + + initInjectFluidState_(); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault("./data/cuvette_11x4.dgf"); + Parameters::SetDefault>(100.0); + Parameters::SetDefault>(1.0); + Parameters::SetDefault>(600.0); + Parameters::SetDefault(true); + } + + /*! + * \name Auxiliary methods + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::shouldWriteRestartFile + * + * This problem writes a restart file after every time step. + */ + bool shouldWriteRestartFile() const + { return true; } + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { return std::string("cuvette_") + Model::name(); } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + //! \} + + /*! + * \name Soil parameters + */ + //! \{ + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return 293.15; /* [K] */ } + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability(const Context& context, unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineK_; + return coarseK_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return finePorosity_; + else + return coarsePorosity_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams(const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineMaterialParams_; + else + return coarseMaterialParams_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::thermalConductionParams + */ + template + const ThermalConductionLawParams & + thermalConductionParams(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return thermalCondParams_; } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const auto& pos = context.pos(spaceIdx, timeIdx); + + if (onRightBoundary_(pos)) { + Opm::CompositionalFluidState fs; + + initialFluidState_(fs, context, spaceIdx, timeIdx); + + values.setFreeFlow(context, spaceIdx, timeIdx, fs); + values.setNoFlow(); + } + else if (onLeftBoundary_(pos)) { + // injection + RateVector molarRate; + + // inject with the same composition as the gas phase of + // the injection fluid state + Scalar molarInjectionRate = 0.3435; // [mol/(m^2 s)] + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + molarRate[conti0EqIdx + compIdx] = + -molarInjectionRate + * injectFluidState_.moleFraction(gasPhaseIdx, compIdx); + + // calculate the total mass injection rate [kg / (m^2 s) + Scalar massInjectionRate = + molarInjectionRate + * injectFluidState_.averageMolarMass(gasPhaseIdx); + + // set the boundary rate vector [J / (m^2 s)] + values.setMolarRate(molarRate); + values.setEnthalpyRate(-injectFluidState_.enthalpy(gasPhaseIdx) * massInjectionRate); + } + else + values.setNoFlow(); + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, const Context& context, unsigned spaceIdx, + unsigned timeIdx) const + { + Opm::CompositionalFluidState fs; + + initialFluidState_(fs, context, spaceIdx, timeIdx); + + const auto& matParams = materialLawParams(context, spaceIdx, timeIdx); + values.assignMassConservative(fs, matParams, /*inEquilibrium=*/false); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + bool onLowerBoundary_(const GlobalPosition& pos) const + { return pos[1] < eps_; } + + bool onUpperBoundary_(const GlobalPosition& pos) const + { return pos[1] > this->boundingBoxMax()[1] - eps_; } + + bool isContaminated_(const GlobalPosition& pos) const + { + return (0.20 <= pos[0]) && (pos[0] <= 0.80) && (0.4 <= pos[1]) + && (pos[1] <= 0.65); + } + + bool isFineMaterial_(const GlobalPosition& pos) const + { + if (0.13 <= pos[0] && 1.20 >= pos[0] && 0.32 <= pos[1] && pos[1] <= 0.57) + return true; + else if (pos[1] <= 0.15 && 1.20 <= pos[0]) + return true; + else + return false; + } + + template + void initialFluidState_(FluidState& fs, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + + fs.setTemperature(293.0 /*[K]*/); + + Scalar pw = 1e5; + + if (isContaminated_(pos)) { + fs.setSaturation(waterPhaseIdx, 0.12); + fs.setSaturation(naplPhaseIdx, 0.07); + fs.setSaturation(gasPhaseIdx, 1 - 0.12 - 0.07); + + // set the capillary pressures + const auto& matParams = materialLawParams(context, spaceIdx, timeIdx); + Scalar pc[numPhases]; + MaterialLaw::capillaryPressures(pc, matParams, fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + fs.setPressure(phaseIdx, pw + (pc[phaseIdx] - pc[waterPhaseIdx])); + + // compute the phase compositions + using MMPC = Opm::MiscibleMultiPhaseComposition; + typename FluidSystem::template ParameterCache paramCache; + MMPC::solve(fs, paramCache, /*setViscosity=*/true, /*setEnthalpy=*/true); + } + else { + fs.setSaturation(waterPhaseIdx, 0.12); + fs.setSaturation(gasPhaseIdx, 1 - fs.saturation(waterPhaseIdx)); + fs.setSaturation(naplPhaseIdx, 0); + + // set the capillary pressures + const auto& matParams = materialLawParams(context, spaceIdx, timeIdx); + Scalar pc[numPhases]; + MaterialLaw::capillaryPressures(pc, matParams, fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + fs.setPressure(phaseIdx, pw + (pc[phaseIdx] - pc[waterPhaseIdx])); + + // compute the phase compositions + using MMPC = Opm::MiscibleMultiPhaseComposition; + typename FluidSystem::template ParameterCache paramCache; + MMPC::solve(fs, paramCache, /*setViscosity=*/true, /*setEnthalpy=*/true); + + // set the contaminant mole fractions to zero. this is a little bit hacky... + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + fs.setMoleFraction(phaseIdx, NAPLIdx, 0.0); + + if (phaseIdx == naplPhaseIdx) + continue; + + Scalar sumx = 0; + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + sumx += fs.moleFraction(phaseIdx, compIdx); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + fs.setMoleFraction(phaseIdx, compIdx, + fs.moleFraction(phaseIdx, compIdx) / sumx); + } + } + } + + void computeThermalCondParams_(ThermalConductionLawParams& params, Scalar poro) + { + Scalar lambdaGranite = 2.8; // [W / (K m)] + + // create a Fluid state which has all phases present + Opm::ImmiscibleFluidState fs; + fs.setTemperature(293.15); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + fs.setPressure(phaseIdx, 1.0135e5); + } + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar rho = FluidSystem::density(fs, paramCache, phaseIdx); + fs.setDensity(phaseIdx, rho); + } + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar lambdaSaturated; + if (FluidSystem::isLiquid(phaseIdx)) { + Scalar lambdaFluid = FluidSystem::thermalConductivity(fs, paramCache, phaseIdx); + lambdaSaturated = + std::pow(lambdaGranite, (1 - poro)) + + + std::pow(lambdaFluid, poro); + } + else + lambdaSaturated = std::pow(lambdaGranite, (1 - poro)); + + params.setFullySaturatedLambda(phaseIdx, lambdaSaturated); + if (!FluidSystem::isLiquid(phaseIdx)) + params.setVacuumLambda(lambdaSaturated); + } + } + + void initInjectFluidState_() + { + injectFluidState_.setTemperature(383.0); // [K] + injectFluidState_.setPressure(gasPhaseIdx, 1e5); // [Pa] + injectFluidState_.setSaturation(gasPhaseIdx, 1.0); // [-] + + Scalar xgH2O = 0.417; + injectFluidState_.setMoleFraction(gasPhaseIdx, H2OIdx, xgH2O); // [-] + injectFluidState_.setMoleFraction(gasPhaseIdx, airIdx, 1 - xgH2O); // [-] + injectFluidState_.setMoleFraction(gasPhaseIdx, NAPLIdx, 0.0); // [-] + + // set the specific enthalpy of the gas phase + typename FluidSystem::template ParameterCache paramCache; + paramCache.updatePhase(injectFluidState_, gasPhaseIdx); + + Scalar h = FluidSystem::enthalpy(injectFluidState_, paramCache, gasPhaseIdx); + injectFluidState_.setEnthalpy(gasPhaseIdx, h); + } + + DimMatrix fineK_; + DimMatrix coarseK_; + + Scalar finePorosity_; + Scalar coarsePorosity_; + + MaterialLawParams fineMaterialParams_; + MaterialLawParams coarseMaterialParams_; + + ThermalConductionLawParams thermalCondParams_; + SolidEnergyLawParams solidEnergyLawParams_; + + Opm::CompositionalFluidState injectFluidState_; + + const Scalar eps_; +}; +} // namespace Opm + +#endif diff --git a/examples/problems/diffusionproblem.hh b/examples/problems/diffusionproblem.hh new file mode 100644 index 00000000000..879a7152eb5 --- /dev/null +++ b/examples/problems/diffusionproblem.hh @@ -0,0 +1,385 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::DiffusionProblem + */ +#ifndef EWOMS_POWER_INJECTION_PROBLEM_HH +#define EWOMS_POWER_INJECTION_PROBLEM_HH + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace Opm { +template +class DiffusionProblem; +} + +namespace Opm::Properties { + +namespace TTag { + +struct DiffusionBaseProblem {}; + +} // namespace TTag + +// Set the grid implementation to be used +template +struct Grid { using type = Dune::YaspGrid; }; + +// set the Vanguard property +template +struct Vanguard { using type = Opm::CubeGridVanguard; }; + +// Set the problem property +template +struct Problem { using type = Opm::DiffusionProblem; }; + +// Set the fluid system +template +struct FluidSystem +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::H2ON2FluidSystem; +}; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + + static_assert(FluidSystem::numPhases == 2, + "A fluid system with two phases is required " + "for this problem!"); + + using Traits = Opm::TwoPhaseMaterialTraits; + +public: + using type = Opm::LinearMaterial; +}; + +// Enable molecular diffusion for this problem +template +struct EnableDiffusion { static constexpr bool value = true; }; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup TestProblems + * \brief 1D problem which is driven by molecular diffusion. + * + * The domain is one meter long and completely filled with gas and + * closed on all boundaries. Its left half exhibits a slightly higher + * water concentration than the right one. After a while, the + * concentration of water will be equilibrate due to molecular + * diffusion. + */ +template +class DiffusionProblem : public GetPropType +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using GridView = GetPropType; + using FluidSystem = GetPropType; + using PrimaryVariables = GetPropType; + using Simulator = GetPropType; + using Model = GetPropType; + + enum { + // number of phases + numPhases = FluidSystem::numPhases, + + // phase indices + liquidPhaseIdx = FluidSystem::liquidPhaseIdx, + gasPhaseIdx = FluidSystem::gasPhaseIdx, + + // component indices + H2OIdx = FluidSystem::H2OIdx, + N2Idx = FluidSystem::N2Idx, + + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + DiffusionProblem(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + FluidSystem::init(); + + temperature_ = 273.15 + 20.0; + + materialParams_.finalize(); + + K_ = this->toDimMatrix_(1e-12); // [m^2] + + setupInitialFluidStates_(); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault(250); + + if constexpr (dim > 1) { + Parameters::SetDefault(1); + } + if constexpr (dim == 3) { + Parameters::SetDefault(1); + } + + Parameters::SetDefault>(1e6); + Parameters::SetDefault>(1000); + } + + /*! + * \name Auxiliary methods + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { return std::string("diffusion_") + Model::name(); } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + //! \} + + /*! + * \name Soil parameters + */ + //! \{ + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return K_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return 0.35; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& + materialLawParams(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return materialParams_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return temperature_; } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + * + * This problem sets no-flow boundaries everywhere. + */ + template + void boundary(BoundaryRateVector& values, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { values.setNoFlow(); } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const auto& pos = context.pos(spaceIdx, timeIdx); + if (onLeftSide_(pos)) + values.assignNaive(leftInitialFluidState_); + else + values.assignNaive(rightInitialFluidState_); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + bool onLeftSide_(const GlobalPosition& pos) const + { return pos[0] < (this->boundingBoxMin()[0] + this->boundingBoxMax()[0]) / 2; } + + void setupInitialFluidStates_() + { + // create the initial fluid state for the left half of the domain + leftInitialFluidState_.setTemperature(temperature_); + + Scalar Sl = 0.0; + leftInitialFluidState_.setSaturation(liquidPhaseIdx, Sl); + leftInitialFluidState_.setSaturation(gasPhaseIdx, 1 - Sl); + + Scalar p = 1e5; + leftInitialFluidState_.setPressure(liquidPhaseIdx, p); + leftInitialFluidState_.setPressure(gasPhaseIdx, p); + + Scalar xH2O = 0.01; + leftInitialFluidState_.setMoleFraction(gasPhaseIdx, H2OIdx, xH2O); + leftInitialFluidState_.setMoleFraction(gasPhaseIdx, N2Idx, 1 - xH2O); + + using CFRP = Opm::ComputeFromReferencePhase; + typename FluidSystem::template ParameterCache paramCache; + CFRP::solve(leftInitialFluidState_, paramCache, gasPhaseIdx, + /*setViscosity=*/false, /*setEnthalpy=*/false); + + // create the initial fluid state for the right half of the domain + rightInitialFluidState_.assign(leftInitialFluidState_); + xH2O = 0.0; + rightInitialFluidState_.setMoleFraction(gasPhaseIdx, H2OIdx, xH2O); + rightInitialFluidState_.setMoleFraction(gasPhaseIdx, N2Idx, 1 - xH2O); + CFRP::solve(rightInitialFluidState_, paramCache, gasPhaseIdx, + /*setViscosity=*/false, /*setEnthalpy=*/false); + } + + DimMatrix K_; + MaterialLawParams materialParams_; + + Opm::CompositionalFluidState leftInitialFluidState_; + Opm::CompositionalFluidState rightInitialFluidState_; + Scalar temperature_; +}; + +} // namespace Opm + +#endif diff --git a/examples/problems/fingerproblem.hh b/examples/problems/fingerproblem.hh new file mode 100644 index 00000000000..f4b6acbb269 --- /dev/null +++ b/examples/problems/fingerproblem.hh @@ -0,0 +1,562 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FingerProblem + */ +#ifndef EWOMS_FINGER_PROBLEM_HH +#define EWOMS_FINGER_PROBLEM_HH + +#if HAVE_DUNE_ALUGRID +#include +#endif + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +#include + +#include + +#include + +namespace Opm { +template +class FingerProblem; + +} // namespace Opm + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct FingerBaseProblem { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +#if HAVE_DUNE_ALUGRID +// use dune-alugrid if available +template +struct Grid +{ using type = Dune::ALUGrid; }; +#endif + +// Set the problem property +template +struct Problem { using type = Opm::FingerProblem; }; + +// Set the wetting phase +template +struct WettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +// Set the non-wetting phase +template +struct NonwettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::GasPhase >; +}; + +// Set the material Law +template +struct MaterialLaw +{ + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using Traits = Opm::TwoPhaseMaterialTraits; + + // use the parker-lenhard hysteresis law + using ParkerLenhard = Opm::ParkerLenhard; + using type = ParkerLenhard; +}; + +// Enable constraints +template +struct EnableConstraints { static constexpr int value = true; }; + +} // namespace Opm::Properties + +namespace Opm::Parameters { + +template +struct InitialWaterSaturation { static constexpr Scalar value = 0.01; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! + * \ingroup TestProblems + * + * \brief Two-phase problem featuring some gravity-driven saturation + * fingers. + * + * The domain of this problem is sized 10cm times 1m and is initially + * dry. Water is then injected at three locations on the top of the + * domain which leads to gravity fingering. The boundary conditions + * used are no-flow for the left and right and top of the domain and + * free-flow at the bottom. This problem uses the Parker-Lenhard + * hystersis model which might lead to non-monotonic saturation in the + * fingers if the right material parameters is chosen and the spatial + * discretization is fine enough. + */ +template +class FingerProblem : public GetPropType +{ + //!\cond SKIP_THIS + using ParentType = GetPropType; + + using Scalar = GetPropType; + using GridView = GetPropType; + using Indices = GetPropType; + using FluidSystem = GetPropType; + using WettingPhase = GetPropType; + using NonwettingPhase = GetPropType; + using PrimaryVariables = GetPropType; + using Simulator = GetPropType; + using Constraints = GetPropType; + using Model = GetPropType; + + enum { + // number of phases + numPhases = FluidSystem::numPhases, + + // phase indices + wettingPhaseIdx = FluidSystem::wettingPhaseIdx, + nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx, + + // equation indices + contiWettingEqIdx = Indices::conti0EqIdx + wettingPhaseIdx, + + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + using ElementContext = GetPropType; + using Stencil = GetPropType ; + enum { codim = Stencil::Entity::codimension }; + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + + using ParkerLenhard = typename GetProp::ParkerLenhard; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + + using Grid = typename GridView :: Grid; + + using MaterialLawParamsContainer = Dune::PersistentContainer< Grid, std::shared_ptr< MaterialLawParams > > ; + //!\endcond + +public: + using RestrictProlongOperator = CopyRestrictProlong< Grid, MaterialLawParamsContainer >; + + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + FingerProblem(Simulator& simulator) + : ParentType(simulator), + materialParams_( simulator.vanguard().grid(), codim ) + { + } + + /*! + * \name Auxiliary methods + */ + //! \{ + + /*! + * \brief \copydoc FvBaseProblem::restrictProlongOperator + */ + RestrictProlongOperator restrictProlongOperator() + { + return RestrictProlongOperator( materialParams_ ); + } + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { return + std::string("finger") + + "_" + Model::name() + + "_" + Model::discretizationName() + + (this->model().enableGridAdaptation()?"_adaptive":""); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register> + ("The initial saturation in the domain [] of the wetting phase"); + + Parameters::SetDefault(20); + Parameters::SetDefault>(0.1); + + if constexpr (dim > 1) { + Parameters::SetDefault(70); + Parameters::SetDefault>(0.3); + } + if constexpr (dim == 3) { + Parameters::SetDefault(1); + Parameters::SetDefault>(0.1); + } + + // Use forward differences + Parameters::SetDefault(+1); + + Parameters::SetDefault>(215); + Parameters::SetDefault>(10); + Parameters::SetDefault(true); + } + + /*! + * \copydoc FvBaseProblem::finishInit() + */ + void finishInit() + { + ParentType::finishInit(); + + eps_ = 3e-6; + + temperature_ = 273.15 + 20; // -> 20Ā°C + + FluidSystem::init(); + + // parameters for the Van Genuchten law of the main imbibition + // and the main drainage curves. + micParams_.setVgAlpha(0.0037); + micParams_.setVgN(4.7); + micParams_.finalize(); + + mdcParams_.setVgAlpha(0.0037); + mdcParams_.setVgN(4.7); + mdcParams_.finalize(); + + // initialize the material parameter objects of the individual + // finite volumes, resize will resize the container to the number of elements + materialParams_.resize(); + + for (auto it = materialParams_.begin(), + end = materialParams_.end(); it != end; ++it ) { + std::shared_ptr< MaterialLawParams >& materialParams = *it ; + if( ! materialParams ) + { + materialParams.reset( new MaterialLawParams() ); + materialParams->setMicParams(&micParams_); + materialParams->setMdcParams(&mdcParams_); + materialParams->setSwr(0.0); + materialParams->setSnr(0.1); + materialParams->finalize(); + ParkerLenhard::reset(*materialParams); + } + } + + K_ = this->toDimMatrix_(4.6e-10); + + setupInitialFluidState_(); + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + // checkConservativeness() does not include the effect of constraints, so we + // disable it for this problem... + //this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + + // update the history of the hysteresis law + ElementContext elemCtx(this->simulator()); + + for (const auto& elem : elements(this->gridView())) { + elemCtx.updateAll(elem); + size_t numDofs = elemCtx.numDof(/*timeIdx=*/0); + for (unsigned scvIdx = 0; scvIdx < numDofs; ++scvIdx) + { + MaterialLawParams& materialParam = materialLawParams( elemCtx, scvIdx, /*timeIdx=*/0 ); + const auto& fs = elemCtx.intensiveQuantities(scvIdx, /*timeIdx=*/0).fluidState(); + ParkerLenhard::update(materialParam, fs); + } + } + } + + //! \} + + /*! + * \name Soil parameters + */ + //! \{ + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& /*context*/, unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const + { return temperature_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability(const Context& /*context*/, unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const + { return K_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& /*context*/, unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const + { return 0.4; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + MaterialLawParams& materialLawParams(const Context& context, + unsigned spaceIdx, unsigned timeIdx) + { + const auto& entity = context.stencil(timeIdx).entity(spaceIdx); + assert(materialParams_[entity]); + return *materialParams_[entity]; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams(const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const auto& entity = context.stencil(timeIdx).entity( spaceIdx ); + assert(materialParams_[entity]); + return *materialParams_[entity]; + } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + + if (onLeftBoundary_(pos) || onRightBoundary_(pos) || onLowerBoundary_(pos)) + values.setNoFlow(); + else { + assert(onUpperBoundary_(pos)); + + values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidState_); + } + + // override the value for the liquid phase by forced + // imbibition of water on inlet boundary segments + if (onInlet_(pos)) { + values[contiWettingEqIdx] = -0.001; // [kg/(m^2 s)] + } + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, const Context& /*context*/, unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const + { + // assign the primary variables + values.assignNaive(initialFluidState_); + } + + /*! + * \copydoc FvBaseProblem::constraints + */ + template + void constraints(Constraints& constraints, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + + if (onUpperBoundary_(pos) && !onInlet_(pos)) { + constraints.setActive(true); + constraints.assignNaive(initialFluidState_); + } + else if (onLowerBoundary_(pos)) { + constraints.setActive(true); + constraints.assignNaive(initialFluidState_); + } + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, const Context& /*context*/, + unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + //! \} + +private: + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < this->boundingBoxMin()[0] + eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + bool onLowerBoundary_(const GlobalPosition& pos) const + { return pos[1] < this->boundingBoxMin()[1] + eps_; } + + bool onUpperBoundary_(const GlobalPosition& pos) const + { return pos[1] > this->boundingBoxMax()[1] - eps_; } + + bool onInlet_(const GlobalPosition& pos) const + { + Scalar width = this->boundingBoxMax()[0] - this->boundingBoxMin()[0]; + Scalar lambda = (this->boundingBoxMax()[0] - pos[0]) / width; + + if (!onUpperBoundary_(pos)) + return false; + + Scalar xInject[] = { 0.25, 0.75 }; + Scalar injectLen[] = { 0.1, 0.1 }; + for (unsigned i = 0; i < sizeof(xInject) / sizeof(Scalar); ++i) { + if (xInject[i] - injectLen[i] / 2 < lambda + && lambda < xInject[i] + injectLen[i] / 2) + return true; + } + return false; + } + + void setupInitialFluidState_() + { + auto& fs = initialFluidState_; + fs.setPressure(wettingPhaseIdx, /*pressure=*/1e5); + + Scalar Sw = Parameters::Get>(); + fs.setSaturation(wettingPhaseIdx, Sw); + fs.setSaturation(nonWettingPhaseIdx, 1 - Sw); + + fs.setTemperature(temperature_); + + // set the absolute pressures + Scalar pn = 1e5; + fs.setPressure(nonWettingPhaseIdx, pn); + fs.setPressure(wettingPhaseIdx, pn); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + fs.setDensity(phaseIdx, FluidSystem::density(fs, paramCache, phaseIdx)); + fs.setViscosity(phaseIdx, FluidSystem::viscosity(fs, paramCache, phaseIdx)); + } + + } + + DimMatrix K_; + + typename MaterialLawParams::VanGenuchtenParams micParams_; + typename MaterialLawParams::VanGenuchtenParams mdcParams_; + + MaterialLawParamsContainer materialParams_; + + Opm::ImmiscibleFluidState initialFluidState_; + + Scalar temperature_; + Scalar eps_; +}; + +} // namespace Opm + +#endif diff --git a/examples/problems/fractureproblem.hh b/examples/problems/fractureproblem.hh new file mode 100644 index 00000000000..e0f34907bc7 --- /dev/null +++ b/examples/problems/fractureproblem.hh @@ -0,0 +1,663 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FractureProblem + */ +#ifndef EWOMS_FRACTURE_PROBLEM_HH +#define EWOMS_FRACTURE_PROBLEM_HH + +#if HAVE_DUNE_ALUGRID +// avoid reordering of macro elements, otherwise this problem won't work +#define DISABLE_ALUGRID_SFC_ORDERING 1 +#include +#include +#else +#error "dune-alugrid not found!" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace Opm { +template +class FractureProblem; +} + +namespace Opm::Properties { + +// Create a type tag for the problem +// Create new type tags +namespace TTag { +struct FractureProblem { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +// Set the grid type +template +struct Grid +{ using type = Dune::ALUGrid; }; + +// Set the Vanguard property +template +struct Vanguard { using type = Opm::DgfVanguard; }; + +// Set the problem property +template +struct Problem { using type = Opm::FractureProblem; }; + +// Set the wetting phase +template +struct WettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +// Set the non-wetting phase +template +struct NonwettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using FluidSystem = GetPropType; + enum { wettingPhaseIdx = FluidSystem::wettingPhaseIdx }; + enum { nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx }; + + using Scalar = GetPropType; + using Traits = Opm::TwoPhaseMaterialTraits; + + // define the material law which is parameterized by effective + // saturations + using EffectiveLaw = Opm::RegularizedBrooksCorey; + // using EffectiveLaw = RegularizedVanGenuchten; + // using EffectiveLaw = LinearMaterial; +public: + using type = Opm::EffToAbsLaw; +}; + +// Enable the energy equation +template +struct EnableEnergy { static constexpr bool value = true; }; + +// Set the thermal conduction law +template +struct ThermalConductionLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + +public: + // define the material law parameterized by absolute saturations + using type = Opm::SomertonThermalConductionLaw; +}; + +// set the energy storage law for the solid phase +template +struct SolidEnergyLaw +{ using type = Opm::ConstantSolidHeatCapLaw>; }; + +// For this problem, we use constraints to specify the left boundary +template +struct EnableConstraints { static constexpr bool value = true; }; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup TestProblems + * + * \brief Two-phase problem which involves fractures + * + * The domain is initially completely saturated by the oil phase, + * except for the left side, which is fully water saturated. Since the + * capillary pressure in the fractures is lower than in the rock + * matrix and the material is hydrophilic, water infiltrates through + * the fractures and gradually pushes the oil out on the right side, + * where the pressure is kept constant. + */ +template +class FractureProblem : public GetPropType +{ + using ParentType = GetPropType; + using GridView = GetPropType; + using FluidSystem = GetPropType; + using WettingPhase = GetPropType; + using NonwettingPhase = GetPropType; + using Constraints = GetPropType; + using EqVector = GetPropType; + using PrimaryVariables = GetPropType; + using BoundaryRateVector = GetPropType; + using RateVector = GetPropType; + using Simulator = GetPropType; + using Scalar = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using ThermalConductionLawParams = GetPropType; + using SolidEnergyLawParams = GetPropType; + using Model = GetPropType; + + enum { + // phase indices + wettingPhaseIdx = MaterialLaw::wettingPhaseIdx, + nonWettingPhaseIdx = MaterialLaw::nonWettingPhaseIdx, + + // number of phases + numPhases = FluidSystem::numPhases, + + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + using FluidState = Opm::ImmiscibleFluidState; + + using GlobalPosition = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + + template + struct FaceLayout + { + bool contains(Dune::GeometryType gt) + { return gt.dim() == dim - 1; } + }; + using FaceMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + + using FractureMapper = Opm::FractureMapper; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + FractureProblem(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + eps_ = 3e-6; + temperature_ = 273.15 + 20; // -> 20Ā°C + + matrixMaterialParams_.setResidualSaturation(wettingPhaseIdx, 0.0); + matrixMaterialParams_.setResidualSaturation(nonWettingPhaseIdx, 0.0); + fractureMaterialParams_.setResidualSaturation(wettingPhaseIdx, 0.0); + fractureMaterialParams_.setResidualSaturation(nonWettingPhaseIdx, 0.0); + +#if 0 // linear + matrixMaterialParams_.setEntryPC(0.0); + matrixMaterialParams_.setMaxPC(2000.0); + fractureMaterialParams_.setEntryPC(0.0); + fractureMaterialParams_.setMaxPC(1000.0); +#endif + +#if 1 // Brooks-Corey + matrixMaterialParams_.setEntryPressure(2000); + matrixMaterialParams_.setLambda(2.0); + matrixMaterialParams_.setPcLowSw(1e-1); + fractureMaterialParams_.setEntryPressure(1000); + fractureMaterialParams_.setLambda(2.0); + fractureMaterialParams_.setPcLowSw(5e-2); +#endif + +#if 0 // van Genuchten + matrixMaterialParams_.setVgAlpha(0.0037); + matrixMaterialParams_.setVgN(4.7); + fractureMaterialParams_.setVgAlpha(0.0025); + fractureMaterialParams_.setVgN(4.7); +#endif + + matrixMaterialParams_.finalize(); + fractureMaterialParams_.finalize(); + + matrixK_ = this->toDimMatrix_(1e-15); // m^2 + fractureK_ = this->toDimMatrix_(1e5 * 1e-15); // m^2 + + matrixPorosity_ = 0.10; + fracturePorosity_ = 0.25; + fractureWidth_ = 1e-3; // [m] + + // initialize the energy-related parameters + initEnergyParams_(thermalConductionParams_, matrixPorosity_); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault("data/fracture.art.dgf"); + Parameters::SetDefault>(3e3); + Parameters::SetDefault>(100); + } + + /*! + * \name Auxiliary methods + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + std::ostringstream oss; + oss << "fracture_" << Model::name(); + return oss.str(); + } + + /*! + * \brief Called directly after the time integration. + */ + void endTimeStep() + { +#ifndef NDEBUG + // checkConservativeness() does not include the effect of constraints, so we + // disable it for this problem... + //this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { return temperature_; } + + // \} + + /*! + * \name Soil parameters + */ + //! \{ + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { return matrixK_; } + + /*! + * \brief Intrinsic permeability of fractures. + * + * \copydoc Doxygen::contextParams + */ + template + const DimMatrix& fractureIntrinsicPermeability([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { return fractureK_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { return matrixPorosity_; } + + /*! + * \brief The porosity inside the fractures. + * + * \copydoc Doxygen::contextParams + */ + template + Scalar fracturePorosity([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { return fracturePorosity_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { return matrixMaterialParams_; } + + /*! + * \brief The parameters for the material law inside the fractures. + * + * \copydoc Doxygen::contextParams + */ + template + const MaterialLawParams& fractureMaterialLawParams([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { return fractureMaterialParams_; } + + /*! + * \brief Returns the object representating the fracture topology. + */ + const FractureMapper& fractureMapper() const + { return this->simulator().vanguard().fractureMapper(); } + + /*! + * \brief Returns the width of the fracture. + * + * \todo This method should get one face index instead of two + * vertex indices. This probably requires a new context + * class, though. + * + * \param context The execution context. + * \param spaceIdx1 The local index of the edge's first edge. + * \param spaceIdx2 The local index of the edge's second edge. + * \param timeIdx The index used by the time discretization. + */ + template + Scalar fractureWidth([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx1, + [[maybe_unused]] unsigned spaceIdx2, + [[maybe_unused]] unsigned timeIdx) const + { return fractureWidth_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::thermalConductionParams + */ + template + const ThermalConductionLawParams& + thermalConductionLawParams([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { return thermalConductionParams_; } + + /*! + * \brief Return the parameters for the energy storage law of the rock + * + * In this case, we assume the rock-matrix to be granite. + */ + template + const SolidEnergyLawParams& + solidEnergyLawParams([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { return solidEnergyParams_; } + + // \} + + /*! + * \name Boundary conditions + */ + // \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + + if (onRightBoundary_(pos)) { + // on the right boundary, we impose a free-flow + // (i.e. Dirichlet) condition + FluidState fluidState; + fluidState.setTemperature(temperature_); + + fluidState.setSaturation(wettingPhaseIdx, 0.0); + fluidState.setSaturation(nonWettingPhaseIdx, + 1.0 - fluidState.saturation(wettingPhaseIdx)); + + fluidState.setPressure(wettingPhaseIdx, 1e5); + fluidState.setPressure(nonWettingPhaseIdx, fluidState.pressure(wettingPhaseIdx)); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fluidState); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + fluidState.setDensity(phaseIdx, + FluidSystem::density(fluidState, paramCache, phaseIdx)); + fluidState.setViscosity(phaseIdx, + FluidSystem::viscosity(fluidState, paramCache, phaseIdx)); + } + + // set a free flow (i.e. Dirichlet) boundary + values.setFreeFlow(context, spaceIdx, timeIdx, fluidState); + } + else + // for the upper, lower and left boundaries, use a no-flow + // condition (i.e. a Neumann 0 condition) + values.setNoFlow(); + } + + // \} + + /*! + * \name Volumetric terms + */ + // \{ + + /*! + * \copydoc FvBaseProblem::constraints + */ + template + void constraints(Constraints& constraints, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + + if (!onLeftBoundary_(pos)) + // only impose constraints adjacent to the left boundary + return; + + unsigned globalIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + if (!fractureMapper().isFractureVertex(globalIdx)) { + // do not impose constraints if the finite volume does + // not contain fractures. + return; + } + + // if the current finite volume is on the left boundary + // and features a fracture, specify the fracture fluid + // state. + FluidState fractureFluidState; + fractureFluidState.setTemperature(temperature_ + 10.0); + + fractureFluidState.setSaturation(wettingPhaseIdx, 1.0); + fractureFluidState.setSaturation(nonWettingPhaseIdx, + 1.0 - fractureFluidState.saturation( + wettingPhaseIdx)); + + Scalar pCFracture[numPhases]; + MaterialLaw::capillaryPressures(pCFracture, fractureMaterialParams_, + fractureFluidState); + + fractureFluidState.setPressure(wettingPhaseIdx, /*pressure=*/1.0e5); + fractureFluidState.setPressure(nonWettingPhaseIdx, + fractureFluidState.pressure(wettingPhaseIdx) + + (pCFracture[nonWettingPhaseIdx] + - pCFracture[wettingPhaseIdx])); + + constraints.setActive(true); + constraints.assignNaiveFromFracture(fractureFluidState, + matrixMaterialParams_); + } + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, + [[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { + FluidState fluidState; + fluidState.setTemperature(temperature_); + fluidState.setPressure(FluidSystem::wettingPhaseIdx, /*pressure=*/1e5); + fluidState.setPressure(nonWettingPhaseIdx, fluidState.pressure(wettingPhaseIdx)); + + fluidState.setSaturation(wettingPhaseIdx, 0.0); + fluidState.setSaturation(nonWettingPhaseIdx, + 1.0 - fluidState.saturation(wettingPhaseIdx)); + + values.assignNaive(fluidState); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + [[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned spaceIdx, + [[maybe_unused]] unsigned timeIdx) const + { rate = Scalar(0.0); } + + // \} + +private: + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < this->boundingBoxMin()[0] + eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + bool onLowerBoundary_(const GlobalPosition& pos) const + { return pos[1] < this->boundingBoxMin()[1] + eps_; } + + bool onUpperBoundary_(const GlobalPosition& pos) const + { return pos[1] > this->boundingBoxMax()[1] - eps_; } + + void initEnergyParams_(ThermalConductionLawParams& params, Scalar poro) + { + // assume the volumetric heat capacity of granite + solidEnergyParams_.setSolidHeatCapacity(790.0 // specific heat capacity of granite [J / (kg K)] + * 2700.0); // density of granite [kg/m^3] + solidEnergyParams_.finalize(); + + Scalar lambdaGranite = 2.8; // [W / (K m)] + + // create a Fluid state which has all phases present + Opm::ImmiscibleFluidState fs; + fs.setTemperature(293.15); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + fs.setPressure(phaseIdx, 1.0135e5); + } + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar rho = FluidSystem::density(fs, paramCache, phaseIdx); + fs.setDensity(phaseIdx, rho); + } + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar lambdaSaturated; + if (FluidSystem::isLiquid(phaseIdx)) { + Scalar lambdaFluid = FluidSystem::thermalConductivity(fs, paramCache, phaseIdx); + lambdaSaturated = + std::pow(lambdaGranite, (1 - poro)) + + std::pow(lambdaFluid, poro); + } + else + lambdaSaturated = std::pow(lambdaGranite, (1 - poro)); + + params.setFullySaturatedLambda(phaseIdx, lambdaSaturated); + } + + Scalar lambdaVac = std::pow(lambdaGranite, (1 - poro)); + params.setVacuumLambda(lambdaVac); + } + + DimMatrix matrixK_; + DimMatrix fractureK_; + + Scalar matrixPorosity_; + Scalar fracturePorosity_; + + Scalar fractureWidth_; + + MaterialLawParams fractureMaterialParams_; + MaterialLawParams matrixMaterialParams_; + + ThermalConductionLawParams thermalConductionParams_; + SolidEnergyLawParams solidEnergyParams_; + + Scalar temperature_; + Scalar eps_; +}; +} // namespace Opm + +#endif // EWOMS_FRACTURE_PROBLEM_HH diff --git a/examples/problems/groundwaterproblem.hh b/examples/problems/groundwaterproblem.hh new file mode 100644 index 00000000000..28cf81971cf --- /dev/null +++ b/examples/problems/groundwaterproblem.hh @@ -0,0 +1,403 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::GroundWaterProblem + */ +#ifndef EWOMS_GROUND_WATER_PROBLEM_HH +#define EWOMS_GROUND_WATER_PROBLEM_HH + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +namespace Opm { +template +class GroundWaterProblem; +} + +namespace Opm::Properties { + +namespace TTag { +struct GroundWaterBaseProblem {}; +} + +template +struct Fluid +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +// Set the grid type +template +struct Grid { using type = Dune::YaspGrid<2>; }; +// struct Grid { using type = Dune::SGrid<2, 2>; }; + +template +struct Problem +{ using type = Opm::GroundWaterProblem; }; + +// Use the conjugated gradient linear solver with the default preconditioner (i.e., +// ILU-0) from dune-istl +template +struct LinearSolverSplice { using type = TTag::ParallelIstlLinearSolver; }; + +template +struct LinearSolverWrapper +{ using type = Opm::Linear::SolverWrapperConjugatedGradients; }; + +} // namespace Opm::Properties + +namespace Opm::Parameters { + +template +struct LensLowerLeftX { static constexpr Scalar value = 0.25; }; + +template +struct LensLowerLeftY { static constexpr Scalar value = 0.25; }; + +template +struct LensLowerLeftZ { static constexpr Scalar value = 0.25; }; + +template +struct LensUpperRightX { static constexpr Scalar value = 0.75; }; + +template +struct LensUpperRightY { static constexpr Scalar value = 0.75; }; + +template +struct LensUpperRightZ { static constexpr Scalar value = 0.75; }; + +template +struct Permeability { static constexpr Scalar value = 1e-10; }; + +template +struct PermeabilityLens { static constexpr Scalar value = 1e-12; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup TestProblems + * + * \brief Test for the immisicible VCVF discretization with only a single phase + * + * This problem is inspired by groundwater flow. Don't expect it to be + * realistic, though: For two dimensions, the domain size is 1m times + * 1m. On the left and right of the domain, no-flow boundaries are + * used, while at the top and bottom free flow boundaries with a + * pressure of 2 bar and 1 bar are used. The center of the domain is + * occupied by a rectangular lens of lower permeability. + */ +template +class GroundWaterProblem : public GetPropType +{ + using ParentType = GetPropType; + + using GridView = GetPropType; + using Scalar = GetPropType; + using FluidSystem = GetPropType; + + // copy some indices for convenience + using Indices = GetPropType; + enum { + numPhases = FluidSystem::numPhases, + + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld, + + // indices of the primary variables + pressure0Idx = Indices::pressure0Idx + }; + + using Simulator = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using PrimaryVariables = GetPropType; + using Model = GetPropType; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + GroundWaterProblem(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + eps_ = 1.0e-3; + + lensLowerLeft_[0] = Parameters::Get>(); + if (dim > 1) + lensLowerLeft_[1] = Parameters::Get>(); + if (dim > 2) + lensLowerLeft_[2] = Parameters::Get>(); + + lensUpperRight_[0] = Parameters::Get>(); + if (dim > 1) + lensUpperRight_[1] = Parameters::Get>(); + if (dim > 2) + lensUpperRight_[2] = Parameters::Get>(); + + intrinsicPerm_ = this->toDimMatrix_(Parameters::Get>()); + intrinsicPermLens_ = this->toDimMatrix_(Parameters::Get>()); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register> + ("The x-coordinate of the lens' lower-left corner [m]."); + Parameters::Register> + ("The x-coordinate of the lens' upper-right corner [m]."); + + if (dimWorld > 1) { + Parameters::Register> + ("The y-coordinate of the lens' lower-left corner [m]."); + Parameters::Register> + ("The y-coordinate of the lens' upper-right corner [m]."); + } + + if (dimWorld > 2) { + Parameters::Register> + ("The z-coordinate of the lens' lower-left corner [m]."); + Parameters::Register> + ("The z-coordinate of the lens' upper-right corner [m]."); + } + + Parameters::Register> + ("The intrinsic permeability [m^2] of the ambient material."); + Parameters::Register> + ("The intrinsic permeability [m^2] of the lens."); + + Parameters::SetDefault("./data/groundwater_2d.dgf"); + Parameters::SetDefault>(1.0); + Parameters::SetDefault>(1.0); + Parameters::SetDefault(true); + } + + /*! + * \name Problem parameters + */ + // \{ + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + std::ostringstream oss; + oss << "groundwater_" << Model::name(); + return oss.str(); + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return 273.15 + 10; } // 10C + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return 0.4; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + if (isInLens_(context.pos(spaceIdx, timeIdx))) + return intrinsicPermLens_; + else + return intrinsicPerm_; + } + + //! \} + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& globalPos = context.pos(spaceIdx, timeIdx); + + if (onLowerBoundary_(globalPos) || onUpperBoundary_(globalPos)) { + Scalar pressure; + Scalar T = temperature(context, spaceIdx, timeIdx); + if (onLowerBoundary_(globalPos)) + pressure = 2e5; + else // on upper boundary + pressure = 1e5; + + Opm::ImmiscibleFluidState fs; + fs.setSaturation(/*phaseIdx=*/0, 1.0); + fs.setPressure(/*phaseIdx=*/0, pressure); + fs.setTemperature(T); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + fs.setDensity(phaseIdx, FluidSystem::density(fs, paramCache, phaseIdx)); + fs.setViscosity(phaseIdx, FluidSystem::viscosity(fs, paramCache, phaseIdx)); + } + + // impose an freeflow boundary condition + values.setFreeFlow(context, spaceIdx, timeIdx, fs); + } + else { + // no flow boundary + values.setNoFlow(); + } + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { + // const GlobalPosition& globalPos = context.pos(spaceIdx, timeIdx); + values[pressure0Idx] = 1.0e+5; // + 9.81*1.23*(20-globalPos[dim-1]); + } + + /*! + * \copydoc FvBaseProblem::source + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + bool onLowerBoundary_(const GlobalPosition& pos) const + { return pos[dim - 1] < eps_; } + + bool onUpperBoundary_(const GlobalPosition& pos) const + { return pos[dim - 1] > this->boundingBoxMax()[dim - 1] - eps_; } + + bool isInLens_(const GlobalPosition& pos) const + { + return lensLowerLeft_[0] <= pos[0] && pos[0] <= lensUpperRight_[0] + && lensLowerLeft_[1] <= pos[1] && pos[1] <= lensUpperRight_[1]; + } + + GlobalPosition lensLowerLeft_; + GlobalPosition lensUpperRight_; + + DimMatrix intrinsicPerm_; + DimMatrix intrinsicPermLens_; + + Scalar eps_; +}; +} // namespace Opm + +#endif diff --git a/examples/problems/infiltrationproblem.hh b/examples/problems/infiltrationproblem.hh new file mode 100644 index 00000000000..cbae93e5a96 --- /dev/null +++ b/examples/problems/infiltrationproblem.hh @@ -0,0 +1,476 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::InfiltrationProblem + */ +#ifndef EWOMS_INFILTRATION_PROBLEM_HH +#define EWOMS_INFILTRATION_PROBLEM_HH + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +namespace Opm { +template +class InfiltrationProblem; +} + +namespace Opm::Properties { + +namespace TTag { +struct InfiltrationBaseProblem {}; +} + +// Set the grid type +template +struct Grid { using type = Dune::YaspGrid<2>; }; + +// Set the problem property +template +struct Problem { using type = Opm::InfiltrationProblem; }; + +// Set the fluid system +template +struct FluidSystem +{ using type = Opm::H2OAirMesityleneFluidSystem>; }; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + + using Traits= Opm::ThreePhaseMaterialTraits< + Scalar, + /*wettingPhaseIdx=*/FluidSystem::waterPhaseIdx, + /*nonWettingPhaseIdx=*/FluidSystem::naplPhaseIdx, + /*gasPhaseIdx=*/FluidSystem::gasPhaseIdx>; + +public: + using type = Opm::ThreePhaseParkerVanGenuchten; +}; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup TestProblems + * \brief Isothermal NAPL infiltration problem where LNAPL + * contaminates the unsaturated and the saturated groundwater + * zone. + * + * The 2D domain of this test problem is 500 m long and 10 m deep, + * where the lower part represents a slightly inclined groundwater + * table, and the upper part is the vadose zone. A LNAPL (Non-Aqueous + * Phase Liquid which is lighter than water) infiltrates (modelled + * with a Neumann boundary condition) into the vadose zone. Upon + * reaching the water table, it spreads (since lighter than water) and + * migrates on top of the water table in the direction of the slope. + * On its way through the vadose zone, it leaves a trace of residually + * trapped immobile NAPL, which can in the following dissolve and + * evaporate slowly, and eventually be transported by advection and + * diffusion. + * + * Left and right boundaries are constant hydraulic head boundaries + * (Dirichlet), Top and bottom are Neumann boundaries, all no-flow + * except for the small infiltration zone in the upper left part. + */ +template +class InfiltrationProblem : public GetPropType +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using GridView = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using PrimaryVariables = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using Simulator = GetPropType; + using FluidSystem = GetPropType; + using Model = GetPropType; + + // copy some indices for convenience + using Indices = GetPropType; + enum { + // equation indices + conti0EqIdx = Indices::conti0EqIdx, + + // number of phases/components + numPhases = FluidSystem::numPhases, + + // component indices + NAPLIdx = FluidSystem::NAPLIdx, + H2OIdx = FluidSystem::H2OIdx, + airIdx = FluidSystem::airIdx, + + // phase indices + waterPhaseIdx = FluidSystem::waterPhaseIdx, + gasPhaseIdx = FluidSystem::gasPhaseIdx, + naplPhaseIdx = FluidSystem::naplPhaseIdx, + + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + InfiltrationProblem(Simulator& simulator) + : ParentType(simulator) + , eps_(1e-6) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + temperature_ = 273.15 + 10.0; // -> 10 degrees Celsius + FluidSystem::init(/*tempMin=*/temperature_ - 1, + /*tempMax=*/temperature_ + 1, + /*nTemp=*/3, + /*pressMin=*/0.8 * 1e5, + /*pressMax=*/3 * 1e5, + /*nPress=*/200); + + // intrinsic permeabilities + fineK_ = this->toDimMatrix_(1e-11); + coarseK_ = this->toDimMatrix_(1e-11); + + // porosities + porosity_ = 0.40; + + // residual saturations + materialParams_.setSwr(0.12); + materialParams_.setSwrx(0.12); + materialParams_.setSnr(0.07); + materialParams_.setSgr(0.03); + + // parameters for the three-phase van Genuchten law + materialParams_.setVgAlpha(0.0005); + materialParams_.setVgN(4.); + materialParams_.setkrRegardsSnr(false); + + materialParams_.finalize(); + materialParams_.checkDefined(); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault("./data/infiltration_50x3.dgf"); + Parameters::SetDefault(1); + Parameters::SetDefault>(6e3); + Parameters::SetDefault>(60.0); + Parameters::SetDefault(true); + } + + /*! + * \name Problem parameters + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::shouldWriteRestartFile + * + * This problem writes a restart file after every time step. + */ + bool shouldWriteRestartFile() const + { return true; } + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + std::ostringstream oss; + oss << "infiltration_" << Model::name(); + return oss.str(); + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return temperature_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& + intrinsicPermeability(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineK_; + return coarseK_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return porosity_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& + materialLawParams(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return materialParams_; } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const auto& pos = context.pos(spaceIdx, timeIdx); + + if (onLeftBoundary_(pos) || onRightBoundary_(pos)) { + Opm::CompositionalFluidState fs; + + initialFluidState_(fs, context, spaceIdx, timeIdx); + + values.setFreeFlow(context, spaceIdx, timeIdx, fs); + } + else if (onInlet_(pos)) { + RateVector molarRate(0.0); + molarRate[conti0EqIdx + NAPLIdx] = -0.001; + + values.setMolarRate(molarRate); + Opm::Valgrind::CheckDefined(values); + } + else + values.setNoFlow(); + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + Opm::CompositionalFluidState fs; + + initialFluidState_(fs, context, spaceIdx, timeIdx); + + const auto& matParams = materialLawParams(context, spaceIdx, timeIdx); + values.assignMassConservative(fs, matParams, /*inEquilibrium=*/true); + Opm::Valgrind::CheckDefined(values); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + bool onLowerBoundary_(const GlobalPosition& pos) const + { return pos[1] < eps_; } + + bool onUpperBoundary_(const GlobalPosition& pos) const + { return pos[1] > this->boundingBoxMax()[1] - eps_; } + + bool onInlet_(const GlobalPosition& pos) const + { return onUpperBoundary_(pos) && 50 < pos[0] && pos[0] < 75; } + + template + void initialFluidState_(FluidState& fs, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition pos = context.pos(spaceIdx, timeIdx); + Scalar y = pos[1]; + Scalar x = pos[0]; + + Scalar densityW = 1000.0; + Scalar pc = 9.81 * densityW * (y - (5 - 5e-4 * x)); + if (pc < 0.0) + pc = 0.0; + + // set pressures + const auto& matParams = materialLawParams(context, spaceIdx, timeIdx); + Scalar Sw = matParams.Swr(); + Scalar Swr = matParams.Swr(); + Scalar Sgr = matParams.Sgr(); + if (Sw < Swr) + Sw = Swr; + if (Sw > 1 - Sgr) + Sw = 1 - Sgr; + Scalar Sg = 1 - Sw; + + Opm::Valgrind::CheckDefined(Sw); + Opm::Valgrind::CheckDefined(Sg); + + fs.setSaturation(waterPhaseIdx, Sw); + fs.setSaturation(gasPhaseIdx, Sg); + fs.setSaturation(naplPhaseIdx, 0); + + // set temperature of all phases + fs.setTemperature(temperature_); + + // compute pressures + Scalar pcAll[numPhases]; + Scalar pg = 1e5; + if (onLeftBoundary_(pos)) + pg += 10e3; + MaterialLaw::capillaryPressures(pcAll, matParams, fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + fs.setPressure(phaseIdx, pg + (pcAll[phaseIdx] - pcAll[gasPhaseIdx])); + + // set composition of gas phase + fs.setMoleFraction(gasPhaseIdx, H2OIdx, 1e-6); + fs.setMoleFraction(gasPhaseIdx, airIdx, + 1 - fs.moleFraction(gasPhaseIdx, H2OIdx)); + fs.setMoleFraction(gasPhaseIdx, NAPLIdx, 0); + + using CFRP = Opm::ComputeFromReferencePhase; + typename FluidSystem::template ParameterCache paramCache; + CFRP::solve(fs, paramCache, gasPhaseIdx, + /*setViscosity=*/true, + /*setEnthalpy=*/false); + + fs.setMoleFraction(waterPhaseIdx, H2OIdx, + 1 - fs.moleFraction(waterPhaseIdx, H2OIdx)); + } + + bool isFineMaterial_(const GlobalPosition& pos) const + { return 70. <= pos[0] && pos[0] <= 85. && 7.0 <= pos[1] && pos[1] <= 7.50; } + + DimMatrix fineK_; + DimMatrix coarseK_; + + Scalar porosity_; + + MaterialLawParams materialParams_; + + Scalar temperature_; + Scalar eps_; +}; +} // namespace Opm + +#endif diff --git a/examples/problems/lensproblem.hh b/examples/problems/lensproblem.hh new file mode 100644 index 00000000000..3517005c2d6 --- /dev/null +++ b/examples/problems/lensproblem.hh @@ -0,0 +1,643 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::LensProblem + */ +#ifndef EWOMS_LENS_PROBLEM_HH +#define EWOMS_LENS_PROBLEM_HH + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include + +namespace Opm { +template +class LensProblem; +} + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct LensBaseProblem { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +// Set the problem property +template +struct Problem { using type = Opm::LensProblem; }; + +// Use Dune-grid's YaspGrid +template +struct Grid { using type = Dune::YaspGrid<2>; }; + +// Set the wetting phase +template +struct WettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +// Set the non-wetting phase +template +struct NonwettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using FluidSystem = GetPropType; + enum { wettingPhaseIdx = FluidSystem::wettingPhaseIdx }; + enum { nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx }; + + using Scalar = GetPropType; + using Traits = Opm::TwoPhaseMaterialTraits; + + // define the material law which is parameterized by effective + // saturations + using EffectiveLaw = Opm::RegularizedVanGenuchten; + +public: + // define the material law parameterized by absolute saturations + using type = Opm::EffToAbsLaw; +}; + +} // namespace Opm::Properties + +namespace Opm::Parameters { + +// define the properties specific for the lens problem +template +struct LensLowerLeftX { static constexpr Scalar value = 1.0; }; + +template +struct LensLowerLeftY { static constexpr Scalar value = 2.0; }; + +template +struct LensLowerLeftZ { static constexpr Scalar value = 0.0; }; + +template +struct LensUpperRightX { static constexpr Scalar value = 4.0; }; + +template +struct LensUpperRightY { static constexpr Scalar value = 3.0; }; + +template +struct LensUpperRightZ { static constexpr Scalar value = 1.0; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! + * \ingroup TestProblems + * + * \brief Soil contamination problem where DNAPL infiltrates a fully + * water saturated medium. + * + * The domain is sized 6m times 4m and features a rectangular lens + * with low permeablility which spans from (1m, 2m) to (4m, 3m) + * and is surrounded by a medium with higher permability. Note that + * this problem is discretized using only two dimensions, so from the + * point of view of the model, the depth of the domain is implicitly + * assumed to be 1 m everywhere. + * + * On the top and the bottom of the domain no-flow boundary conditions + * are used, while free-flow conditions apply on the left and right + * boundaries; DNAPL is injected at the top boundary from 3m to 4m at + * a rate of 0.04 kg/(s m^2). + * + * At the boundary on the left, a free-flow condition using the + * hydrostatic pressure scaled by a factor of 1.125 is imposed, while + * on the right, it is just the hydrostatic pressure. The DNAPL + * saturation on both sides is zero. + */ +template +class LensProblem : public GetPropType +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using GridView = GetPropType; + using Indices = GetPropType; + using FluidSystem = GetPropType; + using WettingPhase = GetPropType; + using NonwettingPhase = GetPropType; + using PrimaryVariables = GetPropType; + using Simulator = GetPropType; + using Model = GetPropType; + + enum { + // number of phases + numPhases = FluidSystem::numPhases, + + // phase indices + wettingPhaseIdx = FluidSystem::wettingPhaseIdx, + nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx, + + // equation indices + contiNEqIdx = Indices::conti0EqIdx + nonWettingPhaseIdx, + + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + LensProblem(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + eps_ = 3e-6; + FluidSystem::init(); + + temperature_ = 273.15 + 20; // -> 20Ā°C + lensLowerLeft_[0] = Parameters::Get>(); + lensLowerLeft_[1] = Parameters::Get>(); + lensUpperRight_[0] = Parameters::Get>(); + lensUpperRight_[1] = Parameters::Get>(); + + if constexpr (dim == 3) { + lensLowerLeft_[2] = Parameters::Get>(); + lensUpperRight_[2] = Parameters::Get>(); + } + + // residual saturations + lensMaterialParams_.setResidualSaturation(wettingPhaseIdx, 0.18); + lensMaterialParams_.setResidualSaturation(nonWettingPhaseIdx, 0.0); + outerMaterialParams_.setResidualSaturation(wettingPhaseIdx, 0.05); + outerMaterialParams_.setResidualSaturation(nonWettingPhaseIdx, 0.0); + + // parameters for the Van Genuchten law: alpha and n + lensMaterialParams_.setVgAlpha(0.00045); + lensMaterialParams_.setVgN(7.3); + outerMaterialParams_.setVgAlpha(0.0037); + outerMaterialParams_.setVgN(4.7); + + lensMaterialParams_.finalize(); + outerMaterialParams_.finalize(); + + lensK_ = this->toDimMatrix_(9.05e-12); + outerK_ = this->toDimMatrix_(4.6e-10); + + if (dimWorld == 3) { + this->gravity_ = 0; + this->gravity_[1] = -9.81; + } + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register> + ("The x-coordinate of the lens' lower-left corner [m]."); + Parameters::Register> + ("The y-coordinate of the lens' lower-left corner [m]."); + Parameters::Register> + ("The x-coordinate of the lens' upper-right corner [m]."); + Parameters::Register> + ("The y-coordinate of the lens' upper-right corner [m]."); + + if constexpr (dim == 3) { + Parameters::Register> + ("The z-coordinate of the lens' lower-left corner [m]."); + Parameters::Register> + ("The z-coordinate of the lens' upper-right corner [m]."); + } + + Parameters::SetDefault(48); + Parameters::SetDefault(32); + Parameters::SetDefault>(6.0); + Parameters::SetDefault>(4.0); + + if constexpr (dim == 3) { + Parameters::SetDefault(16); + Parameters::SetDefault>(1.0); + } + + // Use forward differences + using LLS = GetPropType; + constexpr bool useFD = std::is_same_v; + if constexpr (useFD) { + Parameters::SetDefault(+1); + } + + Parameters::SetDefault>(30e3); + Parameters::SetDefault(true); + Parameters::SetDefault(true); + Parameters::SetDefault>(250.0); + Parameters::SetDefault(true); + Parameters::SetDefault(true); + } + + /*! + * \copydoc FvBaseProblem::briefDescription + */ + static std::string briefDescription() + { + std::string thermal = "isothermal"; + constexpr bool enableEnergy = getPropValue(); + if constexpr (enableEnergy) + thermal = "non-isothermal"; + + std::string deriv = "finite difference"; + using LLS = GetPropType; + constexpr bool useAutoDiff = std::is_same_v; + if constexpr (useAutoDiff) { + deriv = "automatic differentiation"; + } + + std::string disc = "vertex centered finite volume"; + using D = GetPropType; + constexpr bool useEcfv = std::is_same>::value; + if constexpr (useEcfv) + disc = "element centered finite volume"; + + return std::string("")+ + "Ground remediation problem where a dense oil infiltrates "+ + "an aquifer with an embedded low-permability lens. " + + "This is the binary for the "+thermal+" variant using "+deriv+ + "and the "+disc+" discretization"; + } + + /*! + * \name Soil parameters + */ + //! \{ + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability(const Context& context, unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& globalPos = context.pos(spaceIdx, timeIdx); + + if (isInLens_(globalPos)) + return lensK_; + return outerK_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return 0.4; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams(const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& globalPos = context.pos(spaceIdx, timeIdx); + + if (isInLens_(globalPos)) + return lensMaterialParams_; + return outerMaterialParams_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return temperature_; } + + //! \} + + /*! + * \name Auxiliary methods + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + using LLS = GetPropType; + + constexpr bool useAutoDiff = std::is_same_v; + + using FM = GetPropType; + constexpr bool useTrans = std::is_same_v>; + + std::ostringstream oss; + oss << "lens_" << Model::name() + << "_" << Model::discretizationName() + << "_" << (useAutoDiff?"ad":"fd"); + if (useTrans) + oss << "_trans"; + + return oss.str(); + } + + /*! + * \copydoc FvBaseProblem::beginTimeStep + */ + void beginTimeStep() + { } + + /*! + * \copydoc FvBaseProblem::beginIteration + */ + void beginIteration() + { } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + //this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + + if (onLeftBoundary_(pos) || onRightBoundary_(pos)) { + // free flow boundary. we assume incompressible fluids + Scalar densityW = WettingPhase::density(temperature_, /*pressure=*/Scalar(1e5)); + Scalar densityN = NonwettingPhase::density(temperature_, /*pressure=*/Scalar(1e5)); + + Scalar T = temperature(context, spaceIdx, timeIdx); + Scalar pw, Sw; + + // set wetting phase pressure and saturation + if (onLeftBoundary_(pos)) { + Scalar height = this->boundingBoxMax()[1] - this->boundingBoxMin()[1]; + Scalar depth = this->boundingBoxMax()[1] - pos[1]; + Scalar alpha = (1 + 1.5 / height); + + // hydrostatic pressure scaled by alpha + pw = 1e5 - alpha * densityW * this->gravity()[1] * depth; + Sw = 1.0; + } + else { + Scalar depth = this->boundingBoxMax()[1] - pos[1]; + + // hydrostatic pressure + pw = 1e5 - densityW * this->gravity()[1] * depth; + Sw = 1.0; + } + + // specify a full fluid state using pw and Sw + const MaterialLawParams& matParams = this->materialLawParams(context, spaceIdx, timeIdx); + + Opm::ImmiscibleFluidState fs; + fs.setSaturation(wettingPhaseIdx, Sw); + fs.setSaturation(nonWettingPhaseIdx, 1 - Sw); + fs.setTemperature(T); + + Scalar pC[numPhases]; + MaterialLaw::capillaryPressures(pC, matParams, fs); + fs.setPressure(wettingPhaseIdx, pw); + fs.setPressure(nonWettingPhaseIdx, pw + pC[nonWettingPhaseIdx] - pC[wettingPhaseIdx]); + + fs.setDensity(wettingPhaseIdx, densityW); + fs.setDensity(nonWettingPhaseIdx, densityN); + + fs.setViscosity(wettingPhaseIdx, WettingPhase::viscosity(temperature_, fs.pressure(wettingPhaseIdx))); + fs.setViscosity(nonWettingPhaseIdx, NonwettingPhase::viscosity(temperature_, fs.pressure(nonWettingPhaseIdx))); + + // impose an freeflow boundary condition + values.setFreeFlow(context, spaceIdx, timeIdx, fs); + } + else if (onInlet_(pos)) { + RateVector massRate(0.0); + massRate = 0.0; + massRate[contiNEqIdx] = -0.04; // kg / (m^2 * s) + + // impose a forced flow boundary + values.setMassRate(massRate); + } + else { + // no flow boundary + values.setNoFlow(); + } + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + Scalar depth = this->boundingBoxMax()[1] - pos[1]; + + Opm::ImmiscibleFluidState fs; + fs.setPressure(wettingPhaseIdx, /*pressure=*/1e5); + + Scalar Sw = 1.0; + fs.setSaturation(wettingPhaseIdx, Sw); + fs.setSaturation(nonWettingPhaseIdx, 1 - Sw); + + fs.setTemperature(temperature_); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updatePhase(fs, wettingPhaseIdx); + Scalar densityW = FluidSystem::density(fs, paramCache, wettingPhaseIdx); + + // hydrostatic pressure (assuming incompressibility) + Scalar pw = 1e5 - densityW * this->gravity()[1] * depth; + + // calculate the capillary pressure + const MaterialLawParams& matParams = this->materialLawParams(context, spaceIdx, timeIdx); + Scalar pC[numPhases]; + MaterialLaw::capillaryPressures(pC, matParams, fs); + + // make a full fluid state + fs.setPressure(wettingPhaseIdx, pw); + fs.setPressure(nonWettingPhaseIdx, pw + (pC[wettingPhaseIdx] - pC[nonWettingPhaseIdx])); + + // assign the primary variables + values.assignNaive(fs); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + bool isInLens_(const GlobalPosition& pos) const + { + for (unsigned i = 0; i < dim; ++i) { + if (pos[i] < lensLowerLeft_[i] - eps_ || pos[i] > lensUpperRight_[i] + + eps_) + return false; + } + return true; + } + + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < this->boundingBoxMin()[0] + eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + bool onLowerBoundary_(const GlobalPosition& pos) const + { return pos[1] < this->boundingBoxMin()[1] + eps_; } + + bool onUpperBoundary_(const GlobalPosition& pos) const + { return pos[1] > this->boundingBoxMax()[1] - eps_; } + + bool onInlet_(const GlobalPosition& pos) const + { + Scalar width = this->boundingBoxMax()[0] - this->boundingBoxMin()[0]; + Scalar lambda = (this->boundingBoxMax()[0] - pos[0]) / width; + return onUpperBoundary_(pos) && 0.5 < lambda && lambda < 2.0 / 3.0; + } + + GlobalPosition lensLowerLeft_; + GlobalPosition lensUpperRight_; + + DimMatrix lensK_; + DimMatrix outerK_; + MaterialLawParams lensMaterialParams_; + MaterialLawParams outerMaterialParams_; + + Scalar temperature_; + Scalar eps_; +}; + +} // namespace Opm + +#endif diff --git a/examples/problems/obstacleproblem.hh b/examples/problems/obstacleproblem.hh new file mode 100644 index 00000000000..306833f8038 --- /dev/null +++ b/examples/problems/obstacleproblem.hh @@ -0,0 +1,583 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ObstacleProblem + */ +#ifndef EWOMS_OBSTACLE_PROBLEM_HH +#define EWOMS_OBSTACLE_PROBLEM_HH + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +namespace Opm { +template +class ObstacleProblem; +} + +namespace Opm::Properties { + +namespace TTag { +struct ObstacleBaseProblem {}; +} + +// Set the grid type +template +struct Grid { using type = Dune::YaspGrid<2>; }; + +// Set the problem property +template +struct Problem { using type = Opm::ObstacleProblem; }; + +// Set fluid configuration +template +struct FluidSystem +{ using type = Opm::H2ON2FluidSystem>; }; + +// Set the material Law +template +struct MaterialLaw +{ +private: + // define the material law + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using MaterialTraits = Opm::TwoPhaseMaterialTraits; + + using EffMaterialLaw = Opm::LinearMaterial; + +public: + using type = Opm::EffToAbsLaw; +}; + +// Set the thermal conduction law +template +struct ThermalConductionLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + +public: + // define the material law parameterized by absolute saturations + using type = Opm::SomertonThermalConductionLaw; +}; + +// set the energy storage law for the solid phase +template +struct SolidEnergyLaw +{ using type = Opm::ConstantSolidHeatCapLaw>; }; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup TestProblems + * + * \brief Problem where liquid water is first stopped by a + * low-permeability lens and then seeps though it. + * + * Liquid water is injected by using of a free-flow condition on the + * lower right of the domain. This water level then raises until + * hydrostatic pressure is reached. On the left of the domain, a + * rectangular obstacle with \f$10^3\f$ lower permeability than the + * rest of the domain first stops the for a while until it seeps + * through it. + * + * The domain is sized 60m times 40m and consists of two media, a + * moderately permeable soil (\f$ K_0=10e-12 m^2\f$) and an obstacle + * at \f$[10; 20]m \times [0; 35]m \f$ with a lower permeablility of + * \f$ K_1=K_0/1000\f$. + * + * Initially the whole domain is filled by nitrogen, the temperature + * is \f$20^\circ C\f$ for the whole domain. The gas pressure is + * initially 1 bar, at the inlet of the liquid water on the right side + * it is 2 bar. + * + * The boundary is no-flow except on the lower 10 meters of the left + * and the right boundary where a free flow condition is assumed. + */ +template +class ObstacleProblem : public GetPropType +{ + using ParentType = GetPropType; + + using GridView = GetPropType; + using Scalar = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using PrimaryVariables = GetPropType; + using BoundaryRateVector = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using ThermalConductionLawParams = GetPropType; + using SolidEnergyLawParams = GetPropType; + + enum { + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld, + numPhases = getPropValue(), + gasPhaseIdx = FluidSystem::gasPhaseIdx, + liquidPhaseIdx = FluidSystem::liquidPhaseIdx, + H2OIdx = FluidSystem::H2OIdx, + N2Idx = FluidSystem::N2Idx + }; + + using GlobalPosition = Dune::FieldVector; + using PhaseVector = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + using Simulator = GetPropType; + using Model = GetPropType; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + ObstacleProblem(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + eps_ = 1e-6; + temperature_ = 273.15 + 25; // -> 25Ā°C + + // initialize the tables of the fluid system + Scalar Tmin = temperature_ - 1.0; + Scalar Tmax = temperature_ + 1.0; + unsigned nT = 3; + + Scalar pmin = 1.0e5 * 0.75; + Scalar pmax = 2.0e5 * 1.25; + unsigned np = 1000; + + FluidSystem::init(Tmin, Tmax, nT, pmin, pmax, np); + + // intrinsic permeabilities + coarseK_ = this->toDimMatrix_(1e-12); + fineK_ = this->toDimMatrix_(1e-15); + + // the porosity + finePorosity_ = 0.3; + coarsePorosity_ = 0.3; + + // residual saturations + fineMaterialParams_.setResidualSaturation(liquidPhaseIdx, 0.0); + fineMaterialParams_.setResidualSaturation(gasPhaseIdx, 0.0); + coarseMaterialParams_.setResidualSaturation(liquidPhaseIdx, 0.0); + coarseMaterialParams_.setResidualSaturation(gasPhaseIdx, 0.0); + + // parameters for the linear law, i.e. minimum and maximum + // pressures + fineMaterialParams_.setPcMinSat(liquidPhaseIdx, 0.0); + fineMaterialParams_.setPcMaxSat(liquidPhaseIdx, 0.0); + coarseMaterialParams_.setPcMinSat(liquidPhaseIdx, 0.0); + coarseMaterialParams_.setPcMaxSat(liquidPhaseIdx, 0.0); + + /* + // entry pressures for Brooks-Corey + fineMaterialParams_.setEntryPressure(5e3); + coarseMaterialParams_.setEntryPressure(1e3); + + // Brooks-Corey shape parameters + fineMaterialParams_.setLambda(2); + coarseMaterialParams_.setLambda(2); + */ + + fineMaterialParams_.finalize(); + coarseMaterialParams_.finalize(); + + // parameters for the somerton law of thermal conduction + computeThermalCondParams_(fineThermalCondParams_, finePorosity_); + computeThermalCondParams_(coarseThermalCondParams_, coarsePorosity_); + + // assume constant volumetric heat capacity and granite + solidEnergyLawParams_.setSolidHeatCapacity(790.0 // specific heat capacity of granite [J / (kg K)] + * 2700.0); // density of granite [kg/m^3] + solidEnergyLawParams_.finalize(); + + initFluidStates_(); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault("./data/obstacle_24x16.dgf"); + Parameters::SetDefault>(1e4); + Parameters::SetDefault>(250); + Parameters::SetDefault(true); + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + this->model().checkConservativeness(); + + // Calculate storage terms of the individual phases + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + PrimaryVariables phaseStorage; + this->model().globalPhaseStorage(phaseStorage, phaseIdx); + + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage in " << FluidSystem::phaseName(phaseIdx) + << "Phase: [" << phaseStorage << "]" + << "\n" << std::flush; + } + } + + // Calculate total storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage total: [" << storage << "]" + << "\n" << std::flush; + } +#endif // NDEBUG + } + + /*! + * \name Problem parameters + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + std::ostringstream oss; + oss << "obstacle" + << "_" << Model::name(); + return oss.str(); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + * + * This problem simply assumes a constant temperature. + */ + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return temperature_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& + intrinsicPermeability(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + if (isFineMaterial_(context.pos(spaceIdx, timeIdx))) + return fineK_; + return coarseK_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return finePorosity_; + else + return coarsePorosity_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& + materialLawParams(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineMaterialParams_; + else + return coarseMaterialParams_; + } + + /*! + * \brief Return the parameters for the energy storage law of the rock + * + * In this case, we assume the rock-matrix to be granite. + */ + template + const SolidEnergyLawParams& + solidEnergyLawParams(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return solidEnergyLawParams_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::thermalConductionParams + */ + template + const ThermalConductionLawParams & + thermalConductionParams(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineThermalCondParams_; + return coarseThermalCondParams_; + } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const auto& pos = context.pos(spaceIdx, timeIdx); + + if (onInlet_(pos)) + values.setFreeFlow(context, spaceIdx, timeIdx, inletFluidState_); + else if (onOutlet_(pos)) + values.setFreeFlow(context, spaceIdx, timeIdx, outletFluidState_); + else + values.setNoFlow(); + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const auto& matParams = materialLawParams(context, spaceIdx, timeIdx); + values.assignMassConservative(outletFluidState_, matParams); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = 0.0; } + + //! \} + +private: + /*! + * \brief Returns whether a given global position is in the + * fine-permeability region or not. + */ + bool isFineMaterial_(const GlobalPosition& pos) const + { return 10 <= pos[0] && pos[0] <= 20 && 0 <= pos[1] && pos[1] <= 35; } + + bool onInlet_(const GlobalPosition& globalPos) const + { + Scalar x = globalPos[0]; + Scalar y = globalPos[1]; + return x >= 60 - eps_ && y <= 10; + } + + bool onOutlet_(const GlobalPosition& globalPos) const + { + Scalar x = globalPos[0]; + Scalar y = globalPos[1]; + return x < eps_ && y <= 10; + } + + void initFluidStates_() + { + initFluidState_(inletFluidState_, coarseMaterialParams_, + /*isInlet=*/true); + initFluidState_(outletFluidState_, coarseMaterialParams_, + /*isInlet=*/false); + } + + template + void initFluidState_(FluidState& fs, const MaterialLawParams& matParams, bool isInlet) + { + unsigned refPhaseIdx; + unsigned otherPhaseIdx; + + // set the fluid temperatures + fs.setTemperature(temperature_); + + if (isInlet) { + // only liquid on inlet + refPhaseIdx = liquidPhaseIdx; + otherPhaseIdx = gasPhaseIdx; + + // set liquid saturation + fs.setSaturation(liquidPhaseIdx, 1.0); + + // set pressure of the liquid phase + fs.setPressure(liquidPhaseIdx, 2e5); + + // set the liquid composition to pure water + fs.setMoleFraction(liquidPhaseIdx, N2Idx, 0.0); + fs.setMoleFraction(liquidPhaseIdx, H2OIdx, 1.0); + } + else { + // elsewhere, only gas + refPhaseIdx = gasPhaseIdx; + otherPhaseIdx = liquidPhaseIdx; + + // set gas saturation + fs.setSaturation(gasPhaseIdx, 1.0); + + // set pressure of the gas phase + fs.setPressure(gasPhaseIdx, 1e5); + + // set the gas composition to 99% nitrogen and 1% steam + fs.setMoleFraction(gasPhaseIdx, N2Idx, 0.99); + fs.setMoleFraction(gasPhaseIdx, H2OIdx, 0.01); + } + + // set the other saturation + fs.setSaturation(otherPhaseIdx, 1.0 - fs.saturation(refPhaseIdx)); + + // calulate the capillary pressure + PhaseVector pC; + MaterialLaw::capillaryPressures(pC, matParams, fs); + fs.setPressure(otherPhaseIdx, fs.pressure(refPhaseIdx) + + (pC[otherPhaseIdx] - pC[refPhaseIdx])); + + // make the fluid state consistent with local thermodynamic + // equilibrium + using ComputeFromReferencePhase = Opm::ComputeFromReferencePhase; + + typename FluidSystem::template ParameterCache paramCache; + ComputeFromReferencePhase::solve(fs, paramCache, refPhaseIdx, + /*setViscosity=*/true, + /*setEnthalpy=*/false); + } + + void computeThermalCondParams_(ThermalConductionLawParams& params, Scalar poro) + { + Scalar lambdaWater = 0.6; + Scalar lambdaGranite = 2.8; + + Scalar lambdaWet = std::pow(lambdaGranite, (1 - poro)) + * std::pow(lambdaWater, poro); + Scalar lambdaDry = std::pow(lambdaGranite, (1 - poro)); + + params.setFullySaturatedLambda(gasPhaseIdx, lambdaDry); + params.setFullySaturatedLambda(liquidPhaseIdx, lambdaWet); + params.setVacuumLambda(lambdaDry); + } + + DimMatrix coarseK_; + DimMatrix fineK_; + + Scalar coarsePorosity_; + Scalar finePorosity_; + + MaterialLawParams fineMaterialParams_; + MaterialLawParams coarseMaterialParams_; + + ThermalConductionLawParams fineThermalCondParams_; + ThermalConductionLawParams coarseThermalCondParams_; + SolidEnergyLawParams solidEnergyLawParams_; + + Opm::CompositionalFluidState inletFluidState_; + Opm::CompositionalFluidState outletFluidState_; + + Scalar temperature_; + Scalar eps_; +}; +} // namespace Opm + +#endif diff --git a/examples/problems/outflowproblem.hh b/examples/problems/outflowproblem.hh new file mode 100644 index 00000000000..4e52b5365cb --- /dev/null +++ b/examples/problems/outflowproblem.hh @@ -0,0 +1,379 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::OutflowProblem + */ +#ifndef EWOMS_OUTFLOW_PROBLEM_HH +#define EWOMS_OUTFLOW_PROBLEM_HH + +#include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace Opm { +template +class OutflowProblem; +} + +namespace Opm::Properties { + +namespace TTag { + +struct OutflowBaseProblem {}; + +} // namespace TTag + +// Set the grid type +template +struct Grid { using type = Dune::YaspGrid<2>; }; + +// Set the problem property +template +struct Problem { using type = Opm::OutflowProblem; }; + +// Set fluid system +template +struct FluidSystem +{ +private: + using Scalar = GetPropType; + +public: + // Two-component single phase fluid system + using type = Opm::H2ON2LiquidPhaseFluidSystem; +}; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup TestProblems + * + * \brief Problem where dissolved nitrogen is transported with the water + * phase from the left side to the right. + * + * The model domain is 1m times 1m and exhibits homogeneous soil + * properties (\f$ \mathrm{K=10e-10, \Phi=0.4}\f$). Initially the + * domain is fully saturated by water without any nitrogen dissolved. + * + * At the left side, a free-flow condition defines a nitrogen mole + * fraction of 0.02%. The water phase flows from the left side to the + * right due to the imposed pressure gradient of \f$1e5\,Pa/m\f$. The + * nitrogen is transported with the water flow and leaves the domain + * at the right boundary where an outflow boundary condition is + * used. + */ +template +class OutflowProblem : public GetPropType +{ + using ParentType = GetPropType; + + using GridView = GetPropType; + using Scalar = GetPropType; + using PrimaryVariables = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using Simulator = GetPropType; + using FluidSystem = GetPropType; + using MaterialLawParams = GetPropType; + + // copy some indices for convenience + enum { + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld, + + numPhases = FluidSystem::numPhases, + + // component indices + H2OIdx = FluidSystem::H2OIdx, + N2Idx = FluidSystem::N2Idx + }; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + OutflowProblem(Simulator& simulator) + : ParentType(simulator) + , eps_(1e-6) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + temperature_ = 273.15 + 20; + FluidSystem::init(/*minT=*/temperature_ - 1, /*maxT=*/temperature_ + 2, + /*numT=*/3, + /*minp=*/0.8e5, /*maxp=*/2.5e5, /*nump=*/500); + + // set parameters of porous medium + perm_ = this->toDimMatrix_(1e-10); + porosity_ = 0.4; + tortuosity_ = 0.28; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault("./data/outflow.dgf"); + Parameters::SetDefault>(100.0); + Parameters::SetDefault>(1.0); + + Parameters::SetDefault(true); + } + + /*! + * \name Problem parameters + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { return "outflow"; } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + * + * This problem assumes a temperature. + */ + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return temperature_; } // in [K] + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + * + * This problem uses a constant intrinsic permeability. + */ + template + const DimMatrix& intrinsicPermeability(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return perm_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + * + * This problem uses a constant porosity. + */ + template + Scalar porosity(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return porosity_; } + +#if 0 + /*! + * \brief Define the tortuosity \f$[?]\f$. + * + */ + template + Scalar tortuosity(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { return tortuosity_; } + + /*! + * \brief Define the dispersivity \f$[?]\f$. + * + */ + template + Scalar dispersivity(const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { return 0; } +#endif + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& globalPos = context.pos(spaceIdx, timeIdx); + + if (onLeftBoundary_(globalPos)) { + Opm::CompositionalFluidState fs; + initialFluidState_(fs, context, spaceIdx, timeIdx); + fs.setPressure(/*phaseIdx=*/0, fs.pressure(/*phaseIdx=*/0) + 1e5); + + Scalar xlN2 = 2e-4; + fs.setMoleFraction(/*phaseIdx=*/0, N2Idx, xlN2); + fs.setMoleFraction(/*phaseIdx=*/0, H2OIdx, 1 - xlN2); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + fs.setDensity(phaseIdx, FluidSystem::density(fs, paramCache, phaseIdx)); + fs.setViscosity(phaseIdx, FluidSystem::viscosity(fs, paramCache, phaseIdx)); + } + + // impose an freeflow boundary condition + values.setFreeFlow(context, spaceIdx, timeIdx, fs); + } + else if (onRightBoundary_(globalPos)) { + Opm::CompositionalFluidState fs; + initialFluidState_(fs, context, spaceIdx, timeIdx); + + // impose an outflow boundary condition + values.setOutFlow(context, spaceIdx, timeIdx, fs); + } + else + // no flow on top and bottom + values.setNoFlow(); + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + Opm::CompositionalFluidState fs; + initialFluidState_(fs, context, spaceIdx, timeIdx); + + values.assignNaive(fs); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + template + void initialFluidState_(FluidState& fs, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + Scalar T = temperature(context, spaceIdx, timeIdx); + // Scalar rho = FluidSystem::H2O::liquidDensity(T, /*pressure=*/1.5e5); + // Scalar z = context.pos(spaceIdx, timeIdx)[dim - 1] - + // this->boundingBoxMax()[dim - 1]; + // Scalar z = context.pos(spaceIdx, timeIdx)[dim - 1] - + // this->boundingBoxMax()[dim - 1]; + + fs.setSaturation(/*phaseIdx=*/0, 1.0); + fs.setPressure(/*phaseIdx=*/0, 1e5 /* + rho*z */); + fs.setMoleFraction(/*phaseIdx=*/0, H2OIdx, 1.0); + fs.setMoleFraction(/*phaseIdx=*/0, N2Idx, 0); + fs.setTemperature(T); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + fs.setDensity(phaseIdx, FluidSystem::density(fs, paramCache, phaseIdx)); + fs.setViscosity(phaseIdx, FluidSystem::viscosity(fs, paramCache, phaseIdx)); + } + } + + const Scalar eps_; + + MaterialLawParams materialParams_; + DimMatrix perm_; + Scalar temperature_; + Scalar porosity_; + Scalar tortuosity_; +}; +} // namespace Opm + +#endif diff --git a/examples/problems/powerinjectionproblem.hh b/examples/problems/powerinjectionproblem.hh new file mode 100644 index 00000000000..1bda5db6232 --- /dev/null +++ b/examples/problems/powerinjectionproblem.hh @@ -0,0 +1,441 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PowerInjectionProblem + */ +#ifndef EWOMS_POWER_INJECTION_PROBLEM_HH +#define EWOMS_POWER_INJECTION_PROBLEM_HH + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace Opm { +template +class PowerInjectionProblem; +} + +namespace Opm::Properties { + +namespace TTag { +struct PowerInjectionBaseProblem {}; +} + +// Set the grid implementation to be used +template +struct Grid { using type = Dune::YaspGrid; }; + +// set the Vanguard property +template +struct Vanguard { using type = Opm::CubeGridVanguard; }; + +// Set the problem property +template +struct Problem { using type = Opm::PowerInjectionProblem; }; + +// Set the wetting phase +template +struct WettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +// Set the non-wetting phase +template +struct NonwettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::GasPhase >; +}; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using FluidSystem = GetPropType; + enum { wettingPhaseIdx = FluidSystem::wettingPhaseIdx }; + enum { nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx }; + + using Scalar = GetPropType; + using Traits = Opm::TwoPhaseMaterialTraits; + + // define the material law which is parameterized by effective + // saturations + using EffectiveLaw = Opm::RegularizedVanGenuchten; + +public: + // define the material law parameterized by absolute saturations + using type = Opm::EffToAbsLaw; +}; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup TestProblems + * \brief 1D Problem with very fast injection of gas on the left. + * + * The velocity model is chosen in the .cc file in this problem. The + * spatial parameters are inspired by the ones given by + * + * V. Jambhekar: "Forchheimer Porous-media Flow models -- Numerical + * Investigation and Comparison with Experimental Data", Master's + * Thesis at Institute for Modelling Hydraulic and Environmental + * Systems, University of Stuttgart, 2011 + */ +template +class PowerInjectionProblem : public GetPropType +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using GridView = GetPropType; + using Indices = GetPropType; + using FluidSystem = GetPropType; + using WettingPhase = GetPropType; + using NonwettingPhase = GetPropType; + using PrimaryVariables = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using Simulator = GetPropType; + + enum { + // number of phases + numPhases = FluidSystem::numPhases, + + // phase indices + wettingPhaseIdx = FluidSystem::wettingPhaseIdx, + nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx, + + // equation indices + contiNEqIdx = Indices::conti0EqIdx + nonWettingPhaseIdx, + + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + PowerInjectionProblem(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + eps_ = 3e-6; + FluidSystem::init(); + + temperature_ = 273.15 + 26.6; + + // parameters for the Van Genuchten law + // alpha and n + materialParams_.setVgAlpha(0.00045); + materialParams_.setVgN(7.3); + materialParams_.finalize(); + + K_ = this->toDimMatrix_(5.73e-08); // [m^2] + + setupInitialFluidState_(); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault(250); + Parameters::SetDefault>(100.0); + + if constexpr (dim > 1) { + Parameters::SetDefault(1); + Parameters::SetDefault>(1.0); + } + if constexpr (dim == 3) { + Parameters::SetDefault(1); + Parameters::SetDefault>(1.0); + } + + Parameters::SetDefault>(100.0); + Parameters::SetDefault>(1e-3); + Parameters::SetDefault(true); + } + + /*! + * \name Auxiliary methods + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + std::ostringstream oss; + oss << "powerinjection_"; + if (std::is_same, + Opm::DarcyFluxModule >::value) + oss << "darcy"; + else + oss << "forchheimer"; + + if (std::is_same, + Properties::TTag::AutoDiffLocalLinearizer>::value) + oss << "_" << "ad"; + else + oss << "_" << "fd"; + + return oss.str(); + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + //! \} + + /*! + * \name Soil parameters + */ + //! \{ + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return K_; } + + /*! + * \copydoc ForchheimerBaseProblem::ergunCoefficient + */ + template + Scalar ergunCoefficient(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return 0.3866; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return 0.558; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& + materialLawParams(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return materialParams_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return temperature_; } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + * + * This problem sets a very high injection rate of nitrogen on the + * left and a free-flow boundary on the right. + */ + template + void boundary(BoundaryRateVector& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + + if (onLeftBoundary_(pos)) { + RateVector massRate(0.0); + massRate = 0.0; + massRate[contiNEqIdx] = -1.00; // kg / (m^2 * s) + + // impose a forced flow boundary + values.setMassRate(massRate); + } + else if (onRightBoundary_(pos)) + // free flow boundary with initial condition on the right + values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidState_); + else + values.setNoFlow(); + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { + // assign the primary variables + values.assignNaive(initialFluidState_); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < this->boundingBoxMin()[0] + eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + void setupInitialFluidState_() + { + initialFluidState_.setTemperature(temperature_); + + Scalar Sw = 1.0; + initialFluidState_.setSaturation(wettingPhaseIdx, Sw); + initialFluidState_.setSaturation(nonWettingPhaseIdx, 1 - Sw); + + Scalar p = 1e5; + initialFluidState_.setPressure(wettingPhaseIdx, p); + initialFluidState_.setPressure(nonWettingPhaseIdx, p); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(initialFluidState_); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + initialFluidState_.setDensity(phaseIdx, + FluidSystem::density(initialFluidState_, paramCache, phaseIdx)); + initialFluidState_.setViscosity(phaseIdx, + FluidSystem::viscosity(initialFluidState_, paramCache, phaseIdx)); + } + } + + DimMatrix K_; + MaterialLawParams materialParams_; + + Opm::ImmiscibleFluidState initialFluidState_; + Scalar temperature_; + Scalar eps_; +}; + +} // namespace Opm + +#endif diff --git a/examples/problems/reservoirproblem.hh b/examples/problems/reservoirproblem.hh new file mode 100644 index 00000000000..5180093d42a --- /dev/null +++ b/examples/problems/reservoirproblem.hh @@ -0,0 +1,763 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ReservoirProblem + */ +#ifndef EWOMS_RESERVOIR_PROBLEM_HH +#define EWOMS_RESERVOIR_PROBLEM_HH + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include + +#include + +#include +#include + +namespace Opm { +template +class ReservoirProblem; + +} // namespace Opm + +namespace Opm::Properties { + + +namespace TTag { + +struct ReservoirBaseProblem {}; + +} // namespace TTag + +// Set the grid type +template +struct Grid { using type = Dune::YaspGrid<2>; }; + +// Set the problem property +template +struct Problem { using type = Opm::ReservoirProblem; }; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + + using Traits = Opm:: + ThreePhaseMaterialTraits; + +public: + using type = Opm::LinearMaterial; +}; + +// Enable constraint DOFs? +template +struct EnableConstraints { static constexpr bool value = true; }; + +/*! + * \brief Explicitly set the fluid system to the black-oil fluid system + * + * If the black oil model is used, this is superfluous because that model already sets + * the FluidSystem property. Setting it explictly for the problem is a good idea anyway, + * though because other models are more generic and thus do not assume a particular fluid + * system. + */ +template +struct FluidSystem +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::BlackOilFluidSystem; +}; + +} // namespace Opm::Properties + +namespace Opm::Parameters { + +// Maximum depth of the reservoir +template +struct MaxDepth { static constexpr Scalar value = 2500.0; }; + +// The temperature inside the reservoir +template +struct Temperature { static constexpr Scalar value = 293.15; }; + +// The width of producer/injector wells as a fraction of the width of the spatial domain +template +struct WellWidth { static constexpr Scalar value = 0.01; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! + * \ingroup TestProblems + * + * \brief Some simple test problem for the black-oil VCVF discretization + * inspired by an oil reservoir. + * + * The domain is two-dimensional and exhibits a size of 6000m times 60m. Initially, the + * reservoir is assumed by oil with a bubble point pressure of 20 MPa, which also the + * initial pressure in the domain. No-flow boundaries are used for all boundaries. The + * permeability of the lower 10 m is reduced compared to the upper 10 m of the domain + * witch capillary pressure always being neglected. Three wells are approximated using + * constraints: Two water-injector wells, one at the lower-left boundary one at the + * lower-right boundary and one producer well in the upper part of the center of the + * domain. The pressure for the producer is assumed to be 2/3 of the reservoir pressure, + * the injector wells use a pressure which is 50% above the reservoir pressure. + */ +template +class ReservoirProblem : public GetPropType +{ + using ParentType = GetPropType; + + using GridView = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + + // Grid and world dimension + enum { dim = GridView::dimension }; + enum { dimWorld = GridView::dimensionworld }; + + // copy some indices for convenience + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + enum { gasCompIdx = FluidSystem::gasCompIdx }; + enum { oilCompIdx = FluidSystem::oilCompIdx }; + enum { waterCompIdx = FluidSystem::waterCompIdx }; + + using Model = GetPropType; + using ElementContext = GetPropType; + using PrimaryVariables = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using Constraints = GetPropType; + using MaterialLaw = GetPropType; + using Simulator = GetPropType; + using MaterialLawParams = GetPropType; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + using PhaseVector = Dune::FieldVector; + + using InitialFluidState = Opm::CompositionalFluidState; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + ReservoirProblem(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + temperature_ = Parameters::Get>(); + maxDepth_ = Parameters::Get>(); + wellWidth_ = Parameters::Get>(); + + std::vector > Bo = { + { 101353, 1.062 }, + { 1.82504e+06, 1.15 }, + { 3.54873e+06, 1.207 }, + { 6.99611e+06, 1.295 }, + { 1.38909e+07, 1.435 }, + { 1.73382e+07, 1.5 }, + { 2.07856e+07, 1.565 }, + { 2.76804e+07, 1.695 }, + { 3.45751e+07, 1.827 } + }; + std::vector > muo = { + { 101353, 0.00104 }, + { 1.82504e+06, 0.000975 }, + { 3.54873e+06, 0.00091 }, + { 6.99611e+06, 0.00083 }, + { 1.38909e+07, 0.000695 }, + { 1.73382e+07, 0.000641 }, + { 2.07856e+07, 0.000594 }, + { 2.76804e+07, 0.00051 }, + { 3.45751e+07, 0.000449 } + }; + std::vector > Rs = { + { 101353, 0.178108 }, + { 1.82504e+06, 16.1187 }, + { 3.54873e+06, 32.0594 }, + { 6.99611e+06, 66.0779 }, + { 1.38909e+07, 113.276 }, + { 1.73382e+07, 138.033 }, + { 2.07856e+07, 165.64 }, + { 2.76804e+07, 226.197 }, + { 3.45751e+07, 288.178 } + }; + std::vector > Bg = { + { 101353, 0.93576 }, + { 1.82504e+06, 0.0678972 }, + { 3.54873e+06, 0.0352259 }, + { 6.99611e+06, 0.0179498 }, + { 1.38909e+07, 0.00906194 }, + { 1.73382e+07, 0.00726527 }, + { 2.07856e+07, 0.00606375 }, + { 2.76804e+07, 0.00455343 }, + { 3.45751e+07, 0.00364386 }, + { 6.21542e+07, 0.00216723 } + }; + std::vector > mug = { + { 101353, 8e-06 }, + { 1.82504e+06, 9.6e-06 }, + { 3.54873e+06, 1.12e-05 }, + { 6.99611e+06, 1.4e-05 }, + { 1.38909e+07, 1.89e-05 }, + { 1.73382e+07, 2.08e-05 }, + { 2.07856e+07, 2.28e-05 }, + { 2.76804e+07, 2.68e-05 }, + { 3.45751e+07, 3.09e-05 }, + { 6.21542e+07, 4.7e-05 } + }; + + Scalar rhoRefO = 786.0; // [kg] + Scalar rhoRefG = 0.97; // [kg] + Scalar rhoRefW = 1037.0; // [kg] + FluidSystem::initBegin(/*numPvtRegions=*/1); + FluidSystem::setEnableDissolvedGas(true); + FluidSystem::setEnableVaporizedOil(false); + FluidSystem::setReferenceDensities(rhoRefO, rhoRefW, rhoRefG, /*regionIdx=*/0); + + Opm::GasPvtMultiplexer *gasPvt = new Opm::GasPvtMultiplexer; + gasPvt->setApproach(GasPvtApproach::DryGas); + auto& dryGasPvt = gasPvt->template getRealPvt(); + dryGasPvt.setNumRegions(/*numPvtRegion=*/1); + dryGasPvt.setReferenceDensities(/*regionIdx=*/0, rhoRefO, rhoRefG, rhoRefW); + dryGasPvt.setGasFormationVolumeFactor(/*regionIdx=*/0, Bg); + dryGasPvt.setGasViscosity(/*regionIdx=*/0, mug); + + Opm::OilPvtMultiplexer *oilPvt = new Opm::OilPvtMultiplexer; + oilPvt->setApproach(OilPvtApproach::LiveOil); + auto& liveOilPvt = oilPvt->template getRealPvt(); + liveOilPvt.setNumRegions(/*numPvtRegion=*/1); + liveOilPvt.setReferenceDensities(/*regionIdx=*/0, rhoRefO, rhoRefG, rhoRefW); + liveOilPvt.setSaturatedOilGasDissolutionFactor(/*regionIdx=*/0, Rs); + liveOilPvt.setSaturatedOilFormationVolumeFactor(/*regionIdx=*/0, Bo); + liveOilPvt.setSaturatedOilViscosity(/*regionIdx=*/0, muo); + + Opm::WaterPvtMultiplexer *waterPvt = new Opm::WaterPvtMultiplexer; + waterPvt->setApproach(WaterPvtApproach::ConstantCompressibilityWater); + auto& ccWaterPvt = waterPvt->template getRealPvt(); + ccWaterPvt.setNumRegions(/*numPvtRegions=*/1); + ccWaterPvt.setReferenceDensities(/*regionIdx=*/0, rhoRefO, rhoRefG, rhoRefW); + ccWaterPvt.setViscosity(/*regionIdx=*/0, 9.6e-4); + ccWaterPvt.setCompressibility(/*regionIdx=*/0, 1.450377e-10); + + gasPvt->initEnd(); + oilPvt->initEnd(); + waterPvt->initEnd(); + + using GasPvtSharedPtr = std::shared_ptr >; + FluidSystem::setGasPvt(GasPvtSharedPtr(gasPvt)); + + using OilPvtSharedPtr = std::shared_ptr >; + FluidSystem::setOilPvt(OilPvtSharedPtr(oilPvt)); + + using WaterPvtSharedPtr = std::shared_ptr >; + FluidSystem::setWaterPvt(WaterPvtSharedPtr(waterPvt)); + + FluidSystem::initEnd(); + + pReservoir_ = 330e5; + layerBottom_ = 22.0; + + // intrinsic permeabilities + fineK_ = this->toDimMatrix_(1e-12); + coarseK_ = this->toDimMatrix_(1e-11); + + // porosities + finePorosity_ = 0.2; + coarsePorosity_ = 0.3; + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + fineMaterialParams_.setPcMinSat(phaseIdx, 0.0); + fineMaterialParams_.setPcMaxSat(phaseIdx, 0.0); + + coarseMaterialParams_.setPcMinSat(phaseIdx, 0.0); + coarseMaterialParams_.setPcMaxSat(phaseIdx, 0.0); + } + + // wrap up the initialization of the material law's parameters + fineMaterialParams_.finalize(); + coarseMaterialParams_.finalize(); + + materialParams_.resize(this->model().numGridDof()); + ElementContext elemCtx(this->simulator()); + auto eIt = this->simulator().gridView().template begin<0>(); + const auto& eEndIt = this->simulator().gridView().template end<0>(); + for (; eIt != eEndIt; ++eIt) { + elemCtx.updateStencil(*eIt); + size_t nDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + for (unsigned dofIdx = 0; dofIdx < nDof; ++ dofIdx) { + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + const GlobalPosition& pos = elemCtx.pos(dofIdx, /*timeIdx=*/0); + + if (isFineMaterial_(pos)) + materialParams_[globalDofIdx] = &fineMaterialParams_; + else + materialParams_[globalDofIdx] = &coarseMaterialParams_; + } + } + + initFluidState_(); + + // start the first ("settle down") episode for 100 days + this->simulator().startNextEpisode(100.0*24*60*60); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register> + ("The temperature [K] in the reservoir"); + Parameters::Register> + ("The maximum depth [m] of the reservoir"); + Parameters::Register> + ("The width of producer/injector wells as a fraction of the width" + " of the spatial domain"); + + Parameters::SetDefault("data/reservoir.dgf"); + + //! By default this problem spans 1000 days (100 "settle down" days and 900 days of + //! production) + Parameters::SetDefault>(1000.0*24*60*60); + + Parameters::SetDefault(true); + Parameters::SetDefault("data/reservoir.dgf"); + Parameters::SetDefault>(100e3); + // increase the tolerance for this problem to get larger time steps + Parameters::SetDefault>(1e-6); + + Parameters::SetDefault(true); + } + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { return std::string("reservoir_") + Model::name() + "_" + Model::discretizationName(); } + + /*! + * \copydoc FvBaseProblem::endEpisode + */ + void endEpisode() + { + // in the second episode, the actual work is done (the first is "settle down" + // episode). we need to use a pretty short initial time step here as the change + // in conditions is quite abrupt. + this->simulator().startNextEpisode(1e100); + this->simulator().setTimeStepSize(5.0); + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + // checkConservativeness() does not include the effect of constraints, so we + // disable it for this problem... + //this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + * + * For this problem, a layer with high permability is located + * above one with low permeability. + */ + template + const DimMatrix& intrinsicPermeability(const Context& context, unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineK_; + return coarseK_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return finePorosity_; + return coarsePorosity_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams(const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + unsigned globalIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return *materialParams_[globalIdx]; + } + + const MaterialLawParams& materialLawParams(unsigned globalIdx) const + { return *materialParams_[globalIdx]; } + + /*! + * \name Problem parameters + */ + //! \{ + + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + * + * The black-oil model assumes constant temperature to define its + * parameters. Although temperature is thus not really used by the + * model, it gets written to the VTK output. Who nows, maybe we + * will need it one day? + */ + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return temperature_; } + + // \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + * + * The reservoir problem uses constraints to approximate + * extraction and production wells, so all boundaries are no-flow. + */ + template + void boundary(BoundaryRateVector& values, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { + // no flow on top and bottom + values.setNoFlow(); + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + * + * The reservoir problem uses a constant boundary condition for + * the whole domain. + */ + template + void initial(PrimaryVariables& values, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { + values.assignNaive(initialFluidState_); + +#ifndef NDEBUG + for (unsigned pvIdx = 0; pvIdx < values.size(); ++ pvIdx) + assert(std::isfinite(values[pvIdx])); +#endif + } + + /*! + * \copydoc FvBaseProblem::constraints + * + * The reservoir problem places two water-injection wells on the lower-left and + * lower-right of the domain and a production well in the middle. The injection wells + * are fully water saturated with a higher pressure, the producer is fully oil + * saturated with a lower pressure than the remaining reservoir. + */ + template + void constraints(Constraints& constraintValues, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + if (this->simulator().episodeIndex() == 1) + return; // no constraints during the "settle down" episode + + const auto& pos = context.pos(spaceIdx, timeIdx); + if (isInjector_(pos)) { + constraintValues.setActive(true); + constraintValues.assignNaive(injectorFluidState_); + } + else if (isProducer_(pos)) { + constraintValues.setActive(true); + constraintValues.assignNaive(producerFluidState_); + } + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + void initFluidState_() + { + auto& fs = initialFluidState_; + + ////// + // set temperatures + ////// + fs.setTemperature(temperature_); + + ////// + // set saturations + ////// + fs.setSaturation(FluidSystem::oilPhaseIdx, 1.0); + fs.setSaturation(FluidSystem::waterPhaseIdx, 0.0); + fs.setSaturation(FluidSystem::gasPhaseIdx, 0.0); + + ////// + // set pressures + ////// + Scalar pw = pReservoir_; + + PhaseVector pC; + const auto& matParams = fineMaterialParams_; + MaterialLaw::capillaryPressures(pC, matParams, fs); + + fs.setPressure(oilPhaseIdx, pw + (pC[oilPhaseIdx] - pC[waterPhaseIdx])); + fs.setPressure(waterPhaseIdx, pw + (pC[waterPhaseIdx] - pC[waterPhaseIdx])); + fs.setPressure(gasPhaseIdx, pw + (pC[gasPhaseIdx] - pC[waterPhaseIdx])); + + // reset all mole fractions to 0 + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + fs.setMoleFraction(phaseIdx, compIdx, 0.0); + + ////// + // set composition of the gas and water phases + ////// + fs.setMoleFraction(waterPhaseIdx, waterCompIdx, 1.0); + fs.setMoleFraction(gasPhaseIdx, gasCompIdx, 1.0); + + ////// + // set composition of the oil phase + ////// + Scalar RsSat = + FluidSystem::saturatedDissolutionFactor(fs, oilPhaseIdx, /*pvtRegionIdx=*/0); + Scalar XoGSat = FluidSystem::convertRsToXoG(RsSat, /*pvtRegionIdx=*/0); + Scalar xoGSat = FluidSystem::convertXoGToxoG(XoGSat, /*pvtRegionIdx=*/0); + Scalar xoG = 0.95*xoGSat; + Scalar xoO = 1.0 - xoG; + + // finally set the oil-phase composition + fs.setMoleFraction(oilPhaseIdx, gasCompIdx, xoG); + fs.setMoleFraction(oilPhaseIdx, oilCompIdx, xoO); + + using CFRP = Opm::ComputeFromReferencePhase; + typename FluidSystem::template ParameterCache paramCache; + CFRP::solve(fs, + paramCache, + /*refPhaseIdx=*/oilPhaseIdx, + /*setViscosities=*/false, + /*setEnthalpies=*/false); + + // set up the fluid state used for the injectors + auto& injFs = injectorFluidState_; + injFs = initialFluidState_; + + Scalar pInj = pReservoir_ * 1.5; + injFs.setPressure(waterPhaseIdx, pInj); + injFs.setPressure(oilPhaseIdx, pInj); + injFs.setPressure(gasPhaseIdx, pInj); + injFs.setSaturation(waterPhaseIdx, 1.0); + injFs.setSaturation(oilPhaseIdx, 0.0); + injFs.setSaturation(gasPhaseIdx, 0.0); + + // set the composition of the phases to immiscible + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + injFs.setMoleFraction(phaseIdx, compIdx, 0.0); + + injFs.setMoleFraction(gasPhaseIdx, gasCompIdx, 1.0); + injFs.setMoleFraction(oilPhaseIdx, oilCompIdx, 1.0); + injFs.setMoleFraction(waterPhaseIdx, waterCompIdx, 1.0); + + CFRP::solve(injFs, + paramCache, + /*refPhaseIdx=*/waterPhaseIdx, + /*setViscosities=*/true, + /*setEnthalpies=*/false); + + // set up the fluid state used for the producer + auto& prodFs = producerFluidState_; + prodFs = initialFluidState_; + + Scalar pProd = pReservoir_ / 1.5; + prodFs.setPressure(waterPhaseIdx, pProd); + prodFs.setPressure(oilPhaseIdx, pProd); + prodFs.setPressure(gasPhaseIdx, pProd); + prodFs.setSaturation(waterPhaseIdx, 0.0); + prodFs.setSaturation(oilPhaseIdx, 1.0); + prodFs.setSaturation(gasPhaseIdx, 0.0); + + CFRP::solve(prodFs, + paramCache, + /*refPhaseIdx=*/oilPhaseIdx, + /*setViscosities=*/true, + /*setEnthalpies=*/false); + } + + bool isProducer_(const GlobalPosition& pos) const + { + Scalar x = pos[0] - this->boundingBoxMin()[0]; + Scalar y = pos[dim - 1] - this->boundingBoxMin()[dim - 1]; + Scalar width = this->boundingBoxMax()[0] - this->boundingBoxMin()[0]; + Scalar height = this->boundingBoxMax()[dim - 1] - this->boundingBoxMin()[dim - 1]; + + // only the upper half of the center section of the spatial domain is assumed to + // be the producer + if (y <= height/2.0) + return false; + + return width/2.0 - width*1e-5 < x && x < width/2.0 + width*(wellWidth_ + 1e-5); + } + + bool isInjector_(const GlobalPosition& pos) const + { + Scalar x = pos[0] - this->boundingBoxMin()[0]; + Scalar y = pos[dim - 1] - this->boundingBoxMin()[dim - 1]; + Scalar width = this->boundingBoxMax()[0] - this->boundingBoxMin()[0]; + Scalar height = this->boundingBoxMax()[dim - 1] - this->boundingBoxMin()[dim - 1]; + + // only the lower half of the leftmost and rightmost part of the spatial domain + // are assumed to be the water injectors + if (y > height/2.0) + return false; + + return x < width*wellWidth_ - width*1e-5 || x > width*(1.0 - wellWidth_) + width*1e-5; + } + + bool isFineMaterial_(const GlobalPosition& pos) const + { return pos[dim - 1] > layerBottom_; } + + DimMatrix fineK_; + DimMatrix coarseK_; + Scalar layerBottom_; + Scalar pReservoir_; + + Scalar finePorosity_; + Scalar coarsePorosity_; + + MaterialLawParams fineMaterialParams_; + MaterialLawParams coarseMaterialParams_; + std::vector materialParams_; + + InitialFluidState initialFluidState_; + InitialFluidState injectorFluidState_; + InitialFluidState producerFluidState_; + + Scalar temperature_; + Scalar maxDepth_; + Scalar wellWidth_; +}; +} // namespace Opm + +#endif diff --git a/examples/problems/richardslensproblem.hh b/examples/problems/richardslensproblem.hh new file mode 100644 index 00000000000..0e8b24e26f6 --- /dev/null +++ b/examples/problems/richardslensproblem.hh @@ -0,0 +1,488 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsLensProblem + */ +#ifndef EWOMS_RICHARDS_LENS_PROBLEM_HH +#define EWOMS_RICHARDS_LENS_PROBLEM_HH + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace Opm { +template +class RichardsLensProblem; + +} // namespace Opm + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct RichardsLensProblem { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +// Use 2d YaspGrid +template +struct Grid { using type = Dune::YaspGrid<2>; }; + +// Set the physical problem to be solved +template +struct Problem { using type = Opm::RichardsLensProblem; }; + +// Set the wetting phase +template +struct WettingFluid +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using FluidSystem = GetPropType; + enum { wettingPhaseIdx = FluidSystem::wettingPhaseIdx }; + enum { nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx }; + + using Scalar = GetPropType; + using Traits = Opm::TwoPhaseMaterialTraits; + + // define the material law which is parameterized by effective + // saturations + using EffectiveLaw = Opm::RegularizedVanGenuchten; + +public: + // define the material law parameterized by absolute saturations + using type = Opm::EffToAbsLaw; +}; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup TestProblems + * + * \brief A water infiltration problem with a low-permeability lens + * embedded into a high-permeability domain. + * + * The domain is rectangular. The left and right boundaries are + * free-flow boundaries with fixed water pressure which corresponds to + * a fixed saturation of \f$S_w = 0\f$ in the Richards model, the + * bottom boundary is closed. The top boundary is also closed except + * for an infiltration section, where water is infiltrating into an + * initially unsaturated porous medium. This problem is very similar + * the \c LensProblem, with the main difference being that the domain + * is initally fully saturated by gas instead of water and water + * instead of a \c DNAPL infiltrates from the top. + */ +template +class RichardsLensProblem : public GetPropType +{ + using ParentType = GetPropType; + + using GridView = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using PrimaryVariables = GetPropType; + using Stencil = GetPropType; + using Simulator = GetPropType; + using Model = GetPropType; + using FluidSystem = GetPropType; + using Scalar = GetPropType; + + using Indices = GetPropType; + enum { + // copy some indices for convenience + pressureWIdx = Indices::pressureWIdx, + contiEqIdx = Indices::contiEqIdx, + wettingPhaseIdx = FluidSystem::wettingPhaseIdx, + nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx, + numPhases = FluidSystem::numPhases, + + // Grid and world dimension + dimWorld = GridView::dimensionworld + }; + + // get the material law from the property system + using MaterialLaw = GetPropType; + //! The parameters of the material law to be used + using MaterialLawParams = typename MaterialLaw::Params; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + using PhaseVector = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + RichardsLensProblem(Simulator& simulator) + : ParentType(simulator) + , pnRef_(1e5) + { + dofIsInLens_.resize(simulator.model().numGridDof()); + } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + eps_ = 3e-6; + pnRef_ = 1e5; + + lensLowerLeft_[0] = 1.0; + lensLowerLeft_[1] = 2.0; + + lensUpperRight_[0] = 4.0; + lensUpperRight_[1] = 3.0; + + // parameters for the Van Genuchten law + // alpha and n + lensMaterialParams_.setVgAlpha(0.00045); + lensMaterialParams_.setVgN(7.3); + lensMaterialParams_.finalize(); + + outerMaterialParams_.setVgAlpha(0.0037); + outerMaterialParams_.setVgN(4.7); + outerMaterialParams_.finalize(); + + // parameters for the linear law + // minimum and maximum pressures + // lensMaterialParams_.setEntryPC(0); + // outerMaterialParams_.setEntryPC(0); + // lensMaterialParams_.setMaxPC(0); + // outerMaterialParams_.setMaxPC(0); + + lensK_ = this->toDimMatrix_(1e-12); + outerK_ = this->toDimMatrix_(5e-12); + + // determine which degrees of freedom are in the lens + Stencil stencil(this->gridView(), this->simulator().model().dofMapper() ); + for (const auto& elem : elements(this->gridView())) { + stencil.update(elem); + for (unsigned dofIdx = 0; dofIdx < stencil.numPrimaryDof(); ++ dofIdx) { + unsigned globalDofIdx = stencil.globalSpaceIndex(dofIdx); + const auto& dofPos = stencil.subControlVolume(dofIdx).center(); + dofIsInLens_[globalDofIdx] = isInLens_(dofPos); + } + } + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault("./data/richardslens_24x16.dgf"); + + // Use central differences to approximate the Jacobian matrix + using LLS = GetPropType; + constexpr bool useFD = std::is_same_v; + if constexpr (useFD) { + Parameters::SetDefault(0); + } + + Parameters::SetDefault>(3000.0); + Parameters::SetDefault>(100.0); + Parameters::SetDefault(28); + Parameters::SetDefault(18); + Parameters::SetDefault(true); + } + + /*! + * \name Problem parameters + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + std::ostringstream oss; + oss << "lens_richards_" + << Model::discretizationName(); + return oss.str(); + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { return temperature(context.globalSpaceIndex(spaceIdx, timeIdx), timeIdx); } + + Scalar temperature(unsigned /*globalSpaceIdx*/, unsigned /*timeIdx*/) const + { return 273.15 + 10; } // -> 10Ā°C + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isInLens_(pos)) + return lensK_; + return outerK_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return 0.4; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return materialLawParams(globalSpaceIdx, timeIdx); + } + + const MaterialLawParams& materialLawParams(unsigned globalSpaceIdx, + unsigned /*timeIdx*/) const + { + if (dofIsInLens_[globalSpaceIdx]) + return lensMaterialParams_; + return outerMaterialParams_; + } + + /*! + * \brief Return the reference pressure [Pa] of the wetting phase. + * + * \copydetails Doxygen::contextParams + */ + template + Scalar referencePressure(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { return referencePressure(context.globalSpaceIndex(spaceIdx, timeIdx), timeIdx); } + + // the Richards model does not have an element context available at all places + // where the reference pressure is required... + Scalar referencePressure(unsigned /*globalSpaceIdx*/, + unsigned /*timeIdx*/) const + { return pnRef_; } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + */ + template + void boundary(BoundaryRateVector& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const auto& pos = context.pos(spaceIdx, timeIdx); + + if (onLeftBoundary_(pos) || onRightBoundary_(pos)) { + const auto& materialParams = this->materialLawParams(context, spaceIdx, timeIdx); + + Scalar Sw = 0.0; + Opm::ImmiscibleFluidState fs; + fs.setSaturation(wettingPhaseIdx, Sw); + fs.setSaturation(nonWettingPhaseIdx, 1.0 - Sw); + + PhaseVector pC; + MaterialLaw::capillaryPressures(pC, materialParams, fs); + fs.setPressure(wettingPhaseIdx, pnRef_ + pC[wettingPhaseIdx] - pC[nonWettingPhaseIdx]); + fs.setPressure(nonWettingPhaseIdx, pnRef_); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fs); + fs.setDensity(wettingPhaseIdx, FluidSystem::density(fs, paramCache, wettingPhaseIdx)); + //fs.setDensity(nonWettingPhaseIdx, FluidSystem::density(fs, paramCache, nonWettingPhaseIdx)); + + fs.setViscosity(wettingPhaseIdx, FluidSystem::viscosity(fs, paramCache, wettingPhaseIdx)); + //fs.setViscosity(nonWettingPhaseIdx, FluidSystem::viscosity(fs, paramCache, nonWettingPhaseIdx)); + + values.setFreeFlow(context, spaceIdx, timeIdx, fs); + } + else if (onInlet_(pos)) { + RateVector massRate(0.0); + + // inflow of water + massRate[contiEqIdx] = -0.04; // kg / (m * s) + + values.setMassRate(massRate); + } + else + values.setNoFlow(); + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + */ + template + void initial(PrimaryVariables& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const auto& materialParams = this->materialLawParams(context, spaceIdx, timeIdx); + + Scalar Sw = 0.0; + Opm::ImmiscibleFluidState fs; + fs.setSaturation(wettingPhaseIdx, Sw); + fs.setSaturation(nonWettingPhaseIdx, 1.0 - Sw); + + PhaseVector pC; + MaterialLaw::capillaryPressures(pC, materialParams, fs); + values[pressureWIdx] = pnRef_ + (pC[wettingPhaseIdx] - pC[nonWettingPhaseIdx]); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = Scalar(0.0); } + + //! \} + +private: + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < this->boundingBoxMin()[0] + eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + bool onLowerBoundary_(const GlobalPosition& pos) const + { return pos[1] < this->boundingBoxMin()[1] + eps_; } + + bool onUpperBoundary_(const GlobalPosition& pos) const + { return pos[1] > this->boundingBoxMax()[1] - eps_; } + + bool onInlet_(const GlobalPosition& pos) const + { + Scalar width = this->boundingBoxMax()[0] - this->boundingBoxMin()[0]; + Scalar lambda = (this->boundingBoxMax()[0] - pos[0]) / width; + return onUpperBoundary_(pos) && 0.5 < lambda && lambda < 2.0 / 3.0; + } + + bool isInLens_(const GlobalPosition& pos) const + { + for (unsigned i = 0; i < dimWorld; ++i) { + if (pos[i] < lensLowerLeft_[i] || pos[i] > lensUpperRight_[i]) + return false; + } + return true; + } + + GlobalPosition lensLowerLeft_; + GlobalPosition lensUpperRight_; + + DimMatrix lensK_; + DimMatrix outerK_; + MaterialLawParams lensMaterialParams_; + MaterialLawParams outerMaterialParams_; + + std::vector dofIsInLens_; + + Scalar eps_; + Scalar pnRef_; +}; +} // namespace Opm + +#endif diff --git a/examples/problems/waterairproblem.hh b/examples/problems/waterairproblem.hh new file mode 100644 index 00000000000..e675624a64a --- /dev/null +++ b/examples/problems/waterairproblem.hh @@ -0,0 +1,604 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::WaterAirProblem + */ +#ifndef EWOMS_WATER_AIR_PROBLEM_HH +#define EWOMS_WATER_AIR_PROBLEM_HH + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +namespace Opm { +template +class WaterAirProblem; +} + +namespace Opm::Properties { + +namespace TTag { +struct WaterAirBaseProblem {}; +} + +// Set the grid type +template +struct Grid { using type = Dune::YaspGrid<2>; }; + +// Set the problem property +template +struct Problem { using type = Opm::WaterAirProblem; }; + +// Set the material Law +template +struct MaterialLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using Traits = Opm::TwoPhaseMaterialTraits; + + // define the material law which is parameterized by effective + // saturations + using EffMaterialLaw = Opm::RegularizedBrooksCorey; + +public: + // define the material law parameterized by absolute saturations + // which uses the two-phase API + using type = Opm::EffToAbsLaw; +}; + +// Set the thermal conduction law +template +struct ThermalConductionLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + +public: + // define the material law parameterized by absolute saturations + using type = Opm::SomertonThermalConductionLaw; +}; + +// set the energy storage law for the solid phase +template +struct SolidEnergyLaw +{ using type = Opm::ConstantSolidHeatCapLaw>; }; + +// Set the fluid system. in this case, we use the one which describes +// air and water +template +struct FluidSystem +{ using type = Opm::H2OAirFluidSystem>; }; + +// Use the restarted GMRES linear solver with the ILU-2 preconditioner from dune-istl +template +struct LinearSolverSplice +{ using type = TTag::ParallelIstlLinearSolver; }; + +template +struct LinearSolverWrapper +{ using type = Opm::Linear::SolverWrapperRestartedGMRes; }; + +template +struct PreconditionerWrapper +{ using type = Opm::Linear::PreconditionerWrapperILU; }; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup TestProblems + * \brief Non-isothermal gas injection problem where a air + * is injected into a fully water saturated medium. + * + * During buoyancy driven upward migration, the gas passes a + * rectangular high temperature area. This decreases the temperature + * of the high-temperature area and accelerates gas infiltration due + * to the lower viscosity of the gas. (Be aware that the pressure of + * the gas is approximately constant within the lens, so the density + * of the gas is reduced. This more than off-sets the viscosity + * increase of the gas at constant density.) + * + * The domain is sized 40 m times 40 m. The rectangular area with + * increased temperature (380 K) starts at (20 m, 5 m) and ends at (30 + * m, 35 m). + * + * For the mass conservation equation, no-flow boundary conditions are + * used on the top and on the bottom of the domain, while free-flow + * conditions apply on the left and the right boundary. Gas is + * injected at bottom from 15 m to 25 m at a rate of 0.001 kg/(s m^2) + * by means if a forced inflow boundary condition. + * + * At the free-flow boundaries, the initial condition for the bulk + * part of the domain is assumed, i. e. hydrostatic pressure, a gas + * saturation of zero and a geothermal temperature gradient of 0.03 + * K/m. + */ +template +class WaterAirProblem : public GetPropType +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using GridView = GetPropType; + + // copy some indices for convenience + using FluidSystem = GetPropType; + using Indices = GetPropType; + enum { + numPhases = FluidSystem::numPhases, + + // energy related indices + temperatureIdx = Indices::temperatureIdx, + energyEqIdx = Indices::energyEqIdx, + + // component indices + H2OIdx = FluidSystem::H2OIdx, + AirIdx = FluidSystem::AirIdx, + + // phase indices + liquidPhaseIdx = FluidSystem::liquidPhaseIdx, + gasPhaseIdx = FluidSystem::gasPhaseIdx, + + // equation indices + conti0EqIdx = Indices::conti0EqIdx, + + // Grid and world dimension + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + static const bool enableEnergy = getPropValue(); + + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using PrimaryVariables = GetPropType; + using Constraints = GetPropType; + using Simulator = GetPropType; + using Model = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using ThermalConductionLawParams = GetPropType; + using SolidEnergyLawParams = GetPropType; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + WaterAirProblem(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + maxDepth_ = 1000.0; // [m] + eps_ = 1e-6; + + FluidSystem::init(/*Tmin=*/275, /*Tmax=*/600, /*nT=*/100, + /*pmin=*/9.5e6, /*pmax=*/10.5e6, /*np=*/200); + + layerBottom_ = 22.0; + + // intrinsic permeabilities + fineK_ = this->toDimMatrix_(1e-13); + coarseK_ = this->toDimMatrix_(1e-12); + + // porosities + finePorosity_ = 0.3; + coarsePorosity_ = 0.3; + + // residual saturations + fineMaterialParams_.setResidualSaturation(liquidPhaseIdx, 0.2); + fineMaterialParams_.setResidualSaturation(gasPhaseIdx, 0.0); + coarseMaterialParams_.setResidualSaturation(liquidPhaseIdx, 0.2); + coarseMaterialParams_.setResidualSaturation(gasPhaseIdx, 0.0); + + // parameters for the Brooks-Corey law + fineMaterialParams_.setEntryPressure(1e4); + coarseMaterialParams_.setEntryPressure(1e4); + fineMaterialParams_.setLambda(2.0); + coarseMaterialParams_.setLambda(2.0); + + fineMaterialParams_.finalize(); + coarseMaterialParams_.finalize(); + + // parameters for the somerton law of thermal conduction + computeThermalCondParams_(fineThermalCondParams_, finePorosity_); + computeThermalCondParams_(coarseThermalCondParams_, coarsePorosity_); + + // assume constant volumetric heat capacity and granite + solidEnergyLawParams_.setSolidHeatCapacity(790.0 // specific heat capacity of granite [J / (kg K)] + * 2700.0); // density of granite [kg/m^3] + solidEnergyLawParams_.finalize(); + } + + /*! + * \name Problem parameters + */ + //! \{ + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault("./data/waterair.dgf"); + + // Use forward differences + Parameters::SetDefault(+1); + + Parameters::SetDefault>(1.0 * 365 * 24 * 60 * 60); + Parameters::SetDefault>(250.0); + Parameters::SetDefault(true); + Parameters::SetDefault(2); + } + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { + std::ostringstream oss; + oss << "waterair_" << Model::name(); + if (getPropValue()) + oss << "_ni"; + + return oss.str(); + } + + /*! + * \copydoc FvBaseProblem::endTimeStep + */ + void endTimeStep() + { +#ifndef NDEBUG + // checkConservativeness() does not include the effect of constraints, so we + // disable it for this problem... + //this->model().checkConservativeness(); + + // Calculate storage terms + EqVector storage; + this->model().globalStorage(storage); + + // Write mass balance information for rank 0 + if (this->gridView().comm().rank() == 0) { + std::cout << "Storage: " << storage << std::endl << std::flush; + } +#endif // NDEBUG + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + * + * In this problem, the upper part of the domain is sightly less + * permeable than the lower one. + */ + template + const DimMatrix& intrinsicPermeability(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineK_; + return coarseK_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + */ + template + Scalar porosity(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return finePorosity_; + else + return coarsePorosity_; + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineMaterialParams_; + else + return coarseMaterialParams_; + } + + /*! + * \brief Return the parameters for the energy storage law of the rock + * + * In this case, we assume the rock-matrix to be granite. + */ + template + const SolidEnergyLawParams& + solidEnergyLawParams(const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { return solidEnergyLawParams_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::thermalConductionParams + */ + template + const ThermalConductionLawParams& + thermalConductionLawParams(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + if (isFineMaterial_(pos)) + return fineThermalCondParams_; + return coarseThermalCondParams_; + } + + //! \} + + /*! + * \name Boundary conditions + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::boundary + * + * For this problem, we inject air at the inlet on the center of + * the lower domain boundary and use a no-flow condition on the + * top boundary and a and a free-flow condition on the left and + * right boundaries of the domain. + */ + template + void boundary(BoundaryRateVector& values, + const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const auto& pos = context.cvCenter(spaceIdx, timeIdx); + assert(onLeftBoundary_(pos) || + onLowerBoundary_(pos) || + onRightBoundary_(pos) || + onUpperBoundary_(pos)); + + if (onInlet_(pos)) { + RateVector massRate(0.0); + massRate[conti0EqIdx + AirIdx] = -1e-3; // [kg/(m^2 s)] + + // impose an forced inflow boundary condition on the inlet + values.setMassRate(massRate); + + if (enableEnergy) { + Opm::CompositionalFluidState fs; + initialFluidState_(fs, context, spaceIdx, timeIdx); + + Scalar hl = fs.enthalpy(liquidPhaseIdx); + Scalar hg = fs.enthalpy(gasPhaseIdx); + values.setEnthalpyRate(values[conti0EqIdx + AirIdx] * hg + + values[conti0EqIdx + H2OIdx] * hl); + } + } + else if (onLeftBoundary_(pos) || onRightBoundary_(pos)) { + Opm::CompositionalFluidState fs; + initialFluidState_(fs, context, spaceIdx, timeIdx); + + // impose an freeflow boundary condition + values.setFreeFlow(context, spaceIdx, timeIdx, fs); + } + else + // no flow on top and bottom + values.setNoFlow(); + } + + //! \} + + /*! + * \name Volumetric terms + */ + //! \{ + + /*! + * \copydoc FvBaseProblem::initial + * + * For this problem, we set the medium to be fully saturated by + * liquid water and assume hydrostatic pressure. + */ + template + void initial(PrimaryVariables& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + Opm::CompositionalFluidState fs; + initialFluidState_(fs, context, spaceIdx, timeIdx); + + const auto& matParams = materialLawParams(context, spaceIdx, timeIdx); + values.assignMassConservative(fs, matParams, /*inEquilibrium=*/true); + } + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 + * everywhere. + */ + template + void source(RateVector& rate, + const Context& /*context*/, + unsigned /*spaceIdx*/, + unsigned /*timeIdx*/) const + { rate = 0; } + + //! \} + +private: + bool onLeftBoundary_(const GlobalPosition& pos) const + { return pos[0] < eps_; } + + bool onRightBoundary_(const GlobalPosition& pos) const + { return pos[0] > this->boundingBoxMax()[0] - eps_; } + + bool onLowerBoundary_(const GlobalPosition& pos) const + { return pos[1] < eps_; } + + bool onUpperBoundary_(const GlobalPosition& pos) const + { return pos[1] > this->boundingBoxMax()[1] - eps_; } + + bool onInlet_(const GlobalPosition& pos) const + { return onLowerBoundary_(pos) && (15.0 < pos[0]) && (pos[0] < 25.0); } + + bool inHighTemperatureRegion_(const GlobalPosition& pos) const + { return (20 < pos[0]) && (pos[0] < 30) && (pos[1] < 30); } + + template + void initialFluidState_(FluidState& fs, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + const GlobalPosition& pos = context.pos(spaceIdx, timeIdx); + + Scalar densityW = 1000.0; + fs.setPressure(liquidPhaseIdx, 1e5 + (maxDepth_ - pos[1])*densityW*9.81); + fs.setSaturation(liquidPhaseIdx, 1.0); + fs.setMoleFraction(liquidPhaseIdx, H2OIdx, 1.0); + fs.setMoleFraction(liquidPhaseIdx, AirIdx, 0.0); + + if (inHighTemperatureRegion_(pos)) + fs.setTemperature(380); + else + fs.setTemperature(283.0 + (maxDepth_ - pos[1])*0.03); + + // set the gas saturation and pressure + fs.setSaturation(gasPhaseIdx, 0); + Scalar pc[numPhases]; + const auto& matParams = materialLawParams(context, spaceIdx, timeIdx); + MaterialLaw::capillaryPressures(pc, matParams, fs); + fs.setPressure(gasPhaseIdx, fs.pressure(liquidPhaseIdx) + (pc[gasPhaseIdx] - pc[liquidPhaseIdx])); + + typename FluidSystem::template ParameterCache paramCache; + using CFRP = Opm::ComputeFromReferencePhase; + CFRP::solve(fs, paramCache, liquidPhaseIdx, /*setViscosity=*/true, /*setEnthalpy=*/true); + } + + void computeThermalCondParams_(ThermalConductionLawParams& params, Scalar poro) + { + Scalar lambdaGranite = 2.8; // [W / (K m)] + + // create a Fluid state which has all phases present + Opm::ImmiscibleFluidState fs; + fs.setTemperature(293.15); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + fs.setPressure(phaseIdx, 1.0135e5); + } + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar rho = FluidSystem::density(fs, paramCache, phaseIdx); + fs.setDensity(phaseIdx, rho); + } + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar lambdaSaturated; + if (FluidSystem::isLiquid(phaseIdx)) { + Scalar lambdaFluid = + FluidSystem::thermalConductivity(fs, paramCache, phaseIdx); + lambdaSaturated = std::pow(lambdaGranite, (1-poro)) + std::pow(lambdaFluid, poro); + } + else + lambdaSaturated = std::pow(lambdaGranite, (1-poro)); + + params.setFullySaturatedLambda(phaseIdx, lambdaSaturated); + if (!FluidSystem::isLiquid(phaseIdx)) + params.setVacuumLambda(lambdaSaturated); + } + } + + bool isFineMaterial_(const GlobalPosition& pos) const + { return pos[dim-1] > layerBottom_; } + + DimMatrix fineK_; + DimMatrix coarseK_; + Scalar layerBottom_; + + Scalar finePorosity_; + Scalar coarsePorosity_; + + MaterialLawParams fineMaterialParams_; + MaterialLawParams coarseMaterialParams_; + + ThermalConductionLawParams fineThermalCondParams_; + ThermalConductionLawParams coarseThermalCondParams_; + SolidEnergyLawParams solidEnergyLawParams_; + + Scalar maxDepth_; + Scalar eps_; +}; +} // namespace Opm + +#endif diff --git a/examples/reservoir_blackoil_ecfv.cpp b/examples/reservoir_blackoil_ecfv.cpp new file mode 100644 index 00000000000..191d3b3ad86 --- /dev/null +++ b/examples/reservoir_blackoil_ecfv.cpp @@ -0,0 +1,65 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the reservoir problem using the black-oil model, the ECFV discretization + * and automatic differentiation. + */ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "problems/reservoirproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct ReservoirBlackOilEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +// Select the element centered finite volume method as spatial discretization +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +// Use automatic differentiation to linearize the system of PDEs +template +struct LocalLinearizerSplice +{ using type = TTag::AutoDiffLocalLinearizer; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::ReservoirBlackOilEcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/reservoir_blackoil_vcfv.cpp b/examples/reservoir_blackoil_vcfv.cpp new file mode 100644 index 00000000000..bcdb862e3cd --- /dev/null +++ b/examples/reservoir_blackoil_vcfv.cpp @@ -0,0 +1,59 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the black-oil model using the VCFV discretization. + */ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "problems/reservoirproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct ReservoirBlackOilVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +// Select the vertex centered finite volume method as spatial discretization +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::ReservoirBlackOilVcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/reservoir_ncp_ecfv.cpp b/examples/reservoir_ncp_ecfv.cpp new file mode 100644 index 00000000000..40952fe279a --- /dev/null +++ b/examples/reservoir_ncp_ecfv.cpp @@ -0,0 +1,64 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the black-oil VCVF discretization. + */ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "problems/reservoirproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct ReservoirNcpEcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +// Select the element centered finite volume method as spatial discretization +template +struct SpatialDiscretizationSplice +{ using type = TTag::EcfvDiscretization; }; + +//! use automatic differentiation to linearize the system of PDEs +template +struct LocalLinearizerSplice +{ using type = TTag::AutoDiffLocalLinearizer; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::ReservoirNcpEcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/reservoir_ncp_vcfv.cpp b/examples/reservoir_ncp_vcfv.cpp new file mode 100644 index 00000000000..e573238def6 --- /dev/null +++ b/examples/reservoir_ncp_vcfv.cpp @@ -0,0 +1,69 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Test for the reservoir problem using the NCP model, the VCFV discretization and + * finite differences. + */ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "problems/reservoirproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct ReservoirNcpVcfvProblem +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +// Select the vertex centered finite volume method as spatial discretization +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; + +// reduce the base epsilon for the finite difference method to 10^-11. for some reason +// the simulator converges better with this. (TODO: use automatic differentiation?) +template +struct BaseEpsilon +{ + using type = GetPropType; + static constexpr type value = 1e-11; +}; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::ReservoirNcpVcfvProblem; + return Opm::start(argc, argv); +} diff --git a/examples/tutorial1.cpp b/examples/tutorial1.cpp new file mode 100644 index 00000000000..e5956e86951 --- /dev/null +++ b/examples/tutorial1.cpp @@ -0,0 +1,39 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Main file of the tutorial problem using the model which assumes + * immisciblility. + */ +#include "config.h" /*@\label{tutorial1:include-begin}@*/ +#include /*@\label{tutorial1:include-end}@*/ +#include + +#include "tutorial1problem.hh" /*@\label{tutorial1:include-problem-header}@*/ + +int main(int argc, char **argv) +{ + using TypeTag = Opm::Properties::TTag::Tutorial1Problem; /*@\label{tutorial1:set-type-tag}@*/ + return Opm::start(argc, argv); /*@\label{tutorial1:call-start}@*/ +} diff --git a/examples/tutorial1problem.hh b/examples/tutorial1problem.hh new file mode 100644 index 00000000000..f7e7a13a56d --- /dev/null +++ b/examples/tutorial1problem.hh @@ -0,0 +1,333 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::Tutorial1Problem + */ +#ifndef EWOMS_TUTORIAL1_PROBLEM_HH /*@\label{tutorial1:guardian1}@*/ +#define EWOMS_TUTORIAL1_PROBLEM_HH /*@\label{tutorial1:guardian2}@*/ + +// The numerical model +#include + +// The spatial discretization (VCFV == Vertex-Centered Finite Volumes) +#include /*@\label{tutorial1:include-discretization}@*/ + +// The chemical species that are used +#include +#include + +// Headers required for the capillary pressure law +#include /*@\label{tutorial1:rawLawInclude}@*/ +#include +#include + +// For the DUNE grid +#include /*@\label{tutorial1:include-grid-manager}@*/ +#include /*@\label{tutorial1:include-grid-manager}@*/ + +// For Dune::FieldMatrix +#include +#include + +namespace Opm { +// forward declaration of the problem class +template +class Tutorial1Problem; +} + +namespace Opm::Properties { + +// Create a new type tag for the problem +// Create new type tags +namespace TTag { +struct Tutorial1Problem { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +// Select the vertex centered finite volume method as spatial discretization +template +struct SpatialDiscretizationSplice +{ using type = TTag::VcfvDiscretization; }; /*@\label{tutorial1:set-spatial-discretization}@*/ + +// Set the "Problem" property +template +struct Problem +{ using type = Opm::Tutorial1Problem; }; /*@\label{tutorial1:set-problem}@*/ + +// Set grid and the grid manager to be used +template +struct Grid { using type = Dune::YaspGrid; }; /*@\label{tutorial1:set-grid}@*/ +template +struct Vanguard { using type = Opm::CubeGridVanguard; }; /*@\label{tutorial1:set-grid-manager}@*/ + +// Set the wetting phase /*@\label{tutorial1:2p-system-start}@*/ +template +struct WettingPhase /*@\label{tutorial1:wettingPhase}@*/ +{ + using Scalar = GetPropType; + using type = Opm::LiquidPhase >; +}; + +// Set the non-wetting phase +template +struct NonwettingPhase /*@\label{tutorial1:nonwettingPhase}@*/ +{ + using Scalar = GetPropType; + using type = Opm::LiquidPhase >; +}; /*@\label{tutorial1:2p-system-end}@*/ + +// Set the material law +template +struct MaterialLaw +{ +private: + // create a class holding the necessary information for a + // two-phase capillary pressure law + using Scalar = GetPropType; + using FluidSystem = GetPropType; + enum { wettingPhaseIdx = FluidSystem::wettingPhaseIdx }; + enum { nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx }; + using Traits = Opm::TwoPhaseMaterialTraits; + + // define the material law which is parameterized by effective + // saturations + using RawMaterialLaw = Opm::RegularizedBrooksCorey; /*@\label{tutorial1:rawlaw}@*/ + +public: + // Convert absolute saturations into effective ones before passing + // it to the base capillary pressure law + using type = Opm::EffToAbsLaw; /*@\label{tutorial1:eff2abs}@*/ +}; + +} // namespace Opm::Properties + +namespace Opm { + +//! Tutorial problem using the "immiscible" model. +template +class Tutorial1Problem + : public GetPropType /*@\label{tutorial1:def-problem}@*/ +{ + using ParentType = GetPropType; + using Scalar = GetPropType; + using GridView = GetPropType; + + // Grid dimension + enum { + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + // The type of the intrinsic permeability tensor + using DimMatrix = Dune::FieldMatrix; + + // eWoms specific types are specified via the property system + using Simulator = GetPropType; + using PrimaryVariables = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; /*@\label{tutorial1:matLawObjectType}@*/ + + // phase indices + enum { numPhases = FluidSystem::numPhases }; + enum { wettingPhaseIdx = FluidSystem::wettingPhaseIdx }; + enum { nonWettingPhaseIdx = FluidSystem::nonWettingPhaseIdx }; + + // Indices of the conservation equations + enum { contiWettingEqIdx = Indices::conti0EqIdx + wettingPhaseIdx }; + enum { contiNonWettingEqIdx = Indices::conti0EqIdx + nonWettingPhaseIdx }; + +public: + //! The constructor of the problem. This only _allocates_ the memory required by the + //! problem. The constructor is supposed to _never ever_ throw an exception. + Tutorial1Problem(Simulator& simulator) + : ParentType(simulator) + , eps_(3e-6) + { } + + //! This method initializes the data structures allocated by the problem + //! constructor. In contrast to the constructor, exceptions thrown from within this + //! method won't lead to segmentation faults. + void finishInit() + { + ParentType::finishInit(); + + // Use an isotropic and homogeneous intrinsic permeability + K_ = this->toDimMatrix_(1e-7); + + // Parameters of the Brooks-Corey law + materialParams_.setEntryPressure(500.0 /*Pa*/); /*@\label{tutorial1:setLawParams}@*/ + materialParams_.setLambda(2); // shape parameter + + // Set the residual saturations + materialParams_.setResidualSaturation(wettingPhaseIdx, 0.0); + materialParams_.setResidualSaturation(nonWettingPhaseIdx, 0.0); + + // wrap up the initialization of the material law's parameters + materialParams_.finalize(); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::SetDefault(100); + Parameters::SetDefault(1); + Parameters::SetDefault>(300.0); + Parameters::SetDefault>(60.0); + + if constexpr (dim == 3) { + Parameters::SetDefault(1); + Parameters::SetDefault>(0.0); + } + + Parameters::SetDefault>(100e3); + Parameters::SetDefault>(125.0); + } + + //! Specifies the problem name. This is used for files generated by the simulation. + std::string name() const + { return "tutorial1"; } + + //! Returns the temperature at a given position. + template + Scalar temperature(const Context& /*context*/, + unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const + { return 283.15; } + + //! Returns the intrinsic permeability tensor [m^2] at a position. + template + const DimMatrix& intrinsicPermeability(const Context& /*context*/, /*@\label{tutorial1:permeability}@*/ + unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const + { return K_; } + + //! Defines the porosity [-] of the medium at a given position + template + Scalar porosity(const Context& /*context*/, + unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const /*@\label{tutorial1:porosity}@*/ + { return 0.2; } + + //! Returns the parameter object for the material law at a given position + template + const MaterialLawParams& materialLawParams(const Context& /*context*/, /*@\label{tutorial1:matLawParams}@*/ + unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const + { return materialParams_; } + + //! Evaluates the boundary conditions. + template + void boundary(BoundaryRateVector& values, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + const auto& pos = context.pos(spaceIdx, timeIdx); + if (pos[0] < eps_) { + // Free-flow conditions on left boundary + const auto& materialParams = this->materialLawParams(context, spaceIdx, timeIdx); + + Opm::ImmiscibleFluidState fs; + Scalar Sw = 1.0; + fs.setSaturation(wettingPhaseIdx, Sw); + fs.setSaturation(nonWettingPhaseIdx, 1.0 - Sw); + fs.setTemperature(temperature(context, spaceIdx, timeIdx)); + + Scalar pC[numPhases]; + MaterialLaw::capillaryPressures(pC, materialParams, fs); + fs.setPressure(wettingPhaseIdx, 200e3); + fs.setPressure(nonWettingPhaseIdx, 200e3 + pC[nonWettingPhaseIdx] - pC[nonWettingPhaseIdx]); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fs); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + fs.setDensity(phaseIdx, FluidSystem::density(fs, paramCache, phaseIdx)); + fs.setViscosity(phaseIdx, FluidSystem::viscosity(fs, paramCache, phaseIdx)); + } + + values.setFreeFlow(context, spaceIdx, timeIdx, fs); + } + else if (pos[0] > this->boundingBoxMax()[0] - eps_) { + // forced outflow at the right boundary + RateVector massRate(0.0); + + massRate[contiWettingEqIdx] = 0.0; // [kg / (s m^2)] + massRate[contiNonWettingEqIdx] = 3e-2; // [kg / (s m^2)] + + values.setMassRate(massRate); + } + else // no flow at the remaining boundaries + values.setNoFlow(); + } + + //! Evaluates the source term for all conserved quantities at a given + //! position of the domain [kg/(m^3 * s)]. Positive values mean that + //! mass is created. + template + void source(RateVector& sourceRate, const Context& /*context*/, + unsigned /*spaceIdx*/, unsigned /*timeIdx*/) const + { + sourceRate[contiWettingEqIdx] = 0.0; + sourceRate[contiNonWettingEqIdx] = 0.0; + } + + //! Evaluates the initial value at a given position in the domain. + template + void initial(PrimaryVariables& values, const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + Opm::ImmiscibleFluidState fs; + + // the domain is initially fully saturated by LNAPL + Scalar Sw = 0.0; + fs.setSaturation(wettingPhaseIdx, Sw); + fs.setSaturation(nonWettingPhaseIdx, 1.0 - Sw); + + // the temperature is given by the temperature() method + fs.setTemperature(temperature(context, spaceIdx, timeIdx)); + + // set pressure of the wetting phase to 200 kPa = 2 bar + Scalar pC[numPhases]; + MaterialLaw::capillaryPressures(pC, materialLawParams(context, spaceIdx, timeIdx), + fs); + fs.setPressure(wettingPhaseIdx, 200e3); + fs.setPressure(nonWettingPhaseIdx, 200e3 + pC[nonWettingPhaseIdx] - pC[nonWettingPhaseIdx]); + + values.assignNaive(fs); + } + +private: + DimMatrix K_; + // Object that holds the parameters of required by the capillary pressure law. + MaterialLawParams materialParams_; /*@\label{tutorial1:matParamsObject}@*/ + + // small epsilon value + Scalar eps_; +}; +} // namespace Opm + +#endif + diff --git a/examples/waterair_pvs_ni.cpp b/examples/waterair_pvs_ni.cpp new file mode 100644 index 00000000000..4dc529239a8 --- /dev/null +++ b/examples/waterair_pvs_ni.cpp @@ -0,0 +1,53 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief test for the 2p2cni VCVF discretization + */ +#include "config.h" + +#include +#include +#include +#include "problems/waterairproblem.hh" + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +struct WaterAirProblem +{ using InheritsFrom = std::tuple; }; +} // end namespace TTag + +template +struct EnableEnergy +{ static constexpr bool value = true; }; + +} // namespace Opm::Properties + +int main(int argc, char **argv) +{ + using ProblemTypeTag = Opm::Properties::TTag::WaterAirProblem; + return Opm::start(argc, argv); +} diff --git a/jenkins/build.sh b/jenkins/build.sh index eb0e2f7b85f..ded15226b1e 100755 --- a/jenkins/build.sh +++ b/jenkins/build.sh @@ -5,13 +5,11 @@ declare -a upstreams upstreams=(opm-common - opm-grid - opm-models) + opm-grid) declare -A upstreamRev upstreamRev[opm-common]=master upstreamRev[opm-grid]=master -upstreamRev[opm-models]=master if grep -q "opm-common=" <<< $ghprbCommentBody then diff --git a/modelTests.cmake b/modelTests.cmake new file mode 100644 index 00000000000..cd7cebc7938 --- /dev/null +++ b/modelTests.cmake @@ -0,0 +1,117 @@ +opm_set_test_driver(${PROJECT_SOURCE_DIR}/tests/run-vtu-test.sh "--simulation") + +opm_add_test(art2dgf + NO_COMPILE + EXE_NAME $ + DRIVER_ARGS --plain + TEST_ARGS data/fracture-raw.art + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests) + +foreach(tgt lens_immiscible_ecfv_ad + lens_immiscible_ecfv_ad_23 + lens_immiscible_ecfv_ad_trans + lens_immiscible_vcfv_ad + lens_immiscible_vcfv_fd) + opm_add_test(${tgt} + NO_COMPILE + EXE_NAME $ + TEST_ARGS --end-time=3000 + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests) +endforeach() + +opm_add_test(waterair_pvs_ni + NO_COMPILE + EXE_NAME $ + TEST_ARGS --grid-global-refinements=1 + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests) + +set(PLAIN_TGT + co2injection_flash_ecfv + co2injection_flash_ni_ecfv + co2injection_flash_ni_vcfv + co2injection_flash_vcfv + co2injection_immiscible_ecfv + co2injection_immiscible_ni_ecfv + co2injection_immiscible_ni_vcfv + co2injection_immiscible_vcfv + co2injection_ncp_ecfv + co2injection_ncp_ni_vcfv + co2injection_ncp_vcfv + co2injection_pvs_ecfv + co2injection_pvs_ni_vcfv + co2injection_pvs_vcfv + co2injection_ncp_ni_ecfv + co2injection_pvs_ni_ecfv + co2_ptflash_ecfv + cuvette_pvs + diffusion_flash + diffusion_ncp + diffusion_pvs + groundwater_immiscible + infiltration_pvs + lens_richards_ecfv + lens_richards_vcfv + obstacle_immiscible + obstacle_ncp + obstacle_pvs + outflow_pvs + powerinjection_darcy_ad + powerinjection_darcy_fd + powerinjection_forchheimer_ad + powerinjection_forchheimer_fd + tutorial1 +) + +if(dune-alugrid_FOUND) + list(APPEND PLAIN_TGT + finger_immiscible_ecfv + finger_immiscible_vcfv + ) +endif() + +foreach(tgt ${PLAIN_TGT}) + opm_add_test(${tgt} + NO_COMPILE + EXE_NAME $ + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests) +endforeach() + +foreach(tgt reservoir_blackoil_ecfv + reservoir_blackoil_vcfv + reservoir_ncp_ecfv + reservoir_ncp_vcfv) + opm_add_test(${tgt} + NO_COMPILE + EXE_NAME $ + TEST_ARGS --end-time=8750000 + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests) +endforeach() + +if(dune-alugrid_FOUND) + opm_add_test(fracture_discretefracture + NO_COMPILE + EXE_NAME $ + TEST_ARGS --end-time=400 + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests) +endif() + +if(dune-alugrid_FOUND AND dune-fem_FOUND) + opm_add_test(finger_immiscible_ecfv_adaptive + NO_COMPILE + EXE_NAME $ + TEST_ARGS --enable-grid-adaptation=true --end-time=25e3 --enable-async-vtk-output=false + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests) +endif() + +opm_add_test(obstacle_immiscible_parameters + NO_COMPILE + EXE_NAME $ + DRIVER_ARGS --parameters + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests) + +opm_add_test(obstacle_pvs_restart + NO_COMPILE + EXE_NAME $ + TEST_ARGS --pvs-verbosity=2 --end-time=30000 + DRIVER_ARGS --restart + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests) diff --git a/opm-simulators-prereqs.cmake b/opm-simulators-prereqs.cmake index 03bda76cd01..21db38c4c94 100644 --- a/opm-simulators-prereqs.cmake +++ b/opm-simulators-prereqs.cmake @@ -14,6 +14,7 @@ set (opm-simulators_CONFIG_VAR HAVE_ROCALUTION HAVE_ROCSPARSE HAVE_SUITESPARSE_UMFPACK_H + HAVE_DUNE_COMMON HAVE_DUNE_ISTL DUNE_ISTL_WITH_CHECKING DUNE_ISTL_VERSION_MAJOR @@ -25,6 +26,7 @@ set (opm-simulators_CONFIG_VAR USE_HIP USE_TRACY FLOW_INSTANTIATE_FLOAT + HAVE_FLOATING_POINT_FROM_CHARS ) # dependencies @@ -37,6 +39,8 @@ set (opm-simulators_DEPS # DUNE prerequisites "dune-common REQUIRED" "dune-istl REQUIRED" + "dune-alugrid" + "dune-fem" # matrix library "BLAS REQUIRED" "LAPACK REQUIRED" @@ -54,7 +58,6 @@ set (opm-simulators_DEPS # OPM dependency "opm-common REQUIRED" "opm-grid REQUIRED" - "opm-models REQUIRED" "Damaris 1.9" "HDF5" "Tracy" diff --git a/opm/models/blackoil/blackoilboundaryratevector.hh b/opm/models/blackoil/blackoilboundaryratevector.hh new file mode 100644 index 00000000000..60c586801ca --- /dev/null +++ b/opm/models/blackoil/blackoilboundaryratevector.hh @@ -0,0 +1,280 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilBoundaryRateVector + */ +#ifndef EWOMS_BLACK_OIL_BOUNDARY_RATE_VECTOR_HH +#define EWOMS_BLACK_OIL_BOUNDARY_RATE_VECTOR_HH + +#include +#include + +#include "blackoilintensivequantities.hh" +#include "blackoilenergymodules.hh" + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * + * \brief Implements a boundary vector for the fully implicit black-oil model. + */ +template +class BlackOilBoundaryRateVector : public GetPropType +{ + using ParentType = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using LocalResidual = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { enableSolvent = getPropValue() }; + enum { enablePolymer = getPropValue() }; + enum { enableEnergy = getPropValue() }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { contiEnergyEqIdx = Indices::contiEnergyEqIdx }; + enum { enableFoam = getPropValue() }; + enum { enableMICP = getPropValue() }; + + static constexpr bool blackoilConserveSurfaceVolume = getPropValue(); + + using EnergyModule = BlackOilEnergyModule; + +public: + /*! + * \brief Default constructor + */ + BlackOilBoundaryRateVector() : ParentType() + {} + + /*! + * \copydoc ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(Scalar) + */ + BlackOilBoundaryRateVector(Scalar value) : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(const ImmiscibleBoundaryRateVector& ) + */ + BlackOilBoundaryRateVector(const BlackOilBoundaryRateVector& value) = default; + BlackOilBoundaryRateVector& operator=(const BlackOilBoundaryRateVector& value) = default; + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setFreeFlow + */ + template + void setFreeFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + ExtensiveQuantities extQuants; + extQuants.updateBoundary(context, bfIdx, timeIdx, fluidState); + const auto& insideIntQuants = context.intensiveQuantities(bfIdx, timeIdx); + unsigned focusDofIdx = context.focusDofIndex(); + unsigned interiorDofIdx = context.interiorScvIndex(bfIdx, timeIdx); + + //////// + // advective fluxes of all components in all phases + //////// + (*this) = 0.0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + const auto& pBoundary = fluidState.pressure(phaseIdx); + const Evaluation& pInside = insideIntQuants.fluidState().pressure(phaseIdx); + + RateVector tmp; + + // mass conservation + if (pBoundary < pInside) + // outflux + LocalResidual::template evalPhaseFluxes_(tmp, + phaseIdx, + insideIntQuants.pvtRegionIndex(), + extQuants, + insideIntQuants.fluidState()); + else if (pBoundary > pInside) { + using RhsEval = typename std::conditional::value, + Evaluation, Scalar>::type; + // influx + LocalResidual::template evalPhaseFluxes_(tmp, + phaseIdx, + insideIntQuants.pvtRegionIndex(), + extQuants, + fluidState); + } + + for (unsigned i = 0; i < tmp.size(); ++i) + (*this)[i] += tmp[i]; + + // energy conservation + if constexpr (enableEnergy) { + Evaluation density; + Evaluation specificEnthalpy; + if (pBoundary > pInside) { + if (focusDofIdx == interiorDofIdx) { + density = fluidState.density(phaseIdx); + specificEnthalpy = fluidState.enthalpy(phaseIdx); + } + else { + density = getValue(fluidState.density(phaseIdx)); + specificEnthalpy = getValue(fluidState.enthalpy(phaseIdx)); + } + } + else if (focusDofIdx == interiorDofIdx) { + density = insideIntQuants.fluidState().density(phaseIdx); + specificEnthalpy = insideIntQuants.fluidState().enthalpy(phaseIdx); + } + else { + density = getValue(insideIntQuants.fluidState().density(phaseIdx)); + specificEnthalpy = getValue(insideIntQuants.fluidState().enthalpy(phaseIdx)); + } + + Evaluation enthalpyRate = density*extQuants.volumeFlux(phaseIdx)*specificEnthalpy; + EnergyModule::addToEnthalpyRate(*this, enthalpyRate*getPropValue()); + } + } + + if constexpr (enableSolvent) { + (*this)[Indices::contiSolventEqIdx] = extQuants.solventVolumeFlux(); + if (blackoilConserveSurfaceVolume) + (*this)[Indices::contiSolventEqIdx] *= insideIntQuants.solventInverseFormationVolumeFactor(); + else + (*this)[Indices::contiSolventEqIdx] *= insideIntQuants.solventDensity(); + + } + + if constexpr (enablePolymer) { + (*this)[Indices::contiPolymerEqIdx] = extQuants.volumeFlux(FluidSystem::waterPhaseIdx) * insideIntQuants.polymerConcentration(); + } + + if constexpr (enableMICP) { + (*this)[Indices::contiMicrobialEqIdx] = extQuants.volumeFlux(FluidSystem::waterPhaseIdx) * insideIntQuants.microbialConcentration(); + (*this)[Indices::contiOxygenEqIdx] = extQuants.volumeFlux(FluidSystem::waterPhaseIdx) * insideIntQuants.oxygenConcentration(); + (*this)[Indices::contiUreaEqIdx] = extQuants.volumeFlux(FluidSystem::waterPhaseIdx) * insideIntQuants.ureaConcentration(); + } + + // make sure that the right mass conservation quantities are used + LocalResidual::adaptMassConservationQuantities_(*this, insideIntQuants.pvtRegionIndex()); + + // heat conduction + if constexpr (enableEnergy) + EnergyModule::addToEnthalpyRate(*this, extQuants.energyFlux()*getPropValue()); + +#ifndef NDEBUG + for (unsigned i = 0; i < numEq; ++i) { + Valgrind::CheckDefined((*this)[i]); + } + Valgrind::CheckDefined(*this); +#endif + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setInFlow + */ + template + void setInFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the direction opposite to the outer + // unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Scalar& val = this->operator[](eqIdx); + val = std::min(0.0, val); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setOutFlow + */ + template + void setOutFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the same direction as the outer + // unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Scalar& val = this->operator[](eqIdx); + val = std::max( Scalar(0), val); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setNoFlow + */ + void setNoFlow() + { (*this) = Scalar(0); } + + /*! + * \copydoc Specify an energy flux that corresponds to the thermal conduction from + * the domain boundary + * + * This means that a "thermal flow" boundary is a no-flow condition for mass and thermal + * conduction for energy. + */ + template + void setThermalFlow([[maybe_unused]] const Context& context, + [[maybe_unused]] unsigned bfIdx, + [[maybe_unused]] unsigned timeIdx, + [[maybe_unused]] const FluidState& boundaryFluidState) + { + // set the mass no-flow condition + setNoFlow(); + + // if we do not conserve energy there is nothing we should do in addition + if constexpr (enableEnergy) { + ExtensiveQuantities extQuants; + extQuants.updateBoundary(context, bfIdx, timeIdx, boundaryFluidState); + + (*this)[contiEnergyEqIdx] += extQuants.energyFlux(); + +#ifndef NDEBUG + for (unsigned i = 0; i < numEq; ++i) + Valgrind::CheckDefined((*this)[i]); + Valgrind::CheckDefined(*this); +#endif + } + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilbrinemodules.hh b/opm/models/blackoil/blackoilbrinemodules.hh new file mode 100644 index 00000000000..814dc078497 --- /dev/null +++ b/opm/models/blackoil/blackoilbrinemodules.hh @@ -0,0 +1,567 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the classes required to extend the black-oil model by brine. + */ +#ifndef EWOMS_BLACK_OIL_BRINE_MODULE_HH +#define EWOMS_BLACK_OIL_BRINE_MODULE_HH + +#include "blackoilproperties.hh" + +#include + +#if HAVE_ECL_INPUT +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include +#include + +namespace Opm { +/*! + * \ingroup BlackOil + * \brief Contains the high level supplements required to extend the black oil + * model by brine. + */ +template ()> +class BlackOilBrineModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Model = GetPropType; + using Simulator = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + + using Toolbox = MathToolbox; + + using TabulatedFunction = typename BlackOilBrineParams::TabulatedFunction; + + static constexpr unsigned saltConcentrationIdx = Indices::saltConcentrationIdx; + static constexpr unsigned contiBrineEqIdx = Indices::contiBrineEqIdx; + static constexpr unsigned waterPhaseIdx = FluidSystem::waterPhaseIdx; + static constexpr bool gasEnabled = Indices::gasEnabled; + static constexpr bool oilEnabled = Indices::oilEnabled; + static constexpr unsigned enableBrine = enableBrineV; + static constexpr unsigned enableSaltPrecipitation = getPropValue(); + + static constexpr unsigned numEq = getPropValue(); + static constexpr unsigned numPhases = FluidSystem::numPhases; + +public: + +#if HAVE_ECL_INPUT + /*! + * \brief Initialize all internal data structures needed by the brine module + */ + static void initFromState(const EclipseState& eclState) + { + // some sanity checks: if brine are enabled, the BRINE keyword must be + // present, if brine are disabled the keyword must not be present. + if (enableBrine && !eclState.runspec().phases().active(Phase::BRINE)) { + throw std::runtime_error("Non-trivial brine treatment requested at compile time, but " + "the deck does not contain the BRINE keyword"); + } + else if (!enableBrine && eclState.runspec().phases().active(Phase::BRINE)) { + throw std::runtime_error("Brine treatment disabled at compile time, but the deck " + "contains the BRINE keyword"); + } + + if (!eclState.runspec().phases().active(Phase::BRINE)) + return; // brine treatment is supposed to be disabled + + const auto& tableManager = eclState.getTableManager(); + + unsigned numPvtRegions = tableManager.getTabdims().getNumPVTTables(); + params_.referencePressure_.resize(numPvtRegions); + + const auto& pvtwsaltTables = tableManager.getPvtwSaltTables(); + + // initialize the objects which deal with the BDENSITY keyword + const auto& bdensityTables = tableManager.getBrineDensityTables(); + if (!bdensityTables.empty()) { + params_.bdensityTable_.resize(numPvtRegions); + assert(numPvtRegions == bdensityTables.size()); + for (unsigned pvtRegionIdx = 0; pvtRegionIdx < numPvtRegions; ++ pvtRegionIdx) { + const auto& bdensityTable = bdensityTables[pvtRegionIdx]; + const auto& pvtwsaltTable = pvtwsaltTables[pvtRegionIdx]; + const auto& c = pvtwsaltTable.getSaltConcentrationColumn(); + params_.bdensityTable_[pvtRegionIdx].setXYContainers(c, bdensityTable); + } + } + + if constexpr (enableSaltPrecipitation) { + const TableContainer& permfactTables = tableManager.getPermfactTables(); + params_.permfactTable_.resize(numPvtRegions); + for (size_t i = 0; i < permfactTables.size(); ++i) { + const PermfactTable& permfactTable = permfactTables.getTable(i); + params_.permfactTable_[i].setXYContainers(permfactTable.getPorosityChangeColumn(), permfactTable.getPermeabilityMultiplierColumn()); + } + + const TableContainer& saltsolTables = tableManager.getSaltsolTables(); + if (!saltsolTables.empty()) { + params_.saltsolTable_.resize(numPvtRegions); + params_.saltdenTable_.resize(numPvtRegions); + assert(numPvtRegions == saltsolTables.size()); + for (unsigned pvtRegionIdx = 0; pvtRegionIdx < numPvtRegions; ++ pvtRegionIdx) { + const SaltsolTable& saltsolTable = saltsolTables.getTable(pvtRegionIdx ); + params_.saltsolTable_[pvtRegionIdx] = saltsolTable.getSaltsolColumn().front(); + params_.saltdenTable_[pvtRegionIdx] = saltsolTable.getSaltdenColumn().front(); + } + } + + const TableContainer& pcfactTables = tableManager.getPcfactTables(); + if (!pcfactTables.empty()) { + unsigned numSatRegions = tableManager.getTabdims().getNumSatTables(); + params_.pcfactTable_.resize(numSatRegions); + for (size_t i = 0; i < pcfactTables.size(); ++i) { + const PcfactTable& pcfactTable = pcfactTables.getTable(i); + params_.pcfactTable_[i].setXYContainers(pcfactTable.getPorosityChangeColumn(), pcfactTable.getPcMultiplierColumn()); + } + } + } + } +#endif + + /*! + * \brief Register all run-time parameters for the black-oil brine module. + */ + static void registerParameters() + { + } + + static bool primaryVarApplies(unsigned pvIdx) + { + if constexpr (enableBrine) + return pvIdx == saltConcentrationIdx; + else + return false; + } + + /*! + * \brief Assign the brine specific primary variables to a PrimaryVariables object + */ + template + static void assignPrimaryVars(PrimaryVariables& priVars, + const FluidState& fluidState) + { + if constexpr (enableBrine) + priVars[saltConcentrationIdx] = fluidState.saltConcentration(); + } + + static std::string primaryVarName([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + return "saltConcentration"; + } + + static Scalar primaryVarWeight([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + static bool eqApplies(unsigned eqIdx) + { + if constexpr (enableBrine) + return eqIdx == contiBrineEqIdx; + else + return false; + } + + static std::string eqName([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + return "conti^brine"; + } + + static Scalar eqWeight([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + // must be called after water storage is computed + template + static void addStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants) + { + if constexpr (enableBrine) { + const auto& fs = intQuants.fluidState(); + + LhsEval surfaceVolumeWater = + Toolbox::template decay(fs.saturation(waterPhaseIdx)) + * Toolbox::template decay(fs.invB(waterPhaseIdx)) + * Toolbox::template decay(intQuants.porosity()); + + // avoid singular matrix if no water is present. + surfaceVolumeWater = max(surfaceVolumeWater, 1e-10); + + // Brine in water phase + const LhsEval massBrine = surfaceVolumeWater + * Toolbox::template decay(fs.saltConcentration()); + + if (enableSaltPrecipitation){ + double saltDensity = intQuants.saltDensity(); // Solid salt density kg/m3 + const LhsEval solidSalt = + Toolbox::template decay(intQuants.porosity()) + / (1.0 - Toolbox::template decay(intQuants.saltSaturation()) + 1.e-8) + * saltDensity + * Toolbox::template decay(intQuants.saltSaturation()); + + storage[contiBrineEqIdx] += massBrine + solidSalt; + } + else { storage[contiBrineEqIdx] += massBrine;} + } + } + + static void computeFlux([[maybe_unused]] RateVector& flux, + [[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned scvfIdx, + [[maybe_unused]] unsigned timeIdx) + + { + if constexpr (enableBrine) { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + const unsigned upIdx = extQuants.upstreamIndex(FluidSystem::waterPhaseIdx); + const unsigned inIdx = extQuants.interiorIndex(); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + + if (upIdx == inIdx) { + flux[contiBrineEqIdx] = + extQuants.volumeFlux(waterPhaseIdx) + *up.fluidState().invB(waterPhaseIdx) + *up.fluidState().saltConcentration(); + } + else { + flux[contiBrineEqIdx] = + extQuants.volumeFlux(waterPhaseIdx) + *decay(up.fluidState().invB(waterPhaseIdx)) + *decay(up.fluidState().saltConcentration()); + } + } + } + + /*! + * \brief Return how much a Newton-Raphson update is considered an error + */ + static Scalar computeUpdateError(const PrimaryVariables&, + const EqVector&) + { + // do not consider consider the change of Brine primary variables for + // convergence + // TODO: maybe this should be changed + return static_cast(0.0); + } + + template + static void serializeEntity(const Model& model, std::ostream& outstream, const DofEntity& dof) + { + if constexpr (enableBrine) { + unsigned dofIdx = model.dofMapper().index(dof); + const PrimaryVariables& priVars = model.solution(/*timeIdx=*/0)[dofIdx]; + outstream << priVars[saltConcentrationIdx]; + } + } + + template + static void deserializeEntity(Model& model, std::istream& instream, const DofEntity& dof) + { + if constexpr (enableBrine) { + unsigned dofIdx = model.dofMapper().index(dof); + PrimaryVariables& priVars0 = model.solution(/*timeIdx=*/0)[dofIdx]; + PrimaryVariables& priVars1 = model.solution(/*timeIdx=*/1)[dofIdx]; + + instream >> priVars0[saltConcentrationIdx]; + + // set the primary variables for the beginning of the current time step. + priVars1[saltConcentrationIdx] = priVars0[saltConcentrationIdx]; + } + } + + static const Scalar& referencePressure(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned pvtnumRegionIdx = elemCtx.problem().pvtRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.referencePressure_[pvtnumRegionIdx]; + } + + + static const TabulatedFunction& bdensityTable(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned pvtnumRegionIdx = elemCtx.problem().pvtRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.bdensityTable_[pvtnumRegionIdx]; + } + + static const TabulatedFunction& pcfactTable(unsigned satnumRegionIdx) + { + return params_.pcfactTable_[satnumRegionIdx]; + } + + static const TabulatedFunction& permfactTable(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned pvtnumRegionIdx = elemCtx.problem().pvtRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.permfactTable_[pvtnumRegionIdx]; + } + + static const TabulatedFunction& permfactTable(unsigned pvtnumRegionIdx) + { + return params_.permfactTable_[pvtnumRegionIdx]; + } + + static const Scalar saltsolTable(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned pvtnumRegionIdx = elemCtx.problem().pvtRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.saltsolTable_[pvtnumRegionIdx]; + } + + static const Scalar saltdenTable(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned pvtnumRegionIdx = elemCtx.problem().pvtRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.saltdenTable_[pvtnumRegionIdx]; + } + + static bool hasBDensityTables() + { + return !params_.bdensityTable_.empty(); + } + + static bool hasSaltsolTables() + { + return !params_.saltsolTable_.empty(); + } + + static bool hasPcfactTables() + { + if constexpr (enableSaltPrecipitation) + return !params_.pcfactTable_.empty(); + else + return false; + } + + static Scalar saltSol(unsigned regionIdx) { + return params_.saltsolTable_[regionIdx]; + } + +private: + static BlackOilBrineParams params_; +}; + + +template +BlackOilBrineParams::Scalar> +BlackOilBrineModule::params_; + +/*! + * \ingroup BlackOil + * \class Ewoms::BlackOilBrineIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the equations needed by the + * brine extension of the black-oil model. + */ +template ()> +class BlackOilBrineIntensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using Indices = GetPropType; + using ElementContext = GetPropType; + + using BrineModule = BlackOilBrineModule; + + enum { numPhases = getPropValue() }; + static constexpr int saltConcentrationIdx = Indices::saltConcentrationIdx; + static constexpr int waterPhaseIdx = FluidSystem::waterPhaseIdx; + static constexpr int gasPhaseIdx = FluidSystem::gasPhaseIdx; + static constexpr int oilPhaseIdx = FluidSystem::oilPhaseIdx; + static constexpr unsigned enableBrine = enableBrineV; + static constexpr unsigned enableSaltPrecipitation = getPropValue(); + static constexpr int contiBrineEqIdx = Indices::contiBrineEqIdx; + +public: + + /*! + * \brief Update the intensive properties needed to handle brine from the + * primary variables + * + */ + void updateSaltConcentration_(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + const PrimaryVariables& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + + auto& fs = asImp_().fluidState_; + + if constexpr (enableSaltPrecipitation) { + const auto& saltsolTable = BrineModule::saltsolTable(elemCtx, dofIdx, timeIdx); + saltSolubility_ = saltsolTable; + + const auto& saltdenTable = BrineModule::saltdenTable(elemCtx, dofIdx, timeIdx); + saltDensity_ = saltdenTable; + + if (priVars.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp) { + saltSaturation_ = priVars.makeEvaluation(saltConcentrationIdx, timeIdx); + fs.setSaltConcentration(saltSolubility_); + } + else { + saltConcentration_ = priVars.makeEvaluation(saltConcentrationIdx, timeIdx); + fs.setSaltConcentration(saltConcentration_); + saltSaturation_ = 0.0; + } + fs.setSaltSaturation(saltSaturation_); + } + else { + saltConcentration_ = priVars.makeEvaluation(saltConcentrationIdx, timeIdx); + fs.setSaltConcentration(saltConcentration_); + } + } + + void saltPropertiesUpdate_([[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned dofIdx, + [[maybe_unused]] unsigned timeIdx) + { + if constexpr (enableSaltPrecipitation) { + const Evaluation porosityFactor = min(1.0 - saltSaturation(), 1.0); //phi/phi_0 + + const auto& permfactTable = BrineModule::permfactTable(elemCtx, dofIdx, timeIdx); + + permFactor_ = permfactTable.eval(porosityFactor); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + asImp_().mobility_[phaseIdx] *= permFactor_; + } + } + } + + const Evaluation& saltConcentration() const + { return saltConcentration_; } + + const Evaluation& brineRefDensity() const + { return refDensity_; } + + const Evaluation& saltSaturation() const + { return saltSaturation_; } + + Scalar saltSolubility() const + { return saltSolubility_; } + + Scalar saltDensity() const + { return saltDensity_; } + + const Evaluation& permFactor() const + { return permFactor_; } + +protected: + Implementation& asImp_() + { return *static_cast(this); } + + Evaluation saltConcentration_; + Evaluation refDensity_; + Evaluation saltSaturation_; + Evaluation permFactor_; + Scalar saltSolubility_; + Scalar saltDensity_; + +}; + +template +class BlackOilBrineIntensiveQuantities +{ + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + +public: + void updateSaltConcentration_(const ElementContext&, + unsigned, + unsigned) + { } + + void saltPropertiesUpdate_(const ElementContext&, + unsigned, + unsigned) + { } + + const Evaluation& saltConcentration() const + { throw std::runtime_error("saltConcentration() called but brine are disabled"); } + + const Evaluation& brineRefDensity() const + { throw std::runtime_error("brineRefDensity() called but brine are disabled"); } + + const Evaluation& saltSaturation() const + { throw std::logic_error("saltSaturation() called but salt precipitation is disabled"); } + + const Scalar saltSolubility() const + { throw std::logic_error("saltSolubility() called but salt precipitation is disabled"); } + + const Scalar saltDensity() const + { throw std::logic_error("saltDensity() called but salt precipitation is disabled"); } + + const Evaluation& permFactor() const + { throw std::logic_error("permFactor() called but salt precipitation is disabled"); } + +}; + +} // namespace Ewoms + +#endif diff --git a/opm/models/blackoil/blackoilbrineparams.hh b/opm/models/blackoil/blackoilbrineparams.hh new file mode 100644 index 00000000000..67bf50f8169 --- /dev/null +++ b/opm/models/blackoil/blackoilbrineparams.hh @@ -0,0 +1,52 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the parameters required to extend the black-oil model by brine. + */ +#ifndef EWOMS_BLACK_OIL_BRINE_PARAMS_HH +#define EWOMS_BLACK_OIL_BRINE_PARAMS_HH + +#include + +#include + +namespace Opm { + +//! \brief Struct holding the parameters for the BlackoilBrineModule class. +template +struct BlackOilBrineParams { + using TabulatedFunction = Tabulated1DFunction; + + std::vector bdensityTable_; + std::vector pcfactTable_; + std::vector permfactTable_; + std::vector saltsolTable_; + std::vector saltdenTable_; + std::vector referencePressure_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilconvectivemixingmodule.hh b/opm/models/blackoil/blackoilconvectivemixingmodule.hh new file mode 100644 index 00000000000..ac46d3bfd64 --- /dev/null +++ b/opm/models/blackoil/blackoilconvectivemixingmodule.hh @@ -0,0 +1,387 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Classes required for dynamic convective mixing. + */ +#ifndef EWOMS_CONVECTIVEMIXING_MODULE_HH +#define EWOMS_CONVECTIVEMIXING_MODULE_HH + +#include "opm/material/common/MathToolbox.hpp" +#include + +#include +#include + +#include + +#include +#include + +namespace Opm { + +/*! + * \copydoc Opm::BlackOilConvectiveMixingModule + * \brief Provides the convective term in the transport flux for the brine + * when convective mixing (enhanced dissolution of CO2 in brine) occurs. + * Controlled by the regimes for a controlvolume: + * i) initial phase (CO2 dissolves in brine due to diffusion) + * ii) linear phase (Convective fingers of CO2-rich brine propagate downwards) + * iii) steady-state-phase (fingers have passed through the bottom of a control + * -volume but the larger scale convective process is still active) + * iv) decline phase (Convection ceases at the large-scale when the CO2 + * has been completely dissolved) + */ + +template +class BlackOilConvectiveMixingModule; + +/*! + * \copydoc Opm::BlackOilConvectiveMixingModule + */ + +template +class BlackOilConvectiveMixingModule +{ + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using RateVector = GetPropType; + using Evaluation = GetPropType; + using Indices = GetPropType; + using IntensiveQuantities = GetPropType; + using GridView = GetPropType; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { dimWorld = GridView::dimensionworld }; + +public: + struct ConvectiveMixingModuleParam + {}; + + #if HAVE_ECL_INPUT + static void beginEpisode(const EclipseState&, + const Schedule&, + const int, + ConvectiveMixingModuleParam&) + {} + #endif + + template + static bool active(const Context&) { + return false; + } + + static void modifyAvgDensity(Evaluation&, + const IntensiveQuantities&, + const IntensiveQuantities&, + const unsigned int, + const ConvectiveMixingModuleParam&) { + } + + template + static void addConvectiveMixingFlux(RateVector&, + const Context&, + unsigned, + unsigned) + {} + + /*! + * \brief Adds the convective mixing mass flux flux to the flux vector over a flux + * integration point. + */ + static void addConvectiveMixingFlux(RateVector&, + const IntensiveQuantities&, + const IntensiveQuantities&, + const unsigned, + const unsigned, + const Scalar, + const Scalar, + const Scalar, + const ConvectiveMixingModuleParam&) + {} +}; + +template +class BlackOilConvectiveMixingModule +{ + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using RateVector = GetPropType; + using Evaluation = GetPropType; + using Indices = GetPropType; + using IntensiveQuantities = GetPropType; + using GridView = GetPropType; + using Toolbox = MathToolbox; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { dimWorld = GridView::dimensionworld }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + static constexpr bool enableEnergy = getPropValue(); + static constexpr unsigned contiEnergyEqIdx = Indices::contiEnergyEqIdx; + +public: + + struct ConvectiveMixingModuleParam + { + std::vector active_; + std::vector Xhi_; + std::vector Psi_; + }; + + #if HAVE_ECL_INPUT + static void beginEpisode(const EclipseState& eclState, + const Schedule& schedule, + const int episodeIdx, + ConvectiveMixingModuleParam& info) + { + // check that Xhi and Psi didn't change + std::size_t numRegions = eclState.runspec().tabdims().getNumPVTTables(); + const auto& control = schedule[episodeIdx].oilvap(); + if (info.active_.empty()) { + info.active_.resize(numRegions); + info.Xhi_.resize(numRegions); + info.Psi_.resize(numRegions); + } + for (size_t i = 0; i < numRegions; ++i ) { + info.active_[i] = control.drsdtConvective(i); + if (control.drsdtConvective(i)) { + info.Xhi_[i] = control.getMaxDRSDT(i); + info.Psi_[i] = control.getPsi(i); + } + } + } + #endif + + static void modifyAvgDensity(Evaluation& rhoAvg, + const IntensiveQuantities& intQuantsIn, + const IntensiveQuantities& intQuantsEx, + const unsigned phaseIdx, + const ConvectiveMixingModuleParam& info) { + + if (info.active_.empty()) { + return; + } + if (!info.active_[ intQuantsIn.pvtRegionIndex()] || !info.active_[ intQuantsEx.pvtRegionIndex()]) { + return; + } + + if (phaseIdx == FluidSystem::gasPhaseIdx) { + return; + } + + const auto& liquidPhaseIdx = (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) ? + FluidSystem::waterPhaseIdx : + FluidSystem::oilPhaseIdx; + + // Compute avg density based on pure water + const auto& t_in = intQuantsIn.fluidState().temperature(liquidPhaseIdx); + const auto& p_in = intQuantsIn.fluidState().pressure(liquidPhaseIdx); + const auto& salt_in = intQuantsIn.fluidState().saltConcentration(); + + const auto& bLiquidIn = (FluidSystem::phaseIsActive(waterPhaseIdx)) ? + FluidSystem::waterPvt().inverseFormationVolumeFactor(intQuantsIn.pvtRegionIndex(), t_in, p_in, Evaluation(0.0), salt_in) : + FluidSystem::oilPvt().inverseFormationVolumeFactor(intQuantsIn.pvtRegionIndex(), t_in, p_in, Evaluation(0.0)); + + const auto& refDensityLiquidIn = (FluidSystem::phaseIsActive(waterPhaseIdx)) ? + FluidSystem::waterPvt().waterReferenceDensity(intQuantsIn.pvtRegionIndex()) : + FluidSystem::oilPvt().oilReferenceDensity(intQuantsIn.pvtRegionIndex()); + + const auto& rho_in = bLiquidIn * refDensityLiquidIn; + + const auto t_ex = Toolbox::value(intQuantsEx.fluidState().temperature(liquidPhaseIdx)); + const auto p_ex = Toolbox::value(intQuantsEx.fluidState().pressure(liquidPhaseIdx)); + const auto salt_ex = Toolbox::value(intQuantsEx.fluidState().saltConcentration()); + + const auto bLiquidEx = (FluidSystem::phaseIsActive(waterPhaseIdx)) ? + FluidSystem::waterPvt().inverseFormationVolumeFactor(intQuantsEx.pvtRegionIndex(), + t_ex, p_ex, Scalar{0.0}, salt_ex) : + FluidSystem::oilPvt().inverseFormationVolumeFactor(intQuantsEx.pvtRegionIndex(), + t_ex, p_ex, Scalar{0.0}); + + const auto& refDensityLiquidEx = (FluidSystem::phaseIsActive(waterPhaseIdx)) ? + FluidSystem::waterPvt().waterReferenceDensity(intQuantsEx.pvtRegionIndex()) : + FluidSystem::oilPvt().oilReferenceDensity(intQuantsEx.pvtRegionIndex()); + + const auto rho_ex = bLiquidEx * refDensityLiquidEx; + + rhoAvg = (rho_in + rho_ex)/2; + } + + template + static void addConvectiveMixingFlux(RateVector& flux, + const Context& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) { + // need for darcy flux calculation + const auto& problem = elemCtx.problem(); + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& scvf = stencil.interiorFace(scvfIdx); + + unsigned interiorDofIdx = scvf.interiorIndex(); + unsigned exteriorDofIdx = scvf.exteriorIndex(); + assert(interiorDofIdx != exteriorDofIdx); + + const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx); + const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx); + Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); + Scalar faceArea = scvf.area(); + const Scalar g = problem.gravity()[dimWorld - 1]; + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx, timeIdx); + const Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx, timeIdx); + const Scalar zEx = problem.dofCenterDepth(elemCtx, exteriorDofIdx, timeIdx); + const Scalar distZ = zIn - zEx; + addConvectiveMixingFlux(flux, + intQuantsIn, + intQuantsEx, + globalIndexIn, + globalIndexEx, + distZ * g, + trans, + faceArea, + problem.moduleParams().convectiveMixingModuleParam); + } + + /*! + * \brief Adds the convective mixing mass flux flux to the flux vector over a flux + * integration point. + */ + static void addConvectiveMixingFlux(RateVector& flux, + const IntensiveQuantities& intQuantsIn, + const IntensiveQuantities& intQuantsEx, + const unsigned globalIndexIn, + const unsigned globalIndexEx, + const Scalar distZg, + const Scalar trans, + const Scalar faceArea, + const ConvectiveMixingModuleParam& info) + { + if (info.active_.empty()) { + return; + } + + if (!info.active_[ intQuantsIn.pvtRegionIndex()] || !info.active_[ intQuantsEx.pvtRegionIndex()]) { + return; + } + + const auto& liquidPhaseIdx = (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) ? + FluidSystem::waterPhaseIdx : + FluidSystem::oilPhaseIdx; + const Evaluation SoMax = 0.0; + + //interiour + const auto& t_in = intQuantsIn.fluidState().temperature(liquidPhaseIdx); + const auto& p_in = intQuantsIn.fluidState().pressure(liquidPhaseIdx); + const auto& rssat_in = FluidSystem::saturatedDissolutionFactor(intQuantsIn.fluidState(), + liquidPhaseIdx, + intQuantsIn.pvtRegionIndex(), + SoMax); + const auto& salt_in = intQuantsIn.fluidState().saltSaturation(); + + const auto bLiquidSatIn = (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) ? + FluidSystem::waterPvt().inverseFormationVolumeFactor(intQuantsIn.pvtRegionIndex(), t_in, p_in, rssat_in, salt_in): + FluidSystem::oilPvt().inverseFormationVolumeFactor(intQuantsIn.pvtRegionIndex(), t_in, p_in, rssat_in); + + const auto& densityLiquidIn = (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) ? + FluidSystem::waterPvt().waterReferenceDensity(intQuantsIn.pvtRegionIndex()) : + FluidSystem::oilPvt().oilReferenceDensity(intQuantsIn.pvtRegionIndex()); + + const auto rho_in = Opm::getValue(intQuantsIn.fluidState().invB(liquidPhaseIdx)) * densityLiquidIn; + const auto rho_sat_in = bLiquidSatIn + * (densityLiquidIn + rssat_in * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, intQuantsIn.pvtRegionIndex())); + + //exteriour + const auto t_ex = Opm::getValue(intQuantsEx.fluidState().temperature(liquidPhaseIdx)); + const auto p_ex = Opm::getValue(intQuantsEx.fluidState().pressure(liquidPhaseIdx)); + const auto rssat_ex = Opm::getValue(FluidSystem::saturatedDissolutionFactor(intQuantsEx.fluidState(), + liquidPhaseIdx, + intQuantsEx.pvtRegionIndex(), + SoMax)); + const auto salt_ex = Opm::getValue(intQuantsEx.fluidState().saltSaturation()); + const auto bLiquidSatEx = (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) ? + FluidSystem::waterPvt().inverseFormationVolumeFactor(intQuantsEx.pvtRegionIndex(), t_ex, p_ex, rssat_ex, salt_ex): + FluidSystem::oilPvt().inverseFormationVolumeFactor(intQuantsEx.pvtRegionIndex(), t_ex, p_ex, rssat_ex); + + + const auto& densityLiquidEx = (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) ? + FluidSystem::waterPvt().waterReferenceDensity(intQuantsEx.pvtRegionIndex()) : + FluidSystem::oilPvt().oilReferenceDensity(intQuantsEx.pvtRegionIndex()); + + const auto rho_ex = Opm::getValue(intQuantsEx.fluidState().invB(liquidPhaseIdx)) * densityLiquidEx; + const auto rho_sat_ex = bLiquidSatEx + * (densityLiquidEx + rssat_ex * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, intQuantsEx.pvtRegionIndex())); + //rho difference approximation + const auto delta_rho = (rho_sat_ex + rho_sat_in - rho_in - rho_ex)/2; + const auto pressure_difference_convective_mixing = delta_rho * distZg; + + //if change in pressure + if (Opm::abs(pressure_difference_convective_mixing) > 1e-12){ + + // find new upstream direction + short interiorDofIdx = 0; + short exteriorDofIdx = 1; + short upIdx = 0; + + if (pressure_difference_convective_mixing > 0) { + upIdx = exteriorDofIdx; + } + + const auto& up = (upIdx == interiorDofIdx) ? intQuantsIn : intQuantsEx; + const auto& rssat_up = (upIdx == interiorDofIdx) ? rssat_in : rssat_ex; + unsigned globalUpIndex = (upIdx == interiorDofIdx) ? globalIndexIn : globalIndexEx; + const auto& Rsup = (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) ? + up.fluidState().Rsw() : + up.fluidState().Rs(); + + const Evaluation& transMult = up.rockCompTransMultiplier(); + const auto& invB = up.fluidState().invB(liquidPhaseIdx); + const auto& visc = up.fluidState().viscosity(liquidPhaseIdx); + + // We restrict the convective mixing mass flux to rssat * Psi. + const Evaluation RsupRestricted = Opm::min(Rsup, rssat_up*info.Psi_[up.pvtRegionIndex()]); + + const auto convectiveFlux = -trans*transMult*info.Xhi_[up.pvtRegionIndex()]*invB*pressure_difference_convective_mixing*RsupRestricted/(visc*faceArea); + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); + if (globalUpIndex == globalIndexIn) + flux[conti0EqIdx + activeGasCompIdx] += convectiveFlux; + else + flux[conti0EqIdx + activeGasCompIdx] += Opm::getValue(convectiveFlux); + + if constexpr (enableEnergy) { + const auto& h = up.fluidState().enthalpy(liquidPhaseIdx) * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, up.pvtRegionIndex()); + if (globalUpIndex == globalIndexIn) { + flux[contiEnergyEqIdx] += convectiveFlux * h; + } + else { + flux[contiEnergyEqIdx] += Opm::getValue(h) * Opm::getValue(convectiveFlux); + } + } + } + } +}; + +} + +#endif diff --git a/opm/models/blackoil/blackoildarcyfluxmodule.hh b/opm/models/blackoil/blackoildarcyfluxmodule.hh new file mode 100644 index 00000000000..aaf309c2a30 --- /dev/null +++ b/opm/models/blackoil/blackoildarcyfluxmodule.hh @@ -0,0 +1,100 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file contains the default flux module of the blackoil model. + * + * It is neccessary to accomodate the extensions of the black-oil model. + */ +#ifndef EWOMS_BLACK_OIL_DARCY_FLUX_MODULE_HH +#define EWOMS_BLACK_OIL_DARCY_FLUX_MODULE_HH + +#include + +#include +#include + +namespace Opm { + +template +class BlackOilDarcyExtensiveQuantities; + +/*! + * \ingroup FluxModules + * \brief Provides a Darcy flux module for the blackoil model + */ +template +struct BlackOilDarcyFluxModule +{ + using FluxIntensiveQuantities = DarcyIntensiveQuantities; + using FluxExtensiveQuantities = BlackOilDarcyExtensiveQuantities; + using FluxBaseProblem = DarcyBaseProblem; + + /*! + * \brief Register all run-time parameters for the flux module. + */ + static void registerParameters() + { } +}; + + +/*! + * \ingroup FluxModules + * \brief Specifies the extensive quantities for the black-oil model if using Darcy relation. + * + * This class basically forwards everything to the default Darcy flux module and adds a + * few methods needed by the extensions of the black-oil model. (i.e. the solvent and the + * polymer extensions.) + */ +template +class BlackOilDarcyExtensiveQuantities : public DarcyExtensiveQuantities +{ + using Implementation = GetPropType; + + using ElementContext = GetPropType; + +public: + /*! + * \brief Update the extensive quantities which are specific to the solvent extension + * of the black-oil model. + */ + void updateSolvent(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + asImp_().updateVolumeFluxPerm(elemCtx, + scvfIdx, + timeIdx); + + } + + void updatePolymer(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { asImp_().updateShearMultipliersPerm(elemCtx, scvfIdx, timeIdx); } + +protected: + Implementation& asImp_() + { return *static_cast(this); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoildiffusionmodule.hh b/opm/models/blackoil/blackoildiffusionmodule.hh new file mode 100644 index 00000000000..4d77be5917e --- /dev/null +++ b/opm/models/blackoil/blackoildiffusionmodule.hh @@ -0,0 +1,631 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Classes required for molecular diffusion. + */ +#ifndef OPM_BLACKOIL_DIFFUSION_MODULE_HH +#define OPM_BLACKOIL_DIFFUSION_MODULE_HH + +#include + +#include + +#include + +#include + +namespace Opm { + +/*! + * \ingroup Diffusion + * \class Opm::BlackOilDiffusionModule + * \brief Provides the auxiliary methods required for consideration of the + * diffusion equation. + */ +template +class BlackOilDiffusionModule; + +template +class BlackOilDiffusionExtensiveQuantities; + +/*! + * \copydoc Opm::BlackOilDiffusionModule + */ +template +class BlackOilDiffusionModule +{ + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using RateVector = GetPropType; + +public: + + #if HAVE_ECL_INPUT + /*! + * \brief Initialize all internal data structures needed by the diffusion module + */ + static void initFromState(const EclipseState&) + { + } + #endif + + /*! + * \brief Register all run-time parameters for the diffusion module. + */ + static void registerParameters() + {} + + /*! + * \brief Adds the diffusive mass flux flux to the flux vector over a flux + * integration point. + */ + template + static void addDiffusiveFlux(RateVector&, + const Context&, + unsigned, + unsigned) + {} +}; + +/*! + * \copydoc Opm::BlackOilDiffusionModule + */ +template +class BlackOilDiffusionModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using RateVector = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + + using Toolbox = MathToolbox; + +public: + using ExtensiveQuantities = BlackOilDiffusionExtensiveQuantities; + + + #if HAVE_ECL_INPUT + /*! + * \brief Initialize all internal data structures needed by the diffusion module + */ + static void initFromState(const EclipseState& eclState) + { + use_mole_fraction_ = eclState.getTableManager().diffMoleFraction(); + } + #endif + + /*! + * \brief Register all run-time parameters for the diffusion module. + */ + static void registerParameters() + {} + + /*! + * \brief Adds the mass flux due to molecular diffusion to the flux vector over the + * integration point. Following the notation in blackoilmodel.hh, + * the diffusive flux for component \f$\kappa\f$ in phase \f$\alpha\f$ + * is given by: \f$-\phi b_\alpha S_\alpha D \mathbf{grad}X_\alpha^\kappa\f$, + * where \f$b_\alpha\f$ is the shrinkage/expansion factor [-], + * \f$S_\alpha\f$ is the saturation [-] D is the diffusion coefficient [L/T^2] + * and \f$X_\alpha^\kappa\f$ the component mass fraction [-] or molar fraction [-], + * depending on the input use_mole_fraction_ (default true) + * Each component mass/molar fraction can be computed using \f$R_s,\;R_v,\;R_{sw},\;R_{vw}\f$. + * For example the mass fraction are given by, + * \f$X_w^G=\frac{R_{sw}}{R_{sw}+\rho_w/\rho_g}\f$, where \f$\rho_w\f$ and \f$\rho_g\f$ + * are the reference densities. + * Considering the water phase and gas component as an example, for cells i and j, the discrete version + * of the diffusive flux at the face's integration point is given by + * \f$-b_{w,ij}S_{w,ij}D_{w,ij}(\frac{1}{R_{sw,ij}+\rho_w/\rho_g})DT_{ij}(R_{sw,i}-R_{sw,j})\f$ + * where \f$b_{w,ij}\f$, \f$S_{w,ij}\f$, \f$D_{w,ij}\f$, and \f$R_{sw,ij}\f$ are computed using the arithmetic mean, and + * the ratio \f$\frac{1}{R_{sw,ij}+\rho_w/\rho_g}\f$ is denoted as conversion factor. The diffusivity + * \f$DT_{ij}\f$ is computed in ecltransmissibility_impl.hh, using the cells porosity, face area and distance between + * cell center and the integration point. + * For mol fraction the convertion factor is given by \f$\frac{1}{R_{sw,ij}+(Mm_g\rho_w)/(Mm_w\rho_g)}\f$ + */ + template + static void addDiffusiveFlux(RateVector& flux, const Context& context, + unsigned spaceIdx, unsigned timeIdx) + { + // Only work if diffusion is enabled run-time by DIFFUSE in the deck + if(!FluidSystem::enableDiffusion()) + return; + const auto& extQuants = context.extensiveQuantities(spaceIdx, timeIdx); + const auto& fluidStateI = context.intensiveQuantities(extQuants.interiorIndex(), timeIdx).fluidState(); + const auto& fluidStateJ = context.intensiveQuantities(extQuants.exteriorIndex(), timeIdx).fluidState(); + const auto& diffusivity = extQuants.diffusivity(); + const auto& effectiveDiffusionCoefficient = extQuants.effectiveDiffusionCoefficient(); + addDiffusiveFlux(flux, fluidStateI, fluidStateJ, diffusivity, effectiveDiffusionCoefficient); + } + + template + static void addDiffusiveFlux(RateVector& flux, + const FluidState& fluidStateI, + const FluidState& fluidStateJ, + const Evaluation& diffusivity, + const EvaluationArray& effectiveDiffusionCoefficient) + { + unsigned pvtRegionIndex = fluidStateI.pvtRegionIndex(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + + // no diffusion in water for blackoil models + if (!FluidSystem::enableDissolvedGasInWater() && FluidSystem::waterPhaseIdx == phaseIdx) { + continue; + } + + // no diffusion in gas phase in water + gas system. + if (FluidSystem::gasPhaseIdx == phaseIdx && !FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) { + continue; + } + + // arithmetic mean of the phase's b factor weighed by saturation + Evaluation bSAvg = fluidStateI.saturation(phaseIdx) * fluidStateI.invB(phaseIdx); + bSAvg += Toolbox::value(fluidStateJ.saturation(phaseIdx)) * Toolbox::value(fluidStateJ.invB(phaseIdx)); + bSAvg /= 2; + + // phase not present, skip + if(bSAvg < 1.0e-6) + continue; + Evaluation convFactor = 1.0; + Evaluation diffR = 0.0; + if (FluidSystem::enableDissolvedGas() && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) && phaseIdx == FluidSystem::oilPhaseIdx) { + Evaluation rsAvg = (fluidStateI.Rs() + Toolbox::value(fluidStateJ.Rs())) / 2; + convFactor = 1.0 / (toFractionGasOil(pvtRegionIndex) + rsAvg); + diffR = fluidStateI.Rs() - Toolbox::value(fluidStateJ.Rs()); + } + if (FluidSystem::enableVaporizedOil() && FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && phaseIdx == FluidSystem::gasPhaseIdx) { + Evaluation rvAvg = (fluidStateI.Rv() + Toolbox::value(fluidStateJ.Rv())) / 2; + convFactor = toFractionGasOil(pvtRegionIndex) / (1.0 + rvAvg*toFractionGasOil(pvtRegionIndex)); + diffR = fluidStateI.Rv() - Toolbox::value(fluidStateJ.Rv()); + } + if (FluidSystem::enableDissolvedGasInWater() && phaseIdx == FluidSystem::waterPhaseIdx) { + Evaluation rsAvg = (fluidStateI.Rsw() + Toolbox::value(fluidStateJ.Rsw())) / 2; + convFactor = 1.0 / (toFractionGasWater(pvtRegionIndex) + rsAvg); + diffR = fluidStateI.Rsw() - Toolbox::value(fluidStateJ.Rsw()); + } + if (FluidSystem::enableVaporizedWater() && phaseIdx == FluidSystem::gasPhaseIdx) { + Evaluation rvAvg = (fluidStateI.Rvw() + Toolbox::value(fluidStateJ.Rvw())) / 2; + convFactor = toFractionGasWater(pvtRegionIndex)/ (1.0 + rvAvg*toFractionGasWater(pvtRegionIndex)); + diffR = fluidStateI.Rvw() - Toolbox::value(fluidStateJ.Rvw()); + } + + // mass flux of solvent component (oil in oil or gas in gas) + unsigned solventCompIdx = FluidSystem::solventComponentIndex(phaseIdx); + unsigned activeSolventCompIdx = Indices::canonicalToActiveComponentIndex(solventCompIdx); + flux[conti0EqIdx + activeSolventCompIdx] += + - bSAvg + * convFactor + * diffR + * diffusivity + * effectiveDiffusionCoefficient[phaseIdx][solventCompIdx]; + + // mass flux of solute component (gas in oil or oil in gas) + unsigned soluteCompIdx = FluidSystem::soluteComponentIndex(phaseIdx); + unsigned activeSoluteCompIdx = Indices::canonicalToActiveComponentIndex(soluteCompIdx); + flux[conti0EqIdx + activeSoluteCompIdx] += + bSAvg + * diffR + * convFactor + * diffusivity + * effectiveDiffusionCoefficient[phaseIdx][soluteCompIdx]; + } + } + +private: + static Scalar toFractionGasOil (unsigned regionIdx) { + Scalar mMOil = use_mole_fraction_? FluidSystem::molarMass(FluidSystem::oilCompIdx, regionIdx) : 1; + Scalar rhoO = FluidSystem::referenceDensity(FluidSystem::oilPhaseIdx, regionIdx); + Scalar mMGas = use_mole_fraction_? FluidSystem::molarMass(FluidSystem::gasCompIdx, regionIdx) : 1; + Scalar rhoG = FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, regionIdx); + return rhoO * mMGas / (rhoG * mMOil); + } + static Scalar toFractionGasWater (unsigned regionIdx) { + Scalar mMWater = use_mole_fraction_? FluidSystem::molarMass(FluidSystem::waterCompIdx, regionIdx) : 1; + Scalar rhoW = FluidSystem::referenceDensity(FluidSystem::waterPhaseIdx, regionIdx); + Scalar mMGas = use_mole_fraction_? FluidSystem::molarMass(FluidSystem::gasCompIdx, regionIdx) : 1; + Scalar rhoG = FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, regionIdx); + return rhoW * mMGas / (rhoG * mMWater); + } + + static bool use_mole_fraction_; +}; + +template +bool +BlackOilDiffusionModule::use_mole_fraction_; + +/*! + * \ingroup Diffusion + * \class Opm::BlackOilDiffusionIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the + * calculation of molecular diffusive fluxes. + */ +template +class BlackOilDiffusionIntensiveQuantities; + +/*! + * \copydoc Opm::DiffusionIntensiveQuantities + */ +template +class BlackOilDiffusionIntensiveQuantities +{ + using Scalar = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + +public: + /*! + * \brief Returns the tortuousity of the sub-domain of a fluid + * phase in the porous medium. + */ + Scalar tortuosity(unsigned) const + { + throw std::logic_error("Method tortuosity() does not make sense " + "if diffusion is disabled"); + } + + /*! + * \brief Returns the molecular diffusion coefficient for a + * component in a phase. + */ + Scalar diffusionCoefficient(unsigned, unsigned) const + { + throw std::logic_error("Method diffusionCoefficient() does not " + "make sense if diffusion is disabled"); + } + + /*! + * \brief Returns the effective molecular diffusion coefficient of + * the porous medium for a component in a phase. + */ + Scalar effectiveDiffusionCoefficient(unsigned, unsigned) const + { + throw std::logic_error("Method effectiveDiffusionCoefficient() " + "does not make sense if diffusion is disabled"); + } + +protected: + /*! + * \brief Update the quantities required to calculate diffusive + * mass fluxes. + */ + template + void update_(FluidState&, + typename FluidSystem::template ParameterCache&, + const ElementContext&, + unsigned, + unsigned) + { } +}; + +/*! + * \copydoc Opm::DiffusionIntensiveQuantities + */ +template +class BlackOilDiffusionIntensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using IntensiveQuantities = GetPropType; + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + +public: + BlackOilDiffusionIntensiveQuantities() = default; + BlackOilDiffusionIntensiveQuantities(BlackOilDiffusionIntensiveQuantities&&) noexcept = default; + BlackOilDiffusionIntensiveQuantities(const BlackOilDiffusionIntensiveQuantities&) = default; + + BlackOilDiffusionIntensiveQuantities& operator=(BlackOilDiffusionIntensiveQuantities&&) noexcept = default; + + BlackOilDiffusionIntensiveQuantities& + operator=(const BlackOilDiffusionIntensiveQuantities& rhs) + { + if (this == &rhs) return *this; + + if (FluidSystem::enableDiffusion()) { + std::copy(rhs.tortuosity_, rhs.tortuosity_ + numPhases, tortuosity_); + for (size_t i = 0; i < numPhases; ++i) { + std::copy(rhs.diffusionCoefficient_[i], + rhs.diffusionCoefficient_[i]+numComponents, + diffusionCoefficient_[i]); + } + } + return *this; + } + /*! + * \brief Returns the molecular diffusion coefficient for a + * component in a phase. + */ + Evaluation diffusionCoefficient(unsigned phaseIdx, unsigned compIdx) const + { return diffusionCoefficient_[phaseIdx][compIdx]; } + + /*! + * \brief Returns the tortuousity of the sub-domain of a fluid + * phase in the porous medium. + */ + Evaluation tortuosity(unsigned phaseIdx) const + { return tortuosity_[phaseIdx]; } + + /*! + * \brief Returns the effective molecular diffusion coefficient of + * the porous medium for a component in a phase. + */ + Evaluation effectiveDiffusionCoefficient(unsigned phaseIdx, unsigned compIdx) const + { + // For the blackoil model tortuosity is disabled. + // TODO add a run-time parameter to enable tortuosity + static bool enableTortuosity = false; + if (enableTortuosity) + return tortuosity_[phaseIdx] * diffusionCoefficient_[phaseIdx][compIdx]; + + return diffusionCoefficient_[phaseIdx][compIdx]; + } + +protected: + /*! + * \brief Update the quantities required to calculate diffusive + * mass fluxes. + */ + template + void update_(FluidState& fluidState, + typename FluidSystem::template ParameterCache& paramCache, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + // Only work if diffusion is enabled run-time by DIFFUSE in the deck + if(!FluidSystem::enableDiffusion()) + return; + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + update_(fluidState, paramCache, intQuants); + } + + template + void update_(FluidState& fluidState, + typename FluidSystem::template ParameterCache& paramCache, + const IntensiveQuantities& intQuants) { + using Toolbox = MathToolbox; + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + + // no diffusion in water for blackoil models + if (!FluidSystem::enableDissolvedGasInWater() && FluidSystem::waterPhaseIdx == phaseIdx) { + continue; + } + + // Based on Millington, R. J., & Quirk, J. P. (1961). + // \Note: it is possible to use NumericalConstants later + // constexpr auto& numconst = GetPropValue; + constexpr double myeps = 0.0001; //numconst.blackoildiffusionmoduleeps; + const Evaluation& base = + Toolbox::max(myeps, //0.0001, + intQuants.porosity() + * intQuants.fluidState().saturation(phaseIdx)); + tortuosity_[phaseIdx] = + 1.0 / (intQuants.porosity() * intQuants.porosity()) + * Toolbox::pow(base, 10.0/3.0); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + diffusionCoefficient_[phaseIdx][compIdx] = + FluidSystem::diffusionCoefficient(fluidState, + paramCache, + phaseIdx, + compIdx); + } + } + } + +private: + Evaluation tortuosity_[numPhases]; + Evaluation diffusionCoefficient_[numPhases][numComponents]; +}; + +/*! + * \ingroup Diffusion + * \class Opm::BlackOilDiffusionExtensiveQuantities + * + * \brief Provides the quantities required to calculate diffusive mass fluxes. + */ +template +class BlackOilDiffusionExtensiveQuantities; + +/*! + * \copydoc Opm::DiffusionExtensiveQuantities + */ +template +class BlackOilDiffusionExtensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + +protected: + /*! + * \brief Update the quantities required to calculate + * the diffusive mass fluxes. + */ + void update_(const ElementContext&, + unsigned, + unsigned) + {} + + template + void updateBoundary_(const Context&, + unsigned, + unsigned, + const FluidState&) + {} + +public: + /*! + * \brief The diffusivity the face. + * + */ + const Scalar& diffusivity() const + { + throw std::logic_error("The method diffusivity() does not " + "make sense if diffusion is disabled."); + } + + /*! + * \brief The effective diffusion coeffcient of a component in a + * fluid phase at the face's integration point + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Evaluation& effectiveDiffusionCoefficient(unsigned, + unsigned) const + { + throw std::logic_error("The method effectiveDiffusionCoefficient() " + "does not make sense if diffusion is disabled."); + } + +}; + +/*! + * \copydoc Opm::BlackOilDiffusionExtensiveQuantities + */ +template +class BlackOilDiffusionExtensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using GridView = GetPropType; + using FluidSystem = GetPropType; + using Toolbox = MathToolbox; + using IntensiveQuantities = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + using DimVector = Dune::FieldVector; + using DimEvalVector = Dune::FieldVector; +public: + using EvaluationArray = Evaluation[numPhases][numComponents]; +protected: + /*! + * \brief Update the quantities required to calculate + * the diffusive mass fluxes. + */ + void update_(const ElementContext& elemCtx, unsigned faceIdx, unsigned timeIdx) + { + // Only work if diffusion is enabled run-time by DIFFUSE in the deck + if(!FluidSystem::enableDiffusion()) + return; + + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& face = stencil.interiorFace(faceIdx); + const auto& extQuants = elemCtx.extensiveQuantities(faceIdx, timeIdx); + const auto& intQuantsInside = elemCtx.intensiveQuantities(extQuants.interiorIndex(), timeIdx); + const auto& intQuantsOutside = elemCtx.intensiveQuantities(extQuants.exteriorIndex(), timeIdx); + + const Scalar diffusivity = elemCtx.problem().diffusivity(elemCtx, face.interiorIndex(), face.exteriorIndex()); + const Scalar faceArea = face.area(); + diffusivity_ = diffusivity / faceArea; + update(effectiveDiffusionCoefficient_, intQuantsInside, intQuantsOutside); + Valgrind::CheckDefined(diffusivity_); + } + +public: + static void update(EvaluationArray& effectiveDiffusionCoefficient, + const IntensiveQuantities& intQuantsInside, + const IntensiveQuantities& intQuantsOutside) { + // opm-models expects per area flux + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + // no diffusion in water for blackoil models + if (!FluidSystem::enableDissolvedGasInWater() && FluidSystem::waterPhaseIdx == phaseIdx) { + continue; + } + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + // use the arithmetic average for the effective + // diffusion coefficients. + effectiveDiffusionCoefficient[phaseIdx][compIdx] = 0.5 * + ( intQuantsInside.effectiveDiffusionCoefficient(phaseIdx, compIdx) + + intQuantsOutside.effectiveDiffusionCoefficient(phaseIdx, compIdx) ); + Valgrind::CheckDefined(effectiveDiffusionCoefficient[phaseIdx][compIdx]); + } + } + } +protected: + template + void updateBoundary_(const Context&, + unsigned, + unsigned, + const FluidState&) + { + throw std::runtime_error("Not implemented: Diffusion across boundary not implemented for blackoil"); + } + +public: + /*! + * \brief The diffusivity of the face. + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Scalar& diffusivity() const + { return diffusivity_; } + + /*! + * \brief The effective diffusion coeffcient of a component in a + * fluid phase at the face's integration point + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Evaluation& effectiveDiffusionCoefficient(unsigned phaseIdx, unsigned compIdx) const + { return effectiveDiffusionCoefficient_[phaseIdx][compIdx]; } + + const auto& effectiveDiffusionCoefficient() const{ + return effectiveDiffusionCoefficient_; + } + +private: + Scalar diffusivity_; + EvaluationArray effectiveDiffusionCoefficient_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoildispersionmodule.hh b/opm/models/blackoil/blackoildispersionmodule.hh new file mode 100644 index 00000000000..4bc5d30fef3 --- /dev/null +++ b/opm/models/blackoil/blackoildispersionmodule.hh @@ -0,0 +1,549 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Classes required for mechanical dispersion. + */ +#ifndef EWOMS_DISPERSION_MODULE_HH +#define EWOMS_DISPERSION_MODULE_HH + +#include + +#include + +#include + +#include + +namespace Opm { + +/*! + * \ingroup Dispersion + * \class Opm::BlackOilDispersionModule + * \brief Provides the auxiliary methods required for consideration of the + * dispersion equation. + */ +template +class BlackOilDispersionModule; + +template +class BlackOilDispersionExtensiveQuantities; + +/*! + * \copydoc Opm::BlackOilDispersionModule + */ +template +class BlackOilDispersionModule +{ + using Scalar = GetPropType; + using RateVector = GetPropType; + using FluidSystem = GetPropType; + using Evaluation = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + +public: + using ExtensiveQuantities = BlackOilDispersionExtensiveQuantities; + +#if HAVE_ECL_INPUT + static void initFromState(const EclipseState&) + { + } +#endif + + /*! + * \brief Adds the dispersive flux to the flux vector over a flux + * integration point. + */ + template + static void addDispersiveFlux(RateVector&, + const Context&, + unsigned, + unsigned) + {} + + template + static void addDispersiveFlux(RateVector&, + const FluidState&, + const FluidState&, + const Evaluation&, + const Scalar&) + {} +}; + +/*! + * \copydoc Opm::BlackOilDispersionModule + */ +template +class BlackOilDispersionModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Model = GetPropType; + using Simulator = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { enableDispersion = getPropValue() }; + + using Toolbox = MathToolbox; + +public: + using ExtensiveQuantities = BlackOilDispersionExtensiveQuantities; +#if HAVE_ECL_INPUT + static void initFromState(const EclipseState& eclState) + { + if (!eclState.getSimulationConfig().rock_config().dispersion()) { + return; + } + + if (eclState.getSimulationConfig().hasVAPWAT() || eclState.getSimulationConfig().hasVAPOIL()) { + OpmLog::warning("Dispersion is activated in combination with VAPWAT/VAPOIL. \n" + "Water/oil is still allowed to vaporize, but dispersion in the " + "gas phase is ignored."); + } + } +#endif + /*! + * \brief Adds the mass flux due to dispersion to the flux vector over the + * flux integration point. + */ + template + static void addDispersiveFlux(RateVector& flux, const Context& context, + unsigned spaceIdx, unsigned timeIdx) + { + // Only work if dispersion is enabled by DISPERC in the deck + if (!context.simulator().vanguard().eclState().getSimulationConfig().rock_config().dispersion()) { + return; + } + const auto& extQuants = context.extensiveQuantities(spaceIdx, timeIdx); + const auto& fluidStateI = context.intensiveQuantities(extQuants.interiorIndex(), timeIdx).fluidState(); + const auto& fluidStateJ = context.intensiveQuantities(extQuants.exteriorIndex(), timeIdx).fluidState(); + const auto& dispersivity = extQuants.dispersivity(); + const auto& normVelocityAvg = extQuants.normVelocityAvg(); + addDispersiveFlux(flux, fluidStateI, fluidStateJ, dispersivity, normVelocityAvg); + } + + /*! + * \brief Adds the mass flux due to dispersion to the flux vector over the + * integration point. Following the notation in blackoilmodel.hh, + * the dispersive flux for component \f$\kappa\f$ in phase \f$\alpha\f$ + * is given by: \f$-b_\alpha E||\mathrm{v}_\alpha||\mathbf{grad}X_\alpha^\kappa\f$, + * where \f$b_\alpha\f$ is the shrinkage/expansion factor [-], E is the isotropic + * dispersivity coefficient [L], \f$\mathrm{v}_\alpha\f$ is the filter velocity + * [L/T], and \f$X_\alpha^\kappa\f$ the component mass fraction [-]. Each component mass + * fraction can be computed using \f$R_s,\;R_v,\;R_{sw},\;R_{vw}\f$. For example, + * \f$X_w^G=\frac{R_{sw}}{R_{sw}+\rho_w/\rho_g}\f$, where \f$\rho_w\f$ and \f$\rho_g\f$ + * are the reference densities. + * Following the implementation of the diffusive flux (blackoildiffusionmodule.hh) and considering + * the case for the water phase and gas component as an example, for cells i and j, the discrete version + * of the dispersive flux at the face's integration point is given by + * \f$-b_{w,ij}v_{w,ij}(\frac{1}{R_{sw,ij}+\rho_w/\rho_g})D_{ij}(R_{sw,i}-R_{sw,j})\f$ + * where \f$b_{w,ij}\f$, \f$v_{w,ij}\f$, and \f$R_{sw,ij}\f$ are computed using the arithmetic mean, and + * the ratio \f$\frac{1}{R_{sw,ij}+\rho_w/\rho_g}\f$ is denoted as conversion factor. The dispersivity + * \f$D_{ij}\f$ is computed in ecltransmissibility_impl.hh, using the dispersion coefficients \f$E_i\f$ + * and \f$E_j\f$. + */ + template + static void addDispersiveFlux(RateVector& flux, + const FluidState& fluidStateI, + const FluidState& fluidStateJ, + const Evaluation& dispersivity, + const Scalar& normVelocityAvg) + { + unsigned pvtRegionIndex = fluidStateI.pvtRegionIndex(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + + // no dispersion in water for blackoil models unless water can contain dissolved gas + if (!FluidSystem::enableDissolvedGasInWater() && FluidSystem::waterPhaseIdx == phaseIdx) { + continue; + } + + // Adding dispersion in the gas phase leads to + // convergence issues and unphysical results. + // We disable dispersion in the gas phase for now + if (FluidSystem::gasPhaseIdx == phaseIdx) { + continue; + } + + // no dispersion in gas for blackoil models unless gas can contain evaporated water or oil + if ((!FluidSystem::enableVaporizedWater() && !FluidSystem::enableVaporizedOil()) && FluidSystem::gasPhaseIdx == phaseIdx) { + continue; + } + + // arithmetic mean of the phase's b factor + Evaluation bAvg = fluidStateI.invB(phaseIdx); + bAvg += Toolbox::value(fluidStateJ.invB(phaseIdx)); + bAvg /= 2; + + Evaluation convFactor = 1.0; + Evaluation diffR = 0.0; + if (FluidSystem::enableDissolvedGas() && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) && phaseIdx == FluidSystem::oilPhaseIdx) { + Evaluation rsAvg = (fluidStateI.Rs() + Toolbox::value(fluidStateJ.Rs())) / 2; + convFactor = 1.0 / (toMassFractionGasOil(pvtRegionIndex) + rsAvg); + diffR = fluidStateI.Rs() - Toolbox::value(fluidStateJ.Rs()); + } + if (FluidSystem::enableVaporizedOil() && FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && phaseIdx == FluidSystem::gasPhaseIdx) { + Evaluation rvAvg = (fluidStateI.Rv() + Toolbox::value(fluidStateJ.Rv())) / 2; + convFactor = toMassFractionGasOil(pvtRegionIndex) / (1.0 + rvAvg*toMassFractionGasOil(pvtRegionIndex)); + diffR = fluidStateI.Rv() - Toolbox::value(fluidStateJ.Rv()); + } + if (FluidSystem::enableDissolvedGasInWater() && phaseIdx == FluidSystem::waterPhaseIdx) { + Evaluation rsAvg = (fluidStateI.Rsw() + Toolbox::value(fluidStateJ.Rsw())) / 2; + convFactor = 1.0 / (toMassFractionGasWater(pvtRegionIndex) + rsAvg); + diffR = fluidStateI.Rsw() - Toolbox::value(fluidStateJ.Rsw()); + } + if (FluidSystem::enableVaporizedWater() && phaseIdx == FluidSystem::gasPhaseIdx) { + Evaluation rvAvg = (fluidStateI.Rvw() + Toolbox::value(fluidStateJ.Rvw())) / 2; + convFactor = toMassFractionGasWater(pvtRegionIndex)/ (1.0 + rvAvg*toMassFractionGasWater(pvtRegionIndex)); + diffR = fluidStateI.Rvw() - Toolbox::value(fluidStateJ.Rvw()); + } + + // mass flux of solvent component + unsigned solventCompIdx = FluidSystem::solventComponentIndex(phaseIdx); + unsigned activeSolventCompIdx = Indices::canonicalToActiveComponentIndex(solventCompIdx); + flux[conti0EqIdx + activeSolventCompIdx] += + - bAvg + * normVelocityAvg[phaseIdx] + * convFactor + * dispersivity + * diffR; + + // mass flux of solute component + unsigned soluteCompIdx = FluidSystem::soluteComponentIndex(phaseIdx); + unsigned activeSoluteCompIdx = Indices::canonicalToActiveComponentIndex(soluteCompIdx); + flux[conti0EqIdx + activeSoluteCompIdx] += + bAvg + * normVelocityAvg[phaseIdx] + * convFactor + * dispersivity + * diffR; + } + } + +private: + + static Scalar toMassFractionGasOil (unsigned regionIdx) { + Scalar rhoO = FluidSystem::referenceDensity(FluidSystem::oilPhaseIdx, regionIdx); + Scalar rhoG = FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, regionIdx); + return rhoO / rhoG; + } + static Scalar toMassFractionGasWater (unsigned regionIdx) { + Scalar rhoW = FluidSystem::referenceDensity(FluidSystem::waterPhaseIdx, regionIdx); + Scalar rhoG = FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, regionIdx); + return rhoW / rhoG; + } +}; + +/*! + * \ingroup Dispersion + * \class Opm::BlackOilDispersionIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the + * calculation of dispersive fluxes. + */ +template +class BlackOilDispersionIntensiveQuantities; + +/*! + * \copydoc Opm::DispersionIntensiveQuantities + */ +template +class BlackOilDispersionIntensiveQuantities +{ + using Scalar = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + +public: + /*! + * \brief Returns the max. norm of the filter velocity of the cell. + */ + Scalar normVelocityCell(unsigned, unsigned) const + { + throw std::logic_error("Method normVelocityCell() " + "does not make sense if dispersion is disabled"); + } + +protected: + /*! + * \brief Update the quantities required to calculate dispersive + * fluxes. + */ + template + void update_(ElementContext&, + unsigned, + unsigned) + { } +}; + +/*! + * \copydoc Opm::DispersionIntensiveQuantities + */ +template +class BlackOilDispersionIntensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using IntensiveQuantities = GetPropType; + using Indices = GetPropType; + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + enum { gasCompIdx = FluidSystem::gasCompIdx }; + enum { oilCompIdx = FluidSystem::oilCompIdx }; + enum { waterCompIdx = FluidSystem::waterCompIdx }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { enableDispersion = getPropValue() }; + +public: + /*! + * \brief Returns the max. norm of the filter velocity of the cell. + */ + Scalar normVelocityCell(unsigned phaseIdx) const + { + + return normVelocityCell_[phaseIdx]; + } + +protected: + /*! + * \brief Update the quantities required to calculate dispersive + * mass fluxes. This considers the linear disperison model + * described in the SPE CSP11 benchmark document (eq. 2.3) + * https://github.com/Simulation-Benchmarks/11thSPE-CSP/ + * blob/main/description/spe_csp11_description.pdf + * The maximum norm is used to compute the cell + * filter velocity value of the corresponding phase. + */ + template + void update_(const ElementContext& elemCtx, unsigned dofIdx, unsigned timeIdx) + { + // Only work if dispersion is enabled by DISPERC in the deck + if (!elemCtx.simulator().vanguard().eclState().getSimulationConfig().rock_config().dispersion()) { + return; + } + const auto& problem = elemCtx.simulator().problem(); + if (problem.model().linearizer().getVelocityInfo().empty()) { + return; + } + const std::array phaseIdxs = { gasPhaseIdx, oilPhaseIdx, waterPhaseIdx }; + const std::array compIdxs = { gasCompIdx, oilCompIdx, waterCompIdx }; + const auto& velocityInf = problem.model().linearizer().getVelocityInfo(); + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, timeIdx); + auto velocityInfos = velocityInf[globalDofIdx]; + for (unsigned i = 0; i < phaseIdxs.size(); ++i) { + normVelocityCell_[i] = 0; + } + for (auto& velocityInfo : velocityInfos) { + for (unsigned i = 0; i < phaseIdxs.size(); ++i) { + if (FluidSystem::phaseIsActive(phaseIdxs[i])) { + normVelocityCell_[phaseIdxs[i]] = max( normVelocityCell_[phaseIdxs[i]], + std::abs( velocityInfo.velocity[conti0EqIdx + + Indices::canonicalToActiveComponentIndex(compIdxs[i])] )); + } + } + } + } + +private: + Scalar normVelocityCell_[numPhases]; +}; + +/*! + * \ingroup Dispersion + * \class Opm::BlackOilDispersionExtensiveQuantities + * + * \brief Provides the quantities required to calculate dispersive mass fluxes. + */ +template +class BlackOilDispersionExtensiveQuantities; + +/*! + * \copydoc Opm::DispersionExtensiveQuantities + */ +template +class BlackOilDispersionExtensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using IntensiveQuantities = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + +protected: + /*! + * \brief Update the quantities required to calculate + * the dispersive fluxes. + */ + void update_(const ElementContext&, + unsigned, + unsigned) + {} + + template + void updateBoundary_(const Context&, + unsigned, + unsigned, + const FluidState&) + {} + +public: + using ScalarArray = Scalar[numPhases]; + + static void update(ScalarArray&, + const IntensiveQuantities&, + const IntensiveQuantities&) + {} + + /*! + * \brief The dispersivity the face. + * + */ + const Scalar& dispersivity() const + { + throw std::logic_error("The method dispersivity() does not " + "make sense if dispersion is disabled."); + } + + /*! + * \brief The effective filter velocity coefficient in a + * fluid phase at the face's integration point + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Scalar& normVelocityAvg(unsigned) const + { + throw std::logic_error("The method normVelocityAvg() " + "does not make sense if dispersion is disabled."); + } + +}; + +/*! + * \copydoc Opm::BlackOilDispersionExtensiveQuantities + */ +template +class BlackOilDispersionExtensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using GridView = GetPropType; + using FluidSystem = GetPropType; + using Toolbox = MathToolbox; + using IntensiveQuantities = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + using DimVector = Dune::FieldVector; + using DimEvalVector = Dune::FieldVector; +public: + using ScalarArray = Scalar[numPhases]; + static void update(ScalarArray& normVelocityAvg, + const IntensiveQuantities& intQuantsInside, + const IntensiveQuantities& intQuantsOutside) + { + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + // no dispersion in water for blackoil models unless water can contain dissolved gas + if (!FluidSystem::enableDissolvedGasInWater() && FluidSystem::waterPhaseIdx == phaseIdx) { + continue; + } + // no dispersion in gas for blackoil models unless gas can contain evaporated water or oil + if ((!FluidSystem::enableVaporizedWater() && !FluidSystem::enableVaporizedOil()) && FluidSystem::gasPhaseIdx == phaseIdx) { + continue; + } + // use the arithmetic average for the effective + // velocity coefficients at the face's integration point. + normVelocityAvg[phaseIdx] = 0.5 * + ( intQuantsInside.normVelocityCell(phaseIdx) + + intQuantsOutside.normVelocityCell(phaseIdx) ); + Valgrind::CheckDefined(normVelocityAvg[phaseIdx]); + } + } +protected: + template + void updateBoundary_(const Context&, + unsigned, + unsigned, + const FluidState&) + { + throw std::runtime_error("Not implemented: Dispersion across boundary not implemented for blackoil"); + } + +public: + /*! + * \brief The dispersivity of the face. + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Scalar& dispersivity() const + { return dispersivity_; } + + /*! + * \brief The effective velocity coefficient in a + * fluid phase at the face's integration point + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Scalar& normVelocityAvg(unsigned phaseIdx) const + { return normVelocityAvg_[phaseIdx]; } + + const auto& normVelocityAvg() const{ + return normVelocityAvg_; + } + +private: + Scalar dispersivity_; + ScalarArray normVelocityAvg_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilenergymodules.hh b/opm/models/blackoil/blackoilenergymodules.hh new file mode 100644 index 00000000000..9e83fc744f1 --- /dev/null +++ b/opm/models/blackoil/blackoilenergymodules.hh @@ -0,0 +1,727 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the classes required to extend the black-oil model by energy. + */ +#ifndef EWOMS_BLACK_OIL_ENERGY_MODULE_HH +#define EWOMS_BLACK_OIL_ENERGY_MODULE_HH + +#include "blackoilproperties.hh" +#include +#include +#include + +#include + +#include + +#include + +#include + +namespace Opm { +/*! + * \ingroup BlackOil + * \brief Contains the high level supplements required to extend the black oil + * model by energy. + */ +template ()> +class BlackOilEnergyModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using Model = GetPropType; + using Simulator = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + + static constexpr unsigned temperatureIdx = Indices::temperatureIdx; + static constexpr unsigned contiEnergyEqIdx = Indices::contiEnergyEqIdx; + + static constexpr unsigned enableEnergy = enableEnergyV; + static constexpr unsigned numEq = getPropValue(); + static constexpr unsigned numPhases = FluidSystem::numPhases; + +public: + using ExtensiveQuantities = GetPropType; + /*! + * \brief Register all run-time parameters for the black-oil energy module. + */ + static void registerParameters() + { + if constexpr (enableEnergy) + VtkBlackOilEnergyModule::registerParameters(); + } + + /*! + * \brief Register all energy specific VTK and ECL output modules. + */ + static void registerOutputModules(Model& model, + Simulator& simulator) + { + if constexpr (enableEnergy) + model.addOutputModule(new VtkBlackOilEnergyModule(simulator)); + } + + static bool primaryVarApplies(unsigned pvIdx) + { + if constexpr (enableEnergy) + return pvIdx == temperatureIdx; + else + return false; + + } + + static std::string primaryVarName([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + return "temperature"; + } + + static Scalar primaryVarWeight([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + static bool eqApplies(unsigned eqIdx) + { + if constexpr (enableEnergy) + return eqIdx == contiEnergyEqIdx; + else + return false; + } + + static std::string eqName([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + return "conti^energy"; + } + + static Scalar eqWeight([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + return 1.0; + } + + // must be called after water storage is computed + template + static void addStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants) + { + if constexpr (enableEnergy) { + const auto& poro = decay(intQuants.porosity()); + + // accumulate the internal energy of the fluids + const auto& fs = intQuants.fluidState(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + const auto& u = decay(fs.internalEnergy(phaseIdx)); + const auto& S = decay(fs.saturation(phaseIdx)); + const auto& rho = decay(fs.density(phaseIdx)); + + storage[contiEnergyEqIdx] += poro*S*u*rho; + } + + // add the internal energy of the rock + Scalar rockFraction = intQuants.rockFraction(); + const auto& uRock = decay(intQuants.rockInternalEnergy()); + storage[contiEnergyEqIdx] += rockFraction*uRock; + storage[contiEnergyEqIdx] *= getPropValue(); + } + } + + static void computeFlux([[maybe_unused]] RateVector& flux, + [[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned scvfIdx, + [[maybe_unused]] unsigned timeIdx) + { + if constexpr (enableEnergy) { + flux[contiEnergyEqIdx] = 0.0; + + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + unsigned focusIdx = elemCtx.focusDofIndex(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + unsigned upIdx = extQuants.upstreamIndex(phaseIdx); + if (upIdx == focusIdx) + addPhaseEnthalpyFlux_(flux, phaseIdx, elemCtx, scvfIdx, timeIdx); + else + addPhaseEnthalpyFlux_(flux, phaseIdx, elemCtx, scvfIdx, timeIdx); + } + + // diffusive energy flux + flux[contiEnergyEqIdx] += extQuants.energyFlux(); + flux[contiEnergyEqIdx] *= getPropValue(); + } + } + + static void addHeatFlux(RateVector& flux, + const Evaluation& heatFlux) + { + if constexpr (enableEnergy) { + // diffusive energy flux + flux[contiEnergyEqIdx] += heatFlux; + flux[contiEnergyEqIdx] *= getPropValue(); + } + } + + + + template + static void addPhaseEnthalpyFluxes_(RateVector& flux, + unsigned phaseIdx, + const Eval& volumeFlux, + const FluidState& upFs) + { + flux[contiEnergyEqIdx] += + decay(upFs.enthalpy(phaseIdx)) + * decay(upFs.density(phaseIdx)) + * volumeFlux; + } + + template + static void addPhaseEnthalpyFlux_(RateVector& flux, + unsigned phaseIdx, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) + { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + unsigned upIdx = extQuants.upstreamIndex(phaseIdx); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + const auto& fs = up.fluidState(); + const auto& volFlux = extQuants.volumeFlux(phaseIdx); + addPhaseEnthalpyFluxes_(flux, + phaseIdx, + volFlux, + fs); + } + + static void addToEnthalpyRate(RateVector& flux, + const Evaluation& hRate) + { + if constexpr (enableEnergy) + flux[contiEnergyEqIdx] += hRate; + } + + /*! + * \brief Assign the energy specific primary variables to a PrimaryVariables object + */ + static void assignPrimaryVars(PrimaryVariables& priVars, + Scalar) + { + if constexpr (enableEnergy) + priVars[temperatureIdx] = temperatureIdx; + } + + /*! + * \brief Assign the energy specific primary variables to a PrimaryVariables object + */ + template + static void assignPrimaryVars(PrimaryVariables& priVars, + const FluidState& fluidState) + { + if constexpr (enableEnergy) + priVars[temperatureIdx] = fluidState.temperature(/*phaseIdx=*/0); + } + + /*! + * \brief Do a Newton-Raphson update the primary variables of the energys. + */ + static void updatePrimaryVars(PrimaryVariables& newPv, + const PrimaryVariables& oldPv, + const EqVector& delta) + { + if constexpr (enableEnergy) + // do a plain unchopped Newton update + newPv[temperatureIdx] = oldPv[temperatureIdx] - delta[temperatureIdx]; + } + + /*! + * \brief Return how much a Newton-Raphson update is considered an error + */ + static Scalar computeUpdateError(const PrimaryVariables&, + const EqVector&) + { + // do not consider consider the cange of energy primary variables for + // convergence + // TODO: maybe this should be changed + return static_cast(0.0); + } + + /*! + * \brief Return how much a residual is considered an error + */ + static Scalar computeResidualError(const EqVector& resid) + { + // do not weight the residual of energy when it comes to convergence + return std::abs(scalarValue(resid[contiEnergyEqIdx])); + } + + template + static void serializeEntity(const Model& model, std::ostream& outstream, const DofEntity& dof) + { + if constexpr (enableEnergy) { + unsigned dofIdx = model.dofMapper().index(dof); + const PrimaryVariables& priVars = model.solution(/*timeIdx=*/0)[dofIdx]; + outstream << priVars[temperatureIdx]; + } + } + + template + static void deserializeEntity(Model& model, std::istream& instream, const DofEntity& dof) + { + if constexpr (enableEnergy) { + unsigned dofIdx = model.dofMapper().index(dof); + PrimaryVariables& priVars0 = model.solution(/*timeIdx=*/0)[dofIdx]; + PrimaryVariables& priVars1 = model.solution(/*timeIdx=*/1)[dofIdx]; + + instream >> priVars0[temperatureIdx]; + + // set the primary variables for the beginning of the current time step. + priVars1 = priVars0[temperatureIdx]; + } + } +}; + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilEnergyIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the equations needed by the + * energys extension of the black-oil model. + */ +template ()> +class BlackOilEnergyIntensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using FluidSystem = GetPropType; + using SolidEnergyLaw = GetPropType; + using ThermalConductionLaw = GetPropType; + using Indices = GetPropType; + using ElementContext = GetPropType; + using Problem = GetPropType; + + using EnergyModule = BlackOilEnergyModule; + + enum { numPhases = getPropValue() }; + static constexpr int temperatureIdx = Indices::temperatureIdx; + static constexpr int waterPhaseIdx = FluidSystem::waterPhaseIdx; + + +public: + /*! + * \brief Update the temperature of the intensive quantity's fluid state + * + */ + void updateTemperature_(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + auto& fs = asImp_().fluidState_; + const auto& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + + // set temperature + fs.setTemperature(priVars.makeEvaluation(temperatureIdx, timeIdx, elemCtx.linearizationType())); + } + + /*! + * \brief Update the temperature of the intensive quantity's fluid state + * + */ + void updateTemperature_([[maybe_unused]] const Problem& problem, + const PrimaryVariables& priVars, + [[maybe_unused]] unsigned globalDofIdx, + const unsigned timeIdx, + const LinearizationType& lintype) + { + auto& fs = asImp_().fluidState_; + fs.setTemperature(priVars.makeEvaluation(temperatureIdx, timeIdx, lintype)); + } + + /*! + * \brief Compute the intensive quantities needed to handle energy conservation + * + */ + void updateEnergyQuantities_(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx, + const typename FluidSystem::template ParameterCache& paramCache) + { + auto& fs = asImp_().fluidState_; + + // compute the specific enthalpy of the fluids, the specific enthalpy of the rock + // and the thermal condictivity coefficients + for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + + const auto& h = FluidSystem::enthalpy(fs, paramCache, phaseIdx); + fs.setEnthalpy(phaseIdx, h); + } + + const auto& solidEnergyLawParams = elemCtx.problem().solidEnergyLawParams(elemCtx, dofIdx, timeIdx); + rockInternalEnergy_ = SolidEnergyLaw::solidInternalEnergy(solidEnergyLawParams, fs); + + const auto& thermalConductionLawParams = elemCtx.problem().thermalConductionLawParams(elemCtx, dofIdx, timeIdx); + totalThermalConductivity_ = ThermalConductionLaw::thermalConductivity(thermalConductionLawParams, fs); + + // Retrieve the rock fraction from the problem + // Usually 1 - porosity, but if pvmult is used to modify porosity + // we will apply the same multiplier to the rock fraction + // i.e. pvmult*(1 - porosity) and thus interpret multpv as a volume + // multiplier. This is to avoid negative rock volume for pvmult*porosity > 1 + const unsigned cell_idx = elemCtx.globalSpaceIndex(dofIdx, timeIdx); + rockFraction_ = elemCtx.problem().rockFraction(cell_idx, timeIdx); + } + + const Evaluation& rockInternalEnergy() const + { return rockInternalEnergy_; } + + const Evaluation& totalThermalConductivity() const + { return totalThermalConductivity_; } + + const Scalar& rockFraction() const + { return rockFraction_; } + +protected: + Implementation& asImp_() + { return *static_cast(this); } + + Evaluation rockInternalEnergy_; + Evaluation totalThermalConductivity_; + Scalar rockFraction_; +}; + +template +class BlackOilEnergyIntensiveQuantities +{ + using Implementation = GetPropType; + using PrimaryVariables = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Scalar = GetPropType; + + using Problem = GetPropType; + static constexpr bool enableTemperature = getPropValue(); + +public: + void updateTemperature_([[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned dofIdx, + [[maybe_unused]] unsigned timeIdx) + { + if constexpr (enableTemperature) { + // even if energy is conserved, the temperature can vary over the spatial + // domain if the EnableTemperature property is set to true + auto& fs = asImp_().fluidState_; + const Scalar T = elemCtx.problem().temperature(elemCtx, dofIdx, timeIdx); + fs.setTemperature(T); + } + } + + template + void updateTemperature_([[maybe_unused]] const Problem& problem, + [[maybe_unused]] const PrimaryVariables& priVars, + [[maybe_unused]] unsigned globalDofIdx, + [[maybe_unused]] unsigned timeIdx, + [[maybe_unused]] const LinearizationType& lintype + ) + { + if constexpr (enableTemperature) { + auto& fs = asImp_().fluidState_; + // even if energy is conserved, the temperature can vary over the spatial + // domain if the EnableTemperature property is set to true + const Scalar T = problem.temperature(globalDofIdx, timeIdx); + fs.setTemperature(T); + } + } + + void updateEnergyQuantities_(const ElementContext&, + unsigned, + unsigned, + const typename FluidSystem::template ParameterCache&) + { } + + const Evaluation& rockInternalEnergy() const + { throw std::logic_error("Requested the rock internal energy, which is " + "unavailable because energy is not conserved"); } + + const Evaluation& totalThermalConductivity() const + { throw std::logic_error("Requested the total thermal conductivity, which is " + "unavailable because energy is not conserved"); } + +protected: + Implementation& asImp_() + { return *static_cast(this); } +}; + + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilEnergyExtensiveQuantities + * + * \brief Provides the energy specific extensive quantities to the generic black-oil + * module's extensive quantities. + */ +template ()> +class BlackOilEnergyExtensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using GridView = GetPropType; + + using Toolbox = MathToolbox; + + using EnergyModule = BlackOilEnergyModule; + + static const int dimWorld = GridView::dimensionworld; + using DimVector = Dune::FieldVector; + using DimEvalVector = Dune::FieldVector; +public: + template + static void updateEnergy(Evaluation& energyFlux, + const unsigned& focusDofIndex, + const unsigned& inIdx, + const unsigned& exIdx, + const IntensiveQuantities& inIq, + const IntensiveQuantities& exIq, + const FluidState& inFs, + const FluidState& exFs, + const Scalar& inAlpha, + const Scalar& outAlpha, + const Scalar& faceArea) + { + Evaluation deltaT; + if (focusDofIndex == inIdx) + deltaT = + decay(exFs.temperature(/*phaseIdx=*/0)) + - inFs.temperature(/*phaseIdx=*/0); + else if (focusDofIndex == exIdx) + deltaT = + exFs.temperature(/*phaseIdx=*/0) + - decay(inFs.temperature(/*phaseIdx=*/0)); + else + deltaT = + decay(exFs.temperature(/*phaseIdx=*/0)) + - decay(inFs.temperature(/*phaseIdx=*/0)); + + Evaluation inLambda; + if (focusDofIndex == inIdx) + inLambda = inIq.totalThermalConductivity(); + else + inLambda = decay(inIq.totalThermalConductivity()); + + Evaluation exLambda; + if (focusDofIndex == exIdx) + exLambda = exIq.totalThermalConductivity(); + else + exLambda = decay(exIq.totalThermalConductivity()); + + Evaluation H; + const Evaluation& inH = inLambda*inAlpha; + const Evaluation& exH = exLambda*outAlpha; + if (inH > 0 && exH > 0) { + // compute the "thermal transmissibility". In contrast to the normal + // transmissibility this cannot be done as a preprocessing step because the + // average thermal conductivity is analogous to the permeability but + // depends on the solution. + H = 1.0/(1.0/inH + 1.0/exH); + } + else + H = 0.0; + + energyFlux = deltaT * (-H/faceArea); + } + + void updateEnergy(const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) + { + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& scvf = stencil.interiorFace(scvfIdx); + + const Scalar faceArea = scvf.area(); + const unsigned inIdx = scvf.interiorIndex(); + const unsigned exIdx = scvf.exteriorIndex(); + const auto& inIq = elemCtx.intensiveQuantities(inIdx, timeIdx); + const auto& exIq = elemCtx.intensiveQuantities(exIdx, timeIdx); + const auto& inFs = inIq.fluidState(); + const auto& exFs = exIq.fluidState(); + const Scalar inAlpha = elemCtx.problem().thermalHalfTransmissibilityIn(elemCtx, scvfIdx, timeIdx); + const Scalar outAlpha = elemCtx.problem().thermalHalfTransmissibilityOut(elemCtx, scvfIdx, timeIdx); + updateEnergy(energyFlux_, + elemCtx.focusDofIndex(), + inIdx, + exIdx, + inIq, + exIq, + inFs, + exFs, + inAlpha, + outAlpha, + faceArea); + } + + template + void updateEnergyBoundary(const Context& ctx, + unsigned scvfIdx, + unsigned timeIdx, + const BoundaryFluidState& boundaryFs) + { + const auto& stencil = ctx.stencil(timeIdx); + const auto& scvf = stencil.boundaryFace(scvfIdx); + + unsigned inIdx = scvf.interiorIndex(); + const auto& inIq = ctx.intensiveQuantities(inIdx, timeIdx); + const auto& focusDofIdx = ctx.focusDofIndex(); + const Scalar alpha = ctx.problem().thermalHalfTransmissibilityBoundary(ctx, scvfIdx); + updateEnergyBoundary(energyFlux_, inIq, focusDofIdx, inIdx, alpha, boundaryFs); + } + + template + static void updateEnergyBoundary(Evaluation& energyFlux, + const IntensiveQuantities& inIq, + unsigned focusDofIndex, + unsigned inIdx, + Scalar alpha, + const BoundaryFluidState& boundaryFs) + { + const auto& inFs = inIq.fluidState(); + Evaluation deltaT; + if (focusDofIndex == inIdx) + deltaT = + boundaryFs.temperature(/*phaseIdx=*/0) + - inFs.temperature(/*phaseIdx=*/0); + else + deltaT = + decay(boundaryFs.temperature(/*phaseIdx=*/0)) + - decay(inFs.temperature(/*phaseIdx=*/0)); + + Evaluation lambda; + if (focusDofIndex == inIdx) + lambda = inIq.totalThermalConductivity(); + else + lambda = decay(inIq.totalThermalConductivity()); + + + if (lambda > 0.0) { + // compute the "thermal transmissibility". In contrast to the normal + // transmissibility this cannot be done as a preprocessing step because the + // average thermal conductivity is analogous to the permeability but depends + // on the solution. + energyFlux = deltaT*lambda*(-alpha); + } + else + energyFlux = 0.0; + } + + const Evaluation& energyFlux() const + { return energyFlux_; } + +private: + Implementation& asImp_() + { return *static_cast(this); } + + Evaluation energyFlux_; +}; + +template +class BlackOilEnergyExtensiveQuantities +{ + using ElementContext = GetPropType; + using Evaluation = GetPropType; + using IntensiveQuantities = GetPropType; + using Scalar = GetPropType; +public: + template + static void updateEnergy(Evaluation& /*energyFlux*/, + const unsigned& /*focusDofIndex*/, + const unsigned& /*inIdx*/, + const unsigned& /*exIdx*/, + const IntensiveQuantities& /*inIq*/, + const IntensiveQuantities& /*exIq*/, + const FluidState& /*inFs*/, + const FluidState& /*exFs*/, + const Scalar& /*inAlpha*/, + const Scalar& /*outAlpha*/, + const Scalar& /*faceArea*/) + {} + + void updateEnergy(const ElementContext&, + unsigned, + unsigned) + {} + + template + void updateEnergyBoundary(const Context&, + unsigned, + unsigned, + const BoundaryFluidState&) + {} + + template + static void updateEnergyBoundary(Evaluation& /*heatFlux*/, + const IntensiveQuantities& /*inIq*/, + unsigned /*focusDofIndex*/, + unsigned /*inIdx*/, + unsigned /*timeIdx*/, + Scalar /*alpha*/, + const BoundaryFluidState& /*boundaryFs*/) + {} + const Evaluation& energyFlux() const + { throw std::logic_error("Requested the energy flux, but energy is not conserved"); } +}; + + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilextbomodules.hh b/opm/models/blackoil/blackoilextbomodules.hh new file mode 100644 index 00000000000..c8cc28dbfca --- /dev/null +++ b/opm/models/blackoil/blackoilextbomodules.hh @@ -0,0 +1,836 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the classes required to extend the black-oil model by solvent component. + * For details, refer: + * [*] T.H. Sandve, O. SƦvareid and I. Aavatsmark: ā€œImproved Extended Blackoil Formulation + * for CO2 EOR Simulations.ā€ in ECMOR XVII ā€“ The 17th European Conference on the + * Mathematics of Oil Recovery, September 2020. + */ +#ifndef EWOMS_BLACK_OIL_EXTBO_MODULE_HH +#define EWOMS_BLACK_OIL_EXTBO_MODULE_HH + +#include "blackoilproperties.hh" + +#include + +//#include //TODO: Missing ... + +#if HAVE_ECL_INPUT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include +#include +#include +#include + +namespace Opm { + +/*! + * \ingroup BlackOil + * \brief Contains the high level supplements required to extend the black oil + * model. + */ +template ()> +class BlackOilExtboModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Model = GetPropType; + using Simulator = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + + using Toolbox = MathToolbox; + + using TabulatedFunction = typename BlackOilExtboParams::TabulatedFunction; + using Tabulated2DFunction = typename BlackOilExtboParams::Tabulated2DFunction; + + static constexpr unsigned zFractionIdx = Indices::zFractionIdx; + static constexpr unsigned contiZfracEqIdx = Indices::contiZfracEqIdx; + static constexpr unsigned enableExtbo = enableExtboV; + static constexpr unsigned numEq = getPropValue(); + static constexpr unsigned numPhases = FluidSystem::numPhases; + static constexpr unsigned gasPhaseIdx = FluidSystem::gasPhaseIdx; + static constexpr unsigned oilPhaseIdx = FluidSystem::oilPhaseIdx; + static constexpr unsigned waterPhaseIdx = FluidSystem::waterPhaseIdx; + static constexpr bool blackoilConserveSurfaceVolume = getPropValue(); + +public: +#if HAVE_ECL_INPUT + /*! + * \brief Initialize all internal data structures needed by the solvent module + */ + static void initFromState(const EclipseState& eclState) + { + // some sanity checks: if extended BO is enabled, the PVTSOL keyword must be + // present, if extended BO is disabled the keyword must not be present. + if (enableExtbo && !eclState.runspec().phases().active(Phase::ZFRACTION)) + throw std::runtime_error("Extended black oil treatment requested at compile " + "time, but the deck does not contain the PVTSOL keyword"); + else if (!enableExtbo && eclState.runspec().phases().active(Phase::ZFRACTION)) + throw std::runtime_error("Extended black oil treatment disabled at compile time, but the deck " + "contains the PVTSOL keyword"); + + if (!eclState.runspec().phases().active(Phase::ZFRACTION)) + return; // solvent treatment is supposed to be disabled + + // pvt properties from kw PVTSOL: + + const auto& tableManager = eclState.getTableManager(); + const auto& pvtsolTables = tableManager.getPvtsolTables(); + + size_t numPvtRegions = pvtsolTables.size(); + + params_.BO_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + params_.BG_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + params_.RS_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + params_.RV_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + params_.X_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + params_.Y_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + params_.VISCO_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + params_.VISCG_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + + params_.PBUB_RS_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + params_.PBUB_RV_.resize(numPvtRegions, Tabulated2DFunction{Tabulated2DFunction::InterpolationPolicy::LeftExtreme}); + + params_.zLim_.resize(numPvtRegions); + + const bool extractCmpFromPvt = true; //: Default values used in [*] + params_.oilCmp_.resize(numPvtRegions); + params_.gasCmp_.resize(numPvtRegions); + + for (unsigned regionIdx = 0; regionIdx < numPvtRegions; ++ regionIdx) { + const auto& pvtsolTable = pvtsolTables[regionIdx]; + + const auto& saturatedTable = pvtsolTable.getSaturatedTable(); + assert(saturatedTable.numRows() > 1); + + std::vector oilCmp(saturatedTable.numRows(), -4.0e-9); //Default values used in [*] + std::vector gasCmp(saturatedTable.numRows(), -0.08); //-------------"------------- + params_.zLim_[regionIdx] = 0.7; //-------------"------------- + std::vector zArg(saturatedTable.numRows(), 0.0); + + for (unsigned outerIdx = 0; outerIdx < saturatedTable.numRows(); ++ outerIdx) { + Scalar ZCO2 = saturatedTable.get("ZCO2", outerIdx); + + zArg[outerIdx] = ZCO2; + + params_.BO_[regionIdx].appendXPos(ZCO2); + params_.BG_[regionIdx].appendXPos(ZCO2); + + params_.RS_[regionIdx].appendXPos(ZCO2); + params_.RV_[regionIdx].appendXPos(ZCO2); + + params_.X_[regionIdx].appendXPos(ZCO2); + params_.Y_[regionIdx].appendXPos(ZCO2); + + params_.VISCO_[regionIdx].appendXPos(ZCO2); + params_.VISCG_[regionIdx].appendXPos(ZCO2); + + params_.PBUB_RS_[regionIdx].appendXPos(ZCO2); + params_.PBUB_RV_[regionIdx].appendXPos(ZCO2); + + const auto& underSaturatedTable = pvtsolTable.getUnderSaturatedTable(outerIdx); + size_t numRows = underSaturatedTable.numRows(); + + Scalar bo0=0.0; + Scalar po0=0.0; + for (unsigned innerIdx = 0; innerIdx < numRows; ++ innerIdx) { + Scalar po = underSaturatedTable.get("P", innerIdx); + Scalar bo = underSaturatedTable.get("B_O", innerIdx); + Scalar bg = underSaturatedTable.get("B_G", innerIdx); + Scalar rs = underSaturatedTable.get("RS", innerIdx)+innerIdx*1.0e-10; + Scalar rv = underSaturatedTable.get("RV", innerIdx)+innerIdx*1.0e-10; + Scalar xv = underSaturatedTable.get("XVOL", innerIdx); + Scalar yv = underSaturatedTable.get("YVOL", innerIdx); + Scalar mo = underSaturatedTable.get("MU_O", innerIdx); + Scalar mg = underSaturatedTable.get("MU_G", innerIdx); + + if (bo0 > bo) { // This is undersaturated oil-phase for ZCO2 <= zLim ... + // Here we assume tabulated bo to decay beyond boiling point + if (extractCmpFromPvt) { + Scalar cmpFactor = (bo-bo0)/(po-po0); + oilCmp[outerIdx] = cmpFactor; + params_.zLim_[regionIdx] = ZCO2; + //std::cout << "### cmpFactorOil: " << cmpFactor << " zLim: " << zLim_[regionIdx] << std::endl; + } + break; + } else if (bo0 == bo) { // This is undersaturated gas-phase for ZCO2 > zLim ... + // Here we assume tabulated bo to be constant extrapolated beyond dew point + if (innerIdx+1 < numRows && ZCO2<1.0 && extractCmpFromPvt) { + Scalar rvNxt = underSaturatedTable.get("RV", innerIdx+1)+innerIdx*1.0e-10; + Scalar bgNxt = underSaturatedTable.get("B_G", innerIdx+1); + Scalar cmpFactor = (bgNxt-bg)/(rvNxt-rv); + gasCmp[outerIdx] = cmpFactor; + //std::cout << "### cmpFactorGas: " << cmpFactor << " zLim: " << zLim_[regionIdx] << std::endl; + } + + params_.BO_[regionIdx].appendSamplePoint(outerIdx,po,bo); + params_.BG_[regionIdx].appendSamplePoint(outerIdx,po,bg); + params_.RS_[regionIdx].appendSamplePoint(outerIdx,po,rs); + params_.RV_[regionIdx].appendSamplePoint(outerIdx,po,rv); + params_.X_[regionIdx].appendSamplePoint(outerIdx,po,xv); + params_.Y_[regionIdx].appendSamplePoint(outerIdx,po,yv); + params_.VISCO_[regionIdx].appendSamplePoint(outerIdx,po,mo); + params_.VISCG_[regionIdx].appendSamplePoint(outerIdx,po,mg); + break; + } + + bo0=bo; + po0=po; + + params_.BO_[regionIdx].appendSamplePoint(outerIdx,po,bo); + params_.BG_[regionIdx].appendSamplePoint(outerIdx,po,bg); + + params_.RS_[regionIdx].appendSamplePoint(outerIdx,po,rs); + params_.RV_[regionIdx].appendSamplePoint(outerIdx,po,rv); + + params_.X_[regionIdx].appendSamplePoint(outerIdx,po,xv); + params_.Y_[regionIdx].appendSamplePoint(outerIdx,po,yv); + + params_.VISCO_[regionIdx].appendSamplePoint(outerIdx,po,mo); + params_.VISCG_[regionIdx].appendSamplePoint(outerIdx,po,mg); + + // rs,rv -> pressure + params_.PBUB_RS_[regionIdx].appendSamplePoint(outerIdx, rs, po); + params_.PBUB_RV_[regionIdx].appendSamplePoint(outerIdx, rv, po); + + } + } + params_.oilCmp_[regionIdx].setXYContainers(zArg, oilCmp, /*sortInput=*/false); + params_.gasCmp_[regionIdx].setXYContainers(zArg, gasCmp, /*sortInput=*/false); + } + + // Reference density for pure z-component taken from kw SDENSITY + const auto& sdensityTables = eclState.getTableManager().getSolventDensityTables(); + if (sdensityTables.size() == numPvtRegions) { + params_.zReferenceDensity_.resize(numPvtRegions); + for (unsigned regionIdx = 0; regionIdx < numPvtRegions; ++ regionIdx) { + Scalar rhoRefS = sdensityTables[regionIdx].getSolventDensityColumn().front(); + params_.zReferenceDensity_[regionIdx]=rhoRefS; + } + } + else + throw std::runtime_error("Extbo: kw SDENSITY is missing or not aligned with NTPVT\n"); + } +#endif + + /*! + * \brief Register all run-time parameters for the black-oil solvent module. + */ + static void registerParameters() + { + } + + /*! + * \brief Register all solvent specific VTK and ECL output modules. + */ + static void registerOutputModules(Model&, + Simulator&) + { + } + + static bool primaryVarApplies(unsigned pvIdx) + { + if constexpr (enableExtbo) + return pvIdx == zFractionIdx; + else + return false; + } + + static std::string primaryVarName([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + return "z_fraction"; + } + + static Scalar primaryVarWeight([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + static bool eqApplies(unsigned eqIdx) + { + if constexpr (enableExtbo) + return eqIdx == contiZfracEqIdx; + else + return false; + } + + static std::string eqName([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + return "conti^solvent"; + } + + static Scalar eqWeight([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + template + static void addStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants) + { + if constexpr (enableExtbo) { + if constexpr (blackoilConserveSurfaceVolume) { + storage[contiZfracEqIdx] = + Toolbox::template decay(intQuants.porosity()) + * Toolbox::template decay(intQuants.yVolume()) + * Toolbox::template decay(intQuants.fluidState().saturation(gasPhaseIdx)) + * Toolbox::template decay(intQuants.fluidState().invB(gasPhaseIdx)); + if (FluidSystem::enableDissolvedGas()) { // account for dissolved z in oil phase + storage[contiZfracEqIdx] += + Toolbox::template decay(intQuants.porosity()) + * Toolbox::template decay(intQuants.xVolume()) + * Toolbox::template decay(intQuants.fluidState().Rs()) + * Toolbox::template decay(intQuants.fluidState().saturation(oilPhaseIdx)) + * Toolbox::template decay(intQuants.fluidState().invB(oilPhaseIdx)); + } + // Reg. terms: Preliminary attempt to avoid singular behaviour when solvent is invading a pure water + // region. Results seems insensitive to the weighting factor. + // TODO: Further investigations ... + const Scalar regWghtFactor = 1.0e-6; + storage[contiZfracEqIdx] += regWghtFactor*(1.0-Toolbox::template decay(intQuants.zFraction())) + + regWghtFactor*Toolbox::template decay(intQuants.porosity()) + * Toolbox::template decay(intQuants.fluidState().saturation(gasPhaseIdx)) + * Toolbox::template decay(intQuants.fluidState().invB(gasPhaseIdx)); + storage[contiZfracEqIdx-1] += regWghtFactor*Toolbox::template decay(intQuants.zFraction()); + } + else { + throw std::runtime_error("Only component conservation in terms of surface volumes is implemented. "); + } + } + } + + static void computeFlux([[maybe_unused]] RateVector& flux, + [[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned scvfIdx, + [[maybe_unused]] unsigned timeIdx) + + { + if constexpr (enableExtbo) { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + if constexpr (blackoilConserveSurfaceVolume) { + unsigned inIdx = extQuants.interiorIndex(); + + unsigned upIdxGas = static_cast(extQuants.upstreamIndex(gasPhaseIdx)); + const auto& upGas = elemCtx.intensiveQuantities(upIdxGas, timeIdx); + const auto& fsGas = upGas.fluidState(); + if (upIdxGas == inIdx) { + flux[contiZfracEqIdx] = + extQuants.volumeFlux(gasPhaseIdx) + * (upGas.yVolume()) + * fsGas.invB(gasPhaseIdx); + } + else { + flux[contiZfracEqIdx] = + extQuants.volumeFlux(gasPhaseIdx) + * (decay(upGas.yVolume())) + * decay(fsGas.invB(gasPhaseIdx)); + } + if (FluidSystem::enableDissolvedGas()) { // account for dissolved z in oil phase + unsigned upIdxOil = static_cast(extQuants.upstreamIndex(oilPhaseIdx)); + const auto& upOil = elemCtx.intensiveQuantities(upIdxOil, timeIdx); + const auto& fsOil = upOil.fluidState(); + if (upIdxOil == inIdx) { + flux[contiZfracEqIdx] += + extQuants.volumeFlux(oilPhaseIdx) + * upOil.xVolume() + * fsOil.Rs() + * fsOil.invB(oilPhaseIdx); + } + else { + flux[contiZfracEqIdx] += + extQuants.volumeFlux(oilPhaseIdx) + * decay(upOil.xVolume()) + * decay(fsOil.Rs()) + * decay(fsOil.invB(oilPhaseIdx)); + } + } + } + else { + throw std::runtime_error("Only component conservation in terms of surface volumes is implemented. "); + } + } + } + + /*! + * \brief Assign the solvent specific primary variables to a PrimaryVariables object + */ + static void assignPrimaryVars(PrimaryVariables& priVars, + Scalar zFraction) + { + if constexpr (enableExtbo) + priVars[zFractionIdx] = zFraction; + } + + /*! + * \brief Do a Newton-Raphson update the primary variables of the solvents. + */ + static void updatePrimaryVars(PrimaryVariables& newPv, + const PrimaryVariables& oldPv, + const EqVector& delta) + { + if constexpr (enableExtbo) + // do a plain unchopped Newton update + newPv[zFractionIdx] = oldPv[zFractionIdx] - delta[zFractionIdx]; + } + + /*! + * \brief Return how much a Newton-Raphson update is considered an error + */ + static Scalar computeUpdateError(const PrimaryVariables&, + const EqVector&) + { + // do not consider consider the cange of solvent primary variables for + // convergence + // TODO: maybe this should be changed + return static_cast(0.0); + } + + /*! + * \brief Return how much a residual is considered an error + */ + static Scalar computeResidualError(const EqVector& resid) + { + // do not weight the residual of solvents when it comes to convergence + return std::abs(Toolbox::scalarValue(resid[contiZfracEqIdx])); + } + + template + static void serializeEntity(const Model& model, std::ostream& outstream, const DofEntity& dof) + { + if constexpr (enableExtbo) { + unsigned dofIdx = model.dofMapper().index(dof); + + const PrimaryVariables& priVars = model.solution(/*timeIdx=*/0)[dofIdx]; + outstream << priVars[zFractionIdx]; + } + } + + template + static void deserializeEntity(Model& model, std::istream& instream, const DofEntity& dof) + { + if constexpr (enableExtbo) { + unsigned dofIdx = model.dofMapper().index(dof); + + PrimaryVariables& priVars0 = model.solution(/*timeIdx=*/0)[dofIdx]; + PrimaryVariables& priVars1 = model.solution(/*timeIdx=*/1)[dofIdx]; + + instream >> priVars0[zFractionIdx]; + + // set the primary variables for the beginning of the current time step. + priVars1 = priVars0[zFractionIdx]; + } + } + + template + static Value xVolume(unsigned pvtRegionIdx, const Value& pressure, const Value& z) { + return params_.X_[pvtRegionIdx].eval(z, pressure, true); + } + + template + static Value yVolume(unsigned pvtRegionIdx, const Value& pressure, const Value& z) { + return params_.Y_[pvtRegionIdx].eval(z, pressure, true); + } + + template + static Value pbubRs(unsigned pvtRegionIdx, const Value& z, const Value& rs) { + return params_.PBUB_RS_[pvtRegionIdx].eval(z, rs, true); + } + + template + static Value pbubRv(unsigned pvtRegionIdx, const Value& z, const Value& rv) { + return params_.PBUB_RV_[pvtRegionIdx].eval(z, rv, true); + } + + template + static Value oilViscosity(unsigned pvtRegionIdx, const Value& pressure, const Value& z) { + return params_.VISCO_[pvtRegionIdx].eval(z, pressure, true); + } + + template + static Value gasViscosity(unsigned pvtRegionIdx, const Value& pressure, const Value& z) { + return params_.VISCG_[pvtRegionIdx].eval(z, pressure, true); + } + + template + static Value bo(unsigned pvtRegionIdx, const Value& pressure, const Value& z) { + return params_.BO_[pvtRegionIdx].eval(z, pressure, true); + } + + template + static Value bg(unsigned pvtRegionIdx, const Value& pressure, const Value& z) { + return params_.BG_[pvtRegionIdx].eval(z, pressure, true); + } + + template + static Value rs(unsigned pvtRegionIdx, const Value& pressure, const Value& z) { + return params_.RS_[pvtRegionIdx].eval(z, pressure, true); + } + + template + static Value rv(unsigned pvtRegionIdx, const Value& pressure, const Value& z) { + return params_.RV_[pvtRegionIdx].eval(z, pressure, true); + } + + static Scalar referenceDensity(unsigned regionIdx) { + return params_.zReferenceDensity_[regionIdx]; + } + + static Scalar zLim(unsigned regionIdx) { + return params_.zLim_[regionIdx]; + } + + template + static Value oilCmp(unsigned pvtRegionIdx, const Value& z) { + return params_.oilCmp_[pvtRegionIdx].eval(z, true); + } + + template + static Value gasCmp(unsigned pvtRegionIdx, const Value& z) { + return params_.gasCmp_[pvtRegionIdx].eval(z, true); + } + +private: + static BlackOilExtboParams params_; +}; + +template +BlackOilExtboParams::Scalar> +BlackOilExtboModule::params_; + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilExtboIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the equations needed by the + * solvents extension of the black-oil model. + */ +template ()> +class BlackOilExtboIntensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using Indices = GetPropType; + using ElementContext = GetPropType; + + using ExtboModule = BlackOilExtboModule; + + enum { numPhases = getPropValue() }; + static constexpr int zFractionIdx = Indices::zFractionIdx; + static constexpr int oilPhaseIdx = FluidSystem::oilPhaseIdx; + static constexpr int gasPhaseIdx = FluidSystem::gasPhaseIdx; + static constexpr int waterPhaseIdx = FluidSystem::waterPhaseIdx; + static constexpr double cutOff = 1e-12; + + +public: + /*! + * \brief Compute extended pvt properties from table lookups. + * + * At this point the pressures of the fluid state are correct. + */ + void zFractionUpdate_(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + const PrimaryVariables& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + unsigned pvtRegionIdx = priVars.pvtRegionIndex(); + auto& fs = asImp_().fluidState_; + + zFraction_ = priVars.makeEvaluation(zFractionIdx, timeIdx); + + oilViscosity_ = ExtboModule::oilViscosity(pvtRegionIdx, fs.pressure(oilPhaseIdx), zFraction_); + gasViscosity_ = ExtboModule::gasViscosity(pvtRegionIdx, fs.pressure(gasPhaseIdx), zFraction_); + + bo_ = ExtboModule::bo(pvtRegionIdx, fs.pressure(oilPhaseIdx), zFraction_); + bg_ = ExtboModule::bg(pvtRegionIdx, fs.pressure(gasPhaseIdx), zFraction_); + + bz_ = ExtboModule::bg(pvtRegionIdx, fs.pressure(oilPhaseIdx), Evaluation{0.99}); + + if (FluidSystem::enableDissolvedGas()) + rs_ = ExtboModule::rs(pvtRegionIdx, fs.pressure(oilPhaseIdx), zFraction_); + else + rs_ = 0.0; + + if (FluidSystem::enableVaporizedOil()) + rv_ = ExtboModule::rv(pvtRegionIdx, fs.pressure(gasPhaseIdx), zFraction_); + else + rv_ = 0.0; + + xVolume_ = ExtboModule::xVolume(pvtRegionIdx, fs.pressure(oilPhaseIdx), zFraction_); + yVolume_ = ExtboModule::yVolume(pvtRegionIdx, fs.pressure(oilPhaseIdx), zFraction_); + + Evaluation pbub = fs.pressure(oilPhaseIdx); + + if (priVars.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw) { + static const Scalar thresholdWaterFilledCell = 1.0 - 1e-6; + Scalar sw = priVars.makeEvaluation(Indices::waterSwitchIdx, timeIdx).value(); + if (sw >= thresholdWaterFilledCell) + rs_ = 0.0; // water only, zero rs_ ... + } + + if (priVars.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rs) { + rs_ = priVars.makeEvaluation(Indices::compositionSwitchIdx, timeIdx); + const Evaluation zLim = ExtboModule::zLim(pvtRegionIdx); + if (zFraction_ > zLim) { + pbub = ExtboModule::pbubRs(pvtRegionIdx, zLim, rs_); + } else { + pbub = ExtboModule::pbubRs(pvtRegionIdx, zFraction_, rs_); + } + bo_ = ExtboModule::bo(pvtRegionIdx, pbub, zFraction_) + ExtboModule::oilCmp(pvtRegionIdx, zFraction_)*(fs.pressure(oilPhaseIdx)-pbub); + + xVolume_ = ExtboModule::xVolume(pvtRegionIdx, pbub, zFraction_); + } + + if (priVars.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rv) { + rv_ = priVars.makeEvaluation(Indices::compositionSwitchIdx, timeIdx); + Evaluation rvsat = ExtboModule::rv(pvtRegionIdx, pbub, zFraction_); + bg_ = ExtboModule::bg(pvtRegionIdx, pbub, zFraction_) + ExtboModule::gasCmp(pvtRegionIdx, zFraction_)*(rv_-rvsat); + + yVolume_ = ExtboModule::yVolume(pvtRegionIdx, pbub, zFraction_); + } + } + + /*! + * \brief Re-compute face densities to account for zFraction dependency. + * + * At this point the pressures and saturations of the fluid state are correct. + */ + void zPvtUpdate_() + { + const auto& iq = asImp_(); + auto& fs = asImp_().fluidState_; + + unsigned pvtRegionIdx = iq.pvtRegionIndex(); + zRefDensity_ = ExtboModule::referenceDensity(pvtRegionIdx); + + fs.setInvB(oilPhaseIdx, 1.0/bo_); + fs.setInvB(gasPhaseIdx, 1.0/bg_); + + fs.setDensity(oilPhaseIdx, + fs.invB(oilPhaseIdx) + *(FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx) + + (1.0-xVolume_)*fs.Rs()*FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx) + + xVolume_*fs.Rs()*zRefDensity_ )); + fs.setDensity(gasPhaseIdx, + fs.invB(gasPhaseIdx) + *(FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx)*(1.0-yVolume_)+yVolume_*zRefDensity_ + + FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx)*fs.Rv())); + } + + const Evaluation& zFraction() const + { return zFraction_; } + + const Evaluation& xVolume() const + { return xVolume_; } + + const Evaluation& yVolume() const + { return yVolume_; } + + const Evaluation& oilViscosity() const + { return oilViscosity_; } + + const Evaluation& gasViscosity() const + { return gasViscosity_; } + + const Evaluation& bo() const + { return bo_; } + + const Evaluation& bg() const + { return bg_; } + + const Evaluation& rs() const + { return rs_; } + + const Evaluation& rv() const + { return rv_; } + + const Evaluation zPureInvFormationVolumeFactor() const + { return 1.0/bz_; } + + const Scalar& zRefDensity() const + { return zRefDensity_; } + +private: + + +protected: + Implementation& asImp_() + { return *static_cast(this); } + + // Abstract "mass fraction" accounting for the solvent component. The relation between this + // quantity and the actual mass fraction of solvent, is implicitly defined from the specific + // pvt measurements as provided by kw PVTSOL. + Evaluation zFraction_; + + // The solvent component is assumed gas at surface conditions + Evaluation xVolume_; // Solvent volume fraction of Rs + Evaluation yVolume_; // Solvent volume fraction of Sg/Bg + + // Standard black oil parameters modified for presence of solvent + Evaluation oilViscosity_; + Evaluation gasViscosity_; + Evaluation bo_; + Evaluation bg_; + Evaluation rs_; + Evaluation rv_; + + // Properties of pure solvent + Evaluation bz_; + Scalar zRefDensity_; +}; + +template +class BlackOilExtboIntensiveQuantities +{ + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + +public: + + void zPvtUpdate_() + { } + + void zFractionUpdate_(const ElementContext&, + unsigned, + unsigned) + { } + + const Evaluation& xVolume() const + { throw std::runtime_error("xVolume() called but extbo is disabled"); } + + const Evaluation& yVolume() const + { throw std::runtime_error("yVolume() called but extbo is disabled"); } + + const Evaluation& oilViscosity() const + { throw std::runtime_error("oilViscosity() called but extbo is disabled"); } + + const Evaluation& gasViscosity() const + { throw std::runtime_error("gasViscosity() called but extbo is disabled"); } + + const Evaluation& rs() const + { throw std::runtime_error("rs() called but extbo is disabled"); } + + const Evaluation& rv() const + { throw std::runtime_error("rv() called but extbo is disabled"); } + + const Evaluation& zPureInvFormationVolumeFactor() const + { throw std::runtime_error("zPureInvFormationVolumeFactor() called but extbo is disabled"); } + + const Evaluation& zFraction() const + { throw std::runtime_error("zFraction() called but extbo is disabled"); } + + const Evaluation& zInverseFormationVolumeFactor() const + { throw std::runtime_error("zInverseFormationVolumeFactor() called but extbo is disabled"); } + + const Scalar& zRefDensity() const + { throw std::runtime_error("zRefDensity() called but extbo is disabled"); } +}; + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilExtboExtensiveQuantities + * + * \brief Provides the solvent specific extensive quantities to the generic black-oil + * module's extensive quantities. + */ +template ()> +class BlackOilExtboExtensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using GridView = GetPropType; + + using Toolbox = MathToolbox; + + static constexpr unsigned gasPhaseIdx = FluidSystem::gasPhaseIdx; + static constexpr int dimWorld = GridView::dimensionworld; + + typedef Dune::FieldVector DimVector; + typedef Dune::FieldVector DimEvalVector; + +public: + +private: + Implementation& asImp_() + { return *static_cast(this); } + +}; + +template +class BlackOilExtboExtensiveQuantities +{ + using ElementContext = GetPropType; + using Evaluation = GetPropType; + +public: + +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilextboparams.hh b/opm/models/blackoil/blackoilextboparams.hh new file mode 100644 index 00000000000..013c24665a2 --- /dev/null +++ b/opm/models/blackoil/blackoilextboparams.hh @@ -0,0 +1,68 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the parameters required to extend the black-oil model by solvent component. + * For details, refer: + * [*] T.H. Sandve, O. SƦvareid and I. Aavatsmark: ā€œImproved Extended Blackoil Formulation + * for CO2 EOR Simulations.ā€ in ECMOR XVII ā€“ The 17th European Conference on the + * Mathematics of Oil Recovery, September 2020. + */ +#ifndef EWOMS_BLACK_OIL_EXTBO_PARAMS_HH +#define EWOMS_BLACK_OIL_EXTBO_PARAMS_HH + +#include +#include + +#include + +namespace Opm { + +//! \brief Struct holding the parameters for the BlackoilExtboModule class. +template +struct BlackOilExtboParams { + using TabulatedFunction = Tabulated1DFunction; + using Tabulated2DFunction = UniformXTabulated2DFunction; + + std::vector X_; + std::vector Y_; + std::vector PBUB_RS_; + std::vector PBUB_RV_; + std::vector VISCO_; + std::vector VISCG_; + std::vector BO_; + std::vector BG_; + std::vector RS_; + std::vector RV_; + + std::vector zReferenceDensity_; + + std::vector zLim_; + std::vector oilCmp_; + std::vector gasCmp_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilextensivequantities.hh b/opm/models/blackoil/blackoilextensivequantities.hh new file mode 100644 index 00000000000..72f87ca6cef --- /dev/null +++ b/opm/models/blackoil/blackoilextensivequantities.hh @@ -0,0 +1,111 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilExtensiveQuantities + */ +#ifndef EWOMS_BLACK_OIL_EXTENSIVE_QUANTITIES_HH +#define EWOMS_BLACK_OIL_EXTENSIVE_QUANTITIES_HH + +#include "blackoilproperties.hh" +#include "blackoilsolventmodules.hh" +#include "blackoilpolymermodules.hh" +#include "blackoilenergymodules.hh" +#include "blackoildiffusionmodule.hh" +#include "blackoilmicpmodules.hh" +#include + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * \ingroup ExtensiveQuantities + * + * \brief This template class contains the data which is required to + * calculate the fluxes of the fluid phases over a face of a + * finite volume for the black-oil model. + * + * This means pressure and concentration gradients, phase densities at + * the intergration point, etc. + */ +template +class BlackOilExtensiveQuantities + : public MultiPhaseBaseExtensiveQuantities + , public BlackOilSolventExtensiveQuantities + , public BlackOilPolymerExtensiveQuantities + , public BlackOilEnergyExtensiveQuantities + , public BlackOilDiffusionExtensiveQuantities()> + , public BlackOilMICPExtensiveQuantities +{ + using MultiPhaseParent = MultiPhaseBaseExtensiveQuantities; + + using Implementation = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + + enum { enableDiffusion = getPropValue() }; + using DiffusionExtensiveQuantities = BlackOilDiffusionExtensiveQuantities; + + +public: + /*! + * \brief Update the extensive quantities for a given sub-control-volume-face. + * + * \param elemCtx Reference to the current element context. + * \param scvfIdx The local index of the sub-control-volume face for + * which the extensive quantities should be calculated. + * \param timeIdx The index used by the time discretization. + */ + void update(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + MultiPhaseParent::update(elemCtx, scvfIdx, timeIdx); + + asImp_().updateSolvent(elemCtx, scvfIdx, timeIdx); + asImp_().updatePolymer(elemCtx, scvfIdx, timeIdx); + asImp_().updateEnergy(elemCtx, scvfIdx, timeIdx); + DiffusionExtensiveQuantities::update_(elemCtx, scvfIdx, timeIdx); + } + + template + void updateBoundary(const Context& ctx, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + MultiPhaseParent::updateBoundary(ctx, bfIdx, timeIdx, fluidState); + + asImp_().updateEnergyBoundary(ctx, bfIdx, timeIdx, fluidState); + } + +protected: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilfoammodules.hh b/opm/models/blackoil/blackoilfoammodules.hh new file mode 100644 index 00000000000..737d554b8f0 --- /dev/null +++ b/opm/models/blackoil/blackoilfoammodules.hh @@ -0,0 +1,616 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the classes required to extend the black-oil model to include the effects of foam. + */ +#ifndef EWOMS_BLACK_OIL_FOAM_MODULE_HH +#define EWOMS_BLACK_OIL_FOAM_MODULE_HH + +#include "blackoilproperties.hh" + +#include + +#include + +#if HAVE_ECL_INPUT +#include +#include +#include +#endif + +#include + +#include +#include + +#include + +namespace Opm { + +/*! + * \ingroup BlackOil + * \brief Contains the high level supplements required to extend the black oil + * model to include the effects of foam. + */ +template ()> +class BlackOilFoamModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Model = GetPropType; + using Simulator = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + + using Toolbox = MathToolbox; + + using TabulatedFunction = typename BlackOilFoamParams::TabulatedFunction; + + static constexpr unsigned foamConcentrationIdx = Indices::foamConcentrationIdx; + static constexpr unsigned contiFoamEqIdx = Indices::contiFoamEqIdx; + static constexpr unsigned gasPhaseIdx = FluidSystem::gasPhaseIdx; + static constexpr unsigned waterPhaseIdx = FluidSystem::waterPhaseIdx; + + static constexpr unsigned enableFoam = enableFoamV; + + static constexpr unsigned numEq = getPropValue(); + static constexpr unsigned numPhases = FluidSystem::numPhases; + + enum { enableSolvent = getPropValue() }; + +public: +#if HAVE_ECL_INPUT + /*! + * \brief Initialize all internal data structures needed by the foam module + */ + static void initFromState(const EclipseState& eclState) + { + // some sanity checks: if foam is enabled, the FOAM keyword must be + // present, if foam is disabled the keyword must not be present. + if (enableFoam && !eclState.runspec().phases().active(Phase::FOAM)) { + throw std::runtime_error("Non-trivial foam treatment requested at compile time, but " + "the deck does not contain the FOAM keyword"); + } + else if (!enableFoam && eclState.runspec().phases().active(Phase::FOAM)) { + throw std::runtime_error("Foam treatment disabled at compile time, but the deck " + "contains the FOAM keyword"); + } + + if (!eclState.runspec().phases().active(Phase::FOAM)) { + return; // foam treatment is supposed to be disabled + } + + params_.transport_phase_ = eclState.getInitConfig().getFoamConfig().getTransportPhase(); + + if (eclState.getInitConfig().getFoamConfig().getMobilityModel() != FoamConfig::MobilityModel::TAB) { + throw std::runtime_error("In FOAMOPTS, only TAB is allowed for the gas mobility factor reduction model."); + } + + const auto& tableManager = eclState.getTableManager(); + const unsigned int numSatRegions = tableManager.getTabdims().getNumSatTables(); + params_.setNumSatRegions(numSatRegions); + const unsigned int numPvtRegions = tableManager.getTabdims().getNumPVTTables(); + params_.gasMobilityMultiplierTable_.resize(numPvtRegions); + + // Get and check FOAMROCK data. + const FoamConfig& foamConf = eclState.getInitConfig().getFoamConfig(); + if (numSatRegions != foamConf.size()) { + throw std::runtime_error("Inconsistent sizes, number of saturation regions differ from the number of elements " + "in FoamConfig, which typically corresponds to the number of records in FOAMROCK."); + } + + // Get and check FOAMADS data. + const auto& foamadsTables = tableManager.getFoamadsTables(); + if (foamadsTables.empty()) { + throw std::runtime_error("FOAMADS must be specified in FOAM runs"); + } + if (numSatRegions != foamadsTables.size()) { + throw std::runtime_error("Inconsistent sizes, number of saturation regions differ from the " + "number of FOAMADS tables."); + } + + // Set data that vary with saturation region. + for (std::size_t satReg = 0; satReg < numSatRegions; ++satReg) { + const auto& rec = foamConf.getRecord(satReg); + params_.foamCoefficients_[satReg] = typename BlackOilFoamParams::FoamCoefficients(); + params_.foamCoefficients_[satReg].fm_min = rec.minimumSurfactantConcentration(); + params_.foamCoefficients_[satReg].fm_surf = rec.referenceSurfactantConcentration(); + params_.foamCoefficients_[satReg].ep_surf = rec.exponent(); + params_.foamRockDensity_[satReg] = rec.rockDensity(); + params_.foamAllowDesorption_[satReg] = rec.allowDesorption(); + const auto& foamadsTable = foamadsTables.template getTable(satReg); + const auto& conc = foamadsTable.getFoamConcentrationColumn(); + const auto& ads = foamadsTable.getAdsorbedFoamColumn(); + params_.adsorbedFoamTable_[satReg].setXYContainers(conc, ads); + } + + // Get and check FOAMMOB data. + const auto& foammobTables = tableManager.getFoammobTables(); + if (foammobTables.empty()) { + // When in the future adding support for the functional + // model, FOAMMOB will not be required anymore (functional + // family of keywords can be used instead, FOAMFSC etc.). + throw std::runtime_error("FOAMMOB must be specified in FOAM runs"); + } + if (numPvtRegions != foammobTables.size()) { + throw std::runtime_error("Inconsistent sizes, number of PVT regions differ from the " + "number of FOAMMOB tables."); + } + + // Set data that vary with PVT region. + for (std::size_t pvtReg = 0; pvtReg < numPvtRegions; ++pvtReg) { + const auto& foammobTable = foammobTables.template getTable(pvtReg); + const auto& conc = foammobTable.getFoamConcentrationColumn(); + const auto& mobMult = foammobTable.getMobilityMultiplierColumn(); + params_.gasMobilityMultiplierTable_[pvtReg].setXYContainers(conc, mobMult); + } + } +#endif + + /*! + * \brief Register all run-time parameters for the black-oil foam module. + */ + static void registerParameters() + { + } + + /*! + * \brief Register all foam specific VTK and ECL output modules. + */ + static void registerOutputModules(Model&, + Simulator&) + { + if constexpr (enableFoam) { + if (Parameters::Get()) { + OpmLog::warning("VTK output requested, currently unsupported by the foam module."); + } + } + //model.addOutputModule(new VtkBlackOilFoamModule(simulator)); + } + + static bool primaryVarApplies(unsigned pvIdx) + { + if constexpr (enableFoam) + return pvIdx == foamConcentrationIdx; + else + return false; + } + + static std::string primaryVarName([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + return "foam_concentration"; + } + + static Scalar primaryVarWeight([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + static bool eqApplies(unsigned eqIdx) + { + if constexpr (enableFoam) + return eqIdx == contiFoamEqIdx; + else + return false; + + } + + static std::string eqName([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + return "conti^foam"; + } + + static Scalar eqWeight([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + // must be called after water storage is computed + template + static void addStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants) + { + if constexpr (enableFoam) { + const auto& fs = intQuants.fluidState(); + + LhsEval surfaceVolume = Toolbox::template decay(intQuants.porosity()); + if (params_.transport_phase_ == Phase::WATER) { + surfaceVolume *= (Toolbox::template decay(fs.saturation(waterPhaseIdx)) + * Toolbox::template decay(fs.invB(waterPhaseIdx))); + } else if (params_.transport_phase_ == Phase::GAS) { + surfaceVolume *= (Toolbox::template decay(fs.saturation(gasPhaseIdx)) + * Toolbox::template decay(fs.invB(gasPhaseIdx))); + } else if (params_.transport_phase_ == Phase::SOLVENT) { + if constexpr (enableSolvent) { + surfaceVolume *= (Toolbox::template decay( intQuants.solventSaturation()) + * Toolbox::template decay(intQuants.solventInverseFormationVolumeFactor())); + } + } else { + throw std::runtime_error("Transport phase is GAS/WATER/SOLVENT"); + } + + // Avoid singular matrix if no gas is present. + surfaceVolume = max(surfaceVolume, 1e-10); + + // Foam/surfactant in free phase. + const LhsEval freeFoam = surfaceVolume + * Toolbox::template decay(intQuants.foamConcentration()); + + // Adsorbed foam/surfactant. + const LhsEval adsorbedFoam = + Toolbox::template decay(1.0 - intQuants.porosity()) + * Toolbox::template decay(intQuants.foamRockDensity()) + * Toolbox::template decay(intQuants.foamAdsorbed()); + + LhsEval accumulationFoam = freeFoam + adsorbedFoam; + storage[contiFoamEqIdx] += accumulationFoam; + } + } + + static void computeFlux([[maybe_unused]] RateVector& flux, + [[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned scvfIdx, + [[maybe_unused]] unsigned timeIdx) + + { + if constexpr (enableFoam) { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + const unsigned inIdx = extQuants.interiorIndex(); + + // The effect of the mobility reduction factor is + // incorporated in the mobility for the relevant phase, + // so fluxes do not need modification here. + switch (transportPhase()) { + case Phase::WATER: { + const unsigned upIdx = extQuants.upstreamIndex(waterPhaseIdx); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + if (upIdx == inIdx) { + flux[contiFoamEqIdx] = + extQuants.volumeFlux(waterPhaseIdx) + *up.fluidState().invB(waterPhaseIdx) + *up.foamConcentration(); + } else { + flux[contiFoamEqIdx] = + extQuants.volumeFlux(waterPhaseIdx) + *decay(up.fluidState().invB(waterPhaseIdx)) + *decay(up.foamConcentration()); + } + break; + } + case Phase::GAS: { + const unsigned upIdx = extQuants.upstreamIndex(gasPhaseIdx); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + if (upIdx == inIdx) { + flux[contiFoamEqIdx] = + extQuants.volumeFlux(gasPhaseIdx) + *up.fluidState().invB(gasPhaseIdx) + *up.foamConcentration(); + } else { + flux[contiFoamEqIdx] = + extQuants.volumeFlux(gasPhaseIdx) + *decay(up.fluidState().invB(gasPhaseIdx)) + *decay(up.foamConcentration()); + } + break; + } + case Phase::SOLVENT: { + if constexpr (enableSolvent) { + const unsigned upIdx = extQuants.solventUpstreamIndex(); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + if (upIdx == inIdx) { + flux[contiFoamEqIdx] = + extQuants.solventVolumeFlux() + *up.solventInverseFormationVolumeFactor() + *up.foamConcentration(); + } else { + flux[contiFoamEqIdx] = + extQuants.solventVolumeFlux() + *decay(up.solventInverseFormationVolumeFactor()) + *decay(up.foamConcentration()); + } + } else { + throw std::runtime_error("Foam transport phase is SOLVENT but SOLVENT is not activated."); + } + break; + } + default: { + throw std::runtime_error("Foam transport phase must be GAS/WATER/SOLVENT."); + } + } + } + } + + /*! + * \brief Return how much a Newton-Raphson update is considered an error + */ + static Scalar computeUpdateError(const PrimaryVariables&, + const EqVector&) + { + // do not consider the change of foam primary variables for convergence + // TODO: maybe this should be changed + return static_cast(0.0); + } + + template + static void serializeEntity([[maybe_unused]] const Model& model, + [[maybe_unused]] std::ostream& outstream, + [[maybe_unused]] const DofEntity& dof) + { + if constexpr (enableFoam) { + unsigned dofIdx = model.dofMapper().index(dof); + const PrimaryVariables& priVars = model.solution(/*timeIdx=*/0)[dofIdx]; + outstream << priVars[foamConcentrationIdx]; + } + } + + template + static void deserializeEntity([[maybe_unused]] Model& model, + [[maybe_unused]] std::istream& instream, + [[maybe_unused]] const DofEntity& dof) + { + if constexpr (enableFoam) { + unsigned dofIdx = model.dofMapper().index(dof); + PrimaryVariables& priVars0 = model.solution(/*timeIdx=*/0)[dofIdx]; + PrimaryVariables& priVars1 = model.solution(/*timeIdx=*/1)[dofIdx]; + + instream >> priVars0[foamConcentrationIdx]; + + // set the primary variables for the beginning of the current time step. + priVars1[foamConcentrationIdx] = priVars0[foamConcentrationIdx]; + } + } + + static const Scalar foamRockDensity(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.foamRockDensity_[satnumRegionIdx]; + } + + static bool foamAllowDesorption(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.foamAllowDesorption_[satnumRegionIdx]; + } + + static const TabulatedFunction& adsorbedFoamTable(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.adsorbedFoamTable_[satnumRegionIdx]; + } + + static const TabulatedFunction& gasMobilityMultiplierTable(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned pvtnumRegionIdx = elemCtx.problem().pvtRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.gasMobilityMultiplierTable_[pvtnumRegionIdx]; + } + + static const typename BlackOilFoamParams::FoamCoefficients& + foamCoefficients(const ElementContext& elemCtx, + const unsigned scvIdx, + const unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.foamCoefficients_[satnumRegionIdx]; + } + + static Phase transportPhase() { + return params_.transport_phase_; + } + +private: + static BlackOilFoamParams params_; +}; + +template +BlackOilFoamParams::Scalar> +BlackOilFoamModule::params_; + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilFoamIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the equations needed by the + * polymers extension of the black-oil model. + */ +template ()> +class BlackOilFoamIntensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using Indices = GetPropType; + using ElementContext = GetPropType; + + using FoamModule = BlackOilFoamModule; + + enum { numPhases = getPropValue() }; + enum { enableSolvent = getPropValue() }; + + static constexpr int foamConcentrationIdx = Indices::foamConcentrationIdx; + static constexpr unsigned waterPhaseIdx = FluidSystem::waterPhaseIdx; + static constexpr unsigned oilPhaseIdx = FluidSystem::oilPhaseIdx; + static constexpr int gasPhaseIdx = FluidSystem::gasPhaseIdx; + +public: + + /*! + * \brief Update the intensive properties needed to handle polymers from the + * primary variables + * + */ + void foamPropertiesUpdate_(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + const PrimaryVariables& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + foamConcentration_ = priVars.makeEvaluation(foamConcentrationIdx, timeIdx); + const auto& fs = asImp_().fluidState_; + + // Compute gas mobility reduction factor + Evaluation mobilityReductionFactor = 1.0; + if (false) { + // The functional model is used. + // TODO: allow this model. + // In order to do this we must allow transport to be in the water phase, not just the gas phase. + const auto& foamCoefficients = FoamModule::foamCoefficients(elemCtx, dofIdx, timeIdx); + + const Scalar fm_mob = foamCoefficients.fm_mob; + + const Scalar fm_surf = foamCoefficients.fm_surf; + const Scalar ep_surf = foamCoefficients.ep_surf; + + const Scalar fm_oil = foamCoefficients.fm_oil; + const Scalar fl_oil = foamCoefficients.fl_oil; + const Scalar ep_oil = foamCoefficients.ep_oil; + + const Scalar fm_dry = foamCoefficients.fm_dry; + const Scalar ep_dry = foamCoefficients.ep_dry; + + const Scalar fm_cap = foamCoefficients.fm_cap; + const Scalar ep_cap = foamCoefficients.ep_cap; + + const Evaluation C_surf = foamConcentration_; + const Evaluation Ca = 1e10; // TODO: replace with proper capillary number. + const Evaluation S_o = fs.saturation(oilPhaseIdx); + const Evaluation S_w = fs.saturation(waterPhaseIdx); + + Evaluation F1 = pow(C_surf/fm_surf, ep_surf); + Evaluation F2 = pow((fm_oil-S_o)/(fm_oil-fl_oil), ep_oil); + Evaluation F3 = pow(fm_cap/Ca, ep_cap); + Evaluation F7 = 0.5 + atan(ep_dry*(S_w-fm_dry))/M_PI; + + mobilityReductionFactor = 1./(1. + fm_mob*F1*F2*F3*F7); + } else { + // The tabular model is used. + // Note that the current implementation only includes the effect of foam concentration (FOAMMOB), + // and not the optional pressure dependence (FOAMMOBP) or shear dependence (FOAMMOBS). + const auto& gasMobilityMultiplier = FoamModule::gasMobilityMultiplierTable(elemCtx, dofIdx, timeIdx); + mobilityReductionFactor = gasMobilityMultiplier.eval(foamConcentration_, /* extrapolate = */ true); + } + + // adjust mobility + switch (FoamModule::transportPhase()) { + case Phase::WATER: { + asImp_().mobility_[waterPhaseIdx] *= mobilityReductionFactor; + break; + } + case Phase::GAS: { + asImp_().mobility_[gasPhaseIdx] *= mobilityReductionFactor; + break; + } + case Phase::SOLVENT: { + if constexpr (enableSolvent) { + asImp_().solventMobility_ *= mobilityReductionFactor; + } else { + throw std::runtime_error("Foam transport phase is SOLVENT but SOLVENT is not activated."); + } + break; + } + default: { + throw std::runtime_error("Foam transport phase must be GAS/WATER/SOLVENT."); + } + } + + foamRockDensity_ = FoamModule::foamRockDensity(elemCtx, dofIdx, timeIdx); + + const auto& adsorbedFoamTable = FoamModule::adsorbedFoamTable(elemCtx, dofIdx, timeIdx); + foamAdsorbed_ = adsorbedFoamTable.eval(foamConcentration_, /*extrapolate=*/true); + if (!FoamModule::foamAllowDesorption(elemCtx, dofIdx, timeIdx)) { + throw std::runtime_error("Foam module does not support the 'no desorption' option."); + } + } + + const Evaluation& foamConcentration() const + { return foamConcentration_; } + + Scalar foamRockDensity() const + { return foamRockDensity_; } + + const Evaluation& foamAdsorbed() const + { return foamAdsorbed_; } + +protected: + Implementation& asImp_() + { return *static_cast(this); } + + Evaluation foamConcentration_; + Scalar foamRockDensity_; + Evaluation foamAdsorbed_; +}; + +template +class BlackOilFoamIntensiveQuantities +{ + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + +public: + void foamPropertiesUpdate_(const ElementContext&, + unsigned, + unsigned) + { } + + + const Evaluation& foamConcentration() const + { throw std::runtime_error("foamConcentration() called but foam is disabled"); } + + Scalar foamRockDensity() const + { throw std::runtime_error("foamRockDensity() called but foam is disabled"); } + + Scalar foamAdsorbed() const + { throw std::runtime_error("foamAdsorbed() called but foam is disabled"); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilfoamparams.hh b/opm/models/blackoil/blackoilfoamparams.hh new file mode 100644 index 00000000000..7f9e0b4f85c --- /dev/null +++ b/opm/models/blackoil/blackoilfoamparams.hh @@ -0,0 +1,87 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the parameters to extend the black-oil model to include the effects of foam. + */ +#ifndef EWOMS_BLACK_OIL_FOAM_PARAMS_HH +#define EWOMS_BLACK_OIL_FOAM_PARAMS_HH + +#include +#include + +#include + +namespace Opm { + +//! \brief Struct holding the parameters for the BlackoilFoamModule class. +template +struct BlackOilFoamParams { + using TabulatedFunction = Tabulated1DFunction; + + /*! + * \brief Specify the number of saturation regions. + */ + void setNumSatRegions(unsigned numRegions) + { + foamCoefficients_.resize(numRegions); + foamRockDensity_.resize(numRegions); + foamAllowDesorption_.resize(numRegions); + adsorbedFoamTable_.resize(numRegions); + } + + // a struct containing constants to calculate change to relative permeability, + // based on model (1-9) in Table 1 of + // Kun Ma, Guangwei Ren, Khalid Mateen, Danielle Morel, and Philippe Cordelier: + // "Modeling techniques for foam flow in porous media", SPE Journal, 20(03):453ā€“470, jun 2015. + // The constants are provided by various deck keywords as shown in the comments below. + struct FoamCoefficients { + Scalar fm_min = 1e-20; // FOAMFSC + Scalar fm_mob = 1.0; // FOAMFRM + + Scalar fm_surf = 1.0; // FOAMFSC + Scalar ep_surf = 1.0; // FOAMFSC + + Scalar fm_oil = 1.0; // FOAMFSO + Scalar fl_oil = 0.0; // FOAMFSO + Scalar ep_oil = 0.0; // FOAMFSO + + Scalar fm_cap = 1.0; // FOAMFCN + Scalar ep_cap = 0.0; // FOAMFCN + + Scalar fm_dry = 1.0; // FOAMFSW + Scalar ep_dry = 0.0; // FOAMFSW + }; + + std::vector foamRockDensity_; + std::vector foamAllowDesorption_; + std::vector foamCoefficients_; + std::vector adsorbedFoamTable_; + std::vector gasMobilityMultiplierTable_; + Opm::Phase transport_phase_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilindices.hh b/opm/models/blackoil/blackoilindices.hh new file mode 100644 index 00000000000..8350373d051 --- /dev/null +++ b/opm/models/blackoil/blackoilindices.hh @@ -0,0 +1,241 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilIndices + */ +#ifndef EWOMS_BLACK_OIL_INDICES_HH +#define EWOMS_BLACK_OIL_INDICES_HH + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * + * \brief The primary variable and equation indices for the black-oil model. + */ +template +struct BlackOilIndices +{ + //! Number of phases active at all times + static constexpr int numPhases = 3; + + //! All phases are enabled + static constexpr bool oilEnabled = true; + static constexpr bool waterEnabled = true; + static constexpr bool gasEnabled = true; + + //! Are solvents involved? + static constexpr bool enableSolvent = numSolventsV > 0; + + //! Is extbo invoked? + static constexpr bool enableExtbo = numExtbosV > 0; + + //! Are polymers involved? + static constexpr bool enablePolymer = numPolymersV > 0; + + //! Shall energy be conserved? + static constexpr bool enableEnergy = numEnergyV > 0; + + //! Is MICP involved? + static constexpr bool enableMICP = numMICPsV > 0; + + //! Number of solvent components to be considered + static constexpr int numSolvents = enableSolvent ? numSolventsV : 0; + + //! Number of components to be considered for extbo + static constexpr int numExtbos = enableExtbo ? numExtbosV : 0; + + //! Number of polymer components to be considered + static constexpr int numPolymers = enablePolymer ? numPolymersV : 0; + + //! Number of energy equations to be considered + static constexpr int numEnergy = enableEnergy ? numEnergyV : 0; + + //! Number of foam equations to be considered + static constexpr int numFoam = enableFoam? 1 : 0; + + //! Number of salt equations to be considered + static constexpr int numBrine = enableBrine? 1 : 0; + + //! Number of MICP components to be considered + static constexpr int numMICPs = enableMICP ? numMICPsV : 0; + + //! The number of equations + static constexpr int numEq = numPhases + numSolvents + numExtbos + numPolymers + + numEnergy + numFoam + numBrine + numMICPs; + + //! \brief returns the index of "active" component + static constexpr unsigned canonicalToActiveComponentIndex(unsigned compIdx) + { return compIdx; } + + static constexpr unsigned activeToCanonicalComponentIndex(unsigned compIdx) + { return compIdx; } + + //////// + // Primary variable indices + //////// + + /*! + * \brief Index of the switching variable which determines the composistion of the water phase + * + * Depending on the phases present, this variable is either interpreted as + * water saturation or vapporized water in gas phase + */ + static constexpr int waterSwitchIdx = PVOffset + 0; + + /*! + * \brief Index of the switching variable which determines the pressure + * + * Depending on the phases present, this variable is either interpreted as the + * pressure of the oil phase, gas phase (if no oil) or water phase (if only water) + */ + static constexpr int pressureSwitchIdx = PVOffset + 1; + + /*! + * \brief Index of the switching variable which determines the composition of the + * hydrocarbon phases. + * + * Depending on the phases present, this variable is either interpreted as the + * saturation of the gas phase, as the mole fraction of the gas component in the oil + * phase or as the mole fraction of the oil component in the gas phase. + */ + static constexpr int compositionSwitchIdx = PVOffset + 2; + + //! Index of the primary variable for the first solvent + static constexpr int solventSaturationIdx = + enableSolvent ? PVOffset + numPhases : -1000; + + //! Index of the primary variable for the first extbo component + static constexpr int zFractionIdx = + enableExtbo ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the primary variable for the first polymer + static constexpr int polymerConcentrationIdx = + enablePolymer ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the primary variable for the second polymer primary variable (molecular weight) + static constexpr int polymerMoleWeightIdx = + numPolymers > 1 ? polymerConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the first MICP component + static constexpr int microbialConcentrationIdx = + enableMICP ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the primary variable for the second MICP component + static constexpr int oxygenConcentrationIdx = + numMICPs > 1 ? microbialConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the third MICP component + static constexpr int ureaConcentrationIdx = + numMICPs > 2 ? oxygenConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the fourth MICP component + static constexpr int biofilmConcentrationIdx = + numMICPs > 3 ? ureaConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the fifth MICP component + static constexpr int calciteConcentrationIdx = + numMICPs > 4 ? biofilmConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the foam + static constexpr int foamConcentrationIdx = + enableFoam ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs : -1000; + + //! Index of the primary variable for the brine + static constexpr int saltConcentrationIdx = + enableBrine ? PVOffset + numPhases + numSolvents + numExtbos + numExtbos + numPolymers + numMICPs + numFoam : -1000; + + //! Index of the primary variable for temperature + static constexpr int temperatureIdx = + enableEnergy ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs + numFoam + numBrine : - 1000; + + + //////// + // Equation indices + //////// + + //! Index of the continuity equation of the first phase + static constexpr int conti0EqIdx = PVOffset + 0; + // two continuity equations follow + + //! Index of the continuity equation for the first solvent component + static constexpr int contiSolventEqIdx = + enableSolvent ? PVOffset + numPhases : -1000; + + //! Index of the continuity equation for the first extbo component + static constexpr int contiZfracEqIdx = + enableExtbo ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the continuity equation for the first polymer component + static constexpr int contiPolymerEqIdx = + enablePolymer ? PVOffset + numPhases + numSolvents + numExtbos : -1000; + + //! Index of the continuity equation for the second polymer component (molecular weight) + static constexpr int contiPolymerMWEqIdx = + numPolymers > 1 ? contiPolymerEqIdx + 1 : -1000; + + //! Index of the continuity equation for the first MICP component + static constexpr int contiMicrobialEqIdx = + enableMICP ? PVOffset + numPhases + numSolvents + numExtbos : -1000; + + //! Index of the continuity equation for the second MICP component + static constexpr int contiOxygenEqIdx = + numMICPs > 1 ? contiMicrobialEqIdx + 1 : -1000; + + //! Index of the continuity equation for the third MICP component + static constexpr int contiUreaEqIdx = + numMICPs > 2 ? contiOxygenEqIdx + 1 : -1000; + + //! Index of the continuity equation for the fourth MICP component + static constexpr int contiBiofilmEqIdx = + numMICPs > 3 ? contiUreaEqIdx + 1 : -1000; + + //! Index of the continuity equation for the fifth MICP component + static constexpr int contiCalciteEqIdx = + numMICPs > 4 ? contiBiofilmEqIdx + 1 : -1000; + + //! Index of the continuity equation for the foam component + static constexpr int contiFoamEqIdx = + enableFoam ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs : -1000; + + //! Index of the continuity equation for the salt water component + static constexpr int contiBrineEqIdx = + enableBrine ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs + numFoam : -1000; + + //! Index of the continuity equation for energy + static constexpr int contiEnergyEqIdx = + enableEnergy ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs + numFoam + numBrine: -1000; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilintensivequantities.hh b/opm/models/blackoil/blackoilintensivequantities.hh new file mode 100644 index 00000000000..2de7bea2893 --- /dev/null +++ b/opm/models/blackoil/blackoilintensivequantities.hh @@ -0,0 +1,633 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilIntensiveQuantities + */ +#ifndef EWOMS_BLACK_OIL_INTENSIVE_QUANTITIES_HH +#define EWOMS_BLACK_OIL_INTENSIVE_QUANTITIES_HH + +#include "blackoilproperties.hh" +#include "blackoilsolventmodules.hh" +#include "blackoilextbomodules.hh" +#include "blackoilpolymermodules.hh" +#include "blackoilfoammodules.hh" +#include "blackoilbrinemodules.hh" +#include "blackoilenergymodules.hh" +#include "blackoildiffusionmodule.hh" +#include "blackoildispersionmodule.hh" +#include "blackoilmicpmodules.hh" + +#include +#include + +#include + +#include +#include + +#include + +#include + +#include + +#include +#include + +#include + +namespace Opm { +/*! + * \ingroup BlackOilModel + * \ingroup IntensiveQuantities + * + * \brief Contains the quantities which are are constant within a + * finite volume in the black-oil model. + */ +template +class BlackOilIntensiveQuantities + : public GetPropType + , public GetPropType::FluxIntensiveQuantities + , public BlackOilDiffusionIntensiveQuantities() > + , public BlackOilDispersionIntensiveQuantities() > + , public BlackOilSolventIntensiveQuantities + , public BlackOilExtboIntensiveQuantities + , public BlackOilPolymerIntensiveQuantities + , public BlackOilFoamIntensiveQuantities + , public BlackOilBrineIntensiveQuantities + , public BlackOilEnergyIntensiveQuantities + , public BlackOilMICPIntensiveQuantities +{ + using ParentType = GetPropType; + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using ElementContext = GetPropType; + using PrimaryVariables = GetPropType; + using Indices = GetPropType; + using GridView = GetPropType; + using FluxModule = GetPropType; + + enum { numEq = getPropValue() }; + enum { enableSolvent = getPropValue() }; + enum { enableExtbo = getPropValue() }; + enum { enablePolymer = getPropValue() }; + enum { enableFoam = getPropValue() }; + enum { enableBrine = getPropValue() }; + enum { enableVapwat = getPropValue() }; + enum { has_disgas_in_water = getPropValue() }; + enum { enableSaltPrecipitation = getPropValue() }; + enum { enableTemperature = getPropValue() }; + enum { enableEnergy = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + enum { enableDispersion = getPropValue() }; + enum { enableMICP = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { waterCompIdx = FluidSystem::waterCompIdx }; + enum { oilCompIdx = FluidSystem::oilCompIdx }; + enum { gasCompIdx = FluidSystem::gasCompIdx }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { dimWorld = GridView::dimensionworld }; + enum { compositionSwitchIdx = Indices::compositionSwitchIdx }; + + static constexpr bool compositionSwitchEnabled = Indices::compositionSwitchIdx >= 0; + static constexpr bool waterEnabled = Indices::waterEnabled; + static constexpr bool gasEnabled = Indices::gasEnabled; + static constexpr bool oilEnabled = Indices::oilEnabled; + + using Toolbox = MathToolbox; + using DimMatrix = Dune::FieldMatrix; + using FluxIntensiveQuantities = typename FluxModule::FluxIntensiveQuantities; + using DiffusionIntensiveQuantities = BlackOilDiffusionIntensiveQuantities; + using DispersionIntensiveQuantities = BlackOilDispersionIntensiveQuantities; + + using DirectionalMobilityPtr = Opm::Utility::CopyablePtr>; + using BrineModule = BlackOilBrineModule; + + +public: + using FluidState = BlackOilFluidState; + using ScalarFluidState = BlackOilFluidState; + using Problem = GetPropType; + + BlackOilIntensiveQuantities() + { + if (compositionSwitchEnabled) { + fluidState_.setRs(0.0); + fluidState_.setRv(0.0); + } + if (enableVapwat) { + fluidState_.setRvw(0.0); + } + if (has_disgas_in_water) { + fluidState_.setRsw(0.0); + } + } + BlackOilIntensiveQuantities(const BlackOilIntensiveQuantities& other) = default; + + BlackOilIntensiveQuantities& operator=(const BlackOilIntensiveQuantities& other) = default; + + /*! + * \copydoc IntensiveQuantities::update + */ + void update(const ElementContext& elemCtx, unsigned dofIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, dofIdx, timeIdx); + OPM_TIMEBLOCK_LOCAL(blackoilIntensiveQuanititiesUpdate); + const auto& problem = elemCtx.problem(); + const auto& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + const auto& linearizationType = problem.model().linearizer().getLinearizationType(); + unsigned globalSpaceIdx = elemCtx.globalSpaceIndex(dofIdx, timeIdx); + Scalar RvMax = FluidSystem::enableVaporizedOil() + ? problem.maxOilVaporizationFactor(timeIdx, globalSpaceIdx) + : 0.0; + Scalar RsMax = FluidSystem::enableDissolvedGas() + ? problem.maxGasDissolutionFactor(timeIdx, globalSpaceIdx) + : 0.0; + Scalar RswMax = FluidSystem::enableDissolvedGasInWater() + ? problem.maxGasDissolutionFactor(timeIdx, globalSpaceIdx) + : 0.0; + + asImp_().updateTemperature_(elemCtx, dofIdx, timeIdx); + + unsigned pvtRegionIdx = priVars.pvtRegionIndex(); + fluidState_.setPvtRegionIndex(pvtRegionIdx); + + asImp_().updateSaltConcentration_(elemCtx, dofIdx, timeIdx); + + // extract the water and the gas saturations for convenience + Evaluation Sw = 0.0; + if constexpr (waterEnabled) { + if (priVars.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw) { + Sw = priVars.makeEvaluation(Indices::waterSwitchIdx, timeIdx); + } else if(priVars.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Rsw || + priVars.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Disabled) { + // water is enabled but is not a primary variable i.e. one component/phase case + // or two-phase water + gas with only water present + Sw = 1.0; + } // else i.e. for MeaningWater() = Rvw, Sw is still 0.0; + } + Evaluation Sg = 0.0; + if constexpr (gasEnabled) { + if (priVars.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg) { + Sg = priVars.makeEvaluation(Indices::compositionSwitchIdx, timeIdx); + } else if (priVars.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rv) { + Sg = 1.0 - Sw; + } else if (priVars.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Disabled) { + if constexpr (waterEnabled) { + Sg = 1.0 - Sw; // two phase water + gas + } else { + // one phase case + Sg = 1.0; + } + } + } + Valgrind::CheckDefined(Sg); + Valgrind::CheckDefined(Sw); + + Evaluation So = 1.0 - Sw - Sg; + + // deal with solvent + if constexpr (enableSolvent) { + if(priVars.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) { + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + So -= priVars.makeEvaluation(Indices::solventSaturationIdx, timeIdx); + } else if (FluidSystem::phaseIsActive(gasPhaseIdx)) { + Sg -= priVars.makeEvaluation(Indices::solventSaturationIdx, timeIdx); + } + } + } + + if (FluidSystem::phaseIsActive(waterPhaseIdx)) + fluidState_.setSaturation(waterPhaseIdx, Sw); + + if (FluidSystem::phaseIsActive(gasPhaseIdx)) + fluidState_.setSaturation(gasPhaseIdx, Sg); + + if (FluidSystem::phaseIsActive(oilPhaseIdx)) + fluidState_.setSaturation(oilPhaseIdx, So); + + asImp_().solventPreSatFuncUpdate_(elemCtx, dofIdx, timeIdx); + + // now we compute all phase pressures + std::array pC; + const auto& materialParams = problem.materialLawParams(globalSpaceIdx); + MaterialLaw::capillaryPressures(pC, materialParams, fluidState_); + problem.updateRelperms(mobility_, dirMob_, fluidState_, globalSpaceIdx); + + // scaling the capillary pressure due to salt precipitation + if (BrineModule::hasPcfactTables() && priVars.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp) { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, dofIdx, timeIdx); + const Evaluation Sp = priVars.makeEvaluation(Indices::saltConcentrationIdx, timeIdx); + const Evaluation porosityFactor = min(1.0 - Sp, 1.0); //phi/phi_0 + const auto& pcfactTable = BrineModule::pcfactTable(satnumRegionIdx); + const Evaluation pcFactor = pcfactTable.eval(porosityFactor, /*extrapolation=*/true); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + if (FluidSystem::phaseIsActive(phaseIdx)) { + pC[phaseIdx] *= pcFactor; + } + } + + // oil is the reference phase for pressure + if (priVars.primaryVarsMeaningPressure() == PrimaryVariables::PressureMeaning::Pg) { + const Evaluation& pg = priVars.makeEvaluation(Indices::pressureSwitchIdx, timeIdx); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + if (FluidSystem::phaseIsActive(phaseIdx)) + fluidState_.setPressure(phaseIdx, pg + (pC[phaseIdx] - pC[gasPhaseIdx])); + } else if (priVars.primaryVarsMeaningPressure() == PrimaryVariables::PressureMeaning::Pw) { + const Evaluation& pw = priVars.makeEvaluation(Indices::pressureSwitchIdx, timeIdx); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + if (FluidSystem::phaseIsActive(phaseIdx)) + fluidState_.setPressure(phaseIdx, pw + (pC[phaseIdx] - pC[waterPhaseIdx])); + } else { + assert(FluidSystem::phaseIsActive(oilPhaseIdx)); + const Evaluation& po = priVars.makeEvaluation(Indices::pressureSwitchIdx, timeIdx); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + if (FluidSystem::phaseIsActive(phaseIdx)) + fluidState_.setPressure(phaseIdx, po + (pC[phaseIdx] - pC[oilPhaseIdx])); + } + + // update the Saturation functions for the blackoil solvent module. + asImp_().solventPostSatFuncUpdate_(elemCtx, dofIdx, timeIdx); + + // update extBO parameters + asImp_().zFractionUpdate_(elemCtx, dofIdx, timeIdx); + + Evaluation SoMax = 0.0; + if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) { + SoMax = max(fluidState_.saturation(oilPhaseIdx), + problem.maxOilSaturation(globalSpaceIdx)); + } + // take the meaning of the switching primary variable into account for the gas + // and oil phase compositions + if (priVars.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rs) { + const auto& Rs = priVars.makeEvaluation(Indices::compositionSwitchIdx, timeIdx); + fluidState_.setRs(Rs); + } else { + if (FluidSystem::enableDissolvedGas()) { // Add So > 0? i.e. if only water set rs = 0) + const Evaluation& RsSat = enableExtbo ? asImp_().rs() : + FluidSystem::saturatedDissolutionFactor(fluidState_, + oilPhaseIdx, + pvtRegionIdx, + SoMax); + fluidState_.setRs(min(RsMax, RsSat)); + } + else if constexpr (compositionSwitchEnabled) + fluidState_.setRs(0.0); + } + if (priVars.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rv) { + const auto& Rv = priVars.makeEvaluation(Indices::compositionSwitchIdx, timeIdx); + fluidState_.setRv(Rv); + } else { + if (FluidSystem::enableVaporizedOil() ) { // Add Sg > 0? i.e. if only water set rv = 0) + const Evaluation& RvSat = enableExtbo ? asImp_().rv() : + FluidSystem::saturatedDissolutionFactor(fluidState_, + gasPhaseIdx, + pvtRegionIdx, + SoMax); + fluidState_.setRv(min(RvMax, RvSat)); + } + else if constexpr (compositionSwitchEnabled) + fluidState_.setRv(0.0); + } + + if (priVars.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Rvw) { + const auto& Rvw = priVars.makeEvaluation(Indices::waterSwitchIdx, timeIdx); + fluidState_.setRvw(Rvw); + } else { + if (FluidSystem::enableVaporizedWater()) { // Add Sg > 0? i.e. if only water set rv = 0) + const Evaluation& RvwSat = FluidSystem::saturatedVaporizationFactor(fluidState_, + gasPhaseIdx, + pvtRegionIdx); + fluidState_.setRvw(RvwSat); + } + } + + if (priVars.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Rsw) { + const auto& Rsw = priVars.makeEvaluation(Indices::waterSwitchIdx, timeIdx); + fluidState_.setRsw(Rsw); + } else { + if (FluidSystem::enableDissolvedGasInWater()) { + const Evaluation& RswSat = FluidSystem::saturatedDissolutionFactor(fluidState_, + waterPhaseIdx, + pvtRegionIdx); + fluidState_.setRsw(min(RswMax, RswSat)); + } + } + + typename FluidSystem::template ParameterCache paramCache; + paramCache.setRegionIndex(pvtRegionIdx); + if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) { + paramCache.setMaxOilSat(SoMax); + } + paramCache.updateAll(fluidState_); + + // compute the phase densities and transform the phase permeabilities into mobilities + int nmobilities = 1; + std::vector*> mobilities = {&mobility_}; + if (dirMob_) { + for (int i=0; i<3; i++) { + nmobilities += 1; + mobilities.push_back(&(dirMob_->getArray(i))); + } + } + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + const auto& b = FluidSystem::inverseFormationVolumeFactor(fluidState_, phaseIdx, pvtRegionIdx); + fluidState_.setInvB(phaseIdx, b); + const auto& mu = FluidSystem::viscosity(fluidState_, paramCache, phaseIdx); + for (int i = 0; i 0.0) { + Scalar rockRefPressure = problem.rockReferencePressure(globalSpaceIdx); + Evaluation x; + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + x = rockCompressibility*(fluidState_.pressure(oilPhaseIdx) - rockRefPressure); + } else if (FluidSystem::phaseIsActive(waterPhaseIdx)){ + x = rockCompressibility*(fluidState_.pressure(waterPhaseIdx) - rockRefPressure); + } else { + x = rockCompressibility*(fluidState_.pressure(gasPhaseIdx) - rockRefPressure); + } + porosity_ *= 1.0 + x + 0.5*x*x; + } + + // deal with water induced rock compaction + porosity_ *= problem.template rockCompPoroMultiplier(*this, globalSpaceIdx); + + // the MICP processes change the porosity + if constexpr (enableMICP){ + Evaluation biofilm_ = priVars.makeEvaluation(Indices::biofilmConcentrationIdx, timeIdx, linearizationType); + Evaluation calcite_ = priVars.makeEvaluation(Indices::calciteConcentrationIdx, timeIdx, linearizationType); + porosity_ += - biofilm_ - calcite_; + } + + // deal with salt-precipitation + if (enableSaltPrecipitation && priVars.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp) { + Evaluation Sp = priVars.makeEvaluation(Indices::saltConcentrationIdx, timeIdx); + porosity_ *= (1.0 - Sp); + } + + rockCompTransMultiplier_ = problem.template rockCompTransMultiplier(*this, globalSpaceIdx); + + asImp_().solventPvtUpdate_(elemCtx, dofIdx, timeIdx); + asImp_().zPvtUpdate_(); + asImp_().polymerPropertiesUpdate_(elemCtx, dofIdx, timeIdx); + asImp_().updateEnergyQuantities_(elemCtx, dofIdx, timeIdx, paramCache); + asImp_().foamPropertiesUpdate_(elemCtx, dofIdx, timeIdx); + asImp_().MICPPropertiesUpdate_(elemCtx, dofIdx, timeIdx); + asImp_().saltPropertiesUpdate_(elemCtx, dofIdx, timeIdx); + + // update the quantities which are required by the chosen + // velocity model + FluxIntensiveQuantities::update_(elemCtx, dofIdx, timeIdx); + + // update the diffusion specific quantities of the intensive quantities + DiffusionIntensiveQuantities::update_(fluidState_, paramCache, elemCtx, dofIdx, timeIdx); + + // update the dispersion specific quantities of the intensive quantities + DispersionIntensiveQuantities::update_(elemCtx, dofIdx, timeIdx); + +#ifndef NDEBUG + // some safety checks in debug mode + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + assert(isfinite(fluidState_.density(phaseIdx))); + assert(isfinite(fluidState_.saturation(phaseIdx))); + assert(isfinite(fluidState_.temperature(phaseIdx))); + assert(isfinite(fluidState_.pressure(phaseIdx))); + assert(isfinite(fluidState_.invB(phaseIdx))); + } + assert(isfinite(fluidState_.Rs())); + assert(isfinite(fluidState_.Rv())); + +#endif + } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::fluidState + */ + const FluidState& fluidState() const + { return fluidState_; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::mobility + */ + const Evaluation& mobility(unsigned phaseIdx) const + { return mobility_[phaseIdx]; } + + const Evaluation& mobility(unsigned phaseIdx, FaceDir::DirEnum facedir) const + { + using Dir = FaceDir::DirEnum; + if (dirMob_) { + switch(facedir) { + case Dir::XMinus: + case Dir::XPlus: + return dirMob_->mobilityX_[phaseIdx]; + case Dir::YMinus: + case Dir::YPlus: + return dirMob_->mobilityY_[phaseIdx]; + case Dir::ZMinus: + case Dir::ZPlus: + return dirMob_->mobilityZ_[phaseIdx]; + default: + throw std::runtime_error("Unexpected face direction"); + } + } + else { + return mobility_[phaseIdx]; + } + + } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::porosity + */ + const Evaluation& porosity() const + { return porosity_; } + + /*! + * The pressure-dependent transmissibility multiplier due to rock compressibility. + */ + const Evaluation& rockCompTransMultiplier() const + { return rockCompTransMultiplier_; } + + /*! + * \brief Returns the index of the PVT region used to calculate the thermodynamic + * quantities. + * + * This allows to specify different Pressure-Volume-Temperature (PVT) relations in + * different parts of the spatial domain. + */ + auto pvtRegionIndex() const + -> decltype(std::declval().pvtRegionIndex()) + { return fluidState_.pvtRegionIndex(); } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::relativePermeability + */ + Evaluation relativePermeability(unsigned phaseIdx) const + { + // warning: slow + return fluidState_.viscosity(phaseIdx)*mobility(phaseIdx); + } + + /*! + * \brief Returns the porosity of the rock at reference conditions. + * + * I.e., the porosity of rock which is not perturbed by pressure and temperature + * changes. + */ + Scalar referencePorosity() const + { return referencePorosity_; } + +private: + friend BlackOilSolventIntensiveQuantities; + friend BlackOilExtboIntensiveQuantities; + friend BlackOilPolymerIntensiveQuantities; + friend BlackOilEnergyIntensiveQuantities; + friend BlackOilFoamIntensiveQuantities; + friend BlackOilBrineIntensiveQuantities; + friend BlackOilMICPIntensiveQuantities; + + Implementation& asImp_() + { return *static_cast(this); } + + FluidState fluidState_; + Scalar referencePorosity_; + Evaluation porosity_; + Evaluation rockCompTransMultiplier_; + std::array mobility_; + + // Instead of writing a custom copy constructor and a custom assignment operator just to handle + // the dirMob_ unique ptr member variable when copying BlackOilIntensiveQuantites (see for example + // updateIntensitiveQuantities_() in fvbaseelementcontext.hh for a copy example) we write the below + // custom wrapper class CopyablePtr which wraps the unique ptr and makes it copyable. + // + // The advantage of this approach is that we avoid having to call all the base class copy constructors and + // assignment operators explicitly (which is needed when writing the custom copy constructor and assignment + // operators) which could become a maintenance burden. For example, when adding a new base class (if that should + // be needed sometime in the future) to BlackOilIntensiveQuantites we could forget to update the copy + // constructor and assignment operators. + // + // We want each copy of the BlackOilIntensiveQuantites to be unique, (TODO: why?) so we have to make a copy + // of the unique_ptr each time we copy construct or assign to it from another BlackOilIntensiveQuantites. + // (On the other hand, if a copy could share the ptr with the original, a shared_ptr could be used instead and the + // wrapper would not be needed) + DirectionalMobilityPtr dirMob_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoillocalresidual.hh b/opm/models/blackoil/blackoillocalresidual.hh new file mode 100644 index 00000000000..44110bc86af --- /dev/null +++ b/opm/models/blackoil/blackoillocalresidual.hh @@ -0,0 +1,372 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilLocalResidual + */ +#ifndef EWOMS_BLACK_OIL_LOCAL_RESIDUAL_HH +#define EWOMS_BLACK_OIL_LOCAL_RESIDUAL_HH + +#include "blackoilproperties.hh" +#include "blackoilsolventmodules.hh" +#include "blackoilextbomodules.hh" +#include "blackoilpolymermodules.hh" +#include "blackoilenergymodules.hh" +#include "blackoilfoammodules.hh" +#include "blackoilbrinemodules.hh" +#include "blackoildiffusionmodule.hh" +#include "blackoilmicpmodules.hh" +#include "blackoilconvectivemixingmodule.hh" +#include + +namespace Opm { +/*! + * \ingroup BlackOilModel + * + * \brief Calculates the local residual of the black oil model. + */ +template +class BlackOilLocalResidual : public GetPropType +{ + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using Indices = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using FluidSystem = GetPropType; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + + enum { gasCompIdx = FluidSystem::gasCompIdx }; + enum { oilCompIdx = FluidSystem::oilCompIdx }; + enum { waterCompIdx = FluidSystem::waterCompIdx }; + enum { compositionSwitchIdx = Indices::compositionSwitchIdx }; + + static const bool waterEnabled = Indices::waterEnabled; + static const bool gasEnabled = Indices::gasEnabled; + static const bool oilEnabled = Indices::oilEnabled; + static const bool compositionSwitchEnabled = (compositionSwitchIdx >= 0); + + static constexpr bool blackoilConserveSurfaceVolume = getPropValue(); + static constexpr bool enableEnergy = getPropValue(); + static constexpr bool enableDiffusion = getPropValue(); + static constexpr bool enableConvectiveMixing = getPropValue(); + + using Toolbox = MathToolbox; + using SolventModule = BlackOilSolventModule; + using ExtboModule = BlackOilExtboModule; + using PolymerModule = BlackOilPolymerModule; + using EnergyModule = BlackOilEnergyModule; + using FoamModule = BlackOilFoamModule; + using BrineModule = BlackOilBrineModule; + using DiffusionModule = BlackOilDiffusionModule; + using MICPModule = BlackOilMICPModule; + using ConvectiveMixingModule = BlackOilConvectiveMixingModule; + +public: + /*! + * \copydoc FvBaseLocalResidual::computeStorage + */ + template + void computeStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + // retrieve the intensive quantities for the SCV at the specified point in time + const IntensiveQuantities& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + const auto& fs = intQuants.fluidState(); + + storage = 0.0; + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + if (Indices::numPhases == 3) { // add trivial equation for the pseudo phase + unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); + if (timeIdx == 0) + storage[conti0EqIdx + activeCompIdx] = variable(0.0, conti0EqIdx + activeCompIdx); + else + storage[conti0EqIdx + activeCompIdx] = 0.0; + } + continue; + } + + unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); + LhsEval surfaceVolume = + Toolbox::template decay(fs.saturation(phaseIdx)) + * Toolbox::template decay(fs.invB(phaseIdx)) + * Toolbox::template decay(intQuants.porosity()); + + storage[conti0EqIdx + activeCompIdx] += surfaceVolume; + + // account for dissolved gas + if (phaseIdx == oilPhaseIdx && FluidSystem::enableDissolvedGas()) { + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + storage[conti0EqIdx + activeGasCompIdx] += + Toolbox::template decay(intQuants.fluidState().Rs()) + * surfaceVolume; + } + + // account for dissolved gas in water phase + if (phaseIdx == waterPhaseIdx && FluidSystem::enableDissolvedGasInWater()) { + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + storage[conti0EqIdx + activeGasCompIdx] += + Toolbox::template decay(intQuants.fluidState().Rsw()) + * surfaceVolume; + } + + // account for vaporized oil + if (phaseIdx == gasPhaseIdx && FluidSystem::enableVaporizedOil()) { + unsigned activeOilCompIdx = Indices::canonicalToActiveComponentIndex(oilCompIdx); + storage[conti0EqIdx + activeOilCompIdx] += + Toolbox::template decay(intQuants.fluidState().Rv()) + * surfaceVolume; + } + + // account for vaporized water + if (phaseIdx == gasPhaseIdx && FluidSystem::enableVaporizedWater()) { + unsigned activeWaterCompIdx = Indices::canonicalToActiveComponentIndex(waterCompIdx); + storage[conti0EqIdx + activeWaterCompIdx] += + Toolbox::template decay(intQuants.fluidState().Rvw()) + * surfaceVolume; + } + } + + adaptMassConservationQuantities_(storage, intQuants.pvtRegionIndex()); + + // deal with solvents (if present) + SolventModule::addStorage(storage, intQuants); + + // deal with zFracton (if present) + ExtboModule::addStorage(storage, intQuants); + + // deal with polymer (if present) + PolymerModule::addStorage(storage, intQuants); + + // deal with energy (if present) + EnergyModule::addStorage(storage, intQuants); + + // deal with foam (if present) + FoamModule::addStorage(storage, intQuants); + + // deal with salt (if present) + BrineModule::addStorage(storage, intQuants); + + // deal with micp (if present) + MICPModule::addStorage(storage, intQuants); + } + + /*! + * \copydoc FvBaseLocalResidual::computeFlux + */ + void computeFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + assert(timeIdx == 0); + + flux = 0.0; + + const ExtensiveQuantities& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + unsigned focusDofIdx = elemCtx.focusDofIndex(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + unsigned upIdx = static_cast(extQuants.upstreamIndex(phaseIdx)); + const IntensiveQuantities& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + unsigned pvtRegionIdx = up.pvtRegionIndex(); + if (upIdx == focusDofIdx) + evalPhaseFluxes_(flux, phaseIdx, pvtRegionIdx, extQuants, up.fluidState()); + else + evalPhaseFluxes_(flux, phaseIdx, pvtRegionIdx, extQuants, up.fluidState()); + } + // deal with solvents (if present) + SolventModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with zFracton (if present) + ExtboModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with polymer (if present) + PolymerModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with energy (if present) + EnergyModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with foam (if present) + FoamModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with salt (if present) + BrineModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with micp (if present) + MICPModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + DiffusionModule::addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with convective mixing (if enabled) + ConvectiveMixingModule::addConvectiveMixingFlux(flux,elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc FvBaseLocalResidual::computeSource + */ + void computeSource(RateVector& source, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + // retrieve the source term intrinsic to the problem + elemCtx.problem().source(source, elemCtx, dofIdx, timeIdx); + + // deal with MICP (if present) + MICPModule::addSource(source, elemCtx, dofIdx, timeIdx); + + // scale the source term of the energy equation + if (enableEnergy) + source[Indices::contiEnergyEqIdx] *= getPropValue(); + } + + /*! + * \brief Helper function to calculate the flux of mass in terms of conservation + * quantities via specific fluid phase over a face. + */ + template + static void evalPhaseFluxes_(RateVector& flux, + unsigned phaseIdx, + unsigned pvtRegionIdx, + const ExtensiveQuantities& extQuants, + const FluidState& upFs) + { + const auto& invB = getInvB_(upFs, phaseIdx, pvtRegionIdx); + const auto& surfaceVolumeFlux = invB*extQuants.volumeFlux(phaseIdx); + unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); + + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeCompIdx] += surfaceVolumeFlux; + else + flux[conti0EqIdx + activeCompIdx] += surfaceVolumeFlux*FluidSystem::referenceDensity(phaseIdx, pvtRegionIdx); + + if (phaseIdx == oilPhaseIdx) { + // dissolved gas (in the oil phase). + if (FluidSystem::enableDissolvedGas()) { + const auto& Rs = BlackOil::getRs_(upFs, pvtRegionIdx); + + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeGasCompIdx] += Rs*surfaceVolumeFlux; + else + flux[conti0EqIdx + activeGasCompIdx] += Rs*surfaceVolumeFlux*FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx); + } + } else if (phaseIdx == waterPhaseIdx) { + // dissolved gas (in the water phase). + if (FluidSystem::enableDissolvedGasInWater()) { + const auto& Rsw = BlackOil::getRsw_(upFs, pvtRegionIdx); + + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeGasCompIdx] += Rsw*surfaceVolumeFlux; + else + flux[conti0EqIdx + activeGasCompIdx] += Rsw*surfaceVolumeFlux*FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx); + } + } + else if (phaseIdx == gasPhaseIdx) { + // vaporized oil (in the gas phase). + if (FluidSystem::enableVaporizedOil()) { + const auto& Rv = BlackOil::getRv_(upFs, pvtRegionIdx); + + unsigned activeOilCompIdx = Indices::canonicalToActiveComponentIndex(oilCompIdx); + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeOilCompIdx] += Rv*surfaceVolumeFlux; + else + flux[conti0EqIdx + activeOilCompIdx] += Rv*surfaceVolumeFlux*FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx); + } + // vaporized water (in the gas phase). + if (FluidSystem::enableVaporizedWater()) { + const auto& Rvw = BlackOil::getRvw_(upFs, pvtRegionIdx); + + unsigned activeWaterCompIdx = Indices::canonicalToActiveComponentIndex(waterCompIdx); + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeWaterCompIdx] += Rvw*surfaceVolumeFlux; + else + flux[conti0EqIdx + activeWaterCompIdx] += Rvw*surfaceVolumeFlux*FluidSystem::referenceDensity(waterPhaseIdx, pvtRegionIdx); + } + } + } + + /*! + * \brief Helper function to convert the mass-related parts of a Dune::FieldVector + * that stores conservation quantities in terms of "surface-volume" to the + * conservation quantities used by the model. + * + * Depending on the value of the BlackoilConserveSurfaceVolume property, the model + * either conserves mass by means of "surface volume" of the components or mass + * directly. In the former case, this method is a no-op; in the latter, the values + * passed are multiplied by their respective pure component's density at surface + * conditions. + */ + template + static void adaptMassConservationQuantities_(Dune::FieldVector& container, unsigned pvtRegionIdx) + { + if (blackoilConserveSurfaceVolume) + return; + + // convert "surface volume" to mass. this is complicated a bit by the fact that + // not all phases are necessarily enabled. (we here assume that if a fluid phase + // is disabled, its respective "main" component is not considered as well.) + + if (waterEnabled) { + unsigned activeWaterCompIdx = Indices::canonicalToActiveComponentIndex(waterCompIdx); + container[conti0EqIdx + activeWaterCompIdx] *= + FluidSystem::referenceDensity(waterPhaseIdx, pvtRegionIdx); + } + + if (gasEnabled) { + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + container[conti0EqIdx + activeGasCompIdx] *= + FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx); + } + + if (oilEnabled) { + unsigned activeOilCompIdx = Indices::canonicalToActiveComponentIndex(oilCompIdx); + container[conti0EqIdx + activeOilCompIdx] *= + FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx); + } + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoillocalresidualtpfa.hh b/opm/models/blackoil/blackoillocalresidualtpfa.hh new file mode 100644 index 00000000000..42dcee72572 --- /dev/null +++ b/opm/models/blackoil/blackoillocalresidualtpfa.hh @@ -0,0 +1,866 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilLocalResidual + */ +#ifndef EWOMS_BLACK_OIL_LOCAL_TPFA_RESIDUAL_HH +#define EWOMS_BLACK_OIL_LOCAL_TPFA_RESIDUAL_HH + +#include "blackoilproperties.hh" +#include "blackoilsolventmodules.hh" +#include "blackoilextbomodules.hh" +#include "blackoilpolymermodules.hh" +#include "blackoilenergymodules.hh" +#include "blackoilfoammodules.hh" +#include "blackoilbrinemodules.hh" +#include "blackoildiffusionmodule.hh" +#include "blackoilconvectivemixingmodule.hh" +#include "blackoildispersionmodule.hh" +#include "blackoilmicpmodules.hh" +#include +#include +#include + +namespace Opm { +/*! + * \ingroup BlackOilModel + * + * \brief Calculates the local residual of the black oil model. + */ +template +class BlackOilLocalResidualTPFA : public GetPropType +{ + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using Indices = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using FluidSystem = GetPropType; + using GridView = GetPropType; + using Problem = GetPropType; + using FluidState = typename IntensiveQuantities::FluidState; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + enum { dimWorld = GridView::dimensionworld }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + + enum { gasCompIdx = FluidSystem::gasCompIdx }; + enum { oilCompIdx = FluidSystem::oilCompIdx }; + enum { waterCompIdx = FluidSystem::waterCompIdx }; + enum { compositionSwitchIdx = Indices::compositionSwitchIdx }; + + static const bool waterEnabled = Indices::waterEnabled; + static const bool gasEnabled = Indices::gasEnabled; + static const bool oilEnabled = Indices::oilEnabled; + static const bool compositionSwitchEnabled = (compositionSwitchIdx >= 0); + + static constexpr bool blackoilConserveSurfaceVolume = getPropValue(); + + static constexpr bool enableSolvent = getPropValue(); + static constexpr bool enableExtbo = getPropValue(); + static constexpr bool enablePolymer = getPropValue(); + static constexpr bool enableEnergy = getPropValue(); + static constexpr bool enableFoam = getPropValue(); + static constexpr bool enableBrine = getPropValue(); + static constexpr bool enableDiffusion = getPropValue(); + static constexpr bool enableDispersion = getPropValue(); + static constexpr bool enableConvectiveMixing = getPropValue(); + static constexpr bool enableMICP = getPropValue(); + + using SolventModule = BlackOilSolventModule; + using ExtboModule = BlackOilExtboModule; + using PolymerModule = BlackOilPolymerModule; + using EnergyModule = BlackOilEnergyModule; + using FoamModule = BlackOilFoamModule; + using BrineModule = BlackOilBrineModule; + using DiffusionModule = BlackOilDiffusionModule; + using ConvectiveMixingModule = BlackOilConvectiveMixingModule; + using ConvectiveMixingModuleParam = typename ConvectiveMixingModule::ConvectiveMixingModuleParam; + + using DispersionModule = BlackOilDispersionModule; + using MICPModule = BlackOilMICPModule; + + using Toolbox = MathToolbox; + +public: + + struct ResidualNBInfo + { + double trans; + double faceArea; + double thpres; + double dZg; + FaceDir::DirEnum faceDir; + double Vin; + double Vex; + double inAlpha; + double outAlpha; + double diffusivity; + double dispersivity; + }; + + struct ModuleParams { + ConvectiveMixingModuleParam convectiveMixingModuleParam; + }; + + /*! + * \copydoc FvBaseLocalResidual::computeStorage + */ + template + void computeStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + const IntensiveQuantities& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + computeStorage(storage, + intQuants); + } + + template + static void computeStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants) + { + OPM_TIMEBLOCK_LOCAL(computeStorage); + // retrieve the intensive quantities for the SCV at the specified point in time + const auto& fs = intQuants.fluidState(); + storage = 0.0; + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); + LhsEval surfaceVolume = + Toolbox::template decay(fs.saturation(phaseIdx)) + * Toolbox::template decay(fs.invB(phaseIdx)) + * Toolbox::template decay(intQuants.porosity()); + + storage[conti0EqIdx + activeCompIdx] += surfaceVolume; + + // account for dissolved gas + if (phaseIdx == oilPhaseIdx && FluidSystem::enableDissolvedGas()) { + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + storage[conti0EqIdx + activeGasCompIdx] += + Toolbox::template decay(intQuants.fluidState().Rs()) + * surfaceVolume; + } + + // account for dissolved gas in water + if (phaseIdx == waterPhaseIdx && FluidSystem::enableDissolvedGasInWater()) { + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + storage[conti0EqIdx + activeGasCompIdx] += + Toolbox::template decay(intQuants.fluidState().Rsw()) + * surfaceVolume; + } + + // account for vaporized oil + if (phaseIdx == gasPhaseIdx && FluidSystem::enableVaporizedOil()) { + unsigned activeOilCompIdx = Indices::canonicalToActiveComponentIndex(oilCompIdx); + storage[conti0EqIdx + activeOilCompIdx] += + Toolbox::template decay(intQuants.fluidState().Rv()) + * surfaceVolume; + } + + // account for vaporized water + if (phaseIdx == gasPhaseIdx && FluidSystem::enableVaporizedWater()) { + unsigned activeWaterCompIdx = Indices::canonicalToActiveComponentIndex(waterCompIdx); + storage[conti0EqIdx + activeWaterCompIdx] += + Toolbox::template decay(intQuants.fluidState().Rvw()) + * surfaceVolume; + } + } + + adaptMassConservationQuantities_(storage, intQuants.pvtRegionIndex()); + + // deal with solvents (if present) + SolventModule::addStorage(storage, intQuants); + + // deal with zFracton (if present) + ExtboModule::addStorage(storage, intQuants); + + // deal with polymer (if present) + PolymerModule::addStorage(storage, intQuants); + + // deal with energy (if present) + EnergyModule::addStorage(storage, intQuants); + + // deal with foam (if present) + FoamModule::addStorage(storage, intQuants); + + // deal with salt (if present) + BrineModule::addStorage(storage, intQuants); + + // deal with micp (if present) + MICPModule::addStorage(storage, intQuants); + } + + /*! + * This function works like the ElementContext-based version with + * one main difference: The darcy flux is calculated here, not + * read from the extensive quantities of the element context. + */ + static void computeFlux(RateVector& flux, + RateVector& darcy, + const unsigned globalIndexIn, + const unsigned globalIndexEx, + const IntensiveQuantities& intQuantsIn, + const IntensiveQuantities& intQuantsEx, + const ResidualNBInfo& nbInfo, + const ModuleParams& moduleParams) + { + OPM_TIMEBLOCK_LOCAL(computeFlux); + flux = 0.0; + darcy = 0.0; + + calculateFluxes_(flux, + darcy, + intQuantsIn, + intQuantsEx, + globalIndexIn, + globalIndexEx, + nbInfo, + moduleParams); + } + + // This function demonstrates compatibility with the ElementContext-based interface. + // Actually using it will lead to double work since the element context already contains + // fluxes through its stored ExtensiveQuantities. + static void computeFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) + { + OPM_TIMEBLOCK_LOCAL(computeFlux); + assert(timeIdx == 0); + + flux = 0.0; + RateVector darcy = 0.0; + // need for dary flux calculation + const auto& problem = elemCtx.problem(); + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& scvf = stencil.interiorFace(scvfIdx); + + unsigned interiorDofIdx = scvf.interiorIndex(); + unsigned exteriorDofIdx = scvf.exteriorIndex(); + assert(interiorDofIdx != exteriorDofIdx); + + // unsigned I = stencil.globalSpaceIndex(interiorDofIdx); + // unsigned J = stencil.globalSpaceIndex(exteriorDofIdx); + Scalar Vin = elemCtx.dofVolume(interiorDofIdx, /*timeIdx=*/0); + Scalar Vex = elemCtx.dofVolume(exteriorDofIdx, /*timeIdx=*/0); + const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx); + const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx); + Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); + Scalar faceArea = scvf.area(); + const auto faceDir = faceDirFromDirId(scvf.dirId()); + Scalar thpres = problem.thresholdPressure(globalIndexIn, globalIndexEx); + + // estimate the gravity correction: for performance reasons we use a simplified + // approach for this flux module that assumes that gravity is constant and always + // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) + const Scalar g = problem.gravity()[dimWorld - 1]; + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx, timeIdx); + + // this is quite hacky because the dune grid interface does not provide a + // cellCenterDepth() method (so we ask the problem to provide it). The "good" + // solution would be to take the Z coordinate of the element centroids, but since + // ECL seems to like to be inconsistent on that front, it needs to be done like + // here... + const Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx, timeIdx); + const Scalar zEx = problem.dofCenterDepth(elemCtx, exteriorDofIdx, timeIdx); + // the distances from the DOF's depths. (i.e., the additional depth of the + // exterior DOF) + const Scalar distZ = zIn - zEx; + // for thermal harmonic mean of half trans + const Scalar inAlpha = problem.thermalHalfTransmissibility(globalIndexIn, globalIndexEx); + const Scalar outAlpha = problem.thermalHalfTransmissibility(globalIndexEx, globalIndexIn); + const Scalar diffusivity = problem.diffusivity(globalIndexEx, globalIndexIn); + const Scalar dispersivity = problem.dispersivity(globalIndexEx, globalIndexIn); + + const ResidualNBInfo res_nbinfo {trans, faceArea, thpres, distZ * g, faceDir, Vin, Vex, inAlpha, outAlpha, diffusivity, dispersivity}; + + calculateFluxes_(flux, + darcy, + intQuantsIn, + intQuantsEx, + globalIndexIn, + globalIndexEx, + res_nbinfo, + problem.moduleParams()); + } + + static void calculateFluxes_(RateVector& flux, + RateVector& darcy, + const IntensiveQuantities& intQuantsIn, + const IntensiveQuantities& intQuantsEx, + const unsigned& globalIndexIn, + const unsigned& globalIndexEx, + const ResidualNBInfo& nbInfo, + const ModuleParams& moduleParams) + { + OPM_TIMEBLOCK_LOCAL(calculateFluxes); + const Scalar Vin = nbInfo.Vin; + const Scalar Vex = nbInfo.Vex; + const Scalar distZg = nbInfo.dZg; + const Scalar thpres = nbInfo.thpres; + const Scalar trans = nbInfo.trans; + const Scalar faceArea = nbInfo.faceArea; + FaceDir::DirEnum facedir = nbInfo.faceDir; + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + // darcy flux calculation + short dnIdx; + // + short upIdx; + // fake intices should only be used to get upwind anc compatibility with old functions + short interiorDofIdx = 0; // NB + short exteriorDofIdx = 1; // NB + Evaluation pressureDifference; + ExtensiveQuantities::calculatePhasePressureDiff_(upIdx, + dnIdx, + pressureDifference, + intQuantsIn, + intQuantsEx, + phaseIdx, // input + interiorDofIdx, // input + exteriorDofIdx, // input + Vin, + Vex, + globalIndexIn, + globalIndexEx, + distZg, + thpres, + moduleParams); + + + + const IntensiveQuantities& up = (upIdx == interiorDofIdx) ? intQuantsIn : intQuantsEx; + unsigned globalUpIndex = (upIdx == interiorDofIdx) ? globalIndexIn : globalIndexEx; + // Use arithmetic average (more accurate with harmonic, but that requires recomputing the transmissbility) + const Evaluation transMult = (intQuantsIn.rockCompTransMultiplier() + Toolbox::value(intQuantsEx.rockCompTransMultiplier()))/2; + Evaluation darcyFlux; + if (globalUpIndex == globalIndexIn) { + darcyFlux = pressureDifference * up.mobility(phaseIdx, facedir) * transMult * (-trans / faceArea); + } else { + darcyFlux = pressureDifference * + (Toolbox::value(up.mobility(phaseIdx, facedir)) * transMult * (-trans / faceArea)); + } + + unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); + darcy[conti0EqIdx + activeCompIdx] = darcyFlux.value() * faceArea; // NB! For the FLORES fluxes without derivatives + + unsigned pvtRegionIdx = up.pvtRegionIndex(); + // if (upIdx == globalFocusDofIdx){ + if (globalUpIndex == globalIndexIn) { + const auto& invB + = getInvB_(up.fluidState(), phaseIdx, pvtRegionIdx); + const auto& surfaceVolumeFlux = invB * darcyFlux; + evalPhaseFluxes_( + flux, phaseIdx, pvtRegionIdx, surfaceVolumeFlux, up.fluidState()); + if constexpr (enableEnergy) { + EnergyModule::template addPhaseEnthalpyFluxes_( + flux, phaseIdx, darcyFlux, up.fluidState()); + } + } else { + const auto& invB = getInvB_(up.fluidState(), phaseIdx, pvtRegionIdx); + const auto& surfaceVolumeFlux = invB * darcyFlux; + evalPhaseFluxes_( + flux, phaseIdx, pvtRegionIdx, surfaceVolumeFlux, up.fluidState()); + if constexpr (enableEnergy) { + EnergyModule::template + addPhaseEnthalpyFluxes_ + (flux,phaseIdx,darcyFlux, up.fluidState()); + } + } + + } + + // deal with solvents (if present) + static_assert(!enableSolvent, "Relevant computeFlux() method must be implemented for this module before enabling."); + // SolventModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with zFracton (if present) + static_assert(!enableExtbo, "Relevant computeFlux() method must be implemented for this module before enabling."); + // ExtboModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with polymer (if present) + static_assert(!enablePolymer, "Relevant computeFlux() method must be implemented for this module before enabling."); + // PolymerModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with convective mixing + if constexpr(enableConvectiveMixing) { + ConvectiveMixingModule::addConvectiveMixingFlux(flux, + intQuantsIn, + intQuantsEx, + globalIndexIn, + globalIndexEx, + nbInfo.dZg, + nbInfo.trans, + nbInfo.faceArea, + moduleParams.convectiveMixingModuleParam); + } + + // deal with energy (if present) + if constexpr(enableEnergy){ + const Scalar inAlpha = nbInfo.inAlpha; + const Scalar outAlpha = nbInfo.outAlpha; + Evaluation heatFlux; + { + short interiorDofIdx = 0; // NB + short exteriorDofIdx = 1; // NB + + EnergyModule::ExtensiveQuantities::template updateEnergy(heatFlux, + interiorDofIdx, // focusDofIndex, + interiorDofIdx, + exteriorDofIdx, + intQuantsIn, + intQuantsEx, + intQuantsIn.fluidState(), + intQuantsEx.fluidState(), + inAlpha, + outAlpha, + faceArea); + } + EnergyModule::addHeatFlux(flux, heatFlux); + } + // NB need to be tha last energy call since it does scaling + // EnergyModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with foam (if present) + static_assert(!enableFoam, "Relevant computeFlux() method must be implemented for this module before enabling."); + // FoamModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + // deal with salt (if present) + static_assert(!enableBrine, "Relevant computeFlux() method must be implemented for this module before enabling."); + // BrineModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + + + // deal with diffusion (if present). opm-models expects per area flux (added in the tmpdiffusivity). + if constexpr(enableDiffusion){ + typename DiffusionModule::ExtensiveQuantities::EvaluationArray effectiveDiffusionCoefficient; + DiffusionModule::ExtensiveQuantities::update(effectiveDiffusionCoefficient, intQuantsIn, intQuantsEx); + const Scalar diffusivity = nbInfo.diffusivity; + const Scalar tmpdiffusivity = diffusivity / faceArea; + DiffusionModule::addDiffusiveFlux(flux, + intQuantsIn.fluidState(), + intQuantsEx.fluidState(), + tmpdiffusivity, + effectiveDiffusionCoefficient); + + } + // deal with dispersion (if present). opm-models expects per area flux (added in the tmpdispersivity). + if constexpr(enableDispersion){ + typename DispersionModule::ExtensiveQuantities::ScalarArray normVelocityAvg; + DispersionModule::ExtensiveQuantities::update(normVelocityAvg, intQuantsIn, intQuantsEx); + const Scalar dispersivity = nbInfo.dispersivity; + const Scalar tmpdispersivity = dispersivity / faceArea; + DispersionModule::addDispersiveFlux(flux, + intQuantsIn.fluidState(), + intQuantsEx.fluidState(), + tmpdispersivity, + normVelocityAvg); + + } + // deal with micp (if present) + static_assert(!enableMICP, "Relevant computeFlux() method must be implemented for this module before enabling."); + // MICPModule::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + } + + template + static void computeBoundaryFlux(RateVector& bdyFlux, + const Problem& problem, + const BoundaryConditionData& bdyInfo, + const IntensiveQuantities& insideIntQuants, + unsigned globalSpaceIdx) + { + if (bdyInfo.type == BCType::NONE) { + bdyFlux = 0.0; + } else if (bdyInfo.type == BCType::RATE) { + computeBoundaryFluxRate(bdyFlux, bdyInfo); + } else if (bdyInfo.type == BCType::FREE || bdyInfo.type == BCType::DIRICHLET) { + computeBoundaryFluxFree(problem, bdyFlux, bdyInfo, insideIntQuants, globalSpaceIdx); + } else if (bdyInfo.type == BCType::THERMAL) { + computeBoundaryThermal(problem, bdyFlux, bdyInfo, insideIntQuants, globalSpaceIdx); + } else { + throw std::logic_error("Unknown boundary condition type " + std::to_string(static_cast(bdyInfo.type)) + " in computeBoundaryFlux()." ); + } + } + + template + static void computeBoundaryFluxRate(RateVector& bdyFlux, + const BoundaryConditionData& bdyInfo) + { + bdyFlux.setMassRate(bdyInfo.massRate, bdyInfo.pvtRegionIdx); + } + + template + static void computeBoundaryFluxFree(const Problem& problem, + RateVector& bdyFlux, + const BoundaryConditionData& bdyInfo, + const IntensiveQuantities& insideIntQuants, + unsigned globalSpaceIdx) + { + OPM_TIMEBLOCK_LOCAL(computeBoundaryFluxFree); + std::array upIdx; + std::array dnIdx; + std::array volumeFlux; + std::array pressureDifference; + + ExtensiveQuantities::calculateBoundaryGradients_(problem, + globalSpaceIdx, + insideIntQuants, + bdyInfo.boundaryFaceIndex, + bdyInfo.faceArea, + bdyInfo.faceZCoord, + bdyInfo.exFluidState, + upIdx, + dnIdx, + volumeFlux, + pressureDifference); + + //////// + // advective fluxes of all components in all phases + //////// + bdyFlux = 0.0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + const auto& pBoundary = bdyInfo.exFluidState.pressure(phaseIdx); + const Evaluation& pInside = insideIntQuants.fluidState().pressure(phaseIdx); + const unsigned pvtRegionIdx = insideIntQuants.pvtRegionIndex(); + + RateVector tmp(0.0); + const auto& darcyFlux = volumeFlux[phaseIdx]; + // mass conservation + if (pBoundary < pInside) { + // outflux + const auto& invB = getInvB_(insideIntQuants.fluidState(), phaseIdx, pvtRegionIdx); + Evaluation surfaceVolumeFlux = invB * darcyFlux; + evalPhaseFluxes_(tmp, + phaseIdx, + insideIntQuants.pvtRegionIndex(), + surfaceVolumeFlux, + insideIntQuants.fluidState()); + if constexpr (enableEnergy) { + EnergyModule::template + addPhaseEnthalpyFluxes_ + (tmp, phaseIdx, darcyFlux, insideIntQuants.fluidState()); + } + } else if (pBoundary > pInside) { + // influx + using ScalarFluidState = decltype(bdyInfo.exFluidState); + const auto& invB = getInvB_(bdyInfo.exFluidState, phaseIdx, pvtRegionIdx); + Evaluation surfaceVolumeFlux = invB * darcyFlux; + evalPhaseFluxes_(tmp, + phaseIdx, + insideIntQuants.pvtRegionIndex(), + surfaceVolumeFlux, + bdyInfo.exFluidState); + if constexpr (enableEnergy) { + EnergyModule::template + addPhaseEnthalpyFluxes_ + (tmp, + phaseIdx, + darcyFlux, + bdyInfo.exFluidState); + } + } + + for (unsigned i = 0; i < tmp.size(); ++i) { + bdyFlux[i] += tmp[i]; + } + } + + // conductive heat flux from boundary + if constexpr(enableEnergy){ + Evaluation heatFlux; + // avoid overload of functions with same numeber of elements in eclproblem + Scalar alpha = problem.eclTransmissibilities().thermalHalfTransBoundary(globalSpaceIdx, bdyInfo.boundaryFaceIndex); + unsigned inIdx = 0;//dummy + // always calculated with derivatives of this cell + EnergyModule::ExtensiveQuantities::template updateEnergyBoundary(heatFlux, + insideIntQuants, + /*focusDofIndex*/ inIdx, + inIdx, + alpha, + bdyInfo.exFluidState); + EnergyModule::addHeatFlux(bdyFlux, heatFlux); + } + + static_assert(!enableSolvent, "Relevant treatment of boundary conditions must be implemented before enabling."); + static_assert(!enablePolymer, "Relevant treatment of boundary conditions must be implemented before enabling."); + static_assert(!enableMICP, "Relevant treatment of boundary conditions must be implemented before enabling."); + + // make sure that the right mass conservation quantities are used + adaptMassConservationQuantities_(bdyFlux, insideIntQuants.pvtRegionIndex()); + +#ifndef NDEBUG + for (unsigned i = 0; i < numEq; ++i) { + Valgrind::CheckDefined(bdyFlux[i]); + } + Valgrind::CheckDefined(bdyFlux); +#endif + } + + template + static void computeBoundaryThermal(const Problem& problem, + RateVector& bdyFlux, + const BoundaryConditionData& bdyInfo, + const IntensiveQuantities& insideIntQuants, + [[maybe_unused]] unsigned globalSpaceIdx) + { + OPM_TIMEBLOCK_LOCAL(computeBoundaryThermal); + // only heat is allowed to flow through this boundary + bdyFlux = 0.0; + + // conductive heat flux from boundary + if constexpr(enableEnergy){ + Evaluation heatFlux; + // avoid overload of functions with same numeber of elements in eclproblem + Scalar alpha = problem.eclTransmissibilities().thermalHalfTransBoundary(globalSpaceIdx, bdyInfo.boundaryFaceIndex); + unsigned inIdx = 0;//dummy + // always calculated with derivatives of this cell + EnergyModule::ExtensiveQuantities::template updateEnergyBoundary(heatFlux, + insideIntQuants, + /*focusDofIndex*/ inIdx, + inIdx, + alpha, + bdyInfo.exFluidState); + EnergyModule::addHeatFlux(bdyFlux, heatFlux); + } + +#ifndef NDEBUG + for (unsigned i = 0; i < numEq; ++i) { + Valgrind::CheckDefined(bdyFlux[i]); + } + Valgrind::CheckDefined(bdyFlux); +#endif + } + + static void computeSource(RateVector& source, + const Problem& problem, + unsigned globalSpaceIdex, + unsigned timeIdx) + { + OPM_TIMEBLOCK_LOCAL(computeSource); + // retrieve the source term intrinsic to the problem + problem.source(source, globalSpaceIdex, timeIdx); + + // deal with MICP (if present) + // deal with micp (if present) + static_assert(!enableMICP, "Relevant addSource() method must be implemented for this module before enabling."); + // MICPModule::addSource(source, elemCtx, dofIdx, timeIdx); + + // scale the source term of the energy equation + if (enableEnergy) + source[Indices::contiEnergyEqIdx] *= getPropValue(); + } + + static void computeSourceDense(RateVector& source, + const Problem& problem, + unsigned globalSpaceIdex, + unsigned timeIdx) + { + source = 0.0; + problem.addToSourceDense(source, globalSpaceIdex, timeIdx); + + // deal with MICP (if present) + // deal with micp (if present) + static_assert(!enableMICP, "Relevant addSource() method must be implemented for this module before enabling."); + // MICPModule::addSource(source, elemCtx, dofIdx, timeIdx); + + // scale the source term of the energy equation + if (enableEnergy) + source[Indices::contiEnergyEqIdx] *= getPropValue(); + } + + /*! + * \copydoc FvBaseLocalResidual::computeSource + */ + void computeSource(RateVector& source, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + OPM_TIMEBLOCK_LOCAL(computeSource); + // retrieve the source term intrinsic to the problem + elemCtx.problem().source(source, elemCtx, dofIdx, timeIdx); + + // deal with MICP (if present) + MICPModule::addSource(source, elemCtx, dofIdx, timeIdx); + + // scale the source term of the energy equation + if constexpr(enableEnergy) + source[Indices::contiEnergyEqIdx] *= getPropValue(); + } + + template + static void evalPhaseFluxes_(RateVector& flux, + unsigned phaseIdx, + unsigned pvtRegionIdx, + const ExtensiveQuantities& extQuants, + const FluidState& upFs) + { + + const auto& invB = getInvB_(upFs, phaseIdx, pvtRegionIdx); + const auto& surfaceVolumeFlux = invB * extQuants.volumeFlux(phaseIdx); + evalPhaseFluxes_(flux, phaseIdx, pvtRegionIdx, surfaceVolumeFlux, upFs); + } + + /*! + * \brief Helper function to calculate the flux of mass in terms of conservation + * quantities via specific fluid phase over a face. + */ + template + static void evalPhaseFluxes_(RateVector& flux, + unsigned phaseIdx, + unsigned pvtRegionIdx, + const Eval& surfaceVolumeFlux, + const FluidState& upFs) + { + unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); + + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeCompIdx] += surfaceVolumeFlux; + else + flux[conti0EqIdx + activeCompIdx] += surfaceVolumeFlux*FluidSystem::referenceDensity(phaseIdx, pvtRegionIdx); + + if (phaseIdx == oilPhaseIdx) { + // dissolved gas (in the oil phase). + if (FluidSystem::enableDissolvedGas()) { + const auto& Rs = BlackOil::getRs_(upFs, pvtRegionIdx); + + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeGasCompIdx] += Rs*surfaceVolumeFlux; + else + flux[conti0EqIdx + activeGasCompIdx] += Rs*surfaceVolumeFlux*FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx); + } + } else if (phaseIdx == waterPhaseIdx) { + // dissolved gas (in the water phase). + if (FluidSystem::enableDissolvedGasInWater()) { + const auto& Rsw = BlackOil::getRsw_(upFs, pvtRegionIdx); + + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeGasCompIdx] += Rsw*surfaceVolumeFlux; + else + flux[conti0EqIdx + activeGasCompIdx] += Rsw*surfaceVolumeFlux*FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx); + } + } + else if (phaseIdx == gasPhaseIdx) { + // vaporized oil (in the gas phase). + if (FluidSystem::enableVaporizedOil()) { + const auto& Rv = BlackOil::getRv_(upFs, pvtRegionIdx); + + unsigned activeOilCompIdx = Indices::canonicalToActiveComponentIndex(oilCompIdx); + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeOilCompIdx] += Rv*surfaceVolumeFlux; + else + flux[conti0EqIdx + activeOilCompIdx] += Rv*surfaceVolumeFlux*FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx); + } + // vaporized water (in the gas phase). + if (FluidSystem::enableVaporizedWater()) { + const auto& Rvw = BlackOil::getRvw_(upFs, pvtRegionIdx); + + unsigned activeWaterCompIdx = Indices::canonicalToActiveComponentIndex(waterCompIdx); + if (blackoilConserveSurfaceVolume) + flux[conti0EqIdx + activeWaterCompIdx] += Rvw*surfaceVolumeFlux; + else + flux[conti0EqIdx + activeWaterCompIdx] += Rvw*surfaceVolumeFlux*FluidSystem::referenceDensity(waterPhaseIdx, pvtRegionIdx); + } + } + } + + /*! + * \brief Helper function to convert the mass-related parts of a Dune::FieldVector + * that stores conservation quantities in terms of "surface-volume" to the + * conservation quantities used by the model. + * + * Depending on the value of the BlackoilConserveSurfaceVolume property, the model + * either conserves mass by means of "surface volume" of the components or mass + * directly. In the former case, this method is a no-op; in the latter, the values + * passed are multiplied by their respective pure component's density at surface + * conditions. + */ + template + static void adaptMassConservationQuantities_(Dune::FieldVector& container, unsigned pvtRegionIdx) + { + if (blackoilConserveSurfaceVolume) + return; + + // convert "surface volume" to mass. this is complicated a bit by the fact that + // not all phases are necessarily enabled. (we here assume that if a fluid phase + // is disabled, its respective "main" component is not considered as well.) + + if (waterEnabled) { + unsigned activeWaterCompIdx = Indices::canonicalToActiveComponentIndex(waterCompIdx); + container[conti0EqIdx + activeWaterCompIdx] *= + FluidSystem::referenceDensity(waterPhaseIdx, pvtRegionIdx); + } + + if (gasEnabled) { + unsigned activeGasCompIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + container[conti0EqIdx + activeGasCompIdx] *= + FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx); + } + + if (oilEnabled) { + unsigned activeOilCompIdx = Indices::canonicalToActiveComponentIndex(oilCompIdx); + container[conti0EqIdx + activeOilCompIdx] *= + FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx); + } + } + + + static FaceDir::DirEnum faceDirFromDirId(const int dirId) + { + // NNC does not have a direction + if (dirId < 0 ) { + return FaceDir::DirEnum::Unknown; + } + return FaceDir::FromIntersectionIndex(dirId); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilmicpmodules.hh b/opm/models/blackoil/blackoilmicpmodules.hh new file mode 100644 index 00000000000..da49371a150 --- /dev/null +++ b/opm/models/blackoil/blackoilmicpmodules.hh @@ -0,0 +1,596 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the classes required to extend the black-oil model by MICP. + */ +#ifndef EWOMS_BLACK_OIL_MICP_MODULE_HH +#define EWOMS_BLACK_OIL_MICP_MODULE_HH + +#include "blackoilproperties.hh" + +#include +#include + +#if HAVE_ECL_INPUT +#include +#include +#endif + +#include + +#include +#include +#include +#include + +namespace Opm { +/*! + * \ingroup BlackOil + * \brief Contains the high level supplements required to extend the black oil + * model by MICP. + */ +template ()> +class BlackOilMICPModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Model = GetPropType; + using Simulator = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + + using Toolbox = MathToolbox; + + static constexpr unsigned microbialConcentrationIdx = Indices::microbialConcentrationIdx; + static constexpr unsigned oxygenConcentrationIdx = Indices::oxygenConcentrationIdx; + static constexpr unsigned ureaConcentrationIdx = Indices::ureaConcentrationIdx; + static constexpr unsigned biofilmConcentrationIdx = Indices::biofilmConcentrationIdx; + static constexpr unsigned calciteConcentrationIdx = Indices::calciteConcentrationIdx; + static constexpr unsigned contiMicrobialEqIdx = Indices::contiMicrobialEqIdx; + static constexpr unsigned contiOxygenEqIdx = Indices::contiOxygenEqIdx; + static constexpr unsigned contiUreaEqIdx = Indices::contiUreaEqIdx; + static constexpr unsigned contiBiofilmEqIdx = Indices::contiBiofilmEqIdx; + static constexpr unsigned contiCalciteEqIdx = Indices::contiCalciteEqIdx; + static constexpr unsigned waterPhaseIdx = FluidSystem::waterPhaseIdx; + + static constexpr unsigned enableMICP = enableMICPV; + + static constexpr unsigned numEq = getPropValue(); + +public: + +#if HAVE_ECL_INPUT + // + //* \brief Initialize all internal data structures needed by the MICP module + // + static void initFromState(const EclipseState& eclState) + { + // some sanity checks: if MICP is enabled, the MICP keyword must be + // present, if MICP is disabled the keyword must not be present. + if (enableMICP && !eclState.runspec().micp()) { + throw std::runtime_error("Non-trivial MICP treatment requested at compile time, but " + "the deck does not contain the MICP keyword"); + } + else if (!enableMICP && eclState.runspec().micp()) { + throw std::runtime_error("MICP treatment disabled at compile time, but the deck " + "contains the MICP keyword"); + } + + if (!eclState.runspec().micp()) + return; // MICP treatment is supposed to be disabled*/ + + // initialize the objects which deal with the MICPpara keyword + const auto& MICPpara = eclState.getMICPpara(); + setMICPpara(MICPpara.getDensityBiofilm(), + MICPpara.getDensityCalcite(), + MICPpara.getDetachmentRate(), + MICPpara.getCriticalPorosity(), + MICPpara.getFittingFactor(), + MICPpara.getHalfVelocityOxygen(), + MICPpara.getHalfVelocityUrea(), + MICPpara.getMaximumGrowthRate(), + MICPpara.getMaximumUreaUtilization(), + MICPpara.getMicrobialAttachmentRate(), + MICPpara.getMicrobialDeathRate(), + MICPpara.getMinimumPermeability(), + MICPpara.getOxygenConsumptionFactor(), + MICPpara.getYieldGrowthCoefficient(), + MICPpara.getMaximumOxygenConcentration(), + MICPpara.getMaximumUreaConcentration(), + MICPpara.getToleranceBeforeClogging()); + // obtain the porosity for the clamp in the blackoilnewtonmethod + if constexpr (std::is_same_v) { + const auto phi = eclState.fieldProps().get_double("PORO"); + params_.phi_.resize(phi.size()); + std::copy(phi.begin(), phi.end(), params_.phi_.begin()); + } else { + params_.phi_ = eclState.fieldProps().get_double("PORO"); + } + } +#endif + + /*! + * \brief The simulator stops if "clogging" has been (almost) reached in any of the cells. + * + * I.e., porosity - biofilm - calcite < tol_clgg, where tol_clgg is a given tolerance. In the + * implemented model a permebaility-porosity relatonship is used where a minimum + * permeability value is reached if porosity - biofilm - calcite < phi_crit. + */ + static void checkCloggingMICP(const Model& model, const Scalar phi, unsigned dofIdx) + { + const PrimaryVariables& priVars = model.solution(/*timeIdx=*/1)[dofIdx]; + if (phi - priVars[biofilmConcentrationIdx] - priVars[calciteConcentrationIdx] < toleranceBeforeClogging()) + throw std::logic_error("Clogging has been (almost) reached in at least one cell\n"); + } + + /*! + * \brief Specify the MICP properties a single region. + * + * The index of specified here must be in range [0, numSatRegions) + */ + static void setMICPpara(const Scalar& densityBiofilm, + const Scalar& densityCalcite, + const Scalar& detachmentRate, + const Scalar& criticalPorosity, + const Scalar& fittingFactor, + const Scalar& halfVelocityOxygen, + const Scalar& halfVelocityUrea, + const Scalar& maximumGrowthRate, + const Scalar& maximumUreaUtilization, + const Scalar& microbialAttachmentRate, + const Scalar& microbialDeathRate, + const Scalar& minimumPermeability, + const Scalar& oxygenConsumptionFactor, + const Scalar& yieldGrowthCoefficient, + const Scalar& maximumOxygenConcentration, + const Scalar& maximumUreaConcentration, + const Scalar& toleranceBeforeClogging) + { + params_.densityBiofilm_ = densityBiofilm; + params_.densityCalcite_ = densityCalcite; + params_.detachmentRate_ = detachmentRate; + params_.criticalPorosity_ = criticalPorosity; + params_.fittingFactor_ = fittingFactor; + params_.halfVelocityOxygen_ = halfVelocityOxygen; + params_.halfVelocityUrea_ = halfVelocityUrea; + params_.maximumGrowthRate_ = maximumGrowthRate; + params_.maximumUreaUtilization_ = maximumUreaUtilization; + params_.microbialAttachmentRate_ = microbialAttachmentRate; + params_.microbialDeathRate_ = microbialDeathRate; + params_.minimumPermeability_ = minimumPermeability; + params_.oxygenConsumptionFactor_ = oxygenConsumptionFactor; + params_.yieldGrowthCoefficient_ = yieldGrowthCoefficient; + params_.maximumOxygenConcentration_ = maximumOxygenConcentration; + params_.maximumUreaConcentration_ = maximumUreaConcentration; + params_.toleranceBeforeClogging_ = toleranceBeforeClogging; + } + + /*! + * \brief Register all run-time parameters for the black-oil MICP module. + */ + static void registerParameters() + { + if (!enableMICP) + // MICP has been disabled at compile time + return; + + VtkBlackOilMICPModule::registerParameters(); + } + + /*! + * \brief Register all MICP specific VTK and ECL output modules. + */ + static void registerOutputModules(Model& model, + Simulator& simulator) + { + if (!enableMICP) + // MICP has been disabled at compile time + return; + + model.addOutputModule(new VtkBlackOilMICPModule(simulator)); + } + + static bool eqApplies(unsigned eqIdx) + { + if (!enableMICP) + return false; + + // All MICP components are true here + return eqIdx == contiMicrobialEqIdx || eqIdx == contiOxygenEqIdx || eqIdx == contiUreaEqIdx || eqIdx == contiBiofilmEqIdx || eqIdx == contiCalciteEqIdx; + } + + static Scalar eqWeight([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + // must be called after water storage is computed + template + static void addStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants) + { + if (!enableMICP) + return; + + LhsEval surfaceVolumeWater = Toolbox::template decay(intQuants.porosity()); + // avoid singular matrix if no water is present. + surfaceVolumeWater = max(surfaceVolumeWater, 1e-10); + // Suspended microbes in water phase + const LhsEval massMicrobes = surfaceVolumeWater * Toolbox::template decay(intQuants.microbialConcentration()); + LhsEval accumulationMicrobes = massMicrobes; + storage[contiMicrobialEqIdx] += accumulationMicrobes; + // Oxygen in water phase + const LhsEval massOxygen = surfaceVolumeWater * Toolbox::template decay(intQuants.oxygenConcentration()); + LhsEval accumulationOxygen = massOxygen; + storage[contiOxygenEqIdx] += accumulationOxygen; + // Urea in water phase + const LhsEval massUrea = surfaceVolumeWater * Toolbox::template decay(intQuants.ureaConcentration()); + LhsEval accumulationUrea = massUrea; + storage[contiUreaEqIdx] += accumulationUrea; + // Biofilm + const LhsEval massBiofilm = Toolbox::template decay(intQuants.biofilmConcentration()); + LhsEval accumulationBiofilm = massBiofilm; + storage[contiBiofilmEqIdx] += accumulationBiofilm; + // Calcite + const LhsEval massCalcite = Toolbox::template decay(intQuants.calciteConcentration()); + LhsEval accumulationCalcite = massCalcite; + storage[contiCalciteEqIdx] += accumulationCalcite; + } + + static void computeFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) + + { + if (!enableMICP) + return; + + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + const unsigned upIdx = extQuants.upstreamIndex(waterPhaseIdx); + const unsigned inIdx = extQuants.interiorIndex(); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + + + if (upIdx == inIdx) { + flux[contiMicrobialEqIdx] = extQuants.volumeFlux(waterPhaseIdx) * up.microbialConcentration(); + flux[contiOxygenEqIdx] = extQuants.volumeFlux(waterPhaseIdx) * up.oxygenConcentration(); + flux[contiUreaEqIdx] = extQuants.volumeFlux(waterPhaseIdx) * up.ureaConcentration(); + } + else { + flux[contiMicrobialEqIdx] = extQuants.volumeFlux(waterPhaseIdx) * decay(up.microbialConcentration()); + flux[contiOxygenEqIdx] = extQuants.volumeFlux(waterPhaseIdx) * decay(up.oxygenConcentration()); + flux[contiUreaEqIdx] = extQuants.volumeFlux(waterPhaseIdx) * decay(up.ureaConcentration()); + } + } + + // See https://doi.org/10.1016/j.ijggc.2021.103256 for the micp processes in the model. + static void addSource(RateVector& source, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + + { + if (!enableMICP) + return; + + // compute dpW (max norm of the pressure gradient in the cell center) + const IntensiveQuantities& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + const auto& K = elemCtx.problem().intrinsicPermeability(elemCtx, dofIdx, 0); + size_t numInteriorFaces = elemCtx.numInteriorFaces(timeIdx); + Evaluation dpW = 0; + for (unsigned scvfIdx = 0; scvfIdx < numInteriorFaces; scvfIdx++) { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + unsigned upIdx = extQuants.upstreamIndex(waterPhaseIdx); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + const Evaluation& mobWater = up.mobility(waterPhaseIdx); + + // compute water velocity from flux + Evaluation waterVolumeVelocity = extQuants.volumeFlux(waterPhaseIdx) / (K[0][0] * mobWater); + dpW = std::max(dpW, abs(waterVolumeVelocity)); + } + + // get the model parameters + Scalar k_a = microbialAttachmentRate(); + Scalar k_d = microbialDeathRate(); + Scalar rho_b = densityBiofilm(); + Scalar rho_c = densityCalcite(); + Scalar k_str = detachmentRate(); + Scalar k_o = halfVelocityOxygen(); + Scalar k_u = halfVelocityUrea() / 10.0;//Dividing by scaling factor 10 (see WellInterface_impl.hpp) + Scalar mu = maximumGrowthRate(); + Scalar mu_u = maximumUreaUtilization() / 10.0;//Dividing by scaling factor 10 (see WellInterface_impl.hpp) + Scalar Y_sb = yieldGrowthCoefficient(); + Scalar F = oxygenConsumptionFactor(); + Scalar Y_uc = 1.67 * 10; //Multiplying by scaling factor 10 (see WellInterface_impl.hpp) + + // compute the processes + source[Indices::contiMicrobialEqIdx] += intQuants.microbialConcentration() * intQuants.porosity() * + (Y_sb * mu * intQuants.oxygenConcentration() / (k_o + intQuants.oxygenConcentration()) - k_d - k_a) + + rho_b * intQuants.biofilmConcentration() * k_str * pow(intQuants.porosity() * dpW, 0.58); + + source[Indices::contiOxygenEqIdx] -= (intQuants.microbialConcentration() * intQuants.porosity() + rho_b * intQuants.biofilmConcentration()) * + F * mu * intQuants.oxygenConcentration() / (k_o + intQuants.oxygenConcentration()); + + source[Indices::contiUreaEqIdx] -= rho_b * intQuants.biofilmConcentration() * mu_u * intQuants.ureaConcentration() / (k_u + intQuants.ureaConcentration()); + + source[Indices::contiBiofilmEqIdx] += intQuants.biofilmConcentration() * (Y_sb * mu * intQuants.oxygenConcentration() / (k_o + intQuants.oxygenConcentration()) - k_d + - k_str * pow(intQuants.porosity() * dpW, 0.58) - Y_uc * (rho_b / rho_c) * intQuants.biofilmConcentration() * mu_u * + (intQuants.ureaConcentration() / (k_u + intQuants.ureaConcentration())) / (intQuants.porosity() + intQuants.biofilmConcentration())) + + k_a * intQuants.microbialConcentration() * intQuants.porosity() / rho_b; + + source[Indices::contiCalciteEqIdx] += (rho_b / rho_c) * intQuants.biofilmConcentration() * Y_uc * mu_u * intQuants.ureaConcentration() / (k_u + intQuants.ureaConcentration()); + } + + static const Scalar densityBiofilm() + { + return params_.densityBiofilm_; + } + + static const Scalar densityCalcite() + { + return params_.densityCalcite_; + } + + static const Scalar detachmentRate() + { + return params_.detachmentRate_; + } + + static const Scalar criticalPorosity() + { + return params_.criticalPorosity_; + } + + static const Scalar fittingFactor() + { + return params_.fittingFactor_; + } + + static const Scalar halfVelocityOxygen() + { + return params_.halfVelocityOxygen_; + } + + static const Scalar halfVelocityUrea() + { + return params_.halfVelocityUrea_; + } + + static const Scalar maximumGrowthRate() + { + return params_.maximumGrowthRate_; + } + + static const Scalar maximumOxygenConcentration() + { + return params_.maximumOxygenConcentration_; + } + + static const Scalar maximumUreaConcentration() + { + return params_.maximumUreaConcentration_ / 10.0;//Dividing by scaling factor 10 (see WellInterface_impl.hpp); + } + + static const Scalar maximumUreaUtilization() + { + return params_.maximumUreaUtilization_; + } + + static const Scalar microbialAttachmentRate() + { + return params_.microbialAttachmentRate_; + } + + static const Scalar microbialDeathRate() + { + return params_.microbialDeathRate_; + } + + static const Scalar minimumPermeability() + { + return params_.minimumPermeability_; + } + + static const Scalar oxygenConsumptionFactor() + { + return params_.oxygenConsumptionFactor_; + } + + static const Scalar toleranceBeforeClogging() + { + return params_.toleranceBeforeClogging_; + } + + static const Scalar yieldGrowthCoefficient() + { + return params_.yieldGrowthCoefficient_; + } + + static const std::vector phi() + { + return params_.phi_; + } + +private: + static BlackOilMICPParams params_; +}; + + +template +BlackOilMICPParams::Scalar> +BlackOilMICPModule::params_; + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilMICPIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the equations needed by the + * MICP extension of the black-oil model. + */ +template ()> +class BlackOilMICPIntensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + using ElementContext = GetPropType; + + using MICPModule = BlackOilMICPModule; + + static constexpr int microbialConcentrationIdx = Indices::microbialConcentrationIdx; + static constexpr int oxygenConcentrationIdx = Indices::oxygenConcentrationIdx; + static constexpr int ureaConcentrationIdx = Indices::ureaConcentrationIdx; + static constexpr int biofilmConcentrationIdx = Indices::biofilmConcentrationIdx; + static constexpr int calciteConcentrationIdx = Indices::calciteConcentrationIdx; + static constexpr int waterPhaseIdx = FluidSystem::waterPhaseIdx; + + +public: + + /*! + * \brief Update the intensive properties needed to handle MICP from the + * primary variables + * + */ + void MICPPropertiesUpdate_(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + const auto linearizationType = elemCtx.linearizationType(); + const PrimaryVariables& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + const auto& K = elemCtx.problem().intrinsicPermeability(elemCtx, dofIdx, timeIdx); + Scalar referencePorosity_ = elemCtx.problem().porosity(elemCtx, dofIdx, timeIdx); + Scalar eta = MICPModule::fittingFactor(); + Scalar k_min = MICPModule::minimumPermeability(); + Scalar phi_crit = MICPModule::criticalPorosity(); + + microbialConcentration_ = priVars.makeEvaluation(microbialConcentrationIdx, timeIdx, linearizationType); + oxygenConcentration_ = priVars.makeEvaluation(oxygenConcentrationIdx, timeIdx, linearizationType); + ureaConcentration_ = priVars.makeEvaluation(ureaConcentrationIdx, timeIdx, linearizationType); + biofilmConcentration_ = priVars.makeEvaluation(biofilmConcentrationIdx, timeIdx, linearizationType); + calciteConcentration_ = priVars.makeEvaluation(calciteConcentrationIdx, timeIdx, linearizationType); + + // Permeability reduction due to MICP, by adjusting the water mobility + asImp_().mobility_[waterPhaseIdx] *= max((pow((intQuants.porosity() - phi_crit) / (referencePorosity_ - phi_crit), eta) + k_min / K[0][0])/(1. + k_min / K[0][0]), k_min / K[0][0]); + + } + + const Evaluation& microbialConcentration() const + { return microbialConcentration_; } + + const Evaluation& oxygenConcentration() const + { return oxygenConcentration_; } + + const Evaluation& ureaConcentration() const + { return ureaConcentration_; } + + const Evaluation& biofilmConcentration() const + { return biofilmConcentration_; } + + const Evaluation& calciteConcentration() const + { return calciteConcentration_; } + + +protected: + Implementation& asImp_() + { return *static_cast(this); } + + Evaluation microbialConcentration_; + Evaluation oxygenConcentration_; + Evaluation ureaConcentration_; + Evaluation biofilmConcentration_; + Evaluation calciteConcentration_; + +}; + +template +class BlackOilMICPIntensiveQuantities +{ + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + +public: + void MICPPropertiesUpdate_(const ElementContext&, + unsigned, + unsigned) + { } + + const Evaluation& microbialConcentration() const + { throw std::logic_error("microbialConcentration() called but MICP is disabled"); } + + const Evaluation& oxygenConcentration() const + { throw std::logic_error("oxygenConcentration() called but MICP is disabled"); } + + const Evaluation& ureaConcentration() const + { throw std::logic_error("ureaConcentration() called but MICP is disabled"); } + + const Evaluation& biofilmConcentration() const + { throw std::logic_error("biofilmConcentration() called but MICP is disabled"); } + + const Evaluation& calciteConcentration() const + { throw std::logic_error("calciteConcentration() called but MICP is disabled"); } +}; + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilMICPExtensiveQuantities + * + * \brief Provides the MICP specific extensive quantities to the generic black-oil + * module's extensive quantities. + */ +template ()> +class BlackOilMICPExtensiveQuantities +{ + using Implementation = GetPropType; + +private: + Implementation& asImp_() + { return *static_cast(this); } + +}; + +template +class BlackOilMICPExtensiveQuantities{}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilmicpparams.hh b/opm/models/blackoil/blackoilmicpparams.hh new file mode 100644 index 00000000000..ef25010e0cb --- /dev/null +++ b/opm/models/blackoil/blackoilmicpparams.hh @@ -0,0 +1,60 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the parameters required to extend the black-oil model by MICP. + */ +#ifndef EWOMS_BLACK_OIL_MICP_PARAMS_HH +#define EWOMS_BLACK_OIL_MICP_PARAMS_HH + +#include + +namespace Opm { + +//! \brief Struct holding the parameters for the BlackOilMICPModule class. +template +struct BlackOilMICPParams { + Scalar densityBiofilm_; + Scalar densityCalcite_; + Scalar detachmentRate_; + Scalar criticalPorosity_; + Scalar fittingFactor_; + Scalar halfVelocityOxygen_; + Scalar halfVelocityUrea_; + Scalar maximumGrowthRate_; + Scalar maximumUreaUtilization_; + Scalar microbialAttachmentRate_; + Scalar microbialDeathRate_; + Scalar minimumPermeability_; + Scalar oxygenConsumptionFactor_; + Scalar yieldGrowthCoefficient_; + Scalar maximumOxygenConcentration_; + Scalar maximumUreaConcentration_; + Scalar toleranceBeforeClogging_; + std::vector phi_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilmodel.hh b/opm/models/blackoil/blackoilmodel.hh new file mode 100644 index 00000000000..36252fc57a9 --- /dev/null +++ b/opm/models/blackoil/blackoilmodel.hh @@ -0,0 +1,628 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilModel + */ +#ifndef EWOMS_BLACK_OIL_MODEL_HH +#define EWOMS_BLACK_OIL_MODEL_HH + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace Opm { + +template +class BlackOilModel; + +} + +namespace Opm::Properties { + +namespace TTag { + +//! The type tag for the black-oil problems +struct BlackOilModel +{ using InheritsFrom = std::tuple; }; +} // namespace TTag + +//! Set the local residual function +template +struct LocalResidual { using type = BlackOilLocalResidual; }; + +//! Use the black-oil specific newton method +template +struct NewtonMethod { using type = BlackOilNewtonMethod; }; + +//! The Model property +template +struct Model { using type = BlackOilModel; }; + +//! The Problem property +template +struct BaseProblem { using type = BlackOilProblem; }; + +//! the RateVector property +template +struct RateVector { using type = BlackOilRateVector; }; + +//! the BoundaryRateVector property +template +struct BoundaryRateVector { using type = BlackOilBoundaryRateVector; }; + +//! the PrimaryVariables property +template +struct PrimaryVariables { using type = BlackOilPrimaryVariables; }; + +//! the IntensiveQuantities property +template +struct IntensiveQuantities { using type = BlackOilIntensiveQuantities; }; + +//! the ExtensiveQuantities property +template +struct ExtensiveQuantities { using type = BlackOilExtensiveQuantities; }; + +//! Use the the velocity module which is aware of the black-oil specific model extensions +//! (i.e., the polymer and solvent extensions) +template +struct FluxModule { using type = BlackOilDarcyFluxModule; }; + +//! The indices required by the model +template +struct Indices +{ using type = BlackOilIndices(), + getPropValue(), + getPropValue(), + getPropValue(), + getPropValue(), + getPropValue(), + /*PVOffset=*/0, + getPropValue()>; }; + +//! Set the fluid system to the black-oil fluid system by default +template +struct FluidSystem +{ +public: + using Scalar = GetPropType; + using Evaluation = GetPropType; + using type = BlackOilFluidSystem; +}; + +// by default, all ECL extension modules are disabled +template +struct EnableSolvent { static constexpr bool value = false; }; +template +struct EnableExtbo { static constexpr bool value = false; }; +template +struct EnablePolymer { static constexpr bool value = false; }; +template +struct EnablePolymerMW { static constexpr bool value = false; }; +template +struct EnableFoam { static constexpr bool value = false; }; +template +struct EnableBrine { static constexpr bool value = false; }; +template +struct EnableVapwat { static constexpr bool value = false; }; +template +struct EnableDisgasInWater { static constexpr bool value = false; }; +template +struct EnableSaltPrecipitation { static constexpr bool value = false; }; +template +struct EnableMICP { static constexpr bool value = false; }; + +//! By default, the blackoil model is isothermal and does not conserve energy +template +struct EnableTemperature { static constexpr bool value = false; }; +template +struct EnableEnergy { static constexpr bool value = false; }; + +//! disable diffusion by default +template +struct EnableDiffusion { static constexpr bool value = false; }; + +//! disable disperison by default +template +struct EnableDispersion { static constexpr bool value = false; }; +template +struct EnableConvectiveMixing { static constexpr bool value = false; }; + +//! by default, scale the energy equation by the inverse of the energy required to heat +//! up one kg of water by 30 Kelvin. If we conserve surface volumes, this must be divided +//! by the weight of one cubic meter of water. This is required to make the "dumb" linear +//! solvers that do not weight the components of the solutions do the right thing. +//! by default, don't scale the energy equation, i.e. assume that a reasonable linear +//! solver is used. (Not scaling it makes debugging quite a bit easier.) +template +struct BlackOilEnergyScalingFactor +{ +private: + using Scalar = GetPropType; + static constexpr Scalar alpha = getPropValue() ? 1000.0 : 1.0; + +public: + using type = Scalar; + static constexpr Scalar value = 1.0/(30.0*4184.0*alpha); +}; + +// by default, ebos formulates the conservation equations in terms of mass not surface +// volumes +template +struct BlackoilConserveSurfaceVolume { static constexpr bool value = false; }; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * \brief A fully-implicit black-oil flow model. + * + * The black-oil model is a three-phase, three-component model widely + * used for oil reservoir simulation. The phases are denoted by lower + * index \f$\alpha \in \{ w, g, o \}\f$ ("water", "gas" and "oil") and + * the components by upper index \f$\kappa \in \{ W, G, O \}\f$ + * ("Water", "Gas" and "Oil"). The model assumes partial miscibility: + * + * - Water and the gas phases are immisicible and are assumed to be + * only composed of the water and gas components respectively- + * - The oil phase is assumed to be a mixture of the gas and the oil + * components. + * + * The densities of the phases are determined by so-called + * formation volume factors: + * + * \f[ + * B_\alpha := \frac{\varrho_\alpha(1\,\text{bar})}{\varrho_\alpha(p_\alpha)} + * \f] + * + * Since the gas and water phases are assumed to be immiscible, this + * is sufficient to calculate their density. For the formation volume + * factor of the the oil phase \f$B_o\f$ determines the density of + * *saturated* oil, i.e. the density of the oil phase if some gas + * phase is present. + * + * The composition of the oil phase is given by the gas dissolution factor + * \f$R_s\f$, which defined as the volume of gas at atmospheric pressure that is + * dissolved in a given amount of oil at reservoir pressure: + * + * \f[ + * R_s := \frac{\varrho_{o}^G}{\varrho_o^O}\;. + * \f] + * + * This allows to calculate all quantities required for the + * mass-conservation equations for each component, i.e. + * + * \f[ + * \sum_\alpha \frac{\partial\;\phi c_\alpha^\kappa S_\alpha }{\partial t} + * - \sum_\alpha \mathrm{div} \left\{ c_\alpha^\kappa \mathbf{v}_\alpha \right\} + * - q^\kappa = 0 \;, + * \f] + * where \f$\mathrm{v}_\alpha\f$ is the filter velocity of the phase + * \f$\alpha\f$. + * + * By default \f$\mathrm{v}_\alpha\f$ is determined by using the + * standard multi-phase Darcy approach, i.e. + * \f[ \mathbf{v}_\alpha = - \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} + *\left(\mathbf{grad}\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right) \;, \f] + * although the actual approach which is used can be specified via the + * \c FluxModule property. For example, the velocity model can by + * changed to the Forchheimer approach by + * \code + * template +struct FluxModule { using type = Opm::ForchheimerFluxModule; }; + * \endcode + * + * The primary variables used by this model are: + * - The pressure of the phase with the lowest index + * - The two saturations of the phases with the lowest indices + */ +template +class BlackOilModel + : public MultiPhaseBaseModel +{ +public: + using Indices = GetPropType; + using FluidSystem = GetPropType; + using PrimaryVariables = GetPropType; +private: + using Implementation = GetPropType; + using ParentType = MultiPhaseBaseModel; + + using Scalar = GetPropType; + using Simulator = GetPropType; + using Discretization = GetPropType; + using ElementContext = GetPropType; + + enum { numPhases = getPropValue() }; + enum { numComponents = FluidSystem::numComponents }; + enum { numEq = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + enum { enableDispersion = getPropValue() }; + + static constexpr bool compositionSwitchEnabled = Indices::compositionSwitchIdx >= 0; + static constexpr bool waterEnabled = Indices::waterEnabled; + + using SolventModule = BlackOilSolventModule; + using ExtboModule = BlackOilExtboModule; + using PolymerModule = BlackOilPolymerModule; + using EnergyModule = BlackOilEnergyModule; + using DiffusionModule = BlackOilDiffusionModule; + using DispersionModule = BlackOilDispersionModule; + using MICPModule = BlackOilMICPModule; + +public: + + using LocalResidual = GetPropType; + + BlackOilModel(Simulator& simulator) + : ParentType(simulator) + { + eqWeights_.resize(numEq, 1.0); + } + + /*! + * \brief Register all run-time parameters for the immiscible model. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + SolventModule::registerParameters(); + ExtboModule::registerParameters(); + PolymerModule::registerParameters(); + EnergyModule::registerParameters(); + DiffusionModule::registerParameters(); + MICPModule::registerParameters(); + + // register runtime parameters of the VTK output modules + VtkBlackOilModule::registerParameters(); + VtkCompositionModule::registerParameters(); + VtkDiffusionModule::registerParameters(); + } + + /*! + * \copydoc FvBaseDiscretization::name + */ + static std::string name() + { return "blackoil"; } + + /*! + * \copydoc FvBaseDiscretization::primaryVarName + */ + std::string primaryVarName(int pvIdx) const + { + std::ostringstream oss; + + if (pvIdx == Indices::waterSwitchIdx) + oss << "water_switching"; + else if (pvIdx == Indices::pressureSwitchIdx) + oss << "pressure_switching"; + else if (static_cast(pvIdx) == Indices::compositionSwitchIdx) + oss << "composition_switching"; + else if (SolventModule::primaryVarApplies(pvIdx)) + return SolventModule::primaryVarName(pvIdx); + else if (ExtboModule::primaryVarApplies(pvIdx)) + return ExtboModule::primaryVarName(pvIdx); + else if (PolymerModule::primaryVarApplies(pvIdx)) + return PolymerModule::primaryVarName(pvIdx); + else if (EnergyModule::primaryVarApplies(pvIdx)) + return EnergyModule::primaryVarName(pvIdx); + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::eqName + */ + std::string eqName(int eqIdx) const + { + std::ostringstream oss; + + if (Indices::conti0EqIdx <= eqIdx && eqIdx < Indices::conti0EqIdx + numComponents) + oss << "conti_" << FluidSystem::phaseName(eqIdx - Indices::conti0EqIdx); + else if (SolventModule::eqApplies(eqIdx)) + return SolventModule::eqName(eqIdx); + else if (ExtboModule::eqApplies(eqIdx)) + return ExtboModule::eqName(eqIdx); + else if (PolymerModule::eqApplies(eqIdx)) + return PolymerModule::eqName(eqIdx); + else if (EnergyModule::eqApplies(eqIdx)) + return EnergyModule::eqName(eqIdx); + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::primaryVarWeight + */ + Scalar primaryVarWeight(unsigned globalDofIdx, unsigned pvIdx) const + { + // do not care about the auxiliary equations as they are supposed to scale + // themselves + if (globalDofIdx >= this->numGridDof()) + return 1.0; + + // saturations are always in the range [0, 1]! + if (int(Indices::waterSwitchIdx) == int(pvIdx)) + return 1.0; + + // oil pressures usually are in the range of 100 to 500 bars for typical oil + // reservoirs (which is the only relevant application for the black-oil model). + else if (int(Indices::pressureSwitchIdx) == int(pvIdx)) + return 1.0/300e5; + + // deal with primary variables stemming from the solvent module + else if (SolventModule::primaryVarApplies(pvIdx)) + return SolventModule::primaryVarWeight(pvIdx); + + // deal with primary variables stemming from the extBO module + else if (ExtboModule::primaryVarApplies(pvIdx)) + return ExtboModule::primaryVarWeight(pvIdx); + + // deal with primary variables stemming from the polymer module + else if (PolymerModule::primaryVarApplies(pvIdx)) + return PolymerModule::primaryVarWeight(pvIdx); + + // deal with primary variables stemming from the energy module + else if (EnergyModule::primaryVarApplies(pvIdx)) + return EnergyModule::primaryVarWeight(pvIdx); + + // if the primary variable is either the gas saturation, Rs or Rv + assert(int(Indices::compositionSwitchIdx) == int(pvIdx)); + + auto pvMeaning = this->solution(0)[globalDofIdx].primaryVarsMeaningGas(); + if (pvMeaning == PrimaryVariables::GasMeaning::Sg) + return 1.0; // gas saturation + else if (pvMeaning == PrimaryVariables::GasMeaning::Rs) + return 1.0/250.; // gas dissolution factor + else { + assert(pvMeaning == PrimaryVariables::GasMeaning::Rv); + return 1.0/0.025; // oil vaporization factor + } + + } + + /*! + * \copydoc FvBaseDiscretization::eqWeight + */ + Scalar eqWeight(unsigned globalDofIdx, unsigned eqIdx) const + { + // do not care about the auxiliary equations as they are supposed to scale + // themselves + if (globalDofIdx >= this->numGridDof()) + return 1.0; + + return eqWeights_[eqIdx]; + } + + void setEqWeight(unsigned eqIdx, Scalar value) { + eqWeights_[eqIdx] = value; + } + + /*! + * \brief Write the current solution for a degree of freedom to a + * restart file. + * + * \param outstream The stream into which the vertex data should + * be serialized to + * \param dof The Dune entity which's data should be serialized + */ + template + void serializeEntity(std::ostream& outstream, const DofEntity& dof) + { + unsigned dofIdx = static_cast(asImp_().dofMapper().index(dof)); + + // write phase state + if (!outstream.good()) + throw std::runtime_error("Could not serialize degree of freedom "+std::to_string(dofIdx)); + + // write the primary variables + const auto& priVars = this->solution(/*timeIdx=*/0)[dofIdx]; + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) + outstream << priVars[eqIdx] << " "; + + // write the pseudo primary variables + outstream << static_cast(priVars.primaryVarsMeaningGas()) << " "; + outstream << static_cast(priVars.primaryVarsMeaningWater()) << " "; + outstream << static_cast(priVars.primaryVarsMeaningPressure()) << " "; + + outstream << priVars.pvtRegionIndex() << " "; + + SolventModule::serializeEntity(asImp_(), outstream, dof); + ExtboModule::serializeEntity(asImp_(), outstream, dof); + PolymerModule::serializeEntity(asImp_(), outstream, dof); + EnergyModule::serializeEntity(asImp_(), outstream, dof); + } + + /*! + * \brief Reads the current solution variables for a degree of + * freedom from a restart file. + * + * \param instream The stream from which the vertex data should + * be deserialized from + * \param dof The Dune entity which's data should be deserialized + */ + template + void deserializeEntity(std::istream& instream, + const DofEntity& dof) + { + unsigned dofIdx = static_cast(asImp_().dofMapper().index(dof)); + + // read in the "real" primary variables of the DOF + auto& priVars = this->solution(/*timeIdx=*/0)[dofIdx]; + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + if (!instream.good()) + throw std::runtime_error("Could not deserialize degree of freedom "+std::to_string(dofIdx)); + instream >> priVars[eqIdx]; + } + + // read the pseudo primary variables + unsigned primaryVarsMeaningGas; + instream >> primaryVarsMeaningGas; + + unsigned primaryVarsMeaningWater; + instream >> primaryVarsMeaningWater; + + unsigned primaryVarsMeaningPressure; + instream >> primaryVarsMeaningPressure; + + unsigned pvtRegionIdx; + instream >> pvtRegionIdx; + + if (!instream.good()) + throw std::runtime_error("Could not deserialize degree of freedom "+std::to_string(dofIdx)); + + SolventModule::deserializeEntity(asImp_(), instream, dof); + ExtboModule::deserializeEntity(asImp_(), instream, dof); + PolymerModule::deserializeEntity(asImp_(), instream, dof); + EnergyModule::deserializeEntity(asImp_(), instream, dof); + + using PVM_G = typename PrimaryVariables::GasMeaning; + using PVM_W = typename PrimaryVariables::WaterMeaning; + using PVM_P = typename PrimaryVariables::PressureMeaning; + priVars.setPrimaryVarsMeaningGas(static_cast(primaryVarsMeaningGas)); + priVars.setPrimaryVarsMeaningWater(static_cast(primaryVarsMeaningWater)); + priVars.setPrimaryVarsMeaningPressure(static_cast(primaryVarsMeaningPressure)); + + priVars.setPvtRegionIndex(pvtRegionIdx); + } + + /*! + * \brief Deserializes the state of the model. + * + * \tparam Restarter The type of the serializer class + * + * \param res The serializer object + */ + template + void deserialize(Restarter& res) + { + ParentType::deserialize(res); + + // set the PVT indices of the primary variables. This is also done by writing + // them into the restart file and re-reading them, but it is better to calculate + // them from scratch because the input could have been changed in this regard... + ElementContext elemCtx(this->simulator_); + for (const auto& elem : elements(this->gridView())) { + elemCtx.updateStencil(elem); + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timIdx=*/0); ++dofIdx) { + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timIdx=*/0); + updatePvtRegionIndex_(this->solution(/*timeIdx=*/0)[globalDofIdx], + elemCtx, + dofIdx, + /*timeIdx=*/0); + } + } + + this->solution(/*timeIdx=*/1) = this->solution(/*timeIdx=*/0); + } + +/* + // hack: this interferes with the static polymorphism trick +protected: + friend ParentType; + friend Discretization; +*/ + + template + void supplementInitialSolution_(PrimaryVariables& priVars, + const Context& context, + unsigned dofIdx, + unsigned timeIdx) + { updatePvtRegionIndex_(priVars, context, dofIdx, timeIdx); } + + void registerOutputModules_() + { + ParentType::registerOutputModules_(); + + // add the VTK output modules which make sense for the blackoil model + SolventModule::registerOutputModules(asImp_(), this->simulator_); + PolymerModule::registerOutputModules(asImp_(), this->simulator_); + EnergyModule::registerOutputModules(asImp_(), this->simulator_); + MICPModule::registerOutputModules(asImp_(), this->simulator_); + + this->addOutputModule(new VtkBlackOilModule(this->simulator_)); + this->addOutputModule(new VtkCompositionModule(this->simulator_)); + + if constexpr (enableDiffusion) + this->addOutputModule(new VtkDiffusionModule(this->simulator_)); + } + +private: + + std::vector eqWeights_; + Implementation& asImp_() + { return *static_cast(this); } + const Implementation& asImp_() const + { return *static_cast(this); } + + template + void updatePvtRegionIndex_(PrimaryVariables& priVars, + const Context& context, + unsigned dofIdx, + unsigned timeIdx) + { + unsigned regionIdx = context.problem().pvtRegionIndex(context, dofIdx, timeIdx); + priVars.setPvtRegionIndex(regionIdx); + } +}; +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilnewtonmethod.hh b/opm/models/blackoil/blackoilnewtonmethod.hh new file mode 100644 index 00000000000..b160dc62f1f --- /dev/null +++ b/opm/models/blackoil/blackoilnewtonmethod.hh @@ -0,0 +1,452 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilNewtonMethod + */ +#ifndef EWOMS_BLACK_OIL_NEWTON_METHOD_HH +#define EWOMS_BLACK_OIL_NEWTON_METHOD_HH + +#include +#include + +#include + +#include +#include +#include "blackoilmicpmodules.hh" + +namespace Opm::Properties { + +template +struct DiscNewtonMethod; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * + * \brief A newton solver which is specific to the black oil model. + */ +template +class BlackOilNewtonMethod : public GetPropType +{ + using ParentType = GetPropType; + using Simulator = GetPropType; + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + using PrimaryVariables = GetPropType; + using EqVector = GetPropType; + using Indices = GetPropType; + using FluidSystem = GetPropType; + using Scalar = GetPropType; + using Linearizer = GetPropType; + using MICPModule = BlackOilMICPModule; + + static const unsigned numEq = getPropValue(); + static constexpr bool enableSaltPrecipitation = getPropValue(); + +public: + BlackOilNewtonMethod(Simulator& simulator) : ParentType(simulator) + { + priVarOscilationThreshold_ = Parameters::Get>(); + dpMaxRel_ = Parameters::Get>(); + dsMax_ = Parameters::Get>(); + projectSaturations_ = Parameters::Get(); + maxTempChange_ = Parameters::Get>(); + tempMax_ = Parameters::Get>(); + tempMin_ = Parameters::Get>(); + pressMax_ = Parameters::Get>(); + pressMin_ = Parameters::Get>(); + waterSaturationMax_ = Parameters::Get>(); + waterOnlyThreshold_ = Parameters::Get>(); + } + + /*! + * \copydoc NewtonMethod::finishInit() + */ + void finishInit() + { + ParentType::finishInit(); + + wasSwitched_.resize(this->model().numTotalDof()); + std::fill(wasSwitched_.begin(), wasSwitched_.end(), false); + } + + /*! + * \brief Register all run-time parameters for the immiscible model. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register> + ("Maximum relative change of pressure in a single iteration"); + Parameters::Register> + ("Maximum absolute change of any saturation in a single iteration"); + Parameters::Register> + ("The threshold value for the primary variable switching conditions " + "after its meaning has switched to hinder oscilations"); + Parameters::Register + ("Option for doing saturation projection"); + Parameters::Register> + ("Maximum absolute change of temperature in a single iteration"); + Parameters::Register> + ("Maximum absolute temperature"); + Parameters::Register> + ("Minimum absolute temperature"); + Parameters::Register> + ("Maximum absolute pressure"); + Parameters::Register> + ("Minimum absolute pressure"); + Parameters::Register> + ("Maximum water saturation"); + Parameters::Register> + ("Cells with water saturation above or equal is considered one-phase water only"); + } + + /*! + * \brief Returns the number of degrees of freedom for which the + * interpretation has changed for the most recent iteration. + */ + unsigned numPriVarsSwitched() const + { return numPriVarsSwitched_; } + +protected: + friend NewtonMethod; + friend ParentType; + + /*! + * \copydoc FvBaseNewtonMethod::beginIteration_ + */ + void beginIteration_() + { + numPriVarsSwitched_ = 0; + ParentType::beginIteration_(); + } + + /*! + * \copydoc FvBaseNewtonMethod::endIteration_ + */ + void endIteration_(SolutionVector& uCurrentIter, + const SolutionVector& uLastIter) + { +#if HAVE_MPI + // in the MPI enabled case we need to add up the number of DOF + // for which the interpretation changed over all processes. + int localSwitched = numPriVarsSwitched_; + MPI_Allreduce(&localSwitched, + &numPriVarsSwitched_, + /*num=*/1, + MPI_INT, + MPI_SUM, + MPI_COMM_WORLD); +#endif // HAVE_MPI + + this->simulator_.model().newtonMethod().endIterMsg() + << ", num switched=" << numPriVarsSwitched_; + + ParentType::endIteration_(uCurrentIter, uLastIter); + } + +public: + void update_(SolutionVector& nextSolution, + const SolutionVector& currentSolution, + const GlobalEqVector& solutionUpdate, + const GlobalEqVector& currentResidual) + { + const auto& comm = this->simulator_.gridView().comm(); + + int succeeded; + try { + ParentType::update_(nextSolution, + currentSolution, + solutionUpdate, + currentResidual); + succeeded = 1; + } + catch (...) { + succeeded = 0; + } + succeeded = comm.min(succeeded); + + if (!succeeded) + throw NumericalProblem("A process did not succeed in adapting the primary variables"); + + numPriVarsSwitched_ = comm.sum(numPriVarsSwitched_); + } + + template + void update_(SolutionVector& nextSolution, + const SolutionVector& currentSolution, + const GlobalEqVector& solutionUpdate, + const GlobalEqVector& currentResidual, + const DofIndices& dofIndices) + { + const auto zero = 0.0 * solutionUpdate[0]; + for (auto dofIdx : dofIndices) { + if (solutionUpdate[dofIdx] == zero) { + continue; + } + updatePrimaryVariables_(dofIdx, + nextSolution[dofIdx], + currentSolution[dofIdx], + solutionUpdate[dofIdx], + currentResidual[dofIdx]); + } + } + +protected: + /*! + * \copydoc FvBaseNewtonMethod::updatePrimaryVariables_ + */ + void updatePrimaryVariables_(unsigned globalDofIdx, + PrimaryVariables& nextValue, + const PrimaryVariables& currentValue, + const EqVector& update, + const EqVector& currentResidual) + { + static constexpr bool enableSolvent = Indices::solventSaturationIdx >= 0; + static constexpr bool enableExtbo = Indices::zFractionIdx >= 0; + static constexpr bool enablePolymer = Indices::polymerConcentrationIdx >= 0; + static constexpr bool enablePolymerWeight = Indices::polymerMoleWeightIdx >= 0; + static constexpr bool enableEnergy = Indices::temperatureIdx >= 0; + static constexpr bool enableFoam = Indices::foamConcentrationIdx >= 0; + static constexpr bool enableBrine = Indices::saltConcentrationIdx >= 0; + static constexpr bool enableMICP = Indices::microbialConcentrationIdx >= 0; + + currentValue.checkDefined(); + Valgrind::CheckDefined(update); + Valgrind::CheckDefined(currentResidual); + + // saturation delta for each phase + Scalar deltaSw = 0.0; + Scalar deltaSo = 0.0; + Scalar deltaSg = 0.0; + Scalar deltaSs = 0.0; + + if (currentValue.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw) + { + deltaSw = update[Indices::waterSwitchIdx]; + deltaSo -= deltaSw; + } + if (currentValue.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg) + { + deltaSg = update[Indices::compositionSwitchIdx]; + deltaSo -= deltaSg; + } + if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) { + deltaSs = update[Indices::solventSaturationIdx]; + deltaSo -= deltaSs; + } + + // maximum saturation delta + Scalar maxSatDelta = std::max(std::abs(deltaSg), std::abs(deltaSo)); + maxSatDelta = std::max(maxSatDelta, std::abs(deltaSw)); + maxSatDelta = std::max(maxSatDelta, std::abs(deltaSs)); + + // scaling factor for saturation deltas to make sure that none of them exceeds + // the specified threshold value. + Scalar satAlpha = 1.0; + if (maxSatDelta > dsMax_) + satAlpha = dsMax_/maxSatDelta; + + for (int pvIdx = 0; pvIdx < int(numEq); ++pvIdx) { + // calculate the update of the current primary variable. For the black-oil + // model we limit the pressure delta relative to the pressure's current + // absolute value (Default: 30%) and saturation deltas to an absolute change + // (Default: 20%). Further, we ensure that the R factors, solvent + // "saturation" and polymer concentration do not become negative after the + // update. + Scalar delta = update[pvIdx]; + + // limit pressure delta + if (pvIdx == Indices::pressureSwitchIdx) { + if (std::abs(delta) > dpMaxRel_*currentValue[pvIdx]) + delta = signum(delta)*dpMaxRel_*currentValue[pvIdx]; + } + // water saturation delta + else if (pvIdx == Indices::waterSwitchIdx) + if (currentValue.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw) + delta *= satAlpha; + else { + //Ensure Rvw and Rsw factor does not become negative + if (delta > currentValue[ Indices::waterSwitchIdx]) + delta = currentValue[ Indices::waterSwitchIdx]; + } + else if (pvIdx == Indices::compositionSwitchIdx) { + // the switching primary variable for composition is tricky because the + // "reasonable" value ranges it exhibits vary widely depending on its + // interpretation since it can represent Sg, Rs or Rv. For now, we only + // limit saturation deltas and ensure that the R factors do not become + // negative. + if (currentValue.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg) + delta *= satAlpha; + else { + //Ensure Rv and Rs factor does not become negative + if (delta > currentValue[Indices::compositionSwitchIdx]) + delta = currentValue[Indices::compositionSwitchIdx]; + } + } + else if (enableSolvent && pvIdx == Indices::solventSaturationIdx) { + // solvent saturation updates are also subject to the Appleyard chop + if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) { + delta *= satAlpha; + } else { + //Ensure Rssolw factor does not become negative + if (delta > currentValue[Indices::solventSaturationIdx]) + delta = currentValue[Indices::solventSaturationIdx]; + } + } + else if (enableExtbo && pvIdx == Indices::zFractionIdx) { + // z fraction updates are also subject to the Appleyard chop + const auto& curr = currentValue[Indices::zFractionIdx]; // or currentValue[pvIdx] given the block condition + delta = std::clamp(delta, curr - Scalar{1.0}, curr); + } + else if (enablePolymerWeight && pvIdx == Indices::polymerMoleWeightIdx) { + const double sign = delta >= 0. ? 1. : -1.; + // maximum change of polymer molecular weight, the unit is MDa. + // applying this limit to stabilize the simulation. The value itself is still experimental. + const Scalar maxMolarWeightChange = 100.0; + delta = sign * std::min(std::abs(delta), maxMolarWeightChange); + delta *= satAlpha; + } + else if (enableEnergy && pvIdx == Indices::temperatureIdx) { + const double sign = delta >= 0. ? 1. : -1.; + delta = sign * std::min(std::abs(delta), maxTempChange_); + } + else if (enableBrine && pvIdx == Indices::saltConcentrationIdx && + enableSaltPrecipitation && + currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp) { + const Scalar maxSaltSaturationChange = 0.1; + const Scalar sign = delta >= 0. ? 1. : -1.; + delta = sign * std::min(std::abs(delta), maxSaltSaturationChange); + } + + // do the actual update + nextValue[pvIdx] = currentValue[pvIdx] - delta; + + // keep the solvent saturation between 0 and 1 + if (enableSolvent && pvIdx == Indices::solventSaturationIdx) { + if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss ) + nextValue[pvIdx] = std::min(std::max(nextValue[pvIdx], Scalar{0.0}), Scalar{1.0}); + } + + // keep the z fraction between 0 and 1 + if (enableExtbo && pvIdx == Indices::zFractionIdx) + nextValue[pvIdx] = std::min(std::max(nextValue[pvIdx], Scalar{0.0}), Scalar{1.0}); + + // keep the polymer concentration above 0 + if (enablePolymer && pvIdx == Indices::polymerConcentrationIdx) + nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0}); + + if (enablePolymerWeight && pvIdx == Indices::polymerMoleWeightIdx) { + nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0}); + const double polymerConcentration = nextValue[Indices::polymerConcentrationIdx]; + if (polymerConcentration < 1.e-10) + nextValue[pvIdx] = 0.0; + } + + // keep the foam concentration above 0 + if (enableFoam && pvIdx == Indices::foamConcentrationIdx) + nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0}); + + if (enableBrine && pvIdx == Indices::saltConcentrationIdx) { + // keep the salt concentration above 0 + if (!enableSaltPrecipitation || (enableSaltPrecipitation && currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Cs)) + nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0}); + // keep the salt saturation below upperlimit + if ((enableSaltPrecipitation && currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp)) + nextValue[pvIdx] = std::min(nextValue[pvIdx], Scalar{1.0-1.e-8}); + } + + // keep the temperature within given values + if (enableEnergy && pvIdx == Indices::temperatureIdx) + nextValue[pvIdx] = std::clamp(nextValue[pvIdx], tempMin_, tempMax_); + + if (pvIdx == Indices::pressureSwitchIdx) { + nextValue[pvIdx] = std::clamp(nextValue[pvIdx], pressMin_, pressMax_); + } + + + // Limit the variables to [0, cmax] values to improve the convergence. + // For the microorganisms we set this value equal to the biomass density value. + // For the oxygen and urea we set this value to the maximum injected + // concentration (the urea concentration has been scaled by 10). For + // the biofilm and calcite, we set this value equal to the porosity minus the clogging tolerance. + if (enableMICP && pvIdx == Indices::microbialConcentrationIdx) + nextValue[pvIdx] = std::clamp(nextValue[pvIdx], Scalar{0.0}, MICPModule::densityBiofilm()); + if (enableMICP && pvIdx == Indices::oxygenConcentrationIdx) + nextValue[pvIdx] = std::clamp(nextValue[pvIdx], Scalar{0.0}, MICPModule::maximumOxygenConcentration()); + if (enableMICP && pvIdx == Indices::ureaConcentrationIdx) + nextValue[pvIdx] = std::clamp(nextValue[pvIdx], Scalar{0.0}, MICPModule::maximumUreaConcentration()); + if (enableMICP && pvIdx == Indices::biofilmConcentrationIdx) + nextValue[pvIdx] = std::clamp(nextValue[pvIdx], Scalar{0.0}, MICPModule::phi()[globalDofIdx] - MICPModule::toleranceBeforeClogging()); + if (enableMICP && pvIdx == Indices::calciteConcentrationIdx) + nextValue[pvIdx] = std::clamp(nextValue[pvIdx], Scalar{0.0}, MICPModule::phi()[globalDofIdx] - MICPModule::toleranceBeforeClogging()); + } + + // switch the new primary variables to something which is physically meaningful. + // use a threshold value after a switch to make it harder to switch back + // immediately. + if (wasSwitched_[globalDofIdx]) + wasSwitched_[globalDofIdx] = nextValue.adaptPrimaryVariables(this->problem(), globalDofIdx, waterSaturationMax_, waterOnlyThreshold_, priVarOscilationThreshold_); + else + wasSwitched_[globalDofIdx] = nextValue.adaptPrimaryVariables(this->problem(), globalDofIdx, waterSaturationMax_, waterOnlyThreshold_); + + if (wasSwitched_[globalDofIdx]) + ++ numPriVarsSwitched_; + if(projectSaturations_){ + nextValue.chopAndNormalizeSaturations(); + } + + nextValue.checkDefined(); + } + +private: + int numPriVarsSwitched_; + + Scalar priVarOscilationThreshold_; + Scalar waterSaturationMax_; + Scalar waterOnlyThreshold_; + + Scalar dpMaxRel_; + Scalar dsMax_; + bool projectSaturations_; + Scalar maxTempChange_; + Scalar tempMax_; + Scalar tempMin_; + Scalar pressMax_; + Scalar pressMin_; + + // keep track of cells where the primary variable meaning has changed + // to detect and hinder oscillations + std::vector wasSwitched_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilnewtonmethodparameters.hh b/opm/models/blackoil/blackoilnewtonmethodparameters.hh new file mode 100644 index 00000000000..6897083341f --- /dev/null +++ b/opm/models/blackoil/blackoilnewtonmethodparameters.hh @@ -0,0 +1,67 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilNewtonMethod + */ +#ifndef EWOMS_BLACK_OIL_NEWTON_METHOD_PARAMETERS_HH +#define EWOMS_BLACK_OIL_NEWTON_METHOD_PARAMETERS_HH + +namespace Opm::Parameters { + +template +struct DpMaxRel { static constexpr Scalar value = 0.3; }; + +template +struct DsMax { static constexpr Scalar value = 0.2; }; + +template +struct PriVarOscilationThreshold { static constexpr Scalar value = 1e-5; }; + +struct ProjectSaturations { static constexpr bool value = false; }; + +template +struct MaxTemperatureChange { static constexpr Scalar value = 5.0; }; // Kelvin + +template +struct TemperatureMax { static constexpr Scalar value = 1e9; }; // Kelvin + +template +struct TemperatureMin { static constexpr Scalar value = 0.0; }; // Kelvin + +template +struct PressureMax { static constexpr Scalar value = 1e99; }; + +template +struct PressureMin { static constexpr Scalar value = -1e99; }; + +template +struct MaximumWaterSaturation { static constexpr Scalar value = 1.0; }; + +template +struct WaterOnlyThreshold { static constexpr Scalar value = 1.0; }; + +} // namespace Opm::Parameters + +#endif diff --git a/opm/models/blackoil/blackoilonephaseindices.hh b/opm/models/blackoil/blackoilonephaseindices.hh new file mode 100644 index 00000000000..0af66dd5f20 --- /dev/null +++ b/opm/models/blackoil/blackoilonephaseindices.hh @@ -0,0 +1,256 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Ewoms::BlackOilOnePhaseIndices + */ +#ifndef EWOMS_BLACK_OIL_ONE_PHASE_INDICES_HH +#define EWOMS_BLACK_OIL_ONE_PHASE_INDICES_HH + +#include + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * + * \brief The primary variable and equation indices for the black-oil model. + */ +template +struct BlackOilOnePhaseIndices +{ + //! Is phase enabled or not + static constexpr bool oilEnabled = canonicalCompIdx == 0; + static constexpr bool waterEnabled = canonicalCompIdx == 1; + static constexpr bool gasEnabled = canonicalCompIdx == 2; + + //! Are solvents involved? + static constexpr bool enableSolvent = numSolventsV > 0; + + //! Is extbo invoked? + static constexpr bool enableExtbo = numExtbosV > 0; + + //! Are polymers involved? + static constexpr bool enablePolymer = numPolymersV > 0; + + //! Shall energy be conserved? + static constexpr bool enableEnergy = numEnergyV > 0; + + //! Is MICP involved? + static constexpr bool enableMICP = numMICPsV > 0; + + //! Number of solvent components to be considered + static constexpr int numSolvents = enableSolvent ? numSolventsV : 0; + + //! Number of components to be considered for extbo + static constexpr int numExtbos = enableExtbo ? numExtbosV : 0; + + //! Number of polymer components to be considered + static constexpr int numPolymers = enablePolymer ? numPolymersV : 0; + + //! Number of energy equations to be considered + static constexpr int numEnergy = enableEnergy ? numEnergyV : 0; + + //! Number of foam equations to be considered + static constexpr int numFoam = enableFoam? 1 : 0; + + //! Number of salt equations to be considered + static constexpr int numBrine = enableBrine? 1 : 0; + + //! The number of fluid phases + static constexpr int numPhases = 1; + + //! Number of MICP components to be considered + static constexpr int numMICPs = enableMICP ? numMICPsV : 0; + + //! The number of equations + static constexpr int numEq = numPhases + numSolvents + numExtbos + numPolymers + + numEnergy + numFoam + numBrine + numMICPs; + + ////////////////////////////// + // Primary variable indices + ////////////////////////////// + + /*! + * \brief Index of the switching variable which determines the composistion of the water phase + * + * Depending on the phases present, this variable is either interpreted as + * water saturation or vapporized water in gas phase. + * + * \note For one-phase models this is disabled. + */ + static constexpr int waterSwitchIdx = -10000; + + /*! + * \brief Index of the switching variable which determines the pressure + * + * Depending on the phases present, this variable is either interpreted as the + * pressure of the oil phase, gas phase (if no oil) or water phase (if only water) + */ + static constexpr int pressureSwitchIdx = PVOffset + 0; + + /*! + * \brief Index of the switching variable which determines the composition of the + * hydrocarbon phases. + * + * \note For one-phase models this is disabled. + */ + static constexpr int compositionSwitchIdx = -10000; + + //! Index of the primary variable for the first solvent + static constexpr int solventSaturationIdx = + enableSolvent ? PVOffset + numPhases : -1000; + + //! Index of the primary variable for the first extbo component + static constexpr int zFractionIdx = + enableExtbo ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the primary variable for the first polymer + static constexpr int polymerConcentrationIdx = + enablePolymer ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the primary variable for the second polymer primary variable (molecular weight) + static constexpr int polymerMoleWeightIdx = + numPolymers > 1 ? polymerConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the first MICP component + static constexpr int microbialConcentrationIdx = + enableMICP ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the primary variable for the second MICP component + static constexpr int oxygenConcentrationIdx = + numMICPs > 1 ? microbialConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the third MICP component + static constexpr int ureaConcentrationIdx = + numMICPs > 2 ? oxygenConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the fourth MICP component + static constexpr int biofilmConcentrationIdx = + numMICPs > 3 ? ureaConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the fifth MICP component + static constexpr int calciteConcentrationIdx = + numMICPs > 4 ? biofilmConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the foam + static constexpr int foamConcentrationIdx = + enableFoam ? PVOffset + numPhases + numSolvents + numPolymers + numMICPs : -1000; + + //! Index of the primary variable for the salt + static constexpr int saltConcentrationIdx = + enableBrine ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs + numFoam : -1000; + + //! Index of the primary variable for temperature + static constexpr int temperatureIdx = + enableEnergy ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs + numFoam + numBrine: - 1000; + + ////////////////////// + // Equation indices + ////////////////////// + + //! \brief returns the index of "active" component + static unsigned canonicalToActiveComponentIndex(unsigned /*compIdx*/) + { + return 0; + } + + static unsigned activeToCanonicalComponentIndex([[maybe_unused]] unsigned compIdx) + { + // assumes canonical oil = 0, water = 1, gas = 2; + assert(compIdx == 0); + if (gasEnabled) { + return 2; + } else if (waterEnabled) { + return 1; + } else { + assert(oilEnabled); + } + + return 0; + } + + //! Index of the continuity equation of the first (and only) phase + static constexpr int conti0EqIdx = PVOffset + 0; + + //! Index of the continuity equation for the first solvent component + static constexpr int contiSolventEqIdx = + enableSolvent ? PVOffset + numPhases : -1000; + + //! Index of the continuity equation for the first extbo component + static constexpr int contiZfracEqIdx = + enableExtbo ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the continuity equation for the first polymer component + static constexpr int contiPolymerEqIdx = + enablePolymer ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the continuity equation for the second polymer component (molecular weight) + static constexpr int contiPolymerMWEqIdx = + numPolymers > 1 ? contiPolymerEqIdx + 1 : -1000; + + //! Index of the continuity equation for the first MICP component + static constexpr int contiMicrobialEqIdx = + enableMICP ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the continuity equation for the second MICP component + static constexpr int contiOxygenEqIdx = + numMICPs > 1 ? contiMicrobialEqIdx + 1 : -1000; + + //! Index of the continuity equation for the third MICP component + static constexpr int contiUreaEqIdx = + numMICPs > 2 ? contiOxygenEqIdx + 1 : -1000; + + //! Index of the continuity equation for the fourth MICP component + static constexpr int contiBiofilmEqIdx = + numMICPs > 3 ? contiUreaEqIdx + 1 : -1000; + + //! Index of the continuity equation for the fifth MICP component + static constexpr int contiCalciteEqIdx = + numMICPs > 4 ? contiBiofilmEqIdx + 1 : -1000; + + //! Index of the continuity equation for the foam component + static constexpr int contiFoamEqIdx = + enableFoam ? PVOffset + numPhases + numSolvents + numPolymers + numMICPs : -1000; + + //! Index of the continuity equation for the salt component + static constexpr int contiBrineEqIdx = + enableBrine ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs + numFoam : -1000; + + //! Index of the continuity equation for energy + static constexpr int contiEnergyEqIdx = + enableEnergy ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs + numFoam + numBrine: -1000; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilpolymermodules.hh b/opm/models/blackoil/blackoilpolymermodules.hh new file mode 100644 index 00000000000..e5d6ef21e81 --- /dev/null +++ b/opm/models/blackoil/blackoilpolymermodules.hh @@ -0,0 +1,1139 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the classes required to extend the black-oil model by polymer. + */ +#ifndef EWOMS_BLACK_OIL_POLYMER_MODULE_HH +#define EWOMS_BLACK_OIL_POLYMER_MODULE_HH + +#include "blackoilproperties.hh" + +#include +#include + +#include + +#if HAVE_ECL_INPUT +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include +#include +#include + +namespace Opm { +/*! + * \ingroup BlackOil + * \brief Contains the high level supplements required to extend the black oil + * model by polymer. + */ +template ()> +class BlackOilPolymerModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Model = GetPropType; + using Simulator = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + + using Toolbox = MathToolbox; + + using TabulatedFunction = typename BlackOilPolymerParams::TabulatedFunction; + using TabulatedTwoDFunction = typename BlackOilPolymerParams::TabulatedTwoDFunction; + + static constexpr unsigned polymerConcentrationIdx = Indices::polymerConcentrationIdx; + static constexpr unsigned polymerMoleWeightIdx = Indices::polymerMoleWeightIdx; + static constexpr unsigned contiPolymerEqIdx = Indices::contiPolymerEqIdx; + static constexpr unsigned contiPolymerMolarWeightEqIdx = Indices::contiPolymerMWEqIdx; + static constexpr unsigned waterPhaseIdx = FluidSystem::waterPhaseIdx; + + + static constexpr unsigned enablePolymer = enablePolymerV; + static constexpr bool enablePolymerMolarWeight = getPropValue(); + + static constexpr unsigned numEq = getPropValue(); + static constexpr unsigned numPhases = FluidSystem::numPhases; + +public: +#if HAVE_ECL_INPUT + /*! + * \brief Initialize all internal data structures needed by the polymer module + */ + static void initFromState(const EclipseState& eclState) + { + // some sanity checks: if polymers are enabled, the POLYMER keyword must be + // present, if polymers are disabled the keyword must not be present. + if (enablePolymer && !eclState.runspec().phases().active(Phase::POLYMER)) { + throw std::runtime_error("Non-trivial polymer treatment requested at compile time, but " + "the deck does not contain the POLYMER keyword"); + } + else if (!enablePolymer && eclState.runspec().phases().active(Phase::POLYMER)) { + throw std::runtime_error("Polymer treatment disabled at compile time, but the deck " + "contains the POLYMER keyword"); + } + + if (enablePolymerMolarWeight && !eclState.runspec().phases().active(Phase::POLYMW)) { + throw std::runtime_error("Polymer molecular weight tracking is enabled at compile time, but " + "the deck does not contain the POLYMW keyword"); + } + else if (!enablePolymerMolarWeight && eclState.runspec().phases().active(Phase::POLYMW)) { + throw std::runtime_error("Polymer molecular weight tracking is disabled at compile time, but the deck " + "contains the POLYMW keyword"); + } + + if (enablePolymerMolarWeight && !enablePolymer) { + throw std::runtime_error("Polymer molecular weight tracking is enabled while polymer treatment " + "is disabled at compile time"); + } + + if (!eclState.runspec().phases().active(Phase::POLYMER)) + return; // polymer treatment is supposed to be disabled + + const auto& tableManager = eclState.getTableManager(); + + unsigned numSatRegions = tableManager.getTabdims().getNumSatTables(); + params_.setNumSatRegions(numSatRegions); + + // initialize the objects which deal with the PLYROCK keyword + const auto& plyrockTables = tableManager.getPlyrockTables(); + if (!plyrockTables.empty()) { + assert(numSatRegions == plyrockTables.size()); + for (unsigned satRegionIdx = 0; satRegionIdx < numSatRegions; ++ satRegionIdx) { + const auto& plyrockTable = plyrockTables.template getTable(satRegionIdx); + params_.setPlyrock(satRegionIdx, + plyrockTable.getDeadPoreVolumeColumn()[0], + plyrockTable.getResidualResistanceFactorColumn()[0], + plyrockTable.getRockDensityFactorColumn()[0], + static_cast::AdsorptionBehaviour>(plyrockTable.getAdsorbtionIndexColumn()[0]), + plyrockTable.getMaxAdsorbtionColumn()[0]); + } + } + else { + throw std::runtime_error("PLYROCK must be specified in POLYMER runs\n"); + } + + // initialize the objects which deal with the PLYADS keyword + const auto& plyadsTables = tableManager.getPlyadsTables(); + if (!plyadsTables.empty()) { + assert(numSatRegions == plyadsTables.size()); + for (unsigned satRegionIdx = 0; satRegionIdx < numSatRegions; ++ satRegionIdx) { + const auto& plyadsTable = plyadsTables.template getTable(satRegionIdx); + // Copy data + const auto& c = plyadsTable.getPolymerConcentrationColumn(); + const auto& ads = plyadsTable.getAdsorbedPolymerColumn(); + params_.plyadsAdsorbedPolymer_[satRegionIdx].setXYContainers(c, ads); + } + } + else { + throw std::runtime_error("PLYADS must be specified in POLYMER runs\n"); + } + + + unsigned numPvtRegions = tableManager.getTabdims().getNumPVTTables(); + params_.plyviscViscosityMultiplierTable_.resize(numPvtRegions); + + // initialize the objects which deal with the PLYVISC keyword + const auto& plyviscTables = tableManager.getPlyviscTables(); + if (!plyviscTables.empty()) { + // different viscosity model is used for POLYMW + if (enablePolymerMolarWeight) { + OpmLog::warning("PLYVISC should not be used in POLYMW runs, " + "it will have no effect. A viscosity model based on PLYVMH is used instead.\n"); + } + else { + assert(numPvtRegions == plyviscTables.size()); + for (unsigned pvtRegionIdx = 0; pvtRegionIdx < numPvtRegions; ++ pvtRegionIdx) { + const auto& plyadsTable = plyviscTables.template getTable(pvtRegionIdx); + // Copy data + const auto& c = plyadsTable.getPolymerConcentrationColumn(); + const auto& visc = plyadsTable.getViscosityMultiplierColumn(); + params_.plyviscViscosityMultiplierTable_[pvtRegionIdx].setXYContainers(c, visc); + } + } + } + else if (!enablePolymerMolarWeight) { + throw std::runtime_error("PLYVISC must be specified in POLYMER runs\n"); + } + + // initialize the objects which deal with the PLYMAX keyword + const auto& plymaxTables = tableManager.getPlymaxTables(); + const unsigned numMixRegions = plymaxTables.size(); + params_.setNumMixRegions(numMixRegions, enablePolymerMolarWeight); + if (!plymaxTables.empty()) { + for (unsigned mixRegionIdx = 0; mixRegionIdx < numMixRegions; ++ mixRegionIdx) { + const auto& plymaxTable = plymaxTables.template getTable(mixRegionIdx); + params_.plymaxMaxConcentration_[mixRegionIdx] = plymaxTable.getPolymerConcentrationColumn()[0]; + } + } + else { + throw std::runtime_error("PLYMAX must be specified in POLYMER runs\n"); + } + + if (!eclState.getTableManager().getPlmixparTable().empty()) { + if (enablePolymerMolarWeight) { + OpmLog::warning("PLMIXPAR should not be used in POLYMW runs, it will have no effect.\n"); + } + else { + const auto& plmixparTable = eclState.getTableManager().getPlmixparTable(); + // initialize the objects which deal with the PLMIXPAR keyword + for (unsigned mixRegionIdx = 0; mixRegionIdx < numMixRegions; ++ mixRegionIdx) { + params_.plymixparToddLongstaff_[mixRegionIdx] = plmixparTable[mixRegionIdx].todd_langstaff; + } + } + } + else if (!enablePolymerMolarWeight) { + throw std::runtime_error("PLMIXPAR must be specified in POLYMER runs\n"); + } + + params_.hasPlyshlog_ = eclState.getTableManager().hasTables("PLYSHLOG"); + params_.hasShrate_ = eclState.getTableManager().useShrate(); + + if ((params_.hasPlyshlog_ || params_.hasShrate_) && enablePolymerMolarWeight) { + OpmLog::warning("PLYSHLOG and SHRATE should not be used in POLYMW runs, they will have no effect.\n"); + } + + if (params_.hasPlyshlog_ && !enablePolymerMolarWeight) { + const auto& plyshlogTables = tableManager.getPlyshlogTables(); + assert(numPvtRegions == plyshlogTables.size()); + params_.plyshlogShearEffectRefMultiplier_.resize(numPvtRegions); + params_.plyshlogShearEffectRefLogVelocity_.resize(numPvtRegions); + for (unsigned pvtRegionIdx = 0; pvtRegionIdx < numPvtRegions; ++ pvtRegionIdx) { + const auto& plyshlogTable = plyshlogTables.template getTable(pvtRegionIdx); + + Scalar plyshlogRefPolymerConcentration = plyshlogTable.getRefPolymerConcentration(); + auto waterVelocity = plyshlogTable.getWaterVelocityColumn().vectorCopy(); + auto shearMultiplier = plyshlogTable.getShearMultiplierColumn().vectorCopy(); + + // do the unit version here for the waterVelocity + UnitSystem unitSystem = eclState.getDeckUnitSystem(); + double siFactor = params_.hasShrate_? unitSystem.parse("1/Time").getSIScaling() : unitSystem.parse("Length/Time").getSIScaling(); + for (size_t i = 0; i < waterVelocity.size(); ++i) { + waterVelocity[i] *= siFactor; + // for plyshlog the input must be stored as logarithms + // the interpolation is then done the log-space. + waterVelocity[i] = std::log(waterVelocity[i]); + } + + Scalar refViscMult = params_.plyviscViscosityMultiplierTable_[pvtRegionIdx].eval(plyshlogRefPolymerConcentration, /*extrapolate=*/true); + // convert the table using referece conditions + for (size_t i = 0; i < waterVelocity.size(); ++i) { + shearMultiplier[i] *= refViscMult; + shearMultiplier[i] -= 1; + shearMultiplier[i] /= (refViscMult - 1); + shearMultiplier[i] = shearMultiplier[i]; + } + params_.plyshlogShearEffectRefMultiplier_[pvtRegionIdx].resize(waterVelocity.size()); + params_.plyshlogShearEffectRefLogVelocity_[pvtRegionIdx].resize(waterVelocity.size()); + + for (size_t i = 0; i < waterVelocity.size(); ++i) { + params_.plyshlogShearEffectRefMultiplier_[pvtRegionIdx][i] = shearMultiplier[i]; + params_.plyshlogShearEffectRefLogVelocity_[pvtRegionIdx][i] = waterVelocity[i]; + } + } + } + + if (params_.hasShrate_ && !enablePolymerMolarWeight) { + if (!params_.hasPlyshlog_) { + throw std::runtime_error("PLYSHLOG must be specified if SHRATE is used in POLYMER runs\n"); + } + const auto& shrateTable = eclState.getTableManager().getShrateTable(); + params_.shrate_.resize(numPvtRegions); + for (unsigned pvtRegionIdx = 0; pvtRegionIdx < numPvtRegions; ++ pvtRegionIdx) { + if (shrateTable.empty()) { + params_.shrate_[pvtRegionIdx] = 4.8; //default; + } + else if (shrateTable.size() == numPvtRegions) { + params_.shrate_[pvtRegionIdx] = shrateTable[pvtRegionIdx].rate; + } + else { + throw std::runtime_error("SHRATE must either have 0 or number of NUMPVT entries\n"); + } + } + } + + if constexpr (enablePolymerMolarWeight) { + const auto& plyvmhTable = eclState.getTableManager().getPlyvmhTable(); + if (!plyvmhTable.empty()) { + assert(plyvmhTable.size() == numMixRegions); + for (size_t regionIdx = 0; regionIdx < numMixRegions; ++regionIdx) { + params_.plyvmhCoefficients_[regionIdx].k_mh = plyvmhTable[regionIdx].k_mh; + params_.plyvmhCoefficients_[regionIdx].a_mh = plyvmhTable[regionIdx].a_mh; + params_.plyvmhCoefficients_[regionIdx].gamma = plyvmhTable[regionIdx].gamma; + params_.plyvmhCoefficients_[regionIdx].kappa = plyvmhTable[regionIdx].kappa; + } + } + else { + throw std::runtime_error("PLYVMH keyword must be specified in POLYMW rus \n"); + } + + // handling PLYMWINJ keyword + const auto& plymwinjTables = tableManager.getPlymwinjTables(); + for (const auto& table : plymwinjTables) { + const int tableNumber = table.first; + const auto& plymwinjtable = table.second; + const std::vector& throughput = plymwinjtable.getThroughputs(); + const std::vector& watervelocity = plymwinjtable.getVelocities(); + const std::vector>& molecularweight = plymwinjtable.getMoleWeights(); + TabulatedTwoDFunction tablefunc(throughput, watervelocity, molecularweight, true, false); + params_.plymwinjTables_[tableNumber] = std::move(tablefunc); + } + + // handling SKPRWAT keyword + const auto& skprwatTables = tableManager.getSkprwatTables(); + for (const auto& table : skprwatTables) { + const int tableNumber = table.first; + const auto& skprwattable = table.second; + const std::vector& throughput = skprwattable.getThroughputs(); + const std::vector& watervelocity = skprwattable.getVelocities(); + const std::vector>& skinpressure = skprwattable.getSkinPressures(); + TabulatedTwoDFunction tablefunc(throughput, watervelocity, skinpressure, true, false); + params_.skprwatTables_[tableNumber] = std::move(tablefunc); + } + + // handling SKPRPOLY keyword + const auto& skprpolyTables = tableManager.getSkprpolyTables(); + for (const auto& table : skprpolyTables) { + const int tableNumber = table.first; + const auto& skprpolytable = table.second; + const std::vector& throughput = skprpolytable.getThroughputs(); + const std::vector& watervelocity = skprpolytable.getVelocities(); + const std::vector>& skinpressure = skprpolytable.getSkinPressures(); + const double refPolymerConcentration = skprpolytable.referenceConcentration(); + typename BlackOilPolymerParams::SkprpolyTable tablefunc = + {refPolymerConcentration, + TabulatedTwoDFunction(throughput, watervelocity, skinpressure, true, false)}; + params_.skprpolyTables_[tableNumber] = std::move(tablefunc); + } + } + } +#endif + + /*! + * \brief get the PLYMWINJ table + */ + static TabulatedTwoDFunction& getPlymwinjTable(const int tableNumber) + { + const auto iterTable = params_.plymwinjTables_.find(tableNumber); + if (iterTable != params_.plymwinjTables_.end()) { + return iterTable->second; + } + else { + throw std::runtime_error(" the PLYMWINJ table " + std::to_string(tableNumber) + " does not exist\n"); + } + } + + /*! + * \brief get the SKPRWAT table + */ + static TabulatedTwoDFunction& getSkprwatTable(const int tableNumber) + { + const auto iterTable = params_.skprwatTables_.find(tableNumber); + if (iterTable != params_.skprwatTables_.end()) { + return iterTable->second; + } + else { + throw std::runtime_error(" the SKPRWAT table " + std::to_string(tableNumber) + " does not exist\n"); + } + } + + /*! + * \brief get the SKPRPOLY table + */ + static typename BlackOilPolymerParams::SkprpolyTable& + getSkprpolyTable(const int tableNumber) + { + const auto iterTable = params_.skprpolyTables_.find(tableNumber); + if (iterTable != params_.skprpolyTables_.end()) { + return iterTable->second; + } + else { + throw std::runtime_error(" the SKPRPOLY table " + std::to_string(tableNumber) + " does not exist\n"); + } + } + + /*! + * \brief Register all run-time parameters for the black-oil polymer module. + */ + static void registerParameters() + { + if constexpr (enablePolymer) + VtkBlackOilPolymerModule::registerParameters(); + } + + /*! + * \brief Register all polymer specific VTK and ECL output modules. + */ + static void registerOutputModules(Model& model, + Simulator& simulator) + { + if constexpr (enablePolymer) + model.addOutputModule(new VtkBlackOilPolymerModule(simulator)); + } + + static bool primaryVarApplies(unsigned pvIdx) + { + if constexpr (enablePolymer) { + if constexpr (enablePolymerMolarWeight) + return pvIdx == polymerConcentrationIdx || pvIdx == polymerMoleWeightIdx; + else + return pvIdx == polymerConcentrationIdx; + } + else + return false; + } + + static std::string primaryVarName(unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + if (pvIdx == polymerConcentrationIdx) { + return "polymer_waterconcentration"; + } + else { + return "polymer_molecularweight"; + } + } + + static Scalar primaryVarWeight([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + static bool eqApplies(unsigned eqIdx) + { + if constexpr (enablePolymer) { + if constexpr (enablePolymerMolarWeight) + return eqIdx == contiPolymerEqIdx || eqIdx == contiPolymerMolarWeightEqIdx; + else + return eqIdx == contiPolymerEqIdx; + } + else + return false; + } + + static std::string eqName(unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + if (eqIdx == contiPolymerEqIdx) + return "conti^polymer"; + else + return "conti^polymer_molecularweight"; + } + + static Scalar eqWeight([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + // must be called after water storage is computed + template + static void addStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants) + { + if constexpr (enablePolymer) { + const auto& fs = intQuants.fluidState(); + + LhsEval surfaceVolumeWater = + Toolbox::template decay(fs.saturation(waterPhaseIdx)) + * Toolbox::template decay(fs.invB(waterPhaseIdx)) + * Toolbox::template decay(intQuants.porosity()); + + // avoid singular matrix if no water is present. + surfaceVolumeWater = max(surfaceVolumeWater, 1e-10); + + // polymer in water phase + const LhsEval massPolymer = surfaceVolumeWater + * Toolbox::template decay(intQuants.polymerConcentration()) + * (1.0 - Toolbox::template decay(intQuants.polymerDeadPoreVolume())); + + // polymer in solid phase + const LhsEval adsorptionPolymer = + Toolbox::template decay(1.0 - intQuants.porosity()) + * Toolbox::template decay(intQuants.polymerRockDensity()) + * Toolbox::template decay(intQuants.polymerAdsorption()); + + LhsEval accumulationPolymer = massPolymer + adsorptionPolymer; + + storage[contiPolymerEqIdx] += accumulationPolymer; + + // tracking the polymer molecular weight + if constexpr (enablePolymerMolarWeight) { + accumulationPolymer = max(accumulationPolymer, 1e-10); + + storage[contiPolymerMolarWeightEqIdx] += accumulationPolymer + * Toolbox::template decay (intQuants.polymerMoleWeight()); + } + } + } + + static void computeFlux([[maybe_unused]] RateVector& flux, + [[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned scvfIdx, + [[maybe_unused]] unsigned timeIdx) + + { + if constexpr (enablePolymer) { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + const unsigned upIdx = extQuants.upstreamIndex(FluidSystem::waterPhaseIdx); + const unsigned inIdx = extQuants.interiorIndex(); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + const unsigned contiWaterEqIdx = Indices::conti0EqIdx + Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); + + if (upIdx == inIdx) { + flux[contiPolymerEqIdx] = + extQuants.volumeFlux(waterPhaseIdx) + *up.fluidState().invB(waterPhaseIdx) + *up.polymerViscosityCorrection() + /extQuants.polymerShearFactor() + *up.polymerConcentration(); + + // modify water + flux[contiWaterEqIdx] /= + extQuants.waterShearFactor(); + } + else { + flux[contiPolymerEqIdx] = + extQuants.volumeFlux(waterPhaseIdx) + *decay(up.fluidState().invB(waterPhaseIdx)) + *decay(up.polymerViscosityCorrection()) + /decay(extQuants.polymerShearFactor()) + *decay(up.polymerConcentration()); + + // modify water + flux[contiWaterEqIdx] /= + decay(extQuants.waterShearFactor()); + } + + // flux related to transport of polymer molecular weight + if constexpr (enablePolymerMolarWeight) { + if (upIdx == inIdx) + flux[contiPolymerMolarWeightEqIdx] = + flux[contiPolymerEqIdx]*up.polymerMoleWeight(); + else + flux[contiPolymerMolarWeightEqIdx] = + flux[contiPolymerEqIdx]*decay(up.polymerMoleWeight()); + } + } + } + + /*! + * \brief Return how much a Newton-Raphson update is considered an error + */ + static Scalar computeUpdateError(const PrimaryVariables&, + const EqVector&) + { + // do not consider consider the cange of polymer primary variables for + // convergence + // TODO: maybe this should be changed + return static_cast(0.0); + } + + template + static void serializeEntity(const Model& model, std::ostream& outstream, const DofEntity& dof) + { + if constexpr (enablePolymer) { + unsigned dofIdx = model.dofMapper().index(dof); + const PrimaryVariables& priVars = model.solution(/*timeIdx=*/0)[dofIdx]; + outstream << priVars[polymerConcentrationIdx]; + outstream << priVars[polymerMoleWeightIdx]; + } + } + + template + static void deserializeEntity(Model& model, std::istream& instream, const DofEntity& dof) + { + if constexpr (enablePolymer) { + unsigned dofIdx = model.dofMapper().index(dof); + PrimaryVariables& priVars0 = model.solution(/*timeIdx=*/0)[dofIdx]; + PrimaryVariables& priVars1 = model.solution(/*timeIdx=*/1)[dofIdx]; + + instream >> priVars0[polymerConcentrationIdx]; + instream >> priVars0[polymerMoleWeightIdx]; + + // set the primary variables for the beginning of the current time step. + priVars1[polymerConcentrationIdx] = priVars0[polymerConcentrationIdx]; + priVars1[polymerMoleWeightIdx] = priVars0[polymerMoleWeightIdx]; + } + } + + static const Scalar plyrockDeadPoreVolume(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plyrockDeadPoreVolume_[satnumRegionIdx]; + } + + static const Scalar plyrockResidualResistanceFactor(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plyrockResidualResistanceFactor_[satnumRegionIdx]; + } + + static const Scalar plyrockRockDensityFactor(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plyrockRockDensityFactor_[satnumRegionIdx]; + } + + static const Scalar plyrockAdsorbtionIndex(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plyrockAdsorbtionIndex_[satnumRegionIdx]; + } + + static const Scalar plyrockMaxAdsorbtion(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plyrockMaxAdsorbtion_[satnumRegionIdx]; + } + + static const TabulatedFunction& plyadsAdsorbedPolymer(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plyadsAdsorbedPolymer_[satnumRegionIdx]; + } + + static const TabulatedFunction& plyviscViscosityMultiplierTable(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned pvtnumRegionIdx = elemCtx.problem().pvtRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plyviscViscosityMultiplierTable_[pvtnumRegionIdx]; + } + + static const TabulatedFunction& plyviscViscosityMultiplierTable(unsigned pvtnumRegionIdx) + { + return params_.plyviscViscosityMultiplierTable_[pvtnumRegionIdx]; + } + + static const Scalar plymaxMaxConcentration(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned polymerMixRegionIdx = elemCtx.problem().plmixnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plymaxMaxConcentration_[polymerMixRegionIdx]; + } + + static const Scalar plymixparToddLongstaff(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned polymerMixRegionIdx = elemCtx.problem().plmixnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plymixparToddLongstaff_[polymerMixRegionIdx]; + } + + static const typename BlackOilPolymerParams::PlyvmhCoefficients& + plyvmhCoefficients(const ElementContext& elemCtx, + const unsigned scvIdx, + const unsigned timeIdx) + { + const unsigned polymerMixRegionIdx = elemCtx.problem().plmixnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.plyvmhCoefficients_[polymerMixRegionIdx]; + } + + static bool hasPlyshlog() + { + return params_.hasPlyshlog_; + } + + static bool hasShrate() + { + return params_.hasShrate_; + } + + static const Scalar shrate(unsigned pvtnumRegionIdx) + { + return params_.shrate_[pvtnumRegionIdx]; + } + + /*! + * \brief Computes the shear factor + * + * Input is polymer concentration and either the water velocity or the shrate if hasShrate_ is true. + * The pvtnumRegionIdx is needed to make sure the right table is used. + */ + template + static Evaluation computeShearFactor(const Evaluation& polymerConcentration, + unsigned pvtnumRegionIdx, + const Evaluation& v0) + { + using ToolboxLocal = MathToolbox; + + const auto& viscosityMultiplierTable = params_.plyviscViscosityMultiplierTable_[pvtnumRegionIdx]; + Scalar viscosityMultiplier = viscosityMultiplierTable.eval(scalarValue(polymerConcentration), /*extrapolate=*/true); + + const Scalar eps = 1e-14; + // return 1.0 if the polymer has no effect on the water. + if (std::abs((viscosityMultiplier - 1.0)) < eps) + return ToolboxLocal::createConstant(v0, 1.0); + + const std::vector& shearEffectRefLogVelocity = params_.plyshlogShearEffectRefLogVelocity_[pvtnumRegionIdx]; + auto v0AbsLog = log(abs(v0)); + // return 1.0 if the velocity /sharte is smaller than the first velocity entry. + if (v0AbsLog < shearEffectRefLogVelocity[0]) + return ToolboxLocal::createConstant(v0, 1.0); + + // compute shear factor from input + // Z = (1 + (P - 1) * M(v)) / P + // where M(v) is computed from user input + // and P = viscosityMultiplier + const std::vector& shearEffectRefMultiplier = params_.plyshlogShearEffectRefMultiplier_[pvtnumRegionIdx]; + size_t numTableEntries = shearEffectRefLogVelocity.size(); + assert(shearEffectRefMultiplier.size() == numTableEntries); + + std::vector shearEffectMultiplier(numTableEntries, 1.0); + for (size_t i = 0; i < numTableEntries; ++i) { + shearEffectMultiplier[i] = (1.0 + (viscosityMultiplier - 1.0)*shearEffectRefMultiplier[i]) / viscosityMultiplier; + shearEffectMultiplier[i] = log(shearEffectMultiplier[i]); + } + // store the logarithmic velocity and logarithmic multipliers in a table for easy look up and + // linear interpolation in the logarithmic space. + TabulatedFunction logShearEffectMultiplier = TabulatedFunction(numTableEntries, shearEffectRefLogVelocity, shearEffectMultiplier, /*bool sortInputs =*/ false); + + // Find sheared velocity (v) that satisfies + // F = log(v) + log (Z) - log(v0) = 0; + + // Set up the function + // u = log(v) + auto F = [&logShearEffectMultiplier, &v0AbsLog](const Evaluation& u) { + return u + logShearEffectMultiplier.eval(u, true) - v0AbsLog; + }; + // and its derivative + auto dF = [&logShearEffectMultiplier](const Evaluation& u) { + return 1 + logShearEffectMultiplier.evalDerivative(u, true); + }; + + // Solve F = 0 using Newton + // Use log(v0) as initial value for u + auto u = v0AbsLog; + bool converged = false; + // TODO make this into parameters + for (int i = 0; i < 20; ++i) { + auto f = F(u); + auto df = dF(u); + u -= f/df; + if (std::abs(scalarValue(f)) < 1e-12) { + converged = true; + break; + } + } + if (!converged) { + throw std::runtime_error("Not able to compute shear velocity. \n"); + } + + // return the shear factor + return exp(logShearEffectMultiplier.eval(u, /*extrapolate=*/true)); + + } + + const Scalar molarMass() const + { + return 0.25; // kg/mol + } + +private: + static BlackOilPolymerParams params_; +}; + + +template +BlackOilPolymerParams::Scalar> +BlackOilPolymerModule::params_; + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilPolymerIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the equations needed by the + * polymers extension of the black-oil model. + */ +template ()> +class BlackOilPolymerIntensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using Indices = GetPropType; + using ElementContext = GetPropType; + + using PolymerModule = BlackOilPolymerModule; + + enum { numPhases = getPropValue() }; + static constexpr int polymerConcentrationIdx = Indices::polymerConcentrationIdx; + static constexpr int waterPhaseIdx = FluidSystem::waterPhaseIdx; + static constexpr bool enablePolymerMolarWeight = getPropValue(); + static constexpr int polymerMoleWeightIdx = Indices::polymerMoleWeightIdx; + + +public: + + /*! + * \brief Update the intensive properties needed to handle polymers from the + * primary variables + * + */ + void polymerPropertiesUpdate_(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + const auto linearizationType = elemCtx.linearizationType(); + const PrimaryVariables& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + polymerConcentration_ = priVars.makeEvaluation(polymerConcentrationIdx, timeIdx, linearizationType); + if constexpr (enablePolymerMolarWeight) { + polymerMoleWeight_ = priVars.makeEvaluation(polymerMoleWeightIdx, timeIdx, linearizationType); + } + + // permeability reduction due to polymer + const Scalar& maxAdsorbtion = PolymerModule::plyrockMaxAdsorbtion(elemCtx, dofIdx, timeIdx); + const auto& plyadsAdsorbedPolymer = PolymerModule::plyadsAdsorbedPolymer(elemCtx, dofIdx, timeIdx); + polymerAdsorption_ = plyadsAdsorbedPolymer.eval(polymerConcentration_, /*extrapolate=*/true); + if (PolymerModule::plyrockAdsorbtionIndex(elemCtx, dofIdx, timeIdx) == BlackOilPolymerParams::NoDesorption) { + const Scalar& maxPolymerAdsorption = elemCtx.problem().maxPolymerAdsorption(elemCtx, dofIdx, timeIdx); + polymerAdsorption_ = std::max(Evaluation(maxPolymerAdsorption) , polymerAdsorption_); + } + + // compute resitanceFactor + const Scalar& residualResistanceFactor = PolymerModule::plyrockResidualResistanceFactor(elemCtx, dofIdx, timeIdx); + const Evaluation resistanceFactor = 1.0 + (residualResistanceFactor - 1.0) * polymerAdsorption_ / maxAdsorbtion; + + // compute effective viscosities + if constexpr (!enablePolymerMolarWeight) { + const Scalar cmax = PolymerModule::plymaxMaxConcentration(elemCtx, dofIdx, timeIdx); + const auto& fs = asImp_().fluidState_; + const Evaluation& muWater = fs.viscosity(waterPhaseIdx); + const auto& viscosityMultiplier = PolymerModule::plyviscViscosityMultiplierTable(elemCtx, dofIdx, timeIdx); + const Evaluation viscosityMixture = viscosityMultiplier.eval(polymerConcentration_, /*extrapolate=*/true) * muWater; + + // Do the Todd-Longstaff mixing + const Scalar plymixparToddLongstaff = PolymerModule::plymixparToddLongstaff(elemCtx, dofIdx, timeIdx); + const Evaluation viscosityPolymer = viscosityMultiplier.eval(cmax, /*extrapolate=*/true) * muWater; + const Evaluation viscosityPolymerEffective = pow(viscosityMixture, plymixparToddLongstaff) * pow(viscosityPolymer, 1.0 - plymixparToddLongstaff); + const Evaluation viscosityWaterEffective = pow(viscosityMixture, plymixparToddLongstaff) * pow(muWater, 1.0 - plymixparToddLongstaff); + + const Evaluation cbar = polymerConcentration_ / cmax; + // waterViscosity / effectiveWaterViscosity + waterViscosityCorrection_ = muWater * ((1.0 - cbar) / viscosityWaterEffective + cbar / viscosityPolymerEffective); + // effectiveWaterViscosity / effectivePolymerViscosity + polymerViscosityCorrection_ = (muWater / waterViscosityCorrection_) / viscosityPolymerEffective; + } + else { // based on PLYVMH + const auto& plyvmhCoefficients = PolymerModule::plyvmhCoefficients(elemCtx, dofIdx, timeIdx); + const Scalar k_mh = plyvmhCoefficients.k_mh; + const Scalar a_mh = plyvmhCoefficients.a_mh; + const Scalar gamma = plyvmhCoefficients.gamma; + const Scalar kappa = plyvmhCoefficients.kappa; + + // viscosity model based on Mark-Houwink equation and Huggins equation + // 1000 is a emperical constant, most likely related to unit conversion + const Evaluation intrinsicViscosity = k_mh * pow(polymerMoleWeight_ * 1000., a_mh); + const Evaluation x = polymerConcentration_ * intrinsicViscosity; + waterViscosityCorrection_ = 1.0 / (1.0 + gamma * (x + kappa * x * x)); + polymerViscosityCorrection_ = 1.0; + } + + // adjust water mobility + asImp_().mobility_[waterPhaseIdx] *= waterViscosityCorrection_ / resistanceFactor; + + // update rock properties + polymerDeadPoreVolume_ = PolymerModule::plyrockDeadPoreVolume(elemCtx, dofIdx, timeIdx); + polymerRockDensity_ = PolymerModule::plyrockRockDensityFactor(elemCtx, dofIdx, timeIdx); + } + + const Evaluation& polymerConcentration() const + { return polymerConcentration_; } + + const Evaluation& polymerMoleWeight() const + { + if constexpr (enablePolymerMolarWeight) + return polymerMoleWeight_; + else + throw std::logic_error("polymerMoleWeight() is called but polymer milecular weight is disabled"); + } + + const Scalar& polymerDeadPoreVolume() const + { return polymerDeadPoreVolume_; } + + const Evaluation& polymerAdsorption() const + { return polymerAdsorption_; } + + const Scalar& polymerRockDensity() const + { return polymerRockDensity_; } + + // effectiveWaterViscosity / effectivePolymerViscosity + const Evaluation& polymerViscosityCorrection() const + { return polymerViscosityCorrection_; } + + // waterViscosity / effectiveWaterViscosity + const Evaluation& waterViscosityCorrection() const + { return waterViscosityCorrection_; } + + +protected: + Implementation& asImp_() + { return *static_cast(this); } + + Evaluation polymerConcentration_; + // polymer molecular weight + Evaluation polymerMoleWeight_; + Scalar polymerDeadPoreVolume_; + Scalar polymerRockDensity_; + Evaluation polymerAdsorption_; + Evaluation polymerViscosityCorrection_; + Evaluation waterViscosityCorrection_; + + +}; + +template +class BlackOilPolymerIntensiveQuantities +{ + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + +public: + void polymerPropertiesUpdate_(const ElementContext&, + unsigned, + unsigned) + { } + + const Evaluation& polymerMoleWeight() const + { throw std::logic_error("polymerMoleWeight() called but polymer molecular weight is disabled"); } + + const Evaluation& polymerConcentration() const + { throw std::runtime_error("polymerConcentration() called but polymers are disabled"); } + + const Evaluation& polymerDeadPoreVolume() const + { throw std::runtime_error("polymerDeadPoreVolume() called but polymers are disabled"); } + + const Evaluation& polymerAdsorption() const + { throw std::runtime_error("polymerAdsorption() called but polymers are disabled"); } + + const Evaluation& polymerRockDensity() const + { throw std::runtime_error("polymerRockDensity() called but polymers are disabled"); } + + const Evaluation& polymerViscosityCorrection() const + { throw std::runtime_error("polymerViscosityCorrection() called but polymers are disabled"); } + + const Evaluation& waterViscosityCorrection() const + { throw std::runtime_error("waterViscosityCorrection() called but polymers are disabled"); } +}; + + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilPolymerExtensiveQuantities + * + * \brief Provides the polymer specific extensive quantities to the generic black-oil + * module's extensive quantities. + */ +template ()> +class BlackOilPolymerExtensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using GridView = GetPropType; + + static constexpr unsigned gasPhaseIdx = FluidSystem::gasPhaseIdx; + static constexpr int dimWorld = GridView::dimensionworld; + static constexpr unsigned waterPhaseIdx = FluidSystem::waterPhaseIdx; + + using Toolbox = MathToolbox; + using PolymerModule = BlackOilPolymerModule; + using DimVector = Dune::FieldVector; + using DimEvalVector = Dune::FieldVector; + +public: + /*! + * \brief Method which calculates the shear factor based on flow velocity + * + * This is the variant of the method which assumes that the problem is specified + * using permeabilities, i.e., *not* via transmissibilities. + */ + template // we need to make this method a template to avoid + // compiler errors if it is not instantiated! + void updateShearMultipliersPerm(const ElementContext&, + unsigned, + unsigned) + { + throw std::runtime_error("The extension of the blackoil model for polymers is not yet " + "implemented for problems specified using permeabilities."); + } + + /*! + * \brief Method which calculates the shear factor based on flow velocity + * + * This is the variant of the method which assumes that the problem is specified + * using transmissibilities, i.e., *not* via permeabilities. + */ + template // we need to make this method a template to avoid + // compiler errors if it is not instantiated! + void updateShearMultipliers(const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) + { + + waterShearFactor_ = 1.0; + polymerShearFactor_ = 1.0; + + if (!PolymerModule::hasPlyshlog()) + return; + + const ExtensiveQuantities& extQuants = asImp_(); + unsigned upIdx = extQuants.upstreamIndex(waterPhaseIdx); + unsigned interiorDofIdx = extQuants.interiorIndex(); + unsigned exteriorDofIdx = extQuants.exteriorIndex(); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx, timeIdx); + + // compute water velocity from flux + Evaluation poroAvg = intQuantsIn.porosity()*0.5 + intQuantsEx.porosity()*0.5; + unsigned pvtnumRegionIdx = elemCtx.problem().pvtRegionIndex(elemCtx, scvfIdx, timeIdx); + const Evaluation& Sw = up.fluidState().saturation(waterPhaseIdx); + unsigned cellIdx = elemCtx.globalSpaceIndex(scvfIdx, timeIdx); + const auto& materialLawManager = elemCtx.problem().materialLawManager(); + const auto& scaledDrainageInfo = + materialLawManager->oilWaterScaledEpsInfoDrainage(cellIdx); + const Scalar& Swcr = scaledDrainageInfo.Swcr; + + // guard against zero porosity and no mobile water + Evaluation denom = max(poroAvg * (Sw - Swcr), 1e-12); + Evaluation waterVolumeVelocity = extQuants.volumeFlux(waterPhaseIdx) / denom; + + // if shrate is specified. Compute shrate based on the water velocity + if (PolymerModule::hasShrate()) { + const Evaluation& relWater = up.relativePermeability(waterPhaseIdx); + Scalar trans = elemCtx.problem().transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); + if (trans > 0.0) { + Scalar faceArea = elemCtx.stencil(timeIdx).interiorFace(scvfIdx).area(); + auto dist = elemCtx.pos(interiorDofIdx, timeIdx) - elemCtx.pos(exteriorDofIdx, timeIdx); + // compute permeability from transmissibility. + Scalar absPerm = trans / faceArea * dist.two_norm(); + waterVolumeVelocity *= + PolymerModule::shrate(pvtnumRegionIdx)*sqrt(poroAvg*Sw / (relWater*absPerm)); + assert(isfinite(waterVolumeVelocity)); + } + } + + // compute share factors for water and polymer + waterShearFactor_ = + PolymerModule::computeShearFactor(up.polymerConcentration(), + pvtnumRegionIdx, + waterVolumeVelocity); + polymerShearFactor_ = + PolymerModule::computeShearFactor(up.polymerConcentration(), + pvtnumRegionIdx, + waterVolumeVelocity*up.polymerViscosityCorrection()); + + } + + const Evaluation& polymerShearFactor() const + { return polymerShearFactor_; } + + const Evaluation& waterShearFactor() const + { return waterShearFactor_; } + + +private: + Implementation& asImp_() + { return *static_cast(this); } + + Evaluation polymerShearFactor_; + Evaluation waterShearFactor_; + +}; + +template +class BlackOilPolymerExtensiveQuantities +{ + using ElementContext = GetPropType; + using Evaluation = GetPropType; + +public: + void updateShearMultipliers(const ElementContext&, + unsigned, + unsigned) + { } + + void updateShearMultipliersPerm(const ElementContext&, + unsigned, + unsigned) + { } + + const Evaluation& polymerShearFactor() const + { throw std::runtime_error("polymerShearFactor() called but polymers are disabled"); } + + const Evaluation& waterShearFactor() const + { throw std::runtime_error("waterShearFactor() called but polymers are disabled"); } +}; + + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilpolymerparams.hh b/opm/models/blackoil/blackoilpolymerparams.hh new file mode 100644 index 00000000000..9fdb63608b9 --- /dev/null +++ b/opm/models/blackoil/blackoilpolymerparams.hh @@ -0,0 +1,135 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the parameters required to extend the black-oil model by polymer. + */ +#ifndef EWOMS_BLACK_OIL_POLYMER_PARAMS_HH +#define EWOMS_BLACK_OIL_POLYMER_PARAMS_HH + +#include +#include + +#include +#include + +namespace Opm { + +//! \brief Struct holding the parameters for the BlackOilPolymerModule class. +template +struct BlackOilPolymerParams { + using TabulatedFunction = Tabulated1DFunction; + using TabulatedTwoDFunction = IntervalTabulated2DFunction; + + enum AdsorptionBehaviour { Desorption = 1, NoDesorption = 2 }; + + /*! + * \brief Specify the number of satuation regions. + * + * This must be called before setting the PLYROCK and PLYADS of any region. + */ + void setNumSatRegions(unsigned numRegions) + { + plyrockDeadPoreVolume_.resize(numRegions); + plyrockResidualResistanceFactor_.resize(numRegions); + plyrockRockDensityFactor_.resize(numRegions); + plyrockAdsorbtionIndex_.resize(numRegions); + plyrockMaxAdsorbtion_.resize(numRegions); + plyadsAdsorbedPolymer_.resize(numRegions); + } + + /*! + * \brief Specify the number of mix regions. + * + * This must be called before setting the PLYMAC and PLMIXPAR of any region. + */ + void setNumMixRegions(unsigned numRegions, bool enablePolymerMolarWeight) + { + plymaxMaxConcentration_.resize(numRegions); + plymixparToddLongstaff_.resize(numRegions); + + if (enablePolymerMolarWeight) { + plyvmhCoefficients_.resize(numRegions); + } + } + + /*! + * \brief Specify the polymer rock properties a single region. + * + * The index of specified here must be in range [0, numSatRegions) + */ + void setPlyrock(unsigned satRegionIdx, + const Scalar& plyrockDeadPoreVolume, + const Scalar& plyrockResidualResistanceFactor, + const Scalar& plyrockRockDensityFactor, + const Scalar& plyrockAdsorbtionIndex, + const Scalar& plyrockMaxAdsorbtion) + { + plyrockDeadPoreVolume_[satRegionIdx] = plyrockDeadPoreVolume; + plyrockResidualResistanceFactor_[satRegionIdx] = plyrockResidualResistanceFactor; + plyrockRockDensityFactor_[satRegionIdx] = plyrockRockDensityFactor; + plyrockAdsorbtionIndex_[satRegionIdx] = plyrockAdsorbtionIndex; + plyrockMaxAdsorbtion_[satRegionIdx] = plyrockMaxAdsorbtion; + } + + // a struct containing the constants to calculate polymer viscosity + // based on Mark-Houwink equation and Huggins equation, the constants are provided + // by the keyword PLYVMH + struct PlyvmhCoefficients { + Scalar k_mh; + Scalar a_mh; + Scalar gamma; + Scalar kappa; + }; + + struct SkprpolyTable { + double refConcentration; + TabulatedTwoDFunction table_func; + }; + + std::vector plyrockDeadPoreVolume_; + std::vector plyrockResidualResistanceFactor_; + std::vector plyrockRockDensityFactor_; + std::vector plyrockAdsorbtionIndex_; + std::vector plyrockMaxAdsorbtion_; + std::vector plyadsAdsorbedPolymer_; + std::vector plyviscViscosityMultiplierTable_; + std::vector plymaxMaxConcentration_; + std::vector plymixparToddLongstaff_; + std::vector> plyshlogShearEffectRefMultiplier_; + std::vector> plyshlogShearEffectRefLogVelocity_; + std::vector shrate_; + bool hasShrate_; + bool hasPlyshlog_; + + std::vector plyvmhCoefficients_; + std::map plymwinjTables_; + std::map skprwatTables_; + + std::map skprpolyTables_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilprimaryvariables.hh b/opm/models/blackoil/blackoilprimaryvariables.hh new file mode 100644 index 00000000000..e9496bad536 --- /dev/null +++ b/opm/models/blackoil/blackoilprimaryvariables.hh @@ -0,0 +1,1153 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + OPM 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. + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilPrimaryVariables + */ +#ifndef EWOMS_BLACK_OIL_PRIMARY_VARIABLES_HH +#define EWOMS_BLACK_OIL_PRIMARY_VARIABLES_HH + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Opm::Parameters { + +template +struct PressureScale { static constexpr Scalar value = 1.0; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * + * \brief Represents the primary variables used by the black-oil model. + */ +template +class BlackOilPrimaryVariables : public FvBasePrimaryVariables +{ + using ParentType = FvBasePrimaryVariables; + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Indices = GetPropType; + using Problem = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + + // number of equations + enum { numEq = getPropValue() }; + + // primary variable indices + enum { waterSwitchIdx = Indices::waterSwitchIdx }; + enum { pressureSwitchIdx = Indices::pressureSwitchIdx }; + enum { compositionSwitchIdx = Indices::compositionSwitchIdx }; + enum { saltConcentrationIdx = Indices::saltConcentrationIdx }; + enum { solventSaturationIdx = Indices::solventSaturationIdx }; + + static constexpr bool compositionSwitchEnabled = Indices::compositionSwitchIdx >= 0; + static constexpr bool waterEnabled = Indices::waterEnabled; + static constexpr bool gasEnabled = Indices::gasEnabled; + static constexpr bool oilEnabled = Indices::oilEnabled; + + // phase indices from the fluid system + enum { numPhases = getPropValue() }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + + // component indices from the fluid system + enum { numComponents = getPropValue() }; + enum { enableSolvent = getPropValue() }; + enum { enableExtbo = getPropValue() }; + enum { enablePolymer = getPropValue() }; + enum { enableFoam = getPropValue() }; + enum { enableBrine = getPropValue() }; + enum { enableSaltPrecipitation = getPropValue() }; + enum { enableVapwat = getPropValue() }; + enum { enableEnergy = getPropValue() }; + enum { enableTemperature = getPropValue() }; + enum { enableMICP = getPropValue() }; + enum { gasCompIdx = FluidSystem::gasCompIdx }; + enum { waterCompIdx = FluidSystem::waterCompIdx }; + enum { oilCompIdx = FluidSystem::oilCompIdx }; + + using Toolbox = MathToolbox; + using ComponentVector = Dune::FieldVector; + using SolventModule = BlackOilSolventModule; + using ExtboModule = BlackOilExtboModule; + using PolymerModule = BlackOilPolymerModule; + using EnergyModule = BlackOilEnergyModule; + using FoamModule = BlackOilFoamModule; + using BrineModule = BlackOilBrineModule; + using MICPModule = BlackOilMICPModule; + + static_assert(numPhases == 3, "The black-oil model assumes three phases!"); + static_assert(numComponents == 3, "The black-oil model assumes three components!"); + +public: + enum class WaterMeaning { + Sw, // water saturation + Rvw, // vaporized water + Rsw, // dissolved gas in water + Disabled, // The primary variable is not used + }; + + enum class PressureMeaning { + Po, // oil pressure + Pg, // gas pressure + Pw, // water pressure + }; + + enum class GasMeaning { + Sg, // gas saturation + Rs, // dissolved gas in oil + Rv, // vapporized oil + Disabled, // The primary variable is not used + }; + + enum class BrineMeaning { + Cs, // salt concentration + Sp, // (precipitated) salt saturation + Disabled, // The primary variable is not used + }; + + enum class SolventMeaning { + Ss, // solvent saturation + Rsolw, // dissolved solvent in water + Disabled, // The primary variable is not used + }; + + BlackOilPrimaryVariables() + : ParentType() + { + Valgrind::SetUndefined(*this); + pvtRegionIdx_ = 0; + } + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(Scalar) + */ + BlackOilPrimaryVariables(Scalar value) + : ParentType(value) + { + Valgrind::SetUndefined(primaryVarsMeaningWater_); + Valgrind::SetUndefined(primaryVarsMeaningGas_); + Valgrind::SetUndefined(primaryVarsMeaningPressure_); + Valgrind::SetUndefined(primaryVarsMeaningBrine_); + Valgrind::SetUndefined(primaryVarsMeaningSolvent_); + + pvtRegionIdx_ = 0; + } + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(const ImmisciblePrimaryVariables& ) + */ + BlackOilPrimaryVariables(const BlackOilPrimaryVariables& value) = default; + + static BlackOilPrimaryVariables serializationTestObject() + { + BlackOilPrimaryVariables result; + result.pvtRegionIdx_ = 1; + result.primaryVarsMeaningBrine_ = BrineMeaning::Sp; + result.primaryVarsMeaningGas_ = GasMeaning::Rv; + result.primaryVarsMeaningPressure_ = PressureMeaning::Pg; + result.primaryVarsMeaningWater_ = WaterMeaning::Rsw; + result.primaryVarsMeaningSolvent_ = SolventMeaning::Ss; + for (size_t i = 0; i < result.size(); ++i) { + result[i] = i+1; + } + + return result; + } + + static void init() + { + // TODO: these parameters have undocumented non-trivial dependencies + pressureScale_ = Parameters::Get>(); + } + + static void registerParameters() + { + Parameters::Register> + ("Scaling of pressure primary variable"); + } + + void setPressureScale(Scalar val) + { + pressureScale_ = val; + } + + Evaluation + makeEvaluation(unsigned varIdx, unsigned timeIdx, LinearizationType linearizationType = LinearizationType()) const + { + Scalar scale = 1.0; + if (varIdx == pressureSwitchIdx) { + scale = this->pressureScale_; + } + if (std::is_same::value) + return (*this)[varIdx] * scale; // finite differences + else { + // automatic differentiation + if (timeIdx == linearizationType.time) + return Toolbox::createVariable((*this)[varIdx], varIdx) * scale; + else + return Toolbox::createConstant((*this)[varIdx]) * scale; + } + } + + /*! + * \brief Set the index of the region which should be used for PVT properties. + * + * PVT regions represent spatial variation of the composition decribed + * by the pseudo-components used by the black oil model (i.e., oil, gas + * and water). This introduce spatially varying pvt behaviour. + */ + void setPvtRegionIndex(unsigned value) + { pvtRegionIdx_ = static_cast(value); } + + /*! + * \brief Return the index of the region which should be used for PVT properties. + */ + unsigned pvtRegionIndex() const + { return pvtRegionIdx_; } + + /*! + * \brief Return the interpretation which should be applied to the switching primary + * variables. + */ + WaterMeaning primaryVarsMeaningWater() const + { return primaryVarsMeaningWater_; } + + /*! + * \brief Set the interpretation which should be applied to the switching primary + * variables. + */ + void setPrimaryVarsMeaningWater(WaterMeaning newMeaning) + { primaryVarsMeaningWater_ = newMeaning; } + + /*! + * \brief Return the interpretation which should be applied to the switching primary + * variables. + */ + PressureMeaning primaryVarsMeaningPressure() const + { return primaryVarsMeaningPressure_; } + + /*! + * \brief Set the interpretation which should be applied to the switching primary + * variables. + */ + void setPrimaryVarsMeaningPressure(PressureMeaning newMeaning) + { primaryVarsMeaningPressure_ = newMeaning; } + + /*! + * \brief Return the interpretation which should be applied to the switching primary + * variables. + */ + GasMeaning primaryVarsMeaningGas() const + { return primaryVarsMeaningGas_; } + + /*! + * \brief Set the interpretation which should be applied to the switching primary + * variables. + */ + void setPrimaryVarsMeaningGas(GasMeaning newMeaning) + { primaryVarsMeaningGas_ = newMeaning; } + + BrineMeaning primaryVarsMeaningBrine() const + { return primaryVarsMeaningBrine_; } + + /*! + * \brief Set the interpretation which should be applied to the switching primary + * variables. + */ + + void setPrimaryVarsMeaningBrine(BrineMeaning newMeaning) + { primaryVarsMeaningBrine_ = newMeaning; } + + + SolventMeaning primaryVarsMeaningSolvent() const + { return primaryVarsMeaningSolvent_; } + + /*! + * \brief Set the interpretation which should be applied to the switching primary + * variables. + */ + + void setPrimaryVarsMeaningSolvent(SolventMeaning newMeaning) + { primaryVarsMeaningSolvent_ = newMeaning; } + + /*! + * \copydoc ImmisciblePrimaryVariables::assignMassConservative + */ + template + void assignMassConservative(const FluidState& fluidState, + const MaterialLawParams& matParams, + bool isInEquilibrium = false) + { + using ConstEvaluation = typename std::remove_reference::type; + using FsEvaluation = typename std::remove_const::type; + using FsToolbox = MathToolbox; + +#ifndef NDEBUG + // make sure the temperature is the same in all fluid phases + for (unsigned phaseIdx = 1; phaseIdx < numPhases; ++phaseIdx) { + Valgrind::CheckDefined(fluidState.temperature(0)); + Valgrind::CheckDefined(fluidState.temperature(phaseIdx)); + + assert(fluidState.temperature(0) == fluidState.temperature(phaseIdx)); + } +#endif // NDEBUG + + // for the equilibrium case, we don't need complicated + // computations. + if (isInEquilibrium) { + assignNaive(fluidState); + return; + } + + // If your compiler bails out here, you're probably not using a suitable black + // oil fluid system. + typename FluidSystem::template ParameterCache paramCache; + paramCache.setRegionIndex(pvtRegionIdx_); + paramCache.setMaxOilSat(FsToolbox::value(fluidState.saturation(oilPhaseIdx))); + + // create a mutable fluid state with well defined densities based on the input + using NcpFlash = NcpFlash; + using FlashFluidState = CompositionalFluidState; + FlashFluidState fsFlash; + fsFlash.setTemperature(FsToolbox::value(fluidState.temperature(/*phaseIdx=*/0))); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + fsFlash.setPressure(phaseIdx, FsToolbox::value(fluidState.pressure(phaseIdx))); + fsFlash.setSaturation(phaseIdx, FsToolbox::value(fluidState.saturation(phaseIdx))); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + fsFlash.setMoleFraction(phaseIdx, compIdx, FsToolbox::value(fluidState.moleFraction(phaseIdx, compIdx))); + } + + paramCache.updateAll(fsFlash); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + Scalar rho = FluidSystem::template density(fsFlash, paramCache, phaseIdx); + fsFlash.setDensity(phaseIdx, rho); + } + + // calculate the "global molarities" + ComponentVector globalMolarities(0.0); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + globalMolarities[compIdx] += + fsFlash.saturation(phaseIdx) * fsFlash.molarity(phaseIdx, compIdx); + } + } + + // use a flash calculation to calculate a fluid state in + // thermodynamic equilibrium + + // run the flash calculation + NcpFlash::template solve(fsFlash, matParams, paramCache, globalMolarities); + + // use the result to assign the primary variables + assignNaive(fsFlash); + } + + /*! + * \copydoc ImmisciblePrimaryVariables::assignNaive + */ + template + void assignNaive(const FluidState& fluidState) + { + using ConstEvaluation = typename std::remove_reference::type; + using FsEvaluation = typename std::remove_const::type; + using FsToolbox = MathToolbox; + + bool gasPresent = FluidSystem::phaseIsActive(gasPhaseIdx)?(fluidState.saturation(gasPhaseIdx) > 0.0):false; + bool oilPresent = FluidSystem::phaseIsActive(oilPhaseIdx)?(fluidState.saturation(oilPhaseIdx) > 0.0):false; + bool waterPresent = FluidSystem::phaseIsActive(waterPhaseIdx)?(fluidState.saturation(waterPhaseIdx) > 0.0):false; + const auto& saltSaturation = BlackOil::getSaltSaturation_(fluidState, pvtRegionIdx_); + bool precipitatedSaltPresent = enableSaltPrecipitation?(saltSaturation > 0.0):false; + bool oneActivePhases = FluidSystem::numActivePhases() == 1; + // deal with the primary variables for the energy extension + EnergyModule::assignPrimaryVars(*this, fluidState); + + // Determine the meaning of the pressure primary variables + // Depending on the phases present, this variable is either interpreted as the + // pressure of the oil phase, gas phase (if no oil) or water phase (if only water) + if (gasPresent && FluidSystem::enableVaporizedOil() && !oilPresent){ + primaryVarsMeaningPressure_ = PressureMeaning::Pg; + } else if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + primaryVarsMeaningPressure_ = PressureMeaning::Po; + } else if ( waterPresent && FluidSystem::enableDissolvedGasInWater() && !gasPresent){ + primaryVarsMeaningPressure_ = PressureMeaning::Pw; + } else if (FluidSystem::phaseIsActive(gasPhaseIdx)) { + primaryVarsMeaningPressure_ = PressureMeaning::Pg; + } else { + assert(FluidSystem::phaseIsActive(waterPhaseIdx)); + primaryVarsMeaningPressure_ = PressureMeaning::Pw; + } + + // Determine the meaning of the water primary variables + // Depending on the phases present, this variable is either interpreted as + // water saturation or vapporized water in the gas phase + // For two-phase gas-oil models and one-phase case the variable is disabled. + if ( waterPresent && gasPresent ){ + primaryVarsMeaningWater_ = WaterMeaning::Sw; + } else if (gasPresent && FluidSystem::enableVaporizedWater()) { + primaryVarsMeaningWater_ = WaterMeaning::Rvw; + } else if (waterPresent && FluidSystem::enableDissolvedGasInWater()) { + primaryVarsMeaningWater_ = WaterMeaning::Rsw; + } else if (FluidSystem::phaseIsActive(waterPhaseIdx) && !oneActivePhases) { + primaryVarsMeaningWater_ = WaterMeaning::Sw; + } else { + primaryVarsMeaningWater_ = WaterMeaning::Disabled; + } + + // Determine the meaning of the gas primary variables + // Depending on the phases present, this variable is either interpreted as the + // saturation of the gas phase, as the fraction of the gas component in the oil + // phase (Rs) or as the fraction of the oil component (Rv) in the gas phase. + // For two-phase water-oil and water-gas models and one-phase case the variable is disabled. + if ( gasPresent && oilPresent ) { + primaryVarsMeaningGas_ = GasMeaning::Sg; + } else if (oilPresent && FluidSystem::enableDissolvedGas()) { + primaryVarsMeaningGas_ = GasMeaning::Rs; + } else if (gasPresent && FluidSystem::enableVaporizedOil()){ + primaryVarsMeaningGas_ = GasMeaning::Rv; + } else if (FluidSystem::phaseIsActive(gasPhaseIdx) && FluidSystem::phaseIsActive(oilPhaseIdx)) { + primaryVarsMeaningGas_ = GasMeaning::Sg; + } else { + primaryVarsMeaningGas_ = GasMeaning::Disabled; + } + + // Determine the meaning of the brine primary variables + if constexpr (enableSaltPrecipitation){ + if (precipitatedSaltPresent) + primaryVarsMeaningBrine_ = BrineMeaning::Sp; + else + primaryVarsMeaningBrine_ = BrineMeaning::Cs; + } else { + primaryVarsMeaningBrine_ = BrineMeaning::Disabled; + } + + // assign the actual primary variables + switch(primaryVarsMeaningPressure()) { + case PressureMeaning::Po: + this->setScaledPressure_(FsToolbox::value(fluidState.pressure(oilPhaseIdx))); + break; + case PressureMeaning::Pg: + this->setScaledPressure_(FsToolbox::value(fluidState.pressure(gasPhaseIdx))); + break; + case PressureMeaning::Pw: + this->setScaledPressure_(FsToolbox::value(fluidState.pressure(waterPhaseIdx))); + break; + default: + throw std::logic_error("No valid primary variable selected for pressure"); + } + switch(primaryVarsMeaningWater()) { + case WaterMeaning::Sw: + { + (*this)[waterSwitchIdx] = FsToolbox::value(fluidState.saturation(waterPhaseIdx)); + break; + } + case WaterMeaning::Rvw: + { + const auto& rvw = BlackOil::getRvw_(fluidState, pvtRegionIdx_); + (*this)[waterSwitchIdx] = rvw; + break; + } + case WaterMeaning::Rsw: + { + const auto& Rsw = BlackOil::getRsw_(fluidState, pvtRegionIdx_); + (*this)[waterSwitchIdx] = Rsw; + break; + } + case WaterMeaning::Disabled: + { + break; + } + default: + throw std::logic_error("No valid primary variable selected for water"); + } + switch(primaryVarsMeaningGas()) { + case GasMeaning::Sg: + { + (*this)[compositionSwitchIdx] = FsToolbox::value(fluidState.saturation(gasPhaseIdx)); + break; + } + case GasMeaning::Rs: + { + const auto& rs = BlackOil::getRs_(fluidState, pvtRegionIdx_); + (*this)[compositionSwitchIdx] = rs; + break; + } + case GasMeaning::Rv: + { + const auto& rv = BlackOil::getRv_(fluidState, pvtRegionIdx_); + (*this)[compositionSwitchIdx] = rv; + break; + } + case GasMeaning::Disabled: + { + break; + } + default: + throw std::logic_error("No valid primary variable selected for composision"); + } + } + + /*! + * \brief Adapt the interpretation of the switching variables to be physically + * meaningful. + * + * If the meaning of the primary variables changes, their values are also adapted in a + * meaningful manner. + * A Scalar eps can be passed to make the switching condition more strict. + * Useful for avoiding ocsilation in the primaryVarsMeaning. + * + * \return true Iff the interpretation of one of the switching variables was changed + */ + bool adaptPrimaryVariables(const Problem& problem, + unsigned globalDofIdx, + [[maybe_unused]] Scalar swMaximum, + Scalar thresholdWaterFilledCell, Scalar eps = 0.0) + { + // this function accesses quite a few black-oil specific low-level functions + // directly for better performance (instead of going the canonical way through + // the IntensiveQuantities). The reason is that most intensive quantities are not + // required to be able to decide if the primary variables needs to be switched or + // not, so it would be a waste to compute them. + + // Both the primary variable meaning of water and gas are disabled i.e. + // It is a one-phase case and we no variable meaning switch is needed. + if (primaryVarsMeaningWater() == WaterMeaning::Disabled && primaryVarsMeaningGas() == GasMeaning::Disabled){ + return false; + } + + // Read the current saturation from the primary variables + Scalar sw = 0.0; + Scalar sg = 0.0; + Scalar saltConcentration = 0.0; + const Scalar& T = asImp_().temperature_(problem, globalDofIdx); + if (primaryVarsMeaningWater() == WaterMeaning::Sw) + sw = (*this)[waterSwitchIdx]; + if (primaryVarsMeaningGas() == GasMeaning::Sg) + sg = (*this)[compositionSwitchIdx]; + if (primaryVarsMeaningWater() == WaterMeaning::Rsw) + sw = 1.0; + + if (primaryVarsMeaningGas() == GasMeaning::Disabled && gasEnabled) + sg = 1.0 - sw; // water + gas case + + // if solid phase disappeares: Sp (Solid salt saturation) -> Cs (salt concentration) + // if solid phase appears: Cs (salt concentration) -> Sp (Solid salt saturation) + if constexpr (enableSaltPrecipitation) { + Scalar saltSolubility = BrineModule::saltSol(pvtRegionIndex()); + if (primaryVarsMeaningBrine() == BrineMeaning::Sp) { + saltConcentration = saltSolubility; + Scalar saltSat = (*this)[saltConcentrationIdx]; + if (saltSat < -eps){ //precipitated salt dissappears + setPrimaryVarsMeaningBrine(BrineMeaning::Cs); + (*this)[saltConcentrationIdx] = saltSolubility; //set salt concentration to solubility limit + } + } + else if (primaryVarsMeaningBrine() == BrineMeaning::Cs) { + saltConcentration = (*this)[saltConcentrationIdx]; + if (saltConcentration > saltSolubility + eps){ //salt concentration exceeds solubility limit + setPrimaryVarsMeaningBrine(BrineMeaning::Sp); + (*this)[saltConcentrationIdx] = 0.0; + } + } + } + + // if solvent saturation disappeares: Ss (Solvent saturation) -> Rsolw (solvent dissolved in water) + // if solvent saturation appears: Rsolw (solvent dissolved in water) -> Ss (Solvent saturation) + // Scalar rsolw = 0.0; // not needed at the moment since we dont allow for vapwat in combination with rsolw + if constexpr (enableSolvent) { + if (SolventModule::isSolubleInWater()) { + Scalar p = (*this)[pressureSwitchIdx]; // cap-pressure? + Scalar solLimit = SolventModule::solubilityLimit(pvtRegionIndex(), T , p, saltConcentration); + if (primaryVarsMeaningSolvent() == SolventMeaning::Ss) { + Scalar solSat = (*this)[solventSaturationIdx]; + if (solSat < -eps){ //solvent dissappears + setPrimaryVarsMeaningSolvent(SolventMeaning::Rsolw); + (*this)[solventSaturationIdx] = solLimit; //set rsolw to solubility limit + + } + } + else if (primaryVarsMeaningSolvent() == SolventMeaning::Rsolw) { + Scalar rsolw = (*this)[solventSaturationIdx]; + if (rsolw > solLimit + eps){ //solvent appears as phase + setPrimaryVarsMeaningSolvent(SolventMeaning::Ss); + (*this)[solventSaturationIdx] = 0.0; + } + } + } + } + + // keep track if any meaning has changed + bool changed = false; + + // Special case for cells with almost only water + // for these cells both saturations (if the phase is enabled) is used + // to avoid singular systems. + // If dissolved gas in water is enabled we shouldn't enter + // here but instead switch to Rsw as primary variable + // as sw >= 1.0 -> gas <= 0 (i.e. gas phase disappears) + if (sw >= thresholdWaterFilledCell && !FluidSystem::enableDissolvedGasInWater()) { + + // make sure water saturations does not exceed sw_maximum. Default to 1.0 + if constexpr (waterEnabled) { + (*this)[Indices::waterSwitchIdx] = std::min(swMaximum, sw); + assert(primaryVarsMeaningWater() == WaterMeaning::Sw); + } + // the hydrocarbon gas saturation is set to 0.0 + if constexpr (compositionSwitchEnabled) + (*this)[Indices::compositionSwitchIdx] = 0.0; + + changed = primaryVarsMeaningGas() != GasMeaning::Sg; + if(changed) { + if constexpr (compositionSwitchEnabled) + setPrimaryVarsMeaningGas(GasMeaning::Sg); + + // use water pressure? + } + return changed; + } + + if (BrineModule::hasPcfactTables() && primaryVarsMeaningBrine() == BrineMeaning::Sp) { + unsigned satnumRegionIdx = problem.satnumRegionIndex(globalDofIdx); + Scalar Sp = saltConcentration_(); + Scalar porosityFactor = min(1.0 - Sp, 1.0); //phi/phi_0 + const auto& pcfactTable = BrineModule::pcfactTable(satnumRegionIdx); + pcFactor_ = pcfactTable.eval(porosityFactor, /*extrapolation=*/true); + } + else { + pcFactor_ = 1.0; + } + + switch(primaryVarsMeaningWater()) { + case WaterMeaning::Sw: + { + // if water phase disappeares: Sw (water saturation) -> Rvw (fraction of water in gas phase) + if(sw < -eps && sg > eps && FluidSystem::enableVaporizedWater()) { + Scalar p = this->pressure_(); + if(primaryVarsMeaningPressure() == PressureMeaning::Po) { + std::array pC = { 0.0 }; + const MaterialLawParams& matParams = problem.materialLawParams(globalDofIdx); + Scalar so = 1.0 - sg - solventSaturation_(); + computeCapillaryPressures_(pC, so, sg + solventSaturation_(), /*sw=*/ 0.0, matParams); + p += pcFactor_ * (pC[gasPhaseIdx] - pC[oilPhaseIdx]); + } + Scalar rvwSat = FluidSystem::gasPvt().saturatedWaterVaporizationFactor(pvtRegionIdx_, + T, + p, + saltConcentration); + setPrimaryVarsMeaningWater(WaterMeaning::Rvw); + (*this)[Indices::waterSwitchIdx] = rvwSat; //primary variable becomes Rvw + changed = true; + break; + } + // if gas phase disappeares: Sw (water saturation) -> Rsw (fraction of gas in water phase) + // and Pg (gas pressure) -> Pw ( water pressure) + if(sg < -eps && sw > eps && FluidSystem::enableDissolvedGasInWater()) { + const Scalar pg = this->pressure_(); + assert(primaryVarsMeaningPressure() == PressureMeaning::Pg); + std::array pC = { 0.0 }; + const MaterialLawParams& matParams = problem.materialLawParams(globalDofIdx); + Scalar so = 1.0 - sw - solventSaturation_(); + computeCapillaryPressures_(pC, so, /*sg=*/ 0.0, sw, matParams); + Scalar pw = pg + pcFactor_ * (pC[waterPhaseIdx] - pC[gasPhaseIdx]); + Scalar rswSat = FluidSystem::waterPvt().saturatedGasDissolutionFactor(pvtRegionIdx_, + T, + pw, + saltConcentration); + setPrimaryVarsMeaningWater(WaterMeaning::Rsw); + Scalar rswMax = problem.maxGasDissolutionFactor(/*timeIdx=*/0, globalDofIdx); + (*this)[Indices::waterSwitchIdx] = min(rswSat, rswMax); //primary variable becomes Rsw + setPrimaryVarsMeaningPressure(PressureMeaning::Pw); + this->setScaledPressure_(pw); + changed = true; + break; + } + break; + } + case WaterMeaning::Rvw: + { + const Scalar& rvw = (*this)[waterSwitchIdx]; + Scalar p = this->pressure_(); + if(primaryVarsMeaningPressure() == PressureMeaning::Po) { + std::array pC = { 0.0 }; + const MaterialLawParams& matParams = problem.materialLawParams(globalDofIdx); + Scalar so = 1.0 - sg - solventSaturation_(); + computeCapillaryPressures_(pC, so, sg + solventSaturation_(), /*sw=*/ 0.0, matParams); + p += pcFactor_ * (pC[gasPhaseIdx] - pC[oilPhaseIdx]); + } + Scalar rvwSat = FluidSystem::gasPvt().saturatedWaterVaporizationFactor(pvtRegionIdx_, + T, + p, + saltConcentration); + // if water phase appears: Rvw (fraction of water in gas phase) -> Sw (water saturation) + if (rvw > rvwSat*(1.0 + eps)) { + setPrimaryVarsMeaningWater(WaterMeaning::Sw); + (*this)[Indices::waterSwitchIdx] = 0.0; // water saturation + changed = true; + } + break; + } + case WaterMeaning::Rsw: + { + // Gas phase not present. The hydrocarbon gas phase + // appears as soon as more of the gas component is present in the water phase + // than what saturated water can hold. + const Scalar& pw = this->pressure_(); + assert(primaryVarsMeaningPressure() == PressureMeaning::Pw); + Scalar rswSat = FluidSystem::waterPvt().saturatedGasDissolutionFactor(pvtRegionIdx_, + T, + pw, + saltConcentration); + + Scalar rsw = (*this)[Indices::waterSwitchIdx]; + Scalar rswMax = problem.maxGasDissolutionFactor(/*timeIdx=*/0, globalDofIdx); + if (rsw > min(rswSat, rswMax)) { + // the gas phase appears, i.e., switch the primary variables to WaterMeaning::Sw + setPrimaryVarsMeaningWater(WaterMeaning::Sw); + (*this)[Indices::waterSwitchIdx] = 1.0; // hydrocarbon water saturation + setPrimaryVarsMeaningPressure(PressureMeaning::Pg); + std::array pC = { 0.0 }; + const MaterialLawParams& matParams = problem.materialLawParams(globalDofIdx); + computeCapillaryPressures_(pC, /*so=*/ 0.0, /*sg=*/ 0.0, /*sw=*/ 1.0, matParams); + Scalar pg = pw + pcFactor_ * (pC[gasPhaseIdx] - pC[waterPhaseIdx]); + this->setScaledPressure_(pg); + changed = true; + } + break; + } + case WaterMeaning::Disabled: + { + break; + } + default: + throw std::logic_error("No valid primary variable selected for water"); + } + + + // if gas phase disappeares: Sg (gas saturation) -> Rs (fraction of gas in oil phase) + // if oil phase disappeares: Sg (gas saturation) -> Rv (fraction of oil in gas phase) + // Po (oil pressure ) -> Pg (gas pressure) + + // if gas phase appears: Rs (fraction of gas in oil phase) -> Sg (gas saturation) + // if oil phase appears: Rv (fraction of oil in gas phase) -> Sg (gas saturation) + // Pg (gas pressure ) -> Po (oil pressure) + switch(primaryVarsMeaningGas()) { + case GasMeaning::Sg: + { + Scalar s = 1.0 - sw - solventSaturation_(); + if (sg < -eps && s > 0.0 && FluidSystem::enableDissolvedGas()) { + const Scalar po = this->pressure_(); + setPrimaryVarsMeaningGas(GasMeaning::Rs); + Scalar soMax = std::max(s, problem.maxOilSaturation(globalDofIdx)); + Scalar rsMax = problem.maxGasDissolutionFactor(/*timeIdx=*/0, globalDofIdx); + Scalar rsSat = enableExtbo ? ExtboModule::rs(pvtRegionIndex(), + po, + zFraction_()) + : FluidSystem::oilPvt().saturatedGasDissolutionFactor(pvtRegionIdx_, + T, + po, + s, + soMax); + (*this)[Indices::compositionSwitchIdx] = std::min(rsMax, rsSat); + changed = true; + } + Scalar so = 1.0 - sw - solventSaturation_() - sg; + if (so < -eps && sg > 0.0 && FluidSystem::enableVaporizedOil()) { + // the oil phase disappeared and some hydrocarbon gas phase is still + // present, i.e., switch the primary variables to GasMeaning::Rv. + // we only have the oil pressure readily available, but we need the gas + // pressure, i.e. we must determine capillary pressure + const Scalar po = this->pressure_(); + std::array pC = { 0.0 }; + const MaterialLawParams& matParams = problem.materialLawParams(globalDofIdx); + computeCapillaryPressures_(pC, /*so=*/0.0, sg + solventSaturation_(), sw, matParams); + Scalar pg = po + pcFactor_ * (pC[gasPhaseIdx] - pC[oilPhaseIdx]); + + // we start at the GasMeaning::Rv value that corresponds to that of oil-saturated + // hydrocarbon gas + setPrimaryVarsMeaningPressure(PressureMeaning::Pg); + this->setScaledPressure_(pg); + Scalar soMax = problem.maxOilSaturation(globalDofIdx); + Scalar rvMax = problem.maxOilVaporizationFactor(/*timeIdx=*/0, globalDofIdx); + Scalar rvSat = enableExtbo ? ExtboModule::rv(pvtRegionIndex(), + pg, + zFraction_()) + : FluidSystem::gasPvt().saturatedOilVaporizationFactor(pvtRegionIdx_, + T, + pg, + Scalar(0), + soMax); + setPrimaryVarsMeaningGas(GasMeaning::Rv); + (*this)[Indices::compositionSwitchIdx] = std::min(rvMax, rvSat); + changed = true; + } + break; + } + case GasMeaning::Rs: + { + // Gas phase not present. The hydrocarbon gas phase + // appears as soon as more of the gas component is present in the oil phase + // than what saturated oil can hold. + const Scalar po = this->pressure_(); + Scalar so = 1.0 - sw - solventSaturation_(); + Scalar soMax = std::max(so, problem.maxOilSaturation(globalDofIdx)); + Scalar rsMax = problem.maxGasDissolutionFactor(/*timeIdx=*/0, globalDofIdx); + Scalar rsSat = enableExtbo ? ExtboModule::rs(pvtRegionIndex(), + po, + zFraction_()) + : FluidSystem::oilPvt().saturatedGasDissolutionFactor(pvtRegionIdx_, + T, + po, + so, + soMax); + + Scalar rs = (*this)[Indices::compositionSwitchIdx]; + if (rs > std::min(rsMax, rsSat*(Scalar{1.0} + eps))) { + // the gas phase appears, i.e., switch the primary variables to GasMeaning::Sg + setPrimaryVarsMeaningGas(GasMeaning::Sg); + (*this)[Indices::compositionSwitchIdx] = 0.0; // hydrocarbon gas saturation + changed = true; + } + break; + } + case GasMeaning::Rv: + { + // The oil phase appears as + // soon as more of the oil component is present in the hydrocarbon gas phase + // than what saturated gas contains. Note that we use the blackoil specific + // low-level PVT objects here for performance reasons. + const Scalar pg = this->pressure_(); + Scalar soMax = problem.maxOilSaturation(globalDofIdx); + Scalar rvMax = problem.maxOilVaporizationFactor(/*timeIdx=*/0, globalDofIdx); + Scalar rvSat = enableExtbo ? ExtboModule::rv(pvtRegionIndex(), + pg, + zFraction_()) + : FluidSystem::gasPvt().saturatedOilVaporizationFactor(pvtRegionIdx_, + T, + pg, + /*so=*/Scalar(0.0), + soMax); + + Scalar rv = (*this)[Indices::compositionSwitchIdx]; + if (rv > std::min(rvMax, rvSat*(Scalar{1.0} + eps))) { + // switch to phase equilibrium mode because the oil phase appears. here + // we also need the capillary pressures to calculate the oil phase + // pressure using the gas phase pressure + Scalar sg2 = 1.0 - sw - solventSaturation_(); + std::array pC = { 0.0 }; + const MaterialLawParams& matParams = problem.materialLawParams(globalDofIdx); + computeCapillaryPressures_(pC, + /*so=*/0.0, + /*sg=*/sg2 + solventSaturation_(), + sw, + matParams); + Scalar po = pg + pcFactor_ * (pC[oilPhaseIdx] - pC[gasPhaseIdx]); + + setPrimaryVarsMeaningGas(GasMeaning::Sg); + setPrimaryVarsMeaningPressure(PressureMeaning::Po); + this->setScaledPressure_(po); + (*this)[Indices::compositionSwitchIdx] = sg2; // hydrocarbon gas saturation + changed = true; + } + break; + } + case GasMeaning::Disabled: + { + break; + } + default: + throw std::logic_error("No valid primary variable selected for water"); + } + return changed; + } + + bool chopAndNormalizeSaturations(){ + if (primaryVarsMeaningWater() == WaterMeaning::Disabled && + primaryVarsMeaningGas() == GasMeaning::Disabled){ + return false; + } + Scalar sw = 0.0; + if (primaryVarsMeaningWater() == WaterMeaning::Sw) + sw = (*this)[Indices::waterSwitchIdx]; + Scalar sg = 0.0; + if (primaryVarsMeaningGas() == GasMeaning::Sg) + sg = (*this)[Indices::compositionSwitchIdx]; + + Scalar ssol = 0.0; + if (primaryVarsMeaningSolvent() == SolventMeaning::Ss) + ssol =(*this) [Indices::solventSaturationIdx]; + + Scalar so = 1.0 - sw - sg - ssol; + sw = std::min(std::max(sw, Scalar{0.0}), Scalar{1.0}); + so = std::min(std::max(so, Scalar{0.0}), Scalar{1.0}); + sg = std::min(std::max(sg, Scalar{0.0}), Scalar{1.0}); + ssol = std::min(std::max(ssol, Scalar{0.0}), Scalar{1.0}); + Scalar st = sw + so + sg + ssol; + sw = sw/st; + sg = sg/st; + ssol = ssol/st; + assert(st>0.5); + if (primaryVarsMeaningWater() == WaterMeaning::Sw) + (*this)[Indices::waterSwitchIdx] = sw; + if (primaryVarsMeaningGas() == GasMeaning::Sg) + (*this)[Indices::compositionSwitchIdx] = sg; + if (primaryVarsMeaningSolvent() == SolventMeaning::Ss) + (*this) [Indices::solventSaturationIdx] = ssol; + + return !(st==1); + } + + BlackOilPrimaryVariables& operator=(const BlackOilPrimaryVariables& other) = default; + BlackOilPrimaryVariables& operator=(Scalar value) + { + for (unsigned i = 0; i < numEq; ++i) + (*this)[i] = value; + + return *this; + } + + /*! + * \brief Instruct valgrind to check the definedness of all attributes of this class. + * + * We cannot simply check the definedness of the whole object because there might be + * "alignedness holes" in the memory layout which are caused by the pseudo primary + * variables. + */ + void checkDefined() const + { +#ifndef NDEBUG + // check the "real" primary variables + for (unsigned i = 0; i < this->size(); ++i) + Valgrind::CheckDefined((*this)[i]); + + // check the "pseudo" primary variables + Valgrind::CheckDefined(primaryVarsMeaningWater_); + Valgrind::CheckDefined(primaryVarsMeaningGas_); + Valgrind::CheckDefined(primaryVarsMeaningPressure_); + Valgrind::CheckDefined(primaryVarsMeaningBrine_); + Valgrind::CheckDefined(primaryVarsMeaningSolvent_); + + Valgrind::CheckDefined(pvtRegionIdx_); +#endif // NDEBUG + } + + template + void serializeOp(Serializer& serializer) + { + using FV = Dune::FieldVector()>; + serializer(static_cast(*this)); + serializer(primaryVarsMeaningWater_); + serializer(primaryVarsMeaningPressure_); + serializer(primaryVarsMeaningGas_); + serializer(primaryVarsMeaningBrine_); + serializer(primaryVarsMeaningSolvent_); + serializer(pvtRegionIdx_); + } + + bool operator==(const BlackOilPrimaryVariables& rhs) const + { + return static_cast&>(*this) == rhs && + this->primaryVarsMeaningWater_ == rhs.primaryVarsMeaningWater_ && + this->primaryVarsMeaningPressure_ == rhs.primaryVarsMeaningPressure_ && + this->primaryVarsMeaningGas_ == rhs.primaryVarsMeaningGas_ && + this->primaryVarsMeaningBrine_ == rhs.primaryVarsMeaningBrine_ && + this->primaryVarsMeaningSolvent_ == rhs.primaryVarsMeaningSolvent_ && + this->pvtRegionIdx_ == rhs.pvtRegionIdx_; + } + +private: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } + + Scalar solventSaturation_() const + { + if constexpr (enableSolvent) { + if ( primaryVarsMeaningSolvent() == SolventMeaning::Ss) + return (*this)[Indices::solventSaturationIdx]; + } + return 0.0; + } + + Scalar zFraction_() const + { + if constexpr (enableExtbo) + return (*this)[Indices::zFractionIdx]; + else + return 0.0; + } + + Scalar polymerConcentration_() const + { + if constexpr (enablePolymer) + return (*this)[Indices::polymerConcentrationIdx]; + else + return 0.0; + } + + Scalar foamConcentration_() const + { + if constexpr (enableFoam) + return (*this)[Indices::foamConcentrationIdx]; + else + return 0.0; + } + + Scalar saltConcentration_() const + { + if constexpr (enableBrine) + return (*this)[Indices::saltConcentrationIdx]; + else + return 0.0; + } + + Scalar temperature_(const Problem& problem, [[maybe_unused]] unsigned globalDofIdx) const + { + if constexpr (enableEnergy) + return (*this)[Indices::temperatureIdx]; + else if constexpr( enableTemperature) + return problem.temperature(globalDofIdx, /*timeIdx*/ 0); + + else + return FluidSystem::reservoirTemperature(); + } + + Scalar microbialConcentration_() const + { + if constexpr (enableMICP) + return (*this)[Indices::microbialConcentrationIdx]; + else + return 0.0; + } + + Scalar oxygenConcentration_() const + { + if constexpr (enableMICP) + return (*this)[Indices::oxygenConcentrationIdx]; + else + return 0.0; + } + + Scalar ureaConcentration_() const + { + if constexpr (enableMICP) + return (*this)[Indices::ureaConcentrationIdx]; + else + return 0.0; + } + + Scalar biofilmConcentration_() const + { + if constexpr (enableMICP) + return (*this)[Indices::biofilmConcentrationIdx]; + else + return 0.0; + } + + Scalar calciteConcentration_() const + { + if constexpr (enableMICP) + return (*this)[Indices::calciteConcentrationIdx]; + else + return 0.0; + } + + template + void computeCapillaryPressures_(Container& result, + Scalar so, + Scalar sg, + Scalar sw, + const MaterialLawParams& matParams) const + { + using SatOnlyFluidState = SimpleModularFluidState; + SatOnlyFluidState fluidState; + fluidState.setSaturation(waterPhaseIdx, sw); + fluidState.setSaturation(oilPhaseIdx, so); + fluidState.setSaturation(gasPhaseIdx, sg); + + MaterialLaw::capillaryPressures(result, matParams, fluidState); + } + + Scalar pressure_() const + { + return (*this)[Indices::pressureSwitchIdx] * this->pressureScale_; + } + + void setScaledPressure_(Scalar pressure) + { + (*this)[Indices::pressureSwitchIdx] = pressure / (this->pressureScale_); + } + + WaterMeaning primaryVarsMeaningWater_{WaterMeaning::Disabled}; + PressureMeaning primaryVarsMeaningPressure_{PressureMeaning::Po}; + GasMeaning primaryVarsMeaningGas_{GasMeaning::Disabled}; + BrineMeaning primaryVarsMeaningBrine_{BrineMeaning::Disabled}; + SolventMeaning primaryVarsMeaningSolvent_{SolventMeaning::Disabled}; + unsigned short pvtRegionIdx_; + Scalar pcFactor_; + static inline Scalar pressureScale_ = 1.0; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilproblem.hh b/opm/models/blackoil/blackoilproblem.hh new file mode 100644 index 00000000000..e7cd3246bf0 --- /dev/null +++ b/opm/models/blackoil/blackoilproblem.hh @@ -0,0 +1,194 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilProblem + */ +#ifndef EWOMS_BLACKOIL_PROBLEM_HH +#define EWOMS_BLACKOIL_PROBLEM_HH + +#include "blackoilproperties.hh" + +#include + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * \brief Base class for all problems which use the black-oil model. + */ +template +class BlackOilProblem : public MultiPhaseBaseProblem +{ +private: + using ParentType = MultiPhaseBaseProblem; + using Implementation = GetPropType; + using Scalar = GetPropType; + using IntensiveQuantities = GetPropType; + using Simulator = GetPropType; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + * + * \param simulator The manager object of the simulation + */ + BlackOilProblem(Simulator& simulator) + : ParentType(simulator) + {} + + /*! + * \brief Returns the maximum value of the gas dissolution factor at the current time + * for a given degree of freedom. + * + * This is required for the DRSDT keyword. + */ + Scalar maxGasDissolutionFactor(unsigned, unsigned) const + { return std::numeric_limits::max()/2; } + + /*! + * \brief Returns the maximum value of the oil vaporization factor at the current + * time for a given degree of freedom. + * + * This is required for the DRVDT keyword. + */ + Scalar maxOilVaporizationFactor(unsigned, unsigned) const + { return std::numeric_limits::max()/2; } + + /*! + * \brief Returns the maximum value of the oil saturation seen at the current time + * for a given degree of freedom. + * + * This is required for the VAPPARS keyword. + */ + Scalar maxOilSaturation(unsigned) const + { return 1.0; } + + /*! + * \brief Returns the index of the relevant region for thermodynmic properties + */ + template + unsigned pvtRegionIndex(const Context&, + unsigned, + unsigned) const + { return 0; } + + /*! + * \brief Returns the index of the relevant region for saturation functions + */ + template + unsigned satnumRegionIndex(const Context&, + unsigned, + unsigned) const + { return 0; } + + Scalar satnumRegionIndex(unsigned) const + { return 0; } + + /*! + * \brief Returns the index of the relevant region for solvent mixing functions + */ + template + unsigned miscnumRegionIndex(const Context&, + unsigned, + unsigned) const + { return 0; } + + /*! + * \brief Returns the index of the relevant region for polymer mixing functions + */ + template + unsigned plmixnumRegionIndex(const Context&, + unsigned, + unsigned) const + { return 0; } + + /*! + * \brief Returns the compressibility of the porous medium of a cell + */ + template + Scalar rockCompressibility(const Context&, + unsigned, + unsigned) const + { return 0.0; } + + /*! + * \brief Returns the compressibility of the porous medium of a cell + */ + Scalar rockCompressibility(unsigned) const + { return 0.0; } + + /*! + * \brief Returns the reference pressure for rock the compressibility of a cell + */ + template + Scalar rockReferencePressure(const Context&, + unsigned, + unsigned) const + { return 1e5; } + + /*! + * \brief Returns the reference pressure for rock the compressibility of a cell + */ + Scalar rockReferencePressure(unsigned) const + { return 1e5; } + + /*! + * \brief Returns the reference temperature + * + * This is only relevant for temperature dependent quantities, in particular those + * needed by the module for energy conservation. + */ + Scalar referenceTemperature() const + { return 273.15 + 15.56; /* [K] */ } + + /*! + * \brief Returns the porosity multiplier due to water-induced rock compaction + * + * This is a somewhat exotic feature. Most likely you will not need to touch this + * method. + */ + template + Scalar rockCompPoroMultiplier(const IntensiveQuantities&, + unsigned) const + { return 1.0; } + + template + LhsEval rockCompTransMultiplier(const IntensiveQuantities&, + unsigned) const + { return 1.0; } + +private: + //! Returns the implementation of the problem (i.e. static polymorphism) + Implementation& asImp_() + { return *static_cast(this); } + + //! \copydoc asImp_() + const Implementation& asImp_() const + { return *static_cast(this); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilproperties.hh b/opm/models/blackoil/blackoilproperties.hh new file mode 100644 index 00000000000..3094741656d --- /dev/null +++ b/opm/models/blackoil/blackoilproperties.hh @@ -0,0 +1,96 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup BlackOilModel + * + * \brief Declares the properties required by the black oil model. + */ +#ifndef EWOMS_BLACK_OIL_PROPERTIES_HH +#define EWOMS_BLACK_OIL_PROPERTIES_HH + +#include + +namespace Opm::Properties { + +//! Specifies if the simulation should write output files that are +//! compatible with those produced by the commercial Eclipse simulator +template +struct EnableEclipseOutput { using type = UndefinedProperty; }; +//! Enable the ECL-blackoil extension for solvents. ("Second gas") +template +struct EnableSolvent { using type = UndefinedProperty; }; +//! Enable the ECL-blackoil extension for extended BO. ("Second gas" - alternative approach) +template +struct EnableExtbo { using type = UndefinedProperty; }; +//! Enable the ECL-blackoil extension for polymer. +template +struct EnablePolymer { using type = UndefinedProperty; }; +//! Enable the tracking polymer molecular weight tracking and related functionalities +template +struct EnablePolymerMW { using type = UndefinedProperty; }; +//! Enable surface volume scaling +template +struct BlackoilConserveSurfaceVolume { using type = UndefinedProperty; }; +//! Enable the ECL-blackoil extension for foam +template +struct EnableFoam { using type = UndefinedProperty; }; +//! Enable the ECL-blackoil extension for salt +template +struct EnableBrine { using type = UndefinedProperty; }; +//! Enable the ECL-blackoil extension for salt precipitation +template +struct EnableSaltPrecipitation { using type = UndefinedProperty; }; +//! Enable the ECL-blackoil extension for water evaporation +template +struct EnableVapwat { using type = UndefinedProperty; }; +//! Enable the ECL-blackoil extension for disolution of gas into water +template +struct EnableDisgasInWater { using type = UndefinedProperty; }; +//! Enable the ECL-blackoil extension for MICP. +template +struct EnableMICP { using type = UndefinedProperty; }; + + +//! Allow the spatial and temporal domains to exhibit non-constant temperature +//! in the black-oil model +template +struct EnableTemperature { using type = UndefinedProperty; }; + +template +struct EnableMech { using type = UndefinedProperty; }; + +//! The relative weight of the residual of the energy equation compared to the mass +//! residuals +//! +//! this is basically a hack to work around the limitation that the convergence criterion +//! of unmodified dune-istl linear solvers cannot weight the individual equations. if the +//! energy equation is not scaled, its absolute value is normally several orders of +//! magnitude larger than that of the mass balance equations +template +struct BlackOilEnergyScalingFactor { using type = UndefinedProperty; }; + + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/blackoil/blackoilratevector.hh b/opm/models/blackoil/blackoilratevector.hh new file mode 100644 index 00000000000..93d1925eb50 --- /dev/null +++ b/opm/models/blackoil/blackoilratevector.hh @@ -0,0 +1,203 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilRateVector + */ +#ifndef EWOMS_BLACK_OIL_RATE_VECTOR_HH +#define EWOMS_BLACK_OIL_RATE_VECTOR_HH + +#include + +#include +#include + +#include "blackoilintensivequantities.hh" + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * + * \brief Implements a vector representing mass, molar or volumetric rates for + * the black oil model. + * + * This class is basically a Dune::FieldVector which can be set using + * either mass, molar or volumetric rates. + */ +template +class BlackOilRateVector + : public Dune::FieldVector, + getPropValue()> +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + using SolventModule = BlackOilSolventModule; + using PolymerModule = BlackOilPolymerModule; + using FoamModule = BlackOilFoamModule; + using BrineModule = BlackOilBrineModule; + using MICPModule = BlackOilMICPModule; + + enum { numEq = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { contiEnergyEqIdx = Indices::contiEnergyEqIdx }; + enum { enableEnergy = getPropValue() }; + enum { enableSolvent = getPropValue() }; + enum { enablePolymer = getPropValue() }; + enum { enablePolymerMolarWeight = getPropValue() }; + enum { enableFoam = getPropValue() }; + enum { enableBrine = getPropValue() }; + enum { enableMICP = getPropValue() }; + using Toolbox = MathToolbox; + using ParentType = Dune::FieldVector; + +public: + BlackOilRateVector() : ParentType() + { Valgrind::SetUndefined(*this); } + + /*! + * \copydoc ImmiscibleRateVector::ImmiscibleRateVector(Scalar) + */ + BlackOilRateVector(Scalar value) : ParentType(Toolbox::createConstant(value)) + {} + + /*! + * \copydoc ImmiscibleRateVector::setMassRate + */ + void setMassRate(const ParentType& value, unsigned pvtRegionIdx = 0) + { + ParentType::operator=(value); + + // convert to "surface volume" if requested + if constexpr (getPropValue()) { + if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { + (*this)[Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx)] /= + FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, pvtRegionIdx); + } + if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) { + (*this)[Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx)] /= + FluidSystem::referenceDensity(FluidSystem::oilPhaseIdx, pvtRegionIdx); + } + if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { + (*this)[Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx)] /= + FluidSystem::referenceDensity(FluidSystem::waterPhaseIdx, pvtRegionIdx); + } + if constexpr (enableSolvent) { + const auto& solventPvt = SolventModule::solventPvt(); + (*this)[Indices::contiSolventEqIdx] /= + solventPvt.referenceDensity(pvtRegionIdx); + } + + } + } + + /*! + * \copydoc ImmiscibleRateVector::setMolarRate + */ + void setMolarRate(const ParentType& value, unsigned pvtRegionIdx = 0) + { + // first, assign molar rates + ParentType::operator=(value); + + // then, convert them to mass rates + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + (*this)[conti0EqIdx + compIdx] *= FluidSystem::molarMass(compIdx, pvtRegionIdx); + + const auto& solventPvt = SolventModule::solventPvt(); + (*this)[Indices::contiSolventEqIdx] *= solventPvt.molarMass(pvtRegionIdx); + + if constexpr (enablePolymer) { + if constexpr (enablePolymerMolarWeight ) + throw std::logic_error("Set molar rate with polymer weight tracking not implemented"); + + (*this)[Indices::contiPolymerEqIdx] *= PolymerModule::molarMass(pvtRegionIdx); + } + + if constexpr (enableFoam) { + throw std::logic_error("setMolarRate() not implemented for foam"); + } + + if constexpr (enableBrine) { + throw std::logic_error("setMolarRate() not implemented for salt water"); + } + + if constexpr (enableMICP) { + throw std::logic_error("setMolarRate() not implemented for MICP"); + } + + // convert to "surface volume" if requested + if constexpr (getPropValue()) { + if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { + (*this)[Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx)] /= + FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, pvtRegionIdx); + } + if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) { + (*this)[Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx)] /= + FluidSystem::referenceDensity(FluidSystem::oilPhaseIdx, pvtRegionIdx); + } + if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { + (*this)[Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx)] /= + FluidSystem::referenceDensity(FluidSystem::waterPhaseIdx, pvtRegionIdx); + } + if constexpr (enableSolvent) { + (*this)[Indices::contiSolventEqIdx] /= + solventPvt.referenceDensity(pvtRegionIdx); + } + } + } + + /*! + * \copydoc ImmiscibleRateVector::setVolumetricRate + */ + template + void setVolumetricRate(const FluidState& fluidState, + unsigned phaseIdx, + const RhsEval& volume) + { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + (*this)[conti0EqIdx + compIdx] = + fluidState.density(phaseIdx) + * fluidState.massFraction(phaseIdx, compIdx) + * volume; + } + + /*! + * \brief Assignment operator from a scalar or a function evaluation + */ + template + BlackOilRateVector& operator=(const RhsEval& value) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = value; + return *this; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilsolventmodules.hh b/opm/models/blackoil/blackoilsolventmodules.hh new file mode 100644 index 00000000000..b0052e5064b --- /dev/null +++ b/opm/models/blackoil/blackoilsolventmodules.hh @@ -0,0 +1,1579 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the classes required to extend the black-oil model by solvents. + */ +#ifndef EWOMS_BLACK_OIL_SOLVENT_MODULE_HH +#define EWOMS_BLACK_OIL_SOLVENT_MODULE_HH + +#include "blackoilproperties.hh" + +#include + +#include +#include +#include +#include + +#include + +#if HAVE_ECL_INPUT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include + +#include + +namespace Opm { + +/*! + * \ingroup BlackOil + * \brief Contains the high level supplements required to extend the black oil + * model by solvents. + */ +template ()> +class BlackOilSolventModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Model = GetPropType; + using Simulator = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + using Toolbox = MathToolbox; + using SolventPvt = typename BlackOilSolventParams::SolventPvt; + using Co2GasPvt = typename BlackOilSolventParams::Co2GasPvt; + using H2GasPvt = typename BlackOilSolventParams::H2GasPvt; + using BrineCo2Pvt = typename BlackOilSolventParams::BrineCo2Pvt; + using BrineH2Pvt = typename BlackOilSolventParams::BrineH2Pvt; + + using TabulatedFunction = typename BlackOilSolventParams::TabulatedFunction; + + static constexpr unsigned solventSaturationIdx = Indices::solventSaturationIdx; + static constexpr unsigned contiSolventEqIdx = Indices::contiSolventEqIdx; + static constexpr unsigned enableSolvent = enableSolventV; + static constexpr unsigned numEq = getPropValue(); + static constexpr unsigned numPhases = FluidSystem::numPhases; + static constexpr bool blackoilConserveSurfaceVolume = getPropValue(); + static constexpr int waterPhaseIdx = FluidSystem::waterPhaseIdx; + + +public: +#if HAVE_ECL_INPUT + /*! + * \brief Initialize all internal data structures needed by the solvent module + */ + static void initFromState(const EclipseState& eclState, const Schedule& schedule) + { + // some sanity checks: if solvents are enabled, the SOLVENT keyword must be + // present, if solvents are disabled the keyword must not be present. + if (enableSolvent && !eclState.runspec().phases().active(Phase::SOLVENT)) + throw std::runtime_error("Non-trivial solvent treatment requested at compile " + "time, but the deck does not contain the SOLVENT keyword"); + else if (!enableSolvent && eclState.runspec().phases().active(Phase::SOLVENT)) + throw std::runtime_error("Solvent treatment disabled at compile time, but the deck " + "contains the SOLVENT keyword"); + + if (!eclState.runspec().phases().active(Phase::SOLVENT)) + return; // solvent treatment is supposed to be disabled + + params_.co2sol_ = eclState.runspec().co2Sol(); + params_.h2sol_ = eclState.runspec().h2Sol(); + + if (isCO2Sol() && isH2Sol()) { + throw std::runtime_error("CO2SOL and H2SOL can not be used together"); + } + + if (isCO2Sol() || isH2Sol() ) { + if (isCO2Sol()) { + params_.co2GasPvt_.initFromState(eclState, schedule); + params_.brineCo2Pvt_.initFromState(eclState, schedule); + } else { + params_.h2GasPvt_.initFromState(eclState, schedule); + params_.brineH2Pvt_.initFromState(eclState, schedule); + } + if (eclState.getSimulationConfig().hasDISGASW()) { + params_.rsSolw_active_ = true; + } + } else + params_.solventPvt_.initFromState(eclState, schedule); + + const auto& tableManager = eclState.getTableManager(); + // initialize the objects which deal with the SSFN keyword + const auto& ssfnTables = tableManager.getSsfnTables(); + unsigned numSatRegions = tableManager.getTabdims().getNumSatTables(); + params_.setNumSatRegions(numSatRegions); + for (unsigned satRegionIdx = 0; satRegionIdx < numSatRegions; ++ satRegionIdx) { + const auto& ssfnTable = ssfnTables.template getTable(satRegionIdx); + params_.ssfnKrg_[satRegionIdx].setXYContainers(ssfnTable.getSolventFractionColumn(), + ssfnTable.getGasRelPermMultiplierColumn(), + /*sortInput=*/true); + params_.ssfnKrs_[satRegionIdx].setXYContainers(ssfnTable.getSolventFractionColumn(), + ssfnTable.getSolventRelPermMultiplierColumn(), + /*sortInput=*/true); + } + + // initialize the objects needed for miscible solvent and oil simulations + params_.isMiscible_ = false; + if (!eclState.getTableManager().getMiscTables().empty()) { + params_.isMiscible_ = true; + + unsigned numMiscRegions = 1; + + // misicible hydrocabon relative permeability wrt water + const auto& sof2Tables = tableManager.getSof2Tables(); + if (!sof2Tables.empty()) { + // resize the attributes of the object + params_.sof2Krn_.resize(numSatRegions); + for (unsigned satRegionIdx = 0; satRegionIdx < numSatRegions; ++ satRegionIdx) { + const auto& sof2Table = sof2Tables.template getTable(satRegionIdx); + params_.sof2Krn_[satRegionIdx].setXYContainers(sof2Table.getSoColumn(), + sof2Table.getKroColumn(), + /*sortInput=*/true); + } + } + else if(eclState.runspec().phases().active(Phase::OIL)) + throw std::runtime_error("SOF2 must be specified in MISCIBLE (SOLVENT and OIL) runs\n"); + + const auto& miscTables = tableManager.getMiscTables(); + if (!miscTables.empty()) { + assert(numMiscRegions == miscTables.size()); + + // resize the attributes of the object + params_.misc_.resize(numMiscRegions); + for (unsigned miscRegionIdx = 0; miscRegionIdx < numMiscRegions; ++miscRegionIdx) { + const auto& miscTable = miscTables.template getTable(miscRegionIdx); + + // solventFraction = Ss / (Ss + Sg); + const auto& solventFraction = miscTable.getSolventFractionColumn(); + const auto& misc = miscTable.getMiscibilityColumn(); + params_.misc_[miscRegionIdx].setXYContainers(solventFraction, misc); + } + } + else + throw std::runtime_error("MISC must be specified in MISCIBLE (SOLVENT) runs\n"); + + // resize the attributes of the object + params_.pmisc_.resize(numMiscRegions); + const auto& pmiscTables = tableManager.getPmiscTables(); + if (!pmiscTables.empty()) { + assert(numMiscRegions == pmiscTables.size()); + + for (unsigned regionIdx = 0; regionIdx < numMiscRegions; ++regionIdx) { + const auto& pmiscTable = pmiscTables.template getTable(regionIdx); + + // Copy data + const auto& po = pmiscTable.getOilPhasePressureColumn(); + const auto& pmisc = pmiscTable.getMiscibilityColumn(); + + params_.pmisc_[regionIdx].setXYContainers(po, pmisc); + } + } + else { + std::vector x = {0.0,1.0e20}; + std::vector y = {1.0,1.0}; + TabulatedFunction constant = TabulatedFunction(2, x, y); + for (unsigned regionIdx = 0; regionIdx < numMiscRegions; ++regionIdx) { + params_.pmisc_[regionIdx] = constant; + } + } + + // miscible relative permeability multipleiers + params_.msfnKrsg_.resize(numSatRegions); + params_.msfnKro_.resize(numSatRegions); + const auto& msfnTables = tableManager.getMsfnTables(); + if (!msfnTables.empty()) { + assert(numSatRegions == msfnTables.size()); + + for (unsigned regionIdx = 0; regionIdx < numSatRegions; ++regionIdx) { + const MsfnTable& msfnTable = msfnTables.template getTable(regionIdx); + + // Copy data + // Ssg = Ss + Sg; + const auto& Ssg = msfnTable.getGasPhaseFractionColumn(); + const auto& krsg = msfnTable.getGasSolventRelpermMultiplierColumn(); + const auto& kro = msfnTable.getOilRelpermMultiplierColumn(); + + params_.msfnKrsg_[regionIdx].setXYContainers(Ssg, krsg); + params_.msfnKro_[regionIdx].setXYContainers(Ssg, kro); + } + } + else { + std::vector x = {0.0,1.0}; + std::vector y = {1.0,0.0}; + TabulatedFunction unit = TabulatedFunction(2, x, x); + TabulatedFunction invUnit = TabulatedFunction(2, x, y); + + for (unsigned regionIdx = 0; regionIdx < numSatRegions; ++regionIdx) { + params_.setMsfn(regionIdx, unit, invUnit); + } + } + // resize the attributes of the object + params_.sorwmis_.resize(numMiscRegions); + const auto& sorwmisTables = tableManager.getSorwmisTables(); + if (!sorwmisTables.empty()) { + assert(numMiscRegions == sorwmisTables.size()); + + for (unsigned regionIdx = 0; regionIdx < numMiscRegions; ++regionIdx) { + const auto& sorwmisTable = sorwmisTables.template getTable(regionIdx); + + // Copy data + const auto& sw = sorwmisTable.getWaterSaturationColumn(); + const auto& sorwmis = sorwmisTable.getMiscibleResidualOilColumn(); + + params_.sorwmis_[regionIdx].setXYContainers(sw, sorwmis); + } + } + else { + // default + std::vector x = {0.0,1.0}; + std::vector y = {0.0,0.0}; + TabulatedFunction zero = TabulatedFunction(2, x, y); + for (unsigned regionIdx = 0; regionIdx < numMiscRegions; ++regionIdx) { + params_.sorwmis_[regionIdx] = zero; + } + } + + // resize the attributes of the object + params_.sgcwmis_.resize(numMiscRegions); + const auto& sgcwmisTables = tableManager.getSgcwmisTables(); + if (!sgcwmisTables.empty()) { + assert(numMiscRegions == sgcwmisTables.size()); + + for (unsigned regionIdx = 0; regionIdx < numMiscRegions; ++regionIdx) { + const auto& sgcwmisTable = sgcwmisTables.template getTable(regionIdx); + + // Copy data + const auto& sw = sgcwmisTable.getWaterSaturationColumn(); + const auto& sgcwmis = sgcwmisTable.getMiscibleResidualGasColumn(); + + params_.sgcwmis_[regionIdx].setXYContainers(sw, sgcwmis); + } + } + else { + // default + std::vector x = {0.0,1.0}; + std::vector y = {0.0,0.0}; + TabulatedFunction zero = TabulatedFunction(2, x, y); + for (unsigned regionIdx = 0; regionIdx < numMiscRegions; ++regionIdx) + params_.sgcwmis_[regionIdx] = zero; + } + + const auto& tlmixpar = eclState.getTableManager().getTLMixpar(); + if (!tlmixpar.empty()) { + // resize the attributes of the object + params_.tlMixParamViscosity_.resize(numMiscRegions); + params_.tlMixParamDensity_.resize(numMiscRegions); + + assert(numMiscRegions == tlmixpar.size()); + for (unsigned regionIdx = 0; regionIdx < numMiscRegions; ++regionIdx) { + const auto& tlp = tlmixpar[regionIdx]; + params_.tlMixParamViscosity_[regionIdx] = tlp.viscosity_parameter; + params_.tlMixParamDensity_[regionIdx] = tlp.density_parameter; + } + } + else + throw std::runtime_error("TLMIXPAR must be specified in MISCIBLE (SOLVENT) runs\n"); + + // resize the attributes of the object + params_.tlPMixTable_.resize(numMiscRegions); + if (!eclState.getTableManager().getTlpmixpaTables().empty()) { + const auto& tlpmixparTables = tableManager.getTlpmixpaTables(); + if (!tlpmixparTables.empty()) { + assert(numMiscRegions == tlpmixparTables.size()); + for (unsigned regionIdx = 0; regionIdx < numMiscRegions; ++regionIdx) { + const auto& tlpmixparTable = tlpmixparTables.template getTable(regionIdx); + + // Copy data + const auto& po = tlpmixparTable.getOilPhasePressureColumn(); + const auto& tlpmixpa = tlpmixparTable.getMiscibilityColumn(); + + params_.tlPMixTable_[regionIdx].setXYContainers(po, tlpmixpa); + } + } + else { + // if empty keyword. Try to use the pmisc table as default. + if (params_.pmisc_.size() > 0) + params_.tlPMixTable_ = params_.pmisc_; + else + throw std::invalid_argument("If the pressure dependent TL values in " + "TLPMIXPA is defaulted (no entries), then " + "the PMISC tables must be specified."); + } + } + else { + // default + std::vector x = {0.0,1.0e20}; + std::vector y = {1.0,1.0}; + TabulatedFunction ones = TabulatedFunction(2, x, y); + for (unsigned regionIdx = 0; regionIdx < numMiscRegions; ++regionIdx) + params_.tlPMixTable_[regionIdx] = ones; + } + } + } +#endif + + /*! + * \brief Specify the solvent PVT of a all PVT regions. + */ + static void setSolventPvt(const SolventPvt& value) + { params_.solventPvt_ = value; } + + + static void setIsMiscible(const bool isMiscible) + { params_.isMiscible_ = isMiscible; } + + /*! + * \brief Register all run-time parameters for the black-oil solvent module. + */ + static void registerParameters() + { + if constexpr (enableSolvent) + VtkBlackOilSolventModule::registerParameters(); + } + + /*! + * \brief Register all solvent specific VTK and ECL output modules. + */ + static void registerOutputModules(Model& model, + Simulator& simulator) + { + if constexpr (enableSolvent) + model.addOutputModule(new VtkBlackOilSolventModule(simulator)); + } + + static bool primaryVarApplies(unsigned pvIdx) + { + if constexpr (enableSolvent) + return pvIdx == solventSaturationIdx; + else + return false; + } + + static std::string primaryVarName([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + return "saturation_solvent"; + } + + static Scalar primaryVarWeight([[maybe_unused]] unsigned pvIdx) + { + assert(primaryVarApplies(pvIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + static bool eqApplies(unsigned eqIdx) + { + if constexpr (enableSolvent) + return eqIdx == contiSolventEqIdx; + else + return false; + } + + static std::string eqName([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + return "conti^solvent"; + } + + static Scalar eqWeight([[maybe_unused]] unsigned eqIdx) + { + assert(eqApplies(eqIdx)); + + // TODO: it may be beneficial to chose this differently. + return static_cast(1.0); + } + + template + static void addStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants) + { + if constexpr (enableSolvent) { + if constexpr (blackoilConserveSurfaceVolume) { + storage[contiSolventEqIdx] += + Toolbox::template decay(intQuants.porosity()) + * Toolbox::template decay(intQuants.solventSaturation()) + * Toolbox::template decay(intQuants.solventInverseFormationVolumeFactor()); + if (isSolubleInWater()) { + storage[contiSolventEqIdx] += Toolbox::template decay(intQuants.porosity()) + * Toolbox::template decay(intQuants.fluidState().saturation(waterPhaseIdx)) + * Toolbox::template decay(intQuants.fluidState().invB(waterPhaseIdx)) + * Toolbox::template decay(intQuants.rsSolw()); + } + } + else { + storage[contiSolventEqIdx] += + Toolbox::template decay(intQuants.porosity()) + * Toolbox::template decay(intQuants.solventSaturation()) + * Toolbox::template decay(intQuants.solventDensity()); + if (isSolubleInWater()) { + storage[contiSolventEqIdx] += Toolbox::template decay(intQuants.porosity()) + * Toolbox::template decay(intQuants.fluidState().saturation(waterPhaseIdx)) + * Toolbox::template decay(intQuants.fluidState().density(waterPhaseIdx)) + * Toolbox::template decay(intQuants.rsSolw()); + } + + } + } + } + + static void computeFlux([[maybe_unused]] RateVector& flux, + [[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned scvfIdx, + [[maybe_unused]] unsigned timeIdx) + + { + if constexpr (enableSolvent) { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + unsigned upIdx = extQuants.solventUpstreamIndex(); + unsigned inIdx = extQuants.interiorIndex(); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + + if constexpr (blackoilConserveSurfaceVolume) { + if (upIdx == inIdx) + flux[contiSolventEqIdx] = + extQuants.solventVolumeFlux() + *up.solventInverseFormationVolumeFactor(); + else + flux[contiSolventEqIdx] = + extQuants.solventVolumeFlux() + *decay(up.solventInverseFormationVolumeFactor()); + + + if (isSolubleInWater()) { + if (upIdx == inIdx) + flux[contiSolventEqIdx] += + extQuants.volumeFlux(waterPhaseIdx) + * up.fluidState().invB(waterPhaseIdx) + * up.rsSolw(); + else + flux[contiSolventEqIdx] += + extQuants.volumeFlux(waterPhaseIdx) + *decay(up.fluidState().invB(waterPhaseIdx)) + *decay(up.rsSolw()); + } + } + else { + if (upIdx == inIdx) + flux[contiSolventEqIdx] = + extQuants.solventVolumeFlux() + *up.solventDensity(); + else + flux[contiSolventEqIdx] = + extQuants.solventVolumeFlux() + *decay(up.solventDensity()); + + + if (isSolubleInWater()) { + if (upIdx == inIdx) + flux[contiSolventEqIdx] += + extQuants.volumeFlux(waterPhaseIdx) + * up.fluidState().density(waterPhaseIdx) + * up.rsSolw(); + else + flux[contiSolventEqIdx] += + extQuants.volumeFlux(waterPhaseIdx) + *decay(up.fluidState().density(waterPhaseIdx)) + *decay(up.rsSolw()); + } + } + } + } + + /*! + * \brief Assign the solvent specific primary variables to a PrimaryVariables object + */ + static void assignPrimaryVars(PrimaryVariables& priVars, + Scalar solventSaturation, + Scalar solventRsw) + { + if constexpr (!enableSolvent) { + priVars.setPrimaryVarsMeaningSolvent(PrimaryVariables::SolventMeaning::Disabled); + return; + } + // Determine the meaning of the solvent primary variables + if (solventSaturation > 0 || !isSolubleInWater()) { + priVars.setPrimaryVarsMeaningSolvent(PrimaryVariables::SolventMeaning::Ss); + priVars[solventSaturationIdx] = solventSaturation; + } else { + priVars.setPrimaryVarsMeaningSolvent(PrimaryVariables::SolventMeaning::Rsolw); + priVars[solventSaturationIdx] = solventRsw; + } + } + + /*! + * \brief Do a Newton-Raphson update the primary variables of the solvents. + */ + static void updatePrimaryVars(PrimaryVariables& newPv, + const PrimaryVariables& oldPv, + const EqVector& delta) + { + if constexpr (enableSolvent) + // do a plain unchopped Newton update + newPv[solventSaturationIdx] = oldPv[solventSaturationIdx] - delta[solventSaturationIdx]; + } + + /*! + * \brief Return how much a Newton-Raphson update is considered an error + */ + static Scalar computeUpdateError(const PrimaryVariables&, + const EqVector&) + { + // do not consider consider the cange of solvent primary variables for + // convergence + // TODO: maybe this should be changed + return static_cast(0.0); + } + + /*! + * \brief Return how much a residual is considered an error + */ + static Scalar computeResidualError(const EqVector& resid) + { + // do not weight the residual of solvents when it comes to convergence + return std::abs(Toolbox::scalarValue(resid[contiSolventEqIdx])); + } + + template + static void serializeEntity(const Model& model, std::ostream& outstream, const DofEntity& dof) + { + if constexpr (enableSolvent) { + unsigned dofIdx = model.dofMapper().index(dof); + + const PrimaryVariables& priVars = model.solution(/*timeIdx=*/0)[dofIdx]; + outstream << priVars[solventSaturationIdx]; + } + } + + template + static void deserializeEntity(Model& model, std::istream& instream, const DofEntity& dof) + { + if constexpr (enableSolvent) { + unsigned dofIdx = model.dofMapper().index(dof); + + PrimaryVariables& priVars0 = model.solution(/*timeIdx=*/0)[dofIdx]; + PrimaryVariables& priVars1 = model.solution(/*timeIdx=*/1)[dofIdx]; + + instream >> priVars0[solventSaturationIdx]; + + // set the primary variables for the beginning of the current time step. + priVars1 = priVars0[solventSaturationIdx]; + } + } + + static const SolventPvt& solventPvt() + { + return params_.solventPvt_; + } + + + static const Co2GasPvt& co2GasPvt() + { + return params_.co2GasPvt_; + } + + static const H2GasPvt& h2GasPvt() + { + return params_.h2GasPvt_; + } + + static const BrineCo2Pvt& brineCo2Pvt() + { + return params_.brineCo2Pvt_; + } + + static const BrineH2Pvt& brineH2Pvt() + { + return params_.brineH2Pvt_; + } + + static const TabulatedFunction& ssfnKrg(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.ssfnKrg_[satnumRegionIdx]; + } + + static const TabulatedFunction& ssfnKrs(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.ssfnKrs_[satnumRegionIdx]; + } + + static const TabulatedFunction& sof2Krn(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.sof2Krn_[satnumRegionIdx]; + } + + static const TabulatedFunction& misc(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned miscnumRegionIdx = elemCtx.problem().miscnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.misc_[miscnumRegionIdx]; + } + + static const TabulatedFunction& pmisc(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned miscnumRegionIdx = elemCtx.problem().miscnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.pmisc_[miscnumRegionIdx]; + } + + static const TabulatedFunction& msfnKrsg(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.msfnKrsg_[satnumRegionIdx]; + } + + static const TabulatedFunction& msfnKro(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned satnumRegionIdx = elemCtx.problem().satnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.msfnKro_[satnumRegionIdx]; + } + + static const TabulatedFunction& sorwmis(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned miscnumRegionIdx = elemCtx.problem().miscnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.sorwmis_[miscnumRegionIdx]; + } + + static const TabulatedFunction& sgcwmis(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned miscnumRegionIdx = elemCtx.problem().miscnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.sgcwmis_[miscnumRegionIdx]; + } + + static const TabulatedFunction& tlPMixTable(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned miscnumRegionIdx = elemCtx.problem().miscnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.tlPMixTable_[miscnumRegionIdx]; + } + + static const Scalar& tlMixParamViscosity(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned miscnumRegionIdx = elemCtx.problem().miscnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.tlMixParamViscosity_[miscnumRegionIdx]; + } + + static const Scalar& tlMixParamDensity(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + unsigned miscnumRegionIdx = elemCtx.problem().miscnumRegionIndex(elemCtx, scvIdx, timeIdx); + return params_.tlMixParamDensity_[miscnumRegionIdx]; + } + + static bool isMiscible() + { + return params_.isMiscible_; + } + + template + static const Value solubilityLimit(unsigned pvtIdx, const Value& temperature, const Value& pressure, const Value& saltConcentration) + { + if (!isSolubleInWater()) + return 0.0; + + assert(isCO2Sol() || isH2Sol()); + if (isCO2Sol()) + return brineCo2Pvt().saturatedGasDissolutionFactor(pvtIdx, temperature, pressure, saltConcentration); + else + return brineH2Pvt().saturatedGasDissolutionFactor(pvtIdx, temperature, pressure, saltConcentration); + } + + static bool isSolubleInWater() + { + return params_.rsSolw_active_; + } + + static bool isCO2Sol() + { + return params_.co2sol_; + } + + static bool isH2Sol() + { + return params_.h2sol_; + } + +private: + static BlackOilSolventParams params_; // the krg(Fs) column of the SSFN table +}; + +template +BlackOilSolventParams::Scalar> +BlackOilSolventModule::params_; + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilSolventIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the equations needed by the + * solvents extension of the black-oil model. + */ +template ()> +class BlackOilSolventIntensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using PrimaryVariables = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using Indices = GetPropType; + using ElementContext = GetPropType; + + using SolventModule = BlackOilSolventModule; + + enum { numPhases = getPropValue() }; + static constexpr int solventSaturationIdx = Indices::solventSaturationIdx; + static constexpr int oilPhaseIdx = FluidSystem::oilPhaseIdx; + static constexpr int gasPhaseIdx = FluidSystem::gasPhaseIdx; + static constexpr int waterPhaseIdx = FluidSystem::waterPhaseIdx; + static constexpr double cutOff = 1e-12; + + +public: + /*! + * \brief Called before the saturation functions are doing their magic + * + * At this point, the saturations of the fluid state correspond to those if the phases + * were pure hydrocarbons. + */ + void solventPreSatFuncUpdate_(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + const PrimaryVariables& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + + auto& fs = asImp_().fluidState_; + solventSaturation_ = 0.0; + if (priVars.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) { + solventSaturation_ = priVars.makeEvaluation(solventSaturationIdx, timeIdx, elemCtx.linearizationType()); + } + + hydrocarbonSaturation_ = fs.saturation(gasPhaseIdx); + + // apply a cut-off. Don't waste calculations if no solvent + if (solventSaturation().value() < cutOff) + return; + + // make the saturation of the gas phase which is used by the saturation functions + // the sum of the solvent "saturation" and the saturation the hydrocarbon gas. + fs.setSaturation(gasPhaseIdx, hydrocarbonSaturation_ + solventSaturation_); + } + + /*! + * \brief Called after the saturation functions have been doing their magic + * + * After this function, all saturations, pressures + * and relative permeabilities must be final. (i.e., the "hydrocarbon + * saturations".) + */ + void solventPostSatFuncUpdate_(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + // revert the gas "saturation" of the fluid state back to the saturation of the + // hydrocarbon gas. + auto& fs = asImp_().fluidState_; + fs.setSaturation(gasPhaseIdx, hydrocarbonSaturation_); + + + // update rsSolw. This needs to be done after the pressure is defined in the fluid state. + rsSolw_ = 0.0; + const PrimaryVariables& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + if (priVars.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) { + rsSolw_ = SolventModule::solubilityLimit(asImp_().pvtRegionIndex(), fs.temperature(waterPhaseIdx), fs.pressure(waterPhaseIdx), fs.saltConcentration()); + } else if (priVars.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Rsolw) { + rsSolw_ = priVars.makeEvaluation(solventSaturationIdx, timeIdx, elemCtx.linearizationType()); + } + + solventMobility_ = 0.0; + + // apply a cut-off. Don't waste calculations if no solvent + if (solventSaturation().value() < cutOff) + return; + + // Pressure effects on capillary pressure miscibility + if (SolventModule::isMiscible()) { + const Evaluation& p = FluidSystem::phaseIsActive(oilPhaseIdx)? fs.pressure(oilPhaseIdx) : fs.pressure(gasPhaseIdx); + const Evaluation pmisc = SolventModule::pmisc(elemCtx, dofIdx, timeIdx).eval(p, /*extrapolate=*/true); + const Evaluation& pgImisc = fs.pressure(gasPhaseIdx); + + // compute capillary pressure for miscible fluid + const auto& problem = elemCtx.problem(); + Evaluation pgMisc = 0.0; + std::array pC; + const auto& materialParams = problem.materialLawParams(elemCtx, dofIdx, timeIdx); + MaterialLaw::capillaryPressures(pC, materialParams, fs); + + //oil is the reference phase for pressure + const auto linearizationType = elemCtx.linearizationType(); + if (priVars.primaryVarsMeaningPressure() == PrimaryVariables::PressureMeaning::Pg) + pgMisc = priVars.makeEvaluation(Indices::pressureSwitchIdx, timeIdx, linearizationType); + else { + const Evaluation& po = priVars.makeEvaluation(Indices::pressureSwitchIdx, timeIdx, linearizationType); + pgMisc = po + (pC[gasPhaseIdx] - pC[oilPhaseIdx]); + } + + fs.setPressure(gasPhaseIdx, pmisc * pgMisc + (1.0 - pmisc) * pgImisc); + } + + + Evaluation gasSolventSat = hydrocarbonSaturation_ + solventSaturation_; + + if (gasSolventSat.value() < cutOff) // avoid division by zero + return; + + Evaluation Fhydgas = hydrocarbonSaturation_/gasSolventSat; + Evaluation Fsolgas = solventSaturation_/gasSolventSat; + + // account for miscibility of oil and solvent + if (SolventModule::isMiscible() && FluidSystem::phaseIsActive(oilPhaseIdx)) { + const auto& misc = SolventModule::misc(elemCtx, dofIdx, timeIdx); + const auto& pmisc = SolventModule::pmisc(elemCtx, dofIdx, timeIdx); + const Evaluation& p = FluidSystem::phaseIsActive(oilPhaseIdx)? fs.pressure(oilPhaseIdx) : fs.pressure(gasPhaseIdx); + const Evaluation miscibility = misc.eval(Fsolgas, /*extrapolate=*/true) * pmisc.eval(p, /*extrapolate=*/true); + + // TODO adjust endpoints of sn and ssg + unsigned cellIdx = elemCtx.globalSpaceIndex(dofIdx, timeIdx); + const auto& materialLawManager = elemCtx.problem().materialLawManager(); + const auto& scaledDrainageInfo = + materialLawManager->oilWaterScaledEpsInfoDrainage(cellIdx); + + const Scalar& sogcr = scaledDrainageInfo.Sogcr; + Evaluation sor = sogcr; + if (FluidSystem::phaseIsActive(waterPhaseIdx)) { + const Evaluation& sw = fs.saturation(waterPhaseIdx); + const auto& sorwmis = SolventModule::sorwmis(elemCtx, dofIdx, timeIdx); + sor = miscibility * sorwmis.eval(sw, /*extrapolate=*/true) + (1.0 - miscibility) * sogcr; + } + const Scalar& sgcr = scaledDrainageInfo.Sgcr; + Evaluation sgc = sgcr; + if (FluidSystem::phaseIsActive(waterPhaseIdx)) { + const Evaluation& sw = fs.saturation(waterPhaseIdx); + const auto& sgcwmis = SolventModule::sgcwmis(elemCtx, dofIdx, timeIdx); + sgc = miscibility * sgcwmis.eval(sw, /*extrapolate=*/true) + (1.0 - miscibility) * sgcr; + } + + Evaluation oilGasSolventSat = gasSolventSat; + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + oilGasSolventSat += fs.saturation(oilPhaseIdx); + } + const Evaluation zero = 0.0; + const Evaluation oilGasSolventEffSat = std::max(oilGasSolventSat - sor - sgc, zero); + + Evaluation F_totalGas = 0.0; + if (oilGasSolventEffSat.value() > cutOff) { + const Evaluation gasSolventEffSat = std::max(gasSolventSat - sgc, zero); + F_totalGas = gasSolventEffSat / oilGasSolventEffSat; + } + const auto& msfnKro = SolventModule::msfnKro(elemCtx, dofIdx, timeIdx); + const auto& msfnKrsg = SolventModule::msfnKrsg(elemCtx, dofIdx, timeIdx); + const auto& sof2Krn = SolventModule::sof2Krn(elemCtx, dofIdx, timeIdx); + + const Evaluation mkrgt = msfnKrsg.eval(F_totalGas, /*extrapolate=*/true) * sof2Krn.eval(oilGasSolventSat, /*extrapolate=*/true); + const Evaluation mkro = msfnKro.eval(F_totalGas, /*extrapolate=*/true) * sof2Krn.eval(oilGasSolventSat, /*extrapolate=*/true); + + Evaluation& kro = asImp_().mobility_[oilPhaseIdx]; + Evaluation& krg = asImp_().mobility_[gasPhaseIdx]; + + // combine immiscible and miscible part of the relperm + krg *= (1.0 - miscibility); + krg += miscibility * mkrgt; + kro *= (1.0 - miscibility); + kro += miscibility * mkro; + } + + // compute the mobility of the solvent "phase" and modify the gas phase + const auto& ssfnKrg = SolventModule::ssfnKrg(elemCtx, dofIdx, timeIdx); + const auto& ssfnKrs = SolventModule::ssfnKrs(elemCtx, dofIdx, timeIdx); + + Evaluation& krg = asImp_().mobility_[gasPhaseIdx]; + solventMobility_ = krg * ssfnKrs.eval(Fsolgas, /*extrapolate=*/true); + krg *= ssfnKrg.eval(Fhydgas, /*extrapolate=*/true); + + } + + /*! + * \brief Update the intensive PVT properties needed to handle solvents from the + * primary variables. + * + * At this point the pressures and saturations of the fluid state are correct. + */ + void solventPvtUpdate_(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + const auto& iq = asImp_(); + unsigned pvtRegionIdx = iq.pvtRegionIndex(); + const Evaluation& T = iq.fluidState().temperature(gasPhaseIdx); + const Evaluation& p = iq.fluidState().pressure(gasPhaseIdx); + + const Evaluation rv = 0.0; + const Evaluation rvw = 0.0; + if (SolventModule::isCO2Sol() || SolventModule::isH2Sol() ){ + if (SolventModule::isCO2Sol()) { + const auto& co2gasPvt = SolventModule::co2GasPvt(); + solventInvFormationVolumeFactor_ = co2gasPvt.inverseFormationVolumeFactor(pvtRegionIdx, T, p, rv, rvw); + solventRefDensity_ = co2gasPvt.gasReferenceDensity(pvtRegionIdx); + solventViscosity_ = co2gasPvt.viscosity(pvtRegionIdx, T, p, rv, rvw); + + const auto& brineCo2Pvt = SolventModule::brineCo2Pvt(); + auto& fs = asImp_().fluidState_; + const auto& bw = brineCo2Pvt.inverseFormationVolumeFactor(pvtRegionIdx, T, p, rsSolw()); + + const auto denw = bw*brineCo2Pvt.waterReferenceDensity(pvtRegionIdx) + + rsSolw()*bw*brineCo2Pvt.gasReferenceDensity(pvtRegionIdx); + fs.setDensity(waterPhaseIdx, denw); + fs.setInvB(waterPhaseIdx, bw); + Evaluation& mobw = asImp_().mobility_[waterPhaseIdx]; + const auto& muWat = fs.viscosity(waterPhaseIdx); + const auto& muWatEff = brineCo2Pvt.viscosity(pvtRegionIdx, T, p, rsSolw()); + mobw *= muWat / muWatEff; + } else { + const auto& h2gasPvt = SolventModule::h2GasPvt(); + solventInvFormationVolumeFactor_ = h2gasPvt.inverseFormationVolumeFactor(pvtRegionIdx, T, p, rv, rvw); + solventRefDensity_ = h2gasPvt.gasReferenceDensity(pvtRegionIdx); + solventViscosity_ = h2gasPvt.viscosity(pvtRegionIdx, T, p, rv, rvw); + + const auto& brineH2Pvt = SolventModule::brineH2Pvt(); + auto& fs = asImp_().fluidState_; + const auto& bw = brineH2Pvt.inverseFormationVolumeFactor(pvtRegionIdx, T, p, rsSolw()); + + const auto denw = bw*brineH2Pvt.waterReferenceDensity(pvtRegionIdx) + + rsSolw()*bw*brineH2Pvt.gasReferenceDensity(pvtRegionIdx); + fs.setDensity(waterPhaseIdx, denw); + fs.setInvB(waterPhaseIdx, bw); + Evaluation& mobw = asImp_().mobility_[waterPhaseIdx]; + const auto& muWat = fs.viscosity(waterPhaseIdx); + const auto& muWatEff = brineH2Pvt.viscosity(pvtRegionIdx, T, p, rsSolw()); + mobw *= muWat / muWatEff; + } + } else { + const auto& solventPvt = SolventModule::solventPvt(); + solventInvFormationVolumeFactor_ = solventPvt.inverseFormationVolumeFactor(pvtRegionIdx, T, p); + solventRefDensity_ = solventPvt.referenceDensity(pvtRegionIdx); + solventViscosity_ = solventPvt.viscosity(pvtRegionIdx, T, p); + } + + solventDensity_ = solventInvFormationVolumeFactor_*solventRefDensity_; + effectiveProperties(elemCtx, scvIdx, timeIdx); + + solventMobility_ /= solventViscosity_; + + + } + + const Evaluation& solventSaturation() const + { return solventSaturation_; } + + const Evaluation& rsSolw() const + { return rsSolw_; } + + const Evaluation& solventDensity() const + { return solventDensity_; } + + const Evaluation& solventViscosity() const + { return solventViscosity_; } + + const Evaluation& solventMobility() const + { return solventMobility_; } + + const Evaluation& solventInverseFormationVolumeFactor() const + { return solventInvFormationVolumeFactor_; } + + // This could be stored pr pvtRegion instead + const Scalar& solventRefDensity() const + { return solventRefDensity_; } + +private: + // Computes the effective properties based on + // Todd-Longstaff mixing model. + void effectiveProperties(const ElementContext& elemCtx, + unsigned scvIdx, + unsigned timeIdx) + { + if (!SolventModule::isMiscible()) + return; + + // Don't waste calculations if no solvent + // Apply a cut-off for small and negative solvent saturations + if (solventSaturation() < cutOff) + return; + + // We plan to update the fluidstate with the effective + // properties + auto& fs = asImp_().fluidState_; + + // Compute effective saturations + Evaluation oilEffSat = 0.0; + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + oilEffSat = fs.saturation(oilPhaseIdx); + } + Evaluation gasEffSat = fs.saturation(gasPhaseIdx); + Evaluation solventEffSat = solventSaturation(); + if (FluidSystem::phaseIsActive(waterPhaseIdx)) { + const auto& sorwmis = SolventModule::sorwmis(elemCtx, scvIdx, timeIdx); + const auto& sgcwmis = SolventModule::sgcwmis(elemCtx, scvIdx, timeIdx); + const Evaluation zero = 0.0; + const Evaluation& sw = fs.saturation(waterPhaseIdx); + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + oilEffSat = std::max(oilEffSat - sorwmis.eval(sw, /*extrapolate=*/true), zero); + } + gasEffSat = std::max(gasEffSat - sgcwmis.eval(sw, /*extrapolate=*/true), zero); + solventEffSat = std::max(solventEffSat - sgcwmis.eval(sw, /*extrapolate=*/true), zero); + } + const Evaluation oilGasSolventEffSat = oilEffSat + gasEffSat + solventEffSat; + const Evaluation oilSolventEffSat = oilEffSat + solventEffSat; + const Evaluation solventGasEffSat = solventEffSat + gasEffSat; + + // Compute effective viscosities + + // Mixing parameter for viscosity + // The pressureMixingParameter represent the miscibility of the solvent while the mixingParameterViscosity the effect of the porous media. + // The pressureMixingParameter is not implemented in ecl100. + const Evaluation& p = FluidSystem::phaseIsActive(oilPhaseIdx)? fs.pressure(oilPhaseIdx) : fs.pressure(gasPhaseIdx); + // account for pressure effects + const auto& pmiscTable = SolventModule::pmisc(elemCtx, scvIdx, timeIdx); + const Evaluation pmisc = pmiscTable.eval(p, /*extrapolate=*/true); + const auto& tlPMixTable = SolventModule::tlPMixTable(elemCtx, scvIdx, timeIdx); + const Evaluation tlMixParamMu = SolventModule::tlMixParamViscosity(elemCtx, scvIdx, timeIdx) * tlPMixTable.eval(p, /*extrapolate=*/true); + + const Evaluation& muGas = fs.viscosity(gasPhaseIdx); + const Evaluation& muSolvent = solventViscosity_; + + assert(muGas.value() > 0); + assert(muSolvent.value() > 0); + const Evaluation muGasPow = pow(muGas, 0.25); + const Evaluation muSolventPow = pow(muSolvent, 0.25); + + Evaluation muMixSolventGas = muGas; + if (solventGasEffSat > cutOff) + muMixSolventGas *= muSolvent / pow(((gasEffSat / solventGasEffSat) * muSolventPow) + ((solventEffSat / solventGasEffSat) * muGasPow) , 4.0); + + Evaluation muOil = 1.0; + Evaluation muOilPow = 1.0; + Evaluation muMixOilSolvent = 1.0; + Evaluation muOilEff = 1.0; + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + muOil = fs.viscosity(oilPhaseIdx); + assert(muOil.value() > 0); + muOilPow = pow(muOil, 0.25); + muMixOilSolvent = muOil; + if (oilSolventEffSat > cutOff) + muMixOilSolvent *= muSolvent / pow(((oilEffSat / oilSolventEffSat) * muSolventPow) + ((solventEffSat / oilSolventEffSat) * muOilPow) , 4.0); + + muOilEff = pow(muOil,1.0 - tlMixParamMu) * pow(muMixOilSolvent, tlMixParamMu); + } + Evaluation muMixSolventGasOil = muOil; + if (oilGasSolventEffSat > cutOff) + muMixSolventGasOil *= muSolvent * muGas / pow(((oilEffSat / oilGasSolventEffSat) * muSolventPow * muGasPow) + + ((solventEffSat / oilGasSolventEffSat) * muOilPow * muGasPow) + ((gasEffSat / oilGasSolventEffSat) * muSolventPow * muOilPow), 4.0); + + Evaluation muGasEff = pow(muGas,1.0 - tlMixParamMu) * pow(muMixSolventGas, tlMixParamMu); + Evaluation muSolventEff = pow(muSolvent,1.0 - tlMixParamMu) * pow(muMixSolventGasOil, tlMixParamMu); + + // Compute effective densities + const Evaluation& rhoGas = fs.density(gasPhaseIdx); + Evaluation rhoOil = 0.0; + if (FluidSystem::phaseIsActive(oilPhaseIdx)) + rhoOil = fs.density(oilPhaseIdx); + + const Evaluation& rhoSolvent = solventDensity_; + + // Mixing parameter for density + // The pressureMixingParameter represent the miscibility of the solvent while the mixingParameterDenisty the effect of the porous media. + // The pressureMixingParameter is not implemented in ecl100. + const Evaluation tlMixParamRho = SolventModule::tlMixParamDensity(elemCtx, scvIdx, timeIdx) * tlPMixTable.eval(p, /*extrapolate=*/true); + + // compute effective viscosities for density calculations. These have to + // be recomputed as a different mixing parameter may be used. + const Evaluation muOilEffPow = pow(pow(muOil, 1.0 - tlMixParamRho) * pow(muMixOilSolvent, tlMixParamRho), 0.25); + const Evaluation muGasEffPow = pow(pow(muGas, 1.0 - tlMixParamRho) * pow(muMixSolventGas, tlMixParamRho), 0.25); + const Evaluation muSolventEffPow = pow(pow(muSolvent, 1.0 - tlMixParamRho) * pow(muMixSolventGasOil, tlMixParamRho), 0.25); + + const Evaluation oilGasEffSaturation = oilEffSat + gasEffSat; + Evaluation sof = 0.0; + Evaluation sgf = 0.0; + if (oilGasEffSaturation.value() > cutOff) { + sof = oilEffSat / oilGasEffSaturation; + sgf = gasEffSat / oilGasEffSaturation; + } + + const Evaluation muSolventOilGasPow = muSolventPow * ((sgf * muOilPow) + (sof * muGasPow)); + + Evaluation rhoMixSolventGasOil = 0.0; + if (oilGasSolventEffSat.value() > cutOff) + rhoMixSolventGasOil = (rhoOil * oilEffSat / oilGasSolventEffSat) + (rhoGas * gasEffSat / oilGasSolventEffSat) + (rhoSolvent * solventEffSat / oilGasSolventEffSat); + + Evaluation rhoGasEff = 0.0; + if (std::abs(muSolventPow.value() - muGasPow.value()) < cutOff) + rhoGasEff = ((1.0 - tlMixParamRho) * rhoGas) + (tlMixParamRho * rhoMixSolventGasOil); + else { + const Evaluation solventGasEffFraction = (muGasPow * (muSolventPow - muGasEffPow)) / (muGasEffPow * (muSolventPow - muGasPow)); + rhoGasEff = (rhoGas * solventGasEffFraction) + (rhoSolvent * (1.0 - solventGasEffFraction)); + } + + Evaluation rhoSolventEff = 0.0; + if (std::abs((muSolventOilGasPow.value() - (muOilPow.value() * muGasPow.value()))) < cutOff) + rhoSolventEff = ((1.0 - tlMixParamRho) * rhoSolvent) + (tlMixParamRho * rhoMixSolventGasOil); + else { + const Evaluation sfraction_se = (muSolventOilGasPow - (muOilPow * muGasPow * muSolventPow / muSolventEffPow)) / (muSolventOilGasPow - (muOilPow * muGasPow)); + rhoSolventEff = (rhoSolvent * sfraction_se) + (rhoGas * sgf * (1.0 - sfraction_se)) + (rhoOil * sof * (1.0 - sfraction_se)); + } + + // compute invB from densities. + unsigned pvtRegionIdx = asImp_().pvtRegionIndex(); + Evaluation bGasEff = rhoGasEff; + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + bGasEff /= (FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx) + FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx) * fs.Rv()); + } else { + bGasEff /= (FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx)); + } + const Evaluation bSolventEff = rhoSolventEff / solventRefDensity(); + + // copy the unmodified invB factors + const Evaluation bg = fs.invB(gasPhaseIdx); + const Evaluation bs = solventInverseFormationVolumeFactor(); + const Evaluation bg_eff = pmisc * bGasEff + (1.0 - pmisc) * bg; + const Evaluation bs_eff = pmisc * bSolventEff + (1.0 - pmisc) * bs; + + // set the densities + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + fs.setDensity(gasPhaseIdx, + bg_eff + *(FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx) + + FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx)*fs.Rv())); + } else { + fs.setDensity(gasPhaseIdx, + bg_eff + *FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx)); + } + solventDensity_ = bs_eff*solventRefDensity(); + + // set the mobility + Evaluation& mobg = asImp_().mobility_[gasPhaseIdx]; + muGasEff = bg_eff / (pmisc * bGasEff / muGasEff + (1.0 - pmisc) * bg / muGas); + mobg *= muGas / muGasEff; + + // Update viscosity of solvent + solventViscosity_ = bs_eff / (pmisc * bSolventEff / muSolventEff + (1.0 - pmisc) * bs / muSolvent); + + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + Evaluation rhoOilEff = 0.0; + if (std::abs(muOilPow.value() - muSolventPow.value()) < cutOff) { + rhoOilEff = ((1.0 - tlMixParamRho) * rhoOil) + (tlMixParamRho * rhoMixSolventGasOil); + } + else { + const Evaluation solventOilEffFraction = (muOilPow * (muOilEffPow - muSolventPow)) / (muOilEffPow * (muOilPow - muSolventPow)); + rhoOilEff = (rhoOil * solventOilEffFraction) + (rhoSolvent * (1.0 - solventOilEffFraction)); + } + const Evaluation bOilEff = rhoOilEff / (FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx) + FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx) * fs.Rs()); + const Evaluation bo = fs.invB(oilPhaseIdx); + const Evaluation bo_eff = pmisc * bOilEff + (1.0 - pmisc) * bo; + fs.setDensity(oilPhaseIdx, + bo_eff + *(FluidSystem::referenceDensity(oilPhaseIdx, pvtRegionIdx) + + FluidSystem::referenceDensity(gasPhaseIdx, pvtRegionIdx)*fs.Rs())); + + // keep the mu*b interpolation + Evaluation& mobo = asImp_().mobility_[oilPhaseIdx]; + muOilEff = bo_eff / (pmisc * bOilEff / muOilEff + (1.0 - pmisc) * bo / muOil); + mobo *= muOil / muOilEff; + } + } + +protected: + Implementation& asImp_() + { return *static_cast(this); } + + Evaluation hydrocarbonSaturation_; + Evaluation solventSaturation_; + Evaluation rsSolw_; + Evaluation solventDensity_; + Evaluation solventViscosity_; + Evaluation solventMobility_; + Evaluation solventInvFormationVolumeFactor_; + + Scalar solventRefDensity_; +}; + +template +class BlackOilSolventIntensiveQuantities +{ + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + + +public: + void solventPreSatFuncUpdate_(const ElementContext&, + unsigned, + unsigned) + { } + + void solventPostSatFuncUpdate_(const ElementContext&, + unsigned, + unsigned) + { } + + void solventPvtUpdate_(const ElementContext&, + unsigned, + unsigned) + { } + + const Evaluation& solventSaturation() const + { throw std::runtime_error("solventSaturation() called but solvents are disabled"); } + + const Evaluation& rsSolw() const + { throw std::runtime_error("rsSolw() called but solvents are disabled"); } + + const Evaluation& solventDensity() const + { throw std::runtime_error("solventDensity() called but solvents are disabled"); } + + const Evaluation& solventViscosity() const + { throw std::runtime_error("solventViscosity() called but solvents are disabled"); } + + const Evaluation& solventMobility() const + { throw std::runtime_error("solventMobility() called but solvents are disabled"); } + + const Evaluation& solventInverseFormationVolumeFactor() const + { throw std::runtime_error("solventInverseFormationVolumeFactor() called but solvents are disabled"); } + + const Scalar& solventRefDensity() const + { throw std::runtime_error("solventRefDensity() called but solvents are disabled"); } +}; + +/*! + * \ingroup BlackOil + * \class Opm::BlackOilSolventExtensiveQuantities + * + * \brief Provides the solvent specific extensive quantities to the generic black-oil + * module's extensive quantities. + */ +template ()> +class BlackOilSolventExtensiveQuantities +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using GridView = GetPropType; + + using Toolbox = MathToolbox; + + static constexpr unsigned gasPhaseIdx = FluidSystem::gasPhaseIdx; + static constexpr int dimWorld = GridView::dimensionworld; + + using DimVector = Dune::FieldVector; + using DimEvalVector = Dune::FieldVector; + +public: + /*! + * \brief Method which calculates the volume flux of the polymer "phase" using the + * pressure potential gradient of the gas phase and the intrinsic permeability + */ + template // we need to make this method a template to avoid + // compiler errors if it is not instantiated! + void updateVolumeFluxPerm(const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) + { + const auto& gradCalc = elemCtx.gradientCalculator(); + PressureCallback pressureCallback(elemCtx); + + const auto& scvf = elemCtx.stencil(timeIdx).interiorFace(scvfIdx); + const auto& faceNormal = scvf.normal(); + + unsigned i = scvf.interiorIndex(); + unsigned j = scvf.exteriorIndex(); + + // calculate the "raw" pressure gradient + DimEvalVector solventPGrad; + pressureCallback.setPhaseIndex(gasPhaseIdx); + gradCalc.calculateGradient(solventPGrad, + elemCtx, + scvfIdx, + pressureCallback); + Valgrind::CheckDefined(solventPGrad); + + // correct the pressure gradients by the gravitational acceleration + if (Parameters::Get()) { + // estimate the gravitational acceleration at a given SCV face + // using the arithmetic mean + const auto& gIn = elemCtx.problem().gravity(elemCtx, i, timeIdx); + const auto& gEx = elemCtx.problem().gravity(elemCtx, j, timeIdx); + + const auto& intQuantsIn = elemCtx.intensiveQuantities(i, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(j, timeIdx); + + const auto& posIn = elemCtx.pos(i, timeIdx); + const auto& posEx = elemCtx.pos(j, timeIdx); + const auto& posFace = scvf.integrationPos(); + + // the distance between the centers of the control volumes + DimVector distVecIn(posIn); + DimVector distVecEx(posEx); + DimVector distVecTotal(posEx); + + distVecIn -= posFace; + distVecEx -= posFace; + distVecTotal -= posIn; + Scalar absDistTotalSquared = distVecTotal.two_norm2(); + + // calculate the hydrostatic pressure at the integration point of the face + auto rhoIn = intQuantsIn.solventDensity(); + auto pStatIn = - rhoIn*(gIn*distVecIn); + + // the quantities on the exterior side of the face do not influence the + // result for the TPFA scheme, so they can be treated as scalar values. + Scalar rhoEx = Toolbox::value(intQuantsEx.solventDensity()); + Scalar pStatEx = - rhoEx*(gEx*distVecEx); + + // compute the hydrostatic gradient between the two control volumes (this + // gradient exhibitis the same direction as the vector between the two + // control volume centers and the length (pStaticExterior - + // pStaticInterior)/distanceInteriorToExterior + DimEvalVector f(distVecTotal); + f *= (pStatEx - pStatIn)/absDistTotalSquared; + + // calculate the final potential gradient + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) { + solventPGrad[dimIdx] += f[dimIdx]; + + if (!isfinite(solventPGrad[dimIdx])) + throw NumericalProblem("Non-finite potential gradient for solvent 'phase'"); + } + } + + // determine the upstream and downstream DOFs + Evaluation solventPGradNormal = 0.0; + for (unsigned dimIdx = 0; dimIdx < faceNormal.size(); ++dimIdx) + solventPGradNormal += solventPGrad[dimIdx]*faceNormal[dimIdx]; + + if (solventPGradNormal > 0) { + solventUpstreamDofIdx_ = j; + solventDownstreamDofIdx_ = i; + } + else { + solventUpstreamDofIdx_ = i; + solventDownstreamDofIdx_ = j; + } + + const auto& up = elemCtx.intensiveQuantities(solventUpstreamDofIdx_, timeIdx); + + // this is also slightly hacky because it assumes that the derivative of the + // flux between two DOFs only depends on the primary variables in the + // upstream direction. For non-TPFA flux approximation schemes, this is not + // true... + if (solventUpstreamDofIdx_ == i) + solventVolumeFlux_ = solventPGradNormal*up.solventMobility(); + else + solventVolumeFlux_ = solventPGradNormal*scalarValue(up.solventMobility()); + } + + /*! + * \brief Method which calculates the volume flux of the polymer "phase" using the + * gas pressure potential difference between cells and transmissibilities + */ + template // we need to make this method a template to avoid + // compiler errors if it is not instantiated! + void updateVolumeFluxTrans(const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) + { + const ExtensiveQuantities& extQuants = asImp_(); + + unsigned interiorDofIdx = extQuants.interiorIndex(); + unsigned exteriorDofIdx = extQuants.exteriorIndex(); + assert(interiorDofIdx != exteriorDofIdx); + + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx, timeIdx); + + unsigned I = elemCtx.globalSpaceIndex(interiorDofIdx, timeIdx); + unsigned J = elemCtx.globalSpaceIndex(exteriorDofIdx, timeIdx); + + Scalar thpres = elemCtx.problem().thresholdPressure(I, J); + Scalar trans = elemCtx.problem().transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); + Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; + + Scalar zIn = elemCtx.problem().dofCenterDepth(elemCtx, interiorDofIdx, timeIdx); + Scalar zEx = elemCtx.problem().dofCenterDepth(elemCtx, exteriorDofIdx, timeIdx); + Scalar distZ = zIn - zEx; + + const Evaluation& rhoIn = intQuantsIn.solventDensity(); + Scalar rhoEx = Toolbox::value(intQuantsEx.solventDensity()); + const Evaluation& rhoAvg = rhoIn*0.5 + rhoEx*0.5; + + const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(gasPhaseIdx); + Evaluation pressureExterior = Toolbox::value(intQuantsEx.fluidState().pressure(gasPhaseIdx)); + pressureExterior += distZ*g*rhoAvg; + + Evaluation pressureDiffSolvent = pressureExterior - pressureInterior; + if (std::abs(scalarValue(pressureDiffSolvent)) > thpres) { + if (pressureDiffSolvent < 0.0) + pressureDiffSolvent += thpres; + else + pressureDiffSolvent -= thpres; + } + else + pressureDiffSolvent = 0.0; + + if (pressureDiffSolvent > 0.0) { + solventUpstreamDofIdx_ = exteriorDofIdx; + solventDownstreamDofIdx_ = interiorDofIdx; + } + else if (pressureDiffSolvent < 0.0) { + solventUpstreamDofIdx_ = interiorDofIdx; + solventDownstreamDofIdx_ = exteriorDofIdx; + } + else { + // pressure potential gradient is zero; force consistent upstream and + // downstream indices over the intersection regardless of the side which it + // is looked at. + solventUpstreamDofIdx_ = std::min(interiorDofIdx, exteriorDofIdx); + solventDownstreamDofIdx_ = std::max(interiorDofIdx, exteriorDofIdx); + solventVolumeFlux_ = 0.0; + return; + } + + Scalar faceArea = elemCtx.stencil(timeIdx).interiorFace(scvfIdx).area(); + const IntensiveQuantities& up = elemCtx.intensiveQuantities(solventUpstreamDofIdx_, timeIdx); + if (solventUpstreamDofIdx_ == interiorDofIdx) + solventVolumeFlux_ = + up.solventMobility() + *(-trans/faceArea) + *pressureDiffSolvent; + else + solventVolumeFlux_ = + scalarValue(up.solventMobility()) + *(-trans/faceArea) + *pressureDiffSolvent; + } + + unsigned solventUpstreamIndex() const + { return solventUpstreamDofIdx_; } + + unsigned solventDownstreamIndex() const + { return solventDownstreamDofIdx_; } + + const Evaluation& solventVolumeFlux() const + { return solventVolumeFlux_; } + + void setSolventVolumeFlux(const Evaluation& solventVolumeFlux) + { solventVolumeFlux_ = solventVolumeFlux; } + +private: + Implementation& asImp_() + { return *static_cast(this); } + + Evaluation solventVolumeFlux_; + unsigned solventUpstreamDofIdx_; + unsigned solventDownstreamDofIdx_; +}; + +template +class BlackOilSolventExtensiveQuantities +{ + using ElementContext = GetPropType; + using Evaluation = GetPropType; + +public: + void updateVolumeFluxPerm(const ElementContext&, + unsigned, + unsigned) + { } + + void updateVolumeFluxTrans(const ElementContext&, + unsigned, + unsigned) + { } + + unsigned solventUpstreamIndex() const + { throw std::runtime_error("solventUpstreamIndex() called but solvents are disabled"); } + + unsigned solventDownstreamIndex() const + { throw std::runtime_error("solventDownstreamIndex() called but solvents are disabled"); } + + const Evaluation& solventVolumeFlux() const + { throw std::runtime_error("solventVolumeFlux() called but solvents are disabled"); } + + void setSolventVolumeFlux(const Evaluation& /* solventVolumeFlux */) + { throw std::runtime_error("setSolventVolumeFlux() called but solvents are disabled"); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoilsolventparams.hh b/opm/models/blackoil/blackoilsolventparams.hh new file mode 100644 index 00000000000..c96c91d3837 --- /dev/null +++ b/opm/models/blackoil/blackoilsolventparams.hh @@ -0,0 +1,107 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the parameters required to extend the black-oil model by solvents. + */ +#ifndef EWOMS_BLACK_OIL_SOLVENT_PARAMS_HH +#define EWOMS_BLACK_OIL_SOLVENT_PARAMS_HH + +#include +#include +#include +#include +#include + +#include + +namespace Opm { + +//! \brief Struct holding the parameters for the BlackOilSolventModule class. +template +struct BlackOilSolventParams { + using TabulatedFunction = Tabulated1DFunction; + + using SolventPvt = ::Opm::SolventPvt; + SolventPvt solventPvt_; + + using Co2GasPvt = ::Opm::Co2GasPvt; + Co2GasPvt co2GasPvt_; + + using H2GasPvt = ::Opm::H2GasPvt; + H2GasPvt h2GasPvt_; + + using BrineCo2Pvt = ::Opm::BrineCo2Pvt; + BrineCo2Pvt brineCo2Pvt_; + + using BrineH2Pvt = ::Opm::BrineH2Pvt; + BrineH2Pvt brineH2Pvt_; + + std::vector ssfnKrg_; // the krg(Fs) column of the SSFN table + std::vector ssfnKrs_; // the krs(Fs) column of the SSFN table + std::vector sof2Krn_; // the krn(Sn) column of the SOF2 table + std::vector misc_; // the misc(Ss) column of the MISC table + std::vector pmisc_; // the pmisc(pg) column of the PMISC table + std::vector msfnKrsg_; // the krsg(Ssg) column of the MSFN table + std::vector msfnKro_; // the kro(Ssg) column of the MSFN table + std::vector sorwmis_; // the sorwmis(Sw) column of the SORWMIS table + std::vector sgcwmis_; // the sgcwmis(Sw) column of the SGCWMIS table + + std::vector tlMixParamViscosity_; // Todd-Longstaff mixing parameter for viscosity + std::vector tlMixParamDensity_; // Todd-Longstaff mixing parameter for density + std::vector tlPMixTable_; // the tlpmixpa(Po) column of the TLPMIXPA table + + bool isMiscible_; + bool rsSolw_active_ = false; + bool co2sol_; + bool h2sol_; + + /*! + * \brief Specify the number of satuation regions. + * + * This must be called before setting the SSFN of any region. + */ + void setNumSatRegions(unsigned numRegions) + { + ssfnKrg_.resize(numRegions); + ssfnKrs_.resize(numRegions); + } + + /*! + * \brief Specify miscible relative permeability multipliers of a single region. + * + * The index of specified here must be in range [0, numSatRegions) + */ + void setMsfn(unsigned satRegionIdx, + const TabulatedFunction& msfnKrsg, + const TabulatedFunction& msfnKro) + { + msfnKrsg_[satRegionIdx] = msfnKrsg; + msfnKro_[satRegionIdx] = msfnKro; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/blackoil/blackoiltwophaseindices.hh b/opm/models/blackoil/blackoiltwophaseindices.hh new file mode 100644 index 00000000000..6a05ad42761 --- /dev/null +++ b/opm/models/blackoil/blackoiltwophaseindices.hh @@ -0,0 +1,275 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BlackOilTwoPhaseIndices + */ +#ifndef EWOMS_BLACK_OIL_TWO_PHASE_INDICES_HH +#define EWOMS_BLACK_OIL_TWO_PHASE_INDICES_HH + +#include + +namespace Opm { + +/*! + * \ingroup BlackOilModel + * + * \brief The primary variable and equation indices for the black-oil model. + */ +template +struct BlackOilTwoPhaseIndices +{ + //! Is phase enabled or not + static constexpr bool oilEnabled = disabledCanonicalCompIdx != 0; + static constexpr bool waterEnabled = disabledCanonicalCompIdx != 1; + static constexpr bool gasEnabled = disabledCanonicalCompIdx != 2; + + //! Are solvents involved? + static constexpr bool enableSolvent = numSolventsV > 0; + + //! Is extbo invoked? + static constexpr bool enableExtbo = numExtbosV > 0; + + //! Are polymers involved? + static constexpr bool enablePolymer = numPolymersV > 0; + + //! Shall energy be conserved? + static constexpr bool enableEnergy = numEnergyV > 0; + + //! Is MICP involved? + static constexpr bool enableMICP = numMICPsV > 0; + + //! Number of solvent components to be considered + static constexpr int numSolvents = enableSolvent ? numSolventsV : 0; + + //! Number of components to be considered for extbo + static constexpr int numExtbos = enableExtbo ? numExtbosV : 0; + + //! Number of polymer components to be considered + static constexpr int numPolymers = enablePolymer ? numPolymersV : 0; + + //! Number of energy equations to be considered + static constexpr int numEnergy = enableEnergy ? numEnergyV : 0; + + //! Number of foam equations to be considered + static constexpr int numFoam = enableFoam? 1 : 0; + + //! Number of salt equations to be considered + static constexpr int numBrine = enableBrine? 1 : 0; + + //! The number of fluid phases + static constexpr int numPhases = 2; + + //! Number of MICP components to be considered + static constexpr int numMICPs = enableMICP ? numMICPsV : 0; + + //! The number of equations + static constexpr int numEq = numPhases + numSolvents + numExtbos + numPolymers + + numEnergy + numFoam + numBrine + numMICPs; + + ////////////////////////////// + // Primary variable indices + ////////////////////////////// + + /*! + * \brief Index of the switching variable which determines the composistion of the water phase + * + * Depending on the phases present, this variable is either interpreted as + * water saturation or vapporized water in gas phase + * + * \note For two-phase gas-oil models this is disabled. + */ + static constexpr int waterSwitchIdx = waterEnabled ? PVOffset + 0 : -10000; + + /*! + * \brief Index of the switching variable which determines the pressure + * + * Depending on the phases present, this variable is either interpreted as the + * pressure of the oil phase, gas phase (if no oil) or water phase (if only water) + */ + static constexpr int pressureSwitchIdx = waterEnabled ? PVOffset + 1 : PVOffset + 0; + + /*! + * \brief Index of the switching variable which determines the composition of the + * hydrocarbon phases. + * + * \note For two-phase water oil and water gas models this is disabled. + */ + static constexpr int compositionSwitchIdx = (gasEnabled && oilEnabled) ? PVOffset + 1 : -10000; + + //! Index of the primary variable for the first solvent + static constexpr int solventSaturationIdx = + enableSolvent ? PVOffset + numPhases : -1000; + + //! Index of the primary variable for the first extbo component + static constexpr int zFractionIdx = + enableExtbo ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the primary variable for the first polymer + static constexpr int polymerConcentrationIdx = + enablePolymer ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the primary variable for the second polymer primary variable (molecular weight) + static constexpr int polymerMoleWeightIdx = + numPolymers > 1 ? polymerConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the first MICP component + static constexpr int microbialConcentrationIdx = + enableMICP ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the primary variable for the second MICP component + static constexpr int oxygenConcentrationIdx = + numMICPs > 1 ? microbialConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the third MICP component + static constexpr int ureaConcentrationIdx = + numMICPs > 2 ? oxygenConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the fourth MICP component + static constexpr int biofilmConcentrationIdx = + numMICPs > 3 ? ureaConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the fifth MICP component + static constexpr int calciteConcentrationIdx = + numMICPs > 4 ? biofilmConcentrationIdx + 1 : -1000; + + //! Index of the primary variable for the foam + static constexpr int foamConcentrationIdx = + enableFoam ? PVOffset + numPhases + numSolvents + numPolymers + numMICPs : -1000; + + //! Index of the primary variable for the salt + static constexpr int saltConcentrationIdx = + enableBrine ? PVOffset + numPhases + numSolvents + numPolymers + numMICPs + numFoam : -1000; + + //! Index of the primary variable for temperature + static constexpr int temperatureIdx = + enableEnergy ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs + numFoam + numBrine : - 1000; + + ////////////////////// + // Equation indices + ////////////////////// + + //! \brief returns the index of "active" component + static unsigned canonicalToActiveComponentIndex(unsigned compIdx) + { + // assumes canonical oil = 0, water = 1, gas = 2; + if (!gasEnabled) { + assert(compIdx != 2); + // oil = 0, water = 1 + return compIdx; + } else if (!waterEnabled) { + assert(compIdx != 1); + // oil = 0, gas = 1 + return compIdx / 2; + } else { + assert(!oilEnabled); + assert(compIdx != 0); + } + + // water = 0, gas = 1; + return compIdx - 1; + } + + static unsigned activeToCanonicalComponentIndex(unsigned compIdx) + { + // assumes canonical oil = 0, water = 1, gas = 2; + assert(compIdx < 2); + if (!gasEnabled) { + // oil = 0, water = 1 + return compIdx; + } else if (!waterEnabled) { + // oil = 0, gas = 1 + return compIdx * 2; + } else { + assert(!oilEnabled); + } + + // water = 0, gas = 1; + return compIdx + 1; + } + + //! Index of the continuity equation of the first phase + static constexpr int conti0EqIdx = PVOffset + 0; + // one continuity equation follows + + //! Index of the continuity equation for the first solvent component + static constexpr int contiSolventEqIdx = + enableSolvent ? PVOffset + numPhases : -1000; + + //! Index of the continuity equation for the first extbo component + static constexpr int contiZfracEqIdx = + enableExtbo ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the continuity equation for the first polymer component + static constexpr int contiPolymerEqIdx = + enablePolymer ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the continuity equation for the second polymer component (molecular weight) + static constexpr int contiPolymerMWEqIdx = + numPolymers > 1 ? contiPolymerEqIdx + 1 : -1000; + + //! Index of the continuity equation for the first MICP component + static constexpr int contiMicrobialEqIdx = + enableMICP ? PVOffset + numPhases + numSolvents : -1000; + + //! Index of the continuity equation for the second MICP component + static constexpr int contiOxygenEqIdx = + numMICPs > 1 ? contiMicrobialEqIdx + 1 : -1000; + + //! Index of the continuity equation for the third MICP component + static constexpr int contiUreaEqIdx = + numMICPs > 2 ? contiOxygenEqIdx + 1 : -1000; + + //! Index of the continuity equation for the fourth MICP component + static constexpr int contiBiofilmEqIdx = + numMICPs > 3 ? contiUreaEqIdx + 1 : -1000; + + //! Index of the continuity equation for the fifth MICP component + static constexpr int contiCalciteEqIdx = + numMICPs > 4 ? contiBiofilmEqIdx + 1 : -1000; + + //! Index of the continuity equation for the foam component + static constexpr int contiFoamEqIdx = + enableFoam ? PVOffset + numPhases + numSolvents + numPolymers + numMICPs : -1000; + + //! Index of the continuity equation for the salt component + static constexpr int contiBrineEqIdx = + enableBrine ? PVOffset + numPhases + numSolvents + numPolymers + numMICPs + numFoam : -1000; + + //! Index of the continuity equation for energy + static constexpr int contiEnergyEqIdx = + enableEnergy ? PVOffset + numPhases + numSolvents + numExtbos + numPolymers + numMICPs + numFoam + numBrine: -1000; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/common/darcyfluxmodule.hh b/opm/models/common/darcyfluxmodule.hh new file mode 100644 index 00000000000..aef4789a848 --- /dev/null +++ b/opm/models/common/darcyfluxmodule.hh @@ -0,0 +1,560 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file contains the necessary classes to calculate the + * volumetric fluxes out of a pressure potential gradient using the + * Darcy relation. + */ +#ifndef EWOMS_DARCY_FLUX_MODULE_HH +#define EWOMS_DARCY_FLUX_MODULE_HH + +#include +#include + +#include + +#include + +#include +#include +#include + +namespace Opm { + +template +class DarcyIntensiveQuantities; + +template +class DarcyExtensiveQuantities; + +template +class DarcyBaseProblem; + +/*! + * \ingroup FluxModules + * \brief Specifies a flux module which uses the Darcy relation. + */ +template +struct DarcyFluxModule +{ + using FluxIntensiveQuantities = DarcyIntensiveQuantities; + using FluxExtensiveQuantities = DarcyExtensiveQuantities; + using FluxBaseProblem = DarcyBaseProblem; + + /*! + * \brief Register all run-time parameters for the flux module. + */ + static void registerParameters() + { } +}; + +/*! + * \ingroup FluxModules + * \brief Provides the defaults for the parameters required by the + * Darcy velocity approach. + */ +template +class DarcyBaseProblem +{ }; + +/*! + * \ingroup FluxModules + * \brief Provides the intensive quantities for the Darcy flux module + */ +template +class DarcyIntensiveQuantities +{ + using ElementContext = GetPropType; +protected: + void update_(const ElementContext&, + unsigned, + unsigned) + { } +}; + +/*! + * \ingroup FluxModules + * \brief Provides the Darcy flux module + * + * The commonly used Darcy relation looses its validity for Reynolds numbers \f$ Re < + * 1\f$. If one encounters flow velocities in porous media above this threshold, the + * Forchheimer approach can be used. + * + * The Darcy equation is given by the following relation: + * + * \f[ + \vec{v}_\alpha = + \left( \nabla p_\alpha - \rho_\alpha \vec{g}\right) + \frac{\mu_\alpha}{k_{r,\alpha} K} + \f] + */ +template +class DarcyExtensiveQuantities +{ + using ElementContext = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using GridView = GetPropType; + using Implementation = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = getPropValue() }; + + using Toolbox = MathToolbox; + using ParameterCache = typename FluidSystem::template ParameterCache; + using EvalDimVector = Dune::FieldVector; + using DimVector = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \brief Returns the intrinsic permeability tensor for a given + * sub-control volume face. + */ + const DimMatrix& intrinsicPermability() const + { return K_; } + + /*! + * \brief Return the pressure potential gradient of a fluid phase + * at the face's integration point [Pa/m] + * + * \param phaseIdx The index of the fluid phase + */ + const EvalDimVector& potentialGrad(unsigned phaseIdx) const + { return potentialGrad_[phaseIdx]; } + + /*! + * \brief Return the filter velocity of a fluid phase at the + * face's integration point [m/s] + * + * \param phaseIdx The index of the fluid phase + */ + const EvalDimVector& filterVelocity(unsigned phaseIdx) const + { return filterVelocity_[phaseIdx]; } + + /*! + * \brief Return the volume flux of a fluid phase at the face's integration point + * \f$[m^3/s / m^2]\f$ + * + * This is the fluid volume of a phase per second and per square meter of face + * area. + * + * \param phaseIdx The index of the fluid phase + */ + const Evaluation& volumeFlux(unsigned phaseIdx) const + { return volumeFlux_[phaseIdx]; } + +protected: + short upstreamIndex_(unsigned phaseIdx) const + { return upstreamDofIdx_[phaseIdx]; } + + short downstreamIndex_(unsigned phaseIdx) const + { return downstreamDofIdx_[phaseIdx]; } + + /*! + * \brief Calculate the gradients which are required to determine the volumetric fluxes + * + * The the upwind directions is also determined by method. + */ + void calculateGradients_(const ElementContext& elemCtx, + unsigned faceIdx, + unsigned timeIdx) + { + const auto& gradCalc = elemCtx.gradientCalculator(); + PressureCallback pressureCallback(elemCtx); + + const auto& scvf = elemCtx.stencil(timeIdx).interiorFace(faceIdx); + const auto& faceNormal = scvf.normal(); + + unsigned i = scvf.interiorIndex(); + unsigned j = scvf.exteriorIndex(); + interiorDofIdx_ = static_cast(i); + exteriorDofIdx_ = static_cast(j); + unsigned focusDofIdx = elemCtx.focusDofIndex(); + + // calculate the "raw" pressure gradient + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) { + Valgrind::SetUndefined(potentialGrad_[phaseIdx]); + continue; + } + + pressureCallback.setPhaseIndex(phaseIdx); + gradCalc.calculateGradient(potentialGrad_[phaseIdx], + elemCtx, + faceIdx, + pressureCallback); + Valgrind::CheckDefined(potentialGrad_[phaseIdx]); + } + + // correct the pressure gradients by the gravitational acceleration + if (Parameters::Get()) { + // estimate the gravitational acceleration at a given SCV face + // using the arithmetic mean + const auto& gIn = elemCtx.problem().gravity(elemCtx, i, timeIdx); + const auto& gEx = elemCtx.problem().gravity(elemCtx, j, timeIdx); + + const auto& intQuantsIn = elemCtx.intensiveQuantities(i, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(j, timeIdx); + + const auto& posIn = elemCtx.pos(i, timeIdx); + const auto& posEx = elemCtx.pos(j, timeIdx); + const auto& posFace = scvf.integrationPos(); + + // the distance between the centers of the control volumes + DimVector distVecIn(posIn); + DimVector distVecEx(posEx); + DimVector distVecTotal(posEx); + + distVecIn -= posFace; + distVecEx -= posFace; + distVecTotal -= posIn; + Scalar absDistTotalSquared = distVecTotal.two_norm2(); + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + // calculate the hydrostatic pressure at the integration point of the face + Evaluation pStatIn; + + if (std::is_same::value || + interiorDofIdx_ == static_cast(focusDofIdx)) + { + const Evaluation& rhoIn = intQuantsIn.fluidState().density(phaseIdx); + pStatIn = - rhoIn*(gIn*distVecIn); + } + else { + Scalar rhoIn = Toolbox::value(intQuantsIn.fluidState().density(phaseIdx)); + pStatIn = - rhoIn*(gIn*distVecIn); + } + + // the quantities on the exterior side of the face do not influence the + // result for the TPFA scheme, so they can be treated as scalar values. + Evaluation pStatEx; + + if (std::is_same::value || + exteriorDofIdx_ == static_cast(focusDofIdx)) + { + const Evaluation& rhoEx = intQuantsEx.fluidState().density(phaseIdx); + pStatEx = - rhoEx*(gEx*distVecEx); + } + else { + Scalar rhoEx = Toolbox::value(intQuantsEx.fluidState().density(phaseIdx)); + pStatEx = - rhoEx*(gEx*distVecEx); + } + + // compute the hydrostatic gradient between the two control volumes (this + // gradient exhibitis the same direction as the vector between the two + // control volume centers and the length (pStaticExterior - + // pStaticInterior)/distanceInteriorToExterior + Dune::FieldVector f(distVecTotal); + f *= (pStatEx - pStatIn)/absDistTotalSquared; + + // calculate the final potential gradient + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + potentialGrad_[phaseIdx][dimIdx] += f[dimIdx]; + + for (unsigned dimIdx = 0; dimIdx < potentialGrad_[phaseIdx].size(); ++dimIdx) { + if (!isfinite(potentialGrad_[phaseIdx][dimIdx])) { + throw NumericalProblem("Non-finite potential gradient for phase '" + + std::string(FluidSystem::phaseName(phaseIdx))+"'"); + } + } + } + } + + Valgrind::SetUndefined(K_); + elemCtx.problem().intersectionIntrinsicPermeability(K_, elemCtx, faceIdx, timeIdx); + Valgrind::CheckDefined(K_); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) { + Valgrind::SetUndefined(potentialGrad_[phaseIdx]); + continue; + } + + // determine the upstream and downstream DOFs + Evaluation tmp = 0.0; + for (unsigned dimIdx = 0; dimIdx < faceNormal.size(); ++dimIdx) + tmp += potentialGrad_[phaseIdx][dimIdx]*faceNormal[dimIdx]; + + if (tmp > 0) { + upstreamDofIdx_[phaseIdx] = exteriorDofIdx_; + downstreamDofIdx_[phaseIdx] = interiorDofIdx_; + } + else { + upstreamDofIdx_[phaseIdx] = interiorDofIdx_; + downstreamDofIdx_[phaseIdx] = exteriorDofIdx_; + } + + // we only carry the derivatives along if the upstream DOF is the one which + // we currently focus on + const auto& up = elemCtx.intensiveQuantities(upstreamDofIdx_[phaseIdx], timeIdx); + if (upstreamDofIdx_[phaseIdx] == static_cast(focusDofIdx)) + mobility_[phaseIdx] = up.mobility(phaseIdx); + else + mobility_[phaseIdx] = Toolbox::value(up.mobility(phaseIdx)); + } + } + + /*! + * \brief Calculate the gradients at the grid boundary which are required to + * determine the volumetric fluxes + * + * The the upwind directions is also determined by method. + */ + template + void calculateBoundaryGradients_(const ElementContext& elemCtx, + unsigned boundaryFaceIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + const auto& gradCalc = elemCtx.gradientCalculator(); + BoundaryPressureCallback pressureCallback(elemCtx, fluidState); + + // calculate the pressure gradient + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) { + Valgrind::SetUndefined(potentialGrad_[phaseIdx]); + continue; + } + + pressureCallback.setPhaseIndex(phaseIdx); + gradCalc.calculateBoundaryGradient(potentialGrad_[phaseIdx], + elemCtx, + boundaryFaceIdx, + pressureCallback); + Valgrind::CheckDefined(potentialGrad_[phaseIdx]); + } + + const auto& scvf = elemCtx.stencil(timeIdx).boundaryFace(boundaryFaceIdx); + auto i = scvf.interiorIndex(); + interiorDofIdx_ = static_cast(i); + exteriorDofIdx_ = -1; + int focusDofIdx = elemCtx.focusDofIndex(); + + // calculate the intrinsic permeability + const auto& intQuantsIn = elemCtx.intensiveQuantities(i, timeIdx); + K_ = intQuantsIn.intrinsicPermeability(); + + // correct the pressure gradients by the gravitational acceleration + if (Parameters::Get()) { + // estimate the gravitational acceleration at a given SCV face + // using the arithmetic mean + const auto& gIn = elemCtx.problem().gravity(elemCtx, i, timeIdx); + const auto& posIn = elemCtx.pos(i, timeIdx); + const auto& posFace = scvf.integrationPos(); + + // the distance between the face center and the center of the control volume + DimVector distVecIn(posIn); + distVecIn -= posFace; + Scalar absDistSquared = distVecIn.two_norm2(); + Scalar gTimesDist = gIn*distVecIn; + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + // calculate the hydrostatic pressure at the integration point of the face + Evaluation rhoIn = intQuantsIn.fluidState().density(phaseIdx); + Evaluation pStatIn = - gTimesDist*rhoIn; + + Valgrind::CheckDefined(pStatIn); + // compute the hydrostatic gradient between the control volume and face integration + // point. This gradient exhibitis the same direction as the vector between the + // control volume center and face integration point (-distVecIn / absDist) and the + // length of the vector is -pStaticIn / absDist. Note that the two negatives become + // + and the 1 / (absDist * absDist) -> absDistSquared. + EvalDimVector f(distVecIn); + f *= pStatIn / absDistSquared; + + // calculate the final potential gradient + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + potentialGrad_[phaseIdx][dimIdx] += f[dimIdx]; + + Valgrind::CheckDefined(potentialGrad_[phaseIdx]); + for (unsigned dimIdx = 0; dimIdx < potentialGrad_[phaseIdx].size(); ++dimIdx) { + if (!isfinite(potentialGrad_[phaseIdx][dimIdx])) { + throw NumericalProblem("Non-finite potential gradient for phase '" + + std::string(FluidSystem::phaseName(phaseIdx))+"'"); + } + } + } + } + + // determine the upstream and downstream DOFs + const auto& faceNormal = scvf.normal(); + + const auto& matParams = elemCtx.problem().materialLawParams(elemCtx, i, timeIdx); + + Scalar kr[numPhases]; + MaterialLaw::relativePermeabilities(kr, matParams, fluidState); + Valgrind::CheckDefined(kr); + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + Evaluation tmp = 0.0; + for (unsigned dimIdx = 0; dimIdx < faceNormal.size(); ++dimIdx) + tmp += potentialGrad_[phaseIdx][dimIdx]*faceNormal[dimIdx]; + + if (tmp > 0) { + upstreamDofIdx_[phaseIdx] = exteriorDofIdx_; + downstreamDofIdx_[phaseIdx] = interiorDofIdx_; + } + else { + upstreamDofIdx_[phaseIdx] = interiorDofIdx_; + downstreamDofIdx_[phaseIdx] = exteriorDofIdx_; + } + + // take the phase mobility from the DOF in upstream direction + if (upstreamDofIdx_[phaseIdx] < 0) { + if (interiorDofIdx_ == focusDofIdx) + mobility_[phaseIdx] = + kr[phaseIdx] / fluidState.viscosity(phaseIdx); + else + mobility_[phaseIdx] = + Toolbox::value(kr[phaseIdx]) + / Toolbox::value(fluidState.viscosity(phaseIdx)); + } + else if (upstreamDofIdx_[phaseIdx] != focusDofIdx) + mobility_[phaseIdx] = Toolbox::value(intQuantsIn.mobility(phaseIdx)); + else + mobility_[phaseIdx] = intQuantsIn.mobility(phaseIdx); + Valgrind::CheckDefined(mobility_[phaseIdx]); + } + } + + /*! + * \brief Calculate the volumetric fluxes of all phases + * + * The pressure potentials and upwind directions must already be + * determined before calling this method! + */ + void calculateFluxes_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + const auto& scvf = elemCtx.stencil(timeIdx).interiorFace(scvfIdx); + const DimVector& normal = scvf.normal(); + Valgrind::CheckDefined(normal); + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + filterVelocity_[phaseIdx] = 0.0; + volumeFlux_[phaseIdx] = 0.0; + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + asImp_().calculateFilterVelocity_(phaseIdx); + Valgrind::CheckDefined(filterVelocity_[phaseIdx]); + + volumeFlux_[phaseIdx] = 0.0; + for (unsigned i = 0; i < normal.size(); ++i) + volumeFlux_[phaseIdx] += filterVelocity_[phaseIdx][i] * normal[i]; + } + } + + /*! + * \brief Calculate the volumetric fluxes at a boundary face of all fluid phases + * + * The pressure potentials and upwind directions must already be determined before + * calling this method! + */ + void calculateBoundaryFluxes_(const ElementContext& elemCtx, + unsigned boundaryFaceIdx, + unsigned timeIdx) + { + const auto& scvf = elemCtx.stencil(timeIdx).boundaryFace(boundaryFaceIdx); + const DimVector& normal = scvf.normal(); + Valgrind::CheckDefined(normal); + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) { + filterVelocity_[phaseIdx] = 0.0; + volumeFlux_[phaseIdx] = 0.0; + continue; + } + + asImp_().calculateFilterVelocity_(phaseIdx); + Valgrind::CheckDefined(filterVelocity_[phaseIdx]); + volumeFlux_[phaseIdx] = 0.0; + for (unsigned i = 0; i < normal.size(); ++i) + volumeFlux_[phaseIdx] += filterVelocity_[phaseIdx][i] * normal[i]; + } + } + + void calculateFilterVelocity_(unsigned phaseIdx) + { +#ifndef NDEBUG + assert(isfinite(mobility_[phaseIdx])); + for (unsigned i = 0; i < K_.M(); ++ i) + for (unsigned j = 0; j < K_.N(); ++ j) + assert(std::isfinite(K_[i][j])); +#endif + + K_.mv(potentialGrad_[phaseIdx], filterVelocity_[phaseIdx]); + filterVelocity_[phaseIdx] *= - mobility_[phaseIdx]; + +#ifndef NDEBUG + for (unsigned i = 0; i < filterVelocity_[phaseIdx].size(); ++ i) + assert(isfinite(filterVelocity_[phaseIdx][i])); +#endif + } + +private: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } + +protected: + // intrinsic permeability tensor and its square root + DimMatrix K_; + + // mobilities of all fluid phases [1 / (Pa s)] + Evaluation mobility_[numPhases]; + + // filter velocities of all phases [m/s] + EvalDimVector filterVelocity_[numPhases]; + + // the volumetric flux of all fluid phases over the control + // volume's face [m^3/s / m^2] + Evaluation volumeFlux_[numPhases]; + + // pressure potential gradients of all phases [Pa / m] + EvalDimVector potentialGrad_[numPhases]; + + // upstream, downstream, interior and exterior DOFs + short upstreamDofIdx_[numPhases]; + short downstreamDofIdx_[numPhases]; + short interiorDofIdx_; + short exteriorDofIdx_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/common/diffusionmodule.hh b/opm/models/common/diffusionmodule.hh new file mode 100644 index 00000000000..50a87f159b9 --- /dev/null +++ b/opm/models/common/diffusionmodule.hh @@ -0,0 +1,490 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Classes required for molecular diffusion. + */ +#ifndef OPM_DIFFUSION_MODULE_HH +#define OPM_DIFFUSION_MODULE_HH + +#include + +#include + +#include +#include +#include + +namespace Opm { + +/*! + * \ingroup Diffusion + * \class Opm::DiffusionModule + * \brief Provides the auxiliary methods required for consideration of the + * diffusion equation. + */ +template +class DiffusionModule; + +/*! + * \copydoc Opm::DiffusionModule + */ +template +class DiffusionModule +{ + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using RateVector = GetPropType; + +public: + /*! + * \brief Register all run-time parameters for the diffusion module. + */ + static void registerParameters() + {} + + /*! + * \brief Adds the diffusive mass flux flux to the flux vector over a flux + * integration point. + */ + template + static void addDiffusiveFlux(RateVector&, + const Context&, + unsigned, + unsigned) + {} +}; + +/*! + * \copydoc Opm::DiffusionModule + */ +template +class DiffusionModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using RateVector = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + + using Toolbox = Opm::MathToolbox; + +public: + /*! + * \brief Register all run-time parameters for the diffusion module. + */ + static void registerParameters() + {} + + /*! + * \brief Adds the mass flux due to molecular diffusion to the flux vector over the + * flux integration point. + */ + template + static void addDiffusiveFlux(RateVector& flux, const Context& context, + unsigned spaceIdx, unsigned timeIdx) + { + const auto& extQuants = context.extensiveQuantities(spaceIdx, timeIdx); + + const auto& fluidStateI = context.intensiveQuantities(extQuants.interiorIndex(), timeIdx).fluidState(); + const auto& fluidStateJ = context.intensiveQuantities(extQuants.exteriorIndex(), timeIdx).fluidState(); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // arithmetic mean of the phase's molar density + Evaluation rhoMolar = fluidStateI.molarDensity(phaseIdx); + rhoMolar += Toolbox::value(fluidStateJ.molarDensity(phaseIdx)); + rhoMolar /= 2; + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + // mass flux due to molecular diffusion + flux[conti0EqIdx + compIdx] += + -rhoMolar + * extQuants.moleFractionGradientNormal(phaseIdx, compIdx) + * extQuants.effectiveDiffusionCoefficient(phaseIdx, compIdx); + } + } +}; + +/*! + * \ingroup Diffusion + * \class Opm::DiffusionIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the + * calculation of molecular diffusive fluxes. + */ +template +class DiffusionIntensiveQuantities; + +/*! + * \copydoc Opm::DiffusionIntensiveQuantities + */ +template +class DiffusionIntensiveQuantities +{ + using Scalar = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + +public: + /*! + * \brief Returns the tortuousity of the sub-domain of a fluid + * phase in the porous medium. + */ + Scalar tortuosity(unsigned) const + { + throw std::logic_error("Method tortuosity() does not make sense " + "if diffusion is disabled"); + } + + /*! + * \brief Returns the molecular diffusion coefficient for a + * component in a phase. + */ + Scalar diffusionCoefficient(unsigned, unsigned) const + { + throw std::logic_error("Method diffusionCoefficient() does not " + "make sense if diffusion is disabled"); + } + + /*! + * \brief Returns the effective molecular diffusion coefficient of + * the porous medium for a component in a phase. + */ + Scalar effectiveDiffusionCoefficient(unsigned, unsigned) const + { + throw std::logic_error("Method effectiveDiffusionCoefficient() " + "does not make sense if diffusion is disabled"); + } + +protected: + /*! + * \brief Update the quantities required to calculate diffusive + * mass fluxes. + */ + template + void update_(FluidState&, + typename FluidSystem::template ParameterCache&, + const ElementContext&, + unsigned, + unsigned) + { } +}; + +/*! + * \copydoc Opm::DiffusionIntensiveQuantities + */ +template +class DiffusionIntensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + +public: + /*! + * \brief Returns the molecular diffusion coefficient for a + * component in a phase. + */ + Evaluation diffusionCoefficient(unsigned phaseIdx, unsigned compIdx) const + { return diffusionCoefficient_[phaseIdx][compIdx]; } + + /*! + * \brief Returns the tortuousity of the sub-domain of a fluid + * phase in the porous medium. + */ + Evaluation tortuosity(unsigned phaseIdx) const + { return tortuosity_[phaseIdx]; } + + /*! + * \brief Returns the effective molecular diffusion coefficient of + * the porous medium for a component in a phase. + */ + Evaluation effectiveDiffusionCoefficient(unsigned phaseIdx, unsigned compIdx) const + { return tortuosity_[phaseIdx] * diffusionCoefficient_[phaseIdx][compIdx]; } + +protected: + /*! + * \brief Update the quantities required to calculate diffusive + * mass fluxes. + */ + template + void update_(FluidState& fluidState, + typename FluidSystem::template ParameterCache& paramCache, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + using Toolbox = Opm::MathToolbox; + + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + // TODO: let the problem do this (this is a constitutive + // relation of which the model should be free of from the + // abstraction POV!) + // Based on Millington, R. J., & Quirk, J. P. (1961). + const Evaluation& base = + Toolbox::max(0.0001, + intQuants.porosity() + * intQuants.fluidState().saturation(phaseIdx)); + tortuosity_[phaseIdx] = + 1.0 / (intQuants.porosity() * intQuants.porosity()) + * Toolbox::pow(base, 10.0/3.0); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + diffusionCoefficient_[phaseIdx][compIdx] = + FluidSystem::diffusionCoefficient(fluidState, + paramCache, + phaseIdx, + compIdx); + } + } + } + +private: + Evaluation tortuosity_[numPhases]; + Evaluation diffusionCoefficient_[numPhases][numComponents]; +}; + +/*! + * \ingroup Diffusion + * \class Opm::DiffusionExtensiveQuantities + * + * \brief Provides the quantities required to calculate diffusive mass fluxes. + */ +template +class DiffusionExtensiveQuantities; + +/*! + * \copydoc Opm::DiffusionExtensiveQuantities + */ +template +class DiffusionExtensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + +protected: + /*! + * \brief Update the quantities required to calculate + * the diffusive mass fluxes. + */ + void update_(const ElementContext&, + unsigned, + unsigned) + {} + + template + void updateBoundary_(const Context&, + unsigned, + unsigned, + const FluidState&) + {} + +public: + /*! + * \brief The the gradient of the mole fraction times the face normal. + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Evaluation& moleFractionGradientNormal(unsigned, + unsigned) const + { + throw std::logic_error("The method moleFractionGradient() does not " + "make sense if diffusion is disabled."); + } + + /*! + * \brief The effective diffusion coeffcient of a component in a + * fluid phase at the face's integration point + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Evaluation& effectiveDiffusionCoefficient(unsigned, + unsigned) const + { + throw std::logic_error("The method effectiveDiffusionCoefficient() " + "does not make sense if diffusion is disabled."); + } +}; + +/*! + * \copydoc Opm::DiffusionExtensiveQuantities + */ +template +class DiffusionExtensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using GridView = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + using DimVector = Dune::FieldVector; + using DimEvalVector = Dune::FieldVector; + +protected: + /*! + * \brief Update the quantities required to calculate + * the diffusive mass fluxes. + */ + void update_(const ElementContext& elemCtx, unsigned faceIdx, unsigned timeIdx) + { + const auto& gradCalc = elemCtx.gradientCalculator(); + Opm::MoleFractionCallback moleFractionCallback(elemCtx); + + const auto& face = elemCtx.stencil(timeIdx).interiorFace(faceIdx); + const auto& normal = face.normal(); + const auto& extQuants = elemCtx.extensiveQuantities(faceIdx, timeIdx); + + const auto& intQuantsInside = elemCtx.intensiveQuantities(extQuants.interiorIndex(), timeIdx); + const auto& intQuantsOutside = elemCtx.intensiveQuantities(extQuants.exteriorIndex(), timeIdx); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + moleFractionCallback.setPhaseIndex(phaseIdx); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + moleFractionCallback.setComponentIndex(compIdx); + + DimEvalVector moleFractionGradient(0.0); + gradCalc.calculateGradient(moleFractionGradient, + elemCtx, + faceIdx, + moleFractionCallback); + + moleFractionGradientNormal_[phaseIdx][compIdx] = 0.0; + for (unsigned i = 0; i < normal.size(); ++i) + moleFractionGradientNormal_[phaseIdx][compIdx] += + normal[i]*moleFractionGradient[i]; + Opm::Valgrind::CheckDefined(moleFractionGradientNormal_[phaseIdx][compIdx]); + + // use the arithmetic average for the effective + // diffusion coefficients. + effectiveDiffusionCoefficient_[phaseIdx][compIdx] = + (intQuantsInside.effectiveDiffusionCoefficient(phaseIdx, compIdx) + + + intQuantsOutside.effectiveDiffusionCoefficient(phaseIdx, compIdx)) + / 2; + + Opm::Valgrind::CheckDefined(effectiveDiffusionCoefficient_[phaseIdx][compIdx]); + } + } + } + + template + void updateBoundary_(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + const auto& stencil = context.stencil(timeIdx); + const auto& face = stencil.boundaryFace(bfIdx); + + const auto& elemCtx = context.elementContext(); + unsigned insideScvIdx = face.interiorIndex(); + const auto& insideScv = stencil.subControlVolume(insideScvIdx); + + const auto& intQuantsInside = elemCtx.intensiveQuantities(insideScvIdx, timeIdx); + const auto& fluidStateInside = intQuantsInside.fluidState(); + + // distance between the center of the SCV and center of the boundary face + DimVector distVec = face.integrationPos(); + distVec -= context.element().geometry().global(insideScv.localGeometry().center()); + + Scalar dist = distVec * face.normal(); + + // if the following assertation triggers, the center of the + // center of the interior SCV was not inside the element! + assert(dist > 0); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + // calculate mole fraction gradient using two-point + // gradients + moleFractionGradientNormal_[phaseIdx][compIdx] = + (fluidState.moleFraction(phaseIdx, compIdx) + - + fluidStateInside.moleFraction(phaseIdx, compIdx)) + / dist; + Opm::Valgrind::CheckDefined(moleFractionGradientNormal_[phaseIdx][compIdx]); + + // use effective diffusion coefficients of the interior finite + // volume. + effectiveDiffusionCoefficient_[phaseIdx][compIdx] = + intQuantsInside.effectiveDiffusionCoefficient(phaseIdx, compIdx); + + Opm::Valgrind::CheckDefined(effectiveDiffusionCoefficient_[phaseIdx][compIdx]); + } + } + } + +public: + /*! + * \brief The the gradient of the mole fraction times the face normal. + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Evaluation& moleFractionGradientNormal(unsigned phaseIdx, unsigned compIdx) const + { return moleFractionGradientNormal_[phaseIdx][compIdx]; } + + /*! + * \brief The effective diffusion coeffcient of a component in a + * fluid phase at the face's integration point + * + * \copydoc Doxygen::phaseIdxParam + * \copydoc Doxygen::compIdxParam + */ + const Evaluation& effectiveDiffusionCoefficient(unsigned phaseIdx, unsigned compIdx) const + { return effectiveDiffusionCoefficient_[phaseIdx][compIdx]; } + +private: + Evaluation moleFractionGradientNormal_[numPhases][numComponents]; + Evaluation effectiveDiffusionCoefficient_[numPhases][numComponents]; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/common/directionalmobility.hh b/opm/models/common/directionalmobility.hh new file mode 100644 index 00000000000..a6a0375a87f --- /dev/null +++ b/opm/models/common/directionalmobility.hh @@ -0,0 +1,68 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2022 Equinor ASA. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +/*! + * \file + * + * \brief This file contains definitions related to directional mobilities + */ +#ifndef OPM_MODELS_DIRECTIONAL_MOBILITY_HH +#define OPM_MODELS_DIRECTIONAL_MOBILITY_HH + +#include +#include +#include + +#include +#include + +namespace Opm { +template +struct DirectionalMobility { + enum { numPhases = getPropValue() }; + // TODO: This (line below) did not work. I get error: ā€˜Evaluationā€™ is not a member of ā€˜Opm::Propertiesā€™ + // when compiling the tracer model (eclgenerictracermodel.cc). + // QuickFix: I am adding Evaluation as a class template parameter.. + //using Evaluation = GetPropType; + + using array_type = std::array; + DirectionalMobility(const DirectionalMobility& other) + : mobilityX_{other.mobilityX_}, mobilityY_{other.mobilityY_}, mobilityZ_{other.mobilityZ_} {} + DirectionalMobility(const array_type& mX, const array_type& mY, const array_type& mZ) + : mobilityX_{mX}, mobilityY_{mY}, mobilityZ_{mZ} {} + DirectionalMobility() : mobilityX_{}, mobilityY_{}, mobilityZ_{} {} + array_type& getArray(int index) { + switch(index) { + case 0: + return mobilityX_; + case 1: + return mobilityY_; + case 2: + return mobilityZ_; + default: + throw std::runtime_error("Unexpected mobility array index"); + } + } + array_type mobilityX_; + array_type mobilityY_; + array_type mobilityZ_; +}; +} // namespace Opm +#endif diff --git a/opm/models/common/energymodule.hh b/opm/models/common/energymodule.hh new file mode 100644 index 00000000000..019a45e4fc2 --- /dev/null +++ b/opm/models/common/energymodule.hh @@ -0,0 +1,848 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Contains the classes required to consider energy as a + * conservation quantity in a multi-phase module. + */ +#ifndef EWOMS_ENERGY_MODULE_HH +#define EWOMS_ENERGY_MODULE_HH + +#include + +#include + +#include +#include + +#include + +#include + +namespace Opm { +/*! + * \ingroup Energy + * \brief Provides the auxiliary methods required for consideration of + * the energy equation. + */ +template +class EnergyModule; + +/*! + * \copydoc Opm::EnergyModule + */ +template +class EnergyModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using RateVector = GetPropType; + using PrimaryVariables = GetPropType; + using ExtensiveQuantities = GetPropType; + using IntensiveQuantities = GetPropType; + using Model = GetPropType; + + enum { numEq = getPropValue() }; + + using EvalEqVector = Dune::FieldVector; + +public: + /*! + * \brief Register all run-time parameters for the energy module. + */ + static void registerParameters() + {} + + /*! + * \brief Returns the name of a primary variable or an empty + * string if the specified primary variable index does not belong to + * the energy module. + */ + static std::string primaryVarName(unsigned) + { return ""; } + + /*! + * \brief Returns the name of an equation or an empty + * string if the specified equation index does not belong to + * the energy module. + */ + static std::string eqName(unsigned) + { return ""; } + + /*! + * \brief Returns the relative weight of a primary variable for + * calculating relative errors. + */ + static Scalar primaryVarWeight(const Model&, + unsigned, + unsigned) + { return -1; } + + /*! + * \brief Returns the relative weight of a equation of the residual. + */ + static Scalar eqWeight(const Model&, + unsigned, + unsigned) + { return -1; } + + /*! + * \brief Given a fluid state, set the temperature in the primary variables + */ + template + static void setPriVarTemperatures(PrimaryVariables&, + const FluidState&) + {} + + /*! + * \brief Given a fluid state, set the enthalpy rate which emerges + * from a volumetric rate. + */ + template + static void setEnthalpyRate(RateVector&, + const FluidState&, + unsigned, + const Evaluation&) + {} + + /*! + * \brief Add the rate of the enthalpy flux to a rate vector. + */ + static void setEnthalpyRate(RateVector&, + const Evaluation&) + {} + + /*! + * \brief Add the rate of the enthalpy flux to a rate vector. + */ + static void addToEnthalpyRate(RateVector&, + const Evaluation&) + {} + + /*! + * \brief Add the rate of the conductive energy flux to a rate vector. + */ + static Scalar thermalConductionRate(const ExtensiveQuantities&) + { return 0.0; } + + /*! + * \brief Add the energy storage term for a fluid phase to an equation + * vector + */ + template + static void addPhaseStorage(Dune::FieldVector&, + const IntensiveQuantities&, + unsigned) + {} + + /*! + * \brief Add the energy storage term for a fluid phase to an equation + * vector + */ + template + static void addFracturePhaseStorage(Dune::FieldVector&, + const IntensiveQuantities&, + const Scv&, + unsigned) + {} + + /*! + * \brief Add the energy storage term for the fracture part a fluid phase to an + * equation vector + */ + template + static void addSolidEnergyStorage(Dune::FieldVector&, + const IntensiveQuantities&) + {} + + /*! + * \brief Evaluates the advective energy fluxes over a face of a + * subcontrol volume and adds the result in the flux vector. + * + * This method is called by compute flux (base class) + */ + template + static void addAdvectiveFlux(RateVector&, + const Context&, + unsigned, + unsigned) + {} + + /*! + * \brief Evaluates the advective energy fluxes over a fracture + * which should be attributed to a face of a subcontrol + * volume and adds the result in the flux vector. + */ + template + static void handleFractureFlux(RateVector&, + const Context&, + unsigned, + unsigned) + {} + + /*! + * \brief Adds the diffusive energy flux to the flux vector over the face of a + * sub-control volume. + * + * This method is called by compute flux (base class) + */ + template + static void addDiffusiveFlux(RateVector&, + const Context&, + unsigned, + unsigned) + {} +}; + +/*! + * \copydoc Opm::EnergyModule + */ +template +class EnergyModule +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using Indices = GetPropType; + using Model = GetPropType; + + enum { numEq = getPropValue() }; + enum { numPhases = FluidSystem::numPhases }; + enum { energyEqIdx = Indices::energyEqIdx }; + enum { temperatureIdx = Indices::temperatureIdx }; + + using EvalEqVector = Dune::FieldVector; + using Toolbox = Opm::MathToolbox; + +public: + /*! + * \brief Register all run-time parameters for the energy module. + */ + static void registerParameters() + {} + + /*! + * \brief Returns the name of a primary variable or an empty + * string if the specified primary variable index does not belong to + * the energy module. + */ + static std::string primaryVarName(unsigned pvIdx) + { + if (pvIdx == temperatureIdx) + return "temperature"; + return ""; + } + + /*! + * \brief Returns the name of an equation or an empty + * string if the specified equation index does not belong to + * the energy module. + */ + static std::string eqName(unsigned eqIdx) + { + if (eqIdx == energyEqIdx) + return "energy"; + return ""; + } + + /*! + * \brief Returns the relative weight of a primary variable for + * calculating relative errors. + */ + static Scalar primaryVarWeight(const Model& model, unsigned globalDofIdx, unsigned pvIdx) + { + if (pvIdx != temperatureIdx) + return -1; + + // make the weight of the temperature primary variable inversly proportional to its value + return std::max(1.0/1000, 1.0/model.solution(/*timeIdx=*/0)[globalDofIdx][temperatureIdx]); + } + + /*! + * \brief Returns the relative weight of a equation. + */ + static Scalar eqWeight(const Model&, + unsigned, + unsigned eqIdx) + { + if (eqIdx != energyEqIdx) + return -1; + + // approximate change of internal energy of 1kg of liquid water for a temperature + // change of 30K + return 1.0 / (4.184e3 * 30.0); + } + + /*! + * \brief Set the rate of energy flux of a rate vector. + */ + static void setEnthalpyRate(RateVector& rateVec, const Evaluation& rate) + { rateVec[energyEqIdx] = rate; } + + /*! + * \brief Add the rate of the enthalpy flux to a rate vector. + */ + static void addToEnthalpyRate(RateVector& rateVec, const Evaluation& rate) + { rateVec[energyEqIdx] += rate; } + + /*! + * \brief Returns the conductive energy flux for a given flux integration point. + */ + static Evaluation thermalConductionRate(const ExtensiveQuantities& extQuants) + { return -extQuants.temperatureGradNormal() * extQuants.thermalConductivity(); } + + /*! + * \brief Given a fluid state, set the enthalpy rate which emerges + * from a volumetric rate. + */ + template + static void setEnthalpyRate(RateVector& rateVec, + const FluidState& fluidState, + unsigned phaseIdx, + const Evaluation& volume) + { + rateVec[energyEqIdx] = + volume + * fluidState.density(phaseIdx) + * fluidState.enthalpy(phaseIdx); + } + + /*! + * \brief Given a fluid state, set the temperature in the primary variables + */ + template + static void setPriVarTemperatures(PrimaryVariables& priVars, + const FluidState& fs) + { + priVars[temperatureIdx] = Toolbox::value(fs.temperature(/*phaseIdx=*/0)); +#ifndef NDEBUG + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + assert(std::abs(Toolbox::value(fs.temperature(/*phaseIdx=*/0)) + - Toolbox::value(fs.temperature(phaseIdx))) < 1e-30); + } +#endif + } + + /*! + * \brief Add the energy storage term for a fluid phase to an equation + * vector + */ + template + static void addPhaseStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants, + unsigned phaseIdx) + { + const auto& fs = intQuants.fluidState(); + storage[energyEqIdx] += + Toolbox::template decay(fs.density(phaseIdx)) + * Toolbox::template decay(fs.internalEnergy(phaseIdx)) + * Toolbox::template decay(fs.saturation(phaseIdx)) + * Toolbox::template decay(intQuants.porosity()); + } + + /*! + * \brief Add the energy storage term for a fluid phase to an equation + * vector + */ + template + static void addFracturePhaseStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants, + const Scv& scv, + unsigned phaseIdx) + { + const auto& fs = intQuants.fractureFluidState(); + storage[energyEqIdx] += + Toolbox::template decay(fs.density(phaseIdx)) + * Toolbox::template decay(fs.internalEnergy(phaseIdx)) + * Toolbox::template decay(fs.saturation(phaseIdx)) + * Toolbox::template decay(intQuants.fracturePorosity()) + * Toolbox::template decay(intQuants.fractureVolume())/scv.volume(); + } + + /*! + * \brief Add the energy storage term for a fluid phase to an equation + * vector + */ + template + static void addSolidEnergyStorage(Dune::FieldVector& storage, + const IntensiveQuantities& intQuants) + { storage[energyEqIdx] += Opm::decay(intQuants.solidInternalEnergy()); } + + /*! + * \brief Evaluates the advective energy fluxes for a flux integration point and adds + * the result in the flux vector. + * + * This method is called by compute flux (base class) + */ + template + static void addAdvectiveFlux(RateVector& flux, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) + { + const auto& extQuants = context.extensiveQuantities(spaceIdx, timeIdx); + + // advective energy flux in all phases + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!context.model().phaseIsConsidered(phaseIdx)) + continue; + + // intensive quantities of the upstream and the downstream DOFs + unsigned upIdx = static_cast(extQuants.upstreamIndex(phaseIdx)); + const IntensiveQuantities& up = context.intensiveQuantities(upIdx, timeIdx); + + flux[energyEqIdx] += + extQuants.volumeFlux(phaseIdx) + * up.fluidState().enthalpy(phaseIdx) + * up.fluidState().density(phaseIdx); + } + } + + /*! + * \brief Evaluates the advective energy fluxes over a fracture which should be + * attributed to a face of a subcontrol volume and adds the result in the flux + * vector. + */ + template + static void handleFractureFlux(RateVector& flux, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) + { + const auto& scvf = context.stencil(timeIdx).interiorFace(spaceIdx); + const auto& extQuants = context.extensiveQuantities(spaceIdx, timeIdx); + + // reduce the energy flux in the matrix by the half the width occupied by the + // fracture + flux[energyEqIdx] *= + 1 - extQuants.fractureWidth()/(2*scvf.area()); + + // advective energy flux in all phases + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!context.model().phaseIsConsidered(phaseIdx)) + continue; + + // intensive quantities of the upstream and the downstream DOFs + unsigned upIdx = static_cast(extQuants.upstreamIndex(phaseIdx)); + const IntensiveQuantities& up = context.intensiveQuantities(upIdx, timeIdx); + + flux[energyEqIdx] += + extQuants.fractureVolumeFlux(phaseIdx) + * up.fluidState().enthalpy(phaseIdx) + * up.fluidState().density(phaseIdx); + } + } + + /*! + * \brief Adds the diffusive energy flux to the flux vector over the face of a + * sub-control volume. + * + * This method is called by compute flux (base class) + */ + template + static void addDiffusiveFlux(RateVector& flux, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) + { + const auto& extQuants = context.extensiveQuantities(spaceIdx, timeIdx); + + // diffusive energy flux + flux[energyEqIdx] += + - extQuants.temperatureGradNormal() + * extQuants.thermalConductivity(); + } +}; + +/*! + * \ingroup Energy + * \class Opm::EnergyIndices + * + * \brief Provides the indices required for the energy equation. + */ +template +struct EnergyIndices; + +/*! + * \copydoc Opm::EnergyIndices + */ +template +struct EnergyIndices +{ + //! The index of the primary variable representing temperature + enum { temperatureIdx = -1000 }; + + //! The index of the equation representing the conservation of energy + enum { energyEqIdx = -1000 }; + +protected: + enum { numEq_ = 0 }; +}; + +/*! + * \copydoc Opm::EnergyIndices + */ +template +struct EnergyIndices +{ + //! The index of the primary variable representing temperature + enum { temperatureIdx = PVOffset }; + + //! The index of the equation representing the conservation of energy + enum { energyEqIdx = PVOffset }; + +protected: + enum { numEq_ = 1 }; +}; + +/*! + * \ingroup Energy + * \class Opm::EnergyIntensiveQuantities + * + * \brief Provides the volumetric quantities required for the energy equation. + */ +template +class EnergyIntensiveQuantities; + +/*! + * \copydoc Opm::EnergyIntensiveQuantities + */ +template +class EnergyIntensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + + using Toolbox = Opm::MathToolbox; + +public: + /*! + * \brief Returns the volumetric internal energy \f$\mathrm{[J/(m^3]}\f$ of the + * solid matrix in the sub-control volume. + */ + Evaluation solidInternalEnergy() const + { + throw std::logic_error("solidInternalEnergy() does not make sense for isothermal models"); + } + + /*! + * \brief Returns the total thermal conductivity \f$\mathrm{[W/m^2 / (K/m)]}\f$ of + * the solid matrix in the sub-control volume. + */ + Evaluation thermalConductivity() const + { + throw std::logic_error("thermalConductivity() does not make sense for isothermal models"); + } + +protected: + /*! + * \brief Update the temperatures of the fluids of a fluid state. + */ + template + static void updateTemperatures_(FluidState& fluidState, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) + { + Scalar T = context.problem().temperature(context, spaceIdx, timeIdx); + fluidState.setTemperature(Toolbox::createConstant(T)); + } + + /*! + * \brief Update the quantities required to calculate + * energy fluxes. + */ + template + void update_(FluidState&, + typename FluidSystem::template ParameterCache&, + const ElementContext&, + unsigned, + unsigned) + { } +}; + +/*! + * \copydoc Opm::EnergyIntensiveQuantities + */ +template +class EnergyIntensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using ThermalConductionLaw = GetPropType; + using SolidEnergyLaw = GetPropType; + using Indices = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + enum { energyEqIdx = Indices::energyEqIdx }; + enum { temperatureIdx = Indices::temperatureIdx }; + + using Toolbox = Opm::MathToolbox; + +protected: + /*! + * \brief Update the temperatures of the fluids of a fluid state. + */ + template + static void updateTemperatures_(FluidState& fluidState, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) + { + const auto& priVars = context.primaryVars(spaceIdx, timeIdx); + Evaluation val; + if (std::is_same::value) // finite differences + val = Toolbox::createConstant(priVars[temperatureIdx]); + else { + // automatic differentiation + if (timeIdx == 0) + val = Toolbox::createVariable(priVars[temperatureIdx], temperatureIdx); + else + val = Toolbox::createConstant(priVars[temperatureIdx]); + } + fluidState.setTemperature(val); + } + + /*! + * \brief Update the quantities required to calculate + * energy fluxes. + */ + template + void update_(FluidState& fs, + typename FluidSystem::template ParameterCache& paramCache, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + // set the specific enthalpies of the fluids + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + fs.setEnthalpy(phaseIdx, + FluidSystem::enthalpy(fs, paramCache, phaseIdx)); + } + + // compute and set the volumetric internal energy of the solid phase + const auto& problem = elemCtx.problem(); + const auto& solidEnergyParams = problem.solidEnergyLawParams(elemCtx, dofIdx, timeIdx); + const auto& thermalCondParams = problem.thermalConductionLawParams(elemCtx, dofIdx, timeIdx); + + solidInternalEnergy_ = SolidEnergyLaw::solidInternalEnergy(solidEnergyParams, fs); + thermalConductivity_ = ThermalConductionLaw::thermalConductivity(thermalCondParams, fs); + + Opm::Valgrind::CheckDefined(solidInternalEnergy_); + Opm::Valgrind::CheckDefined(thermalConductivity_); + } + +public: + /*! + * \brief Returns the volumetric internal energy \f$\mathrm{[J/m^3]}\f$ of the + * solid matrix in the sub-control volume. + */ + const Evaluation& solidInternalEnergy() const + { return solidInternalEnergy_; } + + /*! + * \brief Returns the total conductivity capacity \f$\mathrm{[W/m^2 / (K/m)]}\f$ of + * the solid matrix in the sub-control volume. + */ + const Evaluation& thermalConductivity() const + { return thermalConductivity_; } + +private: + Evaluation solidInternalEnergy_; + Evaluation thermalConductivity_; +}; + +/*! + * \ingroup Energy + * \class Opm::EnergyExtensiveQuantities + * + * \brief Provides the quantities required to calculate energy fluxes. + */ +template +class EnergyExtensiveQuantities; + +/*! + * \copydoc Opm::EnergyExtensiveQuantities + */ +template +class EnergyExtensiveQuantities +{ + using Scalar = GetPropType; + using ElementContext = GetPropType; + +protected: + /*! + * \brief Update the quantities required to calculate + * energy fluxes. + */ + void update_(const ElementContext&, + unsigned, + unsigned) + {} + + template + void updateBoundary_(const Context&, + unsigned, + unsigned, + const FluidState&) + {} + +public: + /*! + * \brief The temperature gradient times the face normal [K m^2 / m] + */ + Scalar temperatureGradNormal() const + { + throw std::logic_error("Calling temperatureGradNormal() does not make sense " + "for isothermal models"); + } + + /*! + * \brief The total thermal conductivity at the face \f$\mathrm{[W/m^2 / (K/m)]}\f$ + */ + Scalar thermalConductivity() const + { + throw std::logic_error("Calling thermalConductivity() does not make sense for " + "isothermal models"); + } +}; + +/*! + * \copydoc Opm::EnergyExtensiveQuantities + */ +template +class EnergyExtensiveQuantities +{ + using ElementContext = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using GridView = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + using EvalDimVector = Dune::FieldVector; + using DimVector = Dune::FieldVector; + +protected: + /*! + * \brief Update the quantities required to calculate + * energy fluxes. + */ + void update_(const ElementContext& elemCtx, unsigned faceIdx, unsigned timeIdx) + { + const auto& gradCalc = elemCtx.gradientCalculator(); + Opm::TemperatureCallback temperatureCallback(elemCtx); + + EvalDimVector temperatureGrad; + gradCalc.calculateGradient(temperatureGrad, + elemCtx, + faceIdx, + temperatureCallback); + + // scalar product of temperature gradient and scvf normal + const auto& face = elemCtx.stencil(/*timeIdx=*/0).interiorFace(faceIdx); + + temperatureGradNormal_ = 0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + temperatureGradNormal_ += (face.normal()[dimIdx]*temperatureGrad[dimIdx]); + + const auto& extQuants = elemCtx.extensiveQuantities(faceIdx, timeIdx); + const auto& intQuantsInside = elemCtx.intensiveQuantities(extQuants.interiorIndex(), timeIdx); + const auto& intQuantsOutside = elemCtx.intensiveQuantities(extQuants.exteriorIndex(), timeIdx); + + // arithmetic mean + thermalConductivity_ = + 0.5 * (intQuantsInside.thermalConductivity() + intQuantsOutside.thermalConductivity()); + Opm::Valgrind::CheckDefined(thermalConductivity_); + } + + template + void updateBoundary_(const Context& context, unsigned bfIdx, unsigned timeIdx, const FluidState& fs) + { + const auto& stencil = context.stencil(timeIdx); + const auto& face = stencil.boundaryFace(bfIdx); + + const auto& elemCtx = context.elementContext(); + unsigned insideScvIdx = face.interiorIndex(); + const auto& insideScv = elemCtx.stencil(timeIdx).subControlVolume(insideScvIdx); + + const auto& intQuantsInside = elemCtx.intensiveQuantities(insideScvIdx, timeIdx); + const auto& fsInside = intQuantsInside.fluidState(); + + // distance between the center of the SCV and center of the boundary face + DimVector distVec = face.integrationPos(); + distVec -= insideScv.geometry().center(); + + Scalar tmp = 0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + tmp += distVec[dimIdx] * face.normal()[dimIdx]; + Scalar dist = tmp; + + // if the following assertation triggers, the center of the + // center of the interior SCV was not inside the element! + assert(dist > 0); + + // calculate the temperature gradient using two-point gradient + // appoximation + temperatureGradNormal_ = + (fs.temperature(/*phaseIdx=*/0) - fsInside.temperature(/*phaseIdx=*/0)) / dist; + + // take the value for thermal conductivity from the interior finite volume + thermalConductivity_ = intQuantsInside.thermalConductivity(); + } + +public: + /*! + * \brief The temperature gradient times the face normal [K m^2 / m] + */ + const Evaluation& temperatureGradNormal() const + { return temperatureGradNormal_; } + + /*! + * \brief The total thermal conductivity at the face \f$\mathrm{[W/m^2 / + * (K/m)]}\f$ + */ + const Evaluation& thermalConductivity() const + { return thermalConductivity_; } + +private: + Evaluation temperatureGradNormal_; + Evaluation thermalConductivity_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/common/flux.hh b/opm/models/common/flux.hh new file mode 100644 index 00000000000..0310d6bc295 --- /dev/null +++ b/opm/models/common/flux.hh @@ -0,0 +1,35 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file contains the necessary classes to calculate the + * velocity out of a pressure potential gradient. + */ +#ifndef EWOMS_VELOCITY_MODULES_HH +#define EWOMS_VELOCITY_MODULES_HH + +#include +#include + +#endif diff --git a/opm/models/common/forchheimerfluxmodule.hh b/opm/models/common/forchheimerfluxmodule.hh new file mode 100644 index 00000000000..04876784a9d --- /dev/null +++ b/opm/models/common/forchheimerfluxmodule.hh @@ -0,0 +1,577 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file contains the necessary classes to calculate the + * volumetric fluxes out of a pressure potential gradient using the + * Forchhheimer approach. + */ +#ifndef EWOMS_FORCHHEIMER_FLUX_MODULE_HH +#define EWOMS_FORCHHEIMER_FLUX_MODULE_HH + +#include "darcyfluxmodule.hh" + +#include + +#include + +#include + +#include +#include + +#include + +namespace Opm { +template +class ForchheimerIntensiveQuantities; + +template +class ForchheimerExtensiveQuantities; + +template +class ForchheimerBaseProblem; + +/*! + * \ingroup FluxModules + * \brief Specifies a flux module which uses the Forchheimer relation. + */ +template +struct ForchheimerFluxModule +{ + using FluxIntensiveQuantities = ForchheimerIntensiveQuantities; + using FluxExtensiveQuantities = ForchheimerExtensiveQuantities; + using FluxBaseProblem = ForchheimerBaseProblem; + + /*! + * \brief Register all run-time parameters for the flux module. + */ + static void registerParameters() + {} +}; + +/*! + * \ingroup FluxModules + * \brief Provides the defaults for the parameters required by the + * Forchheimer velocity approach. + */ +template +class ForchheimerBaseProblem +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + +public: + /*! + * \brief Returns the Ergun coefficient. + * + * The Ergun coefficient is a measure how much the velocity is + * reduced by turbolence. It is a quantity that does not depend on + * the fluid phase but only on the porous medium in question. A + * value of 0 means that the velocity is not influenced by + * turbolence. + */ + template + Scalar ergunCoefficient(const Context&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: Problem::ergunCoefficient()"); + } + + /*! + * \brief Returns the ratio between the phase mobility + * \f$k_{r,\alpha}\f$ and its passability + * \f$\eta_{r,\alpha}\f$ for a given fluid phase + * \f$\alpha\f$. + * + * The passability coefficient specifies the influence of the + * other fluid phases on the turbolent behaviour of a given fluid + * phase. By default it is equal to the relative permeability. The + * mobility to passability ratio is the inverse of phase' the viscosity. + */ + template + Evaluation mobilityPassabilityRatio(Context& context, + unsigned spaceIdx, + unsigned timeIdx, + unsigned phaseIdx) const + { + return 1.0 / context.intensiveQuantities(spaceIdx, timeIdx).fluidState().viscosity(phaseIdx); + } +}; + +/*! + * \ingroup FluxModules + * \brief Provides the intensive quantities for the Forchheimer module + */ +template +class ForchheimerIntensiveQuantities +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + + enum { numPhases = getPropValue() }; + +public: + /*! + * \brief Returns the Ergun coefficient. + * + * The Ergun coefficient is a measure how much the velocity is + * reduced by turbolence. A value of 0 means that it is not + * influenced. + */ + const Evaluation& ergunCoefficient() const + { return ergunCoefficient_; } + + /*! + * \brief Returns the passability of a phase. + */ + const Evaluation& mobilityPassabilityRatio(unsigned phaseIdx) const + { return mobilityPassabilityRatio_[phaseIdx]; } + +protected: + void update_(const ElementContext& elemCtx, unsigned dofIdx, unsigned timeIdx) + { + const auto& problem = elemCtx.problem(); + ergunCoefficient_ = problem.ergunCoefficient(elemCtx, dofIdx, timeIdx); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + mobilityPassabilityRatio_[phaseIdx] = + problem.mobilityPassabilityRatio(elemCtx, + dofIdx, + timeIdx, + phaseIdx); + } + +private: + Evaluation ergunCoefficient_; + Evaluation mobilityPassabilityRatio_[numPhases]; +}; + +/*! + * \ingroup FluxModules + * \brief Provides the Forchheimer flux module + * + * The commonly used Darcy relation looses its validity for Reynolds numbers \f$ Re < + * 1\f$. If one encounters flow velocities in porous media above this threshold, the + * Forchheimer approach can be used. Like the Darcy approach, it is a relation of with + * the fluid velocity in terms of the gradient of pressure potential. However, this + * relation is not linear (as in the Darcy case) any more. + * + * Therefore, the Newton scheme is used to solve the Forchheimer equation. This velocity + * is then used like the Darcy velocity e.g. by the local residual. + * + * Note that for Reynolds numbers above \f$\approx 500\f$ the standard Forchheimer + * relation also looses it's validity. + * + * The Forchheimer equation is given by the following relation: + * + * \f[ + \nabla p_\alpha - \rho_\alpha \vec{g} = + - \frac{\mu_\alpha}{k_{r,\alpha}} K^{-1}\vec{v}_\alpha + - \frac{\rho_\alpha C_E}{\eta_{r,\alpha}} \sqrt{K}^{-1} + \left| \vec{v}_\alpha \right| \vec{v}_\alpha + \f] + * + * Where \f$C_E\f$ is the modified Ergun parameter and \f$\eta_{r,\alpha}\f$ is the + * passability which is given by a closure relation (usually it is assumed to be + * identical to the relative permeability). To avoid numerical problems, the relation + * implemented by this class multiplies both sides with \f$\frac{k_{r_alpha}}{mu} K\f$, + * so we get + * + * \f[ + \frac{k_{r_alpha}}{mu} K \left( \nabla p_\alpha - \rho_\alpha \vec{g}\right) = + - \vec{v}_\alpha + - \frac{\rho_\alpha C_E}{\eta_{r,\alpha}} \frac{k_{r_alpha}}{mu} \sqrt{K} + \left| \vec{v}_\alpha \right| \vec{v}_\alpha + \f] + + */ +template +class ForchheimerExtensiveQuantities + : public DarcyExtensiveQuantities +{ + using DarcyExtQuants = DarcyExtensiveQuantities; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using GridView = GetPropType; + using Implementation = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = getPropValue() }; + + using Toolbox = MathToolbox; + + using DimVector = Dune::FieldVector; + using DimEvalVector = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + using DimEvalMatrix = Dune::FieldMatrix; + +public: + /*! + * \brief Return the Ergun coefficent at the face's integration point. + */ + const Evaluation& ergunCoefficient() const + { return ergunCoefficient_; } + + /*! + * \brief Return the ratio of the mobility divided by the passability at the face's + * integration point for a given fluid phase. + * + * Usually, that's the inverse of the viscosity. + */ + Evaluation& mobilityPassabilityRatio(unsigned phaseIdx) const + { return mobilityPassabilityRatio_[phaseIdx]; } + +protected: + void calculateGradients_(const ElementContext& elemCtx, + unsigned faceIdx, + unsigned timeIdx) + { + DarcyExtQuants::calculateGradients_(elemCtx, faceIdx, timeIdx); + + auto focusDofIdx = elemCtx.focusDofIndex(); + unsigned i = static_cast(this->interiorDofIdx_); + unsigned j = static_cast(this->exteriorDofIdx_); + const auto& intQuantsIn = elemCtx.intensiveQuantities(i, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(j, timeIdx); + + // calculate the square root of the intrinsic permeability + assert(isDiagonal_(this->K_)); + sqrtK_ = 0.0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + sqrtK_[dimIdx] = std::sqrt(this->K_[dimIdx][dimIdx]); + + // obtain the Ergun coefficient. Lacking better ideas, we use its the arithmetic mean. + if (focusDofIdx == i) { + ergunCoefficient_ = + (intQuantsIn.ergunCoefficient() + + getValue(intQuantsEx.ergunCoefficient()))/2; + } + else if (focusDofIdx == j) + ergunCoefficient_ = + (getValue(intQuantsIn.ergunCoefficient()) + + intQuantsEx.ergunCoefficient())/2; + else + ergunCoefficient_ = + (getValue(intQuantsIn.ergunCoefficient()) + + getValue(intQuantsEx.ergunCoefficient()))/2; + + // obtain the mobility to passability ratio for each phase. + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + unsigned upIdx = static_cast(this->upstreamIndex_(phaseIdx)); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + + if (focusDofIdx == upIdx) { + density_[phaseIdx] = + up.fluidState().density(phaseIdx); + mobilityPassabilityRatio_[phaseIdx] = + up.mobilityPassabilityRatio(phaseIdx); + } + else { + density_[phaseIdx] = + getValue(up.fluidState().density(phaseIdx)); + mobilityPassabilityRatio_[phaseIdx] = + getValue(up.mobilityPassabilityRatio(phaseIdx)); + } + } + } + + template + void calculateBoundaryGradients_(const ElementContext& elemCtx, + unsigned boundaryFaceIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + DarcyExtQuants::calculateBoundaryGradients_(elemCtx, + boundaryFaceIdx, + timeIdx, + fluidState); + + auto focusDofIdx = elemCtx.focusDofIndex(); + unsigned i = static_cast(this->interiorDofIdx_); + const auto& intQuantsIn = elemCtx.intensiveQuantities(i, timeIdx); + + // obtain the Ergun coefficient. Because we are on the boundary here, we will + // take the Ergun coefficient of the interior + if (focusDofIdx == i) + ergunCoefficient_ = intQuantsIn.ergunCoefficient(); + else + ergunCoefficient_ = getValue(intQuantsIn.ergunCoefficient()); + + // calculate the square root of the intrinsic permeability + assert(isDiagonal_(this->K_)); + sqrtK_ = 0.0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + sqrtK_[dimIdx] = std::sqrt(this->K_[dimIdx][dimIdx]); + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + if (focusDofIdx == i) { + density_[phaseIdx] = intQuantsIn.fluidState().density(phaseIdx); + mobilityPassabilityRatio_[phaseIdx] = intQuantsIn.mobilityPassabilityRatio(phaseIdx); + } + else { + density_[phaseIdx] = + getValue(intQuantsIn.fluidState().density(phaseIdx)); + mobilityPassabilityRatio_[phaseIdx] = + getValue(intQuantsIn.mobilityPassabilityRatio(phaseIdx)); + } + } + } + + /*! + * \brief Calculate the volumetric fluxes of all phases + * + * The pressure potentials and upwind directions must already be + * determined before calling this method! + */ + void calculateFluxes_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + auto focusDofIdx = elemCtx.focusDofIndex(); + auto i = asImp_().interiorIndex(); + auto j = asImp_().exteriorIndex(); + const auto& intQuantsI = elemCtx.intensiveQuantities(i, timeIdx); + const auto& intQuantsJ = elemCtx.intensiveQuantities(j, timeIdx); + + const auto& scvf = elemCtx.stencil(timeIdx).interiorFace(scvfIdx); + const auto& normal = scvf.normal(); + Valgrind::CheckDefined(normal); + + // obtain the Ergun coefficient from the intensive quantity object. Until a + // better method comes along, we use arithmetic averaging. + if (focusDofIdx == i) + ergunCoefficient_ = + (intQuantsI.ergunCoefficient() + + getValue(intQuantsJ.ergunCoefficient())) / 2; + else if (focusDofIdx == j) + ergunCoefficient_ = + (getValue(intQuantsI.ergunCoefficient()) + + intQuantsJ.ergunCoefficient()) / 2; + else + ergunCoefficient_ = + (getValue(intQuantsI.ergunCoefficient()) + + getValue(intQuantsJ.ergunCoefficient())) / 2; + + /////////////// + // calculate the weights of the upstream and the downstream control volumes + /////////////// + for (unsigned phaseIdx = 0; phaseIdx < numPhases; phaseIdx++) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) { + this->filterVelocity_[phaseIdx] = 0.0; + this->volumeFlux_[phaseIdx] = 0.0; + continue; + } + + calculateForchheimerFlux_(phaseIdx); + + this->volumeFlux_[phaseIdx] = 0.0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++ dimIdx) + this->volumeFlux_[phaseIdx] += + this->filterVelocity_[phaseIdx][dimIdx]*normal[dimIdx]; + } + } + + /*! + * \brief Calculate the volumetric flux rates of all phases at the domain boundary + */ + void calculateBoundaryFluxes_(const ElementContext& elemCtx, + unsigned bfIdx, + unsigned timeIdx) + { + const auto& boundaryFace = elemCtx.stencil(timeIdx).boundaryFace(bfIdx); + const auto& normal = boundaryFace.normal(); + + /////////////// + // calculate the weights of the upstream and the downstream degrees of freedom + /////////////// + for (unsigned phaseIdx = 0; phaseIdx < numPhases; phaseIdx++) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) { + this->filterVelocity_[phaseIdx] = 0.0; + this->volumeFlux_[phaseIdx] = 0.0; + continue; + } + + calculateForchheimerFlux_(phaseIdx); + + this->volumeFlux_[phaseIdx] = 0.0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + this->volumeFlux_[phaseIdx] += + this->filterVelocity_[phaseIdx][dimIdx]*normal[dimIdx]; + } + } + + void calculateForchheimerFlux_(unsigned phaseIdx) + { + // initial guess: filter velocity is zero + DimEvalVector& velocity = this->filterVelocity_[phaseIdx]; + velocity = 0.0; + + // the change of velocity between two consecutive Newton iterations + DimEvalVector deltaV(1e5); + // the function value that is to be minimized of the equation that is to be + // fulfilled + DimEvalVector residual; + // derivative of equation that is to be solved + DimEvalMatrix gradResid; + + // search by means of the Newton method for a root of Forchheimer equation + unsigned newtonIter = 0; + while (deltaV.one_norm() > 1e-11) { + if (newtonIter >= 50) + throw NumericalProblem("Could not determine Forchheimer velocity within " + + std::to_string(newtonIter)+" iterations"); + ++newtonIter; + + // calculate the residual and its Jacobian matrix + gradForchheimerResid_(residual, gradResid, phaseIdx); + + // newton method + gradResid.solve(deltaV, residual); + velocity -= deltaV; + } + } + + void forchheimerResid_(DimEvalVector& residual, unsigned phaseIdx) const + { + const DimEvalVector& velocity = this->filterVelocity_[phaseIdx]; + + // Obtaining the upstreamed quantities + const auto& mobility = this->mobility_[phaseIdx]; + const auto& density = density_[phaseIdx]; + const auto& mobilityPassabilityRatio = mobilityPassabilityRatio_[phaseIdx]; + + // optain the quantites for the integration point + const auto& pGrad = this->potentialGrad_[phaseIdx]; + + // residual = v_\alpha + residual = velocity; + + // residual += mobility_\alpha K(\grad p_\alpha - \rho_\alpha g) + // -> this->K_.usmv(mobility, pGrad, residual); + assert(isDiagonal_(this->K_)); + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++ dimIdx) + residual[dimIdx] += mobility*pGrad[dimIdx]*this->K_[dimIdx][dimIdx]; + + // Forchheimer turbulence correction: + // + // residual += + // \rho_\alpha + // * mobility_\alpha + // * C_E / \eta_{r,\alpha} + // * abs(v_\alpha) * sqrt(K)*v_\alpha + // + // -> sqrtK_.usmv(density*mobilityPassabilityRatio*ergunCoefficient_*velocity.two_norm(), + // velocity, + // residual); + Evaluation absVel = 0.0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + absVel += velocity[dimIdx]*velocity[dimIdx]; + // the derivatives of the square root of 0 are undefined, so we must guard + // against this case + if (absVel <= 0.0) + absVel = 0.0; + else + absVel = Toolbox::sqrt(absVel); + const auto& alpha = density*mobilityPassabilityRatio*ergunCoefficient_*absVel; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + residual[dimIdx] += sqrtK_[dimIdx]*alpha*velocity[dimIdx]; + Valgrind::CheckDefined(residual); + } + + void gradForchheimerResid_(DimEvalVector& residual, + DimEvalMatrix& gradResid, + unsigned phaseIdx) + { + // TODO (?) use AD for this. + DimEvalVector& velocity = this->filterVelocity_[phaseIdx]; + forchheimerResid_(residual, phaseIdx); + + Scalar eps = 1e-11; + DimEvalVector tmp; + for (unsigned i = 0; i < dimWorld; ++i) { + Scalar coordEps = std::max(eps, Toolbox::scalarValue(velocity[i]) * (1 + eps)); + velocity[i] += coordEps; + forchheimerResid_(tmp, phaseIdx); + tmp -= residual; + tmp /= coordEps; + gradResid[i] = tmp; + velocity[i] -= coordEps; + } + } + + /*! + * \brief Check whether all off-diagonal entries of a tensor are zero. + * + * \param K the tensor that is to be checked. + * \return True iff all off-diagonals are zero. + * + */ + bool isDiagonal_(const DimMatrix& K) const + { + for (unsigned i = 0; i < dimWorld; i++) { + for (unsigned j = 0; j < dimWorld; j++) { + if (i == j) + continue; + + if (std::abs(K[i][j]) > 1e-25) + return false; + } + } + return true; + } + +private: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } + +protected: + // intrinsic permeability tensor and its square root + DimVector sqrtK_; + + // Ergun coefficient of all phases at the integration point + Evaluation ergunCoefficient_; + + // Passability of all phases at the integration point + Evaluation mobilityPassabilityRatio_[numPhases]; + + // Density of all phases at the integration point + Evaluation density_[numPhases]; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/common/multiphasebaseextensivequantities.hh b/opm/models/common/multiphasebaseextensivequantities.hh new file mode 100644 index 00000000000..fde81bb9306 --- /dev/null +++ b/opm/models/common/multiphasebaseextensivequantities.hh @@ -0,0 +1,176 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::MultiPhaseBaseExtensiveQuantities + */ +#ifndef EWOMS_MULTI_PHASE_BASE_EXTENSIVE_QUANTITIES_HH +#define EWOMS_MULTI_PHASE_BASE_EXTENSIVE_QUANTITIES_HH + +#include "multiphasebaseproperties.hh" + +#include +#include +#include + +#include + +#include + +namespace Opm { +/*! + * \ingroup Discretization + * + * \brief This class calculates the pressure potential gradients and + * the filter velocities for multi-phase flow in porous media + */ +template +class MultiPhaseBaseExtensiveQuantities + : public GetPropType + , public GetPropType::FluxExtensiveQuantities +{ + using ParentType = GetPropType; + using Scalar = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + + enum { numPhases = getPropValue() }; + + using FluxModule = GetPropType; + using FluxExtensiveQuantities = typename FluxModule::FluxExtensiveQuantities; + +public: + /*! + * \brief Register all run-time parameters for the extensive quantities. + */ + static void registerParameters() + { + FluxModule::registerParameters(); + } + + /*! + * \brief Update the extensive quantities for a given sub-control-volume-face. + * + * \param elemCtx Reference to the current element context. + * \param scvfIdx The local index of the sub-control-volume face for + * which the extensive quantities should be calculated. + * \param timeIdx The index used by the time discretization. + */ + void update(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, scvfIdx, timeIdx); + + // compute the pressure potential gradients + FluxExtensiveQuantities::calculateGradients_(elemCtx, scvfIdx, timeIdx); + + // Check whether the pressure potential is in the same direction as the face + // normal or in the opposite one + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) { + Valgrind::SetUndefined(upstreamScvIdx_[phaseIdx]); + Valgrind::SetUndefined(downstreamScvIdx_[phaseIdx]); + continue; + } + + upstreamScvIdx_[phaseIdx] = FluxExtensiveQuantities::upstreamIndex_(phaseIdx); + downstreamScvIdx_[phaseIdx] = FluxExtensiveQuantities::downstreamIndex_(phaseIdx); + } + + FluxExtensiveQuantities::calculateFluxes_(elemCtx, scvfIdx, timeIdx); + } + + + /*! + * \brief Update the extensive quantities for a given boundary face. + * + * \param context Reference to the current execution context. + * \param bfIdx The local index of the boundary face for which + * the extensive quantities should be calculated. + * \param timeIdx The index used by the time discretization. + * \param fluidState The FluidState on the domain boundary. + * \param paramCache The FluidSystem's parameter cache. + */ + template + void updateBoundary(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + ParentType::updateBoundary(context, bfIdx, timeIdx, fluidState); + + FluxExtensiveQuantities::calculateBoundaryGradients_(context.elementContext(), + bfIdx, + timeIdx, + fluidState); + FluxExtensiveQuantities::calculateBoundaryFluxes_(context.elementContext(), + bfIdx, + timeIdx); + } + + /*! + * \brief Return the local index of the upstream control volume for a given phase as + * a function of the normal flux. + * + * \param phaseIdx The index of the fluid phase for which the upstream + * direction is requested. + */ + short upstreamIndex(unsigned phaseIdx) const + { return upstreamScvIdx_[phaseIdx]; } + + /*! + * \brief Return the local index of the downstream control volume + * for a given phase as a function of the normal flux. + * + * \param phaseIdx The index of the fluid phase for which the downstream + * direction is requested. + */ + short downstreamIndex(unsigned phaseIdx) const + { return downstreamScvIdx_[phaseIdx]; } + + /*! + * \brief Return the weight of the upstream control volume + * for a given phase as a function of the normal flux. + * + * \param phaseIdx The index of the fluid phase + */ + Scalar upstreamWeight(unsigned) const + { return 1.0; } + + /*! + * \brief Return the weight of the downstream control volume + * for a given phase as a function of the normal flux. + * + * \param phaseIdx The index of the fluid phase + */ + Scalar downstreamWeight(unsigned phaseIdx) const + { return 1.0 - upstreamWeight(phaseIdx); } + +private: + short upstreamScvIdx_[numPhases]; + short downstreamScvIdx_[numPhases]; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/common/multiphasebasemodel.hh b/opm/models/common/multiphasebasemodel.hh new file mode 100644 index 00000000000..1d251ac7396 --- /dev/null +++ b/opm/models/common/multiphasebasemodel.hh @@ -0,0 +1,269 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::MultiPhaseBaseModel + */ +#ifndef EWOMS_MULTI_PHASE_BASE_MODEL_HH +#define EWOMS_MULTI_PHASE_BASE_MODEL_HH + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace Opm { +template +class MultiPhaseBaseModel; +} + +namespace Opm::Properties { + +//! The generic type tag for problems using the immiscible multi-phase model +// Create new type tags +namespace TTag { + +struct MultiPhaseBaseModel {}; + +} // end namespace TTag + +//! Specify the splices of the MultiPhaseBaseModel type tag +template +struct Splices +{ + using type = std::tuple>; +}; + +//! Set the default spatial discretization +//! +//! We use a vertex centered finite volume method by default +template +struct SpatialDiscretizationSplice { using type = TTag::VcfvDiscretization; }; + +//! set the number of equations to the number of phases +template +struct NumEq { static constexpr int value = GetPropType::numEq; }; +//! The number of phases is determined by the fluid system +template +struct NumPhases { static constexpr int value = GetPropType::numPhases; }; +//! Number of chemical species in the system +template +struct NumComponents { static constexpr int value = GetPropType::numComponents; }; + +//! The type of the base base class for actual problems +template +struct BaseProblem { using type = MultiPhaseBaseProblem; }; + +//! By default, use the Darcy relation to determine the phase velocity +template +struct FluxModule { using type = DarcyFluxModule; }; + +/*! + * \brief Set the material law to the null law by default. + */ +template +struct MaterialLaw +{ +private: + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using Traits = NullMaterialTraits; + +public: + using type = NullMaterial; +}; + +/*! + * \brief Set the property for the material parameters by extracting + * it from the material law. + */ +template +struct MaterialLawParams +{ using type = typename GetPropType::Params; }; + +//! set the energy storage law for the solid to the one which assumes zero heat capacity +//! by default +template +struct SolidEnergyLaw +{ using type = NullSolidEnergyLaw>; }; + +//! extract the type of the parameter objects for the solid energy storage law from the +//! law itself +template +struct SolidEnergyLawParams +{ using type = typename GetPropType::Params; }; + +//! set the thermal conduction law to a dummy one by default +template +struct ThermalConductionLaw +{ using type = NullThermalConductionLaw>; }; + +//! extract the type of the parameter objects for the thermal conduction law from the law +//! itself +template +struct ThermalConductionLawParams +{ using type = typename GetPropType::Params; }; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup MultiPhaseBaseModel + * \brief A base class for fully-implicit multi-phase porous-media flow models + * which assume multiple fluid phases. + */ +template +class MultiPhaseBaseModel : public GetPropType +{ + using ParentType = GetPropType; + using Implementation = GetPropType; + using Simulator = GetPropType; + using ThreadManager = GetPropType; + using Scalar = GetPropType; + using Indices = GetPropType; + using FluidSystem = GetPropType; + using ElementContext = GetPropType; + using EqVector = GetPropType; + using GridView = GetPropType; + + using ElementIterator = typename GridView::template Codim<0>::Iterator; + using Element = typename GridView::template Codim<0>::Entity; + + enum { numPhases = getPropValue() }; + enum { numComponents = FluidSystem::numComponents }; + +public: + MultiPhaseBaseModel(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the immiscible model. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + // register runtime parameters of the VTK output modules + VtkMultiPhaseModule::registerParameters(); + VtkTemperatureModule::registerParameters(); + } + + /*! + * \brief Returns true iff a fluid phase is used by the model. + * + * \param phaseIdx The index of the fluid phase in question + */ + bool phaseIsConsidered(unsigned) const + { return true; } + + /*! + * \brief Compute the total storage inside one phase of all + * conservation quantities. + * + * \copydetails Doxygen::storageParam + * \copydetails Doxygen::phaseIdxParam + */ + void globalPhaseStorage(EqVector& storage, unsigned phaseIdx) + { + assert(phaseIdx < numPhases); + + storage = 0; + + ThreadedEntityIterator threadedElemIt(this->gridView()); + std::mutex mutex; +#ifdef _OPENMP +#pragma omp parallel +#endif + { + // Attention: the variables below are thread specific and thus cannot be + // moved in front of the #pragma! + unsigned threadId = ThreadManager::threadId(); + ElementContext elemCtx(this->simulator_); + ElementIterator elemIt = threadedElemIt.beginParallel(); + EqVector tmp; + + for (; !threadedElemIt.isFinished(elemIt); elemIt = threadedElemIt.increment()) { + const Element& elem = *elemIt; + if (elem.partitionType() != Dune::InteriorEntity) + continue; // ignore ghost and overlap elements + + elemCtx.updateStencil(elem); + elemCtx.updateIntensiveQuantities(/*timeIdx=*/0); + + const auto& stencil = elemCtx.stencil(/*timeIdx=*/0); + + for (unsigned dofIdx = 0; dofIdx < elemCtx.numDof(/*timeIdx=*/0); ++dofIdx) { + const auto& scv = stencil.subControlVolume(dofIdx); + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0); + + tmp = 0; + this->localResidual(threadId).addPhaseStorage(tmp, + elemCtx, + dofIdx, + /*timeIdx=*/0, + phaseIdx); + tmp *= scv.volume()*intQuants.extrusionFactor(); + + mutex.lock(); + storage += tmp; + mutex.unlock(); + } + } + } + + storage = this->gridView_.comm().sum(storage); + } + + void registerOutputModules_() + { + ParentType::registerOutputModules_(); + + // add the VTK output modules which make sense for all multi-phase models + this->addOutputModule(new VtkMultiPhaseModule(this->simulator_)); + this->addOutputModule(new VtkTemperatureModule(this->simulator_)); + } + +private: + const Implementation& asImp_() const + { return *static_cast(this); } +}; +} // namespace Opm + +#endif diff --git a/opm/models/common/multiphasebaseparameters.hh b/opm/models/common/multiphasebaseparameters.hh new file mode 100644 index 00000000000..62582f16619 --- /dev/null +++ b/opm/models/common/multiphasebaseparameters.hh @@ -0,0 +1,40 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup MultiPhaseBaseModel + * + * \brief Defines the common parameters for the porous medium + * multi-phase models. + */ +#ifndef EWOMS_MULTI_PHASE_BASE_PARAMETERS_HH +#define EWOMS_MULTI_PHASE_BASE_PARAMETERS_HH + +namespace Opm::Parameters { + +//! Returns whether gravity is considered in the problem. +struct EnableGravity { static constexpr bool value = false; }; + +} // namespace Opm::Parameters + +#endif diff --git a/opm/models/common/multiphasebaseproblem.hh b/opm/models/common/multiphasebaseproblem.hh new file mode 100644 index 00000000000..3b6103eabc7 --- /dev/null +++ b/opm/models/common/multiphasebaseproblem.hh @@ -0,0 +1,409 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::MultiPhaseBaseProblem + */ +#ifndef EWOMS_MULTI_PHASE_BASE_PROBLEM_HH +#define EWOMS_MULTI_PHASE_BASE_PROBLEM_HH + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +namespace Opm { +/*! + * \ingroup Discretization + * + * \brief The base class for the problems of ECFV discretizations which deal + * with a multi-phase flow through a porous medium. + */ +template +class MultiPhaseBaseProblem + : public FvBaseProblem + , public GetPropType::FluxBaseProblem +{ +//! \cond SKIP_THIS + using ParentType = FvBaseProblem; + + using Implementation = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using GridView = GetPropType; + using ElementContext = GetPropType; + using Simulator = GetPropType; + using SolidEnergyLawParams = GetPropType; + using ThermalConductionLawParams = GetPropType; + using MaterialLawParams = typename GetPropType::Params; + using DirectionalMobilityPtr = Opm::Utility::CopyablePtr>; + + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = getPropValue() }; + using DimVector = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; +//! \endcond + +public: + /*! + * \copydoc Problem::FvBaseProblem(Simulator& ) + */ + MultiPhaseBaseProblem(Simulator& simulator) + : ParentType(simulator) + { init_(); } + + /*! + * \brief Register all run-time parameters for the problem and the model. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register + ("Use the gravity correction for the pressure gradients."); + } + + /*! + * \brief Returns the intrinsic permeability of an intersection. + * + * This method is specific to the finite volume discretizations. If left unspecified, + * it calls the intrinsicPermeability() method for the intersection's interior and + * exterior finite volumes and averages them harmonically. Note that if this function + * is defined, the intrinsicPermeability() method does not need to be defined by the + * problem (if a finite-volume discretization is used). + */ + template + void intersectionIntrinsicPermeability(DimMatrix& result, + const Context& context, + unsigned intersectionIdx, + unsigned timeIdx) const + { + const auto& scvf = context.stencil(timeIdx).interiorFace(intersectionIdx); + + const DimMatrix& K1 = asImp_().intrinsicPermeability(context, scvf.interiorIndex(), timeIdx); + const DimMatrix& K2 = asImp_().intrinsicPermeability(context, scvf.exteriorIndex(), timeIdx); + + // entry-wise harmonic mean. this is almost certainly wrong if + // you have off-main diagonal entries in your permeabilities! + for (unsigned i = 0; i < dimWorld; ++i) + for (unsigned j = 0; j < dimWorld; ++j) + result[i][j] = harmonicMean(K1[i][j], K2[i][j]); + } + + /*! + * \name Problem parameters + */ + // \{ + + /*! + * \brief Returns the intrinsic permeability tensor \f$[m^2]\f$ at a given position + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + const DimMatrix& intrinsicPermeability(const Context&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: Problem::intrinsicPermeability()"); + } + + /*! + * \brief Returns the porosity [] of the porous medium for a given + * control volume. + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + Scalar porosity(const Context&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: Problem::porosity()"); + } + + /*! + * \brief Returns the parameter object for the energy storage law of the solid in a + * sub-control volume. + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + const SolidEnergyLawParams& + solidEnergyParams(const Context&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: Problem::solidEnergyParams()"); + } + + /*! + * \brief Returns the parameter object for the thermal conductivity law in a + * sub-control volume. + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + const ThermalConductionLawParams& + thermalConductionParams(const Context&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: Problem::thermalConductionParams()"); + } + + /*! + * \brief Define the tortuosity. + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + Scalar tortuosity(const Context&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: Problem::tortuosity()"); + } + + /*! + * \brief Define the dispersivity. + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + Scalar dispersivity(const Context&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: Problem::dispersivity()"); + } + + /*! + * \brief Returns the material law parameters \f$\mathrm{[K]}\f$ within a control volume. + * + * If you get a compiler error at this method, you set the + * MaterialLaw property to something different than + * Opm::NullMaterialLaw. In this case, you have to overload the + * matererialLaw() method in the derived class! + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + const MaterialLawParams & + materialLawParams(const Context&, + unsigned, + unsigned) const + { + static MaterialLawParams dummy; + return dummy; + } + + template + void updateRelperms([[maybe_unused]] std::array& mobility, + [[maybe_unused]] DirectionalMobilityPtr& dirMob, + [[maybe_unused]] FluidState& fluidState, + [[maybe_unused]] unsigned globalSpaceIdx) const + {} + + /*! + * \brief Returns the temperature \f$\mathrm{[K]}\f$ within a control volume. + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + Scalar temperature(const Context&, + unsigned, + unsigned) const + { return asImp_().temperature(); } + + /*! + * \brief Returns the temperature \f$\mathrm{[K]}\f$ for an isothermal problem. + * + * This is not specific to the discretization. By default it just + * throws an exception so it must be overloaded by the problem if + * no energy equation is to be used. + */ + Scalar temperature() const + { throw std::logic_error("Not implemented:temperature() method not implemented by the actual problem"); } + + + /*! + * \brief Returns the acceleration due to gravity \f$\mathrm{[m/s^2]}\f$. + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + const DimVector& gravity(const Context&, + unsigned, + unsigned) const + { return asImp_().gravity(); } + + /*! + * \brief Returns the acceleration due to gravity \f$\mathrm{[m/s^2]}\f$. + * + * This method is used for problems where the gravitational + * acceleration does not depend on the spatial position. The + * default behaviour is that if the EnableGravity + * property is true, \f$\boldsymbol{g} = ( 0,\dots,\ -9.81)^T \f$ holds, + * else \f$\boldsymbol{g} = ( 0,\dots, 0)^T \f$. + */ + const DimVector& gravity() const + { return gravity_; } + + /*! + * \brief Mark grid cells for refinement or coarsening + * + * \return The number of elements marked for refinement or coarsening. + */ + unsigned markForGridAdaptation() + { + using Toolbox = MathToolbox; + + unsigned numMarked = 0; + ElementContext elemCtx( this->simulator() ); + auto gridView = this->simulator().vanguard().gridView(); + auto& grid = this->simulator().vanguard().grid(); + for (const auto& element : elements(gridView, Dune::Partitions::interior)) { + elemCtx.updateAll(element); + + // HACK: this should better be part of an AdaptionCriterion class + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar minSat = 1e100 ; + Scalar maxSat = -1e100; + size_t nDofs = elemCtx.numDof(/*timeIdx=*/0); + for (unsigned dofIdx = 0; dofIdx < nDofs; ++dofIdx) + { + const auto& intQuant = elemCtx.intensiveQuantities( dofIdx, /*timeIdx=*/0 ); + minSat = std::min(minSat, + Toolbox::value(intQuant.fluidState().saturation(phaseIdx))); + maxSat = std::max(maxSat, + Toolbox::value(intQuant.fluidState().saturation(phaseIdx))); + } + + const Scalar indicator = + (maxSat - minSat)/(std::max(0.01, maxSat+minSat)/2); + if( indicator > 0.2 && element.level() < 2 ) { + grid.mark( 1, element ); + ++ numMarked; + } + else if ( indicator < 0.025 ) { + grid.mark( -1, element ); + ++ numMarked; + } + else + { + grid.mark( 0, element ); + } + } + } + + // get global sum so that every proc is on the same page + numMarked = this->simulator().vanguard().grid().comm().sum( numMarked ); + + return numMarked; + } + + // \} + +protected: + /*! + * \brief Converts a Scalar value to an isotropic Tensor + * + * This is convenient e.g. for specifying intrinsic permebilities: + * \code{.cpp} + * auto permTensor = this->toDimMatrix_(1e-12); + * \endcode + * + * \param val The scalar value which should be expressed as a tensor + */ + DimMatrix toDimMatrix_(Scalar val) const + { + DimMatrix ret(0.0); + for (unsigned i = 0; i < DimMatrix::rows; ++i) + ret[i][i] = val; + return ret; + } + + DimVector gravity_; + +private: + //! Returns the implementation of the problem (i.e. static polymorphism) + Implementation& asImp_() + { return *static_cast(this); } + //! \copydoc asImp_() + const Implementation& asImp_() const + { return *static_cast(this); } + + void init_() + { + gravity_ = 0.0; + if (Parameters::Get()) { + gravity_[dimWorld-1] = -9.81; + } + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/common/multiphasebaseproperties.hh b/opm/models/common/multiphasebaseproperties.hh new file mode 100644 index 00000000000..f067612f950 --- /dev/null +++ b/opm/models/common/multiphasebaseproperties.hh @@ -0,0 +1,89 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup MultiPhaseBaseModel + * + * \brief Defines the common properties required by the porous medium + * multi-phase models. + */ +#ifndef EWOMS_MULTI_PHASE_BASE_PROPERTIES_HH +#define EWOMS_MULTI_PHASE_BASE_PROPERTIES_HH + +#include + +namespace Opm::Properties { + +//! The splice to be used for the spatial discretization +template +struct SpatialDiscretizationSplice { using type = UndefinedProperty; }; +//! Number of fluid phases in the system +template +struct NumPhases { using type = UndefinedProperty; }; +//! Number of chemical species in the system +template +struct NumComponents { using type = UndefinedProperty; }; +//! Enumerations used by the model +template +struct Indices { using type = UndefinedProperty; }; +//! The material law which ought to be used (extracted from the spatial parameters) +template +struct MaterialLaw { using type = UndefinedProperty; }; +//! The context material law (extracted from the spatial parameters) +template +struct MaterialLawParams { using type = UndefinedProperty; }; +//! The material law for the energy stored in the solid matrix +template +struct SolidEnergyLaw { using type = UndefinedProperty; }; +//! The parameters of the material law for energy storage of the solid +template +struct SolidEnergyLawParams { using type = UndefinedProperty; }; +//! The material law for thermal conduction +template +struct ThermalConductionLaw { using type = UndefinedProperty; }; +//! The parameters of the material law for thermal conduction +template +struct ThermalConductionLawParams { using type = UndefinedProperty; }; +//!The fluid systems including the information about the phases +template +struct FluidSystem { using type = UndefinedProperty; }; +//! Specifies the relation used for velocity +template +struct FluxModule { using type = UndefinedProperty; }; + +//! Specify whether energy should be considered as a conservation quantity or not +template +struct EnableEnergy { using type = UndefinedProperty; }; +//! Enable diffusive fluxes? +template +struct EnableDiffusion { using type = UndefinedProperty; }; +//! Enable dispersive fluxes? +template +struct EnableDispersion { using type = UndefinedProperty; }; +//! Enable convective mixing? +template +struct EnableConvectiveMixing { using type = UndefinedProperty; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/common/quantitycallbacks.hh b/opm/models/common/quantitycallbacks.hh new file mode 100644 index 00000000000..5d545b83114 --- /dev/null +++ b/opm/models/common/quantitycallbacks.hh @@ -0,0 +1,482 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This method contains all callback classes for quantities + * that are required by some extensive quantities + */ +#ifndef EWOMS_QUANTITY_CALLBACKS_HH +#define EWOMS_QUANTITY_CALLBACKS_HH + +#include + +#include +#include + +#include +#include + +namespace Opm { +/*! + * \ingroup Discretization + * + * \brief Callback class for temperature. + */ +template +class TemperatureCallback +{ + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + + using IQFluidState = decltype(std::declval().fluidState()); + using ResultRawType = decltype(std::declval().temperature(0)); + +public: + using ResultType = typename std::remove_const::type>::type; + using ResultValueType = typename MathToolbox::ValueType; + + TemperatureCallback(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + {} + + /*! + * \brief Return the temperature given the index of a degree of freedom within an + * element context. + * + * In this context, we assume that thermal equilibrium applies, i.e. that the + * temperature of all phases is equal. + */ + ResultType operator()(unsigned dofIdx) const + { return elemCtx_.intensiveQuantities(dofIdx, /*timeIdx=*/0).fluidState().temperature(/*phaseIdx=*/0); } + +private: + const ElementContext& elemCtx_; +}; + +/*! + * \ingroup Discretization + * + * \brief Callback class for a phase pressure. + */ +template +class PressureCallback +{ + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + + using IQFluidState = decltype(std::declval().fluidState()); + using ResultRawType = decltype(std::declval().pressure(0)); + +public: + using ResultType = typename std::remove_const::type>::type; + using ResultValueType = typename MathToolbox::ValueType; + + PressureCallback(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + { Valgrind::SetUndefined(phaseIdx_); } + + PressureCallback(const ElementContext& elemCtx, unsigned phaseIdx) + : elemCtx_(elemCtx) + , phaseIdx_(static_cast(phaseIdx)) + {} + + /*! + * \brief Set the index of the fluid phase for which the pressure + * should be returned. + */ + void setPhaseIndex(unsigned phaseIdx) + { phaseIdx_ = static_cast(phaseIdx); } + + /*! + * \brief Return the pressure of the specified phase given the index of a degree of + * freedom within an element context. + */ + ResultType operator()(unsigned dofIdx) const + { + Valgrind::CheckDefined(phaseIdx_); + return elemCtx_.intensiveQuantities(dofIdx, /*timeIdx=*/0).fluidState().pressure(phaseIdx_); + } + +private: + const ElementContext& elemCtx_; + unsigned short phaseIdx_; +}; + +/*! + * \ingroup Discretization + * + * \brief Callback class for a phase pressure. + */ +template +class BoundaryPressureCallback +{ + using Scalar = GetPropType; + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + + using IQRawFluidState = decltype(std::declval().fluidState()); + using IQFluidState = typename std::remove_const::type>::type; + using IQScalar = typename IQFluidState::Scalar; + using Toolbox = MathToolbox; + +public: + using ResultType = IQScalar; + + BoundaryPressureCallback(const ElementContext& elemCtx, const FluidState& boundaryFs) + : elemCtx_(elemCtx) + , boundaryFs_(boundaryFs) + { Valgrind::SetUndefined(phaseIdx_); } + + BoundaryPressureCallback(const ElementContext& elemCtx, + const FluidState& boundaryFs, + unsigned phaseIdx) + : elemCtx_(elemCtx) + , boundaryFs_(boundaryFs) + , phaseIdx_(static_cast(phaseIdx)) + {} + + /*! + * \brief Set the index of the fluid phase for which the pressure + * should be returned. + */ + void setPhaseIndex(unsigned phaseIdx) + { phaseIdx_ = static_cast(phaseIdx); } + + /*! + * \brief Return the pressure of a phase given the index of a + * degree of freedom within an element context. + */ + ResultType operator()(unsigned dofIdx) const + { + Valgrind::CheckDefined(phaseIdx_); + return elemCtx_.intensiveQuantities(dofIdx, /*timeIdx=*/0).fluidState().pressure(phaseIdx_); + } + + IQScalar boundaryValue() const + { + Valgrind::CheckDefined(phaseIdx_); + return boundaryFs_.pressure(phaseIdx_); + } + +private: + const ElementContext& elemCtx_; + const FluidState& boundaryFs_; + unsigned short phaseIdx_; +}; + +/*! + * \ingroup Discretization + * + * \brief Callback class for the density of a phase. + */ +template +class DensityCallback +{ + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + + using IQFluidState = decltype(std::declval().fluidState()); + using ResultRawType = decltype(std::declval().density(0)); + +public: + using ResultType = typename std::remove_const::type>::type; + using ResultValueType = typename MathToolbox::ValueType; + + DensityCallback(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + { Valgrind::SetUndefined(phaseIdx_); } + + DensityCallback(const ElementContext& elemCtx, unsigned phaseIdx) + : elemCtx_(elemCtx) + , phaseIdx_(static_cast(phaseIdx)) + {} + + /*! + * \brief Set the index of the fluid phase for which the density + * should be returned. + */ + void setPhaseIndex(unsigned phaseIdx) + { phaseIdx_ = static_cast(phaseIdx); } + + /*! + * \brief Return the density of a phase given the index of a + * degree of freedom within an element context. + */ + ResultType operator()(unsigned dofIdx) const + { + Valgrind::CheckDefined(phaseIdx_); + return elemCtx_.intensiveQuantities(dofIdx, /*timeIdx=*/0).fluidState().density(phaseIdx_); + } + +private: + const ElementContext& elemCtx_; + unsigned short phaseIdx_; +}; + +/*! + * \ingroup Discretization + * + * \brief Callback class for the molar density of a phase. + */ +template +class MolarDensityCallback +{ + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + + using IQFluidState = decltype(std::declval().fluidState()); + +public: + using ResultType = decltype(std::declval().molarDensity(0)); + using ResultValueType = typename MathToolbox::ValueType; + + MolarDensityCallback(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + { Valgrind::SetUndefined(phaseIdx_); } + + MolarDensityCallback(const ElementContext& elemCtx, unsigned phaseIdx) + : elemCtx_(elemCtx) + , phaseIdx_(static_cast(phaseIdx)) + {} + + /*! + * \brief Set the index of the fluid phase for which the molar + * density should be returned. + */ + void setPhaseIndex(unsigned phaseIdx) + { phaseIdx_ = static_cast(phaseIdx); } + + /*! + * \brief Return the molar density of a phase given the index of a + * degree of freedom within an element context. + */ + ResultType operator()(unsigned dofIdx) const + { + Valgrind::CheckDefined(phaseIdx_); + return elemCtx_.intensiveQuantities(dofIdx, /*timeIdx=*/0).fluidState().molarDensity(phaseIdx_); + } + +private: + const ElementContext& elemCtx_; + unsigned short phaseIdx_; +}; + +/*! + * \ingroup Discretization + * + * \brief Callback class for the viscosity of a phase. + */ +template +class ViscosityCallback +{ + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + + using IQFluidState = decltype(std::declval().fluidState()); + using ResultRawType = decltype(std::declval().viscosity(0)); + +public: + using ResultType = typename std::remove_const::type>::type; + using ResultValueType = typename MathToolbox::ValueType; + + ViscosityCallback(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + { Valgrind::SetUndefined(phaseIdx_); } + + ViscosityCallback(const ElementContext& elemCtx, unsigned phaseIdx) + : elemCtx_(elemCtx) + , phaseIdx_(static_cast(phaseIdx)) + {} + + /*! + * \brief Set the index of the fluid phase for which the viscosity + * should be returned. + */ + void setPhaseIndex(unsigned phaseIdx) + { phaseIdx_ = static_cast(phaseIdx); } + + /*! + * \brief Return the viscosity of a phase given the index of a + * degree of freedom within an element context. + */ + ResultType operator()(unsigned dofIdx) const + { + Valgrind::CheckDefined(phaseIdx_); + return elemCtx_.intensiveQuantities(dofIdx, /*timeIdx=*/0).fluidState().viscosity(phaseIdx_); + } + +private: + const ElementContext& elemCtx_; + unsigned short phaseIdx_; +}; + +/*! + * \ingroup Discretization + * + * \brief Callback class for the velocity of a phase at the center of a DOF. + */ +template +class VelocityCallback +{ + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + using GridView = GetPropType; + + using ResultRawType = decltype(IntensiveQuantities().velocityCenter()); + + enum { dim = GridView::dimensionworld }; + +public: + using ResultType = typename std::remove_const::type>::type; + using ResultFieldType = typename ResultType::field_type; + using ResultFieldValueType = typename MathToolbox::ValueType; + + VelocityCallback(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + {} + + /*! + * \brief Return the velocity of a phase given the index of a + * degree of freedom within an element context. + */ + ResultType operator()(unsigned dofIdx) const + { return elemCtx_.intensiveQuantities(dofIdx, /*timeIdx=*/0).velocityCenter(); } + +private: + const ElementContext& elemCtx_; +}; + +/*! + * \ingroup Discretization + * + * \brief Callback class for the velocity of a phase at the center of a DOF. + */ +template +class VelocityComponentCallback +{ + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + + using ResultRawType = decltype(IntensiveQuantities().velocityCenter()[0]); + +public: + using ResultType = typename std::remove_const::type>::type; + using ResultValueType = typename MathToolbox::ValueType; + + VelocityComponentCallback(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + { Valgrind::SetUndefined(dimIdx_); } + + VelocityComponentCallback(const ElementContext& elemCtx, unsigned dimIdx) + : elemCtx_(elemCtx) + , dimIdx_(dimIdx) + {} + + /*! + * \brief Set the index of the component of the velocity + * which should be returned. + */ + void setDimIndex(unsigned dimIdx) + { dimIdx_ = dimIdx; } + + /*! + * \brief Return the velocity of a phase given the index of a + * degree of freedom within an element context. + */ + ResultType operator()(unsigned dofIdx) const + { + Valgrind::CheckDefined(dimIdx_); + return elemCtx_.intensiveQuantities(dofIdx, /*timeIdx=*/0).velocityCenter()[dimIdx_]; + } + +private: + const ElementContext& elemCtx_; + unsigned dimIdx_; +}; + +/*! + * \ingroup Discretization + * + * \brief Callback class for a mole fraction of a component in a phase. + */ +template +class MoleFractionCallback +{ + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + + using IQFluidState = decltype(std::declval().fluidState()); + using ResultRawType = decltype(std::declval().moleFraction(0, 0)); + +public: + using ResultType = typename std::remove_const::type>::type; + using ResultValueType = typename MathToolbox::ValueType; + + MoleFractionCallback(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + { + Valgrind::SetUndefined(phaseIdx_); + Valgrind::SetUndefined(compIdx_); + } + + MoleFractionCallback(const ElementContext& elemCtx, unsigned phaseIdx, unsigned compIdx) + : elemCtx_(elemCtx) + , phaseIdx_(static_cast(phaseIdx)) + , compIdx_(static_cast(compIdx)) + {} + + /*! + * \brief Set the index of the fluid phase for which a mole fraction should be + * returned. + */ + void setPhaseIndex(unsigned phaseIdx) + { phaseIdx_ = static_cast(phaseIdx); } + + /*! + * \brief Set the index of the component for which the mole fraction should be + * returned. + */ + void setComponentIndex(unsigned compIdx) + { compIdx_ = static_cast(compIdx); } + + /*! + * \brief Return the mole fraction of a component in a phase given the index of a + * degree of freedom within an element context. + */ + ResultType operator()(unsigned dofIdx) const + { + Valgrind::CheckDefined(phaseIdx_); + Valgrind::CheckDefined(compIdx_); + return elemCtx_.intensiveQuantities(dofIdx, /*timeIdx=*/0).fluidState().moleFraction(phaseIdx_, compIdx_); + } + +private: + const ElementContext& elemCtx_; + unsigned short phaseIdx_; + unsigned short compIdx_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/common/transfluxmodule.hh b/opm/models/common/transfluxmodule.hh new file mode 100644 index 00000000000..7f9797cef50 --- /dev/null +++ b/opm/models/common/transfluxmodule.hh @@ -0,0 +1,511 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file contains the flux module that uses transmissibilities + * + * The transmissibility approach to fluxes used here is limited + * to the two-point flux approximation + */ +#ifndef EWOMS_TRANS_FLUX_MODULE_HH +#define EWOMS_TRANS_FLUX_MODULE_HH + +#include "multiphasebaseproperties.hh" +#include +#include +#include +#include + +namespace Opm { + +template +class TransIntensiveQuantities; + +template +class TransExtensiveQuantities; + +template +class TransBaseProblem; + +/*! + * \brief Specifies a flux module which uses transmissibilities. + */ +template +struct TransFluxModule +{ + using FluxIntensiveQuantities = TransIntensiveQuantities; + using FluxExtensiveQuantities = TransExtensiveQuantities; + using FluxBaseProblem = TransBaseProblem; + /*! + * \brief Register all run-time parameters for the flux module. + */ + static void registerParameters() + { } +}; + +/*! + * \brief Provides the defaults for the parameters required by the + * transmissibility based volume flux calculation. + */ +template +class TransBaseProblem +{ }; + +/*! + * \brief Provides the intensive quantities for the transmissibility based flux module + */ +template +class TransIntensiveQuantities +{ + using ElementContext = GetPropType; +protected: + void update_(const ElementContext&, unsigned, unsigned) + { } +}; + +/*! + * \brief Provides the transmissibility based flux module + */ +template +class TransExtensiveQuantities +{ + using Implementation = GetPropType; + + using FluidSystem = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using GridView = GetPropType; + using MaterialLaw = GetPropType; + using Discretization = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = FluidSystem::numPhases }; + + typedef MathToolbox Toolbox; + typedef Dune::FieldVector DimVector; + typedef Dune::FieldVector EvalDimVector; + typedef Dune::FieldMatrix DimMatrix; + +public: + /*! + * \brief Return the intrinsic permeability tensor at a face [m^2] + */ + const DimMatrix& intrinsicPermeability() const + { + throw std::logic_error("The ECL transmissibility module does not provide an explicit intrinsic permeability"); + } + + /*! + * \brief Return the pressure potential gradient of a fluid phase at the + * face's integration point [Pa/m] + * + * \param phaseIdx The index of the fluid phase + */ + const EvalDimVector& potentialGrad(unsigned) const + { + throw std::logic_error("The ECL transmissibility module does not provide explicit potential gradients"); + } + + /*! + * \brief Return the gravity corrected pressure difference between the interior and + * the exterior of a face. + * + * \param phaseIdx The index of the fluid phase + */ + const Evaluation& pressureDifference(unsigned phaseIdx) const + { return pressureDifference_[phaseIdx]; } + + /*! + * \brief Return the filter velocity of a fluid phase at the face's integration point + * [m/s] + * + * \param phaseIdx The index of the fluid phase + */ + const EvalDimVector& filterVelocity(unsigned) const + { + throw std::logic_error("The ECL transmissibility module does not provide explicit filter velocities"); + } + + /*! + * \brief Return the volume flux of a fluid phase at the face's integration point + * \f$[m^3/s / m^2]\f$ + * + * This is the fluid volume of a phase per second and per square meter of face + * area. + * + * \param phaseIdx The index of the fluid phase + */ + const Evaluation& volumeFlux(unsigned phaseIdx) const + { return volumeFlux_[phaseIdx]; } + +protected: + /*! + * \brief Returns the local index of the degree of freedom in which is + * in upstream direction. + * + * i.e., the DOF which exhibits a higher effective pressure for + * the given phase. + */ + unsigned upstreamIndex_(unsigned phaseIdx) const + { + assert(phaseIdx < numPhases); + + return upIdx_[phaseIdx]; + } + + /*! + * \brief Returns the local index of the degree of freedom in which is + * in downstream direction. + * + * i.e., the DOF which exhibits a lower effective pressure for the + * given phase. + */ + unsigned downstreamIndex_(unsigned phaseIdx) const + { + assert(phaseIdx < numPhases); + + return dnIdx_[phaseIdx]; + } + + void updateSolvent(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { asImp_().updateVolumeFluxTrans(elemCtx, scvfIdx, timeIdx); } + + void updatePolymer(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { asImp_().updateShearMultipliers(elemCtx, scvfIdx, timeIdx); } + + /*! + * \brief Update the required gradients for interior faces + */ + void calculateGradients_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + Valgrind::SetUndefined(*this); + + // only valied for element center finite volume discretization + static const bool isEcfv = std::is_same >::value; + + static_assert(isEcfv); + + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& scvf = stencil.interiorFace(scvfIdx); + + interiorDofIdx_ = scvf.interiorIndex(); + exteriorDofIdx_ = scvf.exteriorIndex(); + assert(interiorDofIdx_ != exteriorDofIdx_); + + unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); + unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); + + Scalar trans = transmissibility_(elemCtx, scvfIdx, timeIdx); + + // estimate the gravity correction: for performance reasons we use a simplified + // approach for this flux module that assumes that gravity is constant and always + // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) + Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; + + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx_, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx_, timeIdx); + + Scalar zIn = dofCenterDepth_(elemCtx, interiorDofIdx_, timeIdx); + Scalar zEx = dofCenterDepth_(elemCtx, exteriorDofIdx_, timeIdx); + // the distances from the DOF's depths. (i.e., the additional depth of the + // exterior DOF) + Scalar distZ = zIn - zEx; + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + // check shortcut: if the mobility of the phase is zero in the interior as + // well as the exterior DOF, we can skip looking at the phase. + if (intQuantsIn.mobility(phaseIdx) <= 0.0 && + intQuantsEx.mobility(phaseIdx) <= 0.0) + { + upIdx_[phaseIdx] = interiorDofIdx_; + dnIdx_[phaseIdx] = exteriorDofIdx_; + pressureDifference_[phaseIdx] = 0.0; + volumeFlux_[phaseIdx] = 0.0; + continue; + } + + // do the gravity correction: compute the hydrostatic pressure for the + // external at the depth of the internal one + const Evaluation& rhoIn = intQuantsIn.fluidState().density(phaseIdx); + Scalar rhoEx = Toolbox::value(intQuantsEx.fluidState().density(phaseIdx)); + Evaluation rhoAvg = (rhoIn + rhoEx)/2; + + const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(phaseIdx); + Evaluation pressureExterior = Toolbox::value(intQuantsEx.fluidState().pressure(phaseIdx)); + + pressureExterior += rhoAvg*(distZ*g); + + pressureDifference_[phaseIdx] = pressureExterior - pressureInterior; + + // decide the upstream index for the phase. for this we make sure that the + // degree of freedom which is regarded upstream if both pressures are equal + // is always the same: if the pressure is equal, the DOF with the lower + // global index is regarded to be the upstream one. + if (pressureDifference_[phaseIdx] > 0.0) { + upIdx_[phaseIdx] = exteriorDofIdx_; + dnIdx_[phaseIdx] = interiorDofIdx_; + } + else if (pressureDifference_[phaseIdx] < 0.0) { + upIdx_[phaseIdx] = interiorDofIdx_; + dnIdx_[phaseIdx] = exteriorDofIdx_; + } + else { + // if the pressure difference is zero, we chose the DOF which has the + // larger volume associated to it as upstream DOF + Scalar Vin = elemCtx.dofVolume(interiorDofIdx_, /*timeIdx=*/0); + Scalar Vex = elemCtx.dofVolume(exteriorDofIdx_, /*timeIdx=*/0); + if (Vin > Vex) { + upIdx_[phaseIdx] = interiorDofIdx_; + dnIdx_[phaseIdx] = exteriorDofIdx_; + } + else if (Vin < Vex) { + upIdx_[phaseIdx] = exteriorDofIdx_; + dnIdx_[phaseIdx] = interiorDofIdx_; + } + else { + assert(Vin == Vex); + // if the volumes are also equal, we pick the DOF which exhibits the + // smaller global index + if (I < J) { + upIdx_[phaseIdx] = interiorDofIdx_; + dnIdx_[phaseIdx] = exteriorDofIdx_; + } + else { + upIdx_[phaseIdx] = exteriorDofIdx_; + dnIdx_[phaseIdx] = interiorDofIdx_; + } + } + } + + // this is slightly hacky because in the automatic differentiation case, it + // only works for the element centered finite volume method. for ebos this + // does not matter, though. + unsigned upstreamIdx = upstreamIndex_(phaseIdx); + const auto& up = elemCtx.intensiveQuantities(upstreamIdx, timeIdx); + + if (upstreamIdx == interiorDofIdx_) + volumeFlux_[phaseIdx] = + pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*(-trans); + else + volumeFlux_[phaseIdx] = + pressureDifference_[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*(-trans)); + } + } + + /*! + * \brief Update the required gradients for boundary faces + */ + template + void calculateBoundaryGradients_(const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx, + const FluidState& exFluidState) + { + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& scvf = stencil.boundaryFace(scvfIdx); + + interiorDofIdx_ = scvf.interiorIndex(); + + Scalar trans = transmissibilityBoundary_(elemCtx, scvfIdx, timeIdx); + + // estimate the gravity correction: for performance reasons we use a simplified + // approach for this flux module that assumes that gravity is constant and always + // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) + Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; + + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx_, timeIdx); + + // this is quite hacky because the dune grid interface does not provide a + // cellCenterDepth() method (so we ask the problem to provide it). The "good" + // solution would be to take the Z coordinate of the element centroids, but since + // ECL seems to like to be inconsistent on that front, it needs to be done like + // here... + Scalar zIn = dofCenterDepth_(elemCtx, interiorDofIdx_, timeIdx); + Scalar zEx = scvf.integrationPos()[dimWorld - 1]; + + // the distances from the DOF's depths. (i.e., the additional depth of the + // exterior DOF) + Scalar distZ = zIn - zEx; + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + // do the gravity correction: compute the hydrostatic pressure for the + // integration position + const Evaluation& rhoIn = intQuantsIn.fluidState().density(phaseIdx); + const auto& rhoEx = exFluidState.density(phaseIdx); + Evaluation rhoAvg = (rhoIn + rhoEx)/2; + + const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(phaseIdx); + Evaluation pressureExterior = exFluidState.pressure(phaseIdx); + pressureExterior += rhoAvg*(distZ*g); + + pressureDifference_[phaseIdx] = pressureExterior - pressureInterior; + + // decide the upstream index for the phase. for this we make sure that the + // degree of freedom which is regarded upstream if both pressures are equal + // is always the same: if the pressure is equal, the DOF with the lower + // global index is regarded to be the upstream one. + if (pressureDifference_[phaseIdx] > 0.0) { + upIdx_[phaseIdx] = -1; + dnIdx_[phaseIdx] = interiorDofIdx_; + } + else { + upIdx_[phaseIdx] = interiorDofIdx_; + dnIdx_[phaseIdx] = -1; + } + + short upstreamIdx = upstreamIndex_(phaseIdx); + if (upstreamIdx == interiorDofIdx_) { + + // this is slightly hacky because in the automatic differentiation case, it + // only works for the element centered finite volume method. for ebos this + // does not matter, though. + const auto& up = elemCtx.intensiveQuantities(upstreamIdx, timeIdx); + + volumeFlux_[phaseIdx] = + pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*(-trans); + + } + else { + // compute the phase mobility using the material law parameters of the + // interior element. \todo {this could probably be done more efficiently} + const auto& matParams = + elemCtx.problem().materialLawParams(elemCtx, + interiorDofIdx_, + /*timeIdx=*/0); + std::array kr; + MaterialLaw::relativePermeabilities(kr, matParams, exFluidState); + + const auto& mob = kr[phaseIdx]/exFluidState.viscosity(phaseIdx); + volumeFlux_[phaseIdx] = + pressureDifference_[phaseIdx]*mob*(-trans); + + } + } + } + + /*! + * \brief Update the volumetric fluxes for all fluid phases on the interior faces of the context + */ + void calculateFluxes_(const ElementContext&, unsigned, unsigned) + { } + + void calculateBoundaryFluxes_(const ElementContext&, unsigned, unsigned) + {} + +private: + + Scalar transmissibility_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) const + { + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& face = stencil.interiorFace(scvfIdx); + const auto& interiorPos = stencil.subControlVolume(face.interiorIndex()).globalPos(); + const auto& exteriorPos = stencil.subControlVolume(face.exteriorIndex()).globalPos(); + auto distVec0 = face.integrationPos() - interiorPos; + auto distVec1 = face.integrationPos() - exteriorPos; + Scalar ndotDistIn = std::abs(face.normal() * distVec0); + Scalar ndotDistExt = std::abs(face.normal() * distVec1); + + Scalar distSquaredIn = distVec0 * distVec0; + Scalar distSquaredExt = distVec1 * distVec1; + const auto& K0mat = elemCtx.problem().intrinsicPermeability(elemCtx, face.interiorIndex(), timeIdx); + const auto& K1mat = elemCtx.problem().intrinsicPermeability(elemCtx, face.exteriorIndex(), timeIdx); + // the permeability per definition aligns with the grid + // we only support diagonal permeability tensor + // and can therefore neglect off-diagonal values + int idx = 0; + Scalar val = 0.0; + for (unsigned i = 0; i < dimWorld; ++ i){ + if (std::abs(face.normal()[i]) > val) { + val = std::abs(face.normal()[i]); + idx = i; + } + } + const Scalar& K0 = K0mat[idx][idx]; + const Scalar& K1 = K1mat[idx][idx]; + const Scalar T0 = K0 * ndotDistIn / distSquaredIn; + const Scalar T1 = K1 * ndotDistExt / distSquaredExt; + return T0 * T1 / (T0 + T1); + } + Scalar transmissibilityBoundary_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) const + { + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& face = stencil.interiorFace(scvfIdx); + const auto& interiorPos = stencil.subControlVolume(face.interiorIndex()).globalPos(); + auto distVec0 = face.integrationPos() - interiorPos; + Scalar ndotDistIn = face.normal() * distVec0; + Scalar distSquaredIn = distVec0 * distVec0; + const auto& K0mat = elemCtx.problem().intrinsicPermeability(elemCtx, face.interiorIndex(), timeIdx); + // the permeability per definition aligns with the grid + // we only support diagonal permeability tensor + // and can therefore neglect off-diagonal values + int idx = 0; + Scalar val = 0.0; + for (unsigned i = 0; i < dimWorld; ++ i){ + if (std::abs(face.normal()[i]) > val) { + val = std::abs(face.normal()[i]); + idx = i; + } + } + const Scalar& K0 = K0mat[idx][idx]; + const Scalar T0 = K0 * ndotDistIn / distSquaredIn; + return T0; + } + + template + Scalar dofCenterDepth_(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const auto& pos = context.pos(spaceIdx, timeIdx); + return pos[dimWorld-1]; + } + + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } + + // the volumetric flux of all phases [m^3/s] + Evaluation volumeFlux_[numPhases]; + + // the difference in effective pressure between the exterior and the interior degree + // of freedom [Pa] + Evaluation pressureDifference_[numPhases]; + + // the local indices of the interior and exterior degrees of freedom + unsigned short interiorDofIdx_; + unsigned short exteriorDofIdx_; + short upIdx_[numPhases]; + short dnIdx_[numPhases]; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretefracture/discretefractureextensivequantities.hh b/opm/models/discretefracture/discretefractureextensivequantities.hh new file mode 100644 index 00000000000..a1d587d1d87 --- /dev/null +++ b/opm/models/discretefracture/discretefractureextensivequantities.hh @@ -0,0 +1,138 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::DiscreteFractureExtensiveQuantities + */ +#ifndef EWOMS_DISCRETE_FRACTURE_EXTENSIVE_QUANTITIES_HH +#define EWOMS_DISCRETE_FRACTURE_EXTENSIVE_QUANTITIES_HH + +#include + +#include +#include + +namespace Opm { + +/*! + * \ingroup DiscreteFractureModel + * \ingroup ExtensiveQuantities + * + * \brief This class expresses all intensive quantities of the discrete fracture model. + */ +template +class DiscreteFractureExtensiveQuantities : public ImmiscibleExtensiveQuantities +{ + using ParentType = ImmiscibleExtensiveQuantities; + + using ElementContext = GetPropType; + using Scalar = GetPropType; + using GridView = GetPropType; + using FluidSystem = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = FluidSystem::numPhases }; + + using DimMatrix = Dune::FieldMatrix; + using DimVector = Dune::FieldVector; + +public: + /*! + * \copydoc MultiPhaseBaseExtensiveQuantities::update() + */ + void update(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, scvfIdx, timeIdx); + + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& scvf = stencil.interiorFace(scvfIdx); + unsigned insideScvIdx = scvf.interiorIndex(); + unsigned outsideScvIdx = scvf.exteriorIndex(); + + unsigned globalI = elemCtx.globalSpaceIndex(insideScvIdx, timeIdx); + unsigned globalJ = elemCtx.globalSpaceIndex(outsideScvIdx, timeIdx); + const auto& fractureMapper = elemCtx.problem().fractureMapper(); + if (!fractureMapper.isFractureEdge(globalI, globalJ)) + // do nothing if no fracture goes though the current edge + return; + + // average the intrinsic permeability of the fracture + elemCtx.problem().fractureFaceIntrinsicPermeability(fractureIntrinsicPermeability_, + elemCtx, scvfIdx, timeIdx); + + auto distDirection = elemCtx.pos(outsideScvIdx, timeIdx); + distDirection -= elemCtx.pos(insideScvIdx, timeIdx); + distDirection /= distDirection.two_norm(); + + const auto& problem = elemCtx.problem(); + fractureWidth_ = problem.fractureWidth(elemCtx, insideScvIdx, + outsideScvIdx, timeIdx); + assert(fractureWidth_ < scvf.area()); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + const auto& pGrad = extQuants.potentialGrad(phaseIdx); + + unsigned upstreamIdx = static_cast(extQuants.upstreamIndex(phaseIdx)); + const auto& up = elemCtx.intensiveQuantities(upstreamIdx, timeIdx); + + // multiply with the fracture mobility of the upstream vertex + fractureIntrinsicPermeability_.mv(pGrad, + fractureFilterVelocity_[phaseIdx]); + fractureFilterVelocity_[phaseIdx] *= -up.fractureMobility(phaseIdx); + + // divide the volume flux by two. This is required because + // a fracture is always shared by two sub-control-volume + // faces. + fractureVolumeFlux_[phaseIdx] = 0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + fractureVolumeFlux_[phaseIdx] += + (fractureFilterVelocity_[phaseIdx][dimIdx] * distDirection[dimIdx]) + * (fractureWidth_ / 2.0) / scvf.area(); + } + } + +public: + const DimMatrix& fractureIntrinsicPermeability() const + { return fractureIntrinsicPermeability_; } + + Scalar fractureVolumeFlux(unsigned phaseIdx) const + { return fractureVolumeFlux_[phaseIdx]; } + + Scalar fractureWidth() const + { return fractureWidth_; } + + const DimVector& fractureFilterVelocity(unsigned phaseIdx) const + { return fractureFilterVelocity_[phaseIdx]; } + +private: + DimMatrix fractureIntrinsicPermeability_; + DimVector fractureFilterVelocity_[numPhases]; + Scalar fractureVolumeFlux_[numPhases]; + Scalar fractureWidth_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretefracture/discretefractureintensivequantities.hh b/opm/models/discretefracture/discretefractureintensivequantities.hh new file mode 100644 index 00000000000..34eaf98db62 --- /dev/null +++ b/opm/models/discretefracture/discretefractureintensivequantities.hh @@ -0,0 +1,244 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::DiscreteFractureIntensiveQuantities + */ +#ifndef EWOMS_DISCRETE_FRACTURE_INTENSIVE_QUANTITIES_HH +#define EWOMS_DISCRETE_FRACTURE_INTENSIVE_QUANTITIES_HH + +#include "discretefractureproperties.hh" + +#include + +#include + +namespace Opm { + +/*! + * \ingroup DiscreteFractureModel + * \ingroup IntensiveQuantities + * + * \brief Contains the quantities which are are constant within a + * finite volume in the discret fracture immiscible multi-phase + * model. + */ +template +class DiscreteFractureIntensiveQuantities : public ImmiscibleIntensiveQuantities +{ + using ParentType = ImmiscibleIntensiveQuantities; + using Scalar = GetPropType; + using MaterialLaw = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using GridView = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + enum { dimWorld = GridView::dimensionworld }; + + static_assert(dimWorld == 2, "The fracture module currently is only " + "implemented for the 2D case!"); + static_assert(numPhases == 2, "The fracture module currently is only " + "implemented for two fluid phases!"); + + enum { enableEnergy = getPropValue() }; + enum { wettingPhaseIdx = MaterialLaw::wettingPhaseIdx }; + enum { nonWettingPhaseIdx = MaterialLaw::nonWettingPhaseIdx }; + using DimMatrix = Dune::FieldMatrix; + using FluidState = Opm::ImmiscibleFluidState; + +public: + DiscreteFractureIntensiveQuantities() + { } + + DiscreteFractureIntensiveQuantities(const DiscreteFractureIntensiveQuantities& other) = default; + + DiscreteFractureIntensiveQuantities& operator=(const DiscreteFractureIntensiveQuantities& other) = default; + + /*! + * \copydoc IntensiveQuantities::update + */ + void update(const ElementContext& elemCtx, unsigned vertexIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, vertexIdx, timeIdx); + + const auto& problem = elemCtx.problem(); + const auto& fractureMapper = problem.fractureMapper(); + unsigned globalVertexIdx = elemCtx.globalSpaceIndex(vertexIdx, timeIdx); + + Opm::Valgrind::SetUndefined(fractureFluidState_); + Opm::Valgrind::SetUndefined(fractureVolume_); + Opm::Valgrind::SetUndefined(fracturePorosity_); + Opm::Valgrind::SetUndefined(fractureIntrinsicPermeability_); + Opm::Valgrind::SetUndefined(fractureRelativePermeabilities_); + + // do nothing if there is no fracture within the current degree of freedom + if (!fractureMapper.isFractureVertex(globalVertexIdx)) { + fractureVolume_ = 0; + return; + } + + // Make sure that the wetting saturation in the matrix fluid + // state does not get larger than 1 + Scalar SwMatrix = + std::min(1.0, this->fluidState_.saturation(wettingPhaseIdx)); + this->fluidState_.setSaturation(wettingPhaseIdx, SwMatrix); + this->fluidState_.setSaturation(nonWettingPhaseIdx, 1 - SwMatrix); + + // retrieve the facture width and intrinsic permeability from + // the problem + fracturePorosity_ = + problem.fracturePorosity(elemCtx, vertexIdx, timeIdx); + fractureIntrinsicPermeability_ = + problem.fractureIntrinsicPermeability(elemCtx, vertexIdx, timeIdx); + + // compute the fracture volume for the current sub-control + // volume. note, that we don't take overlaps of fractures into + // account for this. + fractureVolume_ = 0; + const auto& vertexPos = elemCtx.pos(vertexIdx, timeIdx); + for (unsigned vertex2Idx = 0; vertex2Idx < elemCtx.numDof(/*timeIdx=*/0); ++ vertex2Idx) { + unsigned globalVertex2Idx = elemCtx.globalSpaceIndex(vertex2Idx, timeIdx); + + if (vertexIdx == vertex2Idx || + !fractureMapper.isFractureEdge(globalVertexIdx, globalVertex2Idx)) + continue; + + Scalar fractureWidth = + problem.fractureWidth(elemCtx, vertexIdx, vertex2Idx, timeIdx); + + auto distVec = elemCtx.pos(vertex2Idx, timeIdx); + distVec -= vertexPos; + + Scalar edgeLength = distVec.two_norm(); + + // the fracture is always adjacent to two sub-control + // volumes of the control volume, so when calculating the + // volume of the fracture which gets attributed to one + // SCV, the fracture width needs to divided by 2. Also, + // only half of the edge is located in the current control + // volume, so its length also needs to divided by 2. + fractureVolume_ += (fractureWidth / 2) * (edgeLength / 2); + } + + ////////// + // set the fluid state for the fracture. + ////////// + + // start with the same fluid state as in the matrix. This + // implies equal saturations, pressures, temperatures, + // enthalpies, etc. + fractureFluidState_.assign(this->fluidState_); + + // ask the problem for the material law parameters of the + // fracture. + const auto& fractureMatParams = + problem.fractureMaterialLawParams(elemCtx, vertexIdx, timeIdx); + + // calculate the fracture saturations which would be required + // to be consistent with the pressures + Scalar saturations[numPhases]; + MaterialLaw::saturations(saturations, fractureMatParams, + fractureFluidState_); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + fractureFluidState_.setSaturation(phaseIdx, saturations[phaseIdx]); + + // Make sure that the wetting saturation in the fracture does + // not get negative + Scalar SwFracture = + std::max(0.0, fractureFluidState_.saturation(wettingPhaseIdx)); + fractureFluidState_.setSaturation(wettingPhaseIdx, SwFracture); + fractureFluidState_.setSaturation(nonWettingPhaseIdx, 1 - SwFracture); + + // calculate the relative permeabilities of the fracture + MaterialLaw::relativePermeabilities(fractureRelativePermeabilities_, + fractureMatParams, + fractureFluidState_); + + // make sure that valgrind complains if the fluid state is not + // fully defined. + fractureFluidState_.checkDefined(); + } + +public: + /*! + * \brief Returns the effective mobility of a given phase within + * the control volume. + * + * \param phaseIdx The phase index + */ + Scalar fractureRelativePermeability(unsigned phaseIdx) const + { return fractureRelativePermeabilities_[phaseIdx]; } + + /*! + * \brief Returns the effective mobility of a given phase within + * the control volume. + * + * \param phaseIdx The phase index + */ + Scalar fractureMobility(unsigned phaseIdx) const + { + return fractureRelativePermeabilities_[phaseIdx] + / fractureFluidState_.viscosity(phaseIdx); + } + + /*! + * \brief Returns the average porosity within the fracture. + */ + Scalar fracturePorosity() const + { return fracturePorosity_; } + + /*! + * \brief Returns the average intrinsic permeability within the + * fracture. + */ + const DimMatrix& fractureIntrinsicPermeability() const + { return fractureIntrinsicPermeability_; } + + /*! + * \brief Return the volume [m^2] occupied by fractures within the + * given sub-control volume. + */ + Scalar fractureVolume() const + { return fractureVolume_; } + + /*! + * \brief Returns a fluid state object which represents the + * thermodynamic state of the fluids within the fracture. + */ + const FluidState& fractureFluidState() const + { return fractureFluidState_; } + +protected: + FluidState fractureFluidState_; + Scalar fractureVolume_; + Scalar fracturePorosity_; + DimMatrix fractureIntrinsicPermeability_; + Scalar fractureRelativePermeabilities_[numPhases]; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretefracture/discretefracturelocalresidual.hh b/opm/models/discretefracture/discretefracturelocalresidual.hh new file mode 100644 index 00000000000..1cf78334c82 --- /dev/null +++ b/opm/models/discretefracture/discretefracturelocalresidual.hh @@ -0,0 +1,159 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::DiscreteFractureLocalResidual + */ +#ifndef EWOMS_DISCRETE_FRACTURE_LOCAL_RESIDUAL_BASE_HH +#define EWOMS_DISCRETE_FRACTURE_LOCAL_RESIDUAL_BASE_HH + +#include + +namespace Opm { + +/*! + * \ingroup DiscreteFractureModel + * + * \brief Calculates the local residual of the discrete fracture + * immiscible multi-phase model. + */ +template +class DiscreteFractureLocalResidual : public ImmiscibleLocalResidual +{ + using ParentType = ImmiscibleLocalResidual; + + using ElementContext = GetPropType; + using Indices = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Scalar = GetPropType; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { numPhases = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + using EnergyModule = Opm::EnergyModule; + +public: + /*! + * \brief Adds the amount all conservation quantities (e.g. phase + * mass) within a single fluid phase + * + * \copydetails Doxygen::storageParam + * \copydetails Doxygen::dofCtxParams + * \copydetails Doxygen::phaseIdxParam + */ + void addPhaseStorage(EqVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx, + unsigned phaseIdx) const + { + EqVector phaseStorage(0.0); + ParentType::addPhaseStorage(phaseStorage, elemCtx, dofIdx, timeIdx, phaseIdx); + + const auto& problem = elemCtx.problem(); + const auto& fractureMapper = problem.fractureMapper(); + unsigned globalIdx = elemCtx.globalSpaceIndex(dofIdx, timeIdx); + + if (!fractureMapper.isFractureVertex(globalIdx)) { + // don't do anything in addition to the immiscible model for degrees of + // freedom that do not feature fractures + storage += phaseStorage; + return; + } + + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + const auto& scv = elemCtx.stencil(timeIdx).subControlVolume(dofIdx); + + // reduce the matrix storage by the fracture volume + phaseStorage *= 1 - intQuants.fractureVolume()/scv.volume(); + + // add the storage term inside the fractures + const auto& fsFracture = intQuants.fractureFluidState(); + + phaseStorage[conti0EqIdx + phaseIdx] += + intQuants.fracturePorosity()* + fsFracture.saturation(phaseIdx) * + fsFracture.density(phaseIdx) * + intQuants.fractureVolume()/scv.volume(); + + EnergyModule::addFracturePhaseStorage(phaseStorage, intQuants, scv, + phaseIdx); + + // add the result to the overall storage term + storage += phaseStorage; + } + + /*! + * \copydoc FvBaseLocalResidual::computeFlux + */ + void computeFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + ParentType::computeFlux(flux, elemCtx, scvfIdx, timeIdx); + + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + unsigned i = extQuants.interiorIndex(); + unsigned j = extQuants.exteriorIndex(); + unsigned I = elemCtx.globalSpaceIndex(i, timeIdx); + unsigned J = elemCtx.globalSpaceIndex(j, timeIdx); + const auto& fractureMapper = elemCtx.problem().fractureMapper(); + if (!fractureMapper.isFractureEdge(I, J)) + // do nothing if the edge from i to j is not part of a + // fracture + return; + + const auto& scvf = elemCtx.stencil(timeIdx).interiorFace(scvfIdx); + Scalar scvfArea = scvf.area(); + + // advective mass fluxes of all phases + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!elemCtx.model().phaseIsConsidered(phaseIdx)) + continue; + + // reduce the matrix mass flux by the width of the scv + // face that is occupied by the fracture. As usual, the + // fracture is shared between two SCVs, so the its width + // needs to be divided by two. + flux[conti0EqIdx + phaseIdx] *= + 1 - extQuants.fractureWidth() / (2 * scvfArea); + + // intensive quantities of the upstream and the downstream DOFs + unsigned upIdx = static_cast(extQuants.upstreamIndex(phaseIdx)); + const auto& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + flux[conti0EqIdx + phaseIdx] += + extQuants.fractureVolumeFlux(phaseIdx) * up.fractureFluidState().density(phaseIdx); + } + + EnergyModule::handleFractureFlux(flux, elemCtx, scvfIdx, timeIdx); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretefracture/discretefracturemodel.hh b/opm/models/discretefracture/discretefracturemodel.hh new file mode 100644 index 00000000000..b8a839ac854 --- /dev/null +++ b/opm/models/discretefracture/discretefracturemodel.hh @@ -0,0 +1,166 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::DiscreteFractureModel + */ +#ifndef EWOMS_DISCRETE_FRACTURE_MODEL_HH +#define EWOMS_DISCRETE_FRACTURE_MODEL_HH + +#include + +#include "discretefractureproperties.hh" +#include "discretefractureprimaryvariables.hh" +#include "discretefractureintensivequantities.hh" +#include "discretefractureextensivequantities.hh" +#include "discretefracturelocalresidual.hh" +#include "discretefractureproblem.hh" + +#include +#include + +#include +#include + +namespace Opm { +template +class DiscreteFractureModel; +} + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +//! The generic type tag for problems using the immiscible multi-phase model +struct DiscreteFractureModel { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! The class for the model +template +struct Model { using type = Opm::DiscreteFractureModel; }; + +//! The class for the model +template +struct BaseProblem { using type = Opm::DiscreteFractureProblem; }; + +//! Use the immiscible multi-phase local jacobian operator for the immiscible multi-phase model +template +struct LocalResidual { using type = Opm::DiscreteFractureLocalResidual; }; + +// The type of the base base class for actual problems. +// TODO!? +// template +// struct BaseProblem { using type = DiscreteFractureBaseProblem; }; + +//! the PrimaryVariables property +template +struct PrimaryVariables +{ using type = Opm::DiscreteFracturePrimaryVariables; }; + +//! the IntensiveQuantities property +template +struct IntensiveQuantities +{ using type = Opm::DiscreteFractureIntensiveQuantities; }; + +//! the ExtensiveQuantities property +template +struct ExtensiveQuantities +{ using type = Opm::DiscreteFractureExtensiveQuantities; }; + +//! For the discrete fracture model, we need to use two-point flux approximation or it +//! will converge very poorly +template +struct UseTwoPointGradients { static constexpr bool value = true; }; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup DiscreteFractureModel + * \brief A fully-implicit multi-phase flow model which assumes + * immiscibility of the phases and is able to include fractures + * in the domain. + * + * This model implements multi-phase flow of \f$M > 0\f$ immiscible + * fluids \f$\alpha\f$. It also can consider edges of the + * computational grid as fractures i.e. as a porous medium with + * different higher permeability than the rest of the domain. + * + * \todo So far, the discrete fracture model only works for 2D grids + * and without energy. Also only the Darcy velocity model is + * supported for the fractures. + * + * \sa ImmiscibleModel + */ +template +class DiscreteFractureModel : public ImmiscibleModel +{ + using ParentType = ImmiscibleModel; + using Simulator = GetPropType; + +public: + DiscreteFractureModel(Simulator& simulator) + : ParentType(simulator) + { + if (Parameters::Get()) { + throw std::runtime_error("The discrete fracture model does not work in conjunction " + "with intensive quantities caching"); + } + } + + /*! + * \brief Register all run-time parameters for the immiscible model. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + // register runtime parameters of the VTK output modules + Opm::VtkDiscreteFractureModule::registerParameters(); + + // The intensive quantity cache cannot be used by the discrete fracture model, because + // the intensive quantities of a control degree of freedom are not identical to the + // intensive quantities of the other intensive quantities of the same of the same degree + // of freedom. This is because the fracture properties (volume, permeability, etc) are + // specific for each... + Parameters::SetDefault(false); + } + + /*! + * \copydoc FvBaseDiscretization::name + */ + static std::string name() + { return "discretefracture"; } + + void registerOutputModules_() + { + ParentType::registerOutputModules_(); + + this->addOutputModule(new Opm::VtkDiscreteFractureModule(this->simulator_)); + } +}; +} // namespace Opm + +#endif diff --git a/opm/models/discretefracture/discretefractureprimaryvariables.hh b/opm/models/discretefracture/discretefractureprimaryvariables.hh new file mode 100644 index 00000000000..0a6778ecea7 --- /dev/null +++ b/opm/models/discretefracture/discretefractureprimaryvariables.hh @@ -0,0 +1,124 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::DiscreteFracturePrimaryVariables + */ +#ifndef EWOMS_DISCRETE_FRACTURE_PRIMARY_VARIABLES_HH +#define EWOMS_DISCRETE_FRACTURE_PRIMARY_VARIABLES_HH + +#include "discretefractureproperties.hh" + +#include + +namespace Opm { +/*! + * \ingroup DiscreteFractureModel + * + * \brief Represents the primary variables used by the discrete fracture + * multi-phase model. + */ +template +class DiscreteFracturePrimaryVariables + : public ImmisciblePrimaryVariables +{ + using ParentType = ImmisciblePrimaryVariables; + + using Scalar = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + + enum { numPhases = getPropValue() }; + +public: + /*! + * \brief Default constructor + */ + DiscreteFracturePrimaryVariables() : ParentType() + {} + + /*! + * \brief Constructor with assignment from scalar + * + * \param value The scalar value to which all entries of the vector will be set. + */ + DiscreteFracturePrimaryVariables(Scalar value) : ParentType(value) + {} + + /*! + * \brief Copy constructor + * + * \param value The primary variables that will be duplicated. + */ + DiscreteFracturePrimaryVariables(const DiscreteFracturePrimaryVariables& value) = default; + DiscreteFracturePrimaryVariables& operator=(const DiscreteFracturePrimaryVariables& value) = default; + + /*! + * \brief Directly retrieve the primary variables from an + * arbitrary fluid state of the fractures. + * + * \param fractureFluidState The fluid state of the fractures + * which should be represented by the + * primary variables. The temperatures, + * pressures and compositions of all + * phases must be defined. + * \param matParams The parameters for the capillary-pressure law + * which apply for the fracture. + */ + template + void assignNaiveFromFracture(const FluidState& fractureFluidState, + const MaterialLawParams& matParams) + { + FluidState matrixFluidState; + fractureToMatrixFluidState_(matrixFluidState, fractureFluidState, + matParams); + + ParentType::assignNaive(matrixFluidState); + } + +private: + template + void fractureToMatrixFluidState_(FluidState& matrixFluidState, + const FluidState& fractureFluidState, + const MaterialLawParams& matParams) const + { + // start with the same fluid state as in the fracture + matrixFluidState.assign(fractureFluidState); + + // the condition for the equilibrium is that the pressures are + // the same in the fracture and in the matrix. This means that + // we have to find saturations for the matrix which result in + // the same pressures as in the fracture. this can be done by + // inverting the capillary pressure-saturation curve. + Scalar saturations[numPhases]; + MaterialLaw::saturations(saturations, matParams, matrixFluidState); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + matrixFluidState.setSaturation(phaseIdx, saturations[phaseIdx]); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretefracture/discretefractureproblem.hh b/opm/models/discretefracture/discretefractureproblem.hh new file mode 100644 index 00000000000..f5bab0af0ff --- /dev/null +++ b/opm/models/discretefracture/discretefractureproblem.hh @@ -0,0 +1,139 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::DiscreteFractureProblem + */ +#ifndef EWOMS_DISCRETE_FRACTURE_PROBLEM_HH +#define EWOMS_DISCRETE_FRACTURE_PROBLEM_HH + +#include "discretefractureproperties.hh" + +#include + +#include + +#include +#include + +namespace Opm { + +/*! + * \ingroup DiscreteFractureModel + * \brief The base class for the problems of ECFV discretizations which deal + * with a multi-phase flow through a porous medium. + */ +template +class DiscreteFractureProblem + : public MultiPhaseBaseProblem +{ + using ParentType = Opm::MultiPhaseBaseProblem; + + using Implementation = GetPropType; + using Scalar = GetPropType; + using GridView = GetPropType; + using Simulator = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + using DimMatrix = Dune::FieldMatrix; + +public: + /*! + * \copydoc Problem::FvBaseProblem(Simulator& ) + */ + DiscreteFractureProblem(Simulator& simulator) + : ParentType(simulator) + {} + + /*! + * \brief Returns the intrinsic permeability of a face due to a fracture. + * + * This method is specific to the finite volume discretizations. If left unspecified, + * it calls the intrinsicPermeability() methods for the face's interior and exterior + * finite volume and averages them harmonically. Note that if this function is + * defined, the intrinsicPermeability() method does not need to be defined by the + * problem (if a finite-volume discretization is used). + */ + template + void fractureFaceIntrinsicPermeability(DimMatrix& result, + const Context& context, + unsigned localFaceIdx, + unsigned timeIdx) const + { + const auto& scvf = context.stencil(timeIdx).interiorFace(localFaceIdx); + unsigned interiorElemIdx = scvf.interiorIndex(); + unsigned exteriorElemIdx = scvf.exteriorIndex(); + const DimMatrix& K1 = asImp_().fractureIntrinsicPermeability(context, interiorElemIdx, timeIdx); + const DimMatrix& K2 = asImp_().fractureIntrinsicPermeability(context, exteriorElemIdx, timeIdx); + + // entry-wise harmonic mean. this is almost certainly wrong if + // you have off-main diagonal entries in your permeabilities! + for (unsigned i = 0; i < dimWorld; ++i) + for (unsigned j = 0; j < dimWorld; ++j) + result[i][j] = Opm::harmonicMean(K1[i][j], K2[i][j]); + } + /*! + * \brief Returns the intrinsic permeability tensor \f$[m^2]\f$ at a given position due to a fracture + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + const DimMatrix& fractureIntrinsicPermeability(const Context&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: Problem::fractureIntrinsicPermeability()"); + } + + /*! + * \brief Returns the porosity [] inside fractures for a given control volume. + * + * \param context Reference to the object which represents the + * current execution context. + * \param spaceIdx The local index of spatial entity defined by the context + * \param timeIdx The index used by the time discretization. + */ + template + Scalar fracturePorosity(const Context&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: Problem::fracturePorosity()"); + } + +private: + //! Returns the implementation of the problem (i.e. static polymorphism) + Implementation& asImp_() + { return *static_cast(this); } + //! \copydoc asImp_() + const Implementation& asImp_() const + { return *static_cast(this); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretefracture/discretefractureproperties.hh b/opm/models/discretefracture/discretefractureproperties.hh new file mode 100644 index 00000000000..7d2b929ea93 --- /dev/null +++ b/opm/models/discretefracture/discretefractureproperties.hh @@ -0,0 +1,44 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup DiscreteFractureModel + * + * \brief Defines the properties required for the immiscible + * multi-phase model which considers discrete fractures. + */ +#ifndef EWOMS_DISCRETE_FRACTIRE_PROPERTIES_HH +#define EWOMS_DISCRETE_FRACTIRE_PROPERTIES_HH + +#include + +#include + +namespace Opm::Properties { + +template +struct UseTwoPointGradients { using type = UndefinedProperty; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/discretefracture/fracturemapper.hh b/opm/models/discretefracture/fracturemapper.hh new file mode 100644 index 00000000000..c18fb1cb173 --- /dev/null +++ b/opm/models/discretefracture/fracturemapper.hh @@ -0,0 +1,108 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::FractureMapper + */ +#ifndef EWOMS_FRACTURE_MAPPER_HH +#define EWOMS_FRACTURE_MAPPER_HH + +#include + +#include +#include + +namespace Opm { + +/*! + * \ingroup DiscreteFractureModel + * \brief Stores the topology of fractures. + */ +template +class FractureMapper +{ + struct FractureEdge + { + FractureEdge(unsigned edgeVertex1Idx, unsigned edgeVertex2Idx) + : i_(std::min(edgeVertex1Idx, edgeVertex2Idx)), + j_(std::max(edgeVertex1Idx, edgeVertex2Idx)) + {} + + bool operator<(const FractureEdge& e) const + { return i_ < e.i_ || (i_ == e.i_ && j_ < e.j_); } + + bool operator==(const FractureEdge& e) const + { return i_ == e.i_ && j_ == e.j_; } + + unsigned i_; + unsigned j_; + }; + +public: + /*! + * \brief Constructor + */ + FractureMapper() + {} + + /*! + * \brief Marks an edge as having a fracture. + * + * \param vertexIdx1 The index of the edge's first vertex. + * \param vertexIdx2 The index of the edge's second vertex. + */ + void addFractureEdge(unsigned vertexIdx1, unsigned vertexIdx2) + { + fractureEdges_.insert(FractureEdge(vertexIdx1, vertexIdx2)); + fractureVertices_.insert(vertexIdx1); + fractureVertices_.insert(vertexIdx2); + } + + /*! + * \brief Returns true iff a fracture cuts through a given vertex. + * + * \param vertexIdx The index of the vertex. + */ + bool isFractureVertex(unsigned vertexIdx) const + { return fractureVertices_.count(vertexIdx) > 0; } + + /*! + * \brief Returns true iff a fracture is associated with a given edge. + * + * \param vertex1Idx The index of the first vertex of the edge. + * \param vertex2Idx The index of the second vertex of the edge. + */ + bool isFractureEdge(unsigned vertex1Idx, unsigned vertex2Idx) const + { + FractureEdge tmp(vertex1Idx, vertex2Idx); + return fractureEdges_.count(tmp) > 0; + } + +private: + std::set fractureEdges_; + std::set fractureVertices_; +}; + +} // namespace Opm + +#endif // EWOMS_FRACTURE_MAPPER_HH diff --git a/opm/models/discretization/common/baseauxiliarymodule.hh b/opm/models/discretization/common/baseauxiliarymodule.hh new file mode 100644 index 00000000000..61dc6624d3f --- /dev/null +++ b/opm/models/discretization/common/baseauxiliarymodule.hh @@ -0,0 +1,130 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::BaseAuxiliaryModule + */ +#ifndef EWOMS_BASE_AUXILIARY_MODULE_HH +#define EWOMS_BASE_AUXILIARY_MODULE_HH + +#include + +#include +#include + +#include +#include + +namespace Opm::Properties::Tag { + +struct AuxModule {}; + +} // namespace Opm::Properties::TTag + +namespace Opm { + +/*! + * \ingroup ModelModules + * + * \brief Base class for specifying auxiliary equations. + * + * For example, these equations can be wells, non-neighboring connections, interfaces + * between model domains, etc. + */ +template +class BaseAuxiliaryModule +{ + using Scalar = GetPropType; + using GridView = GetPropType; + using GlobalEqVector = GetPropType; + using SparseMatrixAdapter = GetPropType; + +protected: + using NeighborSet = std::set; + +public: + virtual ~BaseAuxiliaryModule() + {} + + /*! + * \brief Returns the number of additional degrees of freedom required for the + * auxiliary module. + */ + virtual unsigned numDofs() const = 0; + + /*! + * \brief Set the offset in the global system of equations for the first degree of + * freedom of this auxiliary module. + */ + void setDofOffset(int value) + { dofOffset_ = value; } + + /*! + * \brief Return the offset in the global system of equations for the first degree of + * freedom of this auxiliary module. + */ + int dofOffset() + { return dofOffset_; } + + /*! + * \brief Given a degree of freedom relative to the current auxiliary equation, + * return the corresponding index in the global system of equations. + */ + int localToGlobalDof(unsigned localDofIdx) const + { + assert(localDofIdx < numDofs()); + return dofOffset_ + localDofIdx; + } + + /*! + * \brief Specify the additional neighboring correlations caused by the auxiliary + * module. + */ + virtual void addNeighbors(std::vector& neighbors) const = 0; + + /*! + * \brief Set the initial condition of the auxiliary module in the solution vector. + */ + virtual void applyInitial() = 0; + + /*! + * \brief Linearize the auxiliary equation. + */ + virtual void linearize(SparseMatrixAdapter& matrix, GlobalEqVector& residual) = 0; + + /*! + * \brief This method is called after the linear solver has been called but before + * the solution is updated for the next iteration. + * + * It is intended to implement stuff like Schur complements. + */ + virtual void postSolve(GlobalEqVector&) + {}; + +private: + int dofOffset_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaseadlocallinearizer.hh b/opm/models/discretization/common/fvbaseadlocallinearizer.hh new file mode 100644 index 00000000000..69fb9dfd369 --- /dev/null +++ b/opm/models/discretization/common/fvbaseadlocallinearizer.hh @@ -0,0 +1,307 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseAdLocalLinearizer + */ +#ifndef EWOMS_FV_BASE_AD_LOCAL_LINEARIZER_HH +#define EWOMS_FV_BASE_AD_LOCAL_LINEARIZER_HH + +#include "fvbaseproperties.hh" + +#include +#include + +#include +#include + +#include +#include + +namespace Opm { +// forward declaration +template +class FvBaseAdLocalLinearizer; +} + +namespace Opm::Properties { + +// declare the property tags required for the finite differences local linearizer + +namespace TTag { +struct AutoDiffLocalLinearizer {}; +} // namespace TTag + +// set the properties to be spliced in +template +struct LocalLinearizer +{ using type = FvBaseAdLocalLinearizer; }; + +//! Set the function evaluation w.r.t. the primary variables +template +struct Evaluation +{ +private: + static const unsigned numEq = getPropValue(); + + using Scalar = GetPropType; + +public: + using type = DenseAd::Evaluation; +}; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Calculates the local residual and its Jacobian for a single element of the grid. + * + * This class uses automatic differentiation to calculate the partial derivatives (the + * alternative is finite differences). + */ +template +class FvBaseAdLocalLinearizer +{ +private: + using Implementation = GetPropType; + using LocalResidual = GetPropType; + using Simulator = GetPropType; + using Problem = GetPropType; + using Model = GetPropType; + using PrimaryVariables = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + using GridView = GetPropType; + using Element = typename GridView::template Codim<0>::Entity; + + enum { numEq = getPropValue() }; + + using ScalarVectorBlock = Dune::FieldVector; + // extract local matrices from jacobian matrix for consistency + using ScalarMatrixBlock = typename GetPropType::MatrixBlock; + + using ScalarLocalBlockVector = Dune::BlockVector; + using ScalarLocalBlockMatrix = Dune::Matrix; + +public: + FvBaseAdLocalLinearizer() + : internalElemContext_(0) + { } + + // copying local linearizer objects around is a very bad idea, so we explicitly + // prevent it... + FvBaseAdLocalLinearizer(const FvBaseAdLocalLinearizer&) = delete; + + ~FvBaseAdLocalLinearizer() + { delete internalElemContext_; } + + /*! + * \brief Register all run-time parameters for the local jacobian. + */ + static void registerParameters() + { } + + /*! + * \brief Initialize the local Jacobian object. + * + * At this point we can assume that everything has been allocated, + * although some objects may not yet be completely initialized. + * + * \param simulator The simulator object of the simulation. + */ + void init(Simulator& simulator) + { + simulatorPtr_ = &simulator; + delete internalElemContext_; + internalElemContext_ = new ElementContext(simulator); + } + + /*! + * \brief Compute an element's local Jacobian matrix and evaluate its residual. + * + * The local Jacobian for a given context is defined as the derivatives of the + * residuals of all degrees of freedom featured by the stencil with regard to the + * primary variables of the stencil's "primary" degrees of freedom. Adding the local + * Jacobians for all elements in the grid will give the global Jacobian 'grad f(x)'. + * + * \param element The grid element for which the local residual and its local + * Jacobian should be calculated. + */ + void linearize(const Element& element) + { + linearize(*internalElemContext_, element); + } + + /*! + * \brief Compute an element's local Jacobian matrix and evaluate its residual. + * + * The local Jacobian for a given context is defined as the derivatives of the + * residuals of all degrees of freedom featured by the stencil with regard to the + * primary variables of the stencil's "primary" degrees of freedom. Adding the local + * Jacobians for all elements in the grid will give the global Jacobian 'grad f(x)'. + * + * After calling this method the ElementContext is in an undefined state, so do not + * use it anymore! + * + * \param elemCtx The element execution context for which the local residual and its + * local Jacobian should be calculated. + */ + void linearize(ElementContext& elemCtx, const Element& elem) + { + elemCtx.updateStencil(elem); + elemCtx.updateAllIntensiveQuantities(); + + // update the weights of the primary variables for the context + model_().updatePVWeights(elemCtx); + + // resize the internal arrays of the linearizer + resize_(elemCtx); + + // compute the local residual and its Jacobian + unsigned numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + for (unsigned focusDofIdx = 0; focusDofIdx < numPrimaryDof; focusDofIdx++) { + elemCtx.setFocusDofIndex(focusDofIdx); + elemCtx.updateAllExtensiveQuantities(); + + // calculate the local residual + localResidual_.eval(elemCtx); + + // convert the local Jacobian matrix and the right hand side from the data + // structures used by the automatic differentiation code to the conventional + // ones used by the linear solver. + updateLocalLinearization_(elemCtx, focusDofIdx); + } + } + + /*! + * \brief Return reference to the local residual. + */ + LocalResidual& localResidual() + { return localResidual_; } + + /*! + * \brief Return reference to the local residual. + */ + const LocalResidual& localResidual() const + { return localResidual_; } + + /*! + * \brief Returns the local Jacobian matrix of the residual of a sub-control volume. + * + * \param domainScvIdx The local index of the sub control volume to which the primary + * variables are associated with + * \param rangeScvIdx The local index of the sub control volume which contains the + * local residual + */ + const ScalarMatrixBlock& jacobian(unsigned domainScvIdx, unsigned rangeScvIdx) const + { return jacobian_[domainScvIdx][rangeScvIdx]; } + + /*! + * \brief Returns the local residual of a sub-control volume. + * + * \param dofIdx The local index of the sub control volume + */ + const ScalarVectorBlock& residual(unsigned dofIdx) const + { return residual_[dofIdx]; } + +protected: + Implementation& asImp_() + { return *static_cast(this); } + const Implementation& asImp_() const + { return *static_cast(this); } + + const Simulator& simulator_() const + { return *simulatorPtr_; } + const Problem& problem_() const + { return simulatorPtr_->problem(); } + const Model& model_() const + { return simulatorPtr_->model(); } + + /*! + * \brief Resize all internal attributes to the size of the + * element. + */ + void resize_(const ElementContext& elemCtx) + { + size_t numDof = elemCtx.numDof(/*timeIdx=*/0); + size_t numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + + residual_.resize(numDof); + if (jacobian_.N() != numDof || jacobian_.M() != numPrimaryDof) + jacobian_.setSize(numDof, numPrimaryDof); + } + + /*! + * \brief Reset the all relevant internal attributes to 0 + */ + void reset_(const ElementContext&) + { + residual_ = 0.0; + jacobian_ = 0.0; + } + + /*! + * \brief Updates the current local Jacobian matrix with the partial derivatives of + * all equations for the degree of freedom associated with 'focusDofIdx'. + */ + void updateLocalLinearization_(const ElementContext& elemCtx, + unsigned focusDofIdx) + { + const auto& resid = localResidual_.residual(); + + for (unsigned eqIdx = 0; eqIdx < numEq; eqIdx++) + residual_[focusDofIdx][eqIdx] = resid[focusDofIdx][eqIdx].value(); + + size_t numDof = elemCtx.numDof(/*timeIdx=*/0); + for (unsigned dofIdx = 0; dofIdx < numDof; dofIdx++) { + for (unsigned eqIdx = 0; eqIdx < numEq; eqIdx++) { + for (unsigned pvIdx = 0; pvIdx < numEq; pvIdx++) { + // A[dofIdx][focusDofIdx][eqIdx][pvIdx] is the partial derivative of + // the residual function 'eqIdx' for the degree of freedom 'dofIdx' + // with regard to the focus variable 'pvIdx' of the degree of freedom + // 'focusDofIdx' + jacobian_[dofIdx][focusDofIdx][eqIdx][pvIdx] = resid[dofIdx][eqIdx].derivative(pvIdx); + Valgrind::CheckDefined(jacobian_[dofIdx][focusDofIdx][eqIdx][pvIdx]); + } + } + } + } + + Simulator *simulatorPtr_; + Model *modelPtr_; + + ElementContext *internalElemContext_; + + LocalResidual localResidual_; + + ScalarLocalBlockVector residual_; + ScalarLocalBlockMatrix jacobian_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaseboundarycontext.hh b/opm/models/discretization/common/fvbaseboundarycontext.hh new file mode 100644 index 00000000000..418443116ec --- /dev/null +++ b/opm/models/discretization/common/fvbaseboundarycontext.hh @@ -0,0 +1,274 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseBoundaryContext + */ +#ifndef EWOMS_FV_BASE_BOUNDARY_CONTEXT_HH +#define EWOMS_FV_BASE_BOUNDARY_CONTEXT_HH + +#include "fvbaseproperties.hh" + +#include + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Represents all quantities which available on boundary segments + */ +template +class FvBaseBoundaryContext +{ + using Scalar = GetPropType; + using Problem = GetPropType; + using Model = GetPropType; + using Stencil = GetPropType; + using ElementContext = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using GradientCalculator = GetPropType; + + using GridView = GetPropType; + using Element = typename GridView::template Codim<0>::Entity; + using IntersectionIterator = typename GridView::IntersectionIterator; + using Intersection = typename GridView::Intersection; + + enum { dim = GridView::dimension }; + enum { dimWorld = GridView::dimensionworld }; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + using Vector = Dune::FieldVector; + +public: + /*! + * \brief The constructor. + */ + explicit FvBaseBoundaryContext(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + , intersectionIt_(gridView().ibegin(element())) + { } + + void increment() + { + const auto& iend = gridView().iend(element()); + + if(intersectionIt_ == iend) + return; + + ++intersectionIt_; + // iterate to the next boundary intersection + while (intersectionIt_ != iend && intersectionIt_->neighbor()) { + ++intersectionIt_; + } + } + + /*! + * \copydoc Opm::ElementContext::problem() + */ + const Problem& problem() const + { return elemCtx_.problem(); } + + /*! + * \copydoc Opm::ElementContext::model() + */ + const Model& model() const + { return elemCtx_.model(); } + + /*! + * \copydoc Opm::ElementContext::gridView() + */ + const GridView& gridView() const + { return elemCtx_.gridView(); } + + /*! + * \copydoc Opm::ElementContext::element() + */ + const Element& element() const + { return elemCtx_.element(); } + + /*! + * \brief Returns a reference to the element context object. + */ + const ElementContext& elementContext() const + { return elemCtx_; } + + /*! + * \brief Returns a reference to the current gradient calculator. + */ + const GradientCalculator& gradientCalculator() const + { return elemCtx_.gradientCalculator(); } + + /*! + * \copydoc Opm::ElementContext::numDof() + */ + size_t numDof(unsigned timeIdx) const + { return elemCtx_.numDof(timeIdx); } + + /*! + * \copydoc Opm::ElementContext::numPrimaryDof() + */ + size_t numPrimaryDof(unsigned timeIdx) const + { return elemCtx_.numPrimaryDof(timeIdx); } + + /*! + * \copydoc Opm::ElementContext::numInteriorFaces() + */ + size_t numInteriorFaces(unsigned timeIdx) const + { return elemCtx_.numInteriorFaces(timeIdx); } + + /*! + * \brief Return the number of boundary segments of the current element + */ + size_t numBoundaryFaces(unsigned timeIdx) const + { return elemCtx_.stencil(timeIdx).numBoundaryFaces(); } + + /*! + * \copydoc Opm::ElementContext::stencil() + */ + const Stencil& stencil(unsigned timeIdx) const + { return elemCtx_.stencil(timeIdx); } + + /*! + * \brief Returns the outer unit normal of the boundary segment + * + * \param boundaryFaceIdx The local index of the boundary segment + * \param timeIdx The index of the solution used by the time discretization + */ + Vector normal(unsigned boundaryFaceIdx, unsigned timeIdx) const + { + auto tmp = stencil(timeIdx).boundaryFace[boundaryFaceIdx].normal; + tmp /= tmp.two_norm(); + return tmp; + } + + /*! + * \brief Returns the area [m^2] of a given boudary segment. + */ + Scalar boundarySegmentArea(unsigned boundaryFaceIdx, unsigned timeIdx) const + { return elemCtx_.stencil(timeIdx).boundaryFace(boundaryFaceIdx).area(); } + + /*! + * \brief Return the position of a local entity in global coordinates. + * + * \param boundaryFaceIdx The local index of the boundary segment + * \param timeIdx The index of the solution used by the time discretization + */ + const GlobalPosition& pos(unsigned boundaryFaceIdx, unsigned timeIdx) const + { return stencil(timeIdx).boundaryFace(boundaryFaceIdx).integrationPos(); } + + /*! + * \brief Return the position of a control volume's center in global coordinates. + * + * \param boundaryFaceIdx The local index of the boundary segment + * \param timeIdx The index of the solution used by the time discretization + */ + const GlobalPosition& cvCenter(unsigned boundaryFaceIdx, unsigned timeIdx) const + { + unsigned scvIdx = stencil(timeIdx).boundaryFace(boundaryFaceIdx).interiorIndex(); + return stencil(timeIdx).subControlVolume(scvIdx).globalPos(); + } + + /*! + * \brief Return the local sub-control volume index upon which the linearization is + * currently focused. + */ + unsigned focusDofIndex() const + { return elemCtx_.focusDofIndex(); } + + /*! + * \brief Return the local sub-control volume index of the + * interior of a boundary segment + * + * \param boundaryFaceIdx The local index of the boundary segment + * \param timeIdx The index of the solution used by the time discretization + */ + unsigned interiorScvIndex(unsigned boundaryFaceIdx, unsigned timeIdx) const + { return stencil(timeIdx).boundaryFace(boundaryFaceIdx).interiorIndex(); } + + /*! + * \brief Return the global space index of the sub-control volume + * at the interior of a boundary segment + * + * \param boundaryFaceIdx The local index of the boundary segment + * \param timeIdx The index of the solution used by the time discretization + */ + unsigned globalSpaceIndex(unsigned boundaryFaceIdx, unsigned timeIdx) const + { return elemCtx_.globalSpaceIndex(interiorScvIndex(boundaryFaceIdx, timeIdx), timeIdx); } + + /*! + * \brief Return the intensive quantities for the finite volume in the + * interiour of a boundary segment + * + * \param boundaryFaceIdx The local index of the boundary segment + * \param timeIdx The index of the solution used by the time discretization + */ + const IntensiveQuantities& intensiveQuantities(unsigned boundaryFaceIdx, unsigned timeIdx) const + { + unsigned interiorScvIdx = this->interiorScvIndex(boundaryFaceIdx, timeIdx); + return elemCtx_.intensiveQuantities(interiorScvIdx, timeIdx); + } + + /*! + * \brief Return the extensive quantities for a given boundary face. + * + * \param boundaryFaceIdx The local index of the boundary segment + * \param timeIdx The index of the solution used by the time discretization + */ + const ExtensiveQuantities& extensiveQuantities(unsigned boundaryFaceIdx, unsigned timeIdx) const + { return elemCtx_.boundaryExtensiveQuantities(boundaryFaceIdx, timeIdx); } + + /*! + * \brief Return the intersection for the neumann segment + * + * TODO/HACK: The intersection should take a local index as an + * argument. since that's not supported efficiently by the DUNE + * grid interface, we just ignore the index argument here! + * + * \param boundaryFaceIdx The local index of the boundary segment + */ + const Intersection intersection(unsigned) const + { return *intersectionIt_; } + + /*! + * \brief Return the intersection for the neumann segment + * + * TODO/HACK: the intersection iterator can basically be + * considered as an index which is manipulated externally, but + * context classes should not store any indices. it is done this + * way for performance reasons + */ + IntersectionIterator& intersectionIt() + { return intersectionIt_; } + +protected: + const ElementContext& elemCtx_; + IntersectionIterator intersectionIt_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaseconstraints.hh b/opm/models/discretization/common/fvbaseconstraints.hh new file mode 100644 index 00000000000..1a7d4c9f655 --- /dev/null +++ b/opm/models/discretization/common/fvbaseconstraints.hh @@ -0,0 +1,80 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseConstraints + */ +#ifndef EWOMS_FV_BASE_CONSTRAINTS_HH +#define EWOMS_FV_BASE_CONSTRAINTS_HH + +#include +#include + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Class to specify constraints for a finite volume spatial discretization. + * + * Note that eWoms does not support "partial" constraints. (I.e., either all equations + * must be constraint or none.) + */ +template +class FvBaseConstraints : public GetPropType +{ + using ParentType = GetPropType; + +public: + FvBaseConstraints() + { setActive(false); } + + FvBaseConstraints(const FvBaseConstraints&) = default; + +//! \cond SKIP + /*! + * \brief Use the default assignment operator + */ + FvBaseConstraints &operator=(const FvBaseConstraints&) = default; +//! \endcond + + /*! + * \brief Returns true if the constraints are enforced. + */ + bool isActive() const + { return isActive_; } + + /*! + * \brief Specify whether the constraints should be enforced or not + */ + void setActive(bool yesno) + { isActive_ = yesno; } + +private: + bool isActive_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaseconstraintscontext.hh b/opm/models/discretization/common/fvbaseconstraintscontext.hh new file mode 100644 index 00000000000..0f991007847 --- /dev/null +++ b/opm/models/discretization/common/fvbaseconstraintscontext.hh @@ -0,0 +1,118 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseConstraintsContext + */ +#ifndef EWOMS_FV_BASE_CONSTRAINTS_CONTEXT_HH +#define EWOMS_FV_BASE_CONSTRAINTS_CONTEXT_HH + +#include "fvbaseproperties.hh" + +#include + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Represents all quantities which available for calculating constraints + */ +template +class FvBaseConstraintsContext +{ + using Problem = GetPropType; + using Model = GetPropType; + using ElementContext = GetPropType; + using GridView = GetPropType; + using Element = typename GridView::template Codim<0>::Entity; + + enum { dimWorld = GridView::dimensionworld }; + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + +public: + /*! + * \brief The constructor. + */ + explicit FvBaseConstraintsContext(const ElementContext& elemCtx) + : elemCtx_(elemCtx) + { } + + /*! + * \copydoc Opm::ElementContext::problem() + */ + const Problem& problem() const + { return elemCtx_.problem(); } + + /*! + * \copydoc Opm::ElementContext::model() + */ + const Model& model() const + { return elemCtx_.model(); } + + /*! + * \copydoc Opm::ElementContext::gridView() + */ + const GridView& gridView() const + { return elemCtx_.gridView(); } + + /*! + * \copydoc Opm::ElementContext::element() + */ + const Element& element() const + { return elemCtx_.element(); } + + /*! + * \copydoc Opm::ElementContext::numDof() + */ + int numDof(int timeIdx) const + { return elemCtx_.numDof(timeIdx); } + + /*! + * \copydoc Opm::ElementContext::numInteriorFaces() + */ + int numInteriorFaces(int timeIdx) const + { return elemCtx_.numInteriorFaces(timeIdx); } + + /*! + * \copydoc Opm::ElementContext::globalSpaceIndex + */ + int globalSpaceIndex(int dofIdx, int timeIdx) const + { return elemCtx_.globalSpaceIndex(dofIdx, timeIdx); } + + /*! + * \copydoc Opm::ElementContext::pos + */ + GlobalPosition pos(int dofIdx, int timeIdx) const + { return elemCtx_.pos(dofIdx, timeIdx); } + +protected: + const ElementContext& elemCtx_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbasediscretization.hh b/opm/models/discretization/common/fvbasediscretization.hh new file mode 100644 index 00000000000..2765164cf6e --- /dev/null +++ b/opm/models/discretization/common/fvbasediscretization.hh @@ -0,0 +1,1985 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseDiscretization + */ +#ifndef EWOMS_FV_BASE_DISCRETIZATION_HH +#define EWOMS_FV_BASE_DISCRETIZATION_HH + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Opm { +template +class FvBaseDiscretizationNoAdapt; +template +class FvBaseDiscretization; + +} // namespace Opm + +namespace Opm::Properties { + +//! Set the default type for the time manager +template +struct Simulator +{ using type = ::Opm::Simulator; }; + +//! Mapper for the grid view's vertices. +template +struct VertexMapper +{ using type = Dune::MultipleCodimMultipleGeomTypeMapper>; }; + +//! Mapper for the grid view's elements. +template +struct ElementMapper +{ using type = Dune::MultipleCodimMultipleGeomTypeMapper>; }; + +//! marks the border indices (required for the algebraic overlap stuff) +template +struct BorderListCreator +{ + using DofMapper = GetPropType; + using GridView = GetPropType; + +public: + using type = Linear::NullBorderListCreator; +}; + +template +struct DiscLocalResidual +{ using type = FvBaseLocalResidual; }; + +template +struct DiscIntensiveQuantities +{ using type = FvBaseIntensiveQuantities; }; + +template +struct DiscExtensiveQuantities +{ using type = FvBaseExtensiveQuantities; }; + +//! Calculates the gradient of any quantity given the index of a flux approximation point +template +struct GradientCalculator +{ using type = FvBaseGradientCalculator; }; + +/*! + * \brief A vector of quanties, each for one equation. + */ +template +struct EqVector +{ + using type = Dune::FieldVector, + getPropValue()>; +}; + +/*! + * \brief A vector for mass/energy rates. + * + * E.g. Neumann fluxes or source terms + */ +template +struct RateVector +{ using type = GetPropType; }; + +/*! + * \brief Type of object for specifying boundary conditions. + */ +template +struct BoundaryRateVector +{ using type = GetPropType; }; + +/*! + * \brief The class which represents constraints. + */ +template +struct Constraints +{ using type = FvBaseConstraints; }; + +/*! + * \brief The type for storing a residual for an element. + */ +template +struct ElementEqVector +{ using type = Dune::BlockVector>; }; + +/*! + * \brief The type for storing a residual for the whole grid. + */ +template +struct GlobalEqVector +{ using type = Dune::BlockVector>; }; + +/*! + * \brief An object representing a local set of primary variables. + */ +template +struct PrimaryVariables +{ using type = FvBasePrimaryVariables; }; + +/*! + * \brief The type of a solution for the whole grid at a fixed time. + */ +template +struct SolutionVector +{ using type = Dune::BlockVector>; }; + +/*! + * \brief The class representing intensive quantities. + * + * This should almost certainly be overloaded by the model... + */ +template +struct IntensiveQuantities +{ using type = FvBaseIntensiveQuantities; }; + +/*! + * \brief The element context + */ +template +struct ElementContext +{ using type = FvBaseElementContext; }; + +template +struct BoundaryContext +{ using type = FvBaseBoundaryContext; }; + +template +struct ConstraintsContext +{ using type = FvBaseConstraintsContext; }; + +/*! + * \brief The OpenMP threads manager + */ +template +struct ThreadManager +{ using type = ::Opm::ThreadManager; }; + +template +struct UseLinearizationLock +{ static constexpr bool value = true; }; + +/*! + * \brief Linearizer for the global system of equations. + */ +template +struct Linearizer +{ using type = FvBaseLinearizer; }; + +//! Set the format of the VTK output to ASCII by default +template +struct VtkOutputFormat +{ static constexpr int value = Dune::VTK::ascii; }; + +// disable constraints by default +template +struct EnableConstraints +{ static constexpr bool value = false; }; + +//! Set the history size of the time discretization to 2 (for implicit euler) +template +struct TimeDiscHistorySize +{ static constexpr int value = 2; }; + +//! Most models use extensive quantities for their storage term (so far, only the Stokes +//! model does), so we disable this by default. +template +struct ExtensiveStorageTerm +{ static constexpr bool value = false; }; + +// use volumetric residuals is default +template +struct UseVolumetricResidual +{ static constexpr bool value = true; }; + +//! eWoms is mainly targeted at research, so experimental features are enabled by +//! default. +template +struct EnableExperiments +{ static constexpr bool value = true; }; + +template +struct BaseDiscretizationType { using type = UndefinedProperty; }; + +#if !HAVE_DUNE_FEM +template +struct BaseDiscretizationType +{ using type = FvBaseDiscretizationNoAdapt; }; + +template +struct DiscreteFunction +{ + using BaseDiscretization = FvBaseDiscretization; + using type = typename BaseDiscretization::BlockVectorWrapper; +}; +#endif + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief The base class for the finite volume discretization schemes. + */ +template +class FvBaseDiscretization +{ + using Implementation = GetPropType; + using Discretization = GetPropType; + using Simulator = GetPropType; + using Grid = GetPropType; + using GridView = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementMapper = GetPropType; + using VertexMapper = GetPropType; + using DofMapper = GetPropType; + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using PrimaryVariables = GetPropType; + using Linearizer = GetPropType; + using ElementContext = GetPropType; + using BoundaryContext = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using GradientCalculator = GetPropType; + using Stencil = GetPropType; + using DiscBaseOutputModule = GetPropType; + using GridCommHandleFactory = GetPropType; + using NewtonMethod = GetPropType; + using ThreadManager = GetPropType; + + using LocalLinearizer = GetPropType; + using LocalResidual = GetPropType; + + enum { + numEq = getPropValue(), + historySize = getPropValue(), + }; + + using IntensiveQuantitiesVector = std::vector >; + + using Element = typename GridView::template Codim<0>::Entity; + using ElementIterator = typename GridView::template Codim<0>::Iterator; + + using Toolbox = MathToolbox; + using VectorBlock = Dune::FieldVector; + using EvalEqVector = Dune::FieldVector; + + using LocalEvalBlockVector = typename LocalResidual::LocalEvalBlockVector; + +public: + class BlockVectorWrapper + { + protected: + SolutionVector blockVector_; + public: + BlockVectorWrapper(const std::string&, const size_t size) + : blockVector_(size) + {} + + BlockVectorWrapper() = default; + + static BlockVectorWrapper serializationTestObject() + { + BlockVectorWrapper result("dummy", 3); + result.blockVector_[0] = 1.0; + result.blockVector_[1] = 2.0; + result.blockVector_[2] = 3.0; + + return result; + } + + SolutionVector& blockVector() + { return blockVector_; } + const SolutionVector& blockVector() const + { return blockVector_; } + + bool operator==(const BlockVectorWrapper& wrapper) const + { + return std::equal(this->blockVector_.begin(), this->blockVector_.end(), + wrapper.blockVector_.begin(), wrapper.blockVector_.end()); + } + + template + void serializeOp(Serializer& serializer) + { + serializer(blockVector_); + } + }; + +private: + using DiscreteFunctionSpace = GetPropType; + using DiscreteFunction = GetPropType; + + // copying a discretization object is not a good idea + FvBaseDiscretization(const FvBaseDiscretization& ); + +public: + // this constructor required to be explicitly specified because + // we've defined a constructor above which deletes all implicitly + // generated constructors in C++. + FvBaseDiscretization(Simulator& simulator) + : simulator_(simulator) + , gridView_(simulator.gridView()) + , elementMapper_(gridView_, Dune::mcmgElementLayout()) + , vertexMapper_(gridView_, Dune::mcmgVertexLayout()) + , newtonMethod_(simulator) + , localLinearizer_(ThreadManager::maxThreads()) + , linearizer_(new Linearizer()) + , enableGridAdaptation_(Parameters::Get() ) + , enableIntensiveQuantityCache_(Parameters::Get()) + , enableStorageCache_(Parameters::Get()) + , enableThermodynamicHints_(Parameters::Get()) + { + bool isEcfv = std::is_same >::value; + if (enableGridAdaptation_ && !isEcfv) + throw std::invalid_argument("Grid adaptation currently only works for the " + "element-centered finite volume discretization (is: " + +Dune::className()+")"); + + PrimaryVariables::init(); + size_t numDof = asImp_().numGridDof(); + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + if (storeIntensiveQuantities()) { + intensiveQuantityCache_[timeIdx].resize(numDof); + intensiveQuantityCacheUpToDate_[timeIdx].resize(numDof, /*value=*/false); + } + + if (enableStorageCache_) + storageCache_[timeIdx].resize(numDof); + } + + resizeAndResetIntensiveQuantitiesCache_(); + asImp_().registerOutputModules_(); + } + + ~FvBaseDiscretization() + { + // delete all output modules + auto modIt = outputModules_.begin(); + const auto& modEndIt = outputModules_.end(); + for (; modIt != modEndIt; ++modIt) + delete *modIt; + + delete linearizer_; + } + + /*! + * \brief Register all run-time parameters for the model. + */ + static void registerParameters() + { + Linearizer::registerParameters(); + LocalLinearizer::registerParameters(); + LocalResidual::registerParameters(); + GradientCalculator::registerParameters(); + IntensiveQuantities::registerParameters(); + ExtensiveQuantities::registerParameters(); + NewtonMethod::registerParameters(); + Linearizer::registerParameters(); + PrimaryVariables::registerParameters(); + // register runtime parameters of the output modules + VtkPrimaryVarsModule::registerParameters(); + + Parameters::Register + ("Enable adaptive grid refinement/coarsening"); + Parameters::Register + ("Global switch for turning on writing VTK files"); + Parameters::Register + ("Enable thermodynamic hints"); + Parameters::Register + ("Turn on caching of intensive quantities"); + Parameters::Register + ("Store previous storage terms and avoid re-calculating them."); + Parameters::Register + ("The directory to which result files are written"); + } + + /*! + * \brief Apply the initial conditions to the model. + */ + void finishInit() + { + // initialize the volume of the finite volumes to zero + size_t numDof = asImp_().numGridDof(); + dofTotalVolume_.resize(numDof); + std::fill(dofTotalVolume_.begin(), dofTotalVolume_.end(), 0.0); + + ElementContext elemCtx(simulator_); + gridTotalVolume_ = 0.0; + + // iterate through the grid and evaluate the initial condition + for (const auto& elem : elements(gridView_)) { + const bool isInteriorElement = elem.partitionType() == Dune::InteriorEntity; + // ignore everything which is not in the interior if the + // current process' piece of the grid + if (!isInteriorElement) + continue; + + // deal with the current element + elemCtx.updateStencil(elem); + const auto& stencil = elemCtx.stencil(/*timeIdx=*/0); + + // loop over all element vertices, i.e. sub control volumes + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); dofIdx++) { + // map the local degree of freedom index to the global one + unsigned globalIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + + Scalar dofVolume = stencil.subControlVolume(dofIdx).volume(); + dofTotalVolume_[globalIdx] += dofVolume; + if (isInteriorElement) + gridTotalVolume_ += dofVolume; + } + } + + // determine which DOFs should be considered to lie fully in the interior of the + // local process grid partition: those which do not have a non-zero volume + // before taking the peer processes into account... + isLocalDof_.resize(numDof); + for (unsigned dofIdx = 0; dofIdx < numDof; ++dofIdx) + isLocalDof_[dofIdx] = (dofTotalVolume_[dofIdx] != 0.0); + + // add the volumes of the DOFs on the process boundaries + const auto sumHandle = + GridCommHandleFactory::template sumHandle(dofTotalVolume_, + asImp_().dofMapper()); + gridView_.communicate(*sumHandle, + Dune::InteriorBorder_All_Interface, + Dune::ForwardCommunication); + + // sum up the volumes of the grid partitions + gridTotalVolume_ = gridView_.comm().sum(gridTotalVolume_); + + linearizer_->init(simulator_); + for (unsigned threadId = 0; threadId < ThreadManager::maxThreads(); ++threadId) + localLinearizer_[threadId].init(simulator_); + + resizeAndResetIntensiveQuantitiesCache_(); + if (storeIntensiveQuantities()) { + // invalidate all cached intensive quantities + for (unsigned timeIdx = 0; timeIdx < historySize; ++ timeIdx) + invalidateIntensiveQuantitiesCache(timeIdx); + } + + newtonMethod_.finishInit(); + } + + /*! + * \brief Returns whether the grid ought to be adapted to the solution during the simulation. + */ + bool enableGridAdaptation() const + { return enableGridAdaptation_; } + + /*! + * \brief Applies the initial solution for all degrees of freedom to which the model + * applies. + */ + void applyInitialSolution() + { + // first set the whole domain to zero + SolutionVector& uCur = asImp_().solution(/*timeIdx=*/0); + uCur = Scalar(0.0); + + ElementContext elemCtx(simulator_); + + // iterate through the grid and evaluate the initial condition + for (const auto& elem : elements(gridView_)) { + // ignore everything which is not in the interior if the + // current process' piece of the grid + if (elem.partitionType() != Dune::InteriorEntity) + continue; + + // deal with the current element + elemCtx.updateStencil(elem); + + // loop over all element vertices, i.e. sub control volumes + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); dofIdx++) + { + // map the local degree of freedom index to the global one + unsigned globalIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + + // let the problem do the dirty work of nailing down + // the initial solution. + simulator_.problem().initial(uCur[globalIdx], elemCtx, dofIdx, /*timeIdx=*/0); + asImp_().supplementInitialSolution_(uCur[globalIdx], elemCtx, dofIdx, /*timeIdx=*/0); + uCur[globalIdx].checkDefined(); + } + } + + // synchronize the ghost DOFs (if necessary) + asImp_().syncOverlap(); + + // also set the solutions of the "previous" time steps to the initial solution. + for (unsigned timeIdx = 1; timeIdx < historySize; ++timeIdx) + solution(timeIdx) = solution(/*timeIdx=*/0); + + simulator_.problem().initialSolutionApplied(); + +#ifndef NDEBUG + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + const auto& sol = solution(timeIdx); + for (unsigned dofIdx = 0; dofIdx < sol.size(); ++dofIdx) + sol[dofIdx].checkDefined(); + } +#endif // NDEBUG + } + + /*! + * \brief Allows to improve the performance by prefetching all data which is + * associated with a given element. + */ + void prefetch(const Element&) const + { + // do nothing by default + } + + /*! + * \brief Returns the newton method object + */ + NewtonMethod& newtonMethod() + { return newtonMethod_; } + + /*! + * \copydoc newtonMethod() + */ + const NewtonMethod& newtonMethod() const + { return newtonMethod_; } + + /*! + * \brief Return the thermodynamic hint for a entity on the grid at given time. + * + * The hint is defined as a IntensiveQuantities object which is supposed to be + * "close" to the IntensiveQuantities of the current solution. It can be used as a + * good starting point for non-linear solvers when having to solve non-linear + * relations while updating the intensive quantities. (This may yield a major + * performance boost depending on how the physical models require.) + * + * \attention If no up-to date intensive quantities are available, or if hints have been + * disabled, this method will return 0. + * + * \param globalIdx The global space index for the entity where a hint is requested. + * \param timeIdx The index used by the time discretization. + */ + const IntensiveQuantities* thermodynamicHint(unsigned globalIdx, unsigned timeIdx) const + { + if (!enableThermodynamicHints_) + return 0; + + // the intensive quantities cache doubles as thermodynamic hint + return cachedIntensiveQuantities(globalIdx, timeIdx); + } + + /*! + * \brief Return the cached intensive quantities for a entity on the + * grid at given time. + * + * \attention If no up-to date intensive quantities are available, + * this method will return 0. + * + * \param globalIdx The global space index for the entity where a + * hint is requested. + * \param timeIdx The index used by the time discretization. + */ + const IntensiveQuantities* cachedIntensiveQuantities(unsigned globalIdx, unsigned timeIdx) const + { + if (!enableIntensiveQuantityCache_ || !intensiveQuantityCacheUpToDate_[timeIdx][globalIdx]) { + return nullptr; + } + + // With the storage cache enabled, usually only the + // intensive quantities for the most recent time step are + // cached. However, this may be false for some Problem + // variants, so we should check if the cache exists for + // the timeIdx in question. + if (timeIdx > 0 && enableStorageCache_ && intensiveQuantityCache_[timeIdx].empty()) { + return nullptr; + } + + return &intensiveQuantityCache_[timeIdx][globalIdx]; + } + + /*! + * \brief Update the intensive quantity cache for a entity on the grid at given time. + * + * \param intQuants The IntensiveQuantities object hint for a given degree of freedom. + * \param globalIdx The global space index for the entity where a + * hint is to be set. + * \param timeIdx The index used by the time discretization. + */ + void updateCachedIntensiveQuantities(const IntensiveQuantities& intQuants, + unsigned globalIdx, + unsigned timeIdx) const + { + if (!storeIntensiveQuantities()) + return; + + intensiveQuantityCache_[timeIdx][globalIdx] = intQuants; + intensiveQuantityCacheUpToDate_[timeIdx][globalIdx] = 1; + } + + /*! + * \brief Invalidate the cache for a given intensive quantities object. + * + * \param globalIdx The global space index for the entity where a + * hint is to be set. + * \param timeIdx The index used by the time discretization. + */ + void setIntensiveQuantitiesCacheEntryValidity(unsigned globalIdx, + unsigned timeIdx, + bool newValue) const + { + if (!storeIntensiveQuantities()) + return; + + intensiveQuantityCacheUpToDate_[timeIdx][globalIdx] = newValue ? 1 : 0; + } + + /*! + * \brief Invalidate the whole intensive quantity cache for time index. + * + * \param timeIdx The index used by the time discretization. + */ + void invalidateIntensiveQuantitiesCache(unsigned timeIdx) const + { + if (storeIntensiveQuantities()) { + std::fill(intensiveQuantityCacheUpToDate_[timeIdx].begin(), + intensiveQuantityCacheUpToDate_[timeIdx].end(), + /*value=*/0); + } + } + + void invalidateAndUpdateIntensiveQuantities(unsigned timeIdx) const + { + invalidateIntensiveQuantitiesCache(timeIdx); + + // loop over all elements... + ThreadedEntityIterator threadedElemIt(gridView_); +#ifdef _OPENMP +#pragma omp parallel +#endif + { + ElementContext elemCtx(simulator_); + ElementIterator elemIt = threadedElemIt.beginParallel(); + for (; !threadedElemIt.isFinished(elemIt); elemIt = threadedElemIt.increment()) { + const Element& elem = *elemIt; + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(timeIdx); + } + } + } + + template + void invalidateAndUpdateIntensiveQuantities(unsigned timeIdx, const GridViewType& gridView) const + { + // loop over all elements... + ThreadedEntityIterator threadedElemIt(gridView); +#ifdef _OPENMP +#pragma omp parallel +#endif + { + + ElementContext elemCtx(simulator_); + auto elemIt = threadedElemIt.beginParallel(); + for (; !threadedElemIt.isFinished(elemIt); elemIt = threadedElemIt.increment()) { + if (elemIt->partitionType() != Dune::InteriorEntity) { + continue; + } + const Element& elem = *elemIt; + elemCtx.updatePrimaryStencil(elem); + // Mark cache for this element as invalid. + const std::size_t numPrimaryDof = elemCtx.numPrimaryDof(timeIdx); + for (unsigned dofIdx = 0; dofIdx < numPrimaryDof; ++dofIdx) { + const unsigned globalIndex = elemCtx.globalSpaceIndex(dofIdx, timeIdx); + setIntensiveQuantitiesCacheEntryValidity(globalIndex, timeIdx, false); + } + // Update for this element. + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + } + } + } + + /*! + * \brief Move the intensive quantities for a given time index to the back. + * + * This method should only be called by the time discretization. + * + * \param numSlots The number of time step slots for which the + * hints should be shifted. + */ + void shiftIntensiveQuantityCache(unsigned numSlots = 1) + { + if (!storeIntensiveQuantities()) + return; + + if (enableStorageCache() && simulator_.problem().recycleFirstIterationStorage()) { + // If the storage term is cached, the intensive quantities of the previous + // time steps do not need to be accessed, and we can thus spare ourselves to + // copy the objects for the intensive quantities. + // However, if the storage term at the start of the timestep cannot be deduced + // from the primary variables, we must calculate it from the old intensive + // quantities, and need to shift them. + return; + } + + assert(numSlots > 0); + + for (unsigned timeIdx = 0; timeIdx < historySize - numSlots; ++ timeIdx) { + intensiveQuantityCache_[timeIdx + numSlots] = intensiveQuantityCache_[timeIdx]; + intensiveQuantityCacheUpToDate_[timeIdx + numSlots] = intensiveQuantityCacheUpToDate_[timeIdx]; + } + + // the cache for the most recent time indices do not need to be invalidated + // because the solution for them did not change (TODO: that assumes that there is + // no post-processing of the solution after a time step! fix it?) + } + + /*! + * \brief Returns true iff the storage term is cached. + * + * Be aware that calling the *CachedStorage() methods if the storage cache is + * disabled will crash the program. + */ + bool enableStorageCache() const + { return enableStorageCache_; } + + /*! + * \brief Set the value of enable storage cache + * + * Be aware that calling the *CachedStorage() methods if the storage cache is + * disabled will crash the program. + */ + void setEnableStorageCache(bool enableStorageCache) + { enableStorageCache_= enableStorageCache; } + + /*! + * \brief Retrieve an entry of the cache for the storage term. + * + * This is supposed to represent a DOF's total amount of conservation quantities per + * volume unit at a given time. The user is responsible for making sure that the + * value of this is correct and that it can be used before this method is called. + * + * \param globalDofIdx The index of the relevant degree of freedom in a grid-global vector + * \param timeIdx The relevant index for the time discretization + */ + const EqVector& cachedStorage(unsigned globalIdx, unsigned timeIdx) const + { + assert(enableStorageCache_); + return storageCache_[timeIdx][globalIdx]; + } + + /*! + * \brief Set an entry of the cache for the storage term. + * + * This is supposed to represent a DOF's total amount of conservation quantities per + * volume unit at a given time. The user is responsible for making sure that the + * storage cache is enabled before this method is called. + * + * \param globalDofIdx The index of the relevant degree of freedom in a grid-global vector + * \param timeIdx The relevant index for the time discretization + * \param value The new value of the cache for the storage term + */ + void updateCachedStorage(unsigned globalIdx, unsigned timeIdx, const EqVector& value) const + { + assert(enableStorageCache_); + storageCache_[timeIdx][globalIdx] = value; + } + + /*! + * \brief Compute the global residual for an arbitrary solution + * vector. + * + * \param dest Stores the result + * \param u The solution for which the residual ought to be calculated + */ + Scalar globalResidual(GlobalEqVector& dest, + const SolutionVector& u) const + { + SolutionVector tmp(asImp_().solution(/*timeIdx=*/0)); + mutableSolution(/*timeIdx=*/0) = u; + Scalar res = asImp_().globalResidual(dest); + mutableSolution(/*timeIdx=*/0) = tmp; + return res; + } + + /*! + * \brief Compute the global residual for the current solution + * vector. + * + * \param dest Stores the result + */ + Scalar globalResidual(GlobalEqVector& dest) const + { + dest = 0; + + std::mutex mutex; + ThreadedEntityIterator threadedElemIt(gridView_); +#ifdef _OPENMP +#pragma omp parallel +#endif + { + // Attention: the variables below are thread specific and thus cannot be + // moved in front of the #pragma! + unsigned threadId = ThreadManager::threadId(); + ElementContext elemCtx(simulator_); + ElementIterator elemIt = threadedElemIt.beginParallel(); + LocalEvalBlockVector residual, storageTerm; + + for (; !threadedElemIt.isFinished(elemIt); elemIt = threadedElemIt.increment()) { + const Element& elem = *elemIt; + if (elem.partitionType() != Dune::InteriorEntity) + continue; + + elemCtx.updateAll(elem); + residual.resize(elemCtx.numDof(/*timeIdx=*/0)); + storageTerm.resize(elemCtx.numPrimaryDof(/*timeIdx=*/0)); + asImp_().localResidual(threadId).eval(residual, elemCtx); + + size_t numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + mutex.lock(); + for (unsigned dofIdx = 0; dofIdx < numPrimaryDof; ++dofIdx) { + unsigned globalI = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + dest[globalI][eqIdx] += Toolbox::value(residual[dofIdx][eqIdx]); + } + mutex.unlock(); + } + } + + // add up the residuals on the process borders + const auto sumHandle = + GridCommHandleFactory::template sumHandle(dest, asImp_().dofMapper()); + gridView_.communicate(*sumHandle, + Dune::InteriorBorder_InteriorBorder_Interface, + Dune::ForwardCommunication); + + // calculate the square norm of the residual. this is not + // entirely correct, since the residual for the finite volumes + // which are on the boundary are counted once for every + // process. As often in life: shit happens (, we don't care)... + Scalar result2 = dest.two_norm2(); + result2 = asImp_().gridView().comm().sum(result2); + + return std::sqrt(result2); + } + + /*! + * \brief Compute the integral over the domain of the storage + * terms of all conservation quantities. + * + * \copydetails Doxygen::storageParam + */ + void globalStorage(EqVector& storage, unsigned timeIdx = 0) const + { + storage = 0; + + std::mutex mutex; + ThreadedEntityIterator threadedElemIt(gridView()); +#ifdef _OPENMP +#pragma omp parallel +#endif + { + // Attention: the variables below are thread specific and thus cannot be + // moved in front of the #pragma! + unsigned threadId = ThreadManager::threadId(); + ElementContext elemCtx(simulator_); + ElementIterator elemIt = threadedElemIt.beginParallel(); + LocalEvalBlockVector elemStorage; + + // in this method, we need to disable the storage cache because we want to + // evaluate the storage term for other time indices than the most recent one + elemCtx.setEnableStorageCache(false); + + for (; !threadedElemIt.isFinished(elemIt); elemIt = threadedElemIt.increment()) { + const Element& elem = *elemIt; + if (elem.partitionType() != Dune::InteriorEntity) + continue; // ignore ghost and overlap elements + + elemCtx.updateStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(timeIdx); + + size_t numPrimaryDof = elemCtx.numPrimaryDof(timeIdx); + elemStorage.resize(numPrimaryDof); + + localResidual(threadId).evalStorage(elemStorage, elemCtx, timeIdx); + + mutex.lock(); + for (unsigned dofIdx = 0; dofIdx < numPrimaryDof; ++dofIdx) + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) + storage[eqIdx] += Toolbox::value(elemStorage[dofIdx][eqIdx]); + mutex.unlock(); + } + } + + storage = gridView_.comm().sum(storage); + } + + /*! + * \brief Ensure that the difference between the storage terms of the last and of the + * current time step is consistent with the source and boundary terms. + * + * This method is purely intented for debugging purposes. If the program is compiled + * with optimizations enabled, it becomes a no-op. + */ + void checkConservativeness([[maybe_unused]] Scalar tolerance = -1, + [[maybe_unused]] bool verbose=false) const + { +#ifndef NDEBUG + Scalar totalBoundaryArea(0.0); + Scalar totalVolume(0.0); + EvalEqVector totalRate(0.0); + + // take the newton tolerance times the total volume of the grid if we're not + // given an explicit tolerance... + if (tolerance <= 0) { + tolerance = + simulator_.model().newtonMethod().tolerance() + * simulator_.model().gridTotalVolume() + * 1000; + } + + // we assume the implicit Euler time discretization for now... + assert(historySize == 2); + + EqVector storageBeginTimeStep(0.0); + globalStorage(storageBeginTimeStep, /*timeIdx=*/1); + + EqVector storageEndTimeStep(0.0); + globalStorage(storageEndTimeStep, /*timeIdx=*/0); + + // calculate the rate at the boundary and the source rate + ElementContext elemCtx(simulator_); + elemCtx.setEnableStorageCache(false); + auto eIt = simulator_.gridView().template begin(); + const auto& elemEndIt = simulator_.gridView().template end(); + for (; eIt != elemEndIt; ++eIt) { + if (eIt->partitionType() != Dune::InteriorEntity) + continue; // ignore ghost and overlap elements + + elemCtx.updateAll(*eIt); + + // handle the boundary terms + if (elemCtx.onBoundary()) { + BoundaryContext boundaryCtx(elemCtx); + + for (unsigned faceIdx = 0; faceIdx < boundaryCtx.numBoundaryFaces(/*timeIdx=*/0); ++faceIdx) { + BoundaryRateVector values; + simulator_.problem().boundary(values, + boundaryCtx, + faceIdx, + /*timeIdx=*/0); + Valgrind::CheckDefined(values); + + unsigned dofIdx = boundaryCtx.interiorScvIndex(faceIdx, /*timeIdx=*/0); + const auto& insideIntQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0); + + Scalar bfArea = + boundaryCtx.boundarySegmentArea(faceIdx, /*timeIdx=*/0) + * insideIntQuants.extrusionFactor(); + + for (unsigned i = 0; i < values.size(); ++i) + values[i] *= bfArea; + + totalBoundaryArea += bfArea; + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) + totalRate[eqIdx] += values[eqIdx]; + } + } + + // deal with the source terms + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++ dofIdx) { + RateVector values; + simulator_.problem().source(values, + elemCtx, + dofIdx, + /*timeIdx=*/0); + Valgrind::CheckDefined(values); + + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0); + Scalar dofVolume = + elemCtx.dofVolume(dofIdx, /*timeIdx=*/0) + * intQuants.extrusionFactor(); + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + totalRate[eqIdx] += -dofVolume*Toolbox::value(values[eqIdx]); + totalVolume += dofVolume; + } + } + + // summarize everything over all processes + const auto& comm = simulator_.gridView().comm(); + totalRate = comm.sum(totalRate); + totalBoundaryArea = comm.sum(totalBoundaryArea); + totalVolume = comm.sum(totalVolume); + + if (comm.rank() == 0) { + EqVector storageRate = storageBeginTimeStep; + storageRate -= storageEndTimeStep; + storageRate /= simulator_.timeStepSize(); + if (verbose) { + std::cout << "storage at beginning of time step: " << storageBeginTimeStep << "\n"; + std::cout << "storage at end of time step: " << storageEndTimeStep << "\n"; + std::cout << "rate based on storage terms: " << storageRate << "\n"; + std::cout << "rate based on source and boundary terms: " << totalRate << "\n"; + std::cout << "difference in rates: "; + for (unsigned eqIdx = 0; eqIdx < EqVector::dimension; ++eqIdx) + std::cout << (storageRate[eqIdx] - Toolbox::value(totalRate[eqIdx])) << " "; + std::cout << "\n"; + } + for (unsigned eqIdx = 0; eqIdx < EqVector::dimension; ++eqIdx) { + Scalar eps = + (std::abs(storageRate[eqIdx]) + Toolbox::value(totalRate[eqIdx]))*tolerance; + eps = std::max(tolerance, eps); + assert(std::abs(storageRate[eqIdx] - Toolbox::value(totalRate[eqIdx])) <= eps); + } + } +#endif // NDEBUG + } + + /*! + * \brief Returns the volume \f$\mathrm{[m^3]}\f$ of a given control volume. + * + * \param globalIdx The global index of the degree of freedom + */ + Scalar dofTotalVolume(unsigned globalIdx) const + { return dofTotalVolume_[globalIdx]; } + + /*! + * \brief Returns if the overlap of the volume ofa degree of freedom is non-zero. + * + * \param globalIdx The global index of the degree of freedom + */ + bool isLocalDof(unsigned globalIdx) const + { return isLocalDof_[globalIdx]; } + + /*! + * \brief Returns the volume \f$\mathrm{[m^3]}\f$ of the whole grid which represents + * the spatial domain. + */ + Scalar gridTotalVolume() const + { return gridTotalVolume_; } + + /*! + * \brief Reference to the solution at a given history index as a block vector. + * + * \param timeIdx The index of the solution used by the time discretization. + */ + const SolutionVector& solution(unsigned timeIdx) const + { return solution_[timeIdx]->blockVector(); } + + /*! + * \copydoc solution(int) const + */ + SolutionVector& solution(unsigned timeIdx) + { return solution_[timeIdx]->blockVector(); } + + protected: + /*! + * \copydoc solution(int) const + */ + SolutionVector& mutableSolution(unsigned timeIdx) const + { return solution_[timeIdx]->blockVector(); } + + public: + /*! + * \brief Returns the operator linearizer for the global jacobian of + * the problem. + */ + const Linearizer& linearizer() const + { return *linearizer_; } + + /*! + * \brief Returns the object which linearizes the global system of equations at the + * current solution. + */ + Linearizer& linearizer() + { return *linearizer_; } + + /*! + * \brief Returns the local jacobian which calculates the local + * stiffness matrix for an arbitrary element. + * + * The local stiffness matrices of the element are used by + * the jacobian linearizer to produce a global linerization of the + * problem. + */ + const LocalLinearizer& localLinearizer(unsigned openMpThreadId) const + { return localLinearizer_[openMpThreadId]; } + /*! + * \copydoc localLinearizer() const + */ + LocalLinearizer& localLinearizer(unsigned openMpThreadId) + { return localLinearizer_[openMpThreadId]; } + + /*! + * \brief Returns the object to calculate the local residual function. + */ + const LocalResidual& localResidual(unsigned openMpThreadId) const + { return asImp_().localLinearizer(openMpThreadId).localResidual(); } + /*! + * \copydoc localResidual() const + */ + LocalResidual& localResidual(unsigned openMpThreadId) + { return asImp_().localLinearizer(openMpThreadId).localResidual(); } + + /*! + * \brief Returns the relative weight of a primary variable for + * calculating relative errors. + * + * \param globalDofIdx The global index of the degree of freedom + * \param pvIdx The index of the primary variable + */ + Scalar primaryVarWeight(unsigned globalDofIdx, unsigned pvIdx) const + { + Scalar absPv = std::abs(asImp_().solution(/*timeIdx=*/1)[globalDofIdx][pvIdx]); + return 1.0/std::max(absPv, 1.0); + } + + /*! + * \brief Returns the relative weight of an equation + * + * \param globalVertexIdx The global index of the vertex + * \param eqIdx The index of the equation + */ + Scalar eqWeight(unsigned, unsigned) const + { return 1.0; } + + /*! + * \brief Returns the relative error between two vectors of + * primary variables. + * + * \param vertexIdx The global index of the control volume's + * associated vertex + * \param pv1 The first vector of primary variables + * \param pv2 The second vector of primary variables + */ + Scalar relativeDofError(unsigned vertexIdx, + const PrimaryVariables& pv1, + const PrimaryVariables& pv2) const + { + Scalar result = 0.0; + for (unsigned j = 0; j < numEq; ++j) { + Scalar weight = asImp_().primaryVarWeight(vertexIdx, j); + Scalar eqErr = std::abs((pv1[j] - pv2[j])*weight); + //Scalar eqErr = std::abs(pv1[j] - pv2[j]); + //eqErr *= std::max(1.0, std::abs(pv1[j] + pv2[j])/2); + + result = std::max(result, eqErr); + } + return result; + } + + /*! + * \brief Try to progress the model to the next timestep. + * + * \param solver The non-linear solver + */ + bool update() + { + TimerGuard prePostProcessGuard(prePostProcessTimer_); + +#ifndef NDEBUG + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + // Make sure that the primary variables are defined. Note that because of padding + // bytes, we can't just simply ask valgrind to check the whole solution vectors + // for definedness... + for (size_t i = 0; i < asImp_().solution(/*timeIdx=*/0).size(); ++i) { + asImp_().solution(timeIdx)[i].checkDefined(); + } + } +#endif // NDEBUG + + // make sure all timers are prestine + prePostProcessTimer_.halt(); + linearizeTimer_.halt(); + solveTimer_.halt(); + updateTimer_.halt(); + + prePostProcessTimer_.start(); + asImp_().updateBegin(); + prePostProcessTimer_.stop(); + + bool converged = false; + + try { + converged = newtonMethod_.apply(); + } + catch(...) { + prePostProcessTimer_ += newtonMethod_.prePostProcessTimer(); + linearizeTimer_ += newtonMethod_.linearizeTimer(); + solveTimer_ += newtonMethod_.solveTimer(); + updateTimer_ += newtonMethod_.updateTimer(); + + throw; + } + +#ifndef NDEBUG + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + // Make sure that the primary variables are defined. Note that because of padding + // bytes, we can't just simply ask valgrind to check the whole solution vectors + // for definedness... + for (size_t i = 0; i < asImp_().solution(/*timeIdx=*/0).size(); ++i) { + asImp_().solution(timeIdx)[i].checkDefined(); + } + } +#endif // NDEBUG + + prePostProcessTimer_ += newtonMethod_.prePostProcessTimer(); + linearizeTimer_ += newtonMethod_.linearizeTimer(); + solveTimer_ += newtonMethod_.solveTimer(); + updateTimer_ += newtonMethod_.updateTimer(); + + prePostProcessTimer_.start(); + if (converged) + asImp_().updateSuccessful(); + else + asImp_().updateFailed(); + prePostProcessTimer_.stop(); + +#ifndef NDEBUG + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + // Make sure that the primary variables are defined. Note that because of padding + // bytes, we can't just simply ask valgrind to check the whole solution vectors + // for definedness... + for (size_t i = 0; i < asImp_().solution(/*timeIdx=*/0).size(); ++i) { + asImp_().solution(timeIdx)[i].checkDefined(); + } + } +#endif // NDEBUG + + return converged; + } + + /*! + * \brief Syncronize the values of the primary variables on the + * degrees of freedom that overlap with the neighboring + * processes. + * + * By default, this method does nothing... + */ + void syncOverlap() + { } + + /*! + * \brief Called by the update() method before it tries to + * apply the newton method. This is primary a hook + * which the actual model can overload. + */ + void updateBegin() + { } + + /*! + * \brief Called by the update() method if it was + * successful. + */ + void updateSuccessful() + { } + + /*! + * \brief Called by the update() method when the grid should be refined. + */ + void adaptGrid() + { + throw std::invalid_argument("Grid adaptation need to be implemented for " + "specific settings of grid and function spaces"); + } + + /*! + * \brief Called by the update() method if it was + * unsuccessful. This is primary a hook which the actual + * model can overload. + */ + void updateFailed() + { + // Reset the current solution to the one of the + // previous time step so that we can start the next + // update at a physically meaningful solution. + solution(/*timeIdx=*/0) = solution(/*timeIdx=*/1); + invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); + +#ifndef NDEBUG + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + // Make sure that the primary variables are defined. Note that because of padding + // bytes, we can't just simply ask valgrind to check the whole solution vectors + // for definedness... + for (size_t i = 0; i < asImp_().solution(/*timeIdx=*/0).size(); ++i) + asImp_().solution(timeIdx)[i].checkDefined(); + } +#endif // NDEBUG + } + + /*! + * \brief Called by the problem if a time integration was + * successful, post processing of the solution is done and + * the result has been written to disk. + * + * This should prepare the model for the next time integration. + */ + void advanceTimeLevel() + { + // at this point we can adapt the grid + if (this->enableGridAdaptation_) { + asImp_().adaptGrid(); + } + + // make the current solution the previous one. + solution(/*timeIdx=*/1) = solution(/*timeIdx=*/0); + + // shift the intensive quantities cache by one position in the + // history + asImp_().shiftIntensiveQuantityCache(/*numSlots=*/1); + } + + /*! + * \brief Serializes the current state of the model. + * + * \tparam Restarter The type of the serializer class + * + * \param res The serializer object + */ + template + void serialize(Restarter&) + { + throw std::runtime_error("Not implemented: The discretization chosen for this problem " + "does not support restart files. (serialize() method unimplemented)"); + } + + /*! + * \brief Deserializes the state of the model. + * + * \tparam Restarter The type of the serializer class + * + * \param res The serializer object + */ + template + void deserialize(Restarter&) + { + throw std::runtime_error("Not implemented: The discretization chosen for this problem " + "does not support restart files. (deserialize() method unimplemented)"); + } + + /*! + * \brief Write the current solution for a degree of freedom to a + * restart file. + * + * \param outstream The stream into which the vertex data should + * be serialized to + * \param dof The Dune entity which's data should be serialized + */ + template + void serializeEntity(std::ostream& outstream, + const DofEntity& dof) + { + unsigned dofIdx = static_cast(asImp_().dofMapper().index(dof)); + + // write phase state + if (!outstream.good()) { + throw std::runtime_error("Could not serialize degree of freedom " + +std::to_string(dofIdx)); + } + + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + outstream << solution(/*timeIdx=*/0)[dofIdx][eqIdx] << " "; + } + } + + /*! + * \brief Reads the current solution variables for a degree of + * freedom from a restart file. + * + * \param instream The stream from which the vertex data should + * be deserialized from + * \param dof The Dune entity which's data should be deserialized + */ + template + void deserializeEntity(std::istream& instream, + const DofEntity& dof) + { + unsigned dofIdx = static_cast(asImp_().dofMapper().index(dof)); + + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + if (!instream.good()) + throw std::runtime_error("Could not deserialize degree of freedom " + +std::to_string(dofIdx)); + instream >> solution(/*timeIdx=*/0)[dofIdx][eqIdx]; + } + } + + /*! + * \brief Returns the number of degrees of freedom (DOFs) for the computational grid + */ + size_t numGridDof() const + { throw std::logic_error("The discretization class must implement the numGridDof() method!"); } + + /*! + * \brief Returns the number of degrees of freedom (DOFs) of the auxiliary equations + */ + size_t numAuxiliaryDof() const + { + size_t result = 0; + auto auxModIt = auxEqModules_.begin(); + const auto& auxModEndIt = auxEqModules_.end(); + for (; auxModIt != auxModEndIt; ++auxModIt) + result += (*auxModIt)->numDofs(); + + return result; + } + + /*! + * \brief Returns the total number of degrees of freedom (i.e., grid plux auxiliary DOFs) + */ + size_t numTotalDof() const + { return asImp_().numGridDof() + numAuxiliaryDof(); } + + /*! + * \brief Mapper to convert the Dune entities of the + * discretization's degrees of freedoms are to indices. + */ + const DofMapper& dofMapper() const + { throw std::logic_error("The discretization class must implement the dofMapper() method!"); } + + /*! + * \brief Returns the mapper for vertices to indices. + */ + const VertexMapper& vertexMapper() const + { return vertexMapper_; } + + /*! + * \brief Returns the mapper for elements to indices. + */ + const ElementMapper& elementMapper() const + { return elementMapper_; } + + /*! + * \brief Resets the Jacobian matrix linearizer, so that the + * boundary types can be altered. + */ + void resetLinearizer () + { + delete linearizer_; + linearizer_ = new Linearizer; + linearizer_->init(simulator_); + } + + /*! + * \brief Returns a string of discretization's human-readable name + */ + static std::string discretizationName() + { return ""; } + + /*! + * \brief Given an primary variable index, return a human readable name. + * + * \param pvIdx The index of the primary variable of interest. + */ + std::string primaryVarName(unsigned pvIdx) const + { + std::ostringstream oss; + oss << "primary variable_" << pvIdx; + return oss.str(); + } + + /*! + * \brief Given an equation index, return a human readable name. + * + * \param eqIdx The index of the conservation equation of interest. + */ + std::string eqName(unsigned eqIdx) const + { + std::ostringstream oss; + oss << "equation_" << eqIdx; + return oss.str(); + } + + /*! + * \brief Update the weights of all primary variables within an + * element given the complete set of intensive quantities + * + * \copydetails Doxygen::ecfvElemCtxParam + */ + void updatePVWeights(const ElementContext&) const + { } + + /*! + * \brief Add an module for writing visualization output after a timestep. + */ + void addOutputModule(BaseOutputModule* newModule) + { outputModules_.push_back(newModule); } + + /*! + * \brief Add the vector fields for analysing the convergence of + * the newton method to the a VTK writer. + * + * \param writer The writer object to which the fields should be added. + * \param u The solution function + * \param deltaU The delta of the solution function before and after the Newton update + */ + template + void addConvergenceVtkFields(VtkMultiWriter& writer, + const SolutionVector& u, + const GlobalEqVector& deltaU) const + { + using ScalarBuffer = std::vector; + + GlobalEqVector globalResid(u.size()); + asImp_().globalResidual(globalResid, u); + + // create the required scalar fields + size_t numGridDof = asImp_().numGridDof(); + + // global defect of the two auxiliary equations + ScalarBuffer* def[numEq]; + ScalarBuffer* delta[numEq]; + ScalarBuffer* priVars[numEq]; + ScalarBuffer* priVarWeight[numEq]; + ScalarBuffer* relError = writer.allocateManagedScalarBuffer(numGridDof); + ScalarBuffer* normalizedRelError = writer.allocateManagedScalarBuffer(numGridDof); + for (unsigned pvIdx = 0; pvIdx < numEq; ++pvIdx) { + priVars[pvIdx] = writer.allocateManagedScalarBuffer(numGridDof); + priVarWeight[pvIdx] = writer.allocateManagedScalarBuffer(numGridDof); + delta[pvIdx] = writer.allocateManagedScalarBuffer(numGridDof); + def[pvIdx] = writer.allocateManagedScalarBuffer(numGridDof); + } + + Scalar minRelErr = 1e30; + Scalar maxRelErr = -1e30; + for (unsigned globalIdx = 0; globalIdx < numGridDof; ++ globalIdx) { + for (unsigned pvIdx = 0; pvIdx < numEq; ++pvIdx) { + (*priVars[pvIdx])[globalIdx] = u[globalIdx][pvIdx]; + (*priVarWeight[pvIdx])[globalIdx] = asImp_().primaryVarWeight(globalIdx, pvIdx); + (*delta[pvIdx])[globalIdx] = - deltaU[globalIdx][pvIdx]; + (*def[pvIdx])[globalIdx] = globalResid[globalIdx][pvIdx]; + } + + PrimaryVariables uOld(u[globalIdx]); + PrimaryVariables uNew(uOld); + uNew -= deltaU[globalIdx]; + + Scalar err = asImp_().relativeDofError(globalIdx, uOld, uNew); + (*relError)[globalIdx] = err; + (*normalizedRelError)[globalIdx] = err; + minRelErr = std::min(err, minRelErr); + maxRelErr = std::max(err, maxRelErr); + } + + // do the normalization of the relative error + Scalar alpha = std::max(Scalar{1e-20}, + std::max(std::abs(maxRelErr), + std::abs(minRelErr))); + for (unsigned globalIdx = 0; globalIdx < numGridDof; ++ globalIdx) + (*normalizedRelError)[globalIdx] /= alpha; + + DiscBaseOutputModule::attachScalarDofData_(writer, *relError, "relative error"); + DiscBaseOutputModule::attachScalarDofData_(writer, *normalizedRelError, "normalized relative error"); + + for (unsigned i = 0; i < numEq; ++i) { + std::ostringstream oss; + oss.str(""); oss << "priVar_" << asImp_().primaryVarName(i); + DiscBaseOutputModule::attachScalarDofData_(writer, + *priVars[i], + oss.str()); + + oss.str(""); oss << "delta_" << asImp_().primaryVarName(i); + DiscBaseOutputModule::attachScalarDofData_(writer, + *delta[i], + oss.str()); + + oss.str(""); oss << "weight_" << asImp_().primaryVarName(i); + DiscBaseOutputModule::attachScalarDofData_(writer, + *priVarWeight[i], + oss.str()); + + oss.str(""); oss << "defect_" << asImp_().eqName(i); + DiscBaseOutputModule::attachScalarDofData_(writer, + *def[i], + oss.str()); + } + + asImp_().prepareOutputFields(); + asImp_().appendOutputFields(writer); + } + + /*! + * \brief Prepare the quantities relevant for the current solution + * to be appended to the output writers. + */ + void prepareOutputFields() const + { + bool needFullContextUpdate = false; + auto modIt = outputModules_.begin(); + const auto& modEndIt = outputModules_.end(); + for (; modIt != modEndIt; ++modIt) { + (*modIt)->allocBuffers(); + needFullContextUpdate = needFullContextUpdate || (*modIt)->needExtensiveQuantities(); + } + + // iterate over grid + ThreadedEntityIterator threadedElemIt(gridView()); +#ifdef _OPENMP +#pragma omp parallel +#endif + { + ElementContext elemCtx(simulator_); + ElementIterator elemIt = threadedElemIt.beginParallel(); + for (; !threadedElemIt.isFinished(elemIt); elemIt = threadedElemIt.increment()) { + const auto& elem = *elemIt; + if (elem.partitionType() != Dune::InteriorEntity) + // ignore non-interior entities + continue; + + if (needFullContextUpdate) + elemCtx.updateAll(elem); + else { + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + } + + // we cannot reuse the "modIt" variable here because the code here might + // be threaded and "modIt" is is the same for all threads, i.e., if a + // given thread modifies it, the changes affect all threads. + auto modIt2 = outputModules_.begin(); + for (; modIt2 != modEndIt; ++modIt2) + (*modIt2)->processElement(elemCtx); + } + } + } + + /*! + * \brief Append the quantities relevant for the current solution + * to an output writer. + */ + void appendOutputFields(BaseOutputWriter& writer) const + { + auto modIt = outputModules_.begin(); + const auto& modEndIt = outputModules_.end(); + for (; modIt != modEndIt; ++modIt) + (*modIt)->commitBuffers(writer); + } + + /*! + * \brief Reference to the grid view of the spatial domain. + */ + const GridView& gridView() const + { return gridView_; } + + /*! + * \brief Add a module for an auxiliary equation. + * + * This module can add additional degrees of freedom and additional off-diagonal + * elements, but the number of equations per DOF needs to be the same as for the + * "main" model. + * + * For example, auxiliary modules can be used to specify non-neighboring connections, + * well equations or model couplings via mortar DOFs. Auxiliary equations are + * completely optional, though. + */ + void addAuxiliaryModule(BaseAuxiliaryModule* auxMod) + { + auxMod->setDofOffset(numTotalDof()); + auxEqModules_.push_back(auxMod); + + // resize the solutions + if (enableGridAdaptation_ + && !std::is_same::value) + { + throw std::invalid_argument("Problems which require auxiliary modules cannot be used in" + " conjunction with dune-fem"); + } + + size_t numDof = numTotalDof(); + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) + solution(timeIdx).resize(numDof); + + auxMod->applyInitial(); + } + + /*! + * \brief Causes the list of auxiliary equations to be cleared + * + * Note that this method implies recreateMatrix() + */ + void clearAuxiliaryModules() + { + auxEqModules_.clear(); + linearizer_->eraseMatrix(); + newtonMethod_.eraseMatrix(); + } + + /*! + * \brief Returns the number of modules for auxiliary equations + */ + size_t numAuxiliaryModules() const + { return auxEqModules_.size(); } + + /*! + * \brief Returns a given module for auxiliary equations + */ + BaseAuxiliaryModule* auxiliaryModule(unsigned auxEqModIdx) + { return auxEqModules_[auxEqModIdx]; } + + /*! + * \brief Returns a given module for auxiliary equations + */ + const BaseAuxiliaryModule* auxiliaryModule(unsigned auxEqModIdx) const + { return auxEqModules_[auxEqModIdx]; } + + /*! + * \brief Returns true if the cache for intensive quantities is enabled + */ + bool storeIntensiveQuantities() const + { return enableIntensiveQuantityCache_ || enableThermodynamicHints_; } + + const Timer& prePostProcessTimer() const + { return prePostProcessTimer_; } + + const Timer& linearizeTimer() const + { return linearizeTimer_; } + + const Timer& solveTimer() const + { return solveTimer_; } + + const Timer& updateTimer() const + { return updateTimer_; } + + template + void serializeOp(Serializer& serializer) + { + using BaseDiscretization = GetPropType; + using Helper = typename BaseDiscretization::template SerializeHelper; + Helper::serializeOp(serializer, solution_); + } + + bool operator==(const FvBaseDiscretization& rhs) const + { + return std::equal(this->solution_.begin(), this->solution_.end(), + rhs.solution_.begin(), rhs.solution_.end(), + [](const auto& x, const auto& y) + { + return *x == *y; + }); + } + +protected: + void resizeAndResetIntensiveQuantitiesCache_() + { + // allocate the storage cache + if (enableStorageCache()) { + size_t numDof = asImp_().numGridDof(); + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + storageCache_[timeIdx].resize(numDof); + } + } + + // allocate the intensive quantities cache + if (storeIntensiveQuantities()) { + size_t numDof = asImp_().numGridDof(); + for(unsigned timeIdx=0; timeIdx + void supplementInitialSolution_(PrimaryVariables&, + const Context&, + unsigned, + unsigned) + { } + + /*! + * \brief Register all output modules which make sense for the model. + * + * This method is supposed to be overloaded by the actual models, + * or else only the primary variables can be written to the result + * files. + */ + void registerOutputModules_() + { + // add the output modules available on all model + auto *mod = new VtkPrimaryVarsModule(simulator_); + this->outputModules_.push_back(mod); + } + + /*! + * \brief Reference to the local residal object + */ + LocalResidual& localResidual_() + { return localLinearizer_.localResidual(); } + + /*! + * \brief Returns whether messages should be printed + */ + bool verbose_() const + { return gridView_.comm().rank() == 0; } + + Implementation& asImp_() + { return *static_cast(this); } + const Implementation& asImp_() const + { return *static_cast(this); } + + // the problem we want to solve. defines the constitutive + // relations, matxerial laws, etc. + Simulator& simulator_; + + // the representation of the spatial domain of the problem + GridView gridView_; + + // the mappers for element and vertex entities to global indices + ElementMapper elementMapper_; + VertexMapper vertexMapper_; + + // a vector with all auxiliary equations to be considered + std::vector*> auxEqModules_; + + NewtonMethod newtonMethod_; + + Timer prePostProcessTimer_; + Timer linearizeTimer_; + Timer solveTimer_; + Timer updateTimer_; + + // calculates the local jacobian matrix for a given element + std::vector localLinearizer_; + // Linearizes the problem at the current time step using the + // local jacobian + Linearizer *linearizer_; + + // cur is the current iterative solution, prev the converged + // solution of the previous time step + mutable IntensiveQuantitiesVector intensiveQuantityCache_[historySize]; + // while these are logically bools, concurrent writes to vector are not thread safe. + mutable std::vector intensiveQuantityCacheUpToDate_[historySize]; + + mutable std::array< std::unique_ptr< DiscreteFunction >, historySize > solution_; + + std::list*> outputModules_; + + Scalar gridTotalVolume_; + std::vector dofTotalVolume_; + std::vector isLocalDof_; + + mutable GlobalEqVector storageCache_[historySize]; + + bool enableGridAdaptation_; + bool enableIntensiveQuantityCache_; + bool enableStorageCache_; + bool enableThermodynamicHints_; +}; + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief The base class for the finite volume discretization schemes without adaptation. + */ +template +class FvBaseDiscretizationNoAdapt : public FvBaseDiscretization +{ + using ParentType = FvBaseDiscretization; + using Simulator = GetPropType; + using DiscreteFunction = GetPropType; + + static constexpr unsigned historySize = getPropValue(); + +public: + template + struct SerializeHelper { + template + static void serializeOp(Serializer& serializer, + SolutionType& solution) + { + for (auto& sol : solution) { + serializer(*sol); + } + } + }; + + FvBaseDiscretizationNoAdapt(Simulator& simulator) + : ParentType(simulator) + { + if (this->enableGridAdaptation_) { + throw std::invalid_argument("Grid adaptation need to use" + " BaseDiscretization = FvBaseDiscretizationFemAdapt" + " which currently requires the presence of the" + " dune-fem module"); + } + size_t numDof = this->asImp_().numGridDof(); + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + this->solution_[timeIdx] = std::make_unique("solution", numDof); + } + } +}; + +} // namespace Opm + +#endif // EWOMS_FV_BASE_DISCRETIZATION_HH diff --git a/opm/models/discretization/common/fvbasediscretizationfemadapt.hh b/opm/models/discretization/common/fvbasediscretizationfemadapt.hh new file mode 100644 index 00000000000..015c3a47e5c --- /dev/null +++ b/opm/models/discretization/common/fvbasediscretizationfemadapt.hh @@ -0,0 +1,174 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseDiscretization + */ +#ifndef EWOMS_FV_BASE_DISCRETIZATION_FEMADAPT_HH +#define EWOMS_FV_BASE_DISCRETIZATION_FEMADAPT_HH + +#include + +#include +#include +#include +#include + +namespace Opm { + +template +class FvBaseDiscretizationFemAdapt; + +namespace Properties { + +template +struct BaseDiscretizationType { + using type = FvBaseDiscretizationFemAdapt; +}; + +template +struct DiscreteFunction { + using DiscreteFunctionSpace = GetPropType; + using PrimaryVariables = GetPropType; + using type = Dune::Fem::ISTLBlockVectorDiscreteFunction; +}; + +} // namespace Properties + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief The base class for the finite volume discretization schemes. + */ + +template +class FvBaseDiscretizationFemAdapt : public FvBaseDiscretization +{ + using Grid = GetPropType; + using ParentType = FvBaseDiscretization; + using PrimaryVariables = GetPropType; + using Problem = GetPropType; + using Simulator = GetPropType; + + static constexpr unsigned historySize = getPropValue(); + + using DiscreteFunctionSpace = GetPropType; + // discrete function storing solution data + using DiscreteFunction = Dune::Fem::ISTLBlockVectorDiscreteFunction; + + // problem restriction and prolongation operator for adaptation + using ProblemRestrictProlongOperator = typename Problem::RestrictProlongOperator; + + // discrete function restriction and prolongation operator for adaptation + using DiscreteFunctionRestrictProlong = Dune::Fem::RestrictProlongDefault; + using RestrictProlong + = Dune::Fem::RestrictProlongTuple; + + // adaptation classes + using AdaptationManager = Dune::Fem::AdaptationManager; + +public: + template + struct SerializeHelper { + template + static void serializeOp(Serializer& serializer, + SolutionType& solution) + { + for (auto& sol : solution) { + serializer(sol->blockVector()); + } + } + }; + + FvBaseDiscretizationFemAdapt(Simulator& simulator) + : ParentType(simulator) + , space_(simulator.vanguard().gridPart()) + { + if (this->enableGridAdaptation_ && !Dune::Fem::Capabilities::isLocallyAdaptive::v) { + throw std::invalid_argument("Grid adaptation enabled, but chosen Grid is not capable" + " of adaptivity"); + } + + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + this->solution_[timeIdx] = std::make_unique("solution", space_); + } + } + + void adaptGrid() + { + // adapt the grid if enabled and if all dependencies are available + // adaptation is only done if markForGridAdaptation returns true + if (this->enableGridAdaptation_) { + // check if problem allows for adaptation and cells were marked + if (this->simulator_.problem().markForGridAdaptation()) { + // adapt the grid and load balance if necessary + adaptationManager().adapt(); + + // if the grid has potentially changed, we need to re-create the + // supporting data structures. + this->elementMapper_.update(this->gridView_); + this->vertexMapper_.update(this->gridView_); + this->resetLinearizer(); + // this is a bit hacky because it supposes that Problem::finishInit() + // works fine multiple times in a row. + // + // TODO: move this to Problem::gridChanged() + this->finishInit(); + + // notify the problem that the grid has changed + // + // TODO: come up with a mechanism to access the unadapted data structures + // outside of the problem (i.e., grid, mappers, solutions) + this->simulator_.problem().gridChanged(); + + // notify the modules for visualization output + auto outIt = this->outputModules_.begin(); + auto outEndIt = this->outputModules_.end(); + for (; outIt != outEndIt; ++outIt) + (*outIt)->allocBuffers(); + } + } + } + + AdaptationManager& adaptationManager() + { + if (!adaptationManager_) { + // create adaptation objects here, because when doing so in constructor + // problem is not yet intialized, aka seg fault + restrictProlong_ = std::make_unique(DiscreteFunctionRestrictProlong(*(this->solution_[/*timeIdx=*/0])), + this->simulator_.problem().restrictProlongOperator()); + adaptationManager_ = std::make_unique(this->simulator_.vanguard().grid(), *restrictProlong_); + } + return *adaptationManager_; + } + +private: + DiscreteFunctionSpace space_; + std::unique_ptr restrictProlong_; + std::unique_ptr adaptationManager_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaseelementcontext.hh b/opm/models/discretization/common/fvbaseelementcontext.hh new file mode 100644 index 00000000000..5f552ae9f79 --- /dev/null +++ b/opm/models/discretization/common/fvbaseelementcontext.hh @@ -0,0 +1,607 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseElementContext + */ +#ifndef EWOMS_FV_BASE_ELEMENT_CONTEXT_HH +#define EWOMS_FV_BASE_ELEMENT_CONTEXT_HH + +#include "fvbaseproperties.hh" + +#include + +#include +#include + +#include + +#include + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief This class stores an array of IntensiveQuantities objects, one + * intensive quantities object for each of the element's vertices + */ +template +class FvBaseElementContext +{ + using Implementation = GetPropType; + + using Scalar = GetPropType; + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + + // the history size of the time discretization in number of steps + enum { timeDiscHistorySize = getPropValue() }; + + struct DofStore_ { + IntensiveQuantities intensiveQuantities[timeDiscHistorySize]; + const PrimaryVariables* priVars[timeDiscHistorySize]; + const IntensiveQuantities *thermodynamicHint[timeDiscHistorySize]; + }; + using DofVarsVector = std::vector; + using ExtensiveQuantitiesVector = std::vector; + + using Simulator = GetPropType; + using Problem = GetPropType; + using Model = GetPropType; + using Stencil = GetPropType; + using GradientCalculator = GetPropType; + using SolutionVector = GetPropType; + + using GridView = GetPropType; + using Element = typename GridView::template Codim<0>::Entity; + + static const unsigned dimWorld = GridView::dimensionworld; + static const unsigned numEq = getPropValue(); + + using CoordScalar = typename GridView::ctype; + using GlobalPosition = Dune::FieldVector; + + // we don't allow copies of element contexts! + FvBaseElementContext(const FvBaseElementContext& ) = delete; + +public: + /*! + * \brief The constructor. + */ + explicit FvBaseElementContext(const Simulator& simulator) + : gridView_(simulator.gridView()) + , stencil_(gridView_, simulator.model().dofMapper() ) + { + // remember the simulator object + simulatorPtr_ = &simulator; + enableStorageCache_ = Parameters::Get(); + stashedDofIdx_ = -1; + focusDofIdx_ = -1; + } + + static void *operator new(size_t size) + { return aligned_alloc(alignof(FvBaseElementContext), size); } + + static void operator delete(void *ptr) + { aligned_free(ptr); } + + /*! + * \brief Construct all volume and extensive quantities of an element + * from scratch. + * + * \param elem The DUNE Codim<0> entity for which the volume + * variables ought to be calculated + */ + void updateAll(const Element& elem) + { + asImp_().updateStencil(elem); + asImp_().updateAllIntensiveQuantities(); + asImp_().updateAllExtensiveQuantities(); + } + + /*! + * \brief Compute the finite volume geometry for an element. + * + * \param elem The grid element for which the finite volume geometry ought to be + * computed. + */ + void updateStencil(const Element& elem) + { + // remember the current element + elemPtr_ = &elem; + + // update the stencil. the center gradients are quite expensive to calculate and + // most models don't need them, so that we only do this if the model explicitly + // enables them + stencil_.update(elem); + + // resize the arrays containing the flux and the volume variables + dofVars_.resize(stencil_.numDof()); + extensiveQuantities_.resize(stencil_.numInteriorFaces()); + } + + /*! + * \brief Update the primary topological part of the stencil, but nothing else. + * + * \param elem The grid element for which the finite volume geometry ought to be + * computed. + */ + void updatePrimaryStencil(const Element& elem) + { + // remember the current element + elemPtr_ = &elem; + + // update the finite element geometry + stencil_.updatePrimaryTopology(elem); + + dofVars_.resize(stencil_.numPrimaryDof()); + } + + /*! + * \brief Update the topological part of the stencil, but nothing else. + * + * \param elem The grid element for which the finite volume geometry ought to be + * computed. + */ + void updateStencilTopology(const Element& elem) + { + // remember the current element + elemPtr_ = &elem; + + // update the finite element geometry + stencil_.updateTopology(elem); + } + + /*! + * \brief Compute the intensive quantities of all sub-control volumes of the current + * element for all time indices. + */ + void updateAllIntensiveQuantities() + { + if (!enableStorageCache_) { + // if the storage cache is disabled, we need to calculate the storage term + // from scratch, i.e. we need the intensive quantities of all of the history. + for (unsigned timeIdx = 0; timeIdx < timeDiscHistorySize; ++ timeIdx) + asImp_().updateIntensiveQuantities(timeIdx); + } + else + // if the storage cache is enabled, we only need to recalculate the storage + // term for the most recent point of history (i.e., for the current iterative + // solution) + asImp_().updateIntensiveQuantities(/*timeIdx=*/0); + } + + /*! + * \brief Compute the intensive quantities of all sub-control volumes of the current + * element for a single time index. + * + * \param timeIdx The index of the solution vector used by the time discretization. + */ + void updateIntensiveQuantities(unsigned timeIdx) + { updateIntensiveQuantities_(timeIdx, numDof(timeIdx)); } + + /*! + * \brief Compute the intensive quantities of all sub-control volumes of the current + * element for a single time index. + * + * \param timeIdx The index of the solution vector used by the time discretization. + */ + void updatePrimaryIntensiveQuantities(unsigned timeIdx) + { updateIntensiveQuantities_(timeIdx, numPrimaryDof(timeIdx)); } + + /*! + * \brief Compute the intensive quantities of a single sub-control volume of the + * current element for a single time index. + * + * \param priVars The PrimaryVariables which should be used to calculate the + * intensive quantities. + * \param dofIdx The local index in the current element of the sub-control volume + * which should be updated. + * \param timeIdx The index of the solution vector used by the time discretization. + */ + void updateIntensiveQuantities(const PrimaryVariables& priVars, unsigned dofIdx, unsigned timeIdx) + { asImp_().updateSingleIntQuants_(priVars, dofIdx, timeIdx); } + + /*! + * \brief Compute the extensive quantities of all sub-control volume + * faces of the current element for all time indices. + */ + void updateAllExtensiveQuantities() + { asImp_().updateExtensiveQuantities(/*timeIdx=*/0); } + + /*! + * \brief Compute the extensive quantities of all sub-control volume + * faces of the current element for a single time index. + * + * \param timeIdx The index of the solution vector used by the + * time discretization. + */ + void updateExtensiveQuantities(unsigned timeIdx) + { + gradientCalculator_.prepare(/*context=*/asImp_(), timeIdx); + + for (unsigned fluxIdx = 0; fluxIdx < numInteriorFaces(timeIdx); fluxIdx++) { + extensiveQuantities_[fluxIdx].update(/*context=*/asImp_(), + /*localIndex=*/fluxIdx, + timeIdx); + } + } + + /*! + * \brief Sets the degree of freedom on which the simulator is currently "focused" on + * + * I.e., in the case of automatic differentiation, all derivatives are with regard to + * the primary variables of that degree of freedom. Only "primary" DOFs can be + * focused on. + */ + void setFocusDofIndex(unsigned dofIdx) + { focusDofIdx_ = dofIdx; } + + /*! + * \brief Returns the degree of freedom on which the simulator is currently "focused" on + * + * \copydetails setFocusDof() + */ + unsigned focusDofIndex() const + { return focusDofIdx_; } + + /*! + * \brief Returns the linearization type. + * + * \copydetails setLinearizationType() + */ + LinearizationType linearizationType() const + { return this->model().linearizer().getLinearizationType(); } + + /*! + * \brief Return a reference to the simulator. + */ + const Simulator& simulator() const + { return *simulatorPtr_; } + + /*! + * \brief Return a reference to the problem. + */ + const Problem& problem() const + { return simulatorPtr_->problem(); } + + /*! + * \brief Return a reference to the model. + */ + const Model& model() const + { return simulatorPtr_->model(); } + + /*! + * \brief Return a reference to the grid view. + */ + const GridView& gridView() const + { return gridView_; } + + /*! + * \brief Return the current element. + */ + const Element& element() const + { return *elemPtr_; } + + /*! + * \brief Return the number of sub-control volumes of the current element. + */ + size_t numDof(unsigned timeIdx) const + { return stencil(timeIdx).numDof(); } + + /*! + * \brief Return the number of primary degrees of freedom of the current element. + */ + size_t numPrimaryDof(unsigned timeIdx) const + { return stencil(timeIdx).numPrimaryDof(); } + + /*! + * \brief Return the number of non-boundary faces which need to be + * considered for the flux apporixmation. + */ + size_t numInteriorFaces(unsigned timeIdx) const + { return stencil(timeIdx).numInteriorFaces(); } + + /*! + * \brief Return the number of boundary faces which need to be + * considered for the flux apporixmation. + */ + size_t numBoundaryFaces(unsigned timeIdx) const + { return stencil(timeIdx).numBoundaryFaces(); } + + /*! + * \brief Return the current finite element geometry. + * + * \param timeIdx The index of the solution vector used by the + * time discretization. + */ + const Stencil& stencil(unsigned) const + { return stencil_; } + + /*! + * \brief Return the position of a local entities in global coordinates + * + * \param dofIdx The local index of the degree of freedom + * in the current element. + * \param timeIdx The index of the solution vector used by the + * time discretization. + */ + decltype(auto) pos(unsigned dofIdx, unsigned) const + { return stencil_.subControlVolume(dofIdx).globalPos(); } + + /*! + * \brief Return the global spatial index for a sub-control volume + * + * \param dofIdx The local index of the degree of freedom + * in the current element. + * \param timeIdx The index of the solution vector used by the + * time discretization. + */ + unsigned globalSpaceIndex(unsigned dofIdx, unsigned timeIdx) const + { return stencil(timeIdx).globalSpaceIndex(dofIdx); } + + + /*! + * \brief Return the element-local volume associated with a degree of freedom + * + * In the case of the vertex-centered finite volume method, this is different from + * the total volume because a finite volume usually spans multiple elements... + * + * \param dofIdx The local index of the degree of freedom in the current element. + * \param timeIdx The index of the solution vector used by the time discretization. + */ + Scalar dofVolume(unsigned dofIdx, unsigned timeIdx) const + { return stencil(timeIdx).subControlVolume(dofIdx).volume(); } + + /*! + * \brief Return the total volume associated with a degree of freedom + * + * "Total" means the volume controlled by a degree of freedom disregarding the + * element. (For example in the vertex-centered finite volume method, a control + * volume typically encompasses parts of multiple elements.) + * + * \param dofIdx The local index of the degree of freedom in the current element. + * \param timeIdx The index of the solution vector used by the time discretization. + */ + Scalar dofTotalVolume(unsigned dofIdx, unsigned timeIdx) const + { return model().dofTotalVolume(globalSpaceIndex(dofIdx, timeIdx)); } + + /*! + * \brief Returns whether the current element is on the domain's + * boundary. + */ + bool onBoundary() const + { return element().hasBoundaryIntersections(); } + + /*! + * \brief Return a reference to the intensive quantities of a + * sub-control volume at a given time. + * + * If the time step index is not given, return the volume + * variables for the current time. + * + * \param dofIdx The local index of the degree of freedom in the current element. + * \param timeIdx The index of the solution vector used by the time discretization. + */ + const IntensiveQuantities& intensiveQuantities(unsigned dofIdx, unsigned timeIdx) const + { +#ifndef NDEBUG + assert(dofIdx < numDof(timeIdx)); + + if (enableStorageCache_ && timeIdx != 0 && problem().recycleFirstIterationStorage()) + throw std::logic_error("If caching of the storage term is enabled, only the intensive quantities " + "for the most-recent substep (i.e. time index 0) are available!"); +#endif + + return dofVars_[dofIdx].intensiveQuantities[timeIdx]; + } + + /*! + * \brief Return the thermodynamic hint for a given local index. + * + * \sa Discretization::thermodynamicHint(int, int) + * + * \param dofIdx The local index of the degree of freedom in the current element. + * \param timeIdx The index of the solution vector used by the time discretization. + */ + const IntensiveQuantities *thermodynamicHint(unsigned dofIdx, unsigned timeIdx) const + { + assert(dofIdx < numDof(timeIdx)); + return dofVars_[dofIdx].thermodynamicHint[timeIdx]; + } + /*! + * \copydoc intensiveQuantities() + */ + IntensiveQuantities& intensiveQuantities(unsigned dofIdx, unsigned timeIdx) + { + assert(dofIdx < numDof(timeIdx)); + return dofVars_[dofIdx].intensiveQuantities[timeIdx]; + } + + /*! + * \brief Return the primary variables for a given local index. + * + * \param dofIdx The local index of the degree of freedom + * in the current element. + * \param timeIdx The index of the solution vector used by the + * time discretization. + */ + const PrimaryVariables& primaryVars(unsigned dofIdx, unsigned timeIdx) const + { + assert(dofIdx < numDof(timeIdx)); + return *dofVars_[dofIdx].priVars[timeIdx]; + } + + /*! + * \brief Returns true if no intensive quanties are stashed + * + * In most cases quantities are stashed only if a partial derivative is to be + * calculated via finite difference methods. + */ + bool haveStashedIntensiveQuantities() const + { return stashedDofIdx_ != -1; } + + /*! + * \brief Return the (local) index of the DOF for which the primary variables were + * stashed + * + * If none, then this returns -1. + */ + int stashedDofIdx() const + { return stashedDofIdx_; } + + /*! + * \brief Stash the intensive quantities for a degree of freedom on internal memory. + * + * \param dofIdx The local index of the degree of freedom in the current element. + */ + void stashIntensiveQuantities(unsigned dofIdx) + { + assert(dofIdx < numDof(/*timeIdx=*/0)); + + intensiveQuantitiesStashed_ = dofVars_[dofIdx].intensiveQuantities[/*timeIdx=*/0]; + priVarsStashed_ = *dofVars_[dofIdx].priVars[/*timeIdx=*/0]; + stashedDofIdx_ = static_cast(dofIdx); + } + + /*! + * \brief Restores the intensive quantities for a degree of freedom from internal memory. + * + * \param dofIdx The local index of the degree of freedom in the current element. + */ + void restoreIntensiveQuantities(unsigned dofIdx) + { + dofVars_[dofIdx].priVars[/*timeIdx=*/0] = &priVarsStashed_; + dofVars_[dofIdx].intensiveQuantities[/*timeIdx=*/0] = intensiveQuantitiesStashed_; + stashedDofIdx_ = -1; + } + + /*! + * \brief Return a reference to the gradient calculation class of + * the chosen spatial discretization. + */ + const GradientCalculator& gradientCalculator() const + { return gradientCalculator_; } + + /*! + * \brief Return a reference to the extensive quantities of a + * sub-control volume face. + * + * \param fluxIdx The local index of the sub-control volume face for which the + * extensive quantities are requested + * \param timeIdx The index of the solution vector used by the time discretization. + */ + const ExtensiveQuantities& extensiveQuantities(unsigned fluxIdx, unsigned) const + { return extensiveQuantities_[fluxIdx]; } + + /*! + * \brief Returns true iff the cache for the storage term ought to be used for this context. + * + * If it is used, intensive quantities can only be accessed for the most recent time + * index. (time index 0.) + */ + bool enableStorageCache() const + { return enableStorageCache_; } + + /*! + * \brief Specifies if the cache for the storage term ought to be used for this context. + */ + void setEnableStorageCache(bool yesno) + { enableStorageCache_ = yesno; } + +private: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } + +protected: + /*! + * \brief Update the first 'n' intensive quantities objects from the primary variables. + * + * This method considers the intensive quantities cache. + */ + void updateIntensiveQuantities_(unsigned timeIdx, size_t numDof) + { + // update the intensive quantities for the whole history + const SolutionVector& globalSol = model().solution(timeIdx); + + // update the non-gradient quantities + for (unsigned dofIdx = 0; dofIdx < numDof; dofIdx++) { + unsigned globalIdx = globalSpaceIndex(dofIdx, timeIdx); + const PrimaryVariables& dofSol = globalSol[globalIdx]; + dofVars_[dofIdx].priVars[timeIdx] = &dofSol; + + dofVars_[dofIdx].thermodynamicHint[timeIdx] = + model().thermodynamicHint(globalIdx, timeIdx); + + const auto *cachedIntQuants = model().cachedIntensiveQuantities(globalIdx, timeIdx); + if (cachedIntQuants) { + dofVars_[dofIdx].intensiveQuantities[timeIdx] = *cachedIntQuants; + } + else { + updateSingleIntQuants_(dofSol, dofIdx, timeIdx); + model().updateCachedIntensiveQuantities(dofVars_[dofIdx].intensiveQuantities[timeIdx], + globalIdx, + timeIdx); + } + } + } + + void updateSingleIntQuants_(const PrimaryVariables& priVars, unsigned dofIdx, unsigned timeIdx) + { +#ifndef NDEBUG + if (enableStorageCache_ && timeIdx != 0 && problem().recycleFirstIterationStorage()) + throw std::logic_error("If caching of the storage term is enabled, only the intensive quantities " + "for the most-recent substep (i.e. time index 0) are available!"); +#endif + + dofVars_[dofIdx].priVars[timeIdx] = &priVars; + dofVars_[dofIdx].intensiveQuantities[timeIdx].update(/*context=*/asImp_(), dofIdx, timeIdx); + } + + IntensiveQuantities intensiveQuantitiesStashed_; + PrimaryVariables priVarsStashed_; + + GradientCalculator gradientCalculator_; + + std::vector > dofVars_; + std::vector > extensiveQuantities_; + + const Simulator *simulatorPtr_; + const Element *elemPtr_; + const GridView gridView_; + Stencil stencil_; + + int stashedDofIdx_; + int focusDofIdx_; + bool enableStorageCache_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaseextensivequantities.hh b/opm/models/discretization/common/fvbaseextensivequantities.hh new file mode 100644 index 00000000000..35c97cbb641 --- /dev/null +++ b/opm/models/discretization/common/fvbaseextensivequantities.hh @@ -0,0 +1,135 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseExtensiveQuantities + */ +#ifndef EWOMS_FV_BASE_EXTENSIVE_QUANTITIES_HH +#define EWOMS_FV_BASE_EXTENSIVE_QUANTITIES_HH + +#include "fvbaseproperties.hh" + +#include + +#include + +namespace Opm { +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Provide the properties at a face which make sense indepentently + * of the conserved quantities. + */ +template +class FvBaseExtensiveQuantities +{ + using Scalar = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + +public: + /*! + * \brief Register all run-time parameters for the extensive quantities. + */ + static void registerParameters() + { } + + /*! + * \brief Update the extensive quantities for a given sub-control-volume face. + * + * \param elemCtx Reference to the current element context. + * \param scvfIdx The local index of the sub-control-volume face for which the + * extensive quantities should be calculated. + * \param timeIdx The index used by the time discretization. + */ + void update(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + const auto& scvf = elemCtx.stencil(timeIdx).interiorFace(scvfIdx); + interiorScvIdx_ = scvf.interiorIndex(); + exteriorScvIdx_ = scvf.exteriorIndex(); + + extrusionFactor_ = + (elemCtx.intensiveQuantities(interiorScvIdx_, timeIdx).extrusionFactor() + + elemCtx.intensiveQuantities(exteriorScvIdx_, timeIdx).extrusionFactor()) / 2; + Valgrind::CheckDefined(extrusionFactor_); + assert(extrusionFactor_ > 0); + } + + + /*! + * \brief Update the extensive quantities for a given boundary face. + * + * \param context Reference to the current execution context. + * \param bfIdx The local index of the boundary face for which + * the extensive quantities should be calculated. + * \param timeIdx The index used by the time discretization. + * \param fluidState The FluidState on the domain boundary. + * \param paramCache The FluidSystem's parameter cache. + */ + template + void updateBoundary(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState&) + { + unsigned dofIdx = context.interiorScvIndex(bfIdx, timeIdx); + interiorScvIdx_ = static_cast(dofIdx); + exteriorScvIdx_ = static_cast(dofIdx); + + extrusionFactor_ = context.intensiveQuantities(bfIdx, timeIdx).extrusionFactor(); + Valgrind::CheckDefined(extrusionFactor_); + assert(extrusionFactor_ > 0); + } + + /*! + * \brief Returns th extrusion factor for the sub-control-volume face + */ + Scalar extrusionFactor() const + { return extrusionFactor_; } + + /*! + * \brief Return the local index of the control volume which is on the "interior" of + * the sub-control volume face. + */ + unsigned short interiorIndex() const + { return interiorScvIdx_; } + + /*! + * \brief Return the local index of the control volume which is on the "exterior" of + * the sub-control volume face. + */ + unsigned short exteriorIndex() const + { return exteriorScvIdx_; } + +private: + // local indices of the interior and the exterior sub-control-volumes + unsigned short interiorScvIdx_; + unsigned short exteriorScvIdx_; + + Scalar extrusionFactor_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbasefdlocallinearizer.hh b/opm/models/discretization/common/fvbasefdlocallinearizer.hh new file mode 100644 index 00000000000..ab47650798e --- /dev/null +++ b/opm/models/discretization/common/fvbasefdlocallinearizer.hh @@ -0,0 +1,523 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseFdLocalLinearizer + */ +#ifndef EWOMS_FV_BASE_FD_LOCAL_LINEARIZER_HH +#define EWOMS_FV_BASE_FD_LOCAL_LINEARIZER_HH + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +namespace Opm { +// forward declaration +template +class FvBaseFdLocalLinearizer; + +} // namespace Opm + +namespace Opm::Properties { + +// declare the property tags required for the finite differences local linearizer + +namespace TTag { +struct FiniteDifferenceLocalLinearizer {}; +} // namespace TTag + +template +struct BaseEpsilon { using type = UndefinedProperty; }; + +// set the properties to be spliced in +template +struct LocalLinearizer +{ using type = FvBaseFdLocalLinearizer; }; + +template +struct Evaluation +{ using type = GetPropType; }; + +//! The base epsilon value for finite difference calculations +template +struct BaseEpsilon +{ + using type = GetPropType; + static constexpr type value = std::max(0.9123e-10, std::numeric_limits::epsilon()*1.23e3); +}; + +} // namespace Opm::Properties + +namespace Opm::Parameters { + +/*! + * \brief Specify which kind of method should be used to numerically + * calculate the partial derivatives of the residual. + * + * -1 means backward differences, 0 means central differences, 1 means + * forward differences. By default we use forward differences. + */ +struct NumericDifferenceMethod { static constexpr int value = +1; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Calculates the Jacobian of the local residual for finite volume spatial + * discretizations using a finite difference method + * + * The local Jacobian for a given context is defined as the derivatives of the residuals + * of all degrees of freedom featured by the stencil with regard to the primary variables + * of the stencil's "primary" degrees of freedom. + * + * This class implements numeric differentiation using finite difference methods, i.e. + * forward or backward differences (2nd order), or central differences (3rd order). The + * method used is determined by the "NumericDifferenceMethod" property: + * + * - If the value of this property is smaller than 0, backward differences are used, + * i.e.: + * \f[ + * \frac{\partial f(x)}{\partial x} \approx \frac{f(x) - f(x - \epsilon)}{\epsilon} + * \f] + * + * - If the value of this property is 0, central differences are used, i.e.: + * \f[ + * \frac{\partial f(x)}{\partial x} \approx + * \frac{f(x + \epsilon) - f(x - \epsilon)}{2 \epsilon} + * \f] + * + * - if the value of this property is larger than 0, forward differences are used, i.e.: + * \f[ + * \frac{\partial f(x)}{\partial x} \approx + * \frac{f(x + \epsilon) - f(x)}{\epsilon} + * \f] + * + * Here, \f$ f \f$ is the residual function for all equations, \f$x\f$ is the value of a + * sub-control volume's primary variable at the evaluation point and \f$\epsilon\f$ is a + * small scalar value larger than 0. + */ +template +class FvBaseFdLocalLinearizer +{ +private: + using Implementation = GetPropType; + using LocalResidual = GetPropType; + using Simulator = GetPropType; + using Problem = GetPropType; + using Model = GetPropType; + using PrimaryVariables = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + using GridView = GetPropType; + using Element = typename GridView::template Codim<0>::Entity; + + enum { numEq = getPropValue() }; + + // extract local matrices from jacobian matrix for consistency + using ScalarMatrixBlock = typename GetPropType::MatrixBlock; + using ScalarVectorBlock = Dune::FieldVector; + + using ScalarLocalBlockVector = Dune::BlockVector; + using ScalarLocalBlockMatrix = Dune::Matrix; + + using LocalEvalBlockVector = typename LocalResidual::LocalEvalBlockVector; + +#if __GNUC__ == 4 && __GNUC_MINOR__ <= 6 +public: + // make older GCCs happy by providing a public copy constructor (this is necessary + // for their implementation of std::vector, although the method is never called...) + FvBaseFdLocalLinearizer(const FvBaseFdLocalLinearizer&) + : internalElemContext_(0) + {} + +#else + // copying local residual objects around is a very bad idea, so we explicitly prevent + // it... + FvBaseFdLocalLinearizer(const FvBaseFdLocalLinearizer&) = delete; +#endif +public: + FvBaseFdLocalLinearizer() + : internalElemContext_(0) + { } + + ~FvBaseFdLocalLinearizer() + { delete internalElemContext_; } + + /*! + * \brief Register all run-time parameters for the local jacobian. + */ + static void registerParameters() + { + Parameters::Register + ("The method used for numeric differentiation (-1: backward " + "differences, 0: central differences, 1: forward differences)"); + } + + /*! + * \brief Initialize the local Jacobian object. + * + * At this point we can assume that everything has been allocated, + * although some objects may not yet be completely initialized. + * + * \param simulator The simulator object of the simulation. + */ + void init(Simulator& simulator) + { + simulatorPtr_ = &simulator; + delete internalElemContext_; + internalElemContext_ = new ElementContext(simulator); + } + + /*! + * \brief Compute an element's local Jacobian matrix and evaluate its residual. + * + * The local Jacobian for a given context is defined as the derivatives of the + * residuals of all degrees of freedom featured by the stencil with regard to the + * primary variables of the stencil's "primary" degrees of freedom. Adding the local + * Jacobians for all elements in the grid will give the global Jacobian 'grad f(x)'. + * + * \param element The grid element for which the local residual and its local + * Jacobian should be calculated. + */ + void linearize(const Element& element) + { + linearize(*internalElemContext_, element); + } + + /*! + * \brief Compute an element's local Jacobian matrix and evaluate its residual. + * + * The local Jacobian for a given context is defined as the derivatives of the + * residuals of all degrees of freedom featured by the stencil with regard to the + * primary variables of the stencil's "primary" degrees of freedom. Adding the local + * Jacobians for all elements in the grid will give the global Jacobian 'grad f(x)'. + * + * After calling this method the ElementContext is in an undefined state, so do not + * use it anymore! + * + * \param elemCtx The element execution context for which the local residual and its + * local Jacobian should be calculated. + */ + void linearize(ElementContext& elemCtx, const Element& elem) + { + elemCtx.updateAll(elem); + + // update the weights of the primary variables for the context + model_().updatePVWeights(elemCtx); + + resize_(elemCtx); + reset_(elemCtx); + + // calculate the local residual + localResidual_.eval(residual_, elemCtx); + + // calculate the local jacobian matrix + size_t numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + for (unsigned dofIdx = 0; dofIdx < numPrimaryDof; dofIdx++) { + for (unsigned pvIdx = 0; pvIdx < numEq; pvIdx++) { + asImp_().evalPartialDerivative_(elemCtx, dofIdx, pvIdx); + + // incorporate the partial derivatives into the local Jacobian matrix + updateLocalJacobian_(elemCtx, dofIdx, pvIdx); + } + } + } + + /*! + * \brief Returns the unweighted epsilon value used to calculate + * the local derivatives + */ + static Scalar baseEpsilon() + { return getPropValue(); } + + /*! + * \brief Returns the epsilon value which is added and removed + * from the current solution. + * + * \param elemCtx The element execution context for which the + * local residual and its gradient should be + * calculated. + * \param dofIdx The local index of the element's vertex for + * which the local derivative ought to be calculated. + * \param pvIdx The index of the primary variable which gets varied + */ + Scalar numericEpsilon(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned pvIdx) const + { + unsigned globalIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + Scalar pvWeight = elemCtx.model().primaryVarWeight(globalIdx, pvIdx); + assert(pvWeight > 0 && std::isfinite(pvWeight)); + Valgrind::CheckDefined(pvWeight); + + return baseEpsilon()/pvWeight; + } + + /*! + * \brief Return reference to the local residual. + */ + LocalResidual& localResidual() + { return localResidual_; } + + /*! + * \brief Return reference to the local residual. + */ + const LocalResidual& localResidual() const + { return localResidual_; } + + /*! + * \brief Returns the local Jacobian matrix of the residual of a sub-control volume. + * + * \param domainScvIdx The local index of the sub control volume + * which contains the independents + * \param rangeScvIdx The local index of the sub control volume + * which contains the local residual + */ + const ScalarMatrixBlock& jacobian(unsigned domainScvIdx, unsigned rangeScvIdx) const + { return jacobian_[domainScvIdx][rangeScvIdx]; } + + /*! + * \brief Returns the local residual of a sub-control volume. + * + * \param dofIdx The local index of the sub control volume + */ + const ScalarVectorBlock& residual(unsigned dofIdx) const + { return residual_[dofIdx]; } + +protected: + Implementation& asImp_() + { return *static_cast(this); } + const Implementation& asImp_() const + { return *static_cast(this); } + + const Simulator& simulator_() const + { return *simulatorPtr_; } + const Problem& problem_() const + { return simulatorPtr_->problem(); } + const Model& model_() const + { return simulatorPtr_->model(); } + + /*! + * \brief Returns the numeric difference method which is applied. + */ + static int numericDifferenceMethod_() + { + static int diff = Parameters::Get(); + return diff; + } + + /*! + * \brief Resize all internal attributes to the size of the + * element. + */ + void resize_(const ElementContext& elemCtx) + { + size_t numDof = elemCtx.numDof(/*timeIdx=*/0); + size_t numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + + residual_.resize(numDof); + jacobian_.setSize(numDof, numPrimaryDof); + + derivResidual_.resize(numDof); + } + + /*! + * \brief Reset the all relevant internal attributes to 0 + */ + void reset_(const ElementContext& elemCtx) + { + size_t numDof = elemCtx.numDof(/*timeIdx=*/0); + size_t numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + for (unsigned primaryDofIdx = 0; primaryDofIdx < numPrimaryDof; ++ primaryDofIdx) + for (unsigned dof2Idx = 0; dof2Idx < numDof; ++ dof2Idx) + jacobian_[dof2Idx][primaryDofIdx] = 0.0; + + for (unsigned primaryDofIdx = 0; primaryDofIdx < numDof; ++ primaryDofIdx) + residual_[primaryDofIdx] = 0.0; + } + + /*! + * \brief Compute the partial derivatives of a context's residual functions + * + * This method can be overwritten by the implementation if a better scheme than + * numerical differentiation is available. + * + * The default implementation of this method uses numeric differentiation, + * i.e. forward or backward differences (2nd order), or central differences (3rd + * order). The method used is determined by the "NumericDifferenceMethod" property: + * + * - If the value of this property is smaller than 0, backward differences are used, + * i.e.: + * \f[ + * \frac{\partial f(x)}{\partial x} \approx \frac{f(x) - f(x - \epsilon)}{\epsilon} + * \f] + * + * - If the value of this property is 0, central differences are used, i.e.: + * \f[ + * \frac{\partial f(x)}{\partial x} \approx + * \frac{f(x + \epsilon) - f(x - \epsilon)}{2 \epsilon} + * \f] + * + * - if the value of this property is larger than 0, forward + * differences are used, i.e.: + * \f[ + \frac{\partial f(x)}{\partial x} \approx \frac{f(x + \epsilon) - f(x)}{\epsilon} + * \f] + * + * Here, \f$ f \f$ is the residual function for all equations, \f$x\f$ is the value + * of a sub-control volume's primary variable at the evaluation point and + * \f$\epsilon\f$ is a small value larger than 0. + * + * \param elemCtx The element context for which the local partial + * derivative ought to be calculated + * \param dofIdx The sub-control volume index of the current + * finite element for which the partial derivative + * ought to be calculated + * \param pvIdx The index of the primary variable at the dofIdx' + * sub-control volume of the current finite element + * for which the partial derivative ought to be + * calculated + */ + void evalPartialDerivative_(ElementContext& elemCtx, + unsigned dofIdx, + unsigned pvIdx) + { + // save all quantities which depend on the specified primary + // variable at the given sub control volume + elemCtx.stashIntensiveQuantities(dofIdx); + + PrimaryVariables priVars(elemCtx.primaryVars(dofIdx, /*timeIdx=*/0)); + Scalar eps = asImp_().numericEpsilon(elemCtx, dofIdx, pvIdx); + Scalar delta = 0.0; + + if (numericDifferenceMethod_() >= 0) { + // we are not using backward differences, i.e. we need to + // calculate f(x + \epsilon) + + // deflect primary variables + priVars[pvIdx] += eps; + delta += eps; + + // calculate the deflected residual + elemCtx.updateIntensiveQuantities(priVars, dofIdx, /*timeIdx=*/0); + elemCtx.updateAllExtensiveQuantities(); + localResidual_.eval(derivResidual_, elemCtx); + } + else { + // we are using backward differences, i.e. we don't need + // to calculate f(x + \epsilon) and we can recycle the + // (already calculated) residual f(x) + derivResidual_ = residual_; + } + + if (numericDifferenceMethod_() <= 0) { + // we are not using forward differences, i.e. we don't + // need to calculate f(x - \epsilon) + + // deflect the primary variables + priVars[pvIdx] -= delta + eps; + delta += eps; + + // calculate the deflected residual again, this time we use the local + // residual's internal storage. + elemCtx.updateIntensiveQuantities(priVars, dofIdx, /*timeIdx=*/0); + elemCtx.updateAllExtensiveQuantities(); + localResidual_.eval(elemCtx); + + derivResidual_ -= localResidual_.residual(); + } + else { + // we are using forward differences, i.e. we don't need to + // calculate f(x - \epsilon) and we can recycle the + // (already calculated) residual f(x) + derivResidual_ -= residual_; + } + + assert(delta > 0); + + // divide difference in residuals by the magnitude of the + // deflections between the two function evaluation + derivResidual_ /= delta; + + // restore the original state of the element's volume + // variables + elemCtx.restoreIntensiveQuantities(dofIdx); + +#ifndef NDEBUG + for (unsigned i = 0; i < derivResidual_.size(); ++i) + Valgrind::CheckDefined(derivResidual_[i]); +#endif + } + + /*! + * \brief Updates the current local Jacobian matrix with the partial derivatives of + * all equations for primary variable 'pvIdx' at the degree of freedom + * associated with 'focusDofIdx'. + */ + void updateLocalJacobian_(const ElementContext& elemCtx, + unsigned focusDofIdx, + unsigned pvIdx) + { + size_t numDof = elemCtx.numDof(/*timeIdx=*/0); + for (unsigned dofIdx = 0; dofIdx < numDof; dofIdx++) { + for (unsigned eqIdx = 0; eqIdx < numEq; eqIdx++) { + // A[dofIdx][focusDofIdx][eqIdx][pvIdx] is the partial derivative of the + // residual function 'eqIdx' for the degree of freedom 'dofIdx' with + // regard to the primary variable 'pvIdx' of the degree of freedom + // 'focusDofIdx' + jacobian_[dofIdx][focusDofIdx][eqIdx][pvIdx] = derivResidual_[dofIdx][eqIdx]; + Valgrind::CheckDefined(jacobian_[dofIdx][focusDofIdx][eqIdx][pvIdx]); + } + } + } + + Simulator *simulatorPtr_; + Model *modelPtr_; + + ElementContext *internalElemContext_; + + LocalEvalBlockVector residual_; + LocalEvalBlockVector derivResidual_; + ScalarLocalBlockMatrix jacobian_; + + LocalResidual localResidual_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbasegradientcalculator.hh b/opm/models/discretization/common/fvbasegradientcalculator.hh new file mode 100644 index 00000000000..2f1ccd54701 --- /dev/null +++ b/opm/models/discretization/common/fvbasegradientcalculator.hh @@ -0,0 +1,356 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseGradientCalculator + */ +#ifndef EWOMS_FV_BASE_GRADIENT_CALCULATOR_HH +#define EWOMS_FV_BASE_GRADIENT_CALCULATOR_HH + +#include "fvbaseproperties.hh" + +#include + +namespace Opm { +template +class EcfvDiscretization; + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief This class calculates gradients of arbitrary quantities at + * flux integration points using the two-point approximation scheme + */ +template +class FvBaseGradientCalculator +{ + using GridView = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Discretization = GetPropType; + using ElementContext = GetPropType; + + enum { dim = GridView::dimension }; + enum { dimWorld = GridView::dimensionworld }; + + // maximum number of flux approximation points. to calculate this, + // we assume that the geometry with the most pointsq is a cube. + enum { maxFap = 2 << dim }; + + using DimVector = Dune::FieldVector; + using EvalDimVector = Dune::FieldVector; + +public: + /*! + * \brief Register all run-time parameters for the gradient calculator + * of the base class of the discretization. + */ + static void registerParameters() + { } + + /*! + * \brief Precomputes the common values to calculate gradients and values of + * quantities at every interior flux approximation point. + * + * \param elemCtx The current execution context + * \param timeIdx The index used by the time discretization. + */ + template + void prepare(const ElementContext&, unsigned) + { /* noting to do */ } + + /*! + * \brief Calculates the value of an arbitrary scalar quantity at any interior flux + * approximation point. + * + * \param elemCtx The current execution context + * \param fapIdx The local index of the flux approximation point in the current + * element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity at an index of a degree of + * freedom + */ + template + auto calculateScalarValue(const ElementContext& elemCtx, + unsigned fapIdx, + const QuantityCallback& quantityCallback) const + -> typename std::remove_reference::type + { + using RawReturnType = decltype(quantityCallback.operator()(0)); + using ReturnType = typename std::remove_const::type>::type; + + Scalar interiorDistance; + Scalar exteriorDistance; + computeDistances_(interiorDistance, exteriorDistance, elemCtx, fapIdx); + + const auto& face = elemCtx.stencil(/*timeIdx=*/0).interiorFace(fapIdx); + auto i = face.interiorIndex(); + auto j = face.exteriorIndex(); + auto focusDofIdx = elemCtx.focusDofIndex(); + + // use the average weighted by distance... + ReturnType value; + if (i == focusDofIdx) + value = quantityCallback(i)*interiorDistance; + else + value = getValue(quantityCallback(i))*interiorDistance; + + if (j == focusDofIdx) + value += quantityCallback(j)*exteriorDistance; + else + value += getValue(quantityCallback(j))*exteriorDistance; + + value /= interiorDistance + exteriorDistance; + + return value; + } + + /*! + * \brief Calculates the value of an arbitrary vectorial quantity at any interior flux + * approximation point. + * + * \param elemCtx The current execution context + * \param fapIdx The local index of the flux approximation point in the current + * element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity at an index of a degree of + * freedom + */ + template + auto calculateVectorValue(const ElementContext& elemCtx, + unsigned fapIdx, + const QuantityCallback& quantityCallback) const + -> typename std::remove_reference::type + { + using RawReturnType = decltype(quantityCallback.operator()(0)); + using ReturnType = typename std::remove_const::type>::type; + + Scalar interiorDistance; + Scalar exteriorDistance; + computeDistances_(interiorDistance, exteriorDistance, elemCtx, fapIdx); + + const auto& face = elemCtx.stencil(/*timeIdx=*/0).interiorFace(fapIdx); + auto i = face.interiorIndex(); + auto j = face.exteriorIndex(); + auto focusDofIdx = elemCtx.focusDofIndex(); + + // use the average weighted by distance... + ReturnType value; + if (i == focusDofIdx) { + value = quantityCallback(i); + for (int k = 0; k < value.size(); ++k) + value[k] *= interiorDistance; + } + else { + const auto& dofVal = getValue(quantityCallback(i)); + for (int k = 0; k < dofVal.size(); ++k) + value[k] = getValue(dofVal[k])*interiorDistance; + } + + if (j == focusDofIdx) { + const auto& dofVal = quantityCallback(j); + for (int k = 0; k < dofVal.size(); ++k) + value[k] += dofVal[k]*exteriorDistance; + } + else { + const auto& dofVal = quantityCallback(j); + for (int k = 0; k < dofVal.size(); ++k) + value[k] += getValue(dofVal[k])*exteriorDistance; + } + + Scalar totDistance = interiorDistance + exteriorDistance; + for (int k = 0; k < value.size(); ++k) + value[k] /= totDistance; + + return value; + } + + /*! + * \brief Calculates the gradient of an arbitrary quantity at any + * flux approximation point. + * + * \param elemCtx The current execution context + * \param fapIdx The local index of the flux approximation point + * in the current element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity given the index of a degree of + * freedom + */ + template + void calculateGradient(EvalDimVector& quantityGrad, + const ElementContext& elemCtx, + unsigned fapIdx, + const QuantityCallback& quantityCallback) const + { + const auto& stencil = elemCtx.stencil(/*timeIdx=*/0); + const auto& face = stencil.interiorFace(fapIdx); + + auto i = face.interiorIndex(); + auto j = face.exteriorIndex(); + auto focusIdx = elemCtx.focusDofIndex(); + + const auto& interiorPos = stencil.subControlVolume(i).globalPos(); + const auto& exteriorPos = stencil.subControlVolume(j).globalPos(); + + Evaluation deltay; + if (i == focusIdx) { + deltay = + getValue(quantityCallback(j)) + - quantityCallback(i); + } + else if (j == focusIdx) { + deltay = + quantityCallback(j) + - getValue(quantityCallback(i)); + } + else + deltay = + getValue(quantityCallback(j)) + - getValue(quantityCallback(i)); + + Scalar distSquared = 0.0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) { + Scalar tmp = exteriorPos[dimIdx] - interiorPos[dimIdx]; + distSquared += tmp*tmp; + } + + // divide the gradient by the squared distance between the centers of the + // sub-control volumes: the gradient is the normalized directional vector between + // the two centers times the ratio of the difference of the values and their + // distance, i.e., d/abs(d) * delta y / abs(d) = d*delta y / abs(d)^2. + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) { + Scalar tmp = exteriorPos[dimIdx] - interiorPos[dimIdx]; + quantityGrad[dimIdx] = deltay*(tmp/distSquared); + } + } + + /*! + * \brief Calculates the value of an arbitrary quantity at any + * flux approximation point on the grid boundary. + * + * Boundary values are always calculated using the two-point + * approximation. + * + * \param elemCtx The current execution context + * \param fapIdx The local index of the flux approximation point + * in the current element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity given the index of a degree of + * freedom + */ + template + auto calculateBoundaryValue(const ElementContext&, + unsigned, + const QuantityCallback& quantityCallback) + -> decltype(quantityCallback.boundaryValue()) + { return quantityCallback.boundaryValue(); } + + /*! + * \brief Calculates the gradient of an arbitrary quantity at any + * flux approximation point on the boundary. + * + * Boundary gradients are always calculated using the two-point + * approximation. + * + * \param elemCtx The current execution context + * \param faceIdx The local index of the flux approximation point + * in the current element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity at an index of a degree of + * freedom + */ + template + void calculateBoundaryGradient(EvalDimVector& quantityGrad, + const ElementContext& elemCtx, + unsigned faceIdx, + const QuantityCallback& quantityCallback) const + { + const auto& stencil = elemCtx.stencil(/*timeIdx=*/0); + const auto& face = stencil.boundaryFace(faceIdx); + + Evaluation deltay; + if (face.interiorIndex() == elemCtx.focusDofIndex()) + deltay = quantityCallback.boundaryValue() - quantityCallback(face.interiorIndex()); + else + deltay = + getValue(quantityCallback.boundaryValue()) + - getValue(quantityCallback(face.interiorIndex())); + + const auto& boundaryFacePos = face.integrationPos(); + const auto& interiorPos = stencil.subControlVolume(face.interiorIndex()).center(); + + Scalar distSquared = 0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) { + Scalar tmp = boundaryFacePos[dimIdx] - interiorPos[dimIdx]; + distSquared += tmp*tmp; + } + + // divide the gradient by the squared distance between the center of the + // sub-control and the center of the boundary face: the gradient is the + // normalized directional vector between the two centers times the ratio of the + // difference of the values and their distance, i.e., d/abs(d) * deltay / abs(d) + // = d*deltay / abs(d)^2. + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) { + Scalar tmp = boundaryFacePos[dimIdx] - interiorPos[dimIdx]; + quantityGrad[dimIdx] = deltay*(tmp/distSquared); + } + } + +private: + void computeDistances_(Scalar& interiorDistance, + Scalar& exteriorDistance, + const ElementContext& elemCtx, + unsigned fapIdx) const + { + const auto& stencil = elemCtx.stencil(/*timeIdx=*/0); + const auto& face = stencil.interiorFace(fapIdx); + + // calculate the distances of the position of the interior and of the exterior + // finite volume to the position of the integration point. + const auto& normal = face.normal(); + auto i = face.interiorIndex(); + auto j = face.exteriorIndex(); + const auto& interiorPos = stencil.subControlVolume(i).globalPos(); + const auto& exteriorPos = stencil.subControlVolume(j).globalPos(); + const auto& integrationPos = face.integrationPos(); + + interiorDistance = 0.0; + exteriorDistance = 0.0; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) { + interiorDistance += + (interiorPos[dimIdx] - integrationPos[dimIdx]) + * normal[dimIdx]; + + exteriorDistance += + (exteriorPos[dimIdx] - integrationPos[dimIdx]) + * normal[dimIdx]; + } + + interiorDistance = std::sqrt(std::abs(interiorDistance)); + exteriorDistance = std::sqrt(std::abs(exteriorDistance)); + } +}; +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaseintensivequantities.hh b/opm/models/discretization/common/fvbaseintensivequantities.hh new file mode 100644 index 00000000000..802c21c07e7 --- /dev/null +++ b/opm/models/discretization/common/fvbaseintensivequantities.hh @@ -0,0 +1,96 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseIntensiveQuantities + */ +#ifndef EWOMS_FV_BASE_INTENSIVE_QUANTITIES_HH +#define EWOMS_FV_BASE_INTENSIVE_QUANTITIES_HH + +#include "fvbaseproperties.hh" + +#include + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Base class for the model specific class which provides access to all intensive + * (i.e., volume averaged) quantities. + */ +template +class FvBaseIntensiveQuantities +{ + using Implementation = GetPropType; + using Scalar = GetPropType; + using ElementContext = GetPropType; + +public: + /*! + * \brief Register all run-time parameters for the intensive quantities. + */ + static void registerParameters() + { } + + /*! + * \brief Update all quantities for a given control volume. + */ + void update(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { extrusionFactor_ = elemCtx.problem().extrusionFactor(elemCtx, dofIdx, timeIdx); } + + /*! + * \brief Return how much a given sub-control volume is extruded. + * + * This means the factor by which a lower-dimensional (1D or 2D) + * entity needs to be expanded to get a full dimensional cell. The + * default is 1.0 which means that 1D problems are actually + * thought as pipes with a cross section of 1 m^2 and 2D problems + * are assumed to extend 1 m to the back. + */ + Scalar extrusionFactor() const + { return extrusionFactor_; } + + /*! + * \brief If running in valgrind this makes sure that all + * quantities in the intensive quantities are defined. + */ + void checkDefined() const + { } +protected: + Scalar extrusionFactor_; +private: + const Implementation& asImp_() const + { return *static_cast(this); } + Implementation& asImp_() + { return *static_cast(this); } + + +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaselinearizer.hh b/opm/models/discretization/common/fvbaselinearizer.hh new file mode 100644 index 00000000000..c53035a469a --- /dev/null +++ b/opm/models/discretization/common/fvbaselinearizer.hh @@ -0,0 +1,714 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseLinearizer + */ +#ifndef EWOMS_FV_BASE_LINEARIZER_HH +#define EWOMS_FV_BASE_LINEARIZER_HH + +#include "fvbaseproperties.hh" +#include "linearizationtype.hh" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include // current_exception, rethrow_exception +#include + +namespace Opm { +// forward declarations +template +class EcfvDiscretization; + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief The common code for the linearizers of non-linear systems of equations + * + * This class assumes that these system of equations to be linearized are stemming from + * models that use an finite volume scheme for spatial discretization and an Euler + * scheme for time discretization. + */ +template +class FvBaseLinearizer +{ +//! \cond SKIP_THIS + using Model = GetPropType; + using Discretization = GetPropType; + using Problem = GetPropType; + using Simulator = GetPropType; + using GridView = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using DofMapper = GetPropType; + using ElementMapper = GetPropType; + using ElementContext = GetPropType; + + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + using SparseMatrixAdapter = GetPropType; + using EqVector = GetPropType; + using Constraints = GetPropType; + using Stencil = GetPropType; + using ThreadManager = GetPropType; + + using GridCommHandleFactory = GetPropType; + + using Toolbox = MathToolbox; + + using Element = typename GridView::template Codim<0>::Entity; + using ElementIterator = typename GridView::template Codim<0>::Iterator; + + using Vector = GlobalEqVector; + + using IstlMatrix = typename SparseMatrixAdapter::IstlMatrix; + + enum { numEq = getPropValue() }; + enum { historySize = getPropValue() }; + + using MatrixBlock = typename SparseMatrixAdapter::MatrixBlock; + using VectorBlock = Dune::FieldVector; + + static const bool linearizeNonLocalElements = getPropValue(); + + // copying the linearizer is not a good idea + FvBaseLinearizer(const FvBaseLinearizer&); +//! \endcond + +public: + FvBaseLinearizer() + : jacobian_() + { + simulatorPtr_ = 0; + } + + ~FvBaseLinearizer() + { + auto it = elementCtx_.begin(); + const auto& endIt = elementCtx_.end(); + for (; it != endIt; ++it) + delete *it; + } + + /*! + * \brief Register all run-time parameters for the Jacobian linearizer. + */ + static void registerParameters() + { } + + /*! + * \brief Initialize the linearizer. + * + * At this point we can assume that all objects in the simulator + * have been allocated. We cannot assume that they are fully + * initialized, though. + * + * \copydetails Doxygen::simulatorParam + */ + void init(Simulator& simulator) + { + simulatorPtr_ = &simulator; + eraseMatrix(); + auto it = elementCtx_.begin(); + const auto& endIt = elementCtx_.end(); + for (; it != endIt; ++it){ + delete *it; + } + elementCtx_.resize(0); + fullDomain_ = std::make_unique(simulator.gridView()); + } + + /*! + * \brief Causes the Jacobian matrix to be recreated from scratch before the next + * iteration. + * + * This method is usally called if the sparsity pattern has changed for some + * reason. (e.g. by modifications of the grid or changes of the auxiliary equations.) + */ + void eraseMatrix() + { + jacobian_.reset(); + } + + /*! + * \brief Linearize the full system of non-linear equations. + * + * The linearizationType() controls the scheme used and the focus + * time index. The default is fully implicit scheme, and focus index + * equal to 0, i.e. current time (end of step). + * + * This linearizes the spatial domain and all auxiliary equations. + */ + void linearize() + { + linearizeDomain(); + linearizeAuxiliaryEquations(); + } + + /*! + * \brief Linearize the part of the non-linear system of equations that is associated + * with the spatial domain. + * + * That means that the global Jacobian of the residual is assembled and the residual + * is evaluated for the current solution. + * + * The current state of affairs (esp. the previous and the current solutions) is + * represented by the model object. + */ + void linearizeDomain() + { + linearizeDomain(*fullDomain_); + } + + template + void linearizeDomain(const SubDomainType& domain) + { + OPM_TIMEBLOCK(linearizeDomain); + // we defer the initialization of the Jacobian matrix until here because the + // auxiliary modules usually assume the problem, model and grid to be fully + // initialized... + if (!jacobian_) + initFirstIteration_(); + + // Called here because it is no longer called from linearize_(). + if (static_cast(domain.view.size(0)) == model_().numTotalDof()) { + // We are on the full domain. + resetSystem_(); + } else { + resetSystem_(domain); + } + + int succeeded; + try { + linearize_(domain); + succeeded = 1; + } + catch (const std::exception& e) + { + std::cout << "rank " << simulator_().gridView().comm().rank() + << " caught an exception while linearizing:" << e.what() + << "\n" << std::flush; + succeeded = 0; + } + catch (...) + { + std::cout << "rank " << simulator_().gridView().comm().rank() + << " caught an exception while linearizing" + << "\n" << std::flush; + succeeded = 0; + } + succeeded = simulator_().gridView().comm().min(succeeded); + + if (!succeeded) + throw NumericalProblem("A process did not succeed in linearizing the system"); + } + + void finalize() + { jacobian_->finalize(); } + + /*! + * \brief Linearize the part of the non-linear system of equations that is associated + * with the spatial domain. + */ + void linearizeAuxiliaryEquations() + { + OPM_TIMEBLOCK(linearizeAuxiliaryEquations); + // flush possible local caches into matrix structure + jacobian_->commit(); + + auto& model = model_(); + const auto& comm = simulator_().gridView().comm(); + for (unsigned auxModIdx = 0; auxModIdx < model.numAuxiliaryModules(); ++auxModIdx) { + bool succeeded = true; + try { + model.auxiliaryModule(auxModIdx)->linearize(*jacobian_, residual_); + } + catch (const std::exception& e) { + succeeded = false; + + std::cout << "rank " << simulator_().gridView().comm().rank() + << " caught an exception while linearizing:" << e.what() + << "\n" << std::flush; + } + + succeeded = comm.min(succeeded); + + if (!succeeded) + throw NumericalProblem("linearization of an auxiliary equation failed"); + } + } + + /*! + * \brief Return constant reference to global Jacobian matrix backend. + */ + const SparseMatrixAdapter& jacobian() const + { return *jacobian_; } + + SparseMatrixAdapter& jacobian() + { return *jacobian_; } + + /*! + * \brief Return constant reference to global residual vector. + */ + const GlobalEqVector& residual() const + { return residual_; } + + GlobalEqVector& residual() + { return residual_; } + + void setLinearizationType(LinearizationType linearizationType){ + linearizationType_ = linearizationType; + }; + + const LinearizationType& getLinearizationType() const{ + return linearizationType_; + }; + + void updateDiscretizationParameters() + { + // This linearizer stores no such parameters. + } + + void updateBoundaryConditionData() + { + // This linearizer stores no such data. + } + + void updateFlowsInfo() { + // This linearizer stores no such data. + } + + /*! + * \brief Returns the map of constraint degrees of freedom. + * + * (This object is only non-empty if the EnableConstraints property is true.) + */ + const std::map& constraintsMap() const + { return constraintsMap_; } + + /*! + * \brief Return constant reference to the flowsInfo. + * + * (This object has been only implemented for the tpfalinearizer.) + */ + const auto& getFlowsInfo() const + {return flowsInfo_;} + + /*! + * \brief Return constant reference to the floresInfo. + * + * (This object has been only implemented for the tpfalinearizer.) + */ + const auto& getFloresInfo() const + {return floresInfo_;} + + template + void resetSystem_(const SubDomainType& domain) + { + if (!jacobian_) { + initFirstIteration_(); + } + + // loop over selected elements + using GridViewType = decltype(domain.view); + ThreadedEntityIterator threadedElemIt(domain.view); +#ifdef _OPENMP +#pragma omp parallel +#endif + { + unsigned threadId = ThreadManager::threadId(); + auto elemIt = threadedElemIt.beginParallel(); + MatrixBlock zeroBlock; + zeroBlock = 0.0; + for (; !threadedElemIt.isFinished(elemIt); elemIt = threadedElemIt.increment()) { + const Element& elem = *elemIt; + ElementContext& elemCtx = *elementCtx_[threadId]; + elemCtx.updatePrimaryStencil(elem); + // Set to zero the relevant residual and jacobian parts. + for (unsigned primaryDofIdx = 0; + primaryDofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); + ++primaryDofIdx) + { + unsigned globI = elemCtx.globalSpaceIndex(primaryDofIdx, /*timeIdx=*/0); + residual_[globI] = 0.0; + jacobian_->clearRow(globI, 0.0); + } + } + } + } + +private: + Simulator& simulator_() + { return *simulatorPtr_; } + const Simulator& simulator_() const + { return *simulatorPtr_; } + + Problem& problem_() + { return simulator_().problem(); } + const Problem& problem_() const + { return simulator_().problem(); } + + Model& model_() + { return simulator_().model(); } + const Model& model_() const + { return simulator_().model(); } + + const GridView& gridView_() const + { return problem_().gridView(); } + + const ElementMapper& elementMapper_() const + { return model_().elementMapper(); } + + const DofMapper& dofMapper_() const + { return model_().dofMapper(); } + + void initFirstIteration_() + { + // initialize the BCRS matrix for the Jacobian of the residual function + createMatrix_(); + + // initialize the Jacobian matrix and the vector for the residual function + residual_.resize(model_().numTotalDof()); + resetSystem_(); + + // create the per-thread context objects + elementCtx_.resize(ThreadManager::maxThreads()); + for (unsigned threadId = 0; threadId != ThreadManager::maxThreads(); ++ threadId) + elementCtx_[threadId] = new ElementContext(simulator_()); + } + + // Construct the BCRS matrix for the Jacobian of the residual function + void createMatrix_() + { + const auto& model = model_(); + Stencil stencil(gridView_(), model_().dofMapper()); + + // for the main model, find out the global indices of the neighboring degrees of + // freedom of each primary degree of freedom + sparsityPattern_.clear(); + sparsityPattern_.resize(model.numTotalDof()); + + for (const auto& elem : elements(gridView_())) { + stencil.update(elem); + + for (unsigned primaryDofIdx = 0; primaryDofIdx < stencil.numPrimaryDof(); ++primaryDofIdx) { + unsigned myIdx = stencil.globalSpaceIndex(primaryDofIdx); + + for (unsigned dofIdx = 0; dofIdx < stencil.numDof(); ++dofIdx) { + unsigned neighborIdx = stencil.globalSpaceIndex(dofIdx); + sparsityPattern_[myIdx].insert(neighborIdx); + } + } + } + + // add the additional neighbors and degrees of freedom caused by the auxiliary + // equations + size_t numAuxMod = model.numAuxiliaryModules(); + for (unsigned auxModIdx = 0; auxModIdx < numAuxMod; ++auxModIdx) + model.auxiliaryModule(auxModIdx)->addNeighbors(sparsityPattern_); + + // allocate raw matrix + jacobian_.reset(new SparseMatrixAdapter(simulator_())); + + // create matrix structure based on sparsity pattern + jacobian_->reserve(sparsityPattern_); + } + + // reset the global linear system of equations. + void resetSystem_() + { + residual_ = 0.0; + // zero all matrix entries + jacobian_->clear(); + } + + // query the problem for all constraint degrees of freedom. note that this method is + // quite involved and is thus relatively slow. + void updateConstraintsMap_() + { + if (!enableConstraints_()) + // constraints are not explictly enabled, so we don't need to consider them! + return; + + constraintsMap_.clear(); + + // loop over all elements... + ThreadedEntityIterator threadedElemIt(gridView_()); +#ifdef _OPENMP +#pragma omp parallel +#endif + { + unsigned threadId = ThreadManager::threadId(); + ElementIterator elemIt = threadedElemIt.beginParallel(); + for (; !threadedElemIt.isFinished(elemIt); elemIt = threadedElemIt.increment()) { + // create an element context (the solution-based quantities are not + // available here!) + const Element& elem = *elemIt; + ElementContext& elemCtx = *elementCtx_[threadId]; + elemCtx.updateStencil(elem); + + // check if the problem wants to constrain any degree of the current + // element's freedom. if yes, add the constraint to the map. + for (unsigned primaryDofIdx = 0; + primaryDofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); + ++ primaryDofIdx) + { + Constraints constraints; + elemCtx.problem().constraints(constraints, + elemCtx, + primaryDofIdx, + /*timeIdx=*/0); + if (constraints.isActive()) { + unsigned globI = elemCtx.globalSpaceIndex(primaryDofIdx, /*timeIdx=*/0); + constraintsMap_[globI] = constraints; + continue; + } + } + } + } + } + + // linearize the whole or part of the system + template + void linearize_(const SubDomainType& domain) + { + OPM_TIMEBLOCK(linearize_); + + // We do not call resetSystem_() here, since that will set + // the full system to zero, not just our part. + // Instead, that must be called before starting the linearization. + + // before the first iteration of each time step, we need to update the + // constraints. (i.e., we assume that constraints can be time dependent, but they + // can't depend on the solution.) + if (model_().newtonMethod().numIterations() == 0) + updateConstraintsMap_(); + + applyConstraintsToSolution_(); + + // to avoid a race condition if two threads handle an exception at the same time, + // we use an explicit lock to control access to the exception storage object + // amongst thread-local handlers + std::mutex exceptionLock; + + // storage to any exception that needs to be bridged out of the + // parallel block below. initialized to null to indicate no exception + std::exception_ptr exceptionPtr = nullptr; + + // relinearize the elements... + using GridViewType = decltype(domain.view); + ThreadedEntityIterator threadedElemIt(domain.view); +#ifdef _OPENMP +#pragma omp parallel +#endif + { + auto elemIt = threadedElemIt.beginParallel(); + auto nextElemIt = elemIt; + try { + for (; !threadedElemIt.isFinished(elemIt); elemIt = nextElemIt) { + // give the model and the problem a chance to prefetch the data required + // to linearize the next element, but only if we need to consider it + nextElemIt = threadedElemIt.increment(); + if (!threadedElemIt.isFinished(nextElemIt)) { + const auto& nextElem = *nextElemIt; + if (linearizeNonLocalElements + || nextElem.partitionType() == Dune::InteriorEntity) + { + model_().prefetch(nextElem); + problem_().prefetch(nextElem); + } + } + + const auto& elem = *elemIt; + if (!linearizeNonLocalElements && elem.partitionType() != Dune::InteriorEntity) + continue; + + linearizeElement_(elem); + } + } + // If an exception occurs in the parallel block, it won't escape the + // block; terminate() is called instead of a handler outside! hence, we + // tuck any exceptions that occur away in the pointer. If an exception + // occurs in more than one thread at the same time, we must pick one of + // them to be rethrown as we cannot have two active exceptions at the + // same time. This solution essentially picks one at random. This will + // only be a problem if two different kinds of exceptions are thrown, for + // instance if one thread experiences a (recoverable) numerical issue + // while another is out of memory. + catch(...) { + std::lock_guard take(exceptionLock); + exceptionPtr = std::current_exception(); + threadedElemIt.setFinished(); + } + } // parallel block + + // after reduction from the parallel block, exceptionPtr will point to + // a valid exception if one occurred in one of the threads; rethrow + // it here to let the outer handler take care of it properly + if(exceptionPtr) { + std::rethrow_exception(exceptionPtr); + } + + applyConstraintsToLinearization_(); + } + + + // linearize an element in the interior of the process' grid partition + template + void linearizeElement_(const ElementType& elem) + { + unsigned threadId = ThreadManager::threadId(); + + ElementContext *elementCtx = elementCtx_[threadId]; + auto& localLinearizer = model_().localLinearizer(threadId); + + // the actual work of linearization is done by the local linearizer class + localLinearizer.linearize(*elementCtx, elem); + + // update the right hand side and the Jacobian matrix + if (getPropValue()) + globalMatrixMutex_.lock(); + + size_t numPrimaryDof = elementCtx->numPrimaryDof(/*timeIdx=*/0); + for (unsigned primaryDofIdx = 0; primaryDofIdx < numPrimaryDof; ++ primaryDofIdx) { + unsigned globI = elementCtx->globalSpaceIndex(/*spaceIdx=*/primaryDofIdx, /*timeIdx=*/0); + + // update the right hand side + residual_[globI] += localLinearizer.residual(primaryDofIdx); + + // update the global Jacobian matrix + for (unsigned dofIdx = 0; dofIdx < elementCtx->numDof(/*timeIdx=*/0); ++ dofIdx) { + unsigned globJ = elementCtx->globalSpaceIndex(/*spaceIdx=*/dofIdx, /*timeIdx=*/0); + + jacobian_->addToBlock(globJ, globI, localLinearizer.jacobian(dofIdx, primaryDofIdx)); + } + } + + if (getPropValue()) + globalMatrixMutex_.unlock(); + } + + // apply the constraints to the solution. (i.e., the solution of constraint degrees + // of freedom is set to the value of the constraint.) + void applyConstraintsToSolution_() + { + if (!enableConstraints_()) + return; + + // TODO: assuming a history size of 2 only works for Euler time discretizations! + auto& sol = model_().solution(/*timeIdx=*/0); + auto& oldSol = model_().solution(/*timeIdx=*/1); + + auto it = constraintsMap_.begin(); + const auto& endIt = constraintsMap_.end(); + for (; it != endIt; ++it) { + sol[it->first] = it->second; + oldSol[it->first] = it->second; + } + } + + // apply the constraints to the linearization. (i.e., for constrain degrees of + // freedom the Jacobian matrix maps to identity and the residual is zero) + void applyConstraintsToLinearization_() + { + if (!enableConstraints_()) + return; + + auto it = constraintsMap_.begin(); + const auto& endIt = constraintsMap_.end(); + for (; it != endIt; ++it) { + unsigned constraintDofIdx = it->first; + + // reset the column of the Jacobian matrix + // put an identity matrix on the main diagonal of the Jacobian + jacobian_->clearRow(constraintDofIdx, Scalar(1.0)); + + // make the right-hand side of constraint DOFs zero + residual_[constraintDofIdx] = 0.0; + } + } + + static bool enableConstraints_() + { return getPropValue(); } + + Simulator *simulatorPtr_; + std::vector elementCtx_; + + // The constraint equations (only non-empty if the + // EnableConstraints property is true) + std::map constraintsMap_; + + + struct FlowInfo + { + int faceId; + VectorBlock flow; + unsigned int nncId; + }; + SparseTable flowsInfo_; + SparseTable floresInfo_; + + // the jacobian matrix + std::unique_ptr jacobian_; + + // the right-hand side + GlobalEqVector residual_; + + LinearizationType linearizationType_; + + std::mutex globalMatrixMutex_; + + std::vector> sparsityPattern_; + + struct FullDomain + { + explicit FullDomain(const GridView& v) : view (v) {} + GridView view; + std::vector interior; // Should remain empty. + }; + // Simple domain object used for full-domain linearization, it allows + // us to have the same interface for sub-domain and full-domain work. + // Pointer since it must defer construction, due to GridView member. + std::unique_ptr fullDomain_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaselocalresidual.hh b/opm/models/discretization/common/fvbaselocalresidual.hh new file mode 100644 index 00000000000..e8c4d0716d7 --- /dev/null +++ b/opm/models/discretization/common/fvbaselocalresidual.hh @@ -0,0 +1,641 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseLocalResidual + */ +#ifndef EWOMS_FV_BASE_LOCAL_RESIDUAL_HH +#define EWOMS_FV_BASE_LOCAL_RESIDUAL_HH + +#include "fvbaseproperties.hh" + +#include +#include + +#include + +#include +#include + +#include + +#include + +#include + +namespace Opm { +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Element-wise caculation of the residual matrix for models based on a finite + * volume spatial discretization. + * + * \copydetails Doxygen::typeTagTParam + */ +template +class FvBaseLocalResidual +{ +private: + using Implementation = GetPropType; + + using GridView = GetPropType; + using Element = typename GridView::template Codim<0>::Entity; + + using Problem = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using BoundaryRateVector = GetPropType; + using RateVector = GetPropType; + using EqVector = GetPropType; + using PrimaryVariables = GetPropType; + using ElementContext = GetPropType; + using BoundaryContext = GetPropType; + + static constexpr bool useVolumetricResidual = getPropValue(); + + enum { numEq = getPropValue() }; + enum { extensiveStorageTerm = getPropValue() }; + + using Toolbox = MathToolbox; + using EvalVector = Dune::FieldVector; + + // copying the local residual class is not a good idea + FvBaseLocalResidual(const FvBaseLocalResidual& ) + {} + +public: + using LocalEvalBlockVector = Dune::BlockVector >; + + FvBaseLocalResidual() + { } + + ~FvBaseLocalResidual() + { } + + /*! + * \brief Register all run-time parameters for the local residual. + */ + static void registerParameters() + { } + + /*! + * \brief Return the result of the eval() call using internal + * storage. + */ + const LocalEvalBlockVector& residual() const + { return internalResidual_; } + + /*! + * \brief Return the result of the eval() call using internal + * storage. + * + * \copydetails Doxygen::ecfvScvIdxParam + */ + const EvalVector& residual(unsigned dofIdx) const + { return internalResidual_[dofIdx]; } + + /*! + * \brief Compute the local residual, i.e. the deviation of the + * conservation equations from zero and store the results + * internally. + * + * The results can be requested afterwards using the residual() method. + * + * \copydetails Doxygen::problemParam + * \copydetails Doxygen::elementParam + */ + void eval(const Problem& problem, const Element& element) + { + ElementContext elemCtx(problem); + elemCtx.updateAll(element); + eval(elemCtx); + } + + /*! + * \brief Compute the local residual, i.e. the deviation of the + * conservation equations from zero and store the results + * internally. + * + * The results can be requested afterwards using the residual() method. + * + * \copydetails Doxygen::ecfvElemCtxParam + */ + void eval(ElementContext& elemCtx) + { + size_t numDof = elemCtx.numDof(/*timeIdx=*/0); + internalResidual_.resize(numDof); + asImp_().eval(internalResidual_, elemCtx); + } + + /*! + * \brief Compute the local residual, i.e. the deviation of the + * conservation equations from zero. + * + * \copydetails Doxygen::residualParam + * \copydetails Doxygen::ecfvElemCtxParam + */ + void eval(LocalEvalBlockVector& residual, + ElementContext& elemCtx) const + { + assert(residual.size() == elemCtx.numDof(/*timeIdx=*/0)); + + residual = 0.0; + + // evaluate the flux terms + asImp_().evalFluxes(residual, elemCtx, /*timeIdx=*/0); + + // evaluate the storage and the source terms + asImp_().evalVolumeTerms_(residual, elemCtx); + + // evaluate the boundary conditions + asImp_().evalBoundary_(residual, elemCtx, /*timeIdx=*/0); + + if (useVolumetricResidual) { + // make the residual volume specific (i.e., make it incorrect mass per cubic + // meter instead of total mass) + size_t numDof = elemCtx.numDof(/*timeIdx=*/0); + for (unsigned dofIdx=0; dofIdx < numDof; ++dofIdx) { + if (elemCtx.dofTotalVolume(dofIdx, /*timeIdx=*/0) > 0.0) { + // interior DOF + Scalar dofVolume = elemCtx.dofTotalVolume(dofIdx, /*timeIdx=*/0); + + assert(std::isfinite(dofVolume)); + Valgrind::CheckDefined(dofVolume); + + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + residual[dofIdx][eqIdx] /= dofVolume; + } + } + } + } + + /*! + * \brief Calculate the amount of all conservation quantities stored in all element's + * sub-control volumes for a given history index. + * + * This is used to figure out how much of each conservation quantity is inside the + * element. + * + * \copydetails Doxygen::storageParam + * \copydetails Doxygen::ecfvElemCtxParam + * \copydetails Doxygen::timeIdxParam + */ + void evalStorage(LocalEvalBlockVector& storage, + const ElementContext& elemCtx, + unsigned timeIdx) const + { + // the derivative of the storage term depends on the current primary variables; + // for time indices != 0, the storage term is constant (because these solutions + // are not changed by the Newton method!) + if (timeIdx == 0) { + // calculate the amount of conservation each quantity inside + // all primary sub control volumes + size_t numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + for (unsigned dofIdx=0; dofIdx < numPrimaryDof; dofIdx++) { + storage[dofIdx] = 0.0; + + // the volume of the associated DOF + Scalar alpha = + elemCtx.stencil(timeIdx).subControlVolume(dofIdx).volume() + * elemCtx.intensiveQuantities(dofIdx, timeIdx).extrusionFactor(); + + // If the degree of freedom which we currently look at is the one at the + // center of attention, we need to consider the derivatives for the + // storage term, else the storage term is constant w.r.t. the primary + // variables of the focused DOF. + if (dofIdx == elemCtx.focusDofIndex()) { + asImp_().computeStorage(storage[dofIdx], + elemCtx, + dofIdx, + timeIdx); + + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + storage[dofIdx][eqIdx] *= alpha; + } + else { + Dune::FieldVector tmp; + asImp_().computeStorage(tmp, + elemCtx, + dofIdx, + timeIdx); + + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + storage[dofIdx][eqIdx] = tmp[eqIdx]*alpha; + } + } + } + else { + // for all previous solutions, the storage term does _not_ depend on the + // current primary variables, so we use scalars to store it. + if (elemCtx.enableStorageCache()) { + size_t numPrimaryDof = elemCtx.numPrimaryDof(timeIdx); + for (unsigned dofIdx=0; dofIdx < numPrimaryDof; dofIdx++) { + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, timeIdx); + const auto& cachedStorage = elemCtx.model().cachedStorage(globalDofIdx, timeIdx); + for (unsigned eqIdx=0; eqIdx < numEq; eqIdx++) + storage[dofIdx][eqIdx] = cachedStorage[eqIdx]; + } + } + else { + // calculate the amount of conservation each quantity inside + // all primary sub control volumes + Dune::FieldVector tmp; + size_t numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + for (unsigned dofIdx=0; dofIdx < numPrimaryDof; dofIdx++) { + tmp = 0.0; + asImp_().computeStorage(tmp, + elemCtx, + dofIdx, + timeIdx); + tmp *= + elemCtx.stencil(timeIdx).subControlVolume(dofIdx).volume() + * elemCtx.intensiveQuantities(dofIdx, timeIdx).extrusionFactor(); + + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) + storage[dofIdx][eqIdx] = tmp[eqIdx]; + } + } + } + +#ifndef NDEBUG + size_t numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + for (unsigned dofIdx=0; dofIdx < numPrimaryDof; dofIdx++) { + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Valgrind::CheckDefined(storage[dofIdx][eqIdx]); + assert(isfinite(storage[dofIdx][eqIdx])); + } + } +#endif + } + + /*! + * \brief Add the flux term to a local residual. + * + * \copydetails Doxygen::residualParam + * \copydetails Doxygen::ecfvElemCtxParam + * \copydetails Doxygen::timeIdxParam + */ + void evalFluxes(LocalEvalBlockVector& residual, + const ElementContext& elemCtx, + unsigned timeIdx) const + { + RateVector flux; + + const auto& stencil = elemCtx.stencil(timeIdx); + // calculate the mass flux over the sub-control volume faces + size_t numInteriorFaces = elemCtx.numInteriorFaces(timeIdx); + for (unsigned scvfIdx = 0; scvfIdx < numInteriorFaces; scvfIdx++) { + const auto& face = stencil.interiorFace(scvfIdx); + unsigned i = face.interiorIndex(); + unsigned j = face.exteriorIndex(); + + Valgrind::SetUndefined(flux); + asImp_().computeFlux(flux, /*context=*/elemCtx, scvfIdx, timeIdx); + Valgrind::CheckDefined(flux); +#ifndef NDEBUG + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) + assert(isfinite(flux[eqIdx])); +#endif + + Scalar alpha = elemCtx.extensiveQuantities(scvfIdx, timeIdx).extrusionFactor(); + alpha *= face.area(); + Valgrind::CheckDefined(alpha); + assert(alpha > 0.0); + assert(isfinite(alpha)); + + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + flux[eqIdx] *= alpha; + + // The balance equation for a finite volume is given by + // + // dStorage/dt + Flux = Source + // + // where the 'Flux' and the 'Source' terms represent the + // mass per second which leaves the finite + // volume. Re-arranging this, we get + // + // dStorage/dt + Flux - Source = 0 + // + // Since the mass flux as calculated by computeFlux() goes out of sub-control + // volume i and into sub-control volume j, we need to add the flux to finite + // volume i and subtract it from finite volume j + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + assert(isfinite(flux[eqIdx])); + residual[i][eqIdx] += flux[eqIdx]; + residual[j][eqIdx] -= flux[eqIdx]; + } + } + +#if !defined NDEBUG + // in debug mode, ensure that the residual is well-defined + size_t numDof = elemCtx.numDof(timeIdx); + for (unsigned i=0; i < numDof; i++) { + for (unsigned j = 0; j < numEq; ++ j) { + assert(isfinite(residual[i][j])); + Valgrind::CheckDefined(residual[i][j]); + } + } +#endif + + } + + ///////////////////////////// + // The following methods _must_ be overloaded by the actual flow + // models! + ///////////////////////////// + + /*! + * \brief Evaluate the amount all conservation quantities + * (e.g. phase mass) within a finite sub-control volume. + * + * \copydetails Doxygen::storageParam + * \copydetails Doxygen::ecfvScvCtxParams + */ + void computeStorage(EqVector&, + const ElementContext&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: The local residual "+Dune::className() + +" does not implement the required method 'computeStorage()'"); + } + + /*! + * \brief Evaluates the total mass flux of all conservation + * quantities over a face of a sub-control volume. + * + * \copydetails Doxygen::areaFluxParam + * \copydetails Doxygen::ecfvScvfCtxParams + */ + void computeFlux(RateVector&, + const ElementContext&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: The local residual "+Dune::className() + +" does not implement the required method 'computeFlux()'"); + } + + /*! + * \brief Calculate the source term of the equation + * + * \copydoc Doxygen::sourceParam + * \copydoc Doxygen::ecfvScvCtxParams + */ + void computeSource(RateVector&, + const ElementContext&, + unsigned, + unsigned) const + { + throw std::logic_error("Not implemented: The local residual "+Dune::className() + +" does not implement the required method 'computeSource()'"); + } + +protected: + /*! + * \brief Evaluate the boundary conditions of an element. + */ + void evalBoundary_(LocalEvalBlockVector& residual, + const ElementContext& elemCtx, + unsigned timeIdx) const + { + if (!elemCtx.onBoundary()) + return; + + BoundaryContext boundaryCtx(elemCtx); + // move the iterator to the first boundary + if(boundaryCtx.intersection(0).neighbor()) + boundaryCtx.increment(); + + // evaluate the boundary for all boundary faces of the current context + size_t numBoundaryFaces = boundaryCtx.numBoundaryFaces(/*timeIdx=*/0); + for (unsigned faceIdx = 0; faceIdx < numBoundaryFaces; ++faceIdx, boundaryCtx.increment()) { + // add the residual of all vertices of the boundary + // segment + evalBoundarySegment_(residual, + boundaryCtx, + faceIdx, + timeIdx); + } + +#if !defined NDEBUG + // in debug mode, ensure that the residual and the storage terms are well-defined + size_t numDof = elemCtx.numDof(/*timeIdx=*/0); + for (unsigned i=0; i < numDof; i++) { + for (unsigned j = 0; j < numEq; ++ j) { + assert(isfinite(residual[i][j])); + Valgrind::CheckDefined(residual[i][j]); + } + } +#endif + + } + + /*! + * \brief Evaluate all boundary conditions for a single + * sub-control volume face to the local residual. + */ + void evalBoundarySegment_(LocalEvalBlockVector& residual, + const BoundaryContext& boundaryCtx, + unsigned boundaryFaceIdx, + unsigned timeIdx) const + { + BoundaryRateVector values; + + Valgrind::SetUndefined(values); + boundaryCtx.problem().boundary(values, boundaryCtx, boundaryFaceIdx, timeIdx); + Valgrind::CheckDefined(values); + + const auto& stencil = boundaryCtx.stencil(timeIdx); + unsigned dofIdx = stencil.boundaryFace(boundaryFaceIdx).interiorIndex(); + const auto& insideIntQuants = boundaryCtx.elementContext().intensiveQuantities(dofIdx, timeIdx); + for (unsigned eqIdx = 0; eqIdx < values.size(); ++eqIdx) { + values[eqIdx] *= + stencil.boundaryFace(boundaryFaceIdx).area() + * insideIntQuants.extrusionFactor(); + + Valgrind::CheckDefined(values[eqIdx]); + assert(isfinite(values[eqIdx])); + } + + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) + residual[dofIdx][eqIdx] += values[eqIdx]; + } + + /*! + * \brief Add the change in the storage terms and the source term + * to the local residual of all sub-control volumes of the + * current element. + */ + void evalVolumeTerms_(LocalEvalBlockVector& residual, + ElementContext& elemCtx) const + { + EvalVector tmp; + EqVector tmp2; + RateVector sourceRate; + + tmp = 0.0; + tmp2 = 0.0; + + // evaluate the volumetric terms (storage + source terms) + size_t numPrimaryDof = elemCtx.numPrimaryDof(/*timeIdx=*/0); + for (unsigned dofIdx=0; dofIdx < numPrimaryDof; dofIdx++) { + Scalar extrusionFactor = + elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0).extrusionFactor(); + Valgrind::CheckDefined(extrusionFactor); + assert(isfinite(extrusionFactor)); + assert(extrusionFactor > 0.0); + Scalar scvVolume = + elemCtx.stencil(/*timeIdx=*/0).subControlVolume(dofIdx).volume() * extrusionFactor; + Valgrind::CheckDefined(scvVolume); + assert(isfinite(scvVolume)); + assert(scvVolume > 0.0); + + // if the model uses extensive quantities in its storage term, and we use + // automatic differention and current DOF is also not the one we currently + // focus on, the storage term does not need any derivatives! + if (!extensiveStorageTerm && + !std::is_same::value && + dofIdx != elemCtx.focusDofIndex()) + { + asImp_().computeStorage(tmp2, elemCtx, dofIdx, /*timeIdx=*/0); + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) + tmp[eqIdx] = tmp2[eqIdx]; + } + else + asImp_().computeStorage(tmp, elemCtx, dofIdx, /*timeIdx=*/0); + +#ifndef NDEBUG + Valgrind::CheckDefined(tmp); + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) + assert(isfinite(tmp[eqIdx])); +#endif + + if (elemCtx.enableStorageCache()) { + const auto& model = elemCtx.model(); + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + if (model.newtonMethod().numIterations() == 0 && + !elemCtx.haveStashedIntensiveQuantities()) + { + if (!elemCtx.problem().recycleFirstIterationStorage()) { + // we re-calculate the storage term for the solution of the + // previous time step from scratch instead of using the one of + // the first iteration of the current time step. + tmp2 = 0.0; + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/1); + asImp_().computeStorage(tmp2, elemCtx, dofIdx, /*timeIdx=*/1); + } + else { + // if the storage term is cached and we're in the first iteration + // of the time step, use the storage term of the first iteration + // as the one as the solution of the last time step (this assumes + // that the initial guess for the solution at the end of the time + // step is the same as the solution at the beginning of the time + // step. This is usually true, but some fancy preprocessing + // scheme might invalidate that assumption.) + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + tmp2[eqIdx] = Toolbox::value(tmp[eqIdx]); + } + + Valgrind::CheckDefined(tmp2); + + model.updateCachedStorage(globalDofIdx, /*timeIdx=*/1, tmp2); + } + else { + // if the mass storage at the beginning of the time step is not cached, + // if the storage term is cached and we're not looking at the first + // iteration of the time step, we take the cached data. + tmp2 = model.cachedStorage(globalDofIdx, /*timeIdx=*/1); + Valgrind::CheckDefined(tmp2); + } + } + else { + // if the mass storage at the beginning of the time step is not cached, + // we re-calculate it from scratch. + tmp2 = 0.0; + asImp_().computeStorage(tmp2, elemCtx, dofIdx, /*timeIdx=*/1); + Valgrind::CheckDefined(tmp2); + } + + // Use the implicit Euler time discretization + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + double dt = elemCtx.simulator().timeStepSize(); + assert(dt > 0); + tmp[eqIdx] -= tmp2[eqIdx]; + tmp[eqIdx] *= scvVolume / dt; + + residual[dofIdx][eqIdx] += tmp[eqIdx]; + } + + Valgrind::CheckDefined(residual[dofIdx]); + + // deal with the source term + asImp_().computeSource(sourceRate, elemCtx, dofIdx, /*timeIdx=*/0); + + // if the model uses extensive quantities in its storage term, and we use + // automatic differention and current DOF is also not the one we currently + // focus on, the storage term does not need any derivatives! + if (!extensiveStorageTerm && + !std::is_same::value && + dofIdx != elemCtx.focusDofIndex()) + { + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) + residual[dofIdx][eqIdx] -= scalarValue(sourceRate[eqIdx])*scvVolume; + } + else { + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + sourceRate[eqIdx] *= scvVolume; + residual[dofIdx][eqIdx] -= sourceRate[eqIdx]; + } + } + + Valgrind::CheckDefined(residual[dofIdx]); + } + +#if !defined NDEBUG + // in debug mode, ensure that the residual is well-defined + size_t numDof = elemCtx.numDof(/*timeIdx=*/0); + for (unsigned i=0; i < numDof; i++) { + for (unsigned j = 0; j < numEq; ++ j) { + assert(isfinite(residual[i][j])); + Valgrind::CheckDefined(residual[i][j]); + } + } +#endif + } + + +private: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } + + LocalEvalBlockVector internalResidual_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbasenewtonconvergencewriter.hh b/opm/models/discretization/common/fvbasenewtonconvergencewriter.hh new file mode 100644 index 00000000000..bdff9b0750d --- /dev/null +++ b/opm/models/discretization/common/fvbasenewtonconvergencewriter.hh @@ -0,0 +1,159 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseNewtonConvergenceWriter + */ +#ifndef EWOMS_FV_BASE_NEWTON_CONVERGENCE_WRITER_HH +#define EWOMS_FV_BASE_NEWTON_CONVERGENCE_WRITER_HH + +#include +#include + +#include + +//! \cond SKIP_THIS +namespace Opm::Properties { + +// forward declaration of the required property tags +template +struct SolutionVector; +template +struct GlobalEqVector; +template +struct NewtonMethod; +template +struct VtkOutputFormat; + +} // namespace Opm::Properties +//! \endcond + +namespace Opm { +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Writes the intermediate solutions during the Newton scheme + * for models using a finite volume discretization + */ +template +class FvBaseNewtonConvergenceWriter +{ + using GridView = GetPropType; + + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + using NewtonMethod = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + +public: + FvBaseNewtonConvergenceWriter(NewtonMethod& nm) + : newtonMethod_(nm) + { + timeStepIdx_ = 0; + iteration_ = 0; + vtkMultiWriter_ = 0; + } + + ~FvBaseNewtonConvergenceWriter() + { delete vtkMultiWriter_; } + + /*! + * \brief Called by the Newton method before the actual algorithm + * is started for any given timestep. + */ + void beginTimeStep() + { + ++timeStepIdx_; + iteration_ = 0; + } + + /*! + * \brief Called by the Newton method before an iteration of the + * Newton algorithm is started. + */ + void beginIteration() + { + ++ iteration_; + if (!vtkMultiWriter_) + vtkMultiWriter_ = + new VtkMultiWriter(/*async=*/false, + newtonMethod_.problem().gridView(), + newtonMethod_.problem().outputDir(), + "convergence"); + vtkMultiWriter_->beginWrite(timeStepIdx_ + iteration_ / 100.0); + } + + /*! + * \brief Write the Newton update to disk. + * + * Called after the linear solution is found for an iteration. + * + * \param uLastIter The solution vector of the previous iteration. + * \param deltaU The negative difference between the solution + * vectors of the previous and the current iteration. + */ + void writeFields(const SolutionVector& uLastIter, + const GlobalEqVector& deltaU) + { + try { + newtonMethod_.problem().model().addConvergenceVtkFields(*vtkMultiWriter_, + uLastIter, + deltaU); + } + catch (...) { + std::cout << "Oops: exception thrown on rank " + << newtonMethod_.problem().gridView().comm().rank() + << " while writing the convergence\n" << std::flush; + } + } + + /*! + * \brief Called by the Newton method after an iteration of the + * Newton algorithm has been completed. + */ + void endIteration() + { vtkMultiWriter_->endWrite(); } + + /*! + * \brief Called by the Newton method after Newton algorithm + * has been completed for any given timestep. + * + * This method is called regardless of whether the Newton method + * converged or not. + */ + void endTimeStep() + { iteration_ = 0; } + +private: + int timeStepIdx_; + int iteration_; + VtkMultiWriter *vtkMultiWriter_; + NewtonMethod& newtonMethod_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbasenewtonmethod.hh b/opm/models/discretization/common/fvbasenewtonmethod.hh new file mode 100644 index 00000000000..f02904dfc7f --- /dev/null +++ b/opm/models/discretization/common/fvbasenewtonmethod.hh @@ -0,0 +1,171 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseNewtonMethod + */ +#ifndef EWOMS_FV_BASE_NEWTON_METHOD_HH +#define EWOMS_FV_BASE_NEWTON_METHOD_HH + +#include "fvbasenewtonconvergencewriter.hh" + +#include +#include + +namespace Opm { + +template +class FvBaseNewtonMethod; + +template +class FvBaseNewtonConvergenceWriter; +} // namespace Opm + +namespace Opm::Properties { + +//! create a type tag for the Newton method of the finite-volume discretization +// Create new type tags +namespace TTag { +struct FvBaseNewtonMethod { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! The discretization specific part of he implementing the Newton algorithm +template +struct DiscNewtonMethod { using type = UndefinedProperty; }; + +// set default values +template +struct DiscNewtonMethod +{ using type = FvBaseNewtonMethod; }; + +template +struct NewtonMethod +{ using type = GetPropType; }; + +template +struct NewtonConvergenceWriter +{ using type = FvBaseNewtonConvergenceWriter; }; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief A Newton method for models using a finite volume discretization. + * + * This class is sufficient for most models which use an Element or a + * Vertex Centered Finite Volume discretization. + */ +template +class FvBaseNewtonMethod : public NewtonMethod +{ + using ParentType = NewtonMethod; + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Simulator = GetPropType; + using Model = GetPropType; + using Linearizer = GetPropType; + using GlobalEqVector = GetPropType; + using SolutionVector = GetPropType; + using PrimaryVariables = GetPropType; + using EqVector = GetPropType; + + +public: + FvBaseNewtonMethod(Simulator& simulator) + : ParentType(simulator) + { } + +protected: + friend class NewtonMethod; + + /*! + * \brief Update the current solution with a delta vector. + * + * The error estimates required for the converged() and + * proceed() methods should be updated inside this method. + * + * Different update strategies, such as line search and chopped + * updates can be implemented. The default behavior is just to + * subtract deltaU from uLastIter, i.e. + * \f[ u^{k+1} = u^k - \Delta u^k \f] + * + * \param nextSolution The solution vector at the end of the current iteration + * \param currentSolution The solution vector at the beginning of the current iteration + * \param solutionUpdate The delta as calculated by solving the linear system of + * equations. This parameter also stores the updated solution. + * \param currentResidual The residual (i.e., right-hand-side) of the current solution. + */ + void update_(SolutionVector& nextSolution, + const SolutionVector& currentSolution, + const GlobalEqVector& solutionUpdate, + const GlobalEqVector& currentResidual) + { + ParentType::update_(nextSolution, currentSolution, solutionUpdate, currentResidual); + + // make sure that the intensive quantities get recalculated at the next + // linearization + if (model_().storeIntensiveQuantities()) { + for (unsigned dofIdx = 0; dofIdx < model_().numGridDof(); ++dofIdx) + model_().setIntensiveQuantitiesCacheEntryValidity(dofIdx, + /*timeIdx=*/0, + /*valid=*/false); + } + } + + /*! + * \brief Indicates the beginning of a Newton iteration. + */ + void beginIteration_() + { + model_().syncOverlap(); + + ParentType::beginIteration_(); + } + + /*! + * \brief Returns a reference to the model. + */ + Model& model_() + { return ParentType::model(); } + + /*! + * \brief Returns a reference to the model. + */ + const Model& model_() const + { return ParentType::model(); } + +private: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } +}; +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaseparameters.hh b/opm/models/discretization/common/fvbaseparameters.hh new file mode 100644 index 00000000000..ee6e376815a --- /dev/null +++ b/opm/models/discretization/common/fvbaseparameters.hh @@ -0,0 +1,132 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup FiniteVolumeDiscretizations + * + * \brief Declare the properties used by the infrastructure code of + * the finite volume discretizations. + */ +#ifndef EWOMS_FV_BASE_PARAMETERS_HH +#define EWOMS_FV_BASE_PARAMETERS_HH + +#include + +namespace Opm::Parameters { + +/*! + * \brief Continue with a non-converged solution instead of giving up + * if we encounter a time step size smaller than the minimum time + * step size. + */ +struct ContinueOnConvergenceError { static constexpr bool value = false; }; + +/*! + * \brief Determines if the VTK output is written to disk asynchronously + * + * I.e. written to disk using a separate thread. This has only an effect if + * EnableVtkOutput is true and if the simulation is run sequentially. The reasons for + * this not being used for MPI-parallel simulations are that Dune's VTK output code does + * not support multi-threaded multi-process VTK output and even if it would, the result + * would be slower than when using synchronous output. + */ +struct EnableAsyncVtkOutput { static constexpr bool value = true; }; + +/*! + * \brief Switch to enable or disable grid adaptation + * + * Currently grid adaptation requires the presence of the dune-FEM module. If it is not + * available and grid adaptation is enabled, an exception is thrown. + */ +struct EnableGridAdaptation { static constexpr bool value = false; }; + +/*! + * \brief Specify whether all intensive quantities for the grid should be + * cached in the discretization. + * + * This potentially reduces the CPU time, but comes at the cost of + * higher memory consumption. In turn, the higher memory requirements + * may cause the simulation to exhibit worse cache coherence behavior + * which eats some of the computational benefits again. + */ +struct EnableIntensiveQuantityCache { static constexpr bool value = false; }; + +/*! + * \brief Specify whether the storage terms for previous solutions should be cached. + * + * This potentially reduces the CPU time, but comes at the cost of higher memory + * consumption. + */ +struct EnableStorageCache { static constexpr bool value = false; }; + +/*! + * \brief Specify whether to use the already calculated solutions as + * starting values of the intensive quantities. + * + * This only makes sense if the calculation of the intensive quantities is + * very expensive (e.g. for non-linear fugacity functions where the + * solver converges faster). + */ +struct EnableThermodynamicHints { static constexpr bool value = false; }; + +/*! + * \brief Global switch to enable or disable the writing of VTK output files + * + * If writing VTK files is disabled, then the WriteVtk$FOO options do + * not have any effect... + */ +struct EnableVtkOutput { static constexpr bool value = true; }; + +/*! + * \brief Specify the maximum size of a time integration [s]. + * + * The default is to not limit the step size. + */ +template +struct MaxTimeStepSize { static constexpr Scalar value = std::numeric_limits::infinity(); }; + +/*! + * \brief The maximum allowed number of timestep divisions for the + * Newton solver. + */ +struct MaxTimeStepDivisions { static constexpr unsigned value = 10; }; + +/*! + * \brief Specify the minimal size of a time integration [s]. + * + * The default is to not limit the step size. + */ +template +struct MinTimeStepSize { static constexpr Scalar value = 0.0; }; + +/*! + * \brief The directory to which simulation output ought to be written to. + */ +struct OutputDir { static constexpr auto value = ""; }; + +//! \brief Number of threads per process. +struct ThreadsPerProcess { static constexpr int value = 1; }; + +} // namespace Opm::Parameters + +#endif diff --git a/opm/models/discretization/common/fvbaseprimaryvariables.hh b/opm/models/discretization/common/fvbaseprimaryvariables.hh new file mode 100644 index 00000000000..dc900e20da4 --- /dev/null +++ b/opm/models/discretization/common/fvbaseprimaryvariables.hh @@ -0,0 +1,176 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBasePrimaryVariables + */ +#ifndef EWOMS_FV_BASE_PRIMARY_VARIABLES_HH +#define EWOMS_FV_BASE_PRIMARY_VARIABLES_HH + +#include + +#include "fvbaseproperties.hh" +#include "linearizationtype.hh" +#include + +#include + +#include + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Represents the primary variables used by the a model. + */ +template +class FvBasePrimaryVariables + : public Dune::FieldVector, + getPropValue()> +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + + enum { numEq = getPropValue() }; + + using Toolbox = MathToolbox; + using ParentType = Dune::FieldVector; + +public: + FvBasePrimaryVariables() + : ParentType() + { Valgrind::SetUndefined(*this); } + + /*! + * \brief Construction from a scalar value + */ + FvBasePrimaryVariables(Scalar value) + : ParentType(value) + { } + + /*! + * \brief Assignment from another primary variables object + */ + FvBasePrimaryVariables(const FvBasePrimaryVariables& value) = default; + + /*! + * \brief Assignment from another primary variables object + */ + FvBasePrimaryVariables& operator=(const FvBasePrimaryVariables& value) = default; + + static void init() + { + // Nothing required by default. + } + + static void registerParameters() + { + // No parameters to register by default. + } + + /*! + * \brief Return a primary variable intensive evaluation. + * + * i.e., the result represents the function f = x_i if the time index is zero, else + * it represents the a constant f = x_i. (the difference is that in the first case, + * the derivative w.r.t. x_i is 1, while it is 0 in the second case. + */ + Evaluation makeEvaluation(unsigned varIdx, unsigned timeIdx, LinearizationType linearizationType = LinearizationType()) const + { + if (std::is_same::value) + return (*this)[varIdx]; // finite differences + else { + // automatic differentiation + if (timeIdx == linearizationType.time) + return Toolbox::createVariable((*this)[varIdx], varIdx); + else + return Toolbox::createConstant((*this)[varIdx]); + } + } + + /*! + * \brief Assign the primary variables "somehow" from a fluid state + * + * That is without considering any consistency issues which the + * fluid state might have. This method is guaranteed to produce + * consistent results if the fluid state is consistent to the + * properties at a given spatial location. (Where "consistent + * results" means that the same fluid state can be reconstructed + * from the primary variables.) + */ + template + void assignNaive(const FluidState&) + { + throw std::runtime_error("The PrimaryVariables class does not define " + "an assignNaive() method"); + } + + /*! + * \brief Instruct valgrind to check the definedness of all attributes of this class. + */ + void checkDefined() const + { + Valgrind::CheckDefined(*static_cast(this)); + } +}; + +} // namespace Opm + +namespace Dune { + + /** Compatibility traits class for DenseVector and DenseMatrix. + */ + template + struct FieldTraitsImpl; + + /** FieldTraitsImpl for classes derived from + * Opm::FvBasePrimaryVariables: use FieldVector's FieldTraits implementation) */ + template + struct FieldTraitsImpl< TypeTag, true > + : public FieldTraits, + Opm::getPropValue()> > + { + }; + + /** FieldTraitsImpl for classes not derived from + * Opm::FvBasePrimaryVariables, fall bakc to existing implementation */ + template + struct FieldTraitsImpl< T, false > + : public FieldTraits< T > + { + }; + + + /** Specialization of FieldTraits for all PrimaryVariables derived from Opm::FvBasePrimaryVariables */ + template class EwomsPrimaryVariable> + struct FieldTraits< EwomsPrimaryVariable< TypeTag > > + : public FieldTraitsImpl< TypeTag, + std::is_base_of< Opm::FvBasePrimaryVariables< TypeTag >, + EwomsPrimaryVariable< TypeTag > > :: value > + { + }; +} + +#endif diff --git a/opm/models/discretization/common/fvbaseproblem.hh b/opm/models/discretization/common/fvbaseproblem.hh new file mode 100644 index 00000000000..76701c007ee --- /dev/null +++ b/opm/models/discretization/common/fvbaseproblem.hh @@ -0,0 +1,839 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseProblem + */ +#ifndef EWOMS_FV_BASE_PROBLEM_HH +#define EWOMS_FV_BASE_PROBLEM_HH + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +namespace Opm::Properties { + +template +struct NewtonMethod; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief Base class for all problems which use a finite volume spatial discretization. + * + * \note All quantities are specified assuming a threedimensional world. Problems + * discretized using 2D grids are assumed to be extruded by \f$1 m\f$ and 1D grids + * are assumed to have a cross section of \f$1m \times 1m\f$. + */ +template +class FvBaseProblem +{ +private: + using Implementation = GetPropType; + using GridView = GetPropType; + + static const int vtkOutputFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + using Model = GetPropType; + using Scalar = GetPropType; + using Simulator = GetPropType; + using ThreadManager = GetPropType; + using NewtonMethod = GetPropType; + + using VertexMapper = GetPropType; + using ElementMapper = GetPropType; + + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using PrimaryVariables = GetPropType; + using Constraints = GetPropType; + + enum { + dim = GridView::dimension, + dimWorld = GridView::dimensionworld + }; + + using Element = typename GridView::template Codim<0>::Entity; + using Vertex = typename GridView::template Codim::Entity; + using VertexIterator = typename GridView::template Codim::Iterator; + + using CoordScalar = typename GridView::Grid::ctype; + using GlobalPosition = Dune::FieldVector; + +public: + // the default restriction and prolongation for adaptation is simply an empty one + using RestrictProlongOperator = EmptyRestrictProlong ; + +private: + // copying a problem is not a good idea + FvBaseProblem(const FvBaseProblem& ) = delete; + +public: + /*! + * \copydoc Doxygen::defaultProblemConstructor + * + * \param simulator The time manager of the simulation + * \param gridView The view on the DUNE grid which ought to be + * used (normally the leaf grid view) + */ + FvBaseProblem(Simulator& simulator) + : nextTimeStepSize_(0.0) + , gridView_(simulator.gridView()) + , elementMapper_(gridView_, Dune::mcmgElementLayout()) + , vertexMapper_(gridView_, Dune::mcmgVertexLayout()) + , boundingBoxMin_(std::numeric_limits::max()) + , boundingBoxMax_(-std::numeric_limits::max()) + , simulator_(simulator) + , defaultVtkWriter_(0) + { + // calculate the bounding box of the local partition of the grid view + VertexIterator vIt = gridView_.template begin(); + const VertexIterator vEndIt = gridView_.template end(); + for (; vIt!=vEndIt; ++vIt) { + for (unsigned i=0; igeometry().corner(0)[i]); + boundingBoxMax_[i] = std::max(boundingBoxMax_[i], vIt->geometry().corner(0)[i]); + } + } + + // communicate to get the bounding box of the whole domain + for (unsigned i = 0; i < dim; ++i) { + boundingBoxMin_[i] = gridView_.comm().min(boundingBoxMin_[i]); + boundingBoxMax_[i] = gridView_.comm().max(boundingBoxMax_[i]); + } + + if (enableVtkOutput_()) { + bool asyncVtkOutput = + simulator_.gridView().comm().size() == 1 && + Parameters::Get(); + + // asynchonous VTK output currently does not work in conjunction with grid + // adaptivity because the async-IO code assumes that the grid stays + // constant. complain about that case. + bool enableGridAdaptation = Parameters::Get(); + if (asyncVtkOutput && enableGridAdaptation) + throw std::runtime_error("Asynchronous VTK output currently cannot be used " + "at the same time as grid adaptivity"); + + std::string outputDir = asImp_().outputDir(); + + defaultVtkWriter_ = + new VtkMultiWriter(asyncVtkOutput, gridView_, outputDir, asImp_().name()); + } + } + + ~FvBaseProblem() + { delete defaultVtkWriter_; } + + /*! + * \brief Registers all available parameters for the problem and + * the model. + */ + static void registerParameters() + { + Model::registerParameters(); + Parameters::Register> + ("The maximum size to which all time steps are limited to [s]"); + Parameters::Register> + ("The minimum size to which all time steps are limited to [s]"); + Parameters::Register + ("The maximum number of divisions by two of the timestep size " + "before the simulation bails out"); + Parameters::Register + ("Dispatch a separate thread to write the VTK output"); + Parameters::Register + ("Continue with a non-converged solution instead of giving up " + "if we encounter a time step size smaller than the minimum time " + "step size."); + } + + /*! + * \brief Return if the storage term of the first iteration is identical to the storage + * term for the solution of the previous time step. + * + * This is only relevant if the storage cache is enabled and is usually the case, + * i.e., this method only needs to be overwritten in rare corner cases. + */ + bool recycleFirstIterationStorage() const + { return true; } + + /*! + * \brief Determine the directory for simulation output. + * + * The actual problem may chose to transform the value of the OutputDir parameter and + * it can e.g. choose to create the directory on demand if it does not exist. The + * default behaviour is to just return the OutputDir parameter and to throw an + * exception if no directory with this name exists. + */ + std::string outputDir() const + { + std::string outputDir = Parameters::Get(); + + if (outputDir.empty()) + outputDir = "."; + + // TODO: replace this by std::filesystem once we require c++-2017 + struct stat st; + if (::stat(outputDir.c_str(), &st) != 0) + throw std::runtime_error("Could not access output directory '"+outputDir+"':" + +strerror(errno)); + if (!S_ISDIR(st.st_mode)) + throw std::runtime_error("Path to output directory '"+outputDir+"' exists but is not a directory"); + + if (access(outputDir.c_str(), W_OK) != 0) + throw std::runtime_error("Output directory '"+outputDir+"' exists but is not writeable"); + + return outputDir; + } + + /*! + * \brief Returns the string that is printed before the list of command line + * parameters in the help message. + * + * If the returned string is empty, no help message will be generated. + */ + static std::string helpPreamble(int, + const char **argv) + { + std::string desc = Implementation::briefDescription(); + if (!desc.empty()) + desc = desc + "\n"; + + return + "Usage: "+std::string(argv[0]) + " [OPTIONS]\n" + + desc; + } + + /*! + * \brief Returns a human readable description of the problem for the help message + * + * The problem description is printed as part of the --help message. It is optional + * and should not exceed one or two lines of text. + */ + static std::string briefDescription() + { return ""; } + + // TODO (?): detailedDescription() + + /*! + * \brief Handles positional command line parameters. + * + * Positional parameters are parameters that are not prefixed by any parameter name. + * + * \param seenParams The parameters which have already been seen in the current context + * \param errorMsg If the positional argument cannot be handled, this is the reason why + * \param argc The total number of command line parameters + * \param argv The string value of the command line parameters + * \param paramIdx The index of the positional parameter in the array of all parameters + * \param posParamIdx The number of the positional parameter encountered so far + * + * \return The number of array entries which ought to be skipped before processing + * the next regular parameter. If this is less than 1, it indicated that the + * positional parameter was invalid. + */ + static int handlePositionalParameter(std::set&, + std::string& errorMsg, + int, + const char** argv, + int paramIdx, + int) + { + errorMsg = std::string("Illegal parameter \"")+argv[paramIdx]+"\"."; + return 0; + } + + /*! + * \brief Called by the Opm::Simulator in order to initialize the problem. + * + * If you overload this method don't forget to call ParentType::finishInit() + */ + void finishInit() + { } + + /*! + * \brief Allows to improve the performance by prefetching all data which is + * associated with a given element. + */ + void prefetch(const Element&) const + { + // do nothing by default + } + + /*! + * \brief Handle changes of the grid + */ + void gridChanged() + { +#if DUNE_VERSION_NEWER(DUNE_GRID, 2, 8) + elementMapper_.update(gridView_); + vertexMapper_.update(gridView_); +#else + elementMapper_.update(); + vertexMapper_.update(); +#endif + + if (enableVtkOutput_()) + defaultVtkWriter_->gridChanged(); + } + + /*! + * \brief Evaluate the boundary conditions for a boundary segment. + * + * \param values Stores the fluxes over the boundary segment. + * \param context The object representing the execution context from + * which this method is called. + * \param spaceIdx The local index of the spatial entity which represents the boundary segment. + * \param timeIdx The index used for the time discretization + */ + template + void boundary(BoundaryRateVector&, + const Context&, + unsigned, + unsigned) const + { throw std::logic_error("Problem does not provide a boundary() method"); } + + /*! + * \brief Evaluate the constraints for a control volume. + * + * \param constraints Stores the values of the primary variables at a + * given spatial and temporal location. + * \param context The object representing the execution context from + * which this method is called. + * \param spaceIdx The local index of the spatial entity which represents the boundary segment. + * \param timeIdx The index used for the time discretization + */ + template + void constraints(Constraints&, + const Context&, + unsigned, + unsigned) const + { throw std::logic_error("Problem does not provide a constraints() method"); } + + /*! + * \brief Evaluate the source term for all phases within a given + * sub-control-volume. + * + * \param rate Stores the values of the volumetric creation/anihilition + * rates of the conserved quantities. + * \param context The object representing the execution context from which + * this method is called. + * \param spaceIdx The local index of the spatial entity which represents + * the boundary segment. + * \param timeIdx The index used for the time discretization + */ + template + void source(RateVector&, + const Context&, + unsigned, + unsigned) const + { throw std::logic_error("Problem does not provide a source() method"); } + + /*! + * \brief Evaluate the initial value for a control volume. + * + * \param values Stores the primary variables. + * \param context The object representing the execution context from which + * this method is called. + * \param spaceIdx The local index of the spatial entity which represents + * the boundary segment. + * \param timeIdx The index used for the time discretization + */ + template + void initial(PrimaryVariables&, + const Context&, + unsigned, + unsigned) const + { throw std::logic_error("Problem does not provide a initial() method"); } + + /*! + * \brief Return how much the domain is extruded at a given sub-control volume. + * + * This means the factor by which a lower-dimensional (1D or 2D) + * entity needs to be expanded to get a full dimensional cell. The + * default is 1.0 which means that 1D problems are actually + * thought as pipes with a cross section of 1 m^2 and 2D problems + * are assumed to extend 1 m to the back. + * + * \param context The object representing the execution context from which + * this method is called. + * \param spaceIdx The local index of the spatial entity which represents + * the boundary segment. + * \param timeIdx The index used for the time discretization + */ + template + Scalar extrusionFactor(const Context&, + unsigned, + unsigned) const + { return asImp_().extrusionFactor(); } + + Scalar extrusionFactor() const + { return 1.0; } + + /*! + * \brief Callback used by the model to indicate that the initial solution has been + * determined for all degrees of freedom. + */ + void initialSolutionApplied() + {} + + /*! + * \brief Called at the beginning of an simulation episode. + */ + void beginEpisode() + { } + + /*! + * \brief Called by the simulator before each time integration. + */ + void beginTimeStep() + { } + + /*! + * \brief Called by the simulator before each Newton-Raphson iteration. + */ + void beginIteration() + { } + + /*! + * \brief Called by the simulator after each Newton-Raphson update. + */ + void endIteration() + { } + + /*! + * \brief Called by the simulator after each time integration. + * + * This method is intended to do some post processing of the + * solution. (e.g., some additional output) + */ + void endTimeStep() + { } + + /*! + * \brief Called when the end of an simulation episode is reached. + * + * Typically, a new episode is started in this method. + */ + void endEpisode() + { + std::cerr << "The end of episode " << simulator().episodeIndex() + 1 << " has been " + << "reached, but the problem does not override the endEpisode() method. " + << "Doing nothing!\n"; + } + + /*! + * \brief Called after the simulation has been run sucessfully. + */ + void finalize() + { + const auto& executionTimer = simulator().executionTimer(); + + Scalar executionTime = executionTimer.realTimeElapsed(); + Scalar setupTime = simulator().setupTimer().realTimeElapsed(); + Scalar prePostProcessTime = simulator().prePostProcessTimer().realTimeElapsed(); + Scalar localCpuTime = executionTimer.cpuTimeElapsed(); + Scalar globalCpuTime = executionTimer.globalCpuTimeElapsed(); + Scalar writeTime = simulator().writeTimer().realTimeElapsed(); + Scalar linearizeTime = simulator().linearizeTimer().realTimeElapsed(); + Scalar solveTime = simulator().solveTimer().realTimeElapsed(); + Scalar updateTime = simulator().updateTimer().realTimeElapsed(); + unsigned numProcesses = static_cast(this->gridView().comm().size()); + unsigned threadsPerProcess = ThreadManager::maxThreads(); + if (gridView().comm().rank() == 0) { + std::cout << std::setprecision(3) + << "Simulation of problem '" << asImp_().name() << "' finished.\n" + << "\n" + << "------------------------ Timing ------------------------\n" + << "Setup time: " << setupTime << " seconds" << Simulator::humanReadableTime(setupTime) + << ", " << setupTime/(executionTime + setupTime)*100 << "%\n" + << "Simulation time: " << executionTime << " seconds" << Simulator::humanReadableTime(executionTime) + << ", " << executionTime/(executionTime + setupTime)*100 << "%\n" + << " Linearization time: " << linearizeTime << " seconds" << Simulator::humanReadableTime(linearizeTime) + << ", " << linearizeTime/executionTime*100 << "%\n" + << " Linear solve time: " << solveTime << " seconds" << Simulator::humanReadableTime(solveTime) + << ", " << solveTime/executionTime*100 << "%\n" + << " Newton update time: " << updateTime << " seconds" << Simulator::humanReadableTime(updateTime) + << ", " << updateTime/executionTime*100 << "%\n" + << " Pre/postprocess time: " << prePostProcessTime << " seconds" << Simulator::humanReadableTime(prePostProcessTime) + << ", " << prePostProcessTime/executionTime*100 << "%\n" + << " Output write time: " << writeTime << " seconds" << Simulator::humanReadableTime(writeTime) + << ", " << writeTime/executionTime*100 << "%\n" + << "First process' simulation CPU time: " << localCpuTime << " seconds" << Simulator::humanReadableTime(localCpuTime) << "\n" + << "Number of processes: " << numProcesses << "\n" + << "Threads per processes: " << threadsPerProcess << "\n" + << "Total CPU time: " << globalCpuTime << " seconds" << Simulator::humanReadableTime(globalCpuTime) << "\n" + << "\n" + << "----------------------------------------------------------------\n" + << std::endl; + } + } + + /*! + * \brief Called by Opm::Simulator in order to do a time + * integration on the model. + */ + void timeIntegration() + { + unsigned maxFails = asImp_().maxTimeIntegrationFailures(); + Scalar minTimeStepSize = asImp_().minTimeStepSize(); + + std::string errorMessage; + for (unsigned i = 0; i < maxFails; ++i) { + bool converged = model().update(); + if (converged) + return; + + Scalar dt = simulator().timeStepSize(); + Scalar nextDt = dt / 2.0; + if (dt < minTimeStepSize*(1 + 1e-9)) { + if (asImp_().continueOnConvergenceError()) { + if (gridView().comm().rank() == 0) + std::cout << "Newton solver did not converge with minimum time step of " + << dt << " seconds. Continuing with unconverged solution!\n" + << std::flush; + return; + } + else { + errorMessage = + "Time integration did not succeed with the minumum time step size of " + + std::to_string(double(minTimeStepSize)) + " seconds. Giving up!"; + break; // give up: we can't make the time step smaller anymore! + } + } + else if (nextDt < minTimeStepSize) + nextDt = minTimeStepSize; + simulator().setTimeStepSize(nextDt); + + // update failed + if (gridView().comm().rank() == 0) + std::cout << "Newton solver did not converge with " + << "dt=" << dt << " seconds. Retrying with time step of " + << nextDt << " seconds\n" << std::flush; + } + + if (errorMessage.empty()) + errorMessage = + "Newton solver didn't converge after " + +std::to_string(maxFails)+" time-step divisions. dt=" + +std::to_string(double(simulator().timeStepSize())); + throw std::runtime_error(errorMessage); + } + + /*! + * \brief Returns the minimum allowable size of a time step. + */ + Scalar minTimeStepSize() const + { return Parameters::Get>(); } + + /*! + * \brief Returns the maximum number of subsequent failures for the time integration + * before giving up. + */ + unsigned maxTimeIntegrationFailures() const + { return Parameters::Get(); } + + /*! + * \brief Returns if we should continue with a non-converged solution instead of + * giving up if we encounter a time step size smaller than the minimum time + * step size. + */ + bool continueOnConvergenceError() const + { return Parameters::Get(); } + + /*! + * \brief Impose the next time step size to be used externally. + */ + void setNextTimeStepSize(Scalar dt) + { nextTimeStepSize_ = dt; } + + /*! + * \brief Called by Opm::Simulator whenever a solution for a + * time step has been computed and the simulation time has + * been updated. + */ + Scalar nextTimeStepSize() const + { + if (nextTimeStepSize_ > 0.0) + return nextTimeStepSize_; + + Scalar dtNext = std::min(Parameters::Get>(), + newtonMethod().suggestTimeStepSize(simulator().timeStepSize())); + + if (dtNext < simulator().maxTimeStepSize() + && simulator().maxTimeStepSize() < dtNext*2) + { + dtNext = simulator().maxTimeStepSize()/2 * 1.01; + } + + return dtNext; + } + + /*! + * \brief Returns true if a restart file should be written to + * disk. + * + * The default behavior is to write one restart file every 10 time + * steps. This method should be overwritten by the + * implementation if the default behavior is deemed insufficient. + */ + bool shouldWriteRestartFile() const + { + return simulator().timeStepIndex() > 0 && + (simulator().timeStepIndex() % 10 == 0); + } + + /*! + * \brief Returns true if the current solution should be written to + * disk (i.e. as a VTK file) + * + * The default behavior is to write out the solution for every + * time step. This method is should be overwritten by the + * implementation if the default behavior is deemed insufficient. + */ + bool shouldWriteOutput() const + { return true; } + + /*! + * \brief Called by the simulator after everything which can be + * done about the current time step is finished and the + * model should be prepared to do the next time integration. + */ + void advanceTimeLevel() + { model().advanceTimeLevel(); } + + /*! + * \brief The problem name. + * + * This is used as a prefix for files generated by the simulation. + * It is highly recommend to overwrite this method in the concrete + * problem which is simulated. + */ + std::string name() const + { return "sim"; } + + /*! + * \brief The GridView which used by the problem. + */ + const GridView& gridView() const + { return gridView_; } + + /*! + * \brief The coordinate of the corner of the GridView's bounding + * box with the smallest values. + */ + const GlobalPosition& boundingBoxMin() const + { return boundingBoxMin_; } + + /*! + * \brief The coordinate of the corner of the GridView's bounding + * box with the largest values. + */ + const GlobalPosition& boundingBoxMax() const + { return boundingBoxMax_; } + + /*! + * \brief Returns the mapper for vertices to indices. + */ + const VertexMapper& vertexMapper() const + { return vertexMapper_; } + + /*! + * \brief Returns the mapper for elements to indices. + */ + const ElementMapper& elementMapper() const + { return elementMapper_; } + + /*! + * \brief Returns Simulator object used by the simulation + */ + Simulator& simulator() + { return simulator_; } + + /*! + * \copydoc simulator() + */ + const Simulator& simulator() const + { return simulator_; } + + /*! + * \brief Returns numerical model used for the problem. + */ + Model& model() + { return simulator_.model(); } + + /*! + * \copydoc model() + */ + const Model& model() const + { return simulator_.model(); } + + /*! + * \brief Returns object which implements the Newton method. + */ + NewtonMethod& newtonMethod() + { return model().newtonMethod(); } + + /*! + * \brief Returns object which implements the Newton method. + */ + const NewtonMethod& newtonMethod() const + { return model().newtonMethod(); } + // \} + + /*! + * \brief return restriction and prolongation operator + * \note This method has to be overloaded by the implementation. + */ + RestrictProlongOperator restrictProlongOperator() + { + return RestrictProlongOperator(); + } + + /*! + * \brief Mark grid cells for refinement or coarsening + * \note This method has to be overloaded in derived classes to proper implement + * marking of grid entities. + * + * \return number of marked cells (default is 0) + */ + unsigned markForGridAdaptation() + { + return 0; + } + + /*! + * \brief This method writes the complete state of the problem + * to the harddisk. + * + * The file will start with the prefix returned by the name() + * method, has the current time of the simulation clock in it's + * name and uses the extension .ers. (Ewoms ReStart + * file.) See Opm::Restart for details. + * + * \tparam Restarter The serializer type + * + * \param res The serializer object + */ + template + void serialize(Restarter& res) + { + if (enableVtkOutput_()) + defaultVtkWriter_->serialize(res); + } + + /*! + * \brief This method restores the complete state of the problem + * from disk. + * + * It is the inverse of the serialize() method. + * + * \tparam Restarter The deserializer type + * + * \param res The deserializer object + */ + template + void deserialize(Restarter& res) + { + if (enableVtkOutput_()) + defaultVtkWriter_->deserialize(res); + } + + /*! + * \brief Write the relevant secondary variables of the current + * solution into an VTK output file. + * + * \param verbose Specify if a message should be printed whenever a file is written + */ + void writeOutput(bool verbose = true) + { + if (!enableVtkOutput_()) + return; + + if (verbose && gridView().comm().rank() == 0) + std::cout << "Writing visualization results for the current time step.\n" + << std::flush; + + // calculate the time _after_ the time was updated + Scalar t = simulator().time() + simulator().timeStepSize(); + + defaultVtkWriter_->beginWrite(t); + model().prepareOutputFields(); + model().appendOutputFields(*defaultVtkWriter_); + defaultVtkWriter_->endWrite(); + + } + + /*! + * \brief Method to retrieve the VTK writer which should be used + * to write the default ouput after each time step to disk. + */ + VtkMultiWriter& defaultVtkWriter() const + { return defaultVtkWriter_; } + +protected: + Scalar nextTimeStepSize_; + +private: + bool enableVtkOutput_() const + { return Parameters::Get(); } + + //! Returns the implementation of the problem (i.e. static polymorphism) + Implementation& asImp_() + { return *static_cast(this); } + + //! \copydoc asImp_() + const Implementation& asImp_() const + { return *static_cast(this); } + + // Grid management stuff + const GridView gridView_; + ElementMapper elementMapper_; + VertexMapper vertexMapper_; + GlobalPosition boundingBoxMin_; + GlobalPosition boundingBoxMax_; + + // Attributes required for the actual simulation + Simulator& simulator_; + mutable VtkMultiWriter *defaultVtkWriter_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/fvbaseproperties.hh b/opm/models/discretization/common/fvbaseproperties.hh new file mode 100644 index 00000000000..bcd773a3cd2 --- /dev/null +++ b/opm/models/discretization/common/fvbaseproperties.hh @@ -0,0 +1,265 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup FiniteVolumeDiscretizations + * + * \brief Declare the properties used by the infrastructure code of + * the finite volume discretizations. + */ +#ifndef EWOMS_FV_BASE_PROPERTIES_HH +#define EWOMS_FV_BASE_PROPERTIES_HH + +#include + +namespace Opm::Properties { + +namespace TTag { +struct FvBaseNewtonMethod; +struct FiniteDifferenceLocalLinearizer; +struct ParallelBiCGStabLinearSolver; +} + +namespace TTag { + +//! The type tag for models based on the finite volume schemes +struct FvBaseDiscretization +{ using InheritsFrom = std::tuple; }; + +} // namespace TTag + + +template +struct LinearSolverSplice { using type = UndefinedProperty; }; +template +struct LocalLinearizerSplice { using type = UndefinedProperty; }; + +/*! + * \brief Representation of a function evaluation and all necessary derivatives with + * regard to the intensive quantities of the primary variables. + * + * Depending on the chosen linearization method, this property may be the same as the + * "Scalar" property (if the finite difference linearizer is used), or it may be more + * complex (for the linearizer which uses automatic differentiation). + */ +template +struct Evaluation { using type = UndefinedProperty; }; + +//! The class describing the stencil of the spatial discretization +template +struct Stencil { using type = UndefinedProperty; }; + +//! The class describing the discrete function space when dune-fem is used, otherwise it points to the stencil class +template +struct DiscreteFunctionSpace { using type = UndefinedProperty; }; + +template +struct DiscreteFunction { using type = UndefinedProperty; }; + +//! The type of the problem +template +struct Problem { using type = UndefinedProperty; }; +//! The type of the base class for all problems which use this model +template +struct BaseProblem { using type = UndefinedProperty; }; + +//! The type of the spatial discretization used by the model +template +struct Discretization { using type = UndefinedProperty; }; +//! The discretization specific part of the local residual +template +struct DiscLocalResidual { using type = UndefinedProperty; }; +//! The type of the local residual function +template +struct LocalResidual { using type = UndefinedProperty; }; +//! The type of the local linearizer +template +struct LocalLinearizer { using type = UndefinedProperty; }; +//! Specify if elements that do not belong to the local process' grid partition should be +//! skipped +template +struct LinearizeNonLocalElements { using type = UndefinedProperty; }; + +//! Linearizes the global non-linear system of equations +template +struct BaseLinearizer { using type = UndefinedProperty; }; + +//! A vector of holding a quantity for each equation (usually at a given spatial location) +template +struct EqVector { using type = UndefinedProperty; }; +//! A vector of holding a quantity for each equation for each DOF of an element +template +struct ElementEqVector { using type = UndefinedProperty; }; + +//! Vector containing volumetric or areal rates of quantities +template +struct RateVector { using type = UndefinedProperty; }; +//! Type of object for specifying boundary conditions +template +struct BoundaryRateVector { using type = UndefinedProperty; }; +//! The class which represents a constraint degree of freedom +template +struct Constraints { using type = UndefinedProperty; }; + +//! Vector containing all primary variables of the grid +template +struct SolutionVector { using type = UndefinedProperty; }; + +//! A vector of primary variables within a sub-control volume +template +struct PrimaryVariables { using type = UndefinedProperty; }; +//! The secondary variables within a sub-control volume +template +struct IntensiveQuantities { using type = UndefinedProperty; }; +//! The discretization specific part of the intensive quantities +template +struct DiscIntensiveQuantities { using type = UndefinedProperty; }; + +//! The secondary variables of all degrees of freedom in an element's stencil +template +struct ElementContext { using type = UndefinedProperty; }; +//! The secondary variables of a boundary segment +template +struct BoundaryContext { using type = UndefinedProperty; }; +//! The secondary variables of a constraint degree of freedom +template +struct ConstraintsContext { using type = UndefinedProperty; }; +//! Data required to calculate a flux over a face +template +struct ExtensiveQuantities { using type = UndefinedProperty; }; +//! Calculates gradients of arbitrary quantities at flux integration points +template +struct GradientCalculator { using type = UndefinedProperty; }; + +//! The part of the intensive quantities which is specific to the spatial discretization +template +struct DiscBaseIntensiveQuantities { using type = UndefinedProperty; }; + +//! The part of the extensive quantities which is specific to the spatial discretization +template +struct DiscExtensiveQuantities { using type = UndefinedProperty; }; + +//! The part of the VTK ouput modules which is specific to the spatial discretization +template +struct DiscBaseOutputModule { using type = UndefinedProperty; }; + +//! The class to create grid communication handles +template +struct GridCommHandleFactory { using type = UndefinedProperty; }; + +/*! + * \brief The OpenMP threads manager + */ +template +struct ThreadManager { using type = UndefinedProperty; }; + +//! use locking to prevent race conditions when linearizing the global system of +//! equations in multi-threaded mode. (setting this property to true is always save, but +//! it may slightly deter performance in multi-threaded simlations and some +//! discretizations do not need this.) +template +struct UseLinearizationLock { using type = UndefinedProperty; }; + +// high-level simulation control + +/*! + * \brief Specify the format the VTK output is written to disk + * + * Possible values are: + * - Dune::VTK::ascii (default) + * - Dune::VTK::base64 + * - Dune::VTK::appendedraw + * - Dune::VTK::appendedbase64 + */ +template +struct VtkOutputFormat { using type = UndefinedProperty; }; + +//! Specify whether the some degrees of fredom can be constraint +template +struct EnableConstraints { using type = UndefinedProperty; }; + +// mappers from local to global DOF indices + +/*! + * \brief The mapper to find the global index of a vertex. + */ +template +struct VertexMapper { using type = UndefinedProperty; }; + +/*! + * \brief The mapper to find the global index of an element. + */ +template +struct ElementMapper { using type = UndefinedProperty; }; + +/*! + * \brief The mapper to find the global index of a degree of freedom. + */ +template +struct DofMapper { using type = UndefinedProperty; }; + +/*! + * \brief The history size required by the time discretization + */ +template +struct TimeDiscHistorySize { using type = UndefinedProperty; }; + +/*! + * \brief Specify whether the storage terms use extensive quantities or not. + * + * Most models don't need this, but the (Navier-)Stokes ones do... + */ +template +struct ExtensiveStorageTerm { using type = UndefinedProperty; }; + +//! \brief Specify whether to use volumetric residuals or not +template +struct UseVolumetricResidual { using type = UndefinedProperty; }; + +//! Specify if experimental features should be enabled or not. +template +struct EnableExperiments { using type = UndefinedProperty; }; + +// Set defaults + +//! set the splices for the finite volume discretizations +template +struct Splices +{ + using type = std::tuple, + GetSplicePropType>; +}; + +//! use a parallel BiCGStab linear solver by default +template +struct LinearSolverSplice +{ using type = TTag::ParallelBiCGStabLinearSolver; }; + +//! by default, use finite differences to linearize the system of PDEs +template +struct LocalLinearizerSplice +{ using type = TTag::FiniteDifferenceLocalLinearizer; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/discretization/common/linearizationtype.hh b/opm/models/discretization/common/linearizationtype.hh new file mode 100644 index 00000000000..b4f0cbea45c --- /dev/null +++ b/opm/models/discretization/common/linearizationtype.hh @@ -0,0 +1,44 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseLinearizer + */ +#ifndef EWOMS_FV_BASE_LINEARIZATIONTYPE_HH +#define EWOMS_FV_BASE_LINEARIZATIONTYPE_HH + +namespace Opm +{ + +struct LinearizationType +{ + enum VarType { implicit, pressure, seqtransport }; + VarType type = implicit; + unsigned int time = 0; +}; + +} // namespace Opm + + +#endif diff --git a/opm/models/discretization/common/restrictprolong.hh b/opm/models/discretization/common/restrictprolong.hh new file mode 100644 index 00000000000..441d04b28f5 --- /dev/null +++ b/opm/models/discretization/common/restrictprolong.hh @@ -0,0 +1,203 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef EWOMS_COPYRESTRICTPROLONG_HH +#define EWOMS_COPYRESTRICTPROLONG_HH + +#if HAVE_DUNE_FEM +#include +#endif + +namespace Opm +{ + template < class Grid, class Container > + class CopyRestrictProlong; + + template < class Grid, class Container > + struct CopyRestrictProlongTraits + { + using DomainFieldType = typename Grid::ctype; + using RestProlImp = CopyRestrictProlong< Grid, Container > ; + }; + + template< class Grid, class Container > + class CopyRestrictProlong +#if HAVE_DUNE_FEM + : public Dune::Fem::RestrictProlongInterfaceDefault< CopyRestrictProlongTraits< Grid, Container > > +#endif + { + using ThisType = CopyRestrictProlong< Grid, Container >; + + Container& container_; + public: + using DomainFieldType = typename Grid::ctype; + + explicit CopyRestrictProlong( Container& container ) + : container_( container ) + {} + + /** \brief explicit set volume ratio of son and father + * + * \param[in] weight volume of son / volume of father + * + * \note If this ratio is set, it is assume to be constant. + */ + template + void setFatherChildWeight(const Field&) const + {} + + //! restrict data to father + template< class Entity > + void restrictLocal ( const Entity& father, const Entity& son, bool initialize ) const + { + container_.resize(); + assert( container_.codimension() == 0 ); + if( initialize ) + { + // copy values from son to father (only once) + container_[ father ] = container_[ son ]; + } + } + + //! restrict data to father + template< class Entity, class LocalGeometry > + void restrictLocal(const Entity& father, + const Entity& son, + const LocalGeometry&, + bool initialize) const + { restrictLocal(father, son, initialize); } + + //! prolong data to children + template< class Entity > + void prolongLocal(const Entity& father, + const Entity& son, + bool) const + { + container_.resize(); + assert( container_.codimension() == 0 ); + // copy values from father to son all sons + container_[ son ] = container_[ father ]; + } + + //! prolong data to children + template< class Entity, class LocalGeometry > + void prolongLocal(const Entity& father, + const Entity& son, + const LocalGeometry&, + bool initialize) const + { prolongLocal(father, son, initialize); } + + /** \brief add discrete function to communicator + * \param[in] comm Communicator to add the discrete functions to + */ + template< class Communicator > + void addToList(Communicator&) + { + // TODO + } + + /** \brief add discrete function to load balancer + * \param[in] lb LoadBalancer to add the discrete functions to + */ + template< class LoadBalancer > + void addToLoadBalancer(LoadBalancer&) + { + // TODO + } + + }; + + + class EmptyRestrictProlong; + + struct EmptyRestrictProlongTraits + { + using DomainFieldType = double ; + using RestProlImp = EmptyRestrictProlong ; + }; + + class EmptyRestrictProlong +#if HAVE_DUNE_FEM + : public Dune::Fem::RestrictProlongInterfaceDefault< EmptyRestrictProlongTraits > +#endif + { + using ThisType = EmptyRestrictProlong; + + public: + /** \brief explicit set volume ratio of son and father + * + * \param[in] weight volume of son / volume of father + * + * \note If this ratio is set, it is assume to be constant. + */ + template + void setFatherChildWeight(const Field&) const + { } + + //! restrict data to father + template< class Entity > + void restrictLocal(const Entity&, + const Entity&, + bool) const + { } + + //! restrict data to father + template< class Entity, class LocalGeometry > + void restrictLocal(const Entity&, + const Entity&, + const LocalGeometry&, + bool) const + { } + + //! prolong data to children + template< class Entity > + void prolongLocal(const Entity&, + const Entity&, + bool) const + { } + + //! prolong data to children + template< class Entity, class LocalGeometry > + void prolongLocal(const Entity&, + const Entity&, + const LocalGeometry&, + bool) const + { } + + /** \brief add discrete function to communicator + * \param[in] comm Communicator to add the discrete functions to + */ + template< class Communicator > + void addToList(Communicator&) + { } + + /** \brief add discrete function to load balancer + * \param[in] lb LoadBalancer to add the discrete functions to + */ + template< class LoadBalancer > + void addToLoadBalancer(LoadBalancer&) + { } + }; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/common/tpfalinearizer.hh b/opm/models/discretization/common/tpfalinearizer.hh new file mode 100644 index 00000000000..07662aa01f7 --- /dev/null +++ b/opm/models/discretization/common/tpfalinearizer.hh @@ -0,0 +1,955 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FvBaseLinearizer + */ +#ifndef TPFA_LINEARIZER_HH +#define TPFA_LINEARIZER_HH + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include // current_exception, rethrow_exception +#include +#include +#include +#include +#include + +namespace Opm::Parameters { + +struct SeparateSparseSourceTerms { static constexpr bool value = false; }; + +} // namespace Opm::Parameters + +namespace Opm { + +// forward declarations +template +class EcfvDiscretization; + +/*! + * \ingroup FiniteVolumeDiscretizations + * + * \brief The common code for the linearizers of non-linear systems of equations + * + * This class assumes that these system of equations to be linearized are stemming from + * models that use an finite volume scheme for spatial discretization and an Euler + * scheme for time discretization. + */ +template +class TpfaLinearizer +{ +//! \cond SKIP_THIS + using Model = GetPropType; + using Problem = GetPropType; + using Simulator = GetPropType; + using GridView = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + using SparseMatrixAdapter = GetPropType; + using EqVector = GetPropType; + using Constraints = GetPropType; + using Stencil = GetPropType; + using LocalResidual = GetPropType; + using IntensiveQuantities = GetPropType; + + using Element = typename GridView::template Codim<0>::Entity; + using ElementIterator = typename GridView::template Codim<0>::Iterator; + + using Vector = GlobalEqVector; + + enum { numEq = getPropValue() }; + enum { historySize = getPropValue() }; + enum { dimWorld = GridView::dimensionworld }; + + using MatrixBlock = typename SparseMatrixAdapter::MatrixBlock; + using VectorBlock = Dune::FieldVector; + using ADVectorBlock = GetPropType; + + static const bool linearizeNonLocalElements = getPropValue(); + static const bool enableEnergy = getPropValue(); + static const bool enableDiffusion = getPropValue(); + + // copying the linearizer is not a good idea + TpfaLinearizer(const TpfaLinearizer&) = delete; +//! \endcond + +public: + TpfaLinearizer() + : jacobian_() + { + simulatorPtr_ = 0; + separateSparseSourceTerms_ = Parameters::Get(); + } + + ~TpfaLinearizer() + { + } + + /*! + * \brief Register all run-time parameters for the Jacobian linearizer. + */ + static void registerParameters() + { + Parameters::Register + ("Treat well source terms all in one go, instead of on a cell by cell basis."); + } + + /*! + * \brief Initialize the linearizer. + * + * At this point we can assume that all objects in the simulator + * have been allocated. We cannot assume that they are fully + * initialized, though. + * + * \copydetails Doxygen::simulatorParam + */ + void init(Simulator& simulator) + { + simulatorPtr_ = &simulator; + eraseMatrix(); + } + + /*! + * \brief Causes the Jacobian matrix to be recreated from scratch before the next + * iteration. + * + * This method is usally called if the sparsity pattern has changed for some + * reason. (e.g. by modifications of the grid or changes of the auxiliary equations.) + */ + void eraseMatrix() + { + jacobian_.reset(); + } + + /*! + * \brief Linearize the full system of non-linear equations. + * + * The linearizationType() controls the scheme used and the focus + * time index. The default is fully implicit scheme, and focus index + * equal to 0, i.e. current time (end of step). + * + * This linearizes the spatial domain and all auxiliary equations. + */ + void linearize() + { + linearizeDomain(); + linearizeAuxiliaryEquations(); + } + + /*! + * \brief Linearize the part of the non-linear system of equations that is associated + * with the spatial domain. + * + * That means that the global Jacobian of the residual is assembled and the residual + * is evaluated for the current solution. + * + * The current state of affairs (esp. the previous and the current solutions) is + * represented by the model object. + */ + void linearizeDomain() + { + int succeeded; + try { + linearizeDomain(fullDomain_); + succeeded = 1; + } + catch (const std::exception& e) + { + std::cout << "rank " << simulator_().gridView().comm().rank() + << " caught an exception while linearizing:" << e.what() + << "\n" << std::flush; + succeeded = 0; + } + catch (...) + { + std::cout << "rank " << simulator_().gridView().comm().rank() + << " caught an exception while linearizing" + << "\n" << std::flush; + succeeded = 0; + } + succeeded = simulator_().gridView().comm().min(succeeded); + + if (!succeeded) + throw NumericalProblem("A process did not succeed in linearizing the system"); + } + + /*! + * \brief Linearize the part of the non-linear system of equations that is associated + * with a part of the spatial domain. + * + * That means that the Jacobian of the residual is assembled and the residual + * is evaluated for the current solution, on the domain passed in as argument. + * + * The current state of affairs (esp. the previous and the current solutions) is + * represented by the model object. + */ + template + void linearizeDomain(const SubDomainType& domain) + { + OPM_TIMEBLOCK(linearizeDomain); + // we defer the initialization of the Jacobian matrix until here because the + // auxiliary modules usually assume the problem, model and grid to be fully + // initialized... + if (!jacobian_) + initFirstIteration_(); + + // Called here because it is no longer called from linearize_(). + if (domain.cells.size() == model_().numTotalDof()) { + // We are on the full domain. + resetSystem_(); + } else { + resetSystem_(domain); + } + + linearize_(domain); + } + + void finalize() + { jacobian_->finalize(); } + + /*! + * \brief Linearize the part of the non-linear system of equations that is associated + * with the spatial domain. + */ + void linearizeAuxiliaryEquations() + { + OPM_TIMEBLOCK(linearizeAuxilaryEquations); + // flush possible local caches into matrix structure + jacobian_->commit(); + + auto& model = model_(); + const auto& comm = simulator_().gridView().comm(); + for (unsigned auxModIdx = 0; auxModIdx < model.numAuxiliaryModules(); ++auxModIdx) { + bool succeeded = true; + try { + model.auxiliaryModule(auxModIdx)->linearize(*jacobian_, residual_); + } + catch (const std::exception& e) { + succeeded = false; + + std::cout << "rank " << simulator_().gridView().comm().rank() + << " caught an exception while linearizing:" << e.what() + << "\n" << std::flush; + } + + succeeded = comm.min(succeeded); + + if (!succeeded) + throw NumericalProblem("linearization of an auxiliary equation failed"); + } + } + + /*! + * \brief Return constant reference to global Jacobian matrix backend. + */ + const SparseMatrixAdapter& jacobian() const + { return *jacobian_; } + + SparseMatrixAdapter& jacobian() + { return *jacobian_; } + + /*! + * \brief Return constant reference to global residual vector. + */ + const GlobalEqVector& residual() const + { return residual_; } + + GlobalEqVector& residual() + { return residual_; } + + void setLinearizationType(LinearizationType linearizationType){ + linearizationType_ = linearizationType; + }; + + const LinearizationType& getLinearizationType() const{ + return linearizationType_; + }; + + /*! + * \brief Return constant reference to the flowsInfo. + * + * (This object is only non-empty if the FLOWS keyword is true.) + */ + const auto& getFlowsInfo() const{ + + return flowsInfo_; + } + + /*! + * \brief Return constant reference to the floresInfo. + * + * (This object is only non-empty if the FLORES keyword is true.) + */ + const auto& getFloresInfo() const{ + + return floresInfo_; + } + + /*! + * \brief Return constant reference to the velocityInfo. + * + * (This object is only non-empty if the DISPERC keyword is true.) + */ + const auto& getVelocityInfo() const{ + + return velocityInfo_; + } + + void updateDiscretizationParameters() + { + updateStoredTransmissibilities(); + } + + void updateBoundaryConditionData() { + for (auto& bdyInfo : boundaryInfo_) { + const auto [type, massrateAD] = problem_().boundaryCondition(bdyInfo.cell, bdyInfo.dir); + + // Strip the unnecessary (and zero anyway) derivatives off massrate. + VectorBlock massrate(0.0); + for (size_t ii = 0; ii < massrate.size(); ++ii) { + massrate[ii] = massrateAD[ii].value(); + } + if (type != BCType::NONE) { + const auto& exFluidState = problem_().boundaryFluidState(bdyInfo.cell, bdyInfo.dir); + bdyInfo.bcdata.type = type; + bdyInfo.bcdata.massRate = massrate; + bdyInfo.bcdata.exFluidState = exFluidState; + } + } + } + + /*! + * \brief Returns the map of constraint degrees of freedom. + * + * (This object is only non-empty if the EnableConstraints property is true.) + */ + const std::map constraintsMap() const + { return {}; } + + template + void resetSystem_(const SubDomainType& domain) + { + if (!jacobian_) { + initFirstIteration_(); + } + for (int globI : domain.cells) { + residual_[globI] = 0.0; + jacobian_->clearRow(globI, 0.0); + } + } + +private: + Simulator& simulator_() + { return *simulatorPtr_; } + const Simulator& simulator_() const + { return *simulatorPtr_; } + + Problem& problem_() + { return simulator_().problem(); } + const Problem& problem_() const + { return simulator_().problem(); } + + Model& model_() + { return simulator_().model(); } + const Model& model_() const + { return simulator_().model(); } + + const GridView& gridView_() const + { return problem_().gridView(); } + + void initFirstIteration_() + { + // initialize the BCRS matrix for the Jacobian of the residual function + createMatrix_(); + + // initialize the Jacobian matrix and the vector for the residual function + residual_.resize(model_().numTotalDof()); + resetSystem_(); + + // initialize the sparse tables for Flows and Flores + createFlows_(); + } + + // Construct the BCRS matrix for the Jacobian of the residual function + void createMatrix_() + { + OPM_TIMEBLOCK(createMatrix); + if (!neighborInfo_.empty()) { + // It is ok to call this function multiple times, but it + // should not do anything if already called. + return; + } + const auto& model = model_(); + Stencil stencil(gridView_(), model_().dofMapper()); + + // for the main model, find out the global indices of the neighboring degrees of + // freedom of each primary degree of freedom + using NeighborSet = std::set< unsigned >; + std::vector sparsityPattern(model.numTotalDof()); + const Scalar gravity = problem_().gravity()[dimWorld - 1]; + unsigned numCells = model.numTotalDof(); + neighborInfo_.reserve(numCells, 6 * numCells); + std::vector loc_nbinfo; + for (const auto& elem : elements(gridView_())) { + stencil.update(elem); + + for (unsigned primaryDofIdx = 0; primaryDofIdx < stencil.numPrimaryDof(); ++primaryDofIdx) { + unsigned myIdx = stencil.globalSpaceIndex(primaryDofIdx); + loc_nbinfo.resize(stencil.numDof() - 1); // Do not include the primary dof in neighborInfo_ + + for (unsigned dofIdx = 0; dofIdx < stencil.numDof(); ++dofIdx) { + unsigned neighborIdx = stencil.globalSpaceIndex(dofIdx); + sparsityPattern[myIdx].insert(neighborIdx); + if (dofIdx > 0) { + const Scalar trans = problem_().transmissibility(myIdx, neighborIdx); + const auto scvfIdx = dofIdx - 1; + const auto& scvf = stencil.interiorFace(scvfIdx); + const Scalar area = scvf.area(); + const Scalar Vin = problem_().model().dofTotalVolume(myIdx); + const Scalar Vex = problem_().model().dofTotalVolume(neighborIdx); + const Scalar zIn = problem_().dofCenterDepth(myIdx); + const Scalar zEx = problem_().dofCenterDepth(neighborIdx); + const Scalar dZg = (zIn - zEx)*gravity; + const Scalar thpres = problem_().thresholdPressure(myIdx, neighborIdx); + Scalar inAlpha {0.}; + Scalar outAlpha {0.}; + Scalar diffusivity {0.}; + Scalar dispersivity {0.}; + if constexpr(enableEnergy){ + inAlpha = problem_().thermalHalfTransmissibility(myIdx, neighborIdx); + outAlpha = problem_().thermalHalfTransmissibility(neighborIdx, myIdx); + } + if constexpr(enableDiffusion){ + diffusivity = problem_().diffusivity(myIdx, neighborIdx); + } + if (simulator_().vanguard().eclState().getSimulationConfig().rock_config().dispersion()) { + dispersivity = problem_().dispersivity(myIdx, neighborIdx); + } + const auto dirId = scvf.dirId(); + auto faceDir = dirId < 0 ? FaceDir::DirEnum::Unknown + : FaceDir::FromIntersectionIndex(dirId); + loc_nbinfo[dofIdx - 1] = NeighborInfo{neighborIdx, {trans, area, thpres, dZg, faceDir, Vin, Vex, inAlpha, outAlpha, diffusivity, dispersivity}, nullptr}; + + } + } + neighborInfo_.appendRow(loc_nbinfo.begin(), loc_nbinfo.end()); + if (problem_().nonTrivialBoundaryConditions()) { + for (unsigned bfIndex = 0; bfIndex < stencil.numBoundaryFaces(); ++bfIndex) { + const auto& bf = stencil.boundaryFace(bfIndex); + const int dir_id = bf.dirId(); + // not for NNCs + if (dir_id < 0) + continue; + const auto [type, massrateAD] = problem_().boundaryCondition(myIdx, dir_id); + // Strip the unnecessary (and zero anyway) derivatives off massrate. + VectorBlock massrate(0.0); + for (size_t ii = 0; ii < massrate.size(); ++ii) { + massrate[ii] = massrateAD[ii].value(); + } + const auto& exFluidState = problem_().boundaryFluidState(myIdx, dir_id); + BoundaryConditionData bcdata{type, + massrate, + exFluidState.pvtRegionIndex(), + bfIndex, + bf.area(), + bf.integrationPos()[dimWorld - 1], + exFluidState}; + boundaryInfo_.push_back({myIdx, dir_id, bfIndex, bcdata}); + } + } + } + } + + // add the additional neighbors and degrees of freedom caused by the auxiliary + // equations + size_t numAuxMod = model.numAuxiliaryModules(); + for (unsigned auxModIdx = 0; auxModIdx < numAuxMod; ++auxModIdx) + model.auxiliaryModule(auxModIdx)->addNeighbors(sparsityPattern); + + // allocate raw matrix + jacobian_.reset(new SparseMatrixAdapter(simulator_())); + diagMatAddress_.resize(numCells); + // create matrix structure based on sparsity pattern + jacobian_->reserve(sparsityPattern); + for (unsigned globI = 0; globI < numCells; globI++) { + const auto& nbInfos = neighborInfo_[globI]; + diagMatAddress_[globI] = jacobian_->blockAddress(globI, globI); + for (auto& nbInfo : nbInfos) { + nbInfo.matBlockAddress = jacobian_->blockAddress(nbInfo.neighbor, globI); + } + } + + // Create dummy full domain. + fullDomain_.cells.resize(numCells); + std::iota(fullDomain_.cells.begin(), fullDomain_.cells.end(), 0); + } + + // reset the global linear system of equations. + void resetSystem_() + { + residual_ = 0.0; + // zero all matrix entries + jacobian_->clear(); + } + + // Initialize the flows, flores, and velocity sparse tables + void createFlows_() + { + OPM_TIMEBLOCK(createFlows); + // If FLOWS/FLORES is set in any RPTRST in the schedule, then we initializate the sparse tables + // For now, do the same also if any block flows are requested (TODO: only save requested cells...) + // If DISPERC is in the deck, we initialize the sparse table here as well. + const bool anyFlows = simulator_().problem().eclWriter()->outputModule().anyFlows(); + const bool anyFlores = simulator_().problem().eclWriter()->outputModule().anyFlores(); + const bool enableDispersion = simulator_().vanguard().eclState().getSimulationConfig().rock_config().dispersion(); + if (((!anyFlows || !flowsInfo_.empty()) && (!anyFlores || !floresInfo_.empty())) && !enableDispersion) { + return; + } + const auto& model = model_(); + const auto& nncOutput = simulator_().problem().eclWriter()->getOutputNnc(); + Stencil stencil(gridView_(), model_().dofMapper()); + unsigned numCells = model.numTotalDof(); + std::unordered_multimap> nncIndices; + std::vector loc_flinfo; + std::vector loc_vlinfo; + unsigned int nncId = 0; + VectorBlock flow(0.0); + + // Create a nnc structure to use fast lookup + for (unsigned int nncIdx = 0; nncIdx < nncOutput.size(); ++nncIdx) { + const int ci1 = nncOutput[nncIdx].cell1; + const int ci2 = nncOutput[nncIdx].cell2; + nncIndices.emplace(ci1, std::make_pair(ci2, nncIdx)); + } + + if (anyFlows) { + flowsInfo_.reserve(numCells, 6 * numCells); + } + if (anyFlores) { + floresInfo_.reserve(numCells, 6 * numCells); + } + if (enableDispersion) { + velocityInfo_.reserve(numCells, 6 * numCells); + } + + for (const auto& elem : elements(gridView_())) { + stencil.update(elem); + for (unsigned primaryDofIdx = 0; primaryDofIdx < stencil.numPrimaryDof(); ++primaryDofIdx) { + unsigned myIdx = stencil.globalSpaceIndex(primaryDofIdx); + int numFaces = stencil.numBoundaryFaces() + stencil.numInteriorFaces(); + loc_flinfo.resize(numFaces); + loc_vlinfo.resize(stencil.numDof() - 1); + + for (unsigned dofIdx = 0; dofIdx < stencil.numDof(); ++dofIdx) { + unsigned neighborIdx = stencil.globalSpaceIndex(dofIdx); + if (dofIdx > 0) { + const auto scvfIdx = dofIdx - 1; + const auto& scvf = stencil.interiorFace(scvfIdx); + int faceId = scvf.dirId(); + const int cartMyIdx = simulator_().vanguard().cartesianIndex(myIdx); + const int cartNeighborIdx = simulator_().vanguard().cartesianIndex(neighborIdx); + const auto& range = nncIndices.equal_range(cartMyIdx); + for (auto it = range.first; it != range.second; ++it) { + if (it->second.first == cartNeighborIdx){ + // -1 gives problem since is used for the nncInput from the deck + faceId = -2; + // the index is stored to be used for writting the outputs + nncId = it->second.second; + } + } + loc_flinfo[dofIdx - 1] = FlowInfo{faceId, flow, nncId}; + loc_vlinfo[dofIdx - 1] = VelocityInfo{flow}; + } + } + + for (unsigned bdfIdx = 0; bdfIdx < stencil.numBoundaryFaces(); ++bdfIdx) { + const auto& scvf = stencil.boundaryFace(bdfIdx); + int faceId = scvf.dirId(); + loc_flinfo[stencil.numInteriorFaces() + bdfIdx] = FlowInfo{faceId, flow, nncId}; + } + + + if (anyFlows) { + flowsInfo_.appendRow(loc_flinfo.begin(), loc_flinfo.end()); + } + if (anyFlores) { + floresInfo_.appendRow(loc_flinfo.begin(), loc_flinfo.end()); + } + if (enableDispersion) { + velocityInfo_.appendRow(loc_vlinfo.begin(), loc_vlinfo.end()); + } + } + } + } + +public: + void setResAndJacobi(VectorBlock& res, MatrixBlock& bMat, const ADVectorBlock& resid) const + { + for (unsigned eqIdx = 0; eqIdx < numEq; eqIdx++) + res[eqIdx] = resid[eqIdx].value(); + + for (unsigned eqIdx = 0; eqIdx < numEq; eqIdx++) { + for (unsigned pvIdx = 0; pvIdx < numEq; pvIdx++) { + // A[dofIdx][focusDofIdx][eqIdx][pvIdx] is the partial derivative of + // the residual function 'eqIdx' for the degree of freedom 'dofIdx' + // with regard to the focus variable 'pvIdx' of the degree of freedom + // 'focusDofIdx' + bMat[eqIdx][pvIdx] = resid[eqIdx].derivative(pvIdx); + } + } + } + + void updateFlowsInfo() { + OPM_TIMEBLOCK(updateFlows); + const bool& enableFlows = simulator_().problem().eclWriter()->outputModule().hasFlows() || + simulator_().problem().eclWriter()->outputModule().hasBlockFlows(); + const bool& enableFlores = simulator_().problem().eclWriter()->outputModule().hasFlores(); + if (!enableFlows && !enableFlores) { + return; + } + const unsigned int numCells = model_().numTotalDof(); +#ifdef _OPENMP +#pragma omp parallel for +#endif + for (unsigned globI = 0; globI < numCells; ++globI) { + OPM_TIMEBLOCK_LOCAL(linearizationForEachCell); + const auto& nbInfos = neighborInfo_[globI]; + ADVectorBlock adres(0.0); + ADVectorBlock darcyFlux(0.0); + const IntensiveQuantities& intQuantsIn = model_().intensiveQuantities(globI, /*timeIdx*/ 0); + // Flux term. + { + OPM_TIMEBLOCK_LOCAL(fluxCalculationForEachCell); + short loc = 0; + for (const auto& nbInfo : nbInfos) { + OPM_TIMEBLOCK_LOCAL(fluxCalculationForEachFace); + unsigned globJ = nbInfo.neighbor; + assert(globJ != globI); + adres = 0.0; + darcyFlux = 0.0; + const IntensiveQuantities& intQuantsEx = model_().intensiveQuantities(globJ, /*timeIdx*/ 0); + LocalResidual::computeFlux(adres,darcyFlux, globI, globJ, intQuantsIn, intQuantsEx, nbInfo.res_nbinfo, problem_().moduleParams()); + adres *= nbInfo.res_nbinfo.faceArea; + if (enableFlows) { + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { + flowsInfo_[globI][loc].flow[eqIdx] = adres[eqIdx].value(); + } + } + if (enableFlores) { + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { + floresInfo_[globI][loc].flow[eqIdx] = darcyFlux[eqIdx].value(); + } + } + ++loc; + } + } + } + + // Boundary terms. Only looping over cells with nontrivial bcs. + for (const auto& bdyInfo : boundaryInfo_) { + if (bdyInfo.bcdata.type == BCType::NONE) + continue; + + ADVectorBlock adres(0.0); + const unsigned globI = bdyInfo.cell; + const auto& nbInfos = neighborInfo_[globI]; + const IntensiveQuantities& insideIntQuants = model_().intensiveQuantities(globI, /*timeIdx*/ 0); + LocalResidual::computeBoundaryFlux(adres, problem_(), bdyInfo.bcdata, insideIntQuants, globI); + adres *= bdyInfo.bcdata.faceArea; + const unsigned bfIndex = bdyInfo.bfIndex; + if (enableFlows) { + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { + flowsInfo_[globI][nbInfos.size() + bfIndex].flow[eqIdx] = adres[eqIdx].value(); + } + } + // TODO also store Flores? + } + } + +private: + template + void linearize_(const SubDomainType& domain) + { + // This check should be removed once this is addressed by + // for example storing the previous timesteps' values for + // rsmax (for DRSDT) and similar. + if (!problem_().recycleFirstIterationStorage()) { + if (!model_().storeIntensiveQuantities() && !model_().enableStorageCache()) { + OPM_THROW(std::runtime_error, "Must have cached either IQs or storage when we cannot recycle."); + } + } + + OPM_TIMEBLOCK(linearize); + + // We do not call resetSystem_() here, since that will set + // the full system to zero, not just our part. + // Instead, that must be called before starting the linearization. + const bool& enableDispersion = simulator_().vanguard().eclState().getSimulationConfig().rock_config().dispersion(); + const unsigned int numCells = domain.cells.size(); + const bool on_full_domain = (numCells == model_().numTotalDof()); + +#ifdef _OPENMP +#pragma omp parallel for +#endif + for (unsigned ii = 0; ii < numCells; ++ii) { + OPM_TIMEBLOCK_LOCAL(linearizationForEachCell); + const unsigned globI = domain.cells[ii]; + const auto& nbInfos = neighborInfo_[globI]; + VectorBlock res(0.0); + MatrixBlock bMat(0.0); + ADVectorBlock adres(0.0); + ADVectorBlock darcyFlux(0.0); + const IntensiveQuantities& intQuantsIn = model_().intensiveQuantities(globI, /*timeIdx*/ 0); + + // Flux term. + { + OPM_TIMEBLOCK_LOCAL(fluxCalculationForEachCell); + short loc = 0; + for (const auto& nbInfo : nbInfos) { + OPM_TIMEBLOCK_LOCAL(fluxCalculationForEachFace); + unsigned globJ = nbInfo.neighbor; + assert(globJ != globI); + res = 0.0; + bMat = 0.0; + adres = 0.0; + darcyFlux = 0.0; + const IntensiveQuantities& intQuantsEx = model_().intensiveQuantities(globJ, /*timeIdx*/ 0); + LocalResidual::computeFlux(adres,darcyFlux, globI, globJ, intQuantsIn, intQuantsEx, nbInfo.res_nbinfo, problem_().moduleParams()); + adres *= nbInfo.res_nbinfo.faceArea; + if (enableDispersion) { + for (unsigned phaseIdx = 0; phaseIdx < numEq; ++ phaseIdx) { + velocityInfo_[globI][loc].velocity[phaseIdx] = darcyFlux[phaseIdx].value() / nbInfo.res_nbinfo.faceArea; + } + } + setResAndJacobi(res, bMat, adres); + residual_[globI] += res; + //SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat); + *diagMatAddress_[globI] += bMat; + bMat *= -1.0; + //SparseAdapter syntax: jacobian_->addToBlock(globJ, globI, bMat); + *nbInfo.matBlockAddress += bMat; + ++loc; + } + } + + // Accumulation term. + double dt = simulator_().timeStepSize(); + double volume = model_().dofTotalVolume(globI); + Scalar storefac = volume / dt; + adres = 0.0; + { + OPM_TIMEBLOCK_LOCAL(computeStorage); + LocalResidual::computeStorage(adres, intQuantsIn); + } + setResAndJacobi(res, bMat, adres); + // Either use cached storage term, or compute it on the fly. + if (model_().enableStorageCache()) { + // The cached storage for timeIdx 0 (current time) is not + // used, but after storage cache is shifted at the end of the + // timestep, it will become cached storage for timeIdx 1. + model_().updateCachedStorage(globI, /*timeIdx=*/0, res); + if (model_().newtonMethod().numIterations() == 0) { + // Need to update the storage cache. + if (problem_().recycleFirstIterationStorage()) { + // Assumes nothing have changed in the system which + // affects masses calculated from primary variables. + if (on_full_domain) { + // This is to avoid resetting the start-of-step storage + // to incorrect numbers when we do local solves, where the iteration + // number will start from 0, but the starting state may not be identical + // to the start-of-step state. + // Note that a full assembly must be done before local solves + // otherwise this will be left un-updated. + model_().updateCachedStorage(globI, /*timeIdx=*/1, res); + } + } else { + Dune::FieldVector tmp; + IntensiveQuantities intQuantOld = model_().intensiveQuantities(globI, 1); + LocalResidual::computeStorage(tmp, intQuantOld); + model_().updateCachedStorage(globI, /*timeIdx=*/1, tmp); + } + } + res -= model_().cachedStorage(globI, 1); + } else { + OPM_TIMEBLOCK_LOCAL(computeStorage0); + Dune::FieldVector tmp; + IntensiveQuantities intQuantOld = model_().intensiveQuantities(globI, 1); + LocalResidual::computeStorage(tmp, intQuantOld); + // assume volume do not change + res -= tmp; + } + res *= storefac; + bMat *= storefac; + residual_[globI] += res; + //SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat); + *diagMatAddress_[globI] += bMat; + + // Cell-wise source terms. + // This will include well sources if SeparateSparseSourceTerms is false. + res = 0.0; + bMat = 0.0; + adres = 0.0; + if (separateSparseSourceTerms_) { + LocalResidual::computeSourceDense(adres, problem_(), globI, 0); + } else { + LocalResidual::computeSource(adres, problem_(), globI, 0); + } + adres *= -volume; + setResAndJacobi(res, bMat, adres); + residual_[globI] += res; + //SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat); + *diagMatAddress_[globI] += bMat; + } // end of loop for cell globI. + + // Add sparse source terms. For now only wells. + if (separateSparseSourceTerms_) { + problem_().wellModel().addReservoirSourceTerms(residual_, diagMatAddress_); + } + + // Boundary terms. Only looping over cells with nontrivial bcs. + for (const auto& bdyInfo : boundaryInfo_) { + if (bdyInfo.bcdata.type == BCType::NONE) + continue; + + VectorBlock res(0.0); + MatrixBlock bMat(0.0); + ADVectorBlock adres(0.0); + const unsigned globI = bdyInfo.cell; + const IntensiveQuantities& insideIntQuants = model_().intensiveQuantities(globI, /*timeIdx*/ 0); + LocalResidual::computeBoundaryFlux(adres, problem_(), bdyInfo.bcdata, insideIntQuants, globI); + adres *= bdyInfo.bcdata.faceArea; + setResAndJacobi(res, bMat, adres); + residual_[globI] += res; + ////SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat); + *diagMatAddress_[globI] += bMat; + } + } + + void updateStoredTransmissibilities() + { + if (neighborInfo_.empty()) { + // This function was called before createMatrix_() was called. + // We call initFirstIteration_(), not createMatrix_(), because + // that will also initialize the residual consistently. + initFirstIteration_(); + } + unsigned numCells = model_().numTotalDof(); +#ifdef _OPENMP +#pragma omp parallel for +#endif + for (unsigned globI = 0; globI < numCells; globI++) { + auto nbInfos = neighborInfo_[globI]; // nbInfos will be a SparseTable<...>::mutable_iterator_range. + for (auto& nbInfo : nbInfos) { + unsigned globJ = nbInfo.neighbor; + nbInfo.res_nbinfo.trans = problem_().transmissibility(globI, globJ); + } + } + } + + + Simulator *simulatorPtr_; + + // the jacobian matrix + std::unique_ptr jacobian_; + + // the right-hand side + GlobalEqVector residual_; + + LinearizationType linearizationType_; + + using ResidualNBInfo = typename LocalResidual::ResidualNBInfo; + struct NeighborInfo + { + unsigned int neighbor; + ResidualNBInfo res_nbinfo; + MatrixBlock* matBlockAddress; + }; + SparseTable neighborInfo_; + std::vector diagMatAddress_; + + struct FlowInfo + { + int faceId; + VectorBlock flow; + unsigned int nncId; + }; + SparseTable flowsInfo_; + SparseTable floresInfo_; + + struct VelocityInfo + { + VectorBlock velocity; + }; + SparseTable velocityInfo_; + + using ScalarFluidState = typename IntensiveQuantities::ScalarFluidState; + struct BoundaryConditionData + { + BCType type; + VectorBlock massRate; + unsigned pvtRegionIdx; + unsigned boundaryFaceIndex; + double faceArea; + double faceZCoord; + ScalarFluidState exFluidState; + }; + struct BoundaryInfo + { + unsigned int cell; + int dir; + unsigned int bfIndex; + BoundaryConditionData bcdata; + }; + std::vector boundaryInfo_; + bool separateSparseSourceTerms_ = false; + struct FullDomain + { + std::vector cells; + std::vector interior; + }; + FullDomain fullDomain_; +}; + +} // namespace Opm + +#endif // TPFA_LINEARIZER diff --git a/opm/models/discretization/ecfv/ecfvbaseoutputmodule.hh b/opm/models/discretization/ecfv/ecfvbaseoutputmodule.hh new file mode 100644 index 00000000000..d03896f8b3e --- /dev/null +++ b/opm/models/discretization/ecfv/ecfvbaseoutputmodule.hh @@ -0,0 +1,81 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::EcfvBaseOutputModule + */ +#ifndef EWOMS_ECFV_VTK_BASE_OUTPUT_MODULE_HH +#define EWOMS_ECFV_VTK_BASE_OUTPUT_MODULE_HH + +#include "ecfvproperties.hh" + +#include + +namespace Opm { +/*! + * \ingroup EcfvDiscretization + * + * \brief Implements the discretization specific parts of writing files. + */ +template +class EcfvBaseOutputModule +{ +public: + using Scalar = BaseOutputWriter::Scalar; + using Vector = BaseOutputWriter::Vector; + using ScalarBuffer = BaseOutputWriter::ScalarBuffer; + using VectorBuffer = BaseOutputWriter::VectorBuffer; + using TensorBuffer = BaseOutputWriter::TensorBuffer; + + /*! + * \brief Add a buffer where the data is associated with the + * degrees of freedom to the current VTK output file. + */ + static void attachScalarDofData_(BaseOutputWriter& baseWriter, + ScalarBuffer& buffer, + const std::string& name) + { baseWriter.attachScalarElementData(buffer, name.c_str()); } + + /*! + * \brief Add a buffer where the data is associated with the + * degrees of freedom to the current VTK output file. + */ + static void attachVectorDofData_(BaseOutputWriter& baseWriter, + VectorBuffer& buffer, + const std::string& name) + { baseWriter.attachVectorElementData(buffer, name.c_str()); } + + /*! + * \brief Add a buffer where the data is associated with the + * degrees of freedom to the current VTK output file. + */ + static void attachTensorDofData_(BaseOutputWriter& baseWriter, + TensorBuffer& buffer, + const std::string& name) + { baseWriter.attachTensorElementData(buffer, name.c_str()); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/ecfv/ecfvdiscretization.hh b/opm/models/discretization/ecfv/ecfvdiscretization.hh new file mode 100644 index 00000000000..89c5114221b --- /dev/null +++ b/opm/models/discretization/ecfv/ecfvdiscretization.hh @@ -0,0 +1,237 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::EcfvDiscretization + */ +#ifndef EWOMS_ECFV_DISCRETIZATION_HH +#define EWOMS_ECFV_DISCRETIZATION_HH + +#include + +#include "ecfvproperties.hh" +#include "ecfvstencil.hh" +#include "ecfvgridcommhandlefactory.hh" +#include "ecfvbaseoutputmodule.hh" + +#include +#include + +#if HAVE_DUNE_FEM +#include +#include +#include +#endif + +namespace Opm { +template +class EcfvDiscretization; +} + +namespace Opm::Properties { + +//! Set the stencil +template +struct Stencil +{ +private: + using Scalar = GetPropType; + using GridView = GetPropType; + +public: + using type = EcfvStencil; +}; + +//! Mapper for the degrees of freedoms. +template +struct DofMapper { using type = GetPropType; }; + +//! The concrete class which manages the spatial discretization +template +struct Discretization { using type = EcfvDiscretization; }; + +//! The base class for the output modules (decides whether to write +//! element or vertex based fields) +template +struct DiscBaseOutputModule +{ using type = EcfvBaseOutputModule; }; + +//! The class to create grid communication handles +template +struct GridCommHandleFactory +{ using type = EcfvGridCommHandleFactory; }; + +#if HAVE_DUNE_FEM +//! Set the DiscreteFunctionSpace +template +struct DiscreteFunctionSpace +{ +private: + using Scalar = GetPropType ; + using GridPart = GetPropType; + enum { numEq = getPropValue() }; + using FunctionSpace = Dune::Fem::FunctionSpace; +public: + using type = Dune::Fem::FiniteVolumeSpace< FunctionSpace, GridPart, 0 >; +}; +#else +template +struct DummySpaceEcfv { + using DiscreteFunctionSpace = GetPropType; + DummySpaceEcfv(const DiscreteFunctionSpace&) {}; + DummySpaceEcfv(const int&) {}; +}; + +template +struct DiscreteFunctionSpace { + using type = DummySpaceEcfv; +}; +#endif + +//! Set the border list creator for to the one of an element based +//! method +template +struct BorderListCreator +{ private: + using ElementMapper = GetPropType; + using GridView = GetPropType; +public: + using type = Linear::ElementBorderListFromGrid; +}; + +//! For the element centered finite volume method, ghost and overlap elements must be +//! assembled to calculate the fluxes over the process boundary faces of the local +//! process' grid partition +template +struct LinearizeNonLocalElements { static constexpr bool value = true; }; + +//! locking is not required for the element centered finite volume method because race +//! conditions cannot occur since each matrix/vector entry is written exactly once +template +struct UseLinearizationLock { static constexpr bool value = false; }; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup EcfvDiscretization + * + * \brief The base class for the element-centered finite-volume discretization scheme. + */ +template +class EcfvDiscretization : public GetPropType +{ + using ParentType = GetPropType; + using Implementation = GetPropType; + using DofMapper = GetPropType; + using PrimaryVariables = GetPropType; + using SolutionVector = GetPropType; + using GridView = GetPropType; + using Simulator = GetPropType; + +public: + EcfvDiscretization(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Returns a string of discretization's human-readable name + */ + static std::string discretizationName() + { return "ecfv"; } + + /*! + * \brief Returns the number of global degrees of freedom (DOFs) due to the grid + */ + size_t numGridDof() const + { return static_cast(this->gridView_.size(/*codim=*/0)); } + + /*! + * \brief Mapper to convert the Dune entities of the + * discretization's degrees of freedoms are to indices. + */ + const DofMapper& dofMapper() const + { return this->elementMapper(); } + + /*! + * \brief Syncronize the values of the primary variables on the + * degrees of freedom that overlap with the neighboring + * processes. + * + * For the Element Centered Finite Volume discretization, this + * method retrieves the primary variables corresponding to + * overlap/ghost elements from their respective master process. + */ + void syncOverlap() + { + // syncronize the solution on the ghost and overlap elements + using GhostSyncHandle = GridCommHandleGhostSync; + + auto ghostSync = GhostSyncHandle(this->solution(/*timeIdx=*/0), + asImp_().dofMapper()); + this->gridView().communicate(ghostSync, + Dune::InteriorBorder_All_Interface, + Dune::ForwardCommunication); + } + + /*! + * \brief Serializes the current state of the model. + * + * \tparam Restarter The type of the serializer class + * + * \param res The serializer object + */ + template + void serialize(Restarter& res) + { res.template serializeEntities(asImp_(), this->gridView_); } + + /*! + * \brief Deserializes the state of the model. + * + * \tparam Restarter The type of the serializer class + * + * \param res The serializer object + */ + template + void deserialize(Restarter& res) + { + res.template deserializeEntities(asImp_(), this->gridView_); + this->solution(/*timeIdx=*/1) = this->solution(/*timeIdx=*/0); + } + +private: + Implementation& asImp_() + { return *static_cast(this); } + const Implementation& asImp_() const + { return *static_cast(this); } +}; +} // namespace Opm + +#endif diff --git a/opm/models/discretization/ecfv/ecfvgridcommhandlefactory.hh b/opm/models/discretization/ecfv/ecfvgridcommhandlefactory.hh new file mode 100644 index 00000000000..31fc4fcf3b7 --- /dev/null +++ b/opm/models/discretization/ecfv/ecfvgridcommhandlefactory.hh @@ -0,0 +1,88 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::EcfvGridCommHandleFactory + */ +#ifndef EWOMS_ECFV_GRID_COMM_HANDLE_FACTORY_HH +#define EWOMS_ECFV_GRID_COMM_HANDLE_FACTORY_HH + +#include "ecfvproperties.hh" + +#include + +namespace Opm { +/*! + * \ingroup EcfvDiscretization + * + * \brief A class which provides types for DUNE grid handles for + * communication. + * + * This is required for parallel computations + */ +template +class EcfvGridCommHandleFactory +{ + using DofMapper = GetPropType; + +public: + /*! + * \brief Return a handle which computes the minimum of a value + * for each overlapping degree of freedom across all processes. + */ + template + static std::shared_ptr > + minHandle(ArrayType& array, const DofMapper& dofMapper) + { + using Handle = GridCommHandleMin; + return std::shared_ptr(new Handle(array, dofMapper)); + } + + /*! + * \brief Return a handle which computes the maximum of a value + * for each overlapping degree of freedom across all processes. + */ + template + static std::shared_ptr > + maxHandle(ArrayType& array, const DofMapper& dofMapper) + { + using Handle = GridCommHandleMax; + return std::shared_ptr(new Handle(array, dofMapper)); + } + + /*! + * \brief Return a handle which computes the sum of all values + * all overlapping degrees of freedom across all processes. + */ + template + static std::shared_ptr > + sumHandle(ArrayType& array, const DofMapper& dofMapper) + { + using Handle = GridCommHandleSum; + return std::shared_ptr(new Handle(array, dofMapper)); + } +}; +} // namespace Opm + +#endif diff --git a/opm/models/discretization/ecfv/ecfvproperties.hh b/opm/models/discretization/ecfv/ecfvproperties.hh new file mode 100644 index 00000000000..834d90a6e2b --- /dev/null +++ b/opm/models/discretization/ecfv/ecfvproperties.hh @@ -0,0 +1,46 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Declare the basic properties used by the common infrastructure of + * the element-centered finite volume discretization. + */ +#ifndef EWOMS_ECFV_PROPERTIES_HH +#define EWOMS_ECFV_PROPERTIES_HH + +#include + +#include + +namespace Opm::Properties { + +//! The type tag for models based on the ECFV-scheme +// Create new type tags +namespace TTag { +struct EcfvDiscretization { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/discretization/ecfv/ecfvstencil.hh b/opm/models/discretization/ecfv/ecfvstencil.hh new file mode 100644 index 00000000000..a83cfd9e388 --- /dev/null +++ b/opm/models/discretization/ecfv/ecfvstencil.hh @@ -0,0 +1,432 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::EcfvStencil + */ +#ifndef EWOMS_ECFV_STENCIL_HH +#define EWOMS_ECFV_STENCIL_HH + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Opm { +/*! + * \ingroup EcfvDiscretization + * + * \brief Represents the stencil (finite volume geometry) of a single + * element in the ECFV discretization. + * + * The ECFV discretization is a element centered finite volume + * approach. This means that each element corresponds to a control + * volume. + */ +template +class EcfvStencil +{ + enum { dimWorld = GridView::dimensionworld }; + + using CoordScalar = typename GridView::ctype; + using Intersection = typename GridView::Intersection; + using Element = typename GridView::template Codim<0>::Entity; + + using ElementMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + + using GlobalPosition = Dune::FieldVector; + + using WorldVector = Dune::FieldVector; + +public: + using Entity = Element ; + using Mapper = ElementMapper ; + + using LocalGeometry = typename Element::Geometry; + + /*! + * \brief Represents a sub-control volume. + * + * For element centered finite volumes, this is equivalent to the + * element, in the vertex centered finite volume approach, this + * corresponds to the intersection of a finite volume and the + * grid element. + */ + class SubControlVolume + { + public: + // default construct an uninitialized object. + // this is only here because std::vector needs it... + SubControlVolume() + {} + + SubControlVolume(const Element& element) + : element_(element) + { update(); } + + void update(const Element& element) + { element_ = element; } + + void update() + { } + + /*! + * \brief The global position associated with the sub-control volume + */ + decltype(auto) globalPos() const + { return element_.geometry().center(); } + + /*! + * \brief The center of the sub-control volume + */ + decltype(auto) center() const + { return element_.geometry().center(); } + + /*! + * \brief The volume [m^3] occupied by the sub-control volume + */ + Scalar volume() const + { return element_.geometry().volume(); } + + /*! + * \brief The geometry of the sub-control volume. + */ + const LocalGeometry geometry() const + { return element_.geometry(); } + + /*! + * \brief Geometry of the sub-control volume relative to parent. + */ + const LocalGeometry localGeometry() const + { return element_.geometryInFather(); } + + private: + Element element_; + }; + + /*! + * \brief Represents a face of a sub-control volume. + */ + template + class EcfvSubControlVolumeFace + { + public: + EcfvSubControlVolumeFace() + {} + + EcfvSubControlVolumeFace(const Intersection& intersection, unsigned localNeighborIdx) + { + exteriorIdx_ = static_cast(localNeighborIdx); + + if (needNormal) + (*normal_) = intersection.centerUnitOuterNormal(); + const auto& geometry = intersection.geometry(); + if (needIntegrationPos) + (*integrationPos_) = geometry.center(); + area_ = geometry.volume(); + // TODO: facedir_ = intersection.boundaryId(); // This did not compile for some reason + // using indexInInside() as in ecltransmissibility.cc instead + // Note that indexInInside() returns -1 for NNC faces, that's the reason we + // cannot convert directly to a FaceDir::DirEnum (see FaceDir.hpp) + dirId_ = intersection.indexInInside(); // Legal values: -1, 0, 1, 2, 3, 4, 5 + } + + /*! + * \brief Returns the local index of the degree of freedom to + * the face's interior. + */ + unsigned short interiorIndex() const + { + // The local index of the control volume in the interior + // of a face of the stencil in the element centered finite + // volume discretization is always the "central" + // element. In this implementation this element always has + // index 0.... + return 0; + } + + /*! + * \brief Returns the local index of the degree of freedom to + * the face's outside. + */ + unsigned short exteriorIndex() const + { return exteriorIdx_; } + + /*! + * \brief Returns the global position of the face's + * integration point. + */ + const GlobalPosition& integrationPos() const + { return *integrationPos_; } + + /*! + * \brief Returns the outer unit normal at the face's + * integration point. + */ + const WorldVector& normal() const + { return *normal_; } + + /*! + * \brief Returns the area [m^2] of the face + */ + Scalar area() const + { return area_; } + + /*! + * \brief Returns the direction id of the face w.r.t the cell. + * + * For corner point grids, this is 0-5 for I-, I+, J-, J+, K- and K+ faces, + * and -1 for NNC faces. + */ + int dirId() const + { + return dirId_; + } + + /*! + * \brief Returns the direction of the face + */ + FaceDir::DirEnum faceDirFromDirId() const + { + using Dir = FaceDir::DirEnum; + if (dirId_ == -1) { + OPM_THROW(std::runtime_error, "NNC faces does not have a face id"); + } + switch(dirId_) { + case 0: + case 1: + return Dir::XPlus; + case 2: + case 3: + return Dir::YPlus; + case 4: + case 5: + return Dir::ZPlus; + default: + OPM_THROW(std::runtime_error, + "Unexpected face id" + std::to_string(dirId_)); + } + } + + private: + ConditionalStorage integrationPos_; + ConditionalStorage normal_; + Scalar area_; + int dirId_; + + unsigned short exteriorIdx_; + }; + + using SubControlVolumeFace = EcfvSubControlVolumeFace; + using BoundaryFace = EcfvSubControlVolumeFace; + + EcfvStencil(const GridView& gridView, const Mapper& mapper) + : gridView_(gridView) + , elementMapper_(mapper) + { + // try to ensure that the mapper passed indeed maps elements + assert(int(gridView.size(/*codim=*/0)) == int(elementMapper_.size())); + } + + void updateTopology(const Element& element) + { + auto isIt = gridView_.ibegin(element); + const auto& endIsIt = gridView_.iend(element); + + // add the "center" element of the stencil + subControlVolumes_.clear(); + subControlVolumes_.emplace_back(/*SubControlVolume(*/element/*)*/); + elements_.clear(); + elements_.emplace_back(element); + + interiorFaces_.clear(); + boundaryFaces_.clear(); + + for (; isIt != endIsIt; ++isIt) { + const auto& intersection = *isIt; + // if the current intersection has a neighbor, add a + // degree of freedom and an internal face, else add a + // boundary face + if (intersection.neighbor()) { + elements_.emplace_back( intersection.outside() ); + subControlVolumes_.emplace_back(/*SubControlVolume(*/elements_.back()/*)*/); + interiorFaces_.emplace_back(/*SubControlVolumeFace(*/intersection, subControlVolumes_.size() - 1/*)*/); + } + else { + boundaryFaces_.emplace_back(/*SubControlVolumeFace(*/intersection, - 10000/*)*/); + } + } + } + + void updatePrimaryTopology(const Element& element) + { + // add the "center" element of the stencil + subControlVolumes_.clear(); + subControlVolumes_.emplace_back(/*SubControlVolume(*/element/*)*/); + elements_.clear(); + elements_.emplace_back(element); + } + + void update(const Element& element) + { + updateTopology(element); + } + + void updateCenterGradients() + { + assert(false); // not yet implemented + } + + /*! + * \brief Return the element to which the stencil refers. + */ + const Element& element() const + { return element( 0 ); } + + /*! + * \brief Return the grid view of the element to which the stencil + * refers. + */ + const GridView& gridView() const + { return *gridView_; } + + /*! + * \brief Returns the number of degrees of freedom which the + * current element interacts with. + */ + size_t numDof() const + { return subControlVolumes_.size(); } + + /*! + * \brief Returns the number of degrees of freedom which are contained + * by within the current element. + * + * Primary DOFs are always expected to have a lower index than + * "secondary" DOFs. + * + * For element centered finite elements, this is only the central DOF. + */ + size_t numPrimaryDof() const + { return 1; } + + /*! + * \brief Return the global space index given the index of a degree of + * freedom. + */ + unsigned globalSpaceIndex(unsigned dofIdx) const + { + assert(dofIdx < numDof()); + + return static_cast(elementMapper_.index(element(dofIdx))); + } + + /*! + * \brief Return partition type of a given degree of freedom + */ + Dune::PartitionType partitionType(unsigned dofIdx) const + { return elements_[dofIdx]->partitionType(); } + + /*! + * \brief Return the element given the index of a degree of + * freedom. + * + * If no degree of freedom index is passed, the element which was + * passed to the update() method is returned... + */ + const Element& element(unsigned dofIdx) const + { + assert(dofIdx < numDof()); + + return elements_[dofIdx]; + } + + /*! + * \brief Return the entity given the index of a degree of + * freedom. + */ + const Entity& entity(unsigned dofIdx) const + { + return element( dofIdx ); + } + + /*! + * \brief Returns the sub-control volume object belonging to a + * given degree of freedom. + */ + const SubControlVolume& subControlVolume(unsigned dofIdx) const + { return subControlVolumes_[dofIdx]; } + + /*! + * \brief Returns the number of interior faces of the stencil. + */ + size_t numInteriorFaces() const + { return interiorFaces_.size(); } + + /*! + * \brief Returns the face object belonging to a given face index + * in the interior of the domain. + */ + const SubControlVolumeFace& interiorFace(unsigned faceIdx) const + { return interiorFaces_[faceIdx]; } + + /*! + * \brief Returns the number of boundary faces of the stencil. + */ + size_t numBoundaryFaces() const + { return boundaryFaces_.size(); } + + /*! + * \brief Returns the boundary face object belonging to a given + * boundary face index. + */ + const BoundaryFace& boundaryFace(unsigned bfIdx) const + { return boundaryFaces_[bfIdx]; } + +protected: + const GridView& gridView_; + const ElementMapper& elementMapper_; + + std::vector elements_; + std::vector subControlVolumes_; + std::vector interiorFaces_; + std::vector boundaryFaces_; +}; + +} // namespace Opm + + +#endif + diff --git a/opm/models/discretization/vcfv/p1fegradientcalculator.hh b/opm/models/discretization/vcfv/p1fegradientcalculator.hh new file mode 100644 index 00000000000..4149fef0817 --- /dev/null +++ b/opm/models/discretization/vcfv/p1fegradientcalculator.hh @@ -0,0 +1,357 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::P1FeGradientCalculator + */ +#ifndef EWOMS_P1FE_GRADIENT_CALCULATOR_HH +#define EWOMS_P1FE_GRADIENT_CALCULATOR_HH + +#include "vcfvproperties.hh" + +#include + +#include +#include + +#include + +#if HAVE_DUNE_LOCALFUNCTIONS +#include +#endif // HAVE_DUNE_LOCALFUNCTIONS + +#include + +#include + +namespace Opm { +/*! + * \ingroup FiniteElementDiscretizations + * + * \brief This class calculates gradients of arbitrary quantities at flux integration + * points using first order finite elemens ansatz functions. + * + * This approach can also be used for the vertex-centered finite volume (VCFV) + * discretization. + */ +template +class P1FeGradientCalculator : public FvBaseGradientCalculator +{ + using ParentType = FvBaseGradientCalculator; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using GridView = GetPropType; + using ElementContext = GetPropType; + + enum { dim = GridView::dimension }; + + // set the maximum number of degrees of freedom and the maximum + // number of flux approximation points per elements. For this, we + // assume cubes as the type of element with the most vertices... + enum { maxDof = (2 << dim) }; + enum { maxFap = maxDof }; + + using CoordScalar = typename GridView::ctype; + using DimVector = Dune::FieldVector; + +#if HAVE_DUNE_LOCALFUNCTIONS + using LocalFiniteElementCache = Dune::PQkLocalFiniteElementCache; + using LocalFiniteElement = typename LocalFiniteElementCache::FiniteElementType; + using LocalBasisTraits = typename LocalFiniteElement::Traits::LocalBasisType::Traits; + using ShapeJacobian = typename LocalBasisTraits::JacobianType; +#endif // HAVE_DUNE_LOCALFUNCTIONS + +public: + /*! + * \brief Precomputes the common values to calculate gradients and + * values of quantities at any flux approximation point. + * + * \param elemCtx The current execution context + */ + template + void prepare([[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned timeIdx) + { + if (getPropValue()) { +#if !HAVE_DUNE_LOCALFUNCTIONS + // The dune-localfunctions module is required for P1 finite element gradients + throw std::logic_error("The dune-localfunctions module is required in oder to use" + " finite element gradients"); +#else + const auto& stencil = elemCtx.stencil(timeIdx); + + const LocalFiniteElement& localFE = feCache_.get(elemCtx.element().type()); + localFiniteElement_ = &localFE; + + // loop over all face centeres + for (unsigned faceIdx = 0; faceIdx < stencil.numInteriorFaces(); ++faceIdx) { + const auto& localFacePos = stencil.interiorFace(faceIdx).localPos(); + + // Evaluate the P1 shape functions and their gradients at all + // flux approximation points. + if (prepareValues) + localFE.localBasis().evaluateFunction(localFacePos, p1Value_[faceIdx]); + + if (prepareGradients) { + // first, get the shape function's gradient in local coordinates + std::vector localGradient; + localFE.localBasis().evaluateJacobian(localFacePos, localGradient); + + // convert to a gradient in global space by + // multiplying with the inverse transposed jacobian of + // the position + const auto& geom = elemCtx.element().geometry(); + const auto& jacInvT = + geom.jacobianInverseTransposed(localFacePos); + + size_t numVertices = elemCtx.numDof(timeIdx); + for (unsigned vertIdx = 0; vertIdx < numVertices; vertIdx++) { + jacInvT.mv(/*xVector=*/localGradient[vertIdx][0], + /*destVector=*/p1Gradient_[faceIdx][vertIdx]); + } + } + } +#endif + } + else + ParentType::template prepare(elemCtx, timeIdx); + } + + /*! + * \brief Calculates the value of an arbitrary quantity at any + * interior flux approximation point. + * + * \param elemCtx The current execution context + * \param fapIdx The local index of the flux approximation point + * in the current element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity at an index of a degree of + * freedom + */ + template + auto calculateScalarValue([[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned fapIdx, + [[maybe_unused]] const QuantityCallback& quantityCallback) const + -> typename std::remove_reference::type + { + if (getPropValue()) { +#if !HAVE_DUNE_LOCALFUNCTIONS + // The dune-localfunctions module is required for P1 finite element gradients + throw std::logic_error("The dune-localfunctions module is required in oder to use" + " finite element gradients"); +#else + using QuantityConstType = typename std::remove_reference::type; + using QuantityType = typename std::remove_const::type; + using Toolbox = MathToolbox; + + // If the user does not want to use two-point gradients, we + // use P1 finite element gradients.. + QuantityType value(0.0); + for (unsigned vertIdx = 0; vertIdx < elemCtx.numDof(/*timeIdx=*/0); ++vertIdx) { + if (std::is_same::value || + elemCtx.focusDofIndex() == vertIdx) + value += quantityCallback(vertIdx)*p1Value_[fapIdx][vertIdx]; + else + value += Toolbox::value(quantityCallback(vertIdx))*p1Value_[fapIdx][vertIdx]; + } + + return value; +#endif + } + else + return ParentType::calculateScalarValue(elemCtx, fapIdx, quantityCallback); + } + + /*! + * \brief Calculates the value of an arbitrary quantity at any + * interior flux approximation point. + * + * \param elemCtx The current execution context + * \param fapIdx The local index of the flux approximation point + * in the current element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity at an index of a degree of + * freedom + */ + template + auto calculateVectorValue([[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned fapIdx, + [[maybe_unused]] const QuantityCallback& quantityCallback) const + -> typename std::remove_reference::type + { + if (getPropValue()) { +#if !HAVE_DUNE_LOCALFUNCTIONS + // The dune-localfunctions module is required for P1 finite element gradients + throw std::logic_error("The dune-localfunctions module is required in oder to use" + " finite element gradients"); +#else + using QuantityConstType = typename std::remove_reference::type; + using QuantityType = typename std::remove_const::type; + + using RawFieldType = decltype(std::declval()[0]); + using FieldType = typename std::remove_const::type>::type; + + using Toolbox = MathToolbox; + + // If the user does not want to use two-point gradients, we + // use P1 finite element gradients.. + QuantityType value(0.0); + for (unsigned vertIdx = 0; vertIdx < elemCtx.numDof(/*timeIdx=*/0); ++vertIdx) { + if (std::is_same::value || + elemCtx.focusDofIndex() == vertIdx) + { + const auto& tmp = quantityCallback(vertIdx); + for (unsigned k = 0; k < tmp.size(); ++k) + value[k] += tmp[k]*p1Value_[fapIdx][vertIdx]; + } + else { + const auto& tmp = quantityCallback(vertIdx); + for (unsigned k = 0; k < tmp.size(); ++k) + value[k] += Toolbox::value(tmp[k])*p1Value_[fapIdx][vertIdx]; + } + } + + return value; +#endif + } + else + return ParentType::calculateVectorValue(elemCtx, fapIdx, quantityCallback); + } + + /*! + * \brief Calculates the gradient of an arbitrary quantity at any + * flux approximation point. + * + * \param elemCtx The current execution context + * \param fapIdx The local index of the flux approximation point + * in the current element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity at an index of a degree of + * freedom + */ + template + void calculateGradient([[maybe_unused]] EvalDimVector& quantityGrad, + [[maybe_unused]] const ElementContext& elemCtx, + [[maybe_unused]] unsigned fapIdx, + [[maybe_unused]] const QuantityCallback& quantityCallback) const + { + if (getPropValue()) { +#if !HAVE_DUNE_LOCALFUNCTIONS + // The dune-localfunctions module is required for P1 finite element gradients + throw std::logic_error("The dune-localfunctions module is required in oder to use" + " finite element gradients"); +#else + using QuantityConstType = typename std::remove_reference::type; + using QuantityType = typename std::remove_const::type; + + // If the user does not want two-point gradients, we use P1 finite element + // gradients... + quantityGrad = 0.0; + for (unsigned vertIdx = 0; vertIdx < elemCtx.numDof(/*timeIdx=*/0); ++vertIdx) { + if (std::is_same::value || + elemCtx.focusDofIndex() == vertIdx) + { + const auto& dofVal = quantityCallback(vertIdx); + const auto& tmp = p1Gradient_[fapIdx][vertIdx]; + for (int dimIdx = 0; dimIdx < dim; ++ dimIdx) + quantityGrad[dimIdx] += dofVal*tmp[dimIdx]; + } + else { + const auto& dofVal = quantityCallback(vertIdx); + const auto& tmp = p1Gradient_[fapIdx][vertIdx]; + for (int dimIdx = 0; dimIdx < dim; ++ dimIdx) + quantityGrad[dimIdx] += scalarValue(dofVal)*tmp[dimIdx]; + } + } +#endif + } + else + ParentType::calculateGradient(quantityGrad, elemCtx, fapIdx, quantityCallback); + } + + /*! + * \brief Calculates the value of an arbitrary quantity at any + * flux approximation point on the grid boundary. + * + * Boundary values are always calculated using the two-point + * approximation. + * + * \param elemCtx The current execution context + * \param fapIdx The local index of the flux approximation point + * in the current element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity at an index of a degree of + * freedom + */ + template + auto calculateBoundaryValue(const ElementContext& elemCtx, + unsigned fapIdx, + const QuantityCallback& quantityCallback) + -> decltype(ParentType::calculateBoundaryValue(elemCtx, fapIdx, quantityCallback)) + { return ParentType::calculateBoundaryValue(elemCtx, fapIdx, quantityCallback); } + + /*! + * \brief Calculates the gradient of an arbitrary quantity at any + * flux approximation point on the boundary. + * + * Boundary gradients are always calculated using the two-point + * approximation. + * + * \param elemCtx The current execution context + * \param fapIdx The local index of the flux approximation point + * in the current element's stencil. + * \param quantityCallback A callable object returning the value + * of the quantity at an index of a degree of + * freedom + */ + template + void calculateBoundaryGradient(EvalDimVector& quantityGrad, + const ElementContext& elemCtx, + unsigned fapIdx, + const QuantityCallback& quantityCallback) const + { ParentType::calculateBoundaryGradient(quantityGrad, elemCtx, fapIdx, quantityCallback); } + +#if HAVE_DUNE_LOCALFUNCTIONS + static LocalFiniteElementCache& localFiniteElementCache() + { return feCache_; } +#endif + +private: +#if HAVE_DUNE_LOCALFUNCTIONS + static LocalFiniteElementCache feCache_; + + const LocalFiniteElement* localFiniteElement_; + std::vector> p1Value_[maxFap]; + DimVector p1Gradient_[maxFap][maxDof]; +#endif // HAVE_DUNE_LOCALFUNCTIONS +}; + +#if HAVE_DUNE_LOCALFUNCTIONS +template +typename P1FeGradientCalculator::LocalFiniteElementCache +P1FeGradientCalculator::feCache_; +#endif +} // namespace Opm + +#endif diff --git a/opm/models/discretization/vcfv/vcfvbaseoutputmodule.hh b/opm/models/discretization/vcfv/vcfvbaseoutputmodule.hh new file mode 100644 index 00000000000..3b06b8a0c9c --- /dev/null +++ b/opm/models/discretization/vcfv/vcfvbaseoutputmodule.hh @@ -0,0 +1,85 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::VcfvBaseOutputModule + */ +#ifndef EWOMS_VCFV_VTK_BASE_OUTPUT_MODULE_HH +#define EWOMS_VCFV_VTK_BASE_OUTPUT_MODULE_HH + +#include "vcfvproperties.hh" + +#include + +#include +#include + +namespace Opm { +/*! + * \ingroup VcfvDiscretization + * + * \brief Implements the discretization specific parts of writing files. + */ +template +class VcfvBaseOutputModule +{ +public: + using Scalar = BaseOutputWriter::Scalar; + using Vector = BaseOutputWriter::Vector; + using ScalarBuffer = BaseOutputWriter::ScalarBuffer; + using VectorBuffer = BaseOutputWriter::VectorBuffer; + using TensorBuffer = BaseOutputWriter::TensorBuffer; + + /*! + * \brief Add a buffer where the data is associated with the + * degrees of freedom to the current VTK output file. + */ + static void attachScalarDofData_(BaseOutputWriter& baseWriter, + ScalarBuffer& buffer, + const std::string& name) + { baseWriter.attachScalarVertexData(buffer, name.c_str()); } + + /*! + * \brief Add a buffer where the data is associated with the + * degrees of freedom to the current VTK output file. + */ + static void attachVectorDofData_(BaseOutputWriter& baseWriter, + VectorBuffer& buffer, + const std::string& name) + { baseWriter.attachVectorVertexData(buffer, name.c_str()); } + + + /*! + * \brief Add a buffer where the data is associated with the + * degrees of freedom to the current VTK output file. + */ + static void attachTensorDofData_(BaseOutputWriter& baseWriter, + TensorBuffer& buffer, + const std::string& name) + { baseWriter.attachTensorVertexData(buffer, name.c_str()); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/discretization/vcfv/vcfvdiscretization.hh b/opm/models/discretization/vcfv/vcfvdiscretization.hh new file mode 100644 index 00000000000..540bb3944aa --- /dev/null +++ b/opm/models/discretization/vcfv/vcfvdiscretization.hh @@ -0,0 +1,221 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::VcfvDiscretization + */ +#ifndef EWOMS_VCFV_DISCRETIZATION_HH +#define EWOMS_VCFV_DISCRETIZATION_HH + +#include + +#include "vcfvproperties.hh" +#include "vcfvstencil.hh" +#include "p1fegradientcalculator.hh" +#include "vcfvgridcommhandlefactory.hh" +#include "vcfvbaseoutputmodule.hh" + +#include +#include + +#if HAVE_DUNE_FEM +#include +#include +#include +#endif + +namespace Opm { +template +class VcfvDiscretization; + +} // namespace Opm + +namespace Opm::Properties { + +//! Set the stencil +template +struct Stencil +{ +private: + using GridView = GetPropType; + using CoordScalar = typename GridView::ctype; + +public: + using type = VcfvStencil; +}; + +//! Mapper for the degrees of freedoms. +template +struct DofMapper { using type = GetPropType; }; + +//! The concrete class which manages the spatial discretization +template +struct Discretization { using type = VcfvDiscretization; }; + +//! The base class for the output modules (decides whether to write +//! element or vertex based fields) +template +struct DiscBaseOutputModule +{ using type = VcfvBaseOutputModule; }; + +//! Calculates the gradient of any quantity given the index of a flux approximation point +template +struct GradientCalculator +{ using type = P1FeGradientCalculator; }; + +//! The class to create grid communication handles +template +struct GridCommHandleFactory +{ using type = VcfvGridCommHandleFactory; }; + +//! Use two-point gradients by default for the vertex centered finite volume scheme. +template +struct UseP1FiniteElementGradients { static constexpr bool value = false; }; + +#if HAVE_DUNE_FEM +//! Set the DiscreteFunctionSpace +template +struct DiscreteFunctionSpace +{ +private: + using Scalar = GetPropType ; + using GridPart = GetPropType; + enum { numEq = getPropValue() }; + using FunctionSpace = Dune::Fem::FunctionSpace; +public: + // Lagrange discrete function space with unknowns at the cell vertices + using type = Dune::Fem::LagrangeDiscreteFunctionSpace< FunctionSpace, GridPart, 1 >; +}; +#else +template +struct DummySpaceVcfv { + using DiscreteFunctionSpace = GetPropType; + DummySpaceVcfv(const DiscreteFunctionSpace&) {}; + DummySpaceVcfv(const int&) {}; +}; + +template +struct DiscreteFunctionSpace { + using type = DummySpaceVcfv; +}; +#endif + +//! Set the border list creator for vertices +template +struct BorderListCreator +{ private: + using VertexMapper = GetPropType; + using GridView = GetPropType; +public: + using type = Linear::VertexBorderListFromGrid; +}; + +//! For the vertex centered finite volume method, ghost and overlap elements must _not_ +//! be assembled to avoid accounting twice for the fluxes over the process boundary faces +//! of the local process' grid partition +template +struct LinearizeNonLocalElements { static constexpr bool value = false; }; + + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup VcfvDiscretization + * + * \brief The base class for the vertex centered finite volume discretization scheme. + */ +template +class VcfvDiscretization : public GetPropType +{ + using ParentType = GetPropType; + using Implementation = GetPropType; + using DofMapper = GetPropType; + using GridView = GetPropType; + using Simulator = GetPropType; + + enum { dim = GridView::dimension }; + +public: + VcfvDiscretization(Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Returns a string of discretization's human-readable name + */ + static std::string discretizationName() + { return "vcfv"; } + + /*! + * \brief Returns the number of global degrees of freedom (DOFs) due to the grid + */ + size_t numGridDof() const + { return static_cast(this->gridView_.size(/*codim=*/dim)); } + + /*! + * \brief Mapper to convert the Dune entities of the + * discretization's degrees of freedoms are to indices. + */ + const DofMapper& dofMapper() const + { return this->vertexMapper(); } + + /*! + * \brief Serializes the current state of the model. + * + * \tparam Restarter The type of the serializer class + * + * \param res The serializer object + */ + template + void serialize(Restarter& res) + { res.template serializeEntities(asImp_(), this->gridView_); } + + /*! + * \brief Deserializes the state of the model. + * + * \tparam Restarter The type of the serializer class + * + * \param res The serializer object + */ + template + void deserialize(Restarter& res) + { + res.template deserializeEntities(asImp_(), this->gridView_); + this->solution(/*timeIdx=*/1) = this->solution(/*timeIdx=*/0); + } + +private: + Implementation& asImp_() + { return *static_cast(this); } + const Implementation& asImp_() const + { return *static_cast(this); } +}; +} // namespace Opm + +#endif diff --git a/opm/models/discretization/vcfv/vcfvgridcommhandlefactory.hh b/opm/models/discretization/vcfv/vcfvgridcommhandlefactory.hh new file mode 100644 index 00000000000..8a00c134aaf --- /dev/null +++ b/opm/models/discretization/vcfv/vcfvgridcommhandlefactory.hh @@ -0,0 +1,91 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::VcfvGridCommHandleFactory + */ +#ifndef EWOMS_VCFV_GRID_COMM_HANDLE_FACTORY_HH +#define EWOMS_VCFV_GRID_COMM_HANDLE_FACTORY_HH + +#include "vcfvproperties.hh" + +#include + +namespace Opm { +/*! + * \ingroup VcfvDiscretization + * + * \brief A class which provides types for DUNE grid handles for + * communication. + * + * This is required for parallel computations + */ +template +class VcfvGridCommHandleFactory +{ + using DofMapper = GetPropType; + using GridView = GetPropType; + + static const int dim = GridView::dimension; + +public: + /*! + * \brief Return a handle which computes the minimum of a value + * for each overlapping degree of freedom across all processes. + */ + template + static std::shared_ptr > + minHandle(ArrayType& array, const DofMapper& dofMapper) + { + using Handle = GridCommHandleMin; + return std::shared_ptr(new Handle(array, dofMapper)); + } + + /*! + * \brief Return a handle which computes the maximum of a value + * for each overlapping degree of freedom across all processes. + */ + template + static std::shared_ptr > + maxHandle(ArrayType& array, const DofMapper& dofMapper) + { + using Handle = GridCommHandleMax; + return std::shared_ptr(new Handle(array, dofMapper)); + } + + /*! + * \brief Return a handle which computes the sum of all values + * all overlapping degrees of freedom across all processes. + */ + template + static std::shared_ptr > + sumHandle(ArrayType& array, const DofMapper& dofMapper) + { + using Handle = GridCommHandleSum; + return std::shared_ptr(new Handle(array, dofMapper)); + } +}; +} // namespace Opm + +#endif diff --git a/opm/models/discretization/vcfv/vcfvproperties.hh b/opm/models/discretization/vcfv/vcfvproperties.hh new file mode 100644 index 00000000000..e88e7987c66 --- /dev/null +++ b/opm/models/discretization/vcfv/vcfvproperties.hh @@ -0,0 +1,51 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Declares the basic properties used by the common infrastructure of + * the vertex-centered finite volume discretization. + */ +#ifndef EWOMS_VCFV_PROPERTIES_HH +#define EWOMS_VCFV_PROPERTIES_HH + +#include + +#include + +namespace Opm::Properties { + +//! The type tag for models based on the VCFV-scheme +// Create new type tags +namespace TTag { +struct VcfvDiscretization { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! Use P1 finite-elements gradients instead of two-point gradients. Note that setting +//! this property to true requires the dune-localfunctions module to be available. +template +struct UseP1FiniteElementGradients { using type = UndefinedProperty; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/discretization/vcfv/vcfvstencil.hh b/opm/models/discretization/vcfv/vcfvstencil.hh new file mode 100644 index 00000000000..734bc848025 --- /dev/null +++ b/opm/models/discretization/vcfv/vcfvstencil.hh @@ -0,0 +1,1390 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::VcfvStencil + */ +#ifndef EWOMS_VCFV_STENCIL_HH +#define EWOMS_VCFV_STENCIL_HH + +#include + +#include +#include +#include + +#if HAVE_DUNE_LOCALFUNCTIONS +#include +#endif // HAVE_DUNE_LOCALFUNCTIONS + +#include + +#include +#include + +namespace Opm { + +/*! + * \brief The types of reference elements available. + */ +enum ElementType +{ + none, + simplex, + cube, +}; + +/*! + * \cond SKIP_THIS + */ +template +class VcfvScvGeometries; + +//////////////////// +// local geometries for 1D elements +//////////////////// +template +class VcfvScvGeometries +{ + enum { dim = 1 }; + enum { numScv = 2 }; + +public: + using ScvLocalGeometry = QuadrialteralQuadratureGeometry; + + static void init() + { + // 1D LINE SEGMENTS + Scalar scvCorners[numScv][ScvLocalGeometry::numCorners][dim] = { + { // corners of the first sub control volume + { 0.0 }, + { 0.5 } + }, + + { // corners of the second sub control volume + { 0.5 }, + { 1.0 } + } + }; + for (unsigned scvIdx = 0; scvIdx < numScv; ++scvIdx) + scvGeoms_[scvIdx].setCorners(scvCorners[scvIdx], ScvLocalGeometry::numCorners); + } + + static const ScvLocalGeometry& get(unsigned scvIdx) + { return scvGeoms_[scvIdx]; } + +private: + static ScvLocalGeometry scvGeoms_[numScv]; +}; + +template +typename VcfvScvGeometries::ScvLocalGeometry +VcfvScvGeometries::scvGeoms_[ + VcfvScvGeometries::numScv]; + +template +class VcfvScvGeometries +{ + enum { dim = 1 }; + enum { numScv = 2 }; + +public: + using ScvLocalGeometry = QuadrialteralQuadratureGeometry; + + static const ScvLocalGeometry& get(unsigned) + { + throw std::logic_error("Not implemented: VcfvScvGeometries"); + } +}; + +//////////////////// +// local geometries for 2D elements +//////////////////// +template +class VcfvScvGeometries +{ + enum { dim = 2 }; + enum { numScv = 3 }; + +public: + using ScvLocalGeometry = QuadrialteralQuadratureGeometry; + + static const ScvLocalGeometry& get(unsigned scvIdx) + { return scvGeoms_[scvIdx]; } + + static void init() + { + // 2D SIMPLEX + Scalar scvCorners[numScv][ScvLocalGeometry::numCorners][dim] = + { + { // SCV 0 corners + { 0.0, 0.0 }, + { 1.0/2, 0.0 }, + { 1.0/3, 1.0/3 }, + { 0.0, 1.0/2 }, + }, + + { // SCV 1 corners + { 1.0/2, 0.0 }, + { 1.0, 0.0 }, + { 1.0/3, 1.0/3 }, + { 1.0/2, 1.0/2 }, + }, + + { // SCV 2 corners + { 0.0, 1.0/2 }, + { 1.0/3, 1.0/3 }, + { 0.0, 1.0 }, + { 1.0/2, 1.0/2 }, + } + }; + + for (unsigned scvIdx = 0; scvIdx < numScv; ++scvIdx) + scvGeoms_[scvIdx].setCorners(scvCorners[scvIdx], ScvLocalGeometry::numCorners); + } + +private: + static ScvLocalGeometry scvGeoms_[numScv]; +}; + +template +typename VcfvScvGeometries::ScvLocalGeometry +VcfvScvGeometries::scvGeoms_[ + VcfvScvGeometries::numScv]; + +template +class VcfvScvGeometries +{ + enum { dim = 2 }; + enum { numScv = 4 }; + +public: + using ScvLocalGeometry = QuadrialteralQuadratureGeometry; + + static const ScvLocalGeometry& get(unsigned scvIdx) + { return scvGeoms_[scvIdx]; } + + static void init() + { + // 2D CUBE + Scalar scvCorners[numScv][ScvLocalGeometry::numCorners][dim] = + { + { // SCV 0 corners + { 0.0, 0.0 }, + { 0.5, 0.0 }, + { 0.0, 0.5 }, + { 0.5, 0.5 } + }, + + { // SCV 1 corners + { 0.5, 0.0 }, + { 1.0, 0.0 }, + { 0.5, 0.5 }, + { 1.0, 0.5 } + }, + + { // SCV 2 corners + { 0.0, 0.5 }, + { 0.5, 0.5 }, + { 0.0, 1.0 }, + { 0.5, 1.0 } + }, + + { // SCV 3 corners + { 0.5, 0.5 }, + { 1.0, 0.5 }, + { 0.5, 1.0 }, + { 1.0, 1.0 } + } + }; + + for (unsigned scvIdx = 0; scvIdx < numScv; ++scvIdx) + scvGeoms_[scvIdx].setCorners(scvCorners[scvIdx], ScvLocalGeometry::numCorners); + } + + static ScvLocalGeometry scvGeoms_[numScv]; +}; + +template +typename VcfvScvGeometries::ScvLocalGeometry +VcfvScvGeometries::scvGeoms_[ + VcfvScvGeometries::numScv]; + +//////////////////// +// local geometries for 3D elements +//////////////////// +template +class VcfvScvGeometries +{ + enum { dim = 3 }; + enum { numScv = 4 }; + +public: + using ScvLocalGeometry = QuadrialteralQuadratureGeometry; + + static const ScvLocalGeometry& get(unsigned scvIdx) + { return scvGeoms_[scvIdx]; } + + static void init() + { + // 3D SIMPLEX + Scalar scvCorners[numScv][ScvLocalGeometry::numCorners][dim] = + { + { // SCV 0 corners + { 0.0, 0.0, 0.0 }, + { 1.0/2, 0.0, 0.0 }, + { 0.0, 1.0/2, 0.0 }, + { 1.0/3, 1.0/3, 0.0 }, + + { 0.0, 0.0, 0.5 }, + { 1.0/3, 0.0, 1.0/3 }, + { 0.0, 1.0/3, 1.0/3 }, + { 1.0/4, 1.0/4, 1.0/4 }, + }, + + { // SCV 1 corners + { 1.0/2, 0.0, 0.0 }, + { 1.0, 0.0, 0.0 }, + { 1.0/3, 1.0/3, 0.0 }, + { 1.0/2, 1.0/2, 0.0 }, + + { 1.0/3, 0.0, 1.0/3 }, + { 1.0/2, 0.0, 1.0/2 }, + { 1.0/4, 1.0/4, 1.0/4 }, + { 1.0/3, 1.0/3, 1.0/3 }, + }, + + { // SCV 2 corners + { 0.0, 1.0/2, 0.0 }, + { 1.0/3, 1.0/3, 0.0 }, + { 0.0, 1.0, 0.0 }, + { 1.0/2, 1.0/2, 0.0 }, + + { 0.0, 1.0/3, 1.0/3 }, + { 1.0/4, 1.0/4, 1.0/4 }, + { 0.0, 1.0/2, 1.0/2 }, + { 1.0/3, 1.0/3, 1.0/3 }, + }, + + { // SCV 3 corners + { 0.0, 0.0, 1.0/2 }, + { 1.0/3, 0.0, 1.0/3 }, + { 0.0, 1.0/3, 1.0/3 }, + { 1.0/4, 1.0/4, 1.0/4 }, + + { 0.0, 0.0, 1.0 }, + { 1.0/2, 0.0, 1.0/2 }, + { 0.0, 1.0/2, 1.0/2 }, + { 1.0/3, 1.0/3, 1.0/3 }, + } + }; + + for (unsigned scvIdx = 0; scvIdx < numScv; ++scvIdx) + scvGeoms_[scvIdx].setCorners(scvCorners[scvIdx], ScvLocalGeometry::numCorners); + } + +private: + static ScvLocalGeometry scvGeoms_[numScv]; +}; + +template +typename VcfvScvGeometries::ScvLocalGeometry +VcfvScvGeometries::scvGeoms_[ + VcfvScvGeometries::numScv]; + +template +class VcfvScvGeometries +{ + enum { dim = 3 }; + enum { numScv = 8 }; + +public: + using ScvLocalGeometry = QuadrialteralQuadratureGeometry; + + static const ScvLocalGeometry& get(unsigned scvIdx) + { return scvGeoms_[scvIdx]; } + + static void init() + { + // 3D CUBE + Scalar scvCorners[numScv][ScvLocalGeometry::numCorners][dim] = + { + { // SCV 0 corners + { 0.0, 0.0, 0.0 }, + { 1.0/2, 0.0, 0.0 }, + { 0.0, 1.0/2, 0.0 }, + { 1.0/2, 1.0/2, 0.0 }, + + { 0.0, 0.0, 1.0/2 }, + { 1.0/2, 0.0, 1.0/2 }, + { 0.0, 1.0/2, 1.0/2 }, + { 1.0/2, 1.0/2, 1.0/2 }, + }, + + { // SCV 1 corners + { 1.0/2, 0.0, 0.0 }, + { 1.0, 0.0, 0.0 }, + { 1.0/2, 1.0/2, 0.0 }, + { 1.0, 1.0/2, 0.0 }, + + { 1.0/2, 0.0, 1.0/2 }, + { 1.0, 0.0, 1.0/2 }, + { 1.0/2, 1.0/2, 1.0/2 }, + { 1.0, 1.0/2, 1.0/2 }, + }, + + { // SCV 2 corners + { 0.0, 1.0/2, 0.0 }, + { 1.0/2, 1.0/2, 0.0 }, + { 0.0, 1.0, 0.0 }, + { 1.0/2, 1.0, 0.0 }, + + { 0.0, 1.0/2, 1.0/2 }, + { 1.0/2, 1.0/2, 1.0/2 }, + { 0.0, 1.0, 1.0/2 }, + { 1.0/2, 1.0, 1.0/2 }, + }, + + { // SCV 3 corners + { 1.0/2, 1.0/2, 0.0 }, + { 1.0, 1.0/2, 0.0 }, + { 1.0/2, 1.0, 0.0 }, + { 1.0, 1.0, 0.0 }, + + { 1.0/2, 1.0/2, 1.0/2 }, + { 1.0, 1.0/2, 1.0/2 }, + { 1.0/2, 1.0, 1.0/2 }, + { 1.0, 1.0, 1.0/2 }, + }, + + { // SCV 4 corners + { 0.0, 0.0, 1.0/2 }, + { 1.0/2, 0.0, 1.0/2 }, + { 0.0, 1.0/2, 1.0/2 }, + { 1.0/2, 1.0/2, 1.0/2 }, + + { 0.0, 0.0, 1.0 }, + { 1.0/2, 0.0, 1.0 }, + { 0.0, 1.0/2, 1.0 }, + { 1.0/2, 1.0/2, 1.0 }, + }, + + { // SCV 5 corners + { 1.0/2, 0.0, 1.0/2 }, + { 1.0, 0.0, 1.0/2 }, + { 1.0/2, 1.0/2, 1.0/2 }, + { 1.0, 1.0/2, 1.0/2 }, + + { 1.0/2, 0.0, 1.0 }, + { 1.0, 0.0, 1.0 }, + { 1.0/2, 1.0/2, 1.0 }, + { 1.0, 1.0/2, 1.0 }, + }, + + { // SCV 6 corners + { 0.0, 1.0/2, 1.0/2 }, + { 1.0/2, 1.0/2, 1.0/2 }, + { 0.0, 1.0, 1.0/2 }, + { 1.0/2, 1.0, 1.0/2 }, + + { 0.0, 1.0/2, 1.0 }, + { 1.0/2, 1.0/2, 1.0 }, + { 0.0, 1.0, 1.0 }, + { 1.0/2, 1.0, 1.0 }, + }, + + { // SCV 7 corners + { 1.0/2, 1.0/2, 1.0/2 }, + { 1.0, 1.0/2, 1.0/2 }, + { 1.0/2, 1.0, 1.0/2 }, + { 1.0, 1.0, 1.0/2 }, + + { 1.0/2, 1.0/2, 1.0 }, + { 1.0, 1.0/2, 1.0 }, + { 1.0/2, 1.0, 1.0 }, + { 1.0, 1.0, 1.0 }, + }, + }; + + for (unsigned scvIdx = 0; scvIdx < numScv; ++scvIdx) + scvGeoms_[scvIdx].setCorners(scvCorners[scvIdx], ScvLocalGeometry::numCorners); + } +private: + static ScvLocalGeometry scvGeoms_[numScv]; +}; + +template +typename VcfvScvGeometries::ScvLocalGeometry +VcfvScvGeometries::scvGeoms_[ + VcfvScvGeometries::numScv]; + +/*! + * \endcond + */ + +/*! + * \ingroup VcfvDiscretization + * + * \brief Represents the finite volume geometry of a single element in + * the VCFV discretization. + * + * The VCFV discretization is a vertex centered finite volume approach. This + * means that each vertex corresponds to a control volume which + * intersects each of the vertex' neighboring elements. If only + * looking at a single element of the primary grid (which is what this + * class does), the element is subdivided into multiple fragments of + * control volumes called sub-control volumes. Each of the element's + * vertices corresponds to exactly one sub-control volume in this + * scenario. + * + * For the vertex-cented finite volume method the sub-control volumes + * are constructed by connecting the element's center with each edge + * of the element. + */ +template +class VcfvStencil +{ + enum{dim = GridView::dimension}; + enum{dimWorld = GridView::dimensionworld}; + enum{maxNC = (dim < 3 ? 4 : 8)}; + enum{maxNE = (dim < 3 ? 4 : 12)}; + enum{maxNF = (dim < 3 ? 1 : 6)}; + enum{maxBF = (dim < 3 ? 8 : 24)}; + using CoordScalar = typename GridView::ctype; + using Element = typename GridView::Traits::template Codim<0>::Entity ; +public: + using Entity = typename GridView::Traits::template Codim::Entity ; +private: + using Geometry = typename Element::Geometry; + using DimVector = Dune::FieldVector; + using GlobalPosition = Dune::FieldVector; + using LocalPosition = Dune::FieldVector; + using IntersectionIterator = typename GridView::IntersectionIterator; + + using ScvLocalGeometry = QuadrialteralQuadratureGeometry; + +#if HAVE_DUNE_LOCALFUNCTIONS + using LocalFiniteElementCache = Dune::PQkLocalFiniteElementCache; + using LocalFiniteElement = typename LocalFiniteElementCache::FiniteElementType; + using LocalBasisTraits = typename LocalFiniteElement::Traits::LocalBasisType::Traits; + using ShapeJacobian = typename LocalBasisTraits::JacobianType; +#endif // HAVE_DUNE_LOCALFUNCTIONS + + Scalar quadrilateralArea(const GlobalPosition& p0, + const GlobalPosition& p1, + const GlobalPosition& p2, + const GlobalPosition& p3) + { + return 0.5*std::abs((p3[0] - p1[0])*(p2[1] - p0[1]) - (p3[1] - p1[1])*(p2[0] - p0[0])); + } + + void crossProduct(DimVector& c, const DimVector& a, const DimVector& b) + { + c[0] = a[1]*b[2] - a[2]*b[1]; + c[1] = a[2]*b[0] - a[0]*b[2]; + c[2] = a[0]*b[1] - a[1]*b[0]; + } + + Scalar pyramidVolume(const GlobalPosition& p0, + const GlobalPosition& p1, + const GlobalPosition& p2, + const GlobalPosition& p3, + const GlobalPosition& p4) + { + DimVector a(p2); a -= p0; + DimVector b(p3); b -= p1; + + DimVector n; + crossProduct(n, a, b); + + a = p4; a -= p0; + + return 1.0/6.0*(n*a); + } + + Scalar prismVolume(const GlobalPosition& p0, + const GlobalPosition& p1, + const GlobalPosition& p2, + const GlobalPosition& p3, + const GlobalPosition& p4, + const GlobalPosition& p5) + { + DimVector a(p4); + for (unsigned k = 0; k < dimWorld; ++k) + a[k] -= p0[k]; + DimVector b(p1); + for (unsigned k = 0; k < dimWorld; ++k) + b[k] -= p3[k]; + DimVector m; + crossProduct(m, a, b); + + for (unsigned k = 0; k < dimWorld; ++k) + a[k] = p1[k] - p0[k]; + for (unsigned k = 0; k < dimWorld; ++k) + b[k] = p2[k] - p0[k]; + DimVector n; + crossProduct(n, a, b); + n += m; + + for (unsigned k = 0; k < dimWorld; ++k) + a[k] = p5[k] - p0[k]; + + return std::abs(1.0/6.0*(n*a)); + } + + Scalar hexahedronVolume(const GlobalPosition& p0, + const GlobalPosition& p1, + const GlobalPosition& p2, + const GlobalPosition& p3, + const GlobalPosition& p4, + const GlobalPosition& p5, + const GlobalPosition& p6, + const GlobalPosition& p7) + { + return + prismVolume(p0,p1,p2,p4,p5,p6) + + prismVolume(p0,p2,p3,p4,p6,p7); + } + + void normalOfQuadrilateral3D(DimVector& normal, + const GlobalPosition& p0, + const GlobalPosition& p1, + const GlobalPosition& p2, + const GlobalPosition& p3) + { + DimVector a(p2); + for (unsigned k = 0; k < dimWorld; ++k) + a[k] -= p0[k]; + DimVector b(p3); + for (unsigned k = 0; k < dimWorld; ++k) + b[k] -= p1[k]; + + crossProduct(normal, a, b); + normal *= 0.5; + } + + Scalar quadrilateralArea3D(const GlobalPosition& p0, + const GlobalPosition& p1, + const GlobalPosition& p2, + const GlobalPosition& p3) + { + DimVector normal; + normalOfQuadrilateral3D(normal, p0, p1, p2, p3); + return normal.two_norm(); + } + + void getFaceIndices(unsigned numElemVertices, unsigned k, unsigned& leftFace, unsigned& rightFace) + { + static const unsigned edgeToFaceTet[2][6] = { + {1, 0, 3, 2, 1, 3}, + {0, 2, 0, 1, 3, 2} + }; + static const unsigned edgeToFacePyramid[2][8] = { + {1, 2, 3, 4, 1, 3, 4, 2}, + {0, 0, 0, 0, 3, 2, 1, 4} + }; + static const unsigned edgeToFacePrism[2][9] = { + {1, 0, 2, 0, 1, 2, 4, 4, 4}, + {0, 2, 1, 3, 3, 3, 0, 1, 2} + }; + static const unsigned edgeToFaceHex[2][12] = { + {0, 2, 3, 1, 4, 1, 2, 4, 0, 5, 5, 3}, + {2, 1, 0, 3, 0, 4, 4, 3, 5, 1, 2, 5} + }; + + switch (numElemVertices) { + case 4: + leftFace = edgeToFaceTet[0][k]; + rightFace = edgeToFaceTet[1][k]; + break; + case 5: + leftFace = edgeToFacePyramid[0][k]; + rightFace = edgeToFacePyramid[1][k]; + break; + case 6: + leftFace = edgeToFacePrism[0][k]; + rightFace = edgeToFacePrism[1][k]; + break; + case 8: + leftFace = edgeToFaceHex[0][k]; + rightFace = edgeToFaceHex[1][k]; + break; + default: + throw std::logic_error("Not implemented: VcfvStencil::getFaceIndices for " + +std::to_string(numElemVertices)+" vertices"); + break; + } + } + + void getEdgeIndices(unsigned numElemVertices, unsigned face, unsigned vert, unsigned& leftEdge, unsigned& rightEdge) + { + static const int faceAndVertexToLeftEdgeTet[4][4] = { + { 0, 0, 2, -1}, + { 0, 0, -1, 3}, + { 1, -1, 1, 3}, + {-1, 2, 2, 4} + }; + static const int faceAndVertexToRightEdgeTet[4][4] = { + { 1, 2, 1, -1}, + { 3, 4, -1, 4}, + { 3, -1, 5, 5}, + {-1, 4, 5, 5} + }; + static const int faceAndVertexToLeftEdgePyramid[5][5] = { + { 0, 2, 3, 1, -1}, + { 0, -1, 0, -1, 4}, + {-1, 1, -1, 1, 5}, + { 2, 2, -1, -1, 4}, + {-1, -1, 3, 3, 7} + }; + static const int faceAndVertexToRightEdgePyramid[5][5] = { + { 2, 1, 0, 3, -1}, + { 4, -1, 6, -1, 6}, + {-1, 5, -1, 7, 7}, + { 4, 5, -1, -1, 5}, + {-1, -1, 6, 7, 6} + }; + static const int faceAndVertexToLeftEdgePrism[5][6] = { + { 3, 3, -1, 0, 1, -1}, + { 4, -1, 4, 0, -1, 2}, + {-1, 5, 5, -1, 1, 2}, + { 3, 3, 5, -1, -1, -1}, + {-1, -1, -1, 6, 6, 8} + }; + static const int faceAndVertexToRightEdgePrism[5][6] = { + { 0, 1, -1, 6, 6, -1}, + { 0, -1, 2, 7, -1, 7}, + {-1, 1, 2, -1, 8, 8}, + { 4, 5, 4, -1, -1, -1}, + {-1, -1, -1, 7, 8, 7} + }; + static const int faceAndVertexToLeftEdgeHex[6][8] = { + { 0, -1, 4, -1, 8, -1, 2, -1}, + {-1, 5, -1, 3, -1, 1, -1, 9}, + { 6, 1, -1, -1, 0, 10, -1, -1}, + {-1, -1, 2, 7, -1, -1, 11, 3}, + { 4, 6, 7, 5, -1, -1, -1, -1}, + {-1, -1, -1, -1, 10, 9, 8, 11} + }; + static const int faceAndVertexToRightEdgeHex[6][8] = { + { 4, -1, 2, -1, 0, -1, 8, -1}, + {-1, 1, -1, 5, -1, 9, -1, 3}, + { 0, 6, -1, -1, 10, 1, -1, -1}, + {-1, -1, 7, 3, -1, -1, 2, 11}, + { 6, 5, 4, 7, -1, -1, -1, -1}, + {-1, -1, -1, -1, 8, 10, 11, 9} + }; + + switch (numElemVertices) { + case 4: + leftEdge = static_cast(faceAndVertexToLeftEdgeTet[face][vert]); + rightEdge = static_cast(faceAndVertexToRightEdgeTet[face][vert]); + break; + case 5: + leftEdge = static_cast(faceAndVertexToLeftEdgePyramid[face][vert]); + rightEdge = static_cast(faceAndVertexToRightEdgePyramid[face][vert]); + break; + case 6: + leftEdge = static_cast(faceAndVertexToLeftEdgePrism[face][vert]); + rightEdge = static_cast(faceAndVertexToRightEdgePrism[face][vert]); + break; + case 8: + leftEdge = static_cast(faceAndVertexToLeftEdgeHex[face][vert]); + rightEdge = static_cast(faceAndVertexToRightEdgeHex[face][vert]); + break; + default: + throw std::logic_error("Not implemented: VcfvStencil::getFaceIndices for " + +std::to_string(numElemVertices)+" vertices"); + break; + } + } + +public: + //! exported Mapper type + using Mapper = Dune::MultipleCodimMultipleGeomTypeMapper; + + class ScvGeometry + { + public: + const GlobalPosition center() const + { return global(localGeometry_->center()); } + + const GlobalPosition corner(unsigned cornerIdx) const + { return global(localGeometry_->corner(cornerIdx)); } + + const GlobalPosition global(const LocalPosition& localPos) const + { return element_->geometry().global(localPos); } + + const ScvLocalGeometry& localGeometry() const + { return *localGeometry_; } + + const ScvLocalGeometry *localGeometry_; + const Element *element_; + }; + + struct SubControlVolume //! finite volume intersected with element + { + const GlobalPosition& globalPos() const + { return global; } + + const GlobalPosition center() const + { return geometry_.center(); } + + Scalar volume() const + { return volume_; } + + const ScvLocalGeometry& localGeometry() const + { return geometry_.localGeometry(); } + + const ScvGeometry& geometry() const + { return geometry_; } + + //! local vertex position + LocalPosition local; + //! global vertex position + GlobalPosition global; + //! space occupied by the sub-control volume + Scalar volume_; + //! The geometry of the sub-control volume in local coordinates. + ScvGeometry geometry_; + + //! derivative of shape function at the center of the sub control volume + Dune::FieldVector gradCenter; + }; + + struct SubControlVolumeFace //! interior face of a sub control volume + { + const DimVector& normal() const + { return normal_; } + + unsigned short interiorIndex() const + { return i; } + + unsigned short exteriorIndex() const + { return j; } + + Scalar area() const + { return area_; } + + const LocalPosition& localPos() const + { return ipLocal_; } + + const GlobalPosition& integrationPos() const + { return ipGlobal_; } + + //! scvf seperates corner i and j of elem + unsigned short i,j; + //! integration point in local coords + LocalPosition ipLocal_; + //! integration point in global coords + GlobalPosition ipGlobal_; + //! normal on face pointing to CV j or outward of the domain with length equal to |scvf| + DimVector normal_; + //! area of face + Scalar area_; + }; + + //! compatibility alias + using BoundaryFace = SubControlVolumeFace; + + VcfvStencil(const GridView& gridView, const Mapper& mapper) + : gridView_(gridView) + , vertexMapper_(mapper ) + , element_(*gridView.template begin()) + { + // try to check if the mapper really maps the vertices + assert(static_cast(gridView.size(/*codim=*/dimWorld)) == static_cast(mapper.size())); + + static bool localGeometriesInitialized = false; + if (!localGeometriesInitialized) { + localGeometriesInitialized = true; + + VcfvScvGeometries::init(); + VcfvScvGeometries::init(); + VcfvScvGeometries::init(); + VcfvScvGeometries::init(); + VcfvScvGeometries::init(); + } + } + + /*! + * \brief Update the non-geometric part of the stencil. + * + * I.e., indices and neighboring information, but nothing else... + */ + void updateTopology(const Element& e) + { + element_ = e; + + numVertices = e.subEntities(/*codim=*/dim); + numEdges = e.subEntities(/*codim=*/dim-1); + numFaces = (dim<3)?0:e.subEntities(/*codim=*/1); + + numBoundarySegments_ = 0; // TODO: really required here(?) + + // compute the local and global coordinates of the element + const Geometry& geometry = e.geometry(); + geometryType_ = geometry.type(); + const auto& referenceElement = Dune::ReferenceElements::general(geometryType_); + for (unsigned vertexIdx = 0; vertexIdx < numVertices; vertexIdx++) { + subContVol[vertexIdx].local = referenceElement.position(static_cast(vertexIdx), dim); + subContVol[vertexIdx].global = geometry.corner(static_cast(vertexIdx)); + } + } + + void updatePrimaryTopology(const Element& element) + { + // since all degrees of freedom in a stencil are "primary" DOFs for the + // vertex-centered finite volume method, there's no difference to + // updateTopology() + updateTopology(element); + } + + void update(const Element& e) + { + updateTopology(e); + + const Geometry& geometry = e.geometry(); + geometryType_ = geometry.type(); + + const auto& referenceElement = Dune::ReferenceElements::general(geometryType_); + + elementVolume = geometry.volume(); + elementLocal = referenceElement.position(0,0); + elementGlobal = geometry.global(elementLocal); + + // corners: + for (unsigned vert = 0; vert < numVertices; vert++) { + subContVol[vert].local = referenceElement.position(static_cast(vert), dim); + subContVol[vert].global = geometry.global(subContVol[vert].local); + } + + // edges: + for (unsigned edge = 0; edge < numEdges; edge++) { + edgeCoord[edge] = geometry.global(referenceElement.position(static_cast(edge), dim-1)); + } + + // faces: + for (unsigned face = 0; face < numFaces; face++) { + faceCoord[face] = geometry.global(referenceElement.position(static_cast(face), 1)); + } + + // fill sub control volume data use specialization for this + // \todo maybe it would be a good idea to migrate everything + // which is dependend of the grid's dimension to + // _VcfvFVElemGeomHelper in order to benefit from more aggressive + // compiler optimizations... + fillSubContVolData_(); + + // fill sub control volume face data: + for (unsigned k = 0; k < numEdges; k++) { // begin loop over edges / sub control volume faces + unsigned short i = static_cast(referenceElement.subEntity(static_cast(k), dim-1, 0, dim)); + unsigned short j = static_cast(referenceElement.subEntity(static_cast(k), dim-1, 1, dim)); + if (numEdges == 4 && (i == 2 || j == 2)) + std::swap(i, j); + subContVolFace[k].i = i; + subContVolFace[k].j = j; + + // calculate the local integration point and + // the face normal. note that since dim is a + // constant which is known at compile time + // the compiler can optimize away all if + // cases which don't apply. + LocalPosition ipLocal_; + DimVector diffVec; + if (dim==1) { + subContVolFace[k].ipLocal_ = 0.5; + subContVolFace[k].normal_ = 1.0; + subContVolFace[k].area_ = 1.0; + ipLocal_ = subContVolFace[k].ipLocal_; + } + else if (dim==2) { + ipLocal_ = referenceElement.position(static_cast(k), dim-1) + elementLocal; + ipLocal_ *= 0.5; + subContVolFace[k].ipLocal_ = ipLocal_; + for (unsigned m = 0; m < dimWorld; ++m) + diffVec[m] = elementGlobal[m] - edgeCoord[k][m]; + subContVolFace[k].normal_[0] = diffVec[1]; + subContVolFace[k].normal_[1] = -diffVec[0]; + + for (unsigned m = 0; m < dimWorld; ++m) + diffVec[m] = subContVol[j].global[m] - subContVol[i].global[m]; + // make sure the normal points to the right direction + if (subContVolFace[k].normal_ * diffVec < 0) + subContVolFace[k].normal_ *= -1; + + subContVolFace[k].area_ = subContVolFace[k].normal_.two_norm(); + subContVolFace[k].normal_ /= subContVolFace[k].area_; + } + else if (dim==3) { + unsigned leftFace; + unsigned rightFace; + getFaceIndices(numVertices, k, leftFace, rightFace); + ipLocal_ = referenceElement.position(static_cast(k), dim-1) + elementLocal + + referenceElement.position(static_cast(leftFace), 1) + + referenceElement.position(static_cast(rightFace), 1); + ipLocal_ *= 0.25; + subContVolFace[k].ipLocal_ = ipLocal_; + normalOfQuadrilateral3D(subContVolFace[k].normal_, + edgeCoord[k], faceCoord[rightFace], + elementGlobal, faceCoord[leftFace]); + subContVolFace[k].area_ = subContVolFace[k].normal_.two_norm(); + subContVolFace[k].normal_ /= subContVolFace[k].area_; + } + + // get the global integration point and the Jacobian inverse + subContVolFace[k].ipGlobal_ = geometry.global(ipLocal_); + } // end loop over edges / sub control volume faces + + // fill boundary face data: + IntersectionIterator endit = gridView_.iend(e); + for (IntersectionIterator it = gridView_.ibegin(e); it != endit; ++it) { + const typename IntersectionIterator::Intersection& intersection = *it ; + + if ( ! intersection.boundary()) + continue; + + unsigned face = static_cast(intersection.indexInInside()); + unsigned numVerticesOfFace = static_cast(referenceElement.size(static_cast(face), 1, dim)); + for (unsigned vertInFace = 0; vertInFace < numVerticesOfFace; vertInFace++) + { + unsigned short vertInElement = static_cast(referenceElement.subEntity(static_cast(face), 1, static_cast(vertInFace), dim)); + unsigned bfIdx = numBoundarySegments_; + ++numBoundarySegments_; + + if (dim == 1) { + boundaryFace_[bfIdx].ipLocal_ = referenceElement.position(static_cast(vertInElement), dim); + boundaryFace_[bfIdx].area_ = 1.0; + } + else if (dim == 2) { + boundaryFace_[bfIdx].ipLocal_ = referenceElement.position(static_cast(vertInElement), dim) + + referenceElement.position(static_cast(face), 1); + boundaryFace_[bfIdx].ipLocal_ *= 0.5; + boundaryFace_[bfIdx].area_ = 0.5 * intersection.geometry().volume(); + } + else if (dim == 3) { + unsigned leftEdge; + unsigned rightEdge; + getEdgeIndices(numVertices, face, vertInElement, leftEdge, rightEdge); + boundaryFace_[bfIdx].ipLocal_ = referenceElement.position(static_cast(vertInElement), dim) + + referenceElement.position(static_cast(face), 1) + + referenceElement.position(static_cast(leftEdge), dim-1) + + referenceElement.position(static_cast(rightEdge), dim-1); + boundaryFace_[bfIdx].ipLocal_ *= 0.25; + boundaryFace_[bfIdx].area_ = + quadrilateralArea3D(subContVol[vertInElement].global, + edgeCoord[rightEdge], + faceCoord[face], + edgeCoord[leftEdge]); + } + else + throw std::logic_error("Not implemented:VcfvStencil for dim = "+std::to_string(dim)); + + boundaryFace_[bfIdx].ipGlobal_ = geometry.global(boundaryFace_[bfIdx].ipLocal_); + boundaryFace_[bfIdx].i = vertInElement; + boundaryFace_[bfIdx].j = vertInElement; + + // ASSUME constant normal on the segment of the boundary face + boundaryFace_[bfIdx].normal_ = intersection.centerUnitOuterNormal(); + } + } + + updateScvGeometry(e); + } + + void updateScvGeometry(const Element& element) + { + auto geomType = element.geometry().type(); + + // get the local geometries of the sub control volumes + if (geomType.isTriangle() || geomType.isTetrahedron()) { + for (unsigned vertIdx = 0; vertIdx < numVertices; ++vertIdx) { + subContVol[vertIdx].geometry_.element_ = &element; + subContVol[vertIdx].geometry_.localGeometry_ = + &VcfvScvGeometries::get(vertIdx); + } + } + else if (geomType.isLine() || geomType.isQuadrilateral() || geomType.isHexahedron()) { + for (unsigned vertIdx = 0; vertIdx < numVertices; ++vertIdx) { + subContVol[vertIdx].geometry_.element_ = &element; + subContVol[vertIdx].geometry_.localGeometry_ = + &VcfvScvGeometries::get(vertIdx); + } + } + else + throw std::logic_error("Not implemented: SCV geometries for non hexahedron elements"); + } + +#if HAVE_DUNE_LOCALFUNCTIONS + void updateCenterGradients() + { + const auto& localFiniteElement = feCache_.get(element_.type()); + const auto& geom = element_.geometry(); + + std::vector localJac; + + for (unsigned scvIdx = 0; scvIdx < numVertices; ++ scvIdx) { + const auto& localCenter = subContVol[scvIdx].localGeometry().center(); + localFiniteElement.localBasis().evaluateJacobian(localCenter, localJac); + const auto& globalPos = subContVol[scvIdx].geometry().center(); + + const auto jacInvT = geom.jacobianInverseTransposed(globalPos); + for (unsigned vert = 0; vert < numVertices; vert++) { + jacInvT.mv(localJac[vert][0], subContVol[scvIdx].gradCenter[vert]); + } + } + } +#endif + + unsigned numDof() const + { return numVertices; } + + unsigned numPrimaryDof() const + { return numDof(); } + + Dune::PartitionType partitionType(unsigned scvIdx) const + { return element_.template subEntity(scvIdx)->partitionType(); } + + const SubControlVolume& subControlVolume(unsigned scvIdx) const + { + assert(scvIdx < numDof()); + return subContVol[scvIdx]; + } + + unsigned numInteriorFaces() const + { return numEdges; } + + unsigned numBoundaryFaces() const + { return numBoundarySegments_; } + + const SubControlVolumeFace& interiorFace(unsigned faceIdx) const + { return subContVolFace[faceIdx]; } + + const BoundaryFace& boundaryFace(unsigned bfIdx) const + { return boundaryFace_[bfIdx]; } + + /*! + * \brief Return the global space index given the index of a degree of + * freedom. + */ + unsigned globalSpaceIndex(unsigned dofIdx) const + { + assert(dofIdx < numDof()); + + return static_cast(vertexMapper_.subIndex(element_, static_cast(dofIdx), /*codim=*/dim)); + } + + /*! + * \brief Return the global space index given the index of a degree of + * freedom. + */ + Entity entity(unsigned dofIdx) const + { + assert(dofIdx < numDof()); + return element_.template subEntity(static_cast(dofIdx)); + } + +private: +#if __GNUC__ || __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + void fillSubContVolData_() + { + if (dim == 1) { + // 1D + subContVol[0].volume_ = 0.5*elementVolume; + subContVol[1].volume_ = 0.5*elementVolume; + } + else if (dim == 2) { + switch (numVertices) { + case 3: // 2D, triangle + subContVol[0].volume_ = elementVolume/3; + subContVol[1].volume_ = elementVolume/3; + subContVol[2].volume_ = elementVolume/3; + break; + case 4: // 2D, quadrilinear + subContVol[0].volume_ = + quadrilateralArea(subContVol[0].global, + edgeCoord[2], + elementGlobal, + edgeCoord[0]); + subContVol[1].volume_ = + quadrilateralArea(subContVol[1].global, + edgeCoord[1], + elementGlobal, + edgeCoord[2]); + subContVol[2].volume_ = + quadrilateralArea(subContVol[2].global, + edgeCoord[0], + elementGlobal, + edgeCoord[3]); + subContVol[3].volume_ = + quadrilateralArea(subContVol[3].global, + edgeCoord[3], + elementGlobal, + edgeCoord[1]); + break; + default: + throw std::logic_error("Not implemented:VcfvStencil dim = "+std::to_string(dim) + +", numVertices = "+std::to_string(numVertices)); + } + } + else if (dim == 3) { + switch (numVertices) { + case 4: // 3D, tetrahedron + for (unsigned k = 0; k < numVertices; k++) + subContVol[k].volume_ = elementVolume / 4.0; + break; + case 5: // 3D, pyramid + subContVol[0].volume_ = + hexahedronVolume(subContVol[0].global, + edgeCoord[2], + faceCoord[0], + edgeCoord[0], + edgeCoord[4], + faceCoord[3], + elementGlobal, + faceCoord[1]); + subContVol[1].volume_ = + hexahedronVolume(subContVol[1].global, + edgeCoord[1], + faceCoord[0], + edgeCoord[2], + edgeCoord[5], + faceCoord[2], + elementGlobal, + faceCoord[3]); + subContVol[2].volume_ = + hexahedronVolume(subContVol[2].global, + edgeCoord[0], + faceCoord[0], + edgeCoord[3], + edgeCoord[6], + faceCoord[1], + elementGlobal, + faceCoord[4]); + subContVol[3].volume_ = + hexahedronVolume(subContVol[3].global, + edgeCoord[3], + faceCoord[0], + edgeCoord[1], + edgeCoord[7], + faceCoord[4], + elementGlobal, + faceCoord[2]); + subContVol[4].volume_ = elementVolume - + subContVol[0].volume_ - + subContVol[1].volume_ - + subContVol[2].volume_ - + subContVol[3].volume_; + break; + case 6: // 3D, prism + subContVol[0].volume_ = + hexahedronVolume(subContVol[0].global, + edgeCoord[3], + faceCoord[3], + edgeCoord[4], + edgeCoord[0], + faceCoord[0], + elementGlobal, + faceCoord[1]); + subContVol[1].volume_ = + hexahedronVolume(subContVol[1].global, + edgeCoord[5], + faceCoord[3], + edgeCoord[3], + edgeCoord[1], + faceCoord[2], + elementGlobal, + faceCoord[0]); + subContVol[2].volume_ = + hexahedronVolume(subContVol[2].global, + edgeCoord[4], + faceCoord[3], + edgeCoord[5], + edgeCoord[2], + faceCoord[1], + elementGlobal, + faceCoord[2]); + subContVol[3].volume_ = + hexahedronVolume(edgeCoord[0], + faceCoord[0], + elementGlobal, + faceCoord[1], + subContVol[3].global, + edgeCoord[6], + faceCoord[4], + edgeCoord[7]); + subContVol[4].volume_ = + hexahedronVolume(edgeCoord[1], + faceCoord[2], + elementGlobal, + faceCoord[0], + subContVol[4].global, + edgeCoord[8], + faceCoord[4], + edgeCoord[6]); + subContVol[5].volume_ = + hexahedronVolume(edgeCoord[2], + faceCoord[1], + elementGlobal, + faceCoord[2], + subContVol[5].global, + edgeCoord[7], + faceCoord[4], + edgeCoord[8]); + break; + case 8: // 3D, hexahedron + subContVol[0].volume_ = + hexahedronVolume(subContVol[0].global, + edgeCoord[6], + faceCoord[4], + edgeCoord[4], + edgeCoord[0], + faceCoord[2], + elementGlobal, + faceCoord[0]); + subContVol[1].volume_ = + hexahedronVolume(subContVol[1].global, + edgeCoord[5], + faceCoord[4], + edgeCoord[6], + edgeCoord[1], + faceCoord[1], + elementGlobal, + faceCoord[2]); + subContVol[2].volume_ = + hexahedronVolume(subContVol[2].global, + edgeCoord[4], + faceCoord[4], + edgeCoord[7], + edgeCoord[2], + faceCoord[0], + elementGlobal, + faceCoord[3]); + subContVol[3].volume_ = + hexahedronVolume(subContVol[3].global, + edgeCoord[7], + faceCoord[4], + edgeCoord[5], + edgeCoord[3], + faceCoord[3], + elementGlobal, + faceCoord[1]); + subContVol[4].volume_ = + hexahedronVolume(edgeCoord[0], + faceCoord[2], + elementGlobal, + faceCoord[0], + subContVol[4].global, + edgeCoord[10], + faceCoord[5], + edgeCoord[8]); + subContVol[5].volume_ = + hexahedronVolume(edgeCoord[1], + faceCoord[1], + elementGlobal, + faceCoord[2], + subContVol[5].global, + edgeCoord[9], + faceCoord[5], + edgeCoord[10]); + subContVol[6].volume_ = + hexahedronVolume(edgeCoord[2], + faceCoord[0], + elementGlobal, + faceCoord[3], + subContVol[6].global, + edgeCoord[8], + faceCoord[5], + edgeCoord[11]); + subContVol[7].volume_ = + hexahedronVolume(edgeCoord[3], + faceCoord[3], + elementGlobal, + faceCoord[1], + subContVol[7].global, + edgeCoord[11], + faceCoord[5], + edgeCoord[9]); + break; + default: + throw std::logic_error("Not implemented:VcfvStencil for dim = "+std::to_string(dim) + +", numVertices = "+std::to_string(numVertices)); + } + } + else + throw std::logic_error("Not implemented:VcfvStencil for dim = "+std::to_string(dim)); + } +#if __GNUC__ || __clang__ +#pragma GCC diagnostic pop +#endif + + const GridView& gridView_; + const Mapper& vertexMapper_; + + Element element_; + +#if HAVE_DUNE_LOCALFUNCTIONS + static LocalFiniteElementCache feCache_; +#endif // HAVE_DUNE_LOCALFUNCTIONS + + //! local coordinate of element center + LocalPosition elementLocal; + //! global coordinate of element center + GlobalPosition elementGlobal; + //! element volume + Scalar elementVolume; + //! data of the sub control volumes + SubControlVolume subContVol[maxNC]; + //! data of the sub control volume faces + SubControlVolumeFace subContVolFace[maxNE]; + //! data of the boundary faces + BoundaryFace boundaryFace_[maxBF]; + unsigned numBoundarySegments_; + //! global coordinates of the edge centers + GlobalPosition edgeCoord[maxNE]; + //! global coordinates of the face centers + GlobalPosition faceCoord[maxNF]; + //! number of verts + unsigned numVertices; + //! number of edges + unsigned numEdges; + //! number of faces (0 in < 3D) + unsigned numFaces; + Dune::GeometryType geometryType_; +}; + +#if HAVE_DUNE_LOCALFUNCTIONS +template +typename VcfvStencil::LocalFiniteElementCache +VcfvStencil::feCache_; +#endif // HAVE_DUNE_LOCALFUNCTIONS + +} // namespace Opm + +#endif + diff --git a/opm/models/flash/flashboundaryratevector.hh b/opm/models/flash/flashboundaryratevector.hh new file mode 100644 index 00000000000..ef730d6d815 --- /dev/null +++ b/opm/models/flash/flashboundaryratevector.hh @@ -0,0 +1,203 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashBoundaryRateVector + */ +#ifndef EWOMS_FLASH_BOUNDARY_RATE_VECTOR_HH +#define EWOMS_FLASH_BOUNDARY_RATE_VECTOR_HH + +#include +#include + +namespace Opm { + +/*! + * \ingroup FlashModel + * + * \brief Implements a boundary vector for the fully implicit + * compositional multi-phase model which is based on flash + * calculations. + */ +template +class FlashBoundaryRateVector : public GetPropType +{ + using ParentType = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Indices = GetPropType; + + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { enableEnergy = getPropValue() }; + + using EnergyModule = Opm::EnergyModule; + using Toolbox = Opm::MathToolbox; + +public: + FlashBoundaryRateVector() : ParentType() + {} + + /*! + * \copydoc + * ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(Scalar) + */ + FlashBoundaryRateVector(const Evaluation& value) : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(const + * ImmiscibleBoundaryRateVector& ) + */ + FlashBoundaryRateVector(const FlashBoundaryRateVector& value) = default; + FlashBoundaryRateVector& operator=(const FlashBoundaryRateVector& value) = default; + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setFreeFlow + */ + template + void setFreeFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + ExtensiveQuantities extQuants; + extQuants.updateBoundary(context, bfIdx, timeIdx, fluidState); + const auto& insideIntQuants = context.intensiveQuantities(bfIdx, timeIdx); + unsigned focusDofIdx = context.focusDofIndex(); + unsigned interiorDofIdx = context.interiorScvIndex(bfIdx, timeIdx); + + //////// + // advective fluxes of all components in all phases + //////// + (*this) = Evaluation(0.0); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Evaluation density; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + density = fluidState.density(phaseIdx); + else + density = Opm::getValue(fluidState.density(phaseIdx)); + } + else if (focusDofIdx == interiorDofIdx) + density = insideIntQuants.fluidState().density(phaseIdx); + else + density = Opm::getValue(insideIntQuants.fluidState().density(phaseIdx)); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + Evaluation molarity; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + molarity = fluidState.molarity(phaseIdx, compIdx); + else + molarity = Opm::getValue(fluidState.molarity(phaseIdx, compIdx)); + } + else if (focusDofIdx == interiorDofIdx) + molarity = insideIntQuants.fluidState().molarity(phaseIdx, compIdx); + else + molarity = Opm::getValue(insideIntQuants.fluidState().molarity(phaseIdx, compIdx)); + + // add advective flux of current component in current + // phase + (*this)[conti0EqIdx + compIdx] += extQuants.volumeFlux(phaseIdx)*molarity; + } + + if (enableEnergy) { + Evaluation specificEnthalpy; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + specificEnthalpy = fluidState.enthalpy(phaseIdx); + else + specificEnthalpy = Opm::getValue(fluidState.enthalpy(phaseIdx)); + } + else if (focusDofIdx == interiorDofIdx) + specificEnthalpy = insideIntQuants.fluidState().enthalpy(phaseIdx); + else + specificEnthalpy = Opm::getValue(insideIntQuants.fluidState().enthalpy(phaseIdx)); + + Evaluation enthalpyRate = density*extQuants.volumeFlux(phaseIdx)*specificEnthalpy; + EnergyModule::addToEnthalpyRate(*this, enthalpyRate); + } + } + + // thermal conduction + EnergyModule::addToEnthalpyRate(*this, EnergyModule::thermalConductionRate(extQuants)); + +#ifndef NDEBUG + for (unsigned i = 0; i < numEq; ++i) { + Opm::Valgrind::CheckDefined((*this)[i]); + } +#endif + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setInFlow + */ + template + void setInFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the direction opposite to the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Evaluation& val = this->operator[](eqIdx); + val = Toolbox::min(0.0, val); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setOutFlow + */ + template + void setOutFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the same direction as the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Evaluation& val = this->operator[](eqIdx); + val = Toolbox::max(0.0, val); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setNoFlow + */ + void setNoFlow() + { (*this) = Evaluation(0.0); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/flash/flashextensivequantities.hh b/opm/models/flash/flashextensivequantities.hh new file mode 100644 index 00000000000..c5dbec2af60 --- /dev/null +++ b/opm/models/flash/flashextensivequantities.hh @@ -0,0 +1,94 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashExtensiveQuantities + */ +#ifndef EWOMS_FLASH_EXTENSIVE_QUANTITIES_HH +#define EWOMS_FLASH_EXTENSIVE_QUANTITIES_HH + +#include +#include +#include + +namespace Opm { + +/*! + * \ingroup FlashModel + * \ingroup ExtensiveQuantities + * + * \brief This template class contains the data which is required to + * calculate all fluxes of components over a face of a finite + * volume for the compositional multi-phase model based on + * flash calculations. + * + * This means pressure and concentration gradients, phase densities at + * the integration point, etc. + */ +template +class FlashExtensiveQuantities + : public MultiPhaseBaseExtensiveQuantities + , public EnergyExtensiveQuantities()> + , public DiffusionExtensiveQuantities()> +{ + using ParentType = MultiPhaseBaseExtensiveQuantities; + + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + + enum { enableDiffusion = getPropValue() }; + using DiffusionExtensiveQuantities = Opm::DiffusionExtensiveQuantities; + + enum { enableEnergy = getPropValue() }; + using EnergyExtensiveQuantities = Opm::EnergyExtensiveQuantities; + +public: + /*! + * \copydoc MultiPhaseBaseExtensiveQuantities::update + */ + void update(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, scvfIdx, timeIdx); + DiffusionExtensiveQuantities::update_(elemCtx, scvfIdx, timeIdx); + EnergyExtensiveQuantities::update_(elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc MultiPhaseBaseExtensiveQuantities::updateBoundary + */ + template + void updateBoundary(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + ParentType::updateBoundary(context, bfIdx, timeIdx, fluidState); + DiffusionExtensiveQuantities::updateBoundary_(context, bfIdx, timeIdx, fluidState); + EnergyExtensiveQuantities::updateBoundary_(context, bfIdx, timeIdx, fluidState); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/flash/flashindices.hh b/opm/models/flash/flashindices.hh new file mode 100644 index 00000000000..ead04c2241d --- /dev/null +++ b/opm/models/flash/flashindices.hh @@ -0,0 +1,71 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashIndices + */ +#ifndef EWOMS_FLASH_INDICES_HH +#define EWOMS_FLASH_INDICES_HH + +#include + +namespace Opm { + +/*! + * \ingroup FlashModel + * + * \brief Defines the primary variable and equation indices for the + * compositional multi-phase model based on flash calculations. + * + * \tparam PVOffset The first index in a primary variable vector. + */ +template +class FlashIndices + : public EnergyIndices(), + getPropValue()> +{ + enum { numComponents = getPropValue() }; + enum { enableEnergy = getPropValue() }; + using EnergyIndices = Opm::EnergyIndices; + +public: + //! number of equations/primary variables + static const int numEq = numComponents + EnergyIndices::numEq_; + + // Primary variable indices + + //! Index of the total concentration of the first component in the + //! pore space. + static const int cTot0Idx = PVOffset; + + // equation indices + + //! Index of the mass conservation equation for the first + //! component. + static const int conti0EqIdx = PVOffset; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/flash/flashintensivequantities.hh b/opm/models/flash/flashintensivequantities.hh new file mode 100644 index 00000000000..6bbaa12bfb7 --- /dev/null +++ b/opm/models/flash/flashintensivequantities.hh @@ -0,0 +1,218 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashIntensiveQuantities + */ +#ifndef EWOMS_FLASH_INTENSIVE_QUANTITIES_HH +#define EWOMS_FLASH_INTENSIVE_QUANTITIES_HH + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace Opm { + +/*! + * \ingroup FlashModel + * \ingroup IntensiveQuantities + * + * \brief Contains the intensive quantities of the flash-based compositional multi-phase model + */ +template +class FlashIntensiveQuantities + : public GetPropType + , public DiffusionIntensiveQuantities() > + , public EnergyIntensiveQuantities() > + , public GetPropType::FluxIntensiveQuantities +{ + using ParentType = GetPropType; + + using ElementContext = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using Indices = GetPropType; + using FluxModule = GetPropType; + using GridView = GetPropType; + using ThreadManager = GetPropType; + + // primary variable indices + enum { cTot0Idx = Indices::cTot0Idx }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + enum { enableEnergy = getPropValue() }; + enum { dimWorld = GridView::dimensionworld }; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using FlashSolver = GetPropType; + + using ComponentVector = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + + using FluxIntensiveQuantities = typename FluxModule::FluxIntensiveQuantities; + using DiffusionIntensiveQuantities = Opm::DiffusionIntensiveQuantities; + using EnergyIntensiveQuantities = Opm::EnergyIntensiveQuantities; + +public: + //! The type of the object returned by the fluidState() method + using FluidState = Opm::CompositionalFluidState; + + FlashIntensiveQuantities() + { } + + FlashIntensiveQuantities(const FlashIntensiveQuantities& other) = default; + + FlashIntensiveQuantities& operator=(const FlashIntensiveQuantities& other) = default; + + /*! + * \copydoc IntensiveQuantities::update + */ + void update(const ElementContext& elemCtx, unsigned dofIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, dofIdx, timeIdx); + EnergyIntensiveQuantities::updateTemperatures_(fluidState_, elemCtx, dofIdx, timeIdx); + + const auto& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + const auto& problem = elemCtx.problem(); + Scalar flashTolerance = Parameters::Get>(); + + // extract the total molar densities of the components + ComponentVector cTotal; + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + cTotal[compIdx] = priVars.makeEvaluation(cTot0Idx + compIdx, timeIdx); + + const auto *hint = elemCtx.thermodynamicHint(dofIdx, timeIdx); + if (hint) { + // use the same fluid state as the one of the hint, but + // make sure that we don't overwrite the temperature + // specified by the primary variables + Evaluation T = fluidState_.temperature(/*phaseIdx=*/0); + fluidState_.assign(hint->fluidState()); + fluidState_.setTemperature(T); + } + else + FlashSolver::guessInitial(fluidState_, cTotal); + + // compute the phase compositions, densities and pressures + typename FluidSystem::template ParameterCache paramCache; + const MaterialLawParams& materialParams = + problem.materialLawParams(elemCtx, dofIdx, timeIdx); + FlashSolver::template solve(fluidState_, + materialParams, + paramCache, + cTotal, + flashTolerance); + + // calculate relative permeabilities + MaterialLaw::relativePermeabilities(relativePermeability_, + materialParams, fluidState_); + Opm::Valgrind::CheckDefined(relativePermeability_); + + // set the phase viscosities + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + paramCache.updatePhase(fluidState_, phaseIdx); + + const Evaluation& mu = FluidSystem::viscosity(fluidState_, paramCache, phaseIdx); + fluidState_.setViscosity(phaseIdx, mu); + + mobility_[phaseIdx] = relativePermeability_[phaseIdx] / mu; + Opm::Valgrind::CheckDefined(mobility_[phaseIdx]); + } + + ///////////// + // calculate the remaining quantities + ///////////// + + // porosity + porosity_ = problem.porosity(elemCtx, dofIdx, timeIdx); + Opm::Valgrind::CheckDefined(porosity_); + + // intrinsic permeability + intrinsicPerm_ = problem.intrinsicPermeability(elemCtx, dofIdx, timeIdx); + + // update the quantities specific for the velocity model + FluxIntensiveQuantities::update_(elemCtx, dofIdx, timeIdx); + + // energy related quantities + EnergyIntensiveQuantities::update_(fluidState_, paramCache, elemCtx, dofIdx, timeIdx); + + // update the diffusion specific quantities of the intensive quantities + DiffusionIntensiveQuantities::update_(fluidState_, paramCache, elemCtx, dofIdx, timeIdx); + } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::fluidState + */ + const FluidState& fluidState() const + { return fluidState_; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::intrinsicPermeability + */ + const DimMatrix& intrinsicPermeability() const + { return intrinsicPerm_; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::relativePermeability + */ + const Evaluation& relativePermeability(unsigned phaseIdx) const + { return relativePermeability_[phaseIdx]; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::mobility + */ + const Evaluation& mobility(unsigned phaseIdx) const + { + return mobility_[phaseIdx]; + } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::porosity + */ + const Evaluation& porosity() const + { return porosity_; } + +private: + DimMatrix intrinsicPerm_; + FluidState fluidState_; + Evaluation porosity_; + std::array relativePermeability_; + std::array mobility_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/flash/flashlocalresidual.hh b/opm/models/flash/flashlocalresidual.hh new file mode 100644 index 00000000000..bfec905a614 --- /dev/null +++ b/opm/models/flash/flashlocalresidual.hh @@ -0,0 +1,197 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashLocalResidual + */ +#ifndef EWOMS_FLASH_LOCAL_RESIDUAL_HH +#define EWOMS_FLASH_LOCAL_RESIDUAL_HH + + +#include +#include + +#include + +namespace Opm { +/*! + * \ingroup FlashModel + * + * \brief Calculates the local residual of the compositional multi-phase + * model based on flash calculations. + */ +template +class FlashLocalResidual: public GetPropType +{ + using Evaluation = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + using IntensiveQuantities = GetPropType; + using ElementContext = GetPropType; + + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + + enum { enableDiffusion = getPropValue() }; + using DiffusionModule = Opm::DiffusionModule; + + enum { enableEnergy = getPropValue() }; + using EnergyModule = Opm::EnergyModule; + + using Toolbox = Opm::MathToolbox; + +public: + /*! + * \copydoc ImmiscibleLocalResidual::addPhaseStorage + */ + template + void addPhaseStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx, + unsigned phaseIdx) const + { + const IntensiveQuantities& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + const auto& fs = intQuants.fluidState(); + + // compute storage term of all components within all phases + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + unsigned eqIdx = conti0EqIdx + compIdx; + storage[eqIdx] += + Toolbox::template decay(fs.molarity(phaseIdx, compIdx)) + * Toolbox::template decay(fs.saturation(phaseIdx)) + * Toolbox::template decay(intQuants.porosity()); + } + + EnergyModule::addPhaseStorage(storage, elemCtx.intensiveQuantities(dofIdx, timeIdx), phaseIdx); + } + + /*! + * \copydoc FvBaseLocalResidual::computeStorage + */ + template + void computeStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + storage = 0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + addPhaseStorage(storage, elemCtx, dofIdx, timeIdx, phaseIdx); + + EnergyModule::addSolidEnergyStorage(storage, elemCtx.intensiveQuantities(dofIdx, timeIdx)); + } + + /*! + * \copydoc FvBaseLocalResidual::computeFlux + */ + void computeFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + flux = 0.0; + addAdvectiveFlux(flux, elemCtx, scvfIdx, timeIdx); + Opm::Valgrind::CheckDefined(flux); + + addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + Opm::Valgrind::CheckDefined(flux); + } + + /*! + * \copydoc ImmiscibleLocalResidual::addAdvectiveFlux + */ + void addAdvectiveFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + unsigned focusDofIdx = elemCtx.focusDofIndex(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // data attached to upstream and the finite volume of the current phase + unsigned upIdx = static_cast(extQuants.upstreamIndex(phaseIdx)); + const IntensiveQuantities& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + + // this is a bit hacky because it is specific to the element-centered + // finite volume scheme. (N.B. that if finite differences are used to + // linearize the system of equations, it does not matter.) + if (upIdx == focusDofIdx) { + Evaluation tmp = + up.fluidState().molarDensity(phaseIdx) + * extQuants.volumeFlux(phaseIdx); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + flux[conti0EqIdx + compIdx] += + tmp*up.fluidState().moleFraction(phaseIdx, compIdx); + } + } + else { + Evaluation tmp = + Toolbox::value(up.fluidState().molarDensity(phaseIdx)) + * extQuants.volumeFlux(phaseIdx); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + flux[conti0EqIdx + compIdx] += + tmp*Toolbox::value(up.fluidState().moleFraction(phaseIdx, compIdx)); + } + } + } + + EnergyModule::addAdvectiveFlux(flux, elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc ImmiscibleLocalResidual::addDiffusiveFlux + */ + void addDiffusiveFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + DiffusionModule::addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + EnergyModule::addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc ImmiscibleLocalResidual::computeSource + */ + void computeSource(RateVector& source, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + Opm::Valgrind::SetUndefined(source); + elemCtx.problem().source(source, elemCtx, dofIdx, timeIdx); + Opm::Valgrind::CheckDefined(source); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/flash/flashmodel.hh b/opm/models/flash/flashmodel.hh new file mode 100644 index 00000000000..070bae6136c --- /dev/null +++ b/opm/models/flash/flashmodel.hh @@ -0,0 +1,329 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashModel + */ +#ifndef EWOMS_FLASH_MODEL_HH +#define EWOMS_FLASH_MODEL_HH + +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace Opm { +template +class FlashModel; +} + +namespace Opm::Properties { + +namespace TTag { +//! The type tag for the isothermal single phase problems +struct FlashModel { using InheritsFrom = std::tuple; }; +} // namespace TTag + +//! Use the FlashLocalResidual function for the flash model +template +struct LocalResidual { using type = Opm::FlashLocalResidual; }; + +//! Use the NCP flash solver by default +template +struct FlashSolver +{ using type = Opm::NcpFlash, + GetPropType>; }; + +//! the Model property +template +struct Model { using type = Opm::FlashModel; }; + +//! the PrimaryVariables property +template +struct PrimaryVariables { using type = Opm::FlashPrimaryVariables; }; + +//! the RateVector property +template +struct RateVector { using type = Opm::FlashRateVector; }; + +//! the BoundaryRateVector property +template +struct BoundaryRateVector { using type = Opm::FlashBoundaryRateVector; }; + +//! the IntensiveQuantities property +template +struct IntensiveQuantities { using type = Opm::FlashIntensiveQuantities; }; + +//! the ExtensiveQuantities property +template +struct ExtensiveQuantities { using type = Opm::FlashExtensiveQuantities; }; + +//! The indices required by the flash-baseed isothermal compositional model +template +struct Indices { using type = Opm::FlashIndices; }; + +// disable molecular diffusion by default +template +struct EnableDiffusion { static constexpr bool value = false; }; + +//! Disable the energy equation by default +template +struct EnableEnergy { static constexpr bool value = false; }; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup FlashModel + * + * \brief A compositional multi-phase model based on flash-calculations + * + * This model assumes a flow of \f$M \geq 1\f$ fluid phases + * \f$\alpha\f$, each of which is assumed to be a mixture \f$N \geq + * M\f$ chemical species (denoted by the upper index \f$\kappa\f$). + * + * By default, the standard multi-phase Darcy approach is used to determine + * the velocity, i.e. + * \f[ + * \mathbf{v}_\alpha = + * - \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} + * \left(\mathbf{grad}\, p_\alpha + * - \varrho_{\alpha} \mathbf{g} \right) \;, + * \f] + * although the actual approach which is used can be specified via the + * \c FluxModule property. For example, the velocity model can by + * changed to the Forchheimer approach by + * \code + * template +struct FluxModule { using type = Opm::ForchheimerFluxModule; }; + * \endcode + * + * The core of the model is the conservation mass of each component by + * means of the equation + * \f[ + * \sum_\alpha \frac{\partial\;\phi c_\alpha^\kappa S_\alpha }{\partial t} + * - \sum_\alpha \mathrm{div} \left\{ c_\alpha^\kappa \mathbf{v}_\alpha \right\} + * - q^\kappa = 0 \;. + * \f] + * + * To determine the quanties that occur in the equations above, this + * model uses flash calculations. A flash solver starts with + * the total mass or molar mass per volume for each component and, + * calculates the compositions, saturations and pressures of all + * phases at a given temperature. For this the flash solver has to use + * some model assumptions internally. (Often these are the same + * primary variable switching or NCP assumptions as used by the other + * fully implicit compositional multi-phase models provided by eWoms.) + * + * Using flash calculations for the flow model has some disadvantages: + * - The accuracy of the flash solver needs to be sufficient to + * calculate the parital derivatives using numerical differentiation + * which are required for the Newton scheme. + * - Flash calculations tend to be quite computationally expensive and + * are often numerically unstable. + * + * It is thus adviced to increase the target tolerance of the Newton + * scheme or a to use type for scalar values which exhibits higher + * precision than the standard \c double (e.g. \c quad) if this model + * ought to be used. + * + * The model uses the following primary variables: + * - The total molar concentration of each component: + * \f$c^\kappa = \sum_\alpha S_\alpha x_\alpha^\kappa \rho_{mol, \alpha}\f$ + * - The absolute temperature $T$ in Kelvins if the energy equation enabled. + */ +template +class FlashModel + : public MultiPhaseBaseModel +{ + using ParentType = MultiPhaseBaseModel; + + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using Simulator = GetPropType; + + using Indices = GetPropType; + + enum { numComponents = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + + using EnergyModule = Opm::EnergyModule; + +public: + FlashModel(Simulator& simulator) + : ParentType(simulator) + {} + + /*! + * \brief Register all run-time parameters for the immiscible model. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + // register runtime parameters of the VTK output modules + Opm::VtkCompositionModule::registerParameters(); + + if (enableDiffusion) + Opm::VtkDiffusionModule::registerParameters(); + + if (enableEnergy) + Opm::VtkEnergyModule::registerParameters(); + + Parameters::Register> + ("The maximum tolerance for the flash solver to " + "consider the solution converged"); + + // The updates of intensive quantities tend to be _very_ expensive for this + // model, so let's try to minimize the number of required ones + Parameters::SetDefault(true); + + // since thermodynamic hints are basically free if the cache for intensive quantities is + // enabled, and this model usually shows quite a performance improvment if they are + // enabled, let's enable them by default. + Parameters::SetDefault(true); + } + + /*! + * \copydoc FvBaseDiscretization::name + */ + static std::string name() + { return "flash"; } + + /*! + * \copydoc FvBaseDiscretization::primaryVarName + */ + std::string primaryVarName(unsigned pvIdx) const + { + const std::string& tmp = EnergyModule::primaryVarName(pvIdx); + if (tmp != "") + return tmp; + + std::ostringstream oss; + if (Indices::cTot0Idx <= pvIdx && pvIdx < Indices::cTot0Idx + + numComponents) + oss << "c_tot," << FluidSystem::componentName(/*compIdx=*/pvIdx + - Indices::cTot0Idx); + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::eqName + */ + std::string eqName(unsigned eqIdx) const + { + const std::string& tmp = EnergyModule::eqName(eqIdx); + if (tmp != "") + return tmp; + + std::ostringstream oss; + if (Indices::conti0EqIdx <= eqIdx && eqIdx < Indices::conti0EqIdx + + numComponents) { + unsigned compIdx = eqIdx - Indices::conti0EqIdx; + oss << "continuity^" << FluidSystem::componentName(compIdx); + } + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::primaryVarWeight + */ + Scalar primaryVarWeight(unsigned globalDofIdx, unsigned pvIdx) const + { + Scalar tmp = EnergyModule::primaryVarWeight(*this, globalDofIdx, pvIdx); + if (tmp > 0) + return tmp; + + unsigned compIdx = pvIdx - Indices::cTot0Idx; + + // make all kg equal. also, divide the weight of all total + // compositions by 100 to make the relative errors more + // comparable to the ones of the other models (at 10% porosity + // the medium is fully saturated with water at atmospheric + // conditions if 100 kg/m^3 are present!) + return FluidSystem::molarMass(compIdx) / 100.0; + } + + /*! + * \copydoc FvBaseDiscretization::eqWeight + */ + Scalar eqWeight(unsigned globalDofIdx, unsigned eqIdx) const + { + Scalar tmp = EnergyModule::eqWeight(*this, globalDofIdx, eqIdx); + if (tmp > 0) + return tmp; + + unsigned compIdx = eqIdx - Indices::conti0EqIdx; + + // make all kg equal + return FluidSystem::molarMass(compIdx); + } + + void registerOutputModules_() + { + ParentType::registerOutputModules_(); + + // add the VTK output modules which are meaningful for the model + this->addOutputModule(new Opm::VtkCompositionModule(this->simulator_)); + if (enableDiffusion) + this->addOutputModule(new Opm::VtkDiffusionModule(this->simulator_)); + if (enableEnergy) + this->addOutputModule(new Opm::VtkEnergyModule(this->simulator_)); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/flash/flashparameters.hh b/opm/models/flash/flashparameters.hh new file mode 100644 index 00000000000..a365f35941f --- /dev/null +++ b/opm/models/flash/flashparameters.hh @@ -0,0 +1,42 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup FlashModel + * + * \brief Declares the parameters for the compositional + * multi-phase model based on flash calculations. + */ +#ifndef EWOMS_FLASH_PARAMETERS_HH +#define EWOMS_FLASH_PARAMETERS_HH + +namespace Opm::Parameters { + +//! The maximum accepted error of the flash solver +//! Let the flash solver choose its tolerance by default +template +struct FlashTolerance { static constexpr Scalar value = -1.0; }; + +} // namespace Opm::Parameters + +#endif diff --git a/opm/models/flash/flashprimaryvariables.hh b/opm/models/flash/flashprimaryvariables.hh new file mode 100644 index 00000000000..25141552b59 --- /dev/null +++ b/opm/models/flash/flashprimaryvariables.hh @@ -0,0 +1,148 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashPrimaryVariables + */ +#ifndef EWOMS_FLASH_PRIMARY_VARIABLES_HH +#define EWOMS_FLASH_PRIMARY_VARIABLES_HH + +#include "flashindices.hh" + +#include +#include + +#include +#include +#include + +#include + +#include + +namespace Opm { + +/*! + * \ingroup FlashModel + * + * \brief Represents the primary variables used by the compositional + * flow model based on flash calculations. + * + * This class is basically a Dune::FieldVector which can retrieve its + * contents from an aribitatry fluid state. + */ +template +class FlashPrimaryVariables : public FvBasePrimaryVariables +{ + using ParentType = FvBasePrimaryVariables; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using MaterialLawParams = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + // primary variable indices + enum { cTot0Idx = Indices::cTot0Idx }; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + using Toolbox = typename Opm::MathToolbox; + using EnergyModule = Opm::EnergyModule()>; + +public: + FlashPrimaryVariables() : ParentType() + { Opm::Valgrind::SetDefined(*this); } + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(Scalar) + */ + FlashPrimaryVariables(Scalar value) : ParentType(value) + { + Opm::Valgrind::CheckDefined(value); + Opm::Valgrind::SetDefined(*this); + } + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(const + * ImmisciblePrimaryVariables& ) + */ + FlashPrimaryVariables(const FlashPrimaryVariables& value) = default; + FlashPrimaryVariables& operator=(const FlashPrimaryVariables& value) = default; + + /*! + * \copydoc ImmisciblePrimaryVariables::assignMassConservative + */ + template + void assignMassConservative(const FluidState& fluidState, + const MaterialLawParams&, + bool = false) + { + // there is no difference between naive and mass conservative + // assignment in the flash model. (we only need the total + // concentrations.) + assignNaive(fluidState); + } + + /*! + * \copydoc ImmisciblePrimaryVariables::assignNaive + */ + template + void assignNaive(const FluidState& fluidState) + { + // reset everything + (*this) = 0.0; + + // assign the phase temperatures. this is out-sourced to + // the energy module + EnergyModule::setPriVarTemperatures(*this, fluidState); + + // determine the phase presence. + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + this->operator[](cTot0Idx + compIdx) += + fluidState.molarity(phaseIdx, compIdx) * fluidState.saturation(phaseIdx); + } + } + } + + /*! + * \brief Prints the names of the primary variables and their values. + * + * \param os The \c std::ostream which should be used for the output. + */ + void print(std::ostream& os = std::cout) const + { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + os << "(c_tot," << FluidSystem::componentName(compIdx) << " = " + << this->operator[](cTot0Idx + compIdx); + } + os << ")" << std::flush; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/flash/flashproperties.hh b/opm/models/flash/flashproperties.hh new file mode 100644 index 00000000000..f0430a2a1fe --- /dev/null +++ b/opm/models/flash/flashproperties.hh @@ -0,0 +1,43 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup FlashModel + * + * \brief Declares the properties required by the compositional + * multi-phase model based on flash calculations. + */ +#ifndef EWOMS_FLASH_PROPERTIES_HH +#define EWOMS_FLASH_PROPERTIES_HH + +#include + +namespace Opm::Properties { + +//! The type of the flash constraint solver +template +struct FlashSolver { using type = UndefinedProperty; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/flash/flashratevector.hh b/opm/models/flash/flashratevector.hh new file mode 100644 index 00000000000..5f027a4116e --- /dev/null +++ b/opm/models/flash/flashratevector.hh @@ -0,0 +1,142 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashRateVector + */ +#ifndef EWOMS_FLASH_RATE_VECTOR_HH +#define EWOMS_FLASH_RATE_VECTOR_HH + +#include + +#include +#include + + +namespace Opm { + +/*! + * \ingroup FlashModel + * + * \copydoc Opm::ImmiscibleRateVector + */ +template +class FlashRateVector + : public Dune::FieldVector, + getPropValue()> +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { numComponents = getPropValue() }; + enum { numEq = getPropValue() }; + + using ParentType = Dune::FieldVector; + using EnergyModule = Opm::EnergyModule()>; + +public: + FlashRateVector() : ParentType() + { Opm::Valgrind::SetUndefined(*this); } + + /*! + * \copydoc ImmiscibleRateVector::ImmiscibleRateVector(Scalar) + */ + FlashRateVector(const Evaluation& value) : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleRateVector::ImmiscibleRateVector(const + * ImmiscibleRateVector& ) + */ + FlashRateVector(const FlashRateVector& value) : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleRateVector::setMassRate + */ + void setMassRate(const ParentType& value) + { + // convert to molar rates + ParentType molarRate(value); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + molarRate[conti0EqIdx + compIdx] /= FluidSystem::molarMass(compIdx); + + setMolarRate(molarRate); + } + + /*! + * \copydoc ImmiscibleRateVector::setMolarRate + */ + void setMolarRate(const ParentType& value) + { ParentType::operator=(value); } + + /*! + * \copydoc ImmiscibleRateVector::setEnthalpyRate + */ + void setEnthalpyRate(const Evaluation& rate) + { EnergyModule::setEnthalpyRate(*this, rate); } + + /*! + * \copydoc ImmiscibleRateVector::setVolumetricRate + */ + template + void setVolumetricRate(const FluidState& fluidState, unsigned phaseIdx, const RhsEval& volume) + { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + (*this)[conti0EqIdx + compIdx] = + fluidState.density(phaseIdx, compIdx) + * fluidState.moleFraction(phaseIdx, compIdx) + * volume; + + EnergyModule::setEnthalpyRate(*this, fluidState, phaseIdx, volume); + } + + /*! + * \brief Assignment operator from a scalar or a function evaluation + */ + template + FlashRateVector& operator=(const RhsEval& value) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = value; + return *this; + } + + /*! + * \brief Assignment operator from another rate vector + */ + FlashRateVector& operator=(const FlashRateVector& other) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = other[i]; + return *this; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/immiscible/immiscibleboundaryratevector.hh b/opm/models/immiscible/immiscibleboundaryratevector.hh new file mode 100644 index 00000000000..ddbf761b9a6 --- /dev/null +++ b/opm/models/immiscible/immiscibleboundaryratevector.hh @@ -0,0 +1,218 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ImmiscibleBoundaryRateVector + */ +#ifndef EWOMS_IMMISCIBLE_BOUNDARY_RATE_VECTOR_HH +#define EWOMS_IMMISCIBLE_BOUNDARY_RATE_VECTOR_HH + +#include +#include + +#include "immiscibleintensivequantities.hh" + +namespace Opm { + +/*! + * \ingroup ImmiscibleModel + * + * \brief Implements a boundary vector for the fully implicit + * multi-phase model which assumes immiscibility. + */ +template +class ImmiscibleBoundaryRateVector : public GetPropType +{ + using ParentType = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Indices = GetPropType; + + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { enableEnergy = getPropValue() }; + + using Toolbox = Opm::MathToolbox; + using EnergyModule = Opm::EnergyModule; + +public: + ImmiscibleBoundaryRateVector() + : ParentType() + {} + + /*! + * \brief Constructor that assigns all entries to a scalar value. + * + * \param value The scalar value to which all components of the + * boundary rate vector will be set. + */ + ImmiscibleBoundaryRateVector(const Evaluation& value) + : ParentType(value) + {} + + /*! + * \brief Copy constructor + * + * \param value The boundary rate vector to be duplicated. + */ + ImmiscibleBoundaryRateVector(const ImmiscibleBoundaryRateVector& value) = default; + + ImmiscibleBoundaryRateVector& operator=(const ImmiscibleBoundaryRateVector& value) = default; + + /*! + * \brief Specify a free-flow boundary + * + * \param context The execution context for which the boundary rate should + * be specified. + * \param bfIdx The local space index of the boundary segment. + * \param timeIdx The index used by the time discretization. + * \param fluidState The repesentation of the thermodynamic state + * of the system on the integration point of the + * boundary segment. + */ + template + void setFreeFlow(const Context& context, unsigned bfIdx, unsigned timeIdx, const FluidState& fluidState) + { + ExtensiveQuantities extQuants; + extQuants.updateBoundary(context, bfIdx, timeIdx, fluidState); + const auto& insideIntQuants = context.intensiveQuantities(bfIdx, timeIdx); + unsigned focusDofIdx = context.focusDofIndex(); + unsigned interiorDofIdx = context.interiorScvIndex(bfIdx, timeIdx); + + //////// + // advective fluxes of all components in all phases + //////// + (*this) = Evaluation(0.0); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + const auto& pBoundary = fluidState.pressure(phaseIdx); + const Evaluation& pInside = insideIntQuants.fluidState().pressure(phaseIdx); + + // mass conservation + Evaluation density; + if (pBoundary > pInside) { + if (focusDofIdx == interiorDofIdx) + density = fluidState.density(phaseIdx); + else + density = Opm::getValue(fluidState.density(phaseIdx)); + } + else if (focusDofIdx == interiorDofIdx) + density = insideIntQuants.fluidState().density(phaseIdx); + else + density = Opm::getValue(insideIntQuants.fluidState().density(phaseIdx)); + + Opm::Valgrind::CheckDefined(density); + Opm::Valgrind::CheckDefined(extQuants.volumeFlux(phaseIdx)); + + (*this)[conti0EqIdx + phaseIdx] += extQuants.volumeFlux(phaseIdx)*density; + + // energy conservation + if (enableEnergy) { + Evaluation specificEnthalpy; + if (pBoundary > pInside) { + if (focusDofIdx == interiorDofIdx) + specificEnthalpy = fluidState.enthalpy(phaseIdx); + else + specificEnthalpy = Opm::getValue(fluidState.enthalpy(phaseIdx)); + } + else if (focusDofIdx == interiorDofIdx) + specificEnthalpy = insideIntQuants.fluidState().enthalpy(phaseIdx); + else + specificEnthalpy = Opm::getValue(insideIntQuants.fluidState().enthalpy(phaseIdx)); + + Evaluation enthalpyRate = density*extQuants.volumeFlux(phaseIdx)*specificEnthalpy; + EnergyModule::addToEnthalpyRate(*this, enthalpyRate); + } + } + + // thermal conduction + EnergyModule::addToEnthalpyRate(*this, EnergyModule::thermalConductionRate(extQuants)); + +#ifndef NDEBUG + for (unsigned i = 0; i < numEq; ++i) + Opm::Valgrind::CheckDefined((*this)[i]); + Opm::Valgrind::CheckDefined(*this); +#endif + } + + /*! + * \brief Specify an inflow boundary + * + * An inflow boundary condition is basically a free flow boundary + * condition that is not prevented from specifying a flow out of + * the domain. + * + * \copydetails setFreeFlow + */ + template + void setInFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the direction opposite to the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Evaluation& val = this->operator[](eqIdx); + val = Toolbox::min(0.0, val); + } + } + + /*! + * \brief Specify an outflow boundary + * + * An outflow boundary condition is basically a free flow boundary + * condition that is not prevented from specifying a flow into + * the domain. + * + * \copydetails setFreeFlow + */ + template + void setOutFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the same direction as the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Evaluation& val = this->operator[](eqIdx); + val = Toolbox::max(0.0, val); + } + } + + /*! + * \brief Specify a no-flow boundary for all conserved quantities. + */ + void setNoFlow() + { (*this) = Evaluation(0.0); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/immiscible/immiscibleextensivequantities.hh b/opm/models/immiscible/immiscibleextensivequantities.hh new file mode 100644 index 00000000000..0dfff82d526 --- /dev/null +++ b/opm/models/immiscible/immiscibleextensivequantities.hh @@ -0,0 +1,97 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ImmiscibleExtensiveQuantities + */ +#ifndef EWOMS_IMMISCIBLE_EXTENSIVE_QUANTITIES_HH +#define EWOMS_IMMISCIBLE_EXTENSIVE_QUANTITIES_HH + +#include "immiscibleproperties.hh" + +#include +#include + +namespace Opm { +/*! + * \ingroup ImmiscibleModel + * \ingroup ExtensiveQuantities + * + * \brief This class provides the data all quantities that are required to + * calculate the fluxes of the fluid phases over a face of a + * finite volume for the immiscible multi-phase model. + * + * This means pressure and concentration gradients, phase densities at + * the intergration point, etc. + */ +template +class ImmiscibleExtensiveQuantities + : public MultiPhaseBaseExtensiveQuantities + , public EnergyExtensiveQuantities()> +{ + using ParentType = MultiPhaseBaseExtensiveQuantities; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Evaluation = GetPropType; + + enum { enableEnergy = getPropValue() }; + + using ParameterCache = typename FluidSystem::template ParameterCache; + using EnergyExtensiveQuantities = Opm::EnergyExtensiveQuantities; + +public: + /*! + * \copydoc MultiPhaseBaseExtensiveQuantities::registerParameters() + */ + static void registerParameters() + { + ParentType::registerParameters(); + } + + /*! + * \copydoc MultiPhaseBaseExtensiveQuantities::update() + */ + void update(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, scvfIdx, timeIdx); + EnergyExtensiveQuantities::update_(elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc MultiPhaseBaseExtensiveQuantities::updateBoundary() + */ + template + void updateBoundary(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + ParentType::updateBoundary(context, bfIdx, timeIdx, fluidState); + EnergyExtensiveQuantities::updateBoundary_(context, bfIdx, timeIdx, fluidState); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/immiscible/immiscibleindices.hh b/opm/models/immiscible/immiscibleindices.hh new file mode 100644 index 00000000000..15f65994145 --- /dev/null +++ b/opm/models/immiscible/immiscibleindices.hh @@ -0,0 +1,69 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ImmiscibleIndices + */ +#ifndef EWOMS_IMMISCIBLE_INDICES_HH +#define EWOMS_IMMISCIBLE_INDICES_HH + +#include "immiscibleproperties.hh" +#include + +namespace Opm { + +/*! + * \ingroup ImmiscibleModel + * + * \brief The indices for the isothermal multi-phase model. + */ +template +struct ImmiscibleIndices + : public EnergyIndices(), + getPropValue()> +{ + enum { numPhases = getPropValue() }; + enum { enableEnergy = getPropValue() }; + using EnergyIndices = Opm::EnergyIndices; + +public: + // number of equations/primary variables + static const int numEq = numPhases + EnergyIndices::numEq_; + + // Primary variable indices + + //! Index for wetting/non-wetting phase pressure + //! (depending on formulation) in a solution vector + static const int pressure0Idx = PVOffset + 0; + //! Index of the saturation of the non-wetting/wetting phase + static const int saturation0Idx = PVOffset + 1; + + // indices of the equations + + //! Index of the continuity equation of the first phase + static const int conti0EqIdx = PVOffset + 0; +}; +} // namespace Opm + +#endif diff --git a/opm/models/immiscible/immiscibleintensivequantities.hh b/opm/models/immiscible/immiscibleintensivequantities.hh new file mode 100644 index 00000000000..1450c8bdd13 --- /dev/null +++ b/opm/models/immiscible/immiscibleintensivequantities.hh @@ -0,0 +1,199 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ImmiscibleIntensiveQuantities + */ +#ifndef EWOMS_IMMISCIBLE_INTENSIVE_QUANTITIES_HH +#define EWOMS_IMMISCIBLE_INTENSIVE_QUANTITIES_HH + +#include "immiscibleproperties.hh" + +#include + +#include +#include + +#include +#include + +namespace Opm { +/*! + * \ingroup ImmiscibleModel + * \ingroup IntensiveQuantities + * + * \brief Contains the quantities which are are constant within a + * finite volume for the immiscible multi-phase model. + */ +template +class ImmiscibleIntensiveQuantities + : public GetPropType + , public EnergyIntensiveQuantities()> + , public GetPropType::FluxIntensiveQuantities +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using ElementContext = GetPropType; + using Indices = GetPropType; + using GridView = GetPropType; + using FluxModule = GetPropType; + + enum { numPhases = getPropValue() }; + enum { pressure0Idx = Indices::pressure0Idx }; + enum { saturation0Idx = Indices::saturation0Idx }; + enum { enableEnergy = getPropValue() }; + enum { dimWorld = GridView::dimensionworld }; + + using Toolbox = Opm::MathToolbox; + using DimMatrix = Dune::FieldMatrix; + using PhaseVector = Dune::FieldVector; + using EvalPhaseVector = Dune::FieldVector; + + using FluxIntensiveQuantities = typename FluxModule::FluxIntensiveQuantities; + using EnergyIntensiveQuantities = Opm::EnergyIntensiveQuantities; + using FluidState = Opm::ImmiscibleFluidState; + +public: + ImmiscibleIntensiveQuantities() + { } + + ImmiscibleIntensiveQuantities(const ImmiscibleIntensiveQuantities& other) = default; + + ImmiscibleIntensiveQuantities& operator=(const ImmiscibleIntensiveQuantities& other) = default; + + /*! + * \copydoc IntensiveQuantities::update + */ + void update(const ElementContext& elemCtx, unsigned dofIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, dofIdx, timeIdx); + EnergyIntensiveQuantities::updateTemperatures_(fluidState_, elemCtx, dofIdx, timeIdx); + + // material law parameters + const auto& problem = elemCtx.problem(); + const typename MaterialLaw::Params& materialParams = + problem.materialLawParams(elemCtx, dofIdx, timeIdx); + const auto& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + Opm::Valgrind::CheckDefined(priVars); + + Evaluation sumSat = 0.0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases - 1; ++phaseIdx) { + const Evaluation& Salpha = priVars.makeEvaluation(saturation0Idx + phaseIdx, timeIdx); + fluidState_.setSaturation(phaseIdx, Salpha); + sumSat += Salpha; + } + fluidState_.setSaturation(numPhases - 1, 1 - sumSat); + + EvalPhaseVector pC; + MaterialLaw::capillaryPressures(pC, materialParams, fluidState_); + Opm::Valgrind::CheckDefined(pC); + + // calculate relative permeabilities + MaterialLaw::relativePermeabilities(relativePermeability_, materialParams, fluidState_); + Opm::Valgrind::CheckDefined(relativePermeability_); + + const Evaluation& p0 = priVars.makeEvaluation(pressure0Idx, timeIdx); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + fluidState_.setPressure(phaseIdx, p0 + (pC[phaseIdx] - pC[0])); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fluidState_); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // compute and set the viscosity + const Evaluation& mu = FluidSystem::viscosity(fluidState_, paramCache, phaseIdx); + fluidState_.setViscosity(phaseIdx, mu); + + // compute and set the density + const Evaluation& rho = FluidSystem::density(fluidState_, paramCache, phaseIdx); + fluidState_.setDensity(phaseIdx, rho); + + mobility_[phaseIdx] = relativePermeability_[phaseIdx]/mu; + } + + // porosity + porosity_ = problem.porosity(elemCtx, dofIdx, timeIdx); + + // intrinsic permeability + intrinsicPerm_ = problem.intrinsicPermeability(elemCtx, dofIdx, timeIdx); + + // energy related quantities + EnergyIntensiveQuantities::update_(fluidState_, paramCache, elemCtx, dofIdx, timeIdx); + + // update the quantities specific for the velocity model + FluxIntensiveQuantities::update_(elemCtx, dofIdx, timeIdx); + } + + /*! + * \brief Returns the phase state for the control-volume. + */ + const FluidState& fluidState() const + { return fluidState_; } + + /*! + * \brief Returns the intrinsic permeability tensor a degree of freedom. + */ + const DimMatrix& intrinsicPermeability() const + { return intrinsicPerm_; } + + /*! + * \brief Returns the relative permeability of a given phase + * within the control volume. + * + * \copydetails Doxygen::phaseIdxParam + */ + const Evaluation& relativePermeability(unsigned phaseIdx) const + { return relativePermeability_[phaseIdx]; } + + /*! + * \brief Returns the effective mobility of a given phase within + * the control volume. + * + * \copydetails Doxygen::phaseIdxParam + */ + const Evaluation& mobility(unsigned phaseIdx) const + { return mobility_[phaseIdx]; } + + /*! + * \brief Returns the average porosity within the control volume. + */ + const Evaluation& porosity() const + { return porosity_; } + +protected: + FluidState fluidState_; + Evaluation porosity_; + DimMatrix intrinsicPerm_; + Evaluation relativePermeability_[numPhases]; + Evaluation mobility_[numPhases]; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/immiscible/immisciblelocalresidual.hh b/opm/models/immiscible/immisciblelocalresidual.hh new file mode 100644 index 00000000000..d3aa4826484 --- /dev/null +++ b/opm/models/immiscible/immisciblelocalresidual.hh @@ -0,0 +1,199 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ImmiscibleLocalResidual + */ +#ifndef EWOMS_IMMISCIBLE_LOCAL_RESIDUAL_BASE_HH +#define EWOMS_IMMISCIBLE_LOCAL_RESIDUAL_BASE_HH + +#include "immiscibleproperties.hh" + +#include + +#include + +namespace Opm { +/*! + * \ingroup ImmiscibleModel + * + * \brief Calculates the local residual of the immiscible multi-phase + * model. + */ +template +class ImmiscibleLocalResidual : public GetPropType +{ + using Implementation = GetPropType; + + using Evaluation = GetPropType; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + using EnergyModule = Opm::EnergyModule; + using Toolbox = Opm::MathToolbox; + +public: + /*! + * \brief Adds the amount all conservation quantities (e.g. phase + * mass) within a single fluid phase + * + * \copydetails Doxygen::storageParam + * \copydetails Doxygen::dofCtxParams + * \copydetails Doxygen::phaseIdxParam + */ + template + void addPhaseStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx, + unsigned phaseIdx) const + { + // retrieve the intensive quantities for the SCV at the specified + // point in time + const IntensiveQuantities& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + const auto& fs = intQuants.fluidState(); + + storage[conti0EqIdx + phaseIdx] = + Toolbox::template decay(intQuants.porosity()) + * Toolbox::template decay(fs.saturation(phaseIdx)) + * Toolbox::template decay(fs.density(phaseIdx)); + + EnergyModule::addPhaseStorage(storage, intQuants, phaseIdx); + } + + /*! + * \copydoc FvBaseLocalResidual::computeStorage + */ + template + void computeStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + storage = 0.0; + for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + asImp_().addPhaseStorage(storage, elemCtx, dofIdx, timeIdx, phaseIdx); + + EnergyModule::addSolidEnergyStorage(storage, elemCtx.intensiveQuantities(dofIdx, timeIdx)); + } + + /*! + * \copydoc FvBaseLocalResidual::computeFlux + */ + void computeFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + flux = 0.0; + asImp_().addAdvectiveFlux(flux, elemCtx, scvfIdx, timeIdx); + asImp_().addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + } + + /*! + * \brief Add the advective mass flux at a given flux integration point + * + * \copydetails computeFlux + */ + void addAdvectiveFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + const ExtensiveQuantities& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + //////// + // advective fluxes of all components in all phases + //////// + unsigned focusDofIdx = elemCtx.focusDofIndex(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // data attached to upstream DOF of the current phase. + unsigned upIdx = static_cast(extQuants.upstreamIndex(phaseIdx)); + + const IntensiveQuantities& up = elemCtx.intensiveQuantities(upIdx, /*timeIdx=*/0); + + // add advective flux of current component in current phase. + const Evaluation& rho = up.fluidState().density(phaseIdx); + if (focusDofIdx == upIdx) + flux[conti0EqIdx + phaseIdx] += extQuants.volumeFlux(phaseIdx)*rho; + else + flux[conti0EqIdx + phaseIdx] += extQuants.volumeFlux(phaseIdx)*Toolbox::value(rho); + } + + EnergyModule::addAdvectiveFlux(flux, elemCtx, scvfIdx, timeIdx); + } + + /*! + * \brief Adds the diffusive flux at a given flux integration point. + * + * For the immiscible model, this is a no-op for mass fluxes. For energy it adds the + * contribution of thermal conduction to the enthalpy flux. + * + * \copydetails computeFlux + */ + void addDiffusiveFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + // no diffusive mass fluxes for the immiscible model + + // thermal conduction + EnergyModule::addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc FvBaseLocalResidual::computeSource + * + * By default, this method only asks the problem to specify a + * source term. + */ + void computeSource(RateVector& source, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + Opm::Valgrind::SetUndefined(source); + elemCtx.problem().source(source, elemCtx, dofIdx, timeIdx); + Opm::Valgrind::CheckDefined(source); + } + +private: + const Implementation& asImp_() const + { return *static_cast(this); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/immiscible/immisciblemodel.hh b/opm/models/immiscible/immisciblemodel.hh new file mode 100644 index 00000000000..c6a1401f225 --- /dev/null +++ b/opm/models/immiscible/immisciblemodel.hh @@ -0,0 +1,370 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ImmiscibleModel + */ +#ifndef EWOMS_IMMISCIBLE_MODEL_HH +#define EWOMS_IMMISCIBLE_MODEL_HH + +#include +#include "immiscibleproperties.hh" +#include "immiscibleindices.hh" +#include "immiscibleextensivequantities.hh" +#include "immiscibleprimaryvariables.hh" +#include "immiscibleintensivequantities.hh" +#include "immiscibleratevector.hh" +#include "immiscibleboundaryratevector.hh" +#include "immisciblelocalresidual.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Opm { +template +class ImmiscibleModel; +} + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +//! The generic type tag for problems using the immiscible multi-phase model +struct ImmiscibleModel { using InheritsFrom = std::tuple; }; + +//! The type tag for single-phase immiscible problems +struct ImmiscibleSinglePhaseModel { using InheritsFrom = std::tuple; }; + +//! The type tag for two-phase immiscible problems +struct ImmiscibleTwoPhaseModel { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! Use the immiscible multi-phase local jacobian operator for the immiscible multi-phase model +template +struct LocalResidual { using type = Opm::ImmiscibleLocalResidual; }; + +//! the Model property +template +struct Model { using type = Opm::ImmiscibleModel; }; + +//! the RateVector property +template +struct RateVector { using type = Opm::ImmiscibleRateVector; }; + +//! the BoundaryRateVector property +template +struct BoundaryRateVector { using type = Opm::ImmiscibleBoundaryRateVector; }; + +//! the PrimaryVariables property +template +struct PrimaryVariables { using type = Opm::ImmisciblePrimaryVariables; }; + +//! the IntensiveQuantities property +template +struct IntensiveQuantities { using type = Opm::ImmiscibleIntensiveQuantities; }; + +//! the ExtensiveQuantities property +template +struct ExtensiveQuantities { using type = Opm::ImmiscibleExtensiveQuantities; }; + +//! The indices required by the isothermal immiscible multi-phase model +template +struct Indices { using type = Opm::ImmiscibleIndices; }; + +//! Disable the energy equation by default +template +struct EnableEnergy { static constexpr bool value = false; }; + +///////////////////// +// set slightly different properties for the single-phase case +///////////////////// + +//! The fluid system to use by default +template +struct FluidSystem +{ private: + using Scalar = GetPropType; + using Fluid = GetPropType; +public: + using type = Opm::SinglePhaseFluidSystem; +}; + +template +struct Fluid +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +///////////////////// +// set slightly different properties for the two-phase case +///////////////////// +template +struct WettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +template +struct NonwettingPhase +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +template +struct FluidSystem +{ +private: + using Scalar = GetPropType; + using WettingPhase = GetPropType; + using NonwettingPhase = GetPropType; + +public: + using type = Opm::TwoPhaseImmiscibleFluidSystem; +}; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup ImmiscibleModel + * \brief A fully-implicit multi-phase flow model which assumes + * immiscibility of the phases. + * + * This model implements multi-phase flow of \f$M > 0\f$ immiscible + * fluids \f$\alpha\f$. By default, the standard multi-phase Darcy + * approach is used to determine the velocity, i.e. + * \f[ + * \mathbf{v}_\alpha = + * - \frac{k_{r\alpha}}{\mu_\alpha} + * \mathbf{K}\left(\mathbf{grad}\, p_\alpha - + * \varrho_{\alpha} \mathbf{g} \right) \;, + * \f] + * although the actual approach which is used can be specified via the + * \c FluxModule property. For example, the velocity model can by + * changed to the Forchheimer approach by + * \code + * template +struct FluxModule { using type = Opm::ForchheimerFluxModule; }; + * \endcode + * + * The core of the model is the conservation mass of each component by + * means of the equation + * \f[ + * \frac{\partial\;\phi S_\alpha \rho_\alpha }{\partial t} + * - \mathrm{div} \left\{ \rho_\alpha \mathbf{v}_\alpha \right\} + * - q_\alpha = 0 \;. + * \f] + * + * The model uses the following primary variables: + * - The pressure \f$p_0\f$ in Pascal of the phase with the lowest index + * - The saturations \f$S_\alpha\f$ of the \f$M - 1\f$ phases that + * exhibit the lowest indices + * - The absolute temperature \f$T\f$ in Kelvin if energy is conserved + * via the energy equation + */ +template +class ImmiscibleModel + : public Opm::MultiPhaseBaseModel +{ + using ParentType = Opm::MultiPhaseBaseModel; + using Implementation = GetPropType; + using Simulator = GetPropType; + + using Scalar = GetPropType; + using Indices = GetPropType; + using FluidSystem = GetPropType; + + enum { numComponents = FluidSystem::numComponents }; + + + + enum { numPhases = getPropValue() }; + enum { enableEnergy = getPropValue() }; + using EnergyModule = Opm::EnergyModule; + +public: + ImmiscibleModel(Simulator& simulator) + : ParentType(simulator) + {} + + /*! + * \brief Register all run-time parameters for the immiscible model. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + if (enableEnergy) + Opm::VtkEnergyModule::registerParameters(); + + Parameters::SetDefault(false); + Parameters::SetDefault(false); + Parameters::SetDefault(false); + } + + /*! + * \copydoc FvBaseDiscretization::name + */ + static std::string name() + { return "immiscible"; } + + /*! + * \copydoc FvBaseDiscretization::primaryVarName + */ + std::string primaryVarName(unsigned pvIdx) const + { + std::string s; + if (!(s = EnergyModule::primaryVarName(pvIdx)).empty()) + return s; + + std::ostringstream oss; + + if (pvIdx == Indices::pressure0Idx) { + oss << "pressure_" << FluidSystem::phaseName(/*phaseIdx=*/0); + } + else if (Indices::saturation0Idx <= pvIdx + && pvIdx < Indices::saturation0Idx + numPhases - 1) { + unsigned phaseIdx = pvIdx - Indices::saturation0Idx; + oss << "saturation_" << FluidSystem::phaseName(phaseIdx); + } + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::eqName + */ + std::string eqName(unsigned eqIdx) const + { + std::string s; + if (!(s = EnergyModule::eqName(eqIdx)).empty()) + return s; + + std::ostringstream oss; + + if (Indices::conti0EqIdx <= eqIdx && eqIdx < Indices::conti0EqIdx + numComponents) + oss << "conti_" << FluidSystem::phaseName(eqIdx - Indices::conti0EqIdx); + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::updateBegin + */ + void updateBegin() + { + ParentType::updateBegin(); + + // find the a reference pressure. The first degree of freedom + // might correspond to non-interior entities which would lead + // to an undefined value, so we have to iterate... + size_t nDof = this->numTotalDof(); + for (unsigned dofIdx = 0; dofIdx < nDof; ++ dofIdx) { + if (this->isLocalDof(dofIdx)) { + referencePressure_ = + this->solution(/*timeIdx=*/0)[dofIdx][/*pvIdx=*/Indices::pressure0Idx]; + break; + } + } + } + + /*! + * \copydetails FvBaseDiscretization::primaryVarWeight + */ + Scalar primaryVarWeight(unsigned globalDofIdx, unsigned pvIdx) const + { + assert(referencePressure_ > 0); + + Scalar tmp = EnergyModule::primaryVarWeight(asImp_(), globalDofIdx, pvIdx); + if (tmp > 0) + // energy related quantity + return tmp; + if (Indices::pressure0Idx == pvIdx) { + return 10 / referencePressure_; + } + return 1.0; + } + + /*! + * \copydetails FvBaseDiscretization::eqWeight + */ + Scalar eqWeight(unsigned globalDofIdx, unsigned eqIdx) const + { + Scalar tmp = EnergyModule::eqWeight(asImp_(), globalDofIdx, eqIdx); + if (tmp > 0) + // energy related equation + return tmp; + +#ifndef NDEBUG + unsigned compIdx = eqIdx - Indices::conti0EqIdx; + assert(compIdx <= numPhases); +#endif + + // make all kg equal + return 1.0; + } + + void registerOutputModules_() + { + ParentType::registerOutputModules_(); + + if (enableEnergy) + this->addOutputModule(new Opm::VtkEnergyModule(this->simulator_)); + } + +private: + const Implementation& asImp_() const + { return *static_cast(this); } + + mutable Scalar referencePressure_; +}; +} // namespace Opm + +#endif diff --git a/opm/models/immiscible/immiscibleprimaryvariables.hh b/opm/models/immiscible/immiscibleprimaryvariables.hh new file mode 100644 index 00000000000..82cd6b0dbe4 --- /dev/null +++ b/opm/models/immiscible/immiscibleprimaryvariables.hh @@ -0,0 +1,214 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ImmisciblePrimaryVariables + */ +#ifndef EWOMS_IMMISCIBLE_PRIMARY_VARIABLES_HH +#define EWOMS_IMMISCIBLE_PRIMARY_VARIABLES_HH + +#include "immiscibleproperties.hh" + +#include +#include + +#include +#include +#include + +#include + +namespace Opm { + +/*! + * \ingroup ImmiscibleModel + * + * \brief Represents the primary variables used by the immiscible + * multi-phase, model. + * + * This class is basically a Dune::FieldVector which can retrieve its + * contents from an aribitatry fluid state. + */ +template +class ImmisciblePrimaryVariables : public FvBasePrimaryVariables +{ + using ParentType = FvBasePrimaryVariables; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Implementation = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + + using Indices = GetPropType; + + // primary variable indices + enum { pressure0Idx = Indices::pressure0Idx }; + enum { saturation0Idx = Indices::saturation0Idx }; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + using Toolbox = typename Opm::MathToolbox; + using ComponentVector = Dune::FieldVector; + using ImmiscibleFlash = Opm::ImmiscibleFlash; + using EnergyModule = Opm::EnergyModule()>; + +public: + /*! + * \brief Default constructor + */ + ImmisciblePrimaryVariables() : ParentType() + { Opm::Valgrind::SetUndefined(*this); } + + /*! + * \brief Constructor with assignment from scalar + * + * \param value The scalar value to which all entries of the vector will be set. + */ + ImmisciblePrimaryVariables(Scalar value) : ParentType(value) + {} + + /*! + * \brief Copy constructor + * + * \param value The primary variables that will be duplicated. + */ + ImmisciblePrimaryVariables(const ImmisciblePrimaryVariables& value) = default; + + /*! + * \brief Assignment operator + * + * \param value The primary variables that will be duplicated. + */ + ImmisciblePrimaryVariables& operator=(const ImmisciblePrimaryVariables& value) = default; + + /*! + * \brief Set the primary variables from an arbitrary fluid state + * in a mass conservative way. + * + * If an energy equation is included, the fluid temperatures are + * the same as the one given in the fluid state, *not* the + * enthalpy. + * + * \param fluidState The fluid state which should be represented + * by the primary variables. The temperatures, + * pressures, compositions and densities of all + * phases must be defined. + * \param matParams The capillary pressure law parameters + * \param isInEquilibrium If true, the fluid state expresses + * thermodynamic equilibrium assuming the + * relations expressed by the fluid + * system. This implies that in addition to + * the quantities mentioned above, the + * fugacities are also defined. + */ + template + void assignMassConservative(const FluidState& fluidState, + const MaterialLawParams& matParams, + bool isInEquilibrium = false) + { + #ifndef NDEBUG + // make sure the temperature is the same in all fluid phases + for (unsigned phaseIdx = 1; phaseIdx < numPhases; ++phaseIdx) { + assert(std::abs(fluidState.temperature(0) - fluidState.temperature(phaseIdx)) < 1e-30); + } +#endif // NDEBUG + + // for the equilibrium case, we don't need complicated + // computations. + if (isInEquilibrium) { + assignNaive(fluidState); + return; + } + + // use a flash calculation to calculate a fluid state in + // thermodynamic equilibrium + typename FluidSystem::template ParameterCache paramCache; + Opm::ImmiscibleFluidState fsFlash; + + // use the externally given fluid state as initial value for + // the flash calculation + fsFlash.assign(fluidState); + + // calculate the phase densities + paramCache.updateAll(fsFlash); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar rho = FluidSystem::density(fsFlash, paramCache, phaseIdx); + fsFlash.setDensity(phaseIdx, rho); + } + + // calculate the "global molarities" + ComponentVector globalMolarities(0.0); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + globalMolarities[compIdx] += + fsFlash.saturation(phaseIdx) * fsFlash.molarity(phaseIdx, compIdx); + } + } + + // run the flash calculation + ImmiscibleFlash::template solve(fsFlash, matParams, paramCache, globalMolarities); + + // use the result to assign the primary variables + assignNaive(fsFlash); + } + + /*! + * \brief Directly retrieve the primary variables from an + * arbitrary fluid state. + * + * This method retrieves all primary variables from an abitrary + * fluid state without careing whether the state which is + * represented by the resulting primary variables features the + * equivalent mass as the given fluid state. This method is + * massively cheaper and simpler than assignMassConservative() but + * it should be used with care! + * + * \param fluidState The fluid state which should be represented + * by the primary variables. The temperatures, + * pressures, compositions and densities of all + * phases must be defined. + */ + template + void assignNaive(const FluidState& fluidState) + { + // assign the phase temperatures. this is out-sourced to + // the energy module + EnergyModule::setPriVarTemperatures(asImp_(), fluidState); + + (*this)[pressure0Idx] = fluidState.pressure(/*phaseIdx=*/0); + for (unsigned phaseIdx = 0; phaseIdx < numPhases - 1; ++phaseIdx) + (*this)[saturation0Idx + phaseIdx] = fluidState.saturation(phaseIdx); + } + +private: + Implementation& asImp_() + { return *static_cast(this); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/immiscible/immiscibleproperties.hh b/opm/models/immiscible/immiscibleproperties.hh new file mode 100644 index 00000000000..4dce4c4cd41 --- /dev/null +++ b/opm/models/immiscible/immiscibleproperties.hh @@ -0,0 +1,53 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup ImmiscibleModel + * + * \brief Defines the properties required for the immiscible + * multi-phase model. + */ +#ifndef EWOMS_IMMISCIBLE_PROPERTIES_HH +#define EWOMS_IMMISCIBLE_PROPERTIES_HH + +#include +#include + +namespace Opm::Properties { + +// these properties only make sense for the ImmiscibleTwoPhase type tag +//! The wetting phase for two-phase models +template +struct WettingPhase { using type = UndefinedProperty; }; +//! The non-wetting phase for two-phase models +template +struct NonwettingPhase { using type = UndefinedProperty; }; + +// these properties only make sense for the ImmiscibleSinglePhase type tag +//! The fluid used by the model +template +struct Fluid { using type = UndefinedProperty; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/immiscible/immiscibleratevector.hh b/opm/models/immiscible/immiscibleratevector.hh new file mode 100644 index 00000000000..dc3aafe8a87 --- /dev/null +++ b/opm/models/immiscible/immiscibleratevector.hh @@ -0,0 +1,181 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::ImmiscibleRateVector + */ +#ifndef EWOMS_IMMISCIBLE_RATE_VECTOR_HH +#define EWOMS_IMMISCIBLE_RATE_VECTOR_HH + +#include + +#include +#include + +#include "immiscibleintensivequantities.hh" + +namespace Opm { +/*! + * \ingroup ImmiscibleModel + * + * \brief Implements a vector representing rates of conserved quantities. + * + * This class is basically a Dune::FieldVector which can be set using + * either mass, molar or volumetric rates. + */ +template +class ImmiscibleRateVector + : public Dune::FieldVector, + getPropValue()> +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + enum { numEq = getPropValue() }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { numComponents = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + using ParentType = Dune::FieldVector; + using EnergyModule = Opm::EnergyModule; + +public: + /*! + * \brief Default constructor + */ + ImmiscibleRateVector() : ParentType() + { Opm::Valgrind::SetUndefined(*this); } + + /*! + * \brief Constructor with assignment from scalar + * + * \param value The scalar value to which all entries of the vector will be set. + */ + ImmiscibleRateVector(const Evaluation& value) + : ParentType(value) + {} + + /*! + * \brief Copy constructor + * + * \param value The rate vector that will be duplicated. + */ + ImmiscibleRateVector(const ImmiscibleRateVector& value) + : ParentType(value) + {} + + /*! + * \brief Set a mass rate of the conservation quantities. + * + * Enthalpy is _not_ taken into account seperately here. This + * means that it must be set to the desired value in the + * parameter. + * + * \param value The mass rate in \f$[kg/(m^2\,s)]\f$ (unit for areal fluxes) + */ + void setMassRate(const ParentType& value) + { ParentType::operator=(value); } + + /*! + * \brief Set a molar rate of the conservation quantities. + * + * Enthalpy is _not_ taken into account seperately here. This + * means that it must be set to the desired value in the + * parameter. + * + * \param value The new molar rate in \f$[mol/(m^2\,s)]\f$ + */ + void setMolarRate(const ParentType& value) + { + // convert to mass rates + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + ParentType::operator[](conti0EqIdx + compIdx) = + value[conti0EqIdx + compIdx]*FluidSystem::molarMass(compIdx); + } + + /*! + * \brief Set an enthalpy rate [J/As] where \f$A \in \{m^2, m^3\}\f$ + * + * If the energy equation is not enabled, this method is a no-op. + * + * \param rate The enthalpy rate in \f$[J/(m^2\,s)]\f$ + */ + template + void setEnthalpyRate(const RhsEval& rate) + { EnergyModule::setEnthalpyRate(*this, rate); } + + /*! + * \brief Set a volumetric rate of a phase. + * + * The enthalpy transported into the domain is taken into account + * by this method. + * + * \param fluidState The thermodynamic state of the fluids which + * should be considered. The density and the + * composition of the considered phase must be + * specified before calling this method. + * \param phaseIdx The index of the fluid phase for which the + * given amount of volume should be specified. + * \param volume The volumetric rate of the fluid phase in + * \f$[m^3/(m^2\,s)]\f$ (unit for areal fluxes) + */ + template + void setVolumetricRate(const FluidState& fluidState, unsigned phaseIdx, const RhsEval& volume) + { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + (*this)[conti0EqIdx + compIdx] = + fluidState.density(phaseIdx, compIdx) + * fluidState.massFraction(phaseIdx, compIdx) + * volume; + + EnergyModule::setEnthalpyRate(*this, fluidState, phaseIdx, volume); + } + + /*! + * \brief Assignment operator from a scalar or a function evaluation + */ + template + ImmiscibleRateVector& operator=(const RhsEval& value) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = value; + return *this; + } + + /*! + * \brief Assignment operator from another rate vector + */ + ImmiscibleRateVector& operator=(const ImmiscibleRateVector& other) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = other[i]; + return *this; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/baseoutputmodule.hh b/opm/models/io/baseoutputmodule.hh new file mode 100644 index 00000000000..99d0c17892d --- /dev/null +++ b/opm/models/io/baseoutputmodule.hh @@ -0,0 +1,439 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::BaseOutputModule + */ +#ifndef EWOMS_BASE_OUTPUT_MODULE_HH +#define EWOMS_BASE_OUTPUT_MODULE_HH + +#include + +#include + +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace Opm::Properties { + +template +struct FluidSystem; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \brief The base class for writer modules. + * + * This class also provides some convenience methods for buffer + * management and is the base class for all other output writer + * modules. + */ +template +class BaseOutputModule +{ + using Simulator = GetPropType; + using Model = GetPropType; + using Scalar = GetPropType; + using GridView = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using DiscBaseOutputModule = GetPropType; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { numEq = getPropValue() }; + enum { dim = GridView::dimension }; + enum { dimWorld = GridView::dimensionworld }; + using Vector = BaseOutputWriter::Vector; + using Tensor = BaseOutputWriter::Tensor; + +public: + using ScalarBuffer = BaseOutputWriter::ScalarBuffer; + using VectorBuffer = BaseOutputWriter::VectorBuffer; + using TensorBuffer = BaseOutputWriter::TensorBuffer; + + using EqBuffer = std::array; + using PhaseBuffer = std::array; + using ComponentBuffer = std::array; + using PhaseComponentBuffer = std::array, numPhases>; + + using PhaseVectorBuffer = std::array; + + BaseOutputModule(const Simulator& simulator) + : simulator_(simulator) + {} + + virtual ~BaseOutputModule() + {} + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to disk. + * + * The module can dynamically cast the writer to the desired + * concrete class. If the writer is incompatible with the module, + * this method should become a no-op. + */ + virtual void allocBuffers() = 0; + + /*! + * \brief Modify the internal buffers according to the intensive quanties relevant + * for an element + * + * The module can dynamically cast the writer to the desired + * concrete class. If the writer is incompatible with the module, + * this method should become a no-op. + */ + virtual void processElement(const ElementContext& elemCtx) = 0; + + /*! + * \brief Add all buffers to the VTK output writer. + */ + virtual void commitBuffers(BaseOutputWriter& writer) = 0; + + /*! + * \brief Returns true iff the module needs to access the extensive quantities of a + * context to do its job. + * + * For example, this happens if velocities or gradients should be written. + * + * Always returning true here does not do any harm from the correctness perspective, + * but it slows down writing the output fields. Since most output modules only write + * intensive quantities, this method returns 'false' by default. + */ + virtual bool needExtensiveQuantities() const + { return false; } + +protected: + enum BufferType { + //! Buffer contains data associated with the degrees of freedom + DofBuffer, + + //! Buffer contains data associated with the grid's vertices + VertexBuffer, + + //! Buffer contains data associated with the grid's elements + ElementBuffer + }; + + /*! + * \brief Allocate the space for a buffer storing a scalar quantity + */ + void resizeScalarBuffer_(ScalarBuffer& buffer, + BufferType bufferType = DofBuffer) + { + buffer.resize(this->getBufferSize(bufferType)); + std::fill(buffer.begin(), buffer.end(), 0.0); + } + + /*! + * \brief Allocate the space for a buffer storing a tensorial quantity + */ + void resizeTensorBuffer_(TensorBuffer& buffer, + BufferType bufferType = DofBuffer) + { + buffer.resize(this->getBufferSize(bufferType)); + Tensor nullMatrix(dimWorld, dimWorld, 0.0); + std::fill(buffer.begin(), buffer.end(), nullMatrix); + } + + void resizeVectorBuffer_(VectorBuffer& buffer, + BufferType bufferType = DofBuffer) + { + buffer.resize(this->getBufferSize(bufferType)); + Vector zerovector(dimWorld,0.0); + zerovector = 0.0; + std::fill(buffer.begin(), buffer.end(), zerovector); + } + + /*! + * \brief Allocate the space for a buffer storing a equation specific + * quantity + */ + void resizeEqBuffer_(EqBuffer& buffer, + BufferType bufferType = DofBuffer) + { + const std::size_t n = this->getBufferSize(bufferType); + for (unsigned i = 0; i < numEq; ++i) { + buffer[i].resize(n); + std::fill(buffer[i].begin(), buffer[i].end(), 0.0); + } + } + + /*! + * \brief Allocate the space for a buffer storing a phase-specific + * quantity + */ + void resizePhaseBuffer_(PhaseBuffer& buffer, + BufferType bufferType = DofBuffer) + { + const std::size_t n = this->getBufferSize(bufferType); + for (unsigned i = 0; i < numPhases; ++i) { + buffer[i].resize(n); + std::fill(buffer[i].begin(), buffer[i].end(), 0.0); + } + } + + /*! + * \brief Allocate the space for a buffer storing a component + * specific quantity + */ + void resizeComponentBuffer_(ComponentBuffer& buffer, + BufferType bufferType = DofBuffer) + { + const std::size_t n = this->getBufferSize(bufferType); + for (unsigned i = 0; i < numComponents; ++i) { + buffer[i].resize(n); + std::fill(buffer[i].begin(), buffer[i].end(), 0.0); + } + } + + /*! + * \brief Allocate the space for a buffer storing a phase and + * component specific buffer + */ + void resizePhaseComponentBuffer_(PhaseComponentBuffer& buffer, + BufferType bufferType = DofBuffer) + { + const std::size_t n = this->getBufferSize(bufferType); + for (unsigned i = 0; i < numPhases; ++i) { + for (unsigned j = 0; j < numComponents; ++j) { + buffer[i][j].resize(n); + std::fill(buffer[i][j].begin(), buffer[i][j].end(), 0.0); + } + } + } + + /*! + * \brief Add a buffer containing scalar quantities to the result file. + */ + void commitScalarBuffer_(BaseOutputWriter& baseWriter, + const char *name, + ScalarBuffer& buffer, + BufferType bufferType = DofBuffer) + { + switch (bufferType) { + case DofBuffer: + DiscBaseOutputModule::attachScalarDofData_(baseWriter, buffer, name); + break; + case VertexBuffer: + attachScalarVertexData_(baseWriter, buffer, name); + break; + case ElementBuffer: + attachScalarElementData_(baseWriter, buffer, name); + break; + default: throw std::logic_error("bufferType must be one of Dof, Vertex or Element"); + } + } + + /*! + * \brief Add a buffer containing vectorial quantities to the result file. + */ + void commitVectorBuffer_(BaseOutputWriter& baseWriter, + const char *name, + VectorBuffer& buffer, + BufferType bufferType = DofBuffer) + { + switch (bufferType) { + case DofBuffer: + DiscBaseOutputModule::attachVectorDofData_(baseWriter, buffer, name); + break; + case VertexBuffer: + attachVectorVertexData_(baseWriter, buffer, name); + break; + case ElementBuffer: + attachVectorElementData_(baseWriter, buffer, name); + break; + default: throw std::logic_error("bufferType must be one of Dof, Vertex or Element"); + } + } + + /*! + * \brief Add a buffer containing tensorial quantities to the result file. + */ + void commitTensorBuffer_(BaseOutputWriter& baseWriter, + const char *name, + TensorBuffer& buffer, + BufferType bufferType = DofBuffer) + { + switch (bufferType) { + case DofBuffer: + DiscBaseOutputModule::attachTensorDofData_(baseWriter, buffer, name); + break; + case VertexBuffer: + attachTensorVertexData_(baseWriter, buffer, name); + break; + case ElementBuffer: + attachTensorElementData_(baseWriter, buffer, name); + break; + default: throw std::logic_error("bufferType must be one of Dof, Vertex or Element"); + } + } + + /*! + * \brief Add a buffer with as many variables as PDEs to the result file. + */ + void commitPriVarsBuffer_(BaseOutputWriter& baseWriter, + const char *pattern, + EqBuffer& buffer, + BufferType bufferType = DofBuffer) + { + char name[512]; + for (unsigned i = 0; i < numEq; ++i) { + std::string eqName = simulator_.model().primaryVarName(i); + snprintf(name, 512, pattern, eqName.c_str()); + + this->commitScalarBuffer_(baseWriter, name, buffer[i], bufferType); + } + } + + /*! + * \brief Add a buffer with as many variables as PDEs to the result file. + */ + void commitEqBuffer_(BaseOutputWriter& baseWriter, + const char *pattern, + EqBuffer& buffer, + BufferType bufferType = DofBuffer) + { + char name[512]; + for (unsigned i = 0; i < numEq; ++i) { + std::ostringstream oss; + oss << i; + snprintf(name, 512, pattern, oss.str().c_str()); + + this->commitScalarBuffer(baseWriter, name, buffer[i], bufferType); + } + } + + /*! + * \brief Add a phase-specific buffer to the result file. + */ + void commitPhaseBuffer_(BaseOutputWriter& baseWriter, + const char *pattern, + PhaseBuffer& buffer, + BufferType bufferType = DofBuffer) + { + char name[512]; + for (unsigned i = 0; i < numPhases; ++i) { + snprintf(name, 512, pattern, FluidSystem::phaseName(i).data()); + + this->commitScalarBuffer_(baseWriter, name, buffer[i], bufferType); + } + } + + /*! + * \brief Add a component-specific buffer to the result file. + */ + void commitComponentBuffer_(BaseOutputWriter& baseWriter, + const char *pattern, + ComponentBuffer& buffer, + BufferType bufferType = DofBuffer) + { + char name[512]; + for (unsigned i = 0; i < numComponents; ++i) { + snprintf(name, 512, pattern, FluidSystem::componentName(i).data()); + + this->commitScalarBuffer_(baseWriter, name, buffer[i], bufferType); + } + } + + /*! + * \brief Add a phase and component specific quantities to the output. + */ + void commitPhaseComponentBuffer_(BaseOutputWriter& baseWriter, + const char *pattern, + PhaseComponentBuffer& buffer, + BufferType bufferType = DofBuffer) + { + char name[512]; + for (unsigned i= 0; i < numPhases; ++i) { + for (unsigned j = 0; j < numComponents; ++j) { + snprintf(name, 512, pattern, + FluidSystem::phaseName(i).data(), + FluidSystem::componentName(j).data()); + + this->commitScalarBuffer_(baseWriter, name, buffer[i][j], bufferType); + } + } + } + + void attachScalarElementData_(BaseOutputWriter& baseWriter, + ScalarBuffer& buffer, + const char *name) + { baseWriter.attachScalarElementData(buffer, name); } + + void attachScalarVertexData_(BaseOutputWriter& baseWriter, + ScalarBuffer& buffer, + const char *name) + { baseWriter.attachScalarVertexData(buffer, name); } + + void attachVectorElementData_(BaseOutputWriter& baseWriter, + VectorBuffer& buffer, + const char *name) + { baseWriter.attachVectorElementData(buffer, name); } + + void attachVectorVertexData_(BaseOutputWriter& baseWriter, + VectorBuffer& buffer, + const char *name) + { baseWriter.attachVectorVertexData(buffer, name); } + + void attachTensorElementData_(BaseOutputWriter& baseWriter, + TensorBuffer& buffer, + const char *name) + { baseWriter.attachTensorElementData(buffer, name); } + + void attachTensorVertexData_(BaseOutputWriter& baseWriter, + TensorBuffer& buffer, + const char *name) + { baseWriter.attachTensorVertexData(buffer, name); } + + std::size_t getBufferSize(BufferType bufferType) const + { + switch (bufferType) { + case VertexBuffer: return simulator_.gridView().size(dim); + case ElementBuffer: return simulator_.gridView().size(0); + case DofBuffer: return simulator_.model().numGridDof(); + default: throw std::logic_error("bufferType must be one of Dof, Vertex or Element"); + } + } + + const Simulator& simulator_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/baseoutputwriter.hh b/opm/models/io/baseoutputwriter.hh new file mode 100644 index 00000000000..21ae602cafa --- /dev/null +++ b/opm/models/io/baseoutputwriter.hh @@ -0,0 +1,106 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::BaseOutputWriter + */ +#ifndef EWOMS_BASE_OUTPUT_WRITER_HH +#define EWOMS_BASE_OUTPUT_WRITER_HH + +#include +#include + +#include + +namespace Opm { +/*! + * \brief The base class for all output writers. + * + * The sole purpose of this class is to enable RTTI + * (i.e. dynamic_cast) on writer objects. + */ +class BaseOutputWriter +{ +public: + using Scalar = double; + using Vector = Dune::DynamicVector; + using Tensor = Dune::DynamicMatrix; + using ScalarBuffer = std::vector; + using VectorBuffer = std::vector; + using TensorBuffer = std::vector; + + BaseOutputWriter() + {} + + virtual ~BaseOutputWriter() + {} + + /*! + * \brief Called when ever a new time step or a new grid + * must be written. + */ + virtual void beginWrite(double t) = 0; + + /*! + * \brief Add a scalar vertex centered vector field to the output. + */ + virtual void attachScalarVertexData(ScalarBuffer& buf, std::string name) = 0; + + /*! + * \brief Add a scalar element centered quantity to the output. + */ + virtual void attachScalarElementData(ScalarBuffer& buf, std::string name) = 0; + + /*! + * \brief Add a vectorial vertex centered vector field to the output. + */ + virtual void attachVectorVertexData(VectorBuffer& buf, std::string name) = 0; + + /*! + * \brief Add a vectorial element centered quantity to the output. + */ + virtual void attachVectorElementData(VectorBuffer& buf, std::string name) = 0; + + /*! + * \brief Add a tensorial vertex centered tensor field to the output. + */ + virtual void attachTensorVertexData(TensorBuffer& buf, std::string name) = 0; + + /*! + * \brief Add a tensorial element centered quantity to the output. + */ + virtual void attachTensorElementData(TensorBuffer& buf, std::string name) = 0; + + /*! + * \brief Finalizes the current writer. + * + * This means that everything will be written to disk, except if + * the onlyDiscard argument is true. In this case only all managed + * buffers are deleted, but no output is written. + */ + virtual void endWrite(bool onlyDiscard = false) = 0; +}; +} // namespace Opm + +#endif diff --git a/opm/models/io/basevanguard.hh b/opm/models/io/basevanguard.hh new file mode 100644 index 00000000000..57cc7c9596f --- /dev/null +++ b/opm/models/io/basevanguard.hh @@ -0,0 +1,152 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::BaseVanguard + */ +#ifndef EWOMS_BASE_VANGUARD_HH +#define EWOMS_BASE_VANGUARD_HH + +#include +#include + +#include + +#if HAVE_DUNE_FEM +#include +#endif + +#include +#include + +namespace Opm { + +/*! + * \brief Provides the base class for most (all?) simulator vanguards. + */ +template +class BaseVanguard +{ + using Simulator = GetPropType; + using Grid = GetPropType; + using GridView = GetPropType; + using Implementation = GetPropType; + +#if HAVE_DUNE_FEM + using GridPart = GetPropType; +#endif + +public: + BaseVanguard(Simulator& simulator) + : simulator_(simulator) + {} + + BaseVanguard(const BaseVanguard&) = delete; + + /*! + * \brief Returns a reference to the grid view to be used. + */ + const GridView& gridView() const + { return *gridView_; } + +#if HAVE_DUNE_FEM + /*! + * \brief Returns a reference to the grid part to be used. + */ + const GridPart& gridPart() const + { return *gridPart_; } + + /*! + * \brief Returns a reference to the grid part to be used. + */ + GridPart& gridPart() + { return *gridPart_; } +#endif + + /*! + * \brief Returns the number of times the grid has been changed since its creation. + * + * This basically says how often the grid has been adapted in the current simulation + * run. + */ + int gridSequenceNumber () const + { +#if HAVE_DUNE_FEM + using FemDofManager = Dune::Fem::DofManager< Grid >; + return FemDofManager::instance( asImp_().grid() ).sequence(); +#else + return 0; // return the same sequence number >= 0 means the grid never changes +#endif + } + + + /*! + * \brief Distribute the grid (and attached data) over all + * processes. + */ + void loadBalance() + { + asImp_().grid().loadBalance(); + updateGridView_(); + } + +protected: + // this method should be called after the grid has been allocated + void finalizeInit_() + { + updateGridView_(); + } + + void updateGridView_() + { +#if HAVE_DUNE_FEM + if constexpr (std::is_same_v::GridViewType>) { + gridPart_ = std::make_unique(asImp_().grid()); + gridView_ = std::make_unique(static_cast(*gridPart_)); + assert(gridView_->size(0) == asImp_().grid().leafGridView().size(0)); + } else +#endif + { + gridView_ = std::make_unique(asImp_().grid().leafGridView()); + } + } + +private: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } + + Simulator& simulator_; +#if HAVE_DUNE_FEM + std::unique_ptr gridPart_; +#endif + std::unique_ptr gridView_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/cubegridvanguard.hh b/opm/models/io/cubegridvanguard.hh new file mode 100644 index 00000000000..7e4ca8c5313 --- /dev/null +++ b/opm/models/io/cubegridvanguard.hh @@ -0,0 +1,145 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::CubeGridVanguard + */ +#ifndef EWOMS_CUBE_GRID_VANGUARD_HH +#define EWOMS_CUBE_GRID_VANGUARD_HH + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace Opm { + +/*! + * \brief Provides a simulator vanguad which creates a regular grid made of + * quadrilaterals. + * + * A quadrilateral is a line segment in 1D, a rectangle in 2D and a + * cube in 3D. + */ +template +class CubeGridVanguard : public BaseVanguard +{ + using ParentType = BaseVanguard; + using Scalar = GetPropType; + using Simulator = GetPropType; + using Grid = GetPropType; + + using GridPointer = std::unique_ptr; + using CoordScalar = typename Grid::ctype; + enum { + dim = Grid::dimension, + dimWorld = Grid::dimensionworld, + }; + using GlobalPosition = Dune::FieldVector; + +public: + /*! + * \brief Register all run-time parameters for the simulator vanguad. + */ + static void registerParameters() + { + Parameters::Register + ("The number of global refinements of the grid " + "executed after it was loaded"); + Parameters::Register> + ("The size of the domain in x direction"); + Parameters::Register + ("The number of intervalls in x direction"); + + if constexpr (dim > 1) { + Parameters::Register> + ("The size of the domain in y direction"); + Parameters::Register + ("The number of intervalls in y direction"); + } + if constexpr (dim > 2) { + Parameters::Register> + ("The size of the domain in z direction"); + Parameters::Register + ("The number of intervalls in z direction"); + } + } + + /*! + * \brief Create the grid + */ + CubeGridVanguard(Simulator& simulator) + : ParentType(simulator) + { + std::array cellRes; + GlobalPosition upperRight(0.0); + GlobalPosition lowerLeft(0.0); + + for (unsigned i = 0; i < dim; ++i) + cellRes[i] = 0; + + upperRight[0] = Parameters::Get>(); + cellRes[0] = Parameters::Get(); + + if constexpr (dim > 1) { + upperRight[1] = Parameters::Get>(); + cellRes[1] = Parameters::Get(); + } + if constexpr (dim > 2) { + upperRight[2] = Parameters::Get>(); + cellRes[2] = Parameters::Get(); + } + + unsigned numRefinements = Parameters::Get(); + cubeGrid_ = Dune::StructuredGridFactory::createCubeGrid(lowerLeft, upperRight, cellRes); + cubeGrid_->globalRefine(static_cast(numRefinements)); + + this->finalizeInit_(); + } + + /*! + * \brief Returns a reference to the grid. + */ + Grid& grid() + { return *cubeGrid_; } + + /*! + * \brief Returns a reference to the grid. + */ + const Grid& grid() const + { return *cubeGrid_; } + +protected: + GridPointer cubeGrid_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/dgfvanguard.hh b/opm/models/io/dgfvanguard.hh new file mode 100644 index 00000000000..2a84e037390 --- /dev/null +++ b/opm/models/io/dgfvanguard.hh @@ -0,0 +1,192 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::DgfVanguard + */ +#ifndef EWOMS_DGF_GRID_VANGUARD_HH +#define EWOMS_DGF_GRID_VANGUARD_HH + +#include +#include +#include + +#include +#include +#include + + +#include +#include + +namespace Opm { + +/*! + * \brief Provides a simulator vanguard which creates a grid by parsing a Dune Grid + * Format (DGF) file. + */ +template +class DgfVanguard : public BaseVanguard +{ + using ParentType = BaseVanguard; + using Scalar = GetPropType; + using Simulator = GetPropType; + using Grid = GetPropType; + using FractureMapper = Opm::FractureMapper; + + using GridPointer = std::unique_ptr< Grid >; + +public: + /*! + * \brief Register all run-time parameters for the DGF simulator vanguard. + */ + static void registerParameters() + { + Parameters::Register + ("The file name of the DGF file to load"); + Parameters::Register + ("The number of global refinements of the grid " + "executed after it was loaded"); + } + + /*! + * \brief Load the grid from the file. + */ + DgfVanguard(Simulator& simulator) + : ParentType(simulator) + { + const std::string dgfFileName = Parameters::Get(); + unsigned numRefinments = Parameters::Get(); + + { + // create DGF GridPtr from a dgf file + Dune::GridPtr< Grid > dgfPointer( dgfFileName ); + + // this is only implemented for 2d currently + addFractures_( dgfPointer ); + + // store pointer to dune grid + gridPtr_.reset( dgfPointer.release() ); + } + + if (numRefinments > 0) + gridPtr_->globalRefine(static_cast(numRefinments)); + + this->finalizeInit_(); + } + + /*! + * \brief Returns a reference to the grid. + */ + Grid& grid() + { return *gridPtr_; } + + /*! + * \brief Returns a reference to the grid. + */ + const Grid& grid() const + { return *gridPtr_; } + + /*! + * \brief Distributes the grid on all processes of a parallel + * computation. + * + * This grid manager plays nice and also distributes the data of + * the DGF... + */ + void loadBalance() + { gridPtr_->loadBalance(); } + + /*! + * \brief Returns the fracture mapper + * + * The fracture mapper determines the topology of the fractures. + */ + FractureMapper& fractureMapper() + { return fractureMapper_; } + + /*! + * \brief Returns the fracture mapper + * + * The fracture mapper determines the topology of the fractures. + */ + const FractureMapper& fractureMapper() const + { return fractureMapper_; } + +protected: + void addFractures_(Dune::GridPtr& dgfPointer) + { + using LevelGridView = typename Grid::LevelGridView; + + // check if fractures are available (only 2d currently) + if (dgfPointer.nofParameters(static_cast(Grid::dimension)) == 0) + return; + + LevelGridView gridView = dgfPointer->levelGridView(/*level=*/0); + const unsigned edgeCodim = Grid::dimension - 1; + + using VertexMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + VertexMapper vertexMapper(gridView, Dune::mcmgVertexLayout()); + + // first create a map of the dune to ART vertex indices + auto eIt = gridView.template begin(); + const auto eEndIt = gridView.template end(); + for (; eIt != eEndIt; ++eIt) { + const auto& element = *eIt; + const auto& refElem = + Dune::ReferenceElements::general(element.type()); + + const int edges = refElem.size( edgeCodim ); + for (int edge = 0; edge < edges; ++edge) { + const int vertices = refElem.size(edge, edgeCodim, Grid::dimension); + std::vector vertexIndices; + vertexIndices.reserve(Grid::dimension); + for (int vx = 0; vx < vertices; ++vx) { + // get local vertex number from edge + const int localVx = refElem.subEntity(edge, edgeCodim, vx, Grid::dimension); + + // get vertex + const auto vertex = element.template subEntity(localVx); + + // if vertex has parameter 1 insert as a fracture vertex + if (dgfPointer.parameters( vertex )[ 0 ] > 0) + vertexIndices.push_back( + static_cast(vertexMapper.subIndex(element, + static_cast(localVx), + Grid::dimension))); + } + // if 2 vertices have been found with flag 1 insert a fracture edge + if (static_cast(vertexIndices.size()) == Grid::dimension) + fractureMapper_.addFractureEdge(vertexIndices[0], vertexIndices[1]); + } + } + } + +private: + GridPointer gridPtr_; + FractureMapper fractureMapper_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/restart.hh b/opm/models/io/restart.hh new file mode 100644 index 00000000000..18f11dfbf2f --- /dev/null +++ b/opm/models/io/restart.hh @@ -0,0 +1,275 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Restart + */ +#ifndef EWOMS_RESTART_HH +#define EWOMS_RESTART_HH + +#include +#include +#include +#include + +namespace Opm { + +/*! + * \brief Load or save a state of a problem to/from the harddisk. + */ +class Restart +{ + /*! + * \brief Create a magic cookie for restart files, so that it is + * unlikely to load a restart file for an incorrectly. + */ + template + static const std::string magicRestartCookie_(const GridView& gridView) + { + static const std::string gridName = "blubb"; // gridView.grid().name(); + static const int dim = GridView::dimension; + + int numVertices = gridView.size(dim); + int numElements = gridView.size(0); + int numEdges = gridView.size(dim - 1); + int numCPUs = gridView.comm().size(); + int rank = gridView.comm().rank(); + + std::ostringstream oss; + oss << "eWoms restart file: " + << "gridName='" << gridName << "' " + << "numCPUs=" << numCPUs << " " + << "myRank=" << rank << " " + << "numElements=" << numElements << " " + << "numEdges=" << numEdges << " " + << "numVertices=" << numVertices; + return oss.str(); + } + + /*! + * \brief Return the restart file name. + */ + template + static const std::string restartFileName_(const GridView& gridView, + const std::string& outputDir, + const std::string& simName, + Scalar t) + { + std::string dir = outputDir; + if (dir == ".") + dir = ""; + else if (!dir.empty() && dir.back() != '/') + dir += "/"; + + int rank = gridView.comm().rank(); + std::ostringstream oss; + oss << dir << simName << "_time=" << t << "_rank=" << rank << ".ers"; + return oss.str(); + } + +public: + /*! + * \brief Returns the name of the file which is (de-)serialized. + */ + const std::string& fileName() const + { return fileName_; } + + /*! + * \brief Write the current state of the model to disk. + */ + template + void serializeBegin(Simulator& simulator) + { + const std::string magicCookie = magicRestartCookie_(simulator.gridView()); + fileName_ = restartFileName_(simulator.gridView(), + simulator.problem().outputDir(), + simulator.problem().name(), + simulator.time()); + + // open output file and write magic cookie + outStream_.open(fileName_.c_str()); + outStream_.precision(20); + + serializeSectionBegin(magicCookie); + serializeSectionEnd(); + } + + /*! + * \brief The output stream to write the serialized data. + */ + std::ostream& serializeStream() + { return outStream_; } + + /*! + * \brief Start a new section in the serialized output. + */ + void serializeSectionBegin(const std::string& cookie) + { outStream_ << cookie << "\n"; } + + /*! + * \brief End of a section in the serialized output. + */ + void serializeSectionEnd() + { outStream_ << "\n"; } + + /*! + * \brief Serialize all leaf entities of a codim in a gridView. + * + * The actual work is done by Serializer::serialize(Entity) + */ + template + void serializeEntities(Serializer& serializer, const GridView& gridView) + { + std::ostringstream oss; + oss << "Entities: Codim " << codim; + std::string cookie = oss.str(); + serializeSectionBegin(cookie); + + // write element data + using Iterator = typename GridView::template Codim::Iterator; + + Iterator it = gridView.template begin(); + const Iterator& endIt = gridView.template end(); + for (; it != endIt; ++it) { + serializer.serializeEntity(outStream_, *it); + outStream_ << "\n"; + } + + serializeSectionEnd(); + } + + /*! + * \brief Finish the restart file. + */ + void serializeEnd() + { outStream_.close(); } + + /*! + * \brief Start reading a restart file at a certain simulated + * time. + */ + template + void deserializeBegin(Simulator& simulator, Scalar t) + { + fileName_ = restartFileName_(simulator.gridView(), simulator.problem().outputDir(), simulator.problem().name(), t); + + // open input file and read magic cookie + inStream_.open(fileName_.c_str()); + if (!inStream_.good()) { + throw std::runtime_error("Restart file '"+fileName_+"' could not be opened properly"); + } + + // make sure that we don't open an empty file + inStream_.seekg(0, std::ios::end); + auto pos = inStream_.tellg(); + if (pos == 0) { + throw std::runtime_error("Restart file '"+fileName_+"' is empty"); + } + inStream_.seekg(0, std::ios::beg); + + const std::string magicCookie = magicRestartCookie_(simulator.gridView()); + + deserializeSectionBegin(magicCookie); + deserializeSectionEnd(); + } + + /*! + * \brief The input stream to read the data which ought to be + * deserialized. + */ + std::istream& deserializeStream() + { return inStream_; } + + /*! + * \brief Start reading a new section of the restart file. + */ + void deserializeSectionBegin(const std::string& cookie) + { + if (!inStream_.good()) + throw std::runtime_error("Encountered unexpected EOF in restart file."); + std::string buf; + std::getline(inStream_, buf); + if (buf != cookie) + throw std::runtime_error("Could not start section '"+cookie+"'"); + } + + /*! + * \brief End of a section in the serialized output. + */ + void deserializeSectionEnd() + { + std::string dummy; + std::getline(inStream_, dummy); + for (unsigned i = 0; i < dummy.length(); ++i) { + if (!std::isspace(dummy[i])) { + throw std::logic_error("Encountered unread values while deserializing"); + } + } + } + + /*! + * \brief Deserialize all leaf entities of a codim in a grid. + * + * The actual work is done by Deserializer::deserialize(Entity) + */ + template + void deserializeEntities(Deserializer& deserializer, const GridView& gridView) + { + std::ostringstream oss; + oss << "Entities: Codim " << codim; + std::string cookie = oss.str(); + deserializeSectionBegin(cookie); + + std::string curLine; + + // read entity data + using Iterator = typename GridView::template Codim::Iterator; + Iterator it = gridView.template begin(); + const Iterator& endIt = gridView.template end(); + for (; it != endIt; ++it) { + if (!inStream_.good()) { + throw std::runtime_error("Restart file is corrupted"); + } + + std::getline(inStream_, curLine); + std::istringstream curLineStream(curLine); + deserializer.deserializeEntity(curLineStream, *it); + } + + deserializeSectionEnd(); + } + + /*! + * \brief Stop reading the restart file. + */ + void deserializeEnd() + { inStream_.close(); } + +private: + std::string fileName_; + std::ifstream inStream_; + std::ofstream outStream_; +}; +} // namespace Opm + +#endif diff --git a/opm/models/io/simplexvanguard.hh b/opm/models/io/simplexvanguard.hh new file mode 100644 index 00000000000..595db9248f2 --- /dev/null +++ b/opm/models/io/simplexvanguard.hh @@ -0,0 +1,140 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::SimplexGridVanguard + */ +#ifndef EWOMS_SIMPLEX_GRID_VANGUARD_HH +#define EWOMS_SIMPLEX_GRID_VANGUARD_HH + +#include + +#include + +#include + +#include +#include +#include + +#include + +namespace Opm { +/*! + * \brief Provides a simulator vanguard which a creates regular grid made of simplices. + */ +template +class SimplexGridVanguard +{ + using ParentType = BaseVanguard; + using Scalar = GetPropType; + using Simulator = GetPropType; + using Grid = GetPropType; + + using GridPointer = std::unique_ptr; + using CoordScalar = typename Grid::ctype; + enum { + dim = Grid::dimension, + dimWorld = Grid::dimensionworld, + }; + using GlobalPosition = Dune::FieldVector; + +public: + /*! + * \brief Register all run-time parameters for the grid manager. + */ + static void registerParameters() + { + Parameters::Register + ("The number of global refinements of the grid " + "executed after it was loaded"); + Parameters::Register + ("The size of the domain in x direction"); + Parameters::Register + ("The number of intervalls in x direction"); + if (dimWorld > 1) { + Parameters::Register + ("The size of the domain in y direction"); + Parameters::Register + ("The number of intervalls in y direction"); + } + if constexpr (dim > 2) { + Parameters::Register + ("The size of the domain in z direction"); + Parameters::Register + ("The number of intervalls in z direction"); + } + } + + /*! + * \brief Create the Grid + */ + SimplexGridVanguard(Simulator& simulator) + : ParentType(simulator) + { + Dune::array cellRes; + GlobalPosition upperRight; + GlobalPosition lowerLeft; + + lowerLeft[0] = 0.0; + upperRight[0] = Parameters::Get(); + cellRes[0] = Parameters::Get(); + if constexpr (dim > 1) { + lowerLeft[1] = 0.0; + upperRight[1] = Parameters::Get(); + cellRes[1] = Parameters::Get(); + } + if constexpr (dim > 2) { + lowerLeft[2] = 0.0; + upperRight[2] = Parameters::Get(); + cellRes[2] = Parameters::Get(); + } + + simplexGrid_ = Dune::StructuredGridFactory::createSimplexGrid(lowerLeft, + upperRight, + cellRes); + + unsigned numRefinments = Parameters::Get(); + simplexGrid_->globalRefine(numRefinments); + + this->finalizeInit_(); + } + + /*! + * \brief Returns a reference to the grid. + */ + Grid& grid() + { return simplexGrid_; } + + /*! + * \brief Returns a reference to the grid. + */ + const Grid& grid() const + { return *simplexGrid_; } + +private: + GridPointer simplexGrid_; +}; +} // namespace Opm + +#endif diff --git a/opm/models/io/structuredgridvanguard.hh b/opm/models/io/structuredgridvanguard.hh new file mode 100644 index 00000000000..1c7be289ca0 --- /dev/null +++ b/opm/models/io/structuredgridvanguard.hh @@ -0,0 +1,194 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::StructuredGridVanguard + */ +#ifndef EWOMS_STRUCTURED_GRID_VANGUARD_HH +#define EWOMS_STRUCTURED_GRID_VANGUARD_HH + +#include + +#include +#include +#include +#include + +#include +#include + +#if HAVE_DUNE_ALUGRID +#include +#include +#endif + +#include +#include + +#include + +namespace Opm { + +template +class StructuredGridVanguard; + +} // namespace Opm + +namespace Opm::Properties { + +namespace TTag { + +struct StructuredGridVanguard {}; + +} // namespace TTag + +// GRIDDIM is only set by the finger problem +#ifndef GRIDDIM +static const int dim = 2; +#else +static const int dim = GRIDDIM; +#endif + +// set the Grid and Vanguard properties +#if HAVE_DUNE_ALUGRID +template +struct Grid { using type = Dune::ALUGrid< dim, dim, Dune::cube, Dune::nonconforming >; }; +#else +template +struct Grid { using type = Dune::YaspGrid< dim >; }; +#endif + +template +struct Vanguard { using type = Opm::StructuredGridVanguard; }; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup TestProblems + * + * \brief Helper class for grid instantiation of the lens problem. + */ +template +class StructuredGridVanguard : public BaseVanguard +{ + using ParentType = BaseVanguard; + using Scalar = GetPropType; + using Simulator = GetPropType; + using Grid = GetPropType; + + using GridPointer = std::unique_ptr; + + static constexpr int dim = Grid::dimension; + +public: + /*! + * \brief Register all run-time parameters for the structured grid simulator vanguard. + */ + static void registerParameters() + { + Parameters::Register + ("The number of global refinements of the grid " + "executed after it was loaded"); + Parameters::Register> + ("The size of the domain in x direction"); + Parameters::Register + ("The number of intervalls in x direction"); + if (dim > 1) { + Parameters::Register> + ("The size of the domain in y direction"); + Parameters::Register + ("The number of intervalls in y direction"); + } + if constexpr (dim > 2) { + Parameters::Register> + ("The size of the domain in z direction"); + Parameters::Register + ("The number of intervalls in z direction"); + } + } + + /*! + * \brief Create the grid for the lens problem + */ + StructuredGridVanguard(Simulator& simulator) + : ParentType(simulator) + { + Dune::FieldVector cellRes; + + using GridScalar = double; + Dune::FieldVector upperRight; + Dune::FieldVector lowerLeft( 0 ); + + upperRight[0] = Parameters::Get>(); + upperRight[1] = Parameters::Get>(); + + cellRes[0] = Parameters::Get(); + cellRes[1] = Parameters::Get(); + if constexpr (dim == 3) { + upperRight[2] = Parameters::Get>(); + cellRes[2] = Parameters::Get(); + } + + std::stringstream dgffile; + dgffile << "DGF" << std::endl; + dgffile << "INTERVAL" << std::endl; + dgffile << lowerLeft << std::endl; + dgffile << upperRight << std::endl; + dgffile << cellRes << std::endl; + dgffile << "#" << std::endl; + dgffile << "GridParameter" << std::endl; + dgffile << "overlap 1" << std::endl; + dgffile << "#" << std::endl; + dgffile << "Simplex" << std::endl; + dgffile << "#" << std::endl; + + // use DGF parser to create a grid from interval block + gridPtr_.reset( Dune::GridPtr< Grid >( dgffile ).release() ); + + unsigned numRefinements = Parameters::Get(); + gridPtr_->globalRefine(static_cast(numRefinements)); + + this->finalizeInit_(); + } + + /*! + * \brief Return a reference to the grid object. + */ + Grid& grid() + { return *gridPtr_; } + + /*! + * \brief Return a constant reference to the grid object. + */ + const Grid& grid() const + { return *gridPtr_; } + +private: + GridPointer gridPtr_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/unstructuredgridvanguard.hh b/opm/models/io/unstructuredgridvanguard.hh new file mode 100644 index 00000000000..05e4f81196a --- /dev/null +++ b/opm/models/io/unstructuredgridvanguard.hh @@ -0,0 +1,107 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; +// c-basic-offset: 4 -*- vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + OPM 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. OPM is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU General Public License + for more details. You should have received a copy of the + GNU General Public License along with OPM. If not, see + . Consult the COPYING file + in the top-level source directory of this module for the + precise wording of the license and the list of copyright + holders. +*/ +/*! + * \file + * \copydoc Opm::UnstructuredGridVanguard + */ +#ifndef EWOMS_UNSTRUCTURED_GRID_VANGUARD_HH +#define EWOMS_UNSTRUCTURED_GRID_VANGUARD_HH + +#include +#include +#include + +#ifdef HAVE_OPM_GRID +#include "opm/grid/UnstructuredGrid.h" +#endif + +namespace Opm { +/*! + * \brief Provides a simulator vanguard which creates a grid + * by parsing an unstructured grid file + */ +template +class UnstructuredGridVanguard : public BaseVanguard { + using ParentType = BaseVanguard; + using Scalar = GetPropType; + using Simulator = GetPropType; + using Grid = GetPropType; + + using GridPointer = Dune::GridPtr< Grid >; + + public: + /*! + * \brief Register all run-time parameters for the + * unstructured grid simulator vanguard. + */ + static void registerParameters() { + Parameters::Register + ("The number of global refinements of the grid " + "executed after it was loaded"); + Parameters::Register, + ("The file name of the file to load"); + } + + /*! + * \brief Load the grid from the file. + */ + UnstructuredGridVanguard(Simulator& simulator) : ParentType(simulator){ +#ifdef HAVE_OPM_GRID + const std::string gridFileName = Parameters::Get(); + unsigned numRefinments = Parameters::Get(); + + const char* c_str = gridFileName.c_str(); + + UnstructuredGrid* grid = read_grid(c_str); + if (grid == nullptr) { + std::string msg = + "RuntimeError: UnstructuredGridVanguard could not read grid file: " + + gridFileName + ". Are you sure the filename is correct?"; + throw std::runtime_error(msg); + } + ugPtr_.reset(std::move( grid )); + //GridPointer polygrid( new Grid(*ugPtr) ); + gridPtr_ = new Grid(*ugPtr_);//std::move(polygrid); + if (numRefinments > 0) { + gridPtr_->globalRefine(static_cast(numRefinments)); + } + this->finalizeInit_(); +#endif + } + + /*! + * \brief Return a reference to the grid object. + */ + Grid& grid() { return *gridPtr_; } + + /*! + * \brief Return a constant reference to the grid + * object. + */ + const Grid& grid() const { return *gridPtr_; } + + private: + GridPointer gridPtr_; + typename Grid::UnstructuredGridPtr ugPtr_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkblackoilenergymodule.hh b/opm/models/io/vtkblackoilenergymodule.hh new file mode 100644 index 00000000000..e782fdbcfb3 --- /dev/null +++ b/opm/models/io/vtkblackoilenergymodule.hh @@ -0,0 +1,226 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkBlackOilEnergyModule + */ +#ifndef EWOMS_VTK_BLACK_OIL_ENERGY_MODULE_HH +#define EWOMS_VTK_BLACK_OIL_ENERGY_MODULE_HH + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteRockInternalEnergy { static constexpr bool value = true; }; +struct VtkWriteTotalThermalConductivity { static constexpr bool value = true; }; +struct VtkWriteFluidInternalEnergies { static constexpr bool value = true; }; +struct VtkWriteFluidEnthalpies { static constexpr bool value = true; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup Vtk + * + * \brief VTK output module for the black oil model's energy related quantities. + */ +template +class VtkBlackOilEnergyModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using GridView = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + enum { enableEnergy = getPropValue() }; + enum { numPhases = getPropValue() }; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + using PhaseBuffer = typename ParentType::PhaseBuffer; + +public: + VtkBlackOilEnergyModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the multi-phase VTK output + * module. + */ + static void registerParameters() + { + if (!enableEnergy) + return; + + Parameters::Register + ("Include the volumetric internal energy of rock " + "in the VTK output files"); + Parameters::Register + ("Include the total thermal conductivity of the medium and the fluids " + "in the VTK output files"); + Parameters::Register + ("Include the internal energies of the fluids in the VTK output files"); + Parameters::Register + ("Include the enthalpies of the fluids in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (!Parameters::Get()) { + return; + } + + if (!enableEnergy) + return; + + if (rockInternalEnergyOutput_()) + this->resizeScalarBuffer_(rockInternalEnergy_); + if (totalThermalConductivityOutput_()) + this->resizeScalarBuffer_(totalThermalConductivity_); + if (fluidInternalEnergiesOutput_()) + this->resizePhaseBuffer_(fluidInternalEnergies_); + if (fluidEnthalpiesOutput_()) + this->resizePhaseBuffer_(fluidEnthalpies_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant for + * an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + if (!enableEnergy) + return; + + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) { + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0); + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + + if (rockInternalEnergyOutput_()) + rockInternalEnergy_[globalDofIdx] = + scalarValue(intQuants.rockInternalEnergy()); + + if (totalThermalConductivityOutput_()) + totalThermalConductivity_[globalDofIdx] = + scalarValue(intQuants.totalThermalConductivity()); + + for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + if (FluidSystem::phaseIsActive(phaseIdx)) { + if (fluidInternalEnergiesOutput_()) + fluidInternalEnergies_[phaseIdx][globalDofIdx] = + scalarValue(intQuants.fluidState().internalEnergy(phaseIdx)); + + if (fluidEnthalpiesOutput_()) + fluidEnthalpies_[phaseIdx][globalDofIdx] = + scalarValue(intQuants.fluidState().enthalpy(phaseIdx)); + } + } + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) + return; + + if (!enableEnergy) + return; + + if (rockInternalEnergyOutput_()) + this->commitScalarBuffer_(baseWriter, "volumetric internal energy rock", rockInternalEnergy_); + + if (totalThermalConductivityOutput_()) + this->commitScalarBuffer_(baseWriter, "total thermal conductivity", totalThermalConductivity_); + + if (fluidInternalEnergiesOutput_()) + this->commitPhaseBuffer_(baseWriter, "internal energy_%s", fluidInternalEnergies_); + + if (fluidEnthalpiesOutput_()) + this->commitPhaseBuffer_(baseWriter, "enthalpy_%s", fluidEnthalpies_); + } + +private: + static bool rockInternalEnergyOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool totalThermalConductivityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool fluidInternalEnergiesOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool fluidEnthalpiesOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + ScalarBuffer rockInternalEnergy_; + ScalarBuffer totalThermalConductivity_; + PhaseBuffer fluidInternalEnergies_; + PhaseBuffer fluidEnthalpies_; +}; +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkblackoilmicpmodule.hh b/opm/models/io/vtkblackoilmicpmodule.hh new file mode 100644 index 00000000000..cf626b8ed31 --- /dev/null +++ b/opm/models/io/vtkblackoilmicpmodule.hh @@ -0,0 +1,242 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkBlackOilMICPModule + */ +#ifndef EWOMS_VTK_BLACK_OIL_MICP_MODULE_HH +#define EWOMS_VTK_BLACK_OIL_MICP_MODULE_HH + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteMicrobialConcentration { static constexpr bool value = true; }; +struct VtkWriteOxygenConcentration { static constexpr bool value = true; }; +struct VtkWriteUreaConcentration { static constexpr bool value = true; }; +struct VtkWriteBiofilmConcentration { static constexpr bool value = true; }; +struct VtkWriteCalciteConcentration { static constexpr bool value = true; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup Vtk + * + * \brief VTK output module for the MICP model's related quantities. + */ +template +class VtkBlackOilMICPModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using GridView = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + enum { enableMICP = getPropValue() }; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + +public: + VtkBlackOilMICPModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the multi-phase VTK output + * module. + */ + static void registerParameters() + { + if (!enableMICP) + return; + + Parameters::Register + ("Include the concentration of the microbial component in the water phase " + "in the VTK output files"); + Parameters::Register + ("Include the concentration of the oxygen component in the water phase " + "in the VTK output files"); + Parameters::Register + ("Include the concentration of the urea component in the water phase " + "in the VTK output files"); + Parameters::Register + ("Include the biofilm volume fraction in the VTK output files"); + Parameters::Register + ("Include the calcite volume fraction in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (!Parameters::Get()) { + return; + } + + if (!enableMICP) + return; + + if (microbialConcentrationOutput_()) + this->resizeScalarBuffer_(microbialConcentration_); + if (oxygenConcentrationOutput_()) + this->resizeScalarBuffer_(oxygenConcentration_); + if (ureaConcentrationOutput_()) + this->resizeScalarBuffer_(ureaConcentration_); + if (biofilmConcentrationOutput_()) + this->resizeScalarBuffer_(biofilmConcentration_); + if (calciteConcentrationOutput_()) + this->resizeScalarBuffer_(calciteConcentration_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant for + * an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + if (!enableMICP) + return; + + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) { + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0); + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + + if (microbialConcentrationOutput_()) + microbialConcentration_[globalDofIdx] = + scalarValue(intQuants.microbialConcentration()); + + if (oxygenConcentrationOutput_()) + oxygenConcentration_[globalDofIdx] = + scalarValue(intQuants.oxygenConcentration()); + + if (ureaConcentrationOutput_()) + ureaConcentration_[globalDofIdx] = + 10 * scalarValue(intQuants.ureaConcentration());//Multypliging by scaling factor 10 (see WellInterface_impl.hpp) + + if (biofilmConcentrationOutput_()) + biofilmConcentration_[globalDofIdx] = + scalarValue(intQuants.biofilmConcentration()); + + if (calciteConcentrationOutput_()) + calciteConcentration_[globalDofIdx] = + scalarValue(intQuants.calciteConcentration()); + + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) + return; + + if (!enableMICP) + return; + + if (microbialConcentrationOutput_()) + this->commitScalarBuffer_(baseWriter, "microbial concentration", microbialConcentration_); + + if (oxygenConcentrationOutput_()) + this->commitScalarBuffer_(baseWriter, "oxygen concentration", oxygenConcentration_); + + if (ureaConcentrationOutput_()) + this->commitScalarBuffer_(baseWriter, "urea concentration", ureaConcentration_); + + if (biofilmConcentrationOutput_()) + this->commitScalarBuffer_(baseWriter, "biofilm fraction", biofilmConcentration_); + + if (calciteConcentrationOutput_()) + this->commitScalarBuffer_(baseWriter, "calcite fraction", calciteConcentration_); + + } + +private: + static bool microbialConcentrationOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool oxygenConcentrationOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool ureaConcentrationOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool biofilmConcentrationOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool calciteConcentrationOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + ScalarBuffer microbialConcentration_; + ScalarBuffer oxygenConcentration_; + ScalarBuffer ureaConcentration_; + ScalarBuffer biofilmConcentration_; + ScalarBuffer calciteConcentration_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkblackoilmodule.hh b/opm/models/io/vtkblackoilmodule.hh new file mode 100644 index 00000000000..4b0de723da0 --- /dev/null +++ b/opm/models/io/vtkblackoilmodule.hh @@ -0,0 +1,392 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkBlackOilModule + */ +#ifndef EWOMS_VTK_BLACK_OIL_MODULE_HH +#define EWOMS_VTK_BLACK_OIL_MODULE_HH + +#include "vtkmultiwriter.hh" +#include "baseoutputmodule.hh" + +#include + +#include + +#include + +#include + +#include +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteGasDissolutionFactor { static constexpr bool value = false; }; +struct VtkWriteOilVaporizationFactor { static constexpr bool value = false; }; +struct VtkWriteOilFormationVolumeFactor { static constexpr bool value = false; }; +struct VtkWriteGasFormationVolumeFactor { static constexpr bool value = false; }; +struct VtkWriteWaterFormationVolumeFactor { static constexpr bool value = false; }; +struct VtkWriteOilSaturationPressure { static constexpr bool value = false; }; +struct VtkWriteGasSaturationPressure { static constexpr bool value = false; }; +struct VtkWriteSaturationRatios { static constexpr bool value = false; }; +struct VtkWriteSaturatedOilGasDissolutionFactor { static constexpr bool value = false; }; +struct VtkWriteSaturatedGasOilVaporizationFactor { static constexpr bool value = false; }; +struct VtkWritePrimaryVarsMeaning { static constexpr bool value = false; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup Vtk + * + * \brief VTK output module for the black oil model's parameters. + */ +template +class VtkBlackOilModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + + using GridView = GetPropType; + using FluidSystem = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + + enum { gasCompIdx = FluidSystem::gasCompIdx }; + enum { oilCompIdx = FluidSystem::oilCompIdx }; + enum { waterCompIdx = FluidSystem::waterCompIdx }; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + +public: + VtkBlackOilModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the multi-phase VTK output + * module. + */ + static void registerParameters() + { + Parameters::Register + ("Include the gas dissolution factor (R_s) of the observed oil " + "in the VTK output files"); + Parameters::Register + ("Include the oil vaporization factor (R_v) of the observed gas " + "in the VTK output files"); + Parameters::Register + ("Include the oil formation volume factor (B_o) in the VTK output files"); + Parameters::Register + ("Include the gas formation volume factor (B_g) in the " + "VTK output files"); + Parameters::Register + ("Include the water formation volume factor (B_w) in the " + "VTK output files"); + Parameters::Register + ("Include the saturation pressure of oil (p_o,sat) in the " + "VTK output files"); + Parameters::Register + ("Include the saturation pressure of gas (p_g,sat) in the " + "VTK output files"); + Parameters::Register + ("Include the gas dissolution factor (R_s,sat) of gas saturated " + "oil in the VTK output files"); + Parameters::Register + ("Include the oil vaporization factor (R_v,sat) of oil saturated " + "gas in the VTK output files"); + Parameters::Register + ("Write the ratio of the actually and maximum dissolved component of " + "the mixtures"); + Parameters::Register + ("Include how the primary variables should be interpreted to the " + "VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (gasDissolutionFactorOutput_()) + this->resizeScalarBuffer_(gasDissolutionFactor_); + if (oilVaporizationFactorOutput_()) + this->resizeScalarBuffer_(oilVaporizationFactor_); + if (oilFormationVolumeFactorOutput_()) + this->resizeScalarBuffer_(oilFormationVolumeFactor_); + if (gasFormationVolumeFactorOutput_()) + this->resizeScalarBuffer_(gasFormationVolumeFactor_); + if (waterFormationVolumeFactorOutput_()) + this->resizeScalarBuffer_(waterFormationVolumeFactor_); + if (oilSaturationPressureOutput_()) + this->resizeScalarBuffer_(oilSaturationPressure_); + if (gasSaturationPressureOutput_()) + this->resizeScalarBuffer_(gasSaturationPressure_); + if (saturatedOilGasDissolutionFactorOutput_()) + this->resizeScalarBuffer_(saturatedOilGasDissolutionFactor_); + if (saturatedGasOilVaporizationFactorOutput_()) + this->resizeScalarBuffer_(saturatedGasOilVaporizationFactor_); + if (saturationRatiosOutput_()) { + this->resizeScalarBuffer_(oilSaturationRatio_); + this->resizeScalarBuffer_(gasSaturationRatio_); + } + if (primaryVarsMeaningOutput_()) { + this->resizeScalarBuffer_(primaryVarsMeaningPressure_); + this->resizeScalarBuffer_(primaryVarsMeaningWater_); + this->resizeScalarBuffer_(primaryVarsMeaningGas_); + } + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant for + * an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) { + const auto& fs = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0).fluidState(); + using FluidState = typename std::remove_const::type>::type; + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + + const auto& primaryVars = elemCtx.primaryVars(dofIdx, /*timeIdx=*/0); + + unsigned pvtRegionIdx = elemCtx.primaryVars(dofIdx, /*timeIdx=*/0).pvtRegionIndex(); + Scalar SoMax = 0.0; + if (FluidSystem::phaseIsActive(oilPhaseIdx)) + SoMax = std::max(getValue(fs.saturation(oilPhaseIdx)), + elemCtx.problem().maxOilSaturation(globalDofIdx)); + + if (FluidSystem::phaseIsActive(gasPhaseIdx) && FluidSystem::phaseIsActive(oilPhaseIdx)) { + Scalar x_oG = getValue(fs.moleFraction(oilPhaseIdx, gasCompIdx)); + Scalar x_gO = getValue(fs.moleFraction(gasPhaseIdx, oilCompIdx)); + Scalar X_oG = getValue(fs.massFraction(oilPhaseIdx, gasCompIdx)); + Scalar X_gO = getValue(fs.massFraction(gasPhaseIdx, oilCompIdx)); + Scalar Rs = FluidSystem::convertXoGToRs(X_oG, pvtRegionIdx); + Scalar Rv = FluidSystem::convertXgOToRv(X_gO, pvtRegionIdx); + + Scalar RsSat = + FluidSystem::template saturatedDissolutionFactor(fs, + oilPhaseIdx, + pvtRegionIdx, + SoMax); + Scalar X_oG_sat = FluidSystem::convertRsToXoG(RsSat, pvtRegionIdx); + Scalar x_oG_sat = FluidSystem::convertXoGToxoG(X_oG_sat, pvtRegionIdx); + + Scalar RvSat = + FluidSystem::template saturatedDissolutionFactor(fs, + gasPhaseIdx, + pvtRegionIdx, + SoMax); + Scalar X_gO_sat = FluidSystem::convertRvToXgO(RvSat, pvtRegionIdx); + Scalar x_gO_sat = FluidSystem::convertXgOToxgO(X_gO_sat, pvtRegionIdx); + if (gasDissolutionFactorOutput_()) + gasDissolutionFactor_[globalDofIdx] = Rs; + if (oilVaporizationFactorOutput_()) + oilVaporizationFactor_[globalDofIdx] = Rv; + if (oilSaturationPressureOutput_()) + oilSaturationPressure_[globalDofIdx] = + FluidSystem::template saturationPressure(fs, oilPhaseIdx, pvtRegionIdx); + if (gasSaturationPressureOutput_()) + gasSaturationPressure_[globalDofIdx] = + FluidSystem::template saturationPressure(fs, gasPhaseIdx, pvtRegionIdx); + if (saturatedOilGasDissolutionFactorOutput_()) + saturatedOilGasDissolutionFactor_[globalDofIdx] = RsSat; + if (saturatedGasOilVaporizationFactorOutput_()) + saturatedGasOilVaporizationFactor_[globalDofIdx] = RvSat; + if (saturationRatiosOutput_()) { + if (x_oG_sat <= 0.0) + oilSaturationRatio_[globalDofIdx] = 1.0; + else + oilSaturationRatio_[globalDofIdx] = x_oG / x_oG_sat; + + if (x_gO_sat <= 0.0) + gasSaturationRatio_[globalDofIdx] = 1.0; + else + gasSaturationRatio_[globalDofIdx] = x_gO / x_gO_sat; + } + } + if (oilFormationVolumeFactorOutput_()) + oilFormationVolumeFactor_[globalDofIdx] = + 1.0/FluidSystem::template inverseFormationVolumeFactor(fs, oilPhaseIdx, pvtRegionIdx); + if (gasFormationVolumeFactorOutput_()) + gasFormationVolumeFactor_[globalDofIdx] = + 1.0/FluidSystem::template inverseFormationVolumeFactor(fs, gasPhaseIdx, pvtRegionIdx); + if (waterFormationVolumeFactorOutput_()) + waterFormationVolumeFactor_[globalDofIdx] = + 1.0/FluidSystem::template inverseFormationVolumeFactor(fs, waterPhaseIdx, pvtRegionIdx); + + if (primaryVarsMeaningOutput_()) { + primaryVarsMeaningWater_[globalDofIdx] = + static_cast(primaryVars.primaryVarsMeaningWater()); + primaryVarsMeaningGas_[globalDofIdx] = + static_cast(primaryVars.primaryVarsMeaningGas()); + primaryVarsMeaningPressure_[globalDofIdx] = + static_cast(primaryVars.primaryVarsMeaningPressure()); + } + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) + return; + + if (gasDissolutionFactorOutput_()) + this->commitScalarBuffer_(baseWriter, "R_s", gasDissolutionFactor_); + if (oilVaporizationFactorOutput_()) + this->commitScalarBuffer_(baseWriter, "R_v", oilVaporizationFactor_); + if (oilFormationVolumeFactorOutput_()) + this->commitScalarBuffer_(baseWriter, "B_o", oilFormationVolumeFactor_); + if (gasFormationVolumeFactorOutput_()) + this->commitScalarBuffer_(baseWriter, "B_g", gasFormationVolumeFactor_); + if (waterFormationVolumeFactorOutput_()) + this->commitScalarBuffer_(baseWriter, "B_w", waterFormationVolumeFactor_); + if (oilSaturationPressureOutput_()) + this->commitScalarBuffer_(baseWriter, "p_o,sat", oilSaturationPressure_); + if (gasSaturationPressureOutput_()) + this->commitScalarBuffer_(baseWriter, "p_g,sat", gasSaturationPressure_); + if (saturatedOilGasDissolutionFactorOutput_()) + this->commitScalarBuffer_(baseWriter, "R_s,sat", saturatedOilGasDissolutionFactor_); + if (saturatedGasOilVaporizationFactorOutput_()) + this->commitScalarBuffer_(baseWriter, "R_v,sat", saturatedGasOilVaporizationFactor_); + if (saturationRatiosOutput_()) { + this->commitScalarBuffer_(baseWriter, "saturation ratio_oil", oilSaturationRatio_); + this->commitScalarBuffer_(baseWriter, "saturation ratio_gas", gasSaturationRatio_); + } + + if (primaryVarsMeaningOutput_()) { + this->commitScalarBuffer_(baseWriter, "primary vars meaning water", primaryVarsMeaningWater_); + this->commitScalarBuffer_(baseWriter, "primary vars meaning gas", primaryVarsMeaningGas_); + this->commitScalarBuffer_(baseWriter, "primary vars meaning pressure", primaryVarsMeaningPressure_); + } + } + +private: + static bool gasDissolutionFactorOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool oilVaporizationFactorOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool oilFormationVolumeFactorOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool gasFormationVolumeFactorOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool waterFormationVolumeFactorOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool oilSaturationPressureOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool gasSaturationPressureOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool saturatedOilGasDissolutionFactorOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool saturatedGasOilVaporizationFactorOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool saturationRatiosOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool primaryVarsMeaningOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + ScalarBuffer gasDissolutionFactor_; + ScalarBuffer oilVaporizationFactor_; + ScalarBuffer oilFormationVolumeFactor_; + ScalarBuffer gasFormationVolumeFactor_; + ScalarBuffer waterFormationVolumeFactor_; + ScalarBuffer oilSaturationPressure_; + ScalarBuffer gasSaturationPressure_; + + ScalarBuffer saturatedOilGasDissolutionFactor_; + ScalarBuffer saturatedGasOilVaporizationFactor_; + ScalarBuffer oilSaturationRatio_; + ScalarBuffer gasSaturationRatio_; + + ScalarBuffer primaryVarsMeaningPressure_; + ScalarBuffer primaryVarsMeaningWater_; + ScalarBuffer primaryVarsMeaningGas_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkblackoilpolymermodule.hh b/opm/models/io/vtkblackoilpolymermodule.hh new file mode 100644 index 00000000000..eb7f7f23d79 --- /dev/null +++ b/opm/models/io/vtkblackoilpolymermodule.hh @@ -0,0 +1,268 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkBlackOilPolymerModule + */ +#ifndef EWOMS_VTK_BLACK_OIL_POLYMER_MODULE_HH +#define EWOMS_VTK_BLACK_OIL_POLYMER_MODULE_HH + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +namespace Opm::Properties::TTag { + +// create new type tag for the VTK multi-phase output +struct VtkBlackOilPolymer {}; + +} // namespace Opm::Properties::TTag + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWritePolymerConcentration { static constexpr bool value = true; }; +struct VtkWritePolymerDeadPoreVolume { static constexpr bool value = true; }; +struct VtkWritePolymerViscosityCorrection { static constexpr bool value = true; }; +struct VtkWriteWaterViscosityCorrection { static constexpr bool value = true; }; +struct VtkWritePolymerRockDensity { static constexpr bool value = true; }; +struct VtkWritePolymerAdsorption { static constexpr bool value = true; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup Vtk + * + * \brief VTK output module for the black oil model's polymer related quantities. + */ +template +class VtkBlackOilPolymerModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using GridView = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + enum { enablePolymer = getPropValue() }; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + +public: + VtkBlackOilPolymerModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the multi-phase VTK output + * module. + */ + static void registerParameters() + { + if (!enablePolymer) + return; + + Parameters::Register + ("Include the concentration of the polymer component in the water phase " + "in the VTK output files"); + Parameters::Register + ("Include the fraction of the \"dead\" pore volume " + "in the VTK output files"); + Parameters::Register + ("Include the amount of already adsorbed polymer component" + "in the VTK output files"); + Parameters::Register + ("Include the adsorption rate of the polymer component" + "in the VTK output files"); + Parameters::Register + ("Include the viscosity correction of the polymer component " + "in the VTK output files"); + Parameters::Register + ("Include the viscosity correction of the water component " + "due to polymers in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (!Parameters::Get()) { + return; + } + + if (!enablePolymer) + return; + + if (polymerConcentrationOutput_()) + this->resizeScalarBuffer_(polymerConcentration_); + if (polymerDeadPoreVolumeOutput_()) + this->resizeScalarBuffer_(polymerDeadPoreVolume_); + if (polymerRockDensityOutput_()) + this->resizeScalarBuffer_(polymerRockDensity_); + if (polymerAdsorptionOutput_()) + this->resizeScalarBuffer_(polymerAdsorption_); + if (polymerViscosityCorrectionOutput_()) + this->resizeScalarBuffer_(polymerViscosityCorrection_); + if (waterViscosityCorrectionOutput_()) + this->resizeScalarBuffer_(waterViscosityCorrection_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant for + * an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + if (!enablePolymer) + return; + + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) { + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0); + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + + if (polymerConcentrationOutput_()) + polymerConcentration_[globalDofIdx] = + scalarValue(intQuants.polymerConcentration()); + + if (polymerDeadPoreVolumeOutput_()) + polymerDeadPoreVolume_[globalDofIdx] = + scalarValue(intQuants.polymerDeadPoreVolume()); + + if (polymerRockDensityOutput_()) + polymerRockDensity_[globalDofIdx] = + scalarValue(intQuants.polymerRockDensity()); + + if (polymerAdsorptionOutput_()) + polymerAdsorption_[globalDofIdx] = + scalarValue(intQuants.polymerAdsorption()); + + if (polymerViscosityCorrectionOutput_()) + polymerViscosityCorrection_[globalDofIdx] = + scalarValue(intQuants.polymerViscosityCorrection()); + + if (waterViscosityCorrectionOutput_()) + waterViscosityCorrection_[globalDofIdx] = + scalarValue(intQuants.waterViscosityCorrection()); + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) + return; + + if (!enablePolymer) + return; + + if (polymerConcentrationOutput_()) + this->commitScalarBuffer_(baseWriter, "polymer concentration", polymerConcentration_); + + if (polymerDeadPoreVolumeOutput_()) + this->commitScalarBuffer_(baseWriter, "dead pore volume fraction", polymerDeadPoreVolume_); + + if (polymerRockDensityOutput_()) + this->commitScalarBuffer_(baseWriter, "polymer rock density", polymerRockDensity_); + + if (polymerAdsorptionOutput_()) + this->commitScalarBuffer_(baseWriter, "polymer adsorption", polymerAdsorption_); + + if (polymerViscosityCorrectionOutput_()) + this->commitScalarBuffer_(baseWriter, "polymer viscosity correction", polymerViscosityCorrection_); + + if (waterViscosityCorrectionOutput_()) + this->commitScalarBuffer_(baseWriter, "water viscosity correction", waterViscosityCorrection_); + } + +private: + static bool polymerConcentrationOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool polymerDeadPoreVolumeOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool polymerRockDensityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool polymerAdsorptionOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool polymerViscosityCorrectionOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool waterViscosityCorrectionOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + ScalarBuffer polymerConcentration_; + ScalarBuffer polymerDeadPoreVolume_; + ScalarBuffer polymerRockDensity_; + ScalarBuffer polymerAdsorption_; + ScalarBuffer polymerViscosityCorrection_; + ScalarBuffer waterViscosityCorrection_; +}; +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkblackoilsolventmodule.hh b/opm/models/io/vtkblackoilsolventmodule.hh new file mode 100644 index 00000000000..acb47519a79 --- /dev/null +++ b/opm/models/io/vtkblackoilsolventmodule.hh @@ -0,0 +1,243 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkBlackOilSolventModule + */ +#ifndef EWOMS_VTK_BLACK_OIL_SOLVENT_MODULE_HH +#define EWOMS_VTK_BLACK_OIL_SOLVENT_MODULE_HH + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteSolventSaturation { static constexpr bool value = true; }; +struct VtkWriteSolventRsw { static constexpr bool value = true; }; +struct VtkWriteSolventDensity { static constexpr bool value = true; }; +struct VtkWriteSolventViscosity { static constexpr bool value = true; }; +struct VtkWriteSolventMobility { static constexpr bool value = true; }; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup Vtk + * + * \brief VTK output module for the black oil model's solvent related quantities. + */ +template +class VtkBlackOilSolventModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using GridView = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + enum { enableSolvent = getPropValue() }; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + +public: + VtkBlackOilSolventModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the multi-phase VTK output + * module. + */ + static void registerParameters() + { + if (!enableSolvent) + return; + + Parameters::Register + ("Include the \"saturation\" of the solvent component " + "in the VTK output files"); + Parameters::Register + ("Include the \"dissolved volume in water\" of the solvent component " + "in the VTK output files"); + Parameters::Register + ("Include the \"density\" of the solvent component " + "in the VTK output files"); + Parameters::Register + ("Include the \"viscosity\" of the solvent component " + "in the VTK output files"); + Parameters::Register + ("Include the \"mobility\" of the solvent component " + "in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (!Parameters::Get()) { + return; + } + + if (!enableSolvent) + return; + + if (solventSaturationOutput_()) + this->resizeScalarBuffer_(solventSaturation_); + if (solventRswOutput_()) + this->resizeScalarBuffer_(solventRsw_); + if (solventDensityOutput_()) + this->resizeScalarBuffer_(solventDensity_); + if (solventViscosityOutput_()) + this->resizeScalarBuffer_(solventViscosity_); + if (solventMobilityOutput_()) + this->resizeScalarBuffer_(solventMobility_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant for + * an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + if (!enableSolvent) + return; + + using Toolbox = MathToolbox; + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) { + const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0); + unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + + if (solventSaturationOutput_()) + solventSaturation_[globalDofIdx] = + Toolbox::scalarValue(intQuants.solventSaturation()); + + if (solventRswOutput_()) + solventRsw_[globalDofIdx] = + Toolbox::scalarValue(intQuants.rsSolw()); + + if (solventDensityOutput_()) + solventDensity_[globalDofIdx] = + Toolbox::scalarValue(intQuants.solventDensity()); + + if (solventViscosityOutput_()) + solventViscosity_[globalDofIdx] = + Toolbox::scalarValue(intQuants.solventViscosity()); + + if (solventMobilityOutput_()) + solventMobility_[globalDofIdx] = + Toolbox::scalarValue(intQuants.solventMobility()); + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) + return; + + if (!enableSolvent) + return; + + if (solventSaturationOutput_()) + this->commitScalarBuffer_(baseWriter, "saturation_solvent", solventSaturation_); + + if (solventRswOutput_()) + this->commitScalarBuffer_(baseWriter, "dissolved_solvent", solventRsw_); + + if (solventDensityOutput_()) + this->commitScalarBuffer_(baseWriter, "density_solvent", solventDensity_); + + if (solventViscosityOutput_()) + this->commitScalarBuffer_(baseWriter, "viscosity_solvent", solventViscosity_); + + if (solventMobilityOutput_()) + this->commitScalarBuffer_(baseWriter, "mobility_solvent", solventMobility_); + } + +private: + static bool solventSaturationOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool solventRswOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool solventDensityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool solventViscosityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool solventMobilityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + ScalarBuffer solventSaturation_; + ScalarBuffer solventRsw_; + ScalarBuffer solventDensity_; + ScalarBuffer solventViscosity_; + ScalarBuffer solventMobility_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkcompositionmodule.hh b/opm/models/io/vtkcompositionmodule.hh new file mode 100644 index 00000000000..bc16cf15c74 --- /dev/null +++ b/opm/models/io/vtkcompositionmodule.hh @@ -0,0 +1,284 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkCompositionModule + */ +#ifndef EWOMS_VTK_COMPOSITION_MODULE_HH +#define EWOMS_VTK_COMPOSITION_MODULE_HH + +#include + +#include + +#include +#include + +#include +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteMassFractions { static constexpr bool value = false; }; +struct VtkWriteMoleFractions { static constexpr bool value = true; }; +struct VtkWriteTotalMassFractions { static constexpr bool value = false; }; +struct VtkWriteTotalMoleFractions { static constexpr bool value = false; }; +struct VtkWriteMolarities { static constexpr bool value = false; }; +struct VtkWriteFugacities { static constexpr bool value = false; }; +struct VtkWriteFugacityCoeffs { static constexpr bool value = false; }; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup Vtk + * + * \brief VTK output module for the fluid composition + * + * This module deals with the following quantities: + * - Mole fraction of a component in a fluid phase + * - Mass fraction of a component in a fluid phase + * - Molarity (i.e. molar concentration) of a component in a fluid phase + * - Fugacity of all components + * - FugacityCoefficient of all components in all phases + */ +template +class VtkCompositionModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + + using GridView = GetPropType; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + using ComponentBuffer = typename ParentType::ComponentBuffer; + using PhaseComponentBuffer = typename ParentType::PhaseComponentBuffer; + +public: + VtkCompositionModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the Vtk output module. + */ + static void registerParameters() + { + Parameters::Register + ("Include mass fractions in the VTK output files"); + Parameters::Register + ("Include mole fractions in the VTK output files"); + Parameters::Register + ("Include total mass fractions in the VTK output files"); + Parameters::Register + ("Include total mole fractions in the VTK output files"); + Parameters::Register + ("Include component molarities in the VTK output files"); + Parameters::Register + ("Include component fugacities in the VTK output files"); + Parameters::Register + ("Include component fugacity coefficients in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (moleFracOutput_()) + this->resizePhaseComponentBuffer_(moleFrac_); + if (massFracOutput_()) + this->resizePhaseComponentBuffer_(massFrac_); + if (totalMassFracOutput_()) + this->resizeComponentBuffer_(totalMassFrac_); + if (totalMoleFracOutput_()) + this->resizeComponentBuffer_(totalMoleFrac_); + if (molarityOutput_()) + this->resizePhaseComponentBuffer_(molarity_); + + if (fugacityOutput_()) + this->resizeComponentBuffer_(fugacity_); + if (fugacityCoeffOutput_()) + this->resizePhaseComponentBuffer_(fugacityCoeff_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant + * for an element + */ + void processElement(const ElementContext& elemCtx) + { + using Toolbox = MathToolbox; + + if (!Parameters::Get()) { + return; + } + + for (unsigned i = 0; i < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++i) { + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + const auto& intQuants = elemCtx.intensiveQuantities(i, /*timeIdx=*/0); + const auto& fs = intQuants.fluidState(); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + if (moleFracOutput_()) + moleFrac_[phaseIdx][compIdx][I] = Toolbox::value(fs.moleFraction(phaseIdx, compIdx)); + if (massFracOutput_()) + massFrac_[phaseIdx][compIdx][I] = Toolbox::value(fs.massFraction(phaseIdx, compIdx)); + if (molarityOutput_()) + molarity_[phaseIdx][compIdx][I] = Toolbox::value(fs.molarity(phaseIdx, compIdx)); + + if (fugacityCoeffOutput_()) + fugacityCoeff_[phaseIdx][compIdx][I] = + Toolbox::value(fs.fugacityCoefficient(phaseIdx, compIdx)); + } + } + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + if (totalMassFracOutput_()) { + Scalar compMass = 0; + Scalar totalMass = 0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + totalMass += Toolbox::value(fs.density(phaseIdx)) * Toolbox::value(fs.saturation(phaseIdx)); + compMass += + Toolbox::value(fs.density(phaseIdx)) + *Toolbox::value(fs.saturation(phaseIdx)) + *Toolbox::value(fs.massFraction(phaseIdx, compIdx)); + } + totalMassFrac_[compIdx][I] = compMass / totalMass; + } + if (totalMoleFracOutput_()) { + Scalar compMoles = 0; + Scalar totalMoles = 0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + totalMoles += + Toolbox::value(fs.molarDensity(phaseIdx)) + *Toolbox::value(fs.saturation(phaseIdx)); + compMoles += + Toolbox::value(fs.molarDensity(phaseIdx)) + *Toolbox::value(fs.saturation(phaseIdx)) + *Toolbox::value(fs.moleFraction(phaseIdx, compIdx)); + } + totalMoleFrac_[compIdx][I] = compMoles / totalMoles; + } + if (fugacityOutput_()) + fugacity_[compIdx][I] = Toolbox::value(intQuants.fluidState().fugacity(/*phaseIdx=*/0, compIdx)); + } + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) { + return; + } + + if (moleFracOutput_()) + this->commitPhaseComponentBuffer_(baseWriter, "moleFrac_%s^%s", moleFrac_); + if (massFracOutput_()) + this->commitPhaseComponentBuffer_(baseWriter, "massFrac_%s^%s", massFrac_); + if (molarityOutput_()) + this->commitPhaseComponentBuffer_(baseWriter, "molarity_%s^%s", molarity_); + if (totalMassFracOutput_()) + this->commitComponentBuffer_(baseWriter, "totalMassFrac^%s", totalMassFrac_); + if (totalMoleFracOutput_()) + this->commitComponentBuffer_(baseWriter, "totalMoleFrac^%s", totalMoleFrac_); + + if (fugacityOutput_()) + this->commitComponentBuffer_(baseWriter, "fugacity^%s", fugacity_); + if (fugacityCoeffOutput_()) + this->commitPhaseComponentBuffer_(baseWriter, "fugacityCoeff_%s^%s", fugacityCoeff_); + } + +private: + static bool massFracOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool moleFracOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool totalMassFracOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool totalMoleFracOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool molarityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool fugacityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool fugacityCoeffOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + PhaseComponentBuffer moleFrac_; + PhaseComponentBuffer massFrac_; + PhaseComponentBuffer molarity_; + ComponentBuffer totalMassFrac_; + ComponentBuffer totalMoleFrac_; + + ComponentBuffer fugacity_; + PhaseComponentBuffer fugacityCoeff_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkdiffusionmodule.hh b/opm/models/io/vtkdiffusionmodule.hh new file mode 100644 index 00000000000..13bf3912f18 --- /dev/null +++ b/opm/models/io/vtkdiffusionmodule.hh @@ -0,0 +1,194 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::VtkDiffusionModule + */ +#ifndef EWOMS_VTK_DIFFUSION_MODULE_HH +#define EWOMS_VTK_DIFFUSION_MODULE_HH + +#include +#include + +#include + +#include +#include + +#include +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteTortuosities { static constexpr bool value = false; }; +struct VtkWriteDiffusionCoefficients { static constexpr bool value = false; }; +struct VtkWriteEffectiveDiffusionCoefficients { static constexpr bool value = false; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup Vtk + * + * \brief VTK output module for quantities which make sense for models which + * incorperate molecular diffusion. + * + * This module deals with the following quantities: + * - Molecular diffusion coefficients of all components in all fluid phases + * - Effective molecular diffusion coefficients of the porous medium of all + * components in all fluid phases + */ +template +class VtkDiffusionModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using ElementContext = GetPropType; + using GridView = GetPropType; + using Evaluation = GetPropType; + + using Toolbox = MathToolbox; + + using PhaseComponentBuffer = typename ParentType::PhaseComponentBuffer; + using PhaseBuffer = typename ParentType::PhaseBuffer; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + +public: + VtkDiffusionModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the Vtk output module. + */ + static void registerParameters() + { + Parameters::Register + ("Include the tortuosity for each phase in the VTK output files"); + Parameters::Register + ("Include the molecular diffusion coefficients in " + "the VTK output files"); + Parameters::Register + ("Include the effective molecular diffusion " + "coefficients the medium in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (tortuosityOutput_()) + this->resizePhaseBuffer_(tortuosity_); + if (diffusionCoefficientOutput_()) + this->resizePhaseComponentBuffer_(diffusionCoefficient_); + if (effectiveDiffusionCoefficientOutput_()) + this->resizePhaseComponentBuffer_(effectiveDiffusionCoefficient_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quanties relevant + * for an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + for (unsigned i = 0; i < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++i) { + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + const auto& intQuants = elemCtx.intensiveQuantities(i, /*timeIdx=*/0); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (tortuosityOutput_()) + tortuosity_[phaseIdx][I] = Toolbox::value(intQuants.tortuosity(phaseIdx)); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + if (diffusionCoefficientOutput_()) + diffusionCoefficient_[phaseIdx][compIdx][I] = + Toolbox::value(intQuants.diffusionCoefficient(phaseIdx, compIdx)); + if (effectiveDiffusionCoefficientOutput_()) + effectiveDiffusionCoefficient_[phaseIdx][compIdx][I] = + Toolbox::value(intQuants.effectiveDiffusionCoefficient(phaseIdx, compIdx)); + } + } + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) { + return; + } + + if (tortuosityOutput_()) + this->commitPhaseBuffer_(baseWriter, "tortuosity", tortuosity_); + if (diffusionCoefficientOutput_()) + this->commitPhaseComponentBuffer_(baseWriter, "diffusionCoefficient", + diffusionCoefficient_); + if (effectiveDiffusionCoefficientOutput_()) + this->commitPhaseComponentBuffer_(baseWriter, + "effectiveDiffusionCoefficient", + effectiveDiffusionCoefficient_); + } + +private: + static bool tortuosityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool diffusionCoefficientOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool effectiveDiffusionCoefficientOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + PhaseBuffer tortuosity_; + PhaseComponentBuffer diffusionCoefficient_; + PhaseComponentBuffer effectiveDiffusionCoefficient_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkdiscretefracturemodule.hh b/opm/models/io/vtkdiscretefracturemodule.hh new file mode 100644 index 00000000000..49904a5e9e2 --- /dev/null +++ b/opm/models/io/vtkdiscretefracturemodule.hh @@ -0,0 +1,350 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkDiscreteFractureModule + */ +#ifndef EWOMS_VTK_DISCRETE_FRACTURE_MODULE_HH +#define EWOMS_VTK_DISCRETE_FRACTURE_MODULE_HH + +#include + +#include + +#include + +#include +#include + +#include +#include + +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteFractureSaturations { static constexpr bool value = true; }; +struct VtkWriteFractureMobilities { static constexpr bool value = false; }; +struct VtkWriteFractureRelativePermeabilities { static constexpr bool value = true; }; +struct VtkWriteFracturePorosity { static constexpr bool value = true; }; +struct VtkWriteFractureIntrinsicPermeabilities { static constexpr bool value = false; }; +struct VtkWriteFractureFilterVelocities { static constexpr bool value = false; }; +struct VtkWriteFractureVolumeFraction { static constexpr bool value = true; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup Vtk + * + * \brief VTK output module for quantities which make sense for all + * models which deal with discrete fractures in porous media. + * + * This module deals with the following quantities: + * - Saturations of all fluid phases in the fracture + * - Mobilities of all fluid phases in the fracture + * - Relative permeabilities of all fluid phases in the fracture + * - Porosity of the medium in the fracture + * - Norm of the intrinsic permeability of the medium in the fracture + */ +template +class VtkDiscreteFractureModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using Scalar = GetPropType; + using ElementContext = GetPropType; + + using Vanguard = GetPropType; + using GridView = GetPropType; + using FluidSystem = GetPropType; + + using DiscBaseOutputModule = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = Opm::VtkMultiWriter; + + enum { dim = GridView::dimension }; + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = getPropValue() }; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + using PhaseBuffer = typename ParentType::PhaseBuffer; + using PhaseVectorBuffer = typename ParentType::PhaseVectorBuffer; + +public: + VtkDiscreteFractureModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the multi-phase VTK output module. + */ + static void registerParameters() + { + Parameters::Register + ("Include the phase saturations in the VTK output files"); + Parameters::Register + ("Include the phase mobilities in the VTK output files"); + Parameters::Register + ("Include the phase relative permeabilities in the " + "VTK output files"); + Parameters::Register + ("Include the porosity in the VTK output files"); + Parameters::Register + ("Include the intrinsic permeability in the VTK output files"); + Parameters::Register + ("Include in the filter velocities of the phases in the VTK output files"); + Parameters::Register + ("Add the fraction of the total volume which is " + "occupied by fractures in the VTK output"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (saturationOutput_()) + this->resizePhaseBuffer_(fractureSaturation_); + if (mobilityOutput_()) + this->resizePhaseBuffer_(fractureMobility_); + if (relativePermeabilityOutput_()) + this->resizePhaseBuffer_(fractureRelativePermeability_); + + if (porosityOutput_()) + this->resizeScalarBuffer_(fracturePorosity_); + if (intrinsicPermeabilityOutput_()) + this->resizeScalarBuffer_(fractureIntrinsicPermeability_); + if (volumeFractionOutput_()) + this->resizeScalarBuffer_(fractureVolumeFraction_); + + if (velocityOutput_()) { + size_t nDof = this->simulator_.model().numGridDof(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + fractureVelocity_[phaseIdx].resize(nDof); + for (unsigned dofIdx = 0; dofIdx < nDof; ++dofIdx) { + fractureVelocity_[phaseIdx][dofIdx].resize(dimWorld); + fractureVelocity_[phaseIdx][dofIdx] = 0.0; + } + } + this->resizePhaseBuffer_(fractureVelocityWeight_); + } + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant for + * an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + const auto& fractureMapper = elemCtx.simulator().vanguard().fractureMapper(); + + for (unsigned i = 0; i < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++i) { + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + if (!fractureMapper.isFractureVertex(I)) + continue; + + const auto& intQuants = elemCtx.intensiveQuantities(i, /*timeIdx=*/0); + const auto& fs = intQuants.fractureFluidState(); + + if (porosityOutput_()) { + Opm::Valgrind::CheckDefined(intQuants.fracturePorosity()); + fracturePorosity_[I] = intQuants.fracturePorosity(); + } + if (intrinsicPermeabilityOutput_()) { + const auto& K = intQuants.fractureIntrinsicPermeability(); + fractureIntrinsicPermeability_[I] = K[0][0]; + } + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (saturationOutput_()) { + Opm::Valgrind::CheckDefined(fs.saturation(phaseIdx)); + fractureSaturation_[phaseIdx][I] = fs.saturation(phaseIdx); + } + if (mobilityOutput_()) { + Opm::Valgrind::CheckDefined(intQuants.fractureMobility(phaseIdx)); + fractureMobility_[phaseIdx][I] = intQuants.fractureMobility(phaseIdx); + } + if (relativePermeabilityOutput_()) { + Opm::Valgrind::CheckDefined(intQuants.fractureRelativePermeability(phaseIdx)); + fractureRelativePermeability_[phaseIdx][I] = + intQuants.fractureRelativePermeability(phaseIdx); + } + if (volumeFractionOutput_()) { + Opm::Valgrind::CheckDefined(intQuants.fractureVolume()); + fractureVolumeFraction_[I] += intQuants.fractureVolume(); + } + } + } + + if (velocityOutput_()) { + // calculate velocities if requested by the simulator + for (unsigned scvfIdx = 0; scvfIdx < elemCtx.numInteriorFaces(/*timeIdx=*/0); ++ scvfIdx) { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx=*/0); + + unsigned i = extQuants.interiorIndex(); + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + + unsigned j = extQuants.exteriorIndex(); + unsigned J = elemCtx.globalSpaceIndex(j, /*timeIdx=*/0); + + if (!fractureMapper.isFractureEdge(I, J)) + continue; + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar weight = + std::max(1e-16, std::abs(extQuants.fractureVolumeFlux(phaseIdx))); + Opm::Valgrind::CheckDefined(extQuants.extrusionFactor()); + assert(extQuants.extrusionFactor() > 0); + weight *= extQuants.extrusionFactor(); + + Dune::FieldVector v(extQuants.fractureFilterVelocity(phaseIdx)); + v *= weight; + + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) { + fractureVelocity_[phaseIdx][I][dimIdx] += v[dimIdx]; + fractureVelocity_[phaseIdx][J][dimIdx] += v[dimIdx]; + } + + fractureVelocityWeight_[phaseIdx][I] += weight; + fractureVelocityWeight_[phaseIdx][J] += weight; + } + } + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) { + return; + } + + if (saturationOutput_()) + this->commitPhaseBuffer_(baseWriter, "fractureSaturation_%s", fractureSaturation_); + if (mobilityOutput_()) + this->commitPhaseBuffer_(baseWriter, "fractureMobility_%s", fractureMobility_); + if (relativePermeabilityOutput_()) + this->commitPhaseBuffer_(baseWriter, "fractureRelativePerm_%s", fractureRelativePermeability_); + + if (porosityOutput_()) + this->commitScalarBuffer_(baseWriter, "fracturePorosity", fracturePorosity_); + if (intrinsicPermeabilityOutput_()) + this->commitScalarBuffer_(baseWriter, "fractureIntrinsicPerm", fractureIntrinsicPermeability_); + if (volumeFractionOutput_()) { + // divide the fracture volume by the total volume of the finite volumes + for (unsigned I = 0; I < fractureVolumeFraction_.size(); ++I) + fractureVolumeFraction_[I] /= this->simulator_.model().dofTotalVolume(I); + this->commitScalarBuffer_(baseWriter, "fractureVolumeFraction", fractureVolumeFraction_); + } + + if (velocityOutput_()) { + size_t nDof = this->simulator_.model().numGridDof(); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // first, divide the velocity field by the + // respective finite volume's surface area + for (unsigned dofIdx = 0; dofIdx < nDof; ++dofIdx) + fractureVelocity_[phaseIdx][dofIdx] /= + std::max(1e-20, fractureVelocityWeight_[phaseIdx][dofIdx]); + // commit the phase velocity + char name[512]; + snprintf(name, 512, "fractureFilterVelocity_%s", FluidSystem::phaseName(phaseIdx).data()); + + DiscBaseOutputModule::attachVectorDofData_(baseWriter, fractureVelocity_[phaseIdx], name); + } + } + } + +private: + static bool saturationOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool mobilityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool relativePermeabilityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool porosityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool intrinsicPermeabilityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool volumeFractionOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool velocityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + PhaseBuffer fractureSaturation_; + PhaseBuffer fractureMobility_; + PhaseBuffer fractureRelativePermeability_; + + ScalarBuffer fracturePorosity_; + ScalarBuffer fractureVolumeFraction_; + ScalarBuffer fractureIntrinsicPermeability_; + + PhaseVectorBuffer fractureVelocity_; + PhaseBuffer fractureVelocityWeight_; + + PhaseVectorBuffer potentialGradient_; + PhaseBuffer potentialWeight_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkenergymodule.hh b/opm/models/io/vtkenergymodule.hh new file mode 100644 index 00000000000..414d545cee5 --- /dev/null +++ b/opm/models/io/vtkenergymodule.hh @@ -0,0 +1,210 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkEnergyModule + */ +#ifndef EWOMS_VTK_ENERGY_MODULE_HH +#define EWOMS_VTK_ENERGY_MODULE_HH + +#include + +#include +#include + +#include + +#include +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteSolidInternalEnergy { static constexpr bool value = false; }; +struct VtkWriteThermalConductivity { static constexpr bool value = false; }; +struct VtkWriteInternalEnergies { static constexpr bool value = false; }; +struct VtkWriteEnthalpies { static constexpr bool value = false; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup Vtk + * + * \brief VTK output module for quantities which make sense for models which + * assume thermal equilibrium. + * + * This module deals with the following quantities: + * - Specific enthalpy of all fluid phases + * - Specific internal energy of all fluid phases + * - Volumetric internal energy of the solid phase + * - Total thermal conductivity, i.e. the conductivity of the solid and all fluid phases + * combined + */ +template +class VtkEnergyModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + using GridView = GetPropType; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + using PhaseBuffer = typename ParentType::PhaseBuffer; + + static const int vtkFormat = getPropValue(); + enum { numPhases = getPropValue() }; + + using Toolbox = typename Opm::MathToolbox; + using VtkMultiWriter = Opm::VtkMultiWriter; + +public: + VtkEnergyModule(const Simulator& simulator) + : ParentType(simulator) + { + } + + /*! + * \brief Register all run-time parameters for the Vtk output module. + */ + static void registerParameters() + { + Parameters::Register + ("Include the volumetric internal energy of solid" + "matrix in the VTK output files"); + Parameters::Register + ("Include the total thermal conductivity of the" + "medium in the VTK output files"); + Parameters::Register + ("Include the specific enthalpy of the phases in " + "the VTK output files"); + Parameters::Register + ("Include the specific internal energy of the " + "phases in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (enthalpyOutput_()) + this->resizePhaseBuffer_(enthalpy_); + if (internalEnergyOutput_()) + this->resizePhaseBuffer_(internalEnergy_); + + if (solidInternalEnergyOutput_()) + this->resizeScalarBuffer_(solidInternalEnergy_); + if (thermalConductivityOutput_()) + this->resizeScalarBuffer_(thermalConductivity_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quanties relevant + * for an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + for (unsigned i = 0; i < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++i) { + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + const auto& intQuants = elemCtx.intensiveQuantities(i, /*timeIdx=*/0); + const auto& fs = intQuants.fluidState(); + + if (solidInternalEnergyOutput_()) + solidInternalEnergy_[I] = Toolbox::value(intQuants.solidInternalEnergy()); + if (thermalConductivityOutput_()) + thermalConductivity_[I] = Toolbox::value(intQuants.thermalConductivity()); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (enthalpyOutput_()) + enthalpy_[phaseIdx][I] = Toolbox::value(fs.enthalpy(phaseIdx)); + if (internalEnergyOutput_()) + internalEnergy_[phaseIdx][I] = Toolbox::value(fs.internalEnergy(phaseIdx)); + } + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) { + return; + } + + if (solidInternalEnergyOutput_()) + this->commitScalarBuffer_(baseWriter, "internalEnergySolid", solidInternalEnergy_); + if (thermalConductivityOutput_()) + this->commitScalarBuffer_(baseWriter, "thermalConductivity", thermalConductivity_); + + if (enthalpyOutput_()) + this->commitPhaseBuffer_(baseWriter, "enthalpy_%s", enthalpy_); + if (internalEnergyOutput_()) + this->commitPhaseBuffer_(baseWriter, "internalEnergy_%s", internalEnergy_); + } + +private: + static bool solidInternalEnergyOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool thermalConductivityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool enthalpyOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool internalEnergyOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + PhaseBuffer enthalpy_; + PhaseBuffer internalEnergy_; + + ScalarBuffer thermalConductivity_; + ScalarBuffer solidInternalEnergy_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkmultiphasemodule.hh b/opm/models/io/vtkmultiphasemodule.hh new file mode 100644 index 00000000000..b5e86b5e51c --- /dev/null +++ b/opm/models/io/vtkmultiphasemodule.hh @@ -0,0 +1,469 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkMultiPhaseModule + */ +#ifndef EWOMS_VTK_MULTI_PHASE_MODULE_HH +#define EWOMS_VTK_MULTI_PHASE_MODULE_HH + +#include + +#include +#include + +#include + +#include +#include + +#include +#include + +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteExtrusionFactor { static constexpr bool value = false; }; +struct VtkWritePressures { static constexpr bool value = true; }; +struct VtkWriteDensities { static constexpr bool value = true; }; +struct VtkWriteSaturations { static constexpr bool value = true; }; +struct VtkWriteMobilities { static constexpr bool value = false; }; +struct VtkWriteRelativePermeabilities { static constexpr bool value = true; }; +struct VtkWriteViscosities { static constexpr bool value = false; }; +struct VtkWriteAverageMolarMasses { static constexpr bool value = false; }; +struct VtkWritePorosity { static constexpr bool value = true; }; +struct VtkWriteIntrinsicPermeabilities { static constexpr bool value = false; }; +struct VtkWritePotentialGradients { static constexpr bool value = false; }; +struct VtkWriteFilterVelocities { static constexpr bool value = false; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! + * \ingroup Vtk + * + * \brief VTK output module for quantities which make sense for all + * models which deal with multiple fluid phases in porous media + * that don't use flashy concepts like interfacial area. + * + * This module deals with the following quantities: + * - Pressures of all fluid phases + * - Densities of all fluid phases + * - Saturations of all fluid phases + * - Mobilities of all fluid phases + * - Relative permeabilities of all fluid phases + * - Viscosities of all fluid phases + * - Average molar masses of all fluid phases + * - Porosity of the medium + * - Norm of the intrinsic permeability of the medium + */ +template +class VtkMultiPhaseModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using Scalar = GetPropType; + using ElementContext = GetPropType; + + using GridView = GetPropType; + using FluidSystem = GetPropType; + using DiscBaseOutputModule = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + enum { dimWorld = GridView::dimensionworld }; + enum { numPhases = getPropValue() }; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + using VectorBuffer = typename ParentType::VectorBuffer; + using TensorBuffer = typename ParentType::TensorBuffer; + using PhaseBuffer = typename ParentType::PhaseBuffer; + + using DimVector = Dune::FieldVector; + + using PhaseVectorBuffer = std::array; + +public: + VtkMultiPhaseModule(const Simulator& simulator) + : ParentType(simulator) + {} + + /*! + * \brief Register all run-time parameters for the multi-phase VTK output module. + */ + static void registerParameters() + { + Parameters::Register + ("Include the extrusion factor of the degrees of freedom into the VTK output files"); + Parameters::Register + ("Include the phase pressures in the VTK output files"); + Parameters::Register + ("Include the phase densities in the VTK output files"); + Parameters::Register + ("Include the phase saturations in the VTK output files"); + Parameters::Register + ("Include the phase mobilities in the VTK output files"); + Parameters::Register + ("Include the phase relative permeabilities in the VTK output files"); + Parameters::Register + ("Include component phase viscosities in the VTK output files"); + Parameters::Register + ("Include the average phase mass in the VTK output files"); + Parameters::Register + ("Include the porosity in the VTK output files"); + Parameters::Register + ("Include the intrinsic permeability in the VTK output files"); + Parameters::Register + ("Include in the filter velocities of the phases the VTK output files"); + Parameters::Register + ("Include the phase pressure potential gradients in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (extrusionFactorOutput_()) this->resizeScalarBuffer_(extrusionFactor_); + if (pressureOutput_()) this->resizePhaseBuffer_(pressure_); + if (densityOutput_()) this->resizePhaseBuffer_(density_); + if (saturationOutput_()) this->resizePhaseBuffer_(saturation_); + if (mobilityOutput_()) this->resizePhaseBuffer_(mobility_); + if (relativePermeabilityOutput_()) this->resizePhaseBuffer_(relativePermeability_); + if (viscosityOutput_()) this->resizePhaseBuffer_(viscosity_); + if (averageMolarMassOutput_()) this->resizePhaseBuffer_(averageMolarMass_); + + if (porosityOutput_()) this->resizeScalarBuffer_(porosity_); + if (intrinsicPermeabilityOutput_()) this->resizeTensorBuffer_(intrinsicPermeability_); + + if (velocityOutput_()) { + size_t nDof = this->simulator_.model().numGridDof(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + velocity_[phaseIdx].resize(nDof); + for (unsigned dofIdx = 0; dofIdx < nDof; ++ dofIdx) { + velocity_[phaseIdx][dofIdx].resize(dimWorld); + velocity_[phaseIdx][dofIdx] = 0.0; + } + } + this->resizePhaseBuffer_(velocityWeight_); + } + + if (potentialGradientOutput_()) { + size_t nDof = this->simulator_.model().numGridDof(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) { + potentialGradient_[phaseIdx].resize(nDof); + for (unsigned dofIdx = 0; dofIdx < nDof; ++ dofIdx) { + potentialGradient_[phaseIdx][dofIdx].resize(dimWorld); + potentialGradient_[phaseIdx][dofIdx] = 0.0; + } + } + + this->resizePhaseBuffer_(potentialWeight_); + } + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities seen on + * an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + const auto& problem = elemCtx.problem(); + for (unsigned i = 0; i < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++i) { + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + const auto& intQuants = elemCtx.intensiveQuantities(i, /*timeIdx=*/0); + const auto& fs = intQuants.fluidState(); + + if (extrusionFactorOutput_()) extrusionFactor_[I] = intQuants.extrusionFactor(); + if (porosityOutput_()) porosity_[I] = getValue(intQuants.porosity()); + + if (intrinsicPermeabilityOutput_()) { + const auto& K = problem.intrinsicPermeability(elemCtx, i, /*timeIdx=*/0); + for (unsigned rowIdx = 0; rowIdx < K.rows; ++rowIdx) + for (unsigned colIdx = 0; colIdx < K.cols; ++colIdx) + intrinsicPermeability_[I][rowIdx][colIdx] = K[rowIdx][colIdx]; + } + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) { + continue; + } + if (pressureOutput_()) + pressure_[phaseIdx][I] = getValue(fs.pressure(phaseIdx)); + if (densityOutput_()) + density_[phaseIdx][I] = getValue(fs.density(phaseIdx)); + if (saturationOutput_()) + saturation_[phaseIdx][I] = getValue(fs.saturation(phaseIdx)); + if (mobilityOutput_()) + mobility_[phaseIdx][I] = getValue(intQuants.mobility(phaseIdx)); + if (relativePermeabilityOutput_()) + relativePermeability_[phaseIdx][I] = getValue(intQuants.relativePermeability(phaseIdx)); + if (viscosityOutput_()) + viscosity_[phaseIdx][I] = getValue(fs.viscosity(phaseIdx)); + if (averageMolarMassOutput_()) + averageMolarMass_[phaseIdx][I] = getValue(fs.averageMolarMass(phaseIdx)); + } + } + + if (potentialGradientOutput_()) { + // calculate velocities if requested + for (unsigned faceIdx = 0; faceIdx < elemCtx.numInteriorFaces(/*timeIdx=*/0); ++ faceIdx) { + const auto& extQuants = elemCtx.extensiveQuantities(faceIdx, /*timeIdx=*/0); + + unsigned i = extQuants.interiorIndex(); + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar weight = extQuants.extrusionFactor(); + + potentialWeight_[phaseIdx][I] += weight; + + const auto& inputPGrad = extQuants.potentialGrad(phaseIdx); + DimVector pGrad; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) + pGrad[dimIdx] = getValue(inputPGrad[dimIdx])*weight; + potentialGradient_[phaseIdx][I] += pGrad; + } // end for all phases + } // end for all faces + } + + if (velocityOutput_()) { + // calculate velocities if requested + for (unsigned faceIdx = 0; faceIdx < elemCtx.numInteriorFaces(/*timeIdx=*/0); ++ faceIdx) { + const auto& extQuants = elemCtx.extensiveQuantities(faceIdx, /*timeIdx=*/0); + + unsigned i = extQuants.interiorIndex(); + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + + unsigned j = extQuants.exteriorIndex(); + unsigned J = elemCtx.globalSpaceIndex(j, /*timeIdx=*/0); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar weight = std::max(1e-16, + std::abs(getValue(extQuants.volumeFlux(phaseIdx)))); + Valgrind::CheckDefined(extQuants.extrusionFactor()); + assert(extQuants.extrusionFactor() > 0); + weight *= extQuants.extrusionFactor(); + + const auto& inputV = extQuants.filterVelocity(phaseIdx); + DimVector v; + for (unsigned k = 0; k < dimWorld; ++k) + v[k] = getValue(inputV[k]); + if (v.two_norm() > 1e-20) + weight /= v.two_norm(); + v *= weight; + + velocity_[phaseIdx][I] += v; + velocity_[phaseIdx][J] += v; + + velocityWeight_[phaseIdx][I] += weight; + velocityWeight_[phaseIdx][J] += weight; + } // end for all phases + } // end for all faces + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) + return; + + if (extrusionFactorOutput_()) + this->commitScalarBuffer_(baseWriter, "extrusionFactor", extrusionFactor_); + if (pressureOutput_()) + this->commitPhaseBuffer_(baseWriter, "pressure_%s", pressure_); + if (densityOutput_()) + this->commitPhaseBuffer_(baseWriter, "density_%s", density_); + if (saturationOutput_()) + this->commitPhaseBuffer_(baseWriter, "saturation_%s", saturation_); + if (mobilityOutput_()) + this->commitPhaseBuffer_(baseWriter, "mobility_%s", mobility_); + if (relativePermeabilityOutput_()) + this->commitPhaseBuffer_(baseWriter, "relativePerm_%s", relativePermeability_); + if (viscosityOutput_()) + this->commitPhaseBuffer_(baseWriter, "viscosity_%s", viscosity_); + if (averageMolarMassOutput_()) + this->commitPhaseBuffer_(baseWriter, "averageMolarMass_%s", averageMolarMass_); + + if (porosityOutput_()) + this->commitScalarBuffer_(baseWriter, "porosity", porosity_); + if (intrinsicPermeabilityOutput_()) + this->commitTensorBuffer_(baseWriter, "intrinsicPerm", intrinsicPermeability_); + + if (velocityOutput_()) { + size_t numDof = this->simulator_.model().numGridDof(); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // first, divide the velocity field by the + // respective finite volume's surface area + for (unsigned i = 0; i < numDof; ++i) + velocity_[phaseIdx][i] /= velocityWeight_[phaseIdx][i]; + // commit the phase velocity + char name[512]; + snprintf(name, 512, "filterVelocity_%s", FluidSystem::phaseName(phaseIdx).data()); + + DiscBaseOutputModule::attachVectorDofData_(baseWriter, velocity_[phaseIdx], name); + } + } + + if (potentialGradientOutput_()) { + size_t numDof = this->simulator_.model().numGridDof(); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // first, divide the velocity field by the + // respective finite volume's surface area + for (unsigned i = 0; i < numDof; ++i) + potentialGradient_[phaseIdx][i] /= potentialWeight_[phaseIdx][i]; + // commit the phase velocity + char name[512]; + snprintf(name, 512, "gradP_%s", FluidSystem::phaseName(phaseIdx).data()); + + DiscBaseOutputModule::attachVectorDofData_(baseWriter, + potentialGradient_[phaseIdx], + name); + } + } + } + + /*! + * \brief Returns true iff the module needs to access the extensive quantities of a + * context to do its job. + * + * For example, this happens if velocities or gradients should be written. Always + * returning true here does not do any harm from the correctness perspective, but it + * slows down writing the output fields. + */ + virtual bool needExtensiveQuantities() const final + { + return velocityOutput_() || potentialGradientOutput_(); + } + +private: + static bool extrusionFactorOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool pressureOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool densityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool saturationOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool mobilityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool relativePermeabilityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool viscosityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool averageMolarMassOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool porosityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool intrinsicPermeabilityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool velocityOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool potentialGradientOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + ScalarBuffer extrusionFactor_; + PhaseBuffer pressure_; + PhaseBuffer density_; + PhaseBuffer saturation_; + PhaseBuffer mobility_; + PhaseBuffer relativePermeability_; + PhaseBuffer viscosity_; + PhaseBuffer averageMolarMass_; + + ScalarBuffer porosity_; + TensorBuffer intrinsicPermeability_; + + PhaseVectorBuffer velocity_; + PhaseBuffer velocityWeight_; + + PhaseVectorBuffer potentialGradient_; + PhaseBuffer potentialWeight_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkmultiwriter.hh b/opm/models/io/vtkmultiwriter.hh new file mode 100644 index 00000000000..1a1827a0f6f --- /dev/null +++ b/opm/models/io/vtkmultiwriter.hh @@ -0,0 +1,570 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::VtkMultiWriter + */ +#ifndef EWOMS_VTK_MULTI_WRITER_HH +#define EWOMS_VTK_MULTI_WRITER_HH + +#include "vtkscalarfunction.hh" +#include "vtkvectorfunction.hh" +#include "vtktensorfunction.hh" + +#include +#include + +#include + +#include +#include +#include +#include + +#if HAVE_MPI +#include +#endif + +#include +#include +#include +#include +#include +#include + +namespace Opm { +/*! + * \brief Simplifies writing multi-file VTK datasets. + * + * This class automatically keeps the meta file up to date and + * simplifies writing datasets consisting of multiple files. (i.e. + * multiple time steps or grid refinements within a time step.) + */ +template +class VtkMultiWriter : public BaseOutputWriter +{ + class WriteDataTasklet : public TaskletInterface + { + public: + WriteDataTasklet(VtkMultiWriter& multiWriter) + : multiWriter_(multiWriter) + { } + + void run() final + { + std::string fileName; + // write the actual data as vtu or vtp (plus the pieces file in the parallel case) + if (multiWriter_.commSize_ > 1) + fileName = multiWriter_.curWriter_->pwrite(/*name=*/multiWriter_.curOutFileName_, + /*path=*/multiWriter_.outputDir_, + /*extendPath=*/"", + static_cast(vtkFormat)); + else + fileName = multiWriter_.curWriter_->write(/*name=*/multiWriter_.outputDir_ + "/" + multiWriter_.curOutFileName_, + static_cast(vtkFormat)); + + // determine name to write into the multi-file for the + // current time step + // The file names in the pvd file are relative, the path should therefore be stripped. + const std::filesystem::path fullPath{fileName}; + const std::string localFileName = fullPath.filename(); + multiWriter_.multiFile_.precision(16); + multiWriter_.multiFile_ << " \n"; + } + + private: + VtkMultiWriter& multiWriter_; + }; + + enum { dim = GridView::dimension }; + + using VertexMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + using ElementMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + +public: + using Scalar = BaseOutputWriter::Scalar; + using Vector = BaseOutputWriter::Vector; + using Tensor = BaseOutputWriter::Tensor; + using ScalarBuffer = BaseOutputWriter::ScalarBuffer; + using VectorBuffer = BaseOutputWriter::VectorBuffer; + using TensorBuffer = BaseOutputWriter::TensorBuffer; + + using VtkWriter = Dune::VTKWriter; + using FunctionPtr = std::shared_ptr< Dune::VTKFunction< GridView > >; + + VtkMultiWriter(bool asyncWriting, + const GridView& gridView, + const std::string& outputDir, + const std::string& simName = "", + std::string multiFileName = "") + : gridView_(gridView) + , elementMapper_(gridView, Dune::mcmgElementLayout()) + , vertexMapper_(gridView, Dune::mcmgVertexLayout()) + , curWriter_(nullptr) + , curWriterNum_(0) + , taskletRunner_(/*numThreads=*/asyncWriting?1:0) + { + outputDir_ = outputDir; + if (outputDir == "") + outputDir_ = "."; + + simName_ = (simName.empty()) ? "sim" : simName; + multiFileName_ = multiFileName; + if (multiFileName_.empty()) + multiFileName_ = outputDir_+"/"+simName_+".pvd"; + + commRank_ = gridView.comm().rank(); + commSize_ = gridView.comm().size(); + } + + ~VtkMultiWriter() + { + taskletRunner_.barrier(); + releaseBuffers_(); + finishMultiFile_(); + + if (commRank_ == 0) + multiFile_.close(); + } + + /*! + * \brief Returns the number of the current VTK file. + */ + int curWriterNum() const + { return curWriterNum_; } + + /*! + * \brief Updates the internal data structures after mesh + * refinement. + * + * If the grid changes between two calls of beginWrite(), this + * method _must_ be called before the second beginWrite()! + */ + void gridChanged() + { +#if DUNE_VERSION_NEWER(DUNE_GRID, 2, 8) + elementMapper_.update(gridView_); + vertexMapper_.update(gridView_); +#else + elementMapper_.update(); + vertexMapper_.update(); +#endif + } + + /*! + * \brief Called whenever a new time step must be written. + */ + void beginWrite(double t) + { + if (!multiFile_.is_open()) { + startMultiFile_(multiFileName_); + } + + // make sure that all previous output has been written and no other thread + // accesses the memory used as the target for the extracted quantities + taskletRunner_.barrier(); + releaseBuffers_(); + + curTime_ = t; + curOutFileName_ = fileName_(); + + curWriter_ = new VtkWriter(gridView_, Dune::VTK::conforming); + ++curWriterNum_; + } + + /*! + * \brief Allocate a managed buffer for a scalar field + * + * The buffer will be deleted automatically after the data has + * been written by to disk. + */ + ScalarBuffer *allocateManagedScalarBuffer(size_t numEntities) + { + ScalarBuffer *buf = new ScalarBuffer(numEntities); + managedScalarBuffers_.push_back(buf); + return buf; + } + + /*! + * \brief Allocate a managed buffer for a vector field + * + * The buffer will be deleted automatically after the data has + * been written by to disk. + */ + VectorBuffer *allocateManagedVectorBuffer(size_t numOuter, size_t numInner) + { + VectorBuffer *buf = new VectorBuffer(numOuter); + for (size_t i = 0; i < numOuter; ++ i) + (*buf)[i].resize(numInner); + + managedVectorBuffers_.push_back(buf); + return buf; + } + + /*! + * \brief Add a finished vertex centered vector field to the + * output. + * + * If the buffer is managed by the VtkMultiWriter, it must have + * been created using allocateManagedBuffer() and may not be used + * anywhere after calling this method. After the data is written + * to disk, it will be deleted automatically. + * + * If the buffer is not managed by the MultiWriter, the buffer + * must exist at least until the call to endWrite() + * finishes. + * + * In both cases, modifying the buffer between the call to this + * method and endWrite() results in _undefined behavior_. + */ + void attachScalarVertexData(ScalarBuffer& buf, std::string name) + { + sanitizeScalarBuffer_(buf); + + using VtkFn = VtkScalarFunction; + FunctionPtr fnPtr(new VtkFn(name, + gridView_, + vertexMapper_, + buf, + /*codim=*/dim)); + curWriter_->addVertexData(fnPtr); + } + + /*! + * \brief Add a element centered quantity to the output. + * + * If the buffer is managed by the VtkMultiWriter, it must have + * been created using createField() and may not be used by + * anywhere after calling this method. After the data is written + * to disk, it will be deleted automatically. + * + * If the buffer is not managed by the MultiWriter, the buffer + * must exist at least until the call to endWrite() + * finishes. + * + * In both cases, modifying the buffer between the call to this + * method and endWrite() results in _undefined behaviour_. + */ + void attachScalarElementData(ScalarBuffer& buf, std::string name) + { + sanitizeScalarBuffer_(buf); + + using VtkFn = VtkScalarFunction; + FunctionPtr fnPtr(new VtkFn(name, + gridView_, + elementMapper_, + buf, + /*codim=*/0)); + curWriter_->addCellData(fnPtr); + } + + /*! + * \brief Add a finished vertex centered vector field to the + * output. + * + * If the buffer is managed by the VtkMultiWriter, it must have + * been created using allocateManagedBuffer() and may not be used + * anywhere after calling this method. After the data is written + * to disk, it will be deleted automatically. + * + * If the buffer is not managed by the MultiWriter, the buffer + * must exist at least until the call to endWrite() + * finishes. + * + * In both cases, modifying the buffer between the call to this + * method and endWrite() results in _undefined behavior_. + */ + void attachVectorVertexData(VectorBuffer& buf, std::string name) + { + sanitizeVectorBuffer_(buf); + + using VtkFn = VtkVectorFunction; + FunctionPtr fnPtr(new VtkFn(name, + gridView_, + vertexMapper_, + buf, + /*codim=*/dim)); + curWriter_->addVertexData(fnPtr); + } + + /*! + * \brief Add a finished vertex-centered tensor field to the output. + */ + void attachTensorVertexData(TensorBuffer& buf, std::string name) + { + using VtkFn = VtkTensorFunction; + + for (unsigned colIdx = 0; colIdx < buf[0].N(); ++colIdx) { + std::ostringstream oss; + oss << name << "[" << colIdx << "]"; + + FunctionPtr fnPtr(new VtkFn(oss.str(), + gridView_, + vertexMapper_, + buf, + /*codim=*/dim, + colIdx)); + curWriter_->addVertexData(fnPtr); + } + } + + /*! + * \brief Add a element centered quantity to the output. + * + * If the buffer is managed by the VtkMultiWriter, it must have + * been created using createField() and may not be used by + * anywhere after calling this method. After the data is written + * to disk, it will be deleted automatically. + * + * If the buffer is not managed by the MultiWriter, the buffer + * must exist at least until the call to endWrite() + * finishes. + * + * In both cases, modifying the buffer between the call to this + * method and endWrite() results in _undefined behaviour_. + */ + void attachVectorElementData(VectorBuffer& buf, std::string name) + { + sanitizeVectorBuffer_(buf); + + using VtkFn = VtkVectorFunction; + FunctionPtr fnPtr(new VtkFn(name, + gridView_, + elementMapper_, + buf, + /*codim=*/0)); + curWriter_->addCellData(fnPtr); + } + + /*! + * \brief Add a finished element-centered tensor field to the output. + */ + void attachTensorElementData(TensorBuffer& buf, std::string name) + { + using VtkFn = VtkTensorFunction; + + for (unsigned colIdx = 0; colIdx < buf[0].N(); ++colIdx) { + std::ostringstream oss; + oss << name << "[" << colIdx << "]"; + + FunctionPtr fnPtr(new VtkFn(oss.str(), + gridView_, + elementMapper_, + buf, + /*codim=*/0, + colIdx)); + curWriter_->addCellData(fnPtr); + } + } + + /*! + * \brief Finalizes the current writer. + * + * This means that everything will be written to disk, except if + * the onlyDiscard argument is true. In this case only all managed + * buffers are deleted, but no output is written. + */ + void endWrite(bool onlyDiscard = false) + { + if (!onlyDiscard) { + auto tasklet = std::make_shared(*this); + taskletRunner_.dispatch(tasklet); + } + else + --curWriterNum_; + + // temporarily write the closing XML mumbo-jumbo to the mashup + // file so that the data set can be loaded even if the + // simulation is aborted (or not yet finished) + finishMultiFile_(); + } + + /*! + * \brief Write the multi-writer's state to a restart file. + */ + template + void serialize(Restarter& res) + { + res.serializeSectionBegin("VTKMultiWriter"); + res.serializeStream() << curWriterNum_ << "\n"; + + if (commRank_ == 0) { + std::streamsize fileLen = 0; + std::streamoff filePos = 0; + if (multiFile_.is_open()) { + // write the meta file into the restart file + filePos = multiFile_.tellp(); + multiFile_.seekp(0, std::ios::end); + fileLen = multiFile_.tellp(); + multiFile_.seekp(filePos); + } + + res.serializeStream() << fileLen << " " << filePos << "\n"; + + if (fileLen > 0) { + std::ifstream multiFileIn(multiFileName_.c_str()); + char *tmp = new char[fileLen]; + multiFileIn.read(tmp, static_cast(fileLen)); + res.serializeStream().write(tmp, fileLen); + delete[] tmp; + } + } + + res.serializeSectionEnd(); + } + + /*! + * \brief Read the multi-writer's state from a restart file. + */ + template + void deserialize(Restarter& res) + { + res.deserializeSectionBegin("VTKMultiWriter"); + res.deserializeStream() >> curWriterNum_; + + if (commRank_ == 0) { + std::string dummy; + std::getline(res.deserializeStream(), dummy); + + // recreate the meta file from the restart file + std::streamoff filePos; + std::streamsize fileLen; + res.deserializeStream() >> fileLen >> filePos; + std::getline(res.deserializeStream(), dummy); + if (multiFile_.is_open()) + multiFile_.close(); + + if (fileLen > 0) { + multiFile_.open(multiFileName_.c_str()); + + char *tmp = new char[fileLen]; + res.deserializeStream().read(tmp, fileLen); + multiFile_.write(tmp, fileLen); + delete[] tmp; + } + + multiFile_.seekp(filePos); + } + else { + std::string tmp; + std::getline(res.deserializeStream(), tmp); + } + res.deserializeSectionEnd(); + } + +private: + std::string fileName_() + { + // use a new file name for each time step + std::ostringstream oss; + oss << simName_ << "-" + << std::setw(5) << std::setfill('0') << curWriterNum_; + return oss.str(); + } + + std::string fileSuffix_() + { return (GridView::dimension == 1) ? "vtp" : "vtu"; } + + void startMultiFile_(const std::string& multiFileName) + { + // only the first process writes to the multi-file + if (commRank_ == 0) { + // generate one meta vtk-file holding the individual time steps + multiFile_.open(multiFileName.c_str()); + multiFile_ << "\n" + "\n" + " \n"; + } + } + + void finishMultiFile_() + { + // only the first process writes to the multi-file + if (commRank_ == 0) { + // make sure that we always have a working meta file + std::ofstream::pos_type pos = multiFile_.tellp(); + multiFile_ << " \n" + "\n"; + multiFile_.seekp(pos); + multiFile_.flush(); + } + } + + // make sure the field is well defined if running under valgrind + // and make sure that all values can be displayed by paraview + void sanitizeScalarBuffer_(ScalarBuffer&) + { + // nothing to do: this is done by VtkScalarFunction + } + + void sanitizeVectorBuffer_(VectorBuffer&) + { + // nothing to do: this is done by VtkVectorFunction + } + + // release the memory occupied by all buffer objects managed by the multi-writer + void releaseBuffers_() + { + // discard managed objects and the current VTK writer + delete curWriter_; + curWriter_ = nullptr; + while (managedScalarBuffers_.begin() != managedScalarBuffers_.end()) { + delete managedScalarBuffers_.front(); + managedScalarBuffers_.pop_front(); + } + while (managedVectorBuffers_.begin() != managedVectorBuffers_.end()) { + delete managedVectorBuffers_.front(); + managedVectorBuffers_.pop_front(); + } + } + + const GridView gridView_; + ElementMapper elementMapper_; + VertexMapper vertexMapper_; + + std::string outputDir_; + std::string simName_; + std::ofstream multiFile_; + std::string multiFileName_; + + int commSize_; // number of processes in the communicator + int commRank_; // rank of the current process in the communicator + + VtkWriter *curWriter_; + double curTime_; + std::string curOutFileName_; + int curWriterNum_; + + std::list managedScalarBuffers_; + std::list managedVectorBuffers_; + + TaskletRunner taskletRunner_; +}; +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkphasepresencemodule.hh b/opm/models/io/vtkphasepresencemodule.hh new file mode 100644 index 00000000000..225218438f3 --- /dev/null +++ b/opm/models/io/vtkphasepresencemodule.hh @@ -0,0 +1,136 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkPhasePresenceModule + */ +#ifndef EWOMS_VTK_PHASE_PRESENCE_MODULE_HH +#define EWOMS_VTK_PHASE_PRESENCE_MODULE_HH + +#include + +#include +#include + +#include +#include + +namespace Opm::Parameters { + +struct VtkWritePhasePresence { static constexpr bool value = false; }; + +} // namespace Opm::Parameters + +namespace Opm { +/*! + * \ingroup Vtk + * + * \brief VTK output module for the fluid composition + */ +template +class VtkPhasePresenceModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using Scalar = GetPropType; + using ElementContext = GetPropType; + using GridView = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = Opm::VtkMultiWriter; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + + +public: + VtkPhasePresenceModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the Vtk output module. + */ + static void registerParameters() + { + Parameters::Register + ("Include the phase presence pseudo primary " + "variable in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (phasePresenceOutput_()) this->resizeScalarBuffer_(phasePresence_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quanties relevant + * for an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + for (unsigned i = 0; i < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++i) { + // calculate the phase presence + int phasePresence = elemCtx.primaryVars(i, /*timeIdx=*/0).phasePresence(); + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + + if (phasePresenceOutput_()) + phasePresence_[I] = phasePresence; + } + } + + /*! + * \brief Add all buffers to the output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) { + return; + } + + if (phasePresenceOutput_()) + this->commitScalarBuffer_(baseWriter, "phase presence", phasePresence_); + } + +private: + static bool phasePresenceOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + ScalarBuffer phasePresence_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkprimaryvarsmodule.hh b/opm/models/io/vtkprimaryvarsmodule.hh new file mode 100644 index 00000000000..8cbd9aae500 --- /dev/null +++ b/opm/models/io/vtkprimaryvarsmodule.hh @@ -0,0 +1,177 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkPrimaryVarsModule + */ +#ifndef EWOMS_VTK_PRIMARY_VARS_MODULE_HH +#define EWOMS_VTK_PRIMARY_VARS_MODULE_HH + +#include + +#include +#include + +#include +#include + +namespace Opm::Parameters { + +struct VtkWritePrimaryVars { static constexpr bool value = false; }; +struct VtkWriteProcessRank { static constexpr bool value = false; }; +struct VtkWriteDofIndex { static constexpr bool value = false; }; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup Vtk + * + * \brief VTK output module for the fluid composition + */ +template +class VtkPrimaryVarsModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using ElementContext = GetPropType; + using GridView = GetPropType; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + using EqBuffer = typename ParentType::EqBuffer; + + enum { numEq = getPropValue() }; + +public: + VtkPrimaryVarsModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the Vtk output module. + */ + static void registerParameters() + { + Parameters::Register + ("Include the primary variables into the VTK output files"); + Parameters::Register + ("Include the MPI process rank into the VTK output files"); + Parameters::Register + ("Include the index of the degrees of freedom into the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (primaryVarsOutput_()) + this->resizeEqBuffer_(primaryVars_); + if (processRankOutput_()) + this->resizeScalarBuffer_(processRank_, + /*bufferType=*/ParentType::ElementBuffer); + if (dofIndexOutput_()) + this->resizeScalarBuffer_(dofIndex_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant for + * an element + */ + void processElement(const ElementContext& elemCtx) + { + if (!Parameters::Get()) { + return; + } + + const auto& elementMapper = elemCtx.model().elementMapper(); + unsigned elemIdx = static_cast(elementMapper.index(elemCtx.element())); + if (processRankOutput_() && !processRank_.empty()) + processRank_[elemIdx] = static_cast(this->simulator_.gridView().comm().rank()); + + for (unsigned i = 0; i < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++i) { + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + const auto& priVars = elemCtx.primaryVars(i, /*timeIdx=*/0); + + if (dofIndexOutput_()) + dofIndex_[I] = I; + + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + if (primaryVarsOutput_() && !primaryVars_[eqIdx].empty()) + primaryVars_[eqIdx][I] = priVars[eqIdx]; + } + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) { + return; + } + + if (primaryVarsOutput_()) + this->commitPriVarsBuffer_(baseWriter, "PV_%s", primaryVars_); + if (processRankOutput_()) + this->commitScalarBuffer_(baseWriter, + "process rank", + processRank_, + /*bufferType=*/ParentType::ElementBuffer); + if (dofIndexOutput_()) + this->commitScalarBuffer_(baseWriter, "DOF index", dofIndex_); + } + +private: + static bool primaryVarsOutput_() + { + static bool val = Parameters::Get(); + return val; + } + static bool processRankOutput_() + { + static bool val = Parameters::Get(); + return val; + } + static bool dofIndexOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + EqBuffer primaryVars_; + ScalarBuffer processRank_; + ScalarBuffer dofIndex_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkptflashmodule.hh b/opm/models/io/vtkptflashmodule.hh new file mode 100644 index 00000000000..cb25f1e0d25 --- /dev/null +++ b/opm/models/io/vtkptflashmodule.hh @@ -0,0 +1,169 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkPTFlashModule + */ +#ifndef OPM_VTK_PTFLASH_MODULE_HH +#define OPM_VTK_PTFLASH_MODULE_HH + +#include + +#include + +#include +#include + +#include +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteLiquidMoleFractions { static constexpr bool value = false; }; +struct VtkWriteEquilibriumConstants { static constexpr bool value = false; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! + * \ingroup Vtk + * + * \brief VTK output module for the PT Flash calculation + * This module deals with the following quantities: + * K, equilibrium ratio for all the components + * L, liquid fraction in the two-phase system + */ +template +class VtkPTFlashModule: public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ElementContext = GetPropType; + + using GridView = GetPropType; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + + using ComponentBuffer = typename ParentType::ComponentBuffer; + using ScalarBuffer = typename ParentType::ScalarBuffer; + +public: + explicit VtkPTFlashModule(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the Vtk output module. + */ + static void registerParameters() + { + Parameters::Register + ("Include liquid mole fractions (L) in the VTK output files"); + Parameters::Register + ("Include equilibrium constants (K) in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (LOutput_()) + this->resizeScalarBuffer_(L_); + if (equilConstOutput_()) + this->resizeComponentBuffer_(K_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant + * for an element + */ + void processElement(const ElementContext& elemCtx) + { + using Toolbox = MathToolbox; + + if (!Parameters::Get()) { + return; + } + + for (unsigned i = 0; i < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++i) { + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + const auto& intQuants = elemCtx.intensiveQuantities(i, /*timeIdx=*/0); + const auto& fs = intQuants.fluidState(); + + if (LOutput_()) + L_[I] = Toolbox::value(fs.L()); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + if (equilConstOutput_()) + K_[compIdx][I] = Toolbox::value(fs.K(compIdx)); + } + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + auto *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) { + return; + } + + if (equilConstOutput_()) + this->commitComponentBuffer_(baseWriter, "K^%s", K_); + if (LOutput_()) + this->commitScalarBuffer_(baseWriter, "L", L_); + } + +private: + static bool LOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + static bool equilConstOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + ComponentBuffer K_; + ScalarBuffer L_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkscalarfunction.hh b/opm/models/io/vtkscalarfunction.hh new file mode 100644 index 00000000000..1b0ff68f869 --- /dev/null +++ b/opm/models/io/vtkscalarfunction.hh @@ -0,0 +1,123 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkScalarFunction + */ +#ifndef VTK_SCALAR_FUNCTION_HH +#define VTK_SCALAR_FUNCTION_HH + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Opm { + +/*! + * \brief Provides a vector-valued function using Dune::FieldVectors + * as elements. + */ +template +class VtkScalarFunction : public Dune::VTKFunction +{ + enum { dim = GridView::dimension }; + using ctype = typename GridView::ctype; + using Element = typename GridView::template Codim<0>::Entity; + + using ScalarBuffer = BaseOutputWriter::ScalarBuffer; + +public: + VtkScalarFunction(std::string name, + const GridView& gridView, + const Mapper& mapper, + const ScalarBuffer& buf, + unsigned codim) + : name_(name) + , gridView_(gridView) + , mapper_(mapper) + , buf_(buf) + , codim_(codim) + { assert(int(buf_.size()) == int(mapper_.size())); } + + virtual std::string name() const + { return name_; } + + virtual int ncomps() const + { return 1; } + + virtual double evaluate(int, + const Element& e, + const Dune::FieldVector& xi) const + { + unsigned idx; + if (codim_ == 0) { + // cells. map element to the index + idx = static_cast(mapper_.index(e)); + } + else if (codim_ == dim) { + // find vertex which is closest to xi in local + // coordinates. This code is based on Dune::P1VTKFunction + double min = 1e100; + int imin = -1; + Dune::GeometryType gt = e.type(); + int n = static_cast(e.subEntities(dim)); + for (int i = 0; i < n; ++i) { + Dune::FieldVector local = + Dune::ReferenceElements::general(gt).position(i, dim); + + local -= xi; + if (local.infinity_norm() < min) { + min = local.infinity_norm(); + imin = static_cast(i); + } + } + + // map vertex to an index + idx = static_cast(mapper_.subIndex(e, imin, codim_)); + } + else + throw std::logic_error("Only element and vertex based vector fields are" + " supported so far."); + + return static_cast(static_cast(buf_[idx])); + } + +private: + const std::string name_; + const GridView gridView_; + const Mapper& mapper_; + const ScalarBuffer& buf_; + unsigned codim_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtktemperaturemodule.hh b/opm/models/io/vtktemperaturemodule.hh new file mode 100644 index 00000000000..96f25c9f00e --- /dev/null +++ b/opm/models/io/vtktemperaturemodule.hh @@ -0,0 +1,142 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkTemperatureModule + */ +#ifndef EWOMS_VTK_TEMPERATURE_MODULE_HH +#define EWOMS_VTK_TEMPERATURE_MODULE_HH + +#include + +#include + +#include +#include + +#include +#include + +namespace Opm::Parameters { + +// set default values for what quantities to output +struct VtkWriteTemperature { static constexpr bool value = true; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! + * \ingroup Vtk + * + * \brief VTK output module for the temperature in which assume + * thermal equilibrium + */ +template +class VtkTemperatureModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using Simulator = GetPropType; + using ElementContext = GetPropType; + + using GridView = GetPropType; + using Evaluation = GetPropType; + + using ScalarBuffer = typename ParentType::ScalarBuffer; + + static const int vtkFormat = getPropValue(); + using VtkMultiWriter = ::Opm::VtkMultiWriter; + +public: + VtkTemperatureModule(const Simulator& simulator) + : ParentType(simulator) + {} + + /*! + * \brief Register all run-time parameters for the Vtk output module. + */ + static void registerParameters() + { + Parameters::Register + ("Include the temperature in the VTK output files"); + } + + /*! + * \brief Allocate memory for the scalar fields we would like to + * write to the VTK file. + */ + void allocBuffers() + { + if (temperatureOutput_()) this->resizeScalarBuffer_(temperature_); + } + + /*! + * \brief Modify the internal buffers according to the intensive quantities relevant + * for an element + */ + void processElement(const ElementContext& elemCtx) + { + using Toolbox = MathToolbox; + + if (!Parameters::Get()) { + return; + } + + for (unsigned i = 0; i < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++i) { + unsigned I = elemCtx.globalSpaceIndex(i, /*timeIdx=*/0); + const auto& intQuants = elemCtx.intensiveQuantities(i, /*timeIdx=*/0); + const auto& fs = intQuants.fluidState(); + + if (temperatureOutput_()) + temperature_[I] = Toolbox::value(fs.temperature(/*phaseIdx=*/0)); + } + } + + /*! + * \brief Add all buffers to the VTK output writer. + */ + void commitBuffers(BaseOutputWriter& baseWriter) + { + VtkMultiWriter *vtkWriter = dynamic_cast(&baseWriter); + if (!vtkWriter) { + return; + } + + if (temperatureOutput_()) + this->commitScalarBuffer_(baseWriter, "temperature", temperature_); + } + +private: + static bool temperatureOutput_() + { + static bool val = Parameters::Get(); + return val; + } + + ScalarBuffer temperature_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtktensorfunction.hh b/opm/models/io/vtktensorfunction.hh new file mode 100644 index 00000000000..bd8852c029c --- /dev/null +++ b/opm/models/io/vtktensorfunction.hh @@ -0,0 +1,125 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkTensorFunction + */ +#ifndef VTK_TENSOR_FUNCTION_HH +#define VTK_TENSOR_FUNCTION_HH + +#include + +#include +#include +#include + +#include +#include +#include + +namespace Opm { + +/*! + * \brief Provides a tensor-valued function using Dune::FieldMatrix objects as elements. + */ +template +class VtkTensorFunction : public Dune::VTKFunction +{ + enum { dim = GridView::dimension }; + using ctype = typename GridView::ctype; + using Element = typename GridView::template Codim<0>::Entity; + + using TensorBuffer = BaseOutputWriter::TensorBuffer; + +public: + VtkTensorFunction(std::string name, + const GridView& gridView, + const Mapper& mapper, + const TensorBuffer& buf, + unsigned codim, + unsigned matrixColumnIdx) + : name_(name) + , gridView_(gridView) + , mapper_(mapper) + , buf_(buf) + , codim_(codim) + , matrixColumnIdx_(matrixColumnIdx) + { assert(int(buf_.size()) == int(mapper_.size())); } + + virtual std::string name() const + { return name_; } + + virtual int ncomps() const + { return static_cast(buf_[0].M()); } + + virtual double evaluate(int mycomp, + const Element& e, + const Dune::FieldVector& xi) const + { + size_t idx; + if (codim_ == 0) { + // cells. map element to the index + idx = static_cast(mapper_.index(e)); + } + else if (codim_ == dim) { + // find vertex which is closest to xi in local + // coordinates. This code is based on Dune::P1VTKFunction + double min = 1e100; + int imin = -1; + Dune::GeometryType gt = e.type(); + int n = static_cast(e.subEntities(dim)); + for (int i = 0; i < n; ++i) { + Dune::FieldVector local = + Dune::ReferenceElements::general(gt).position(i, dim); + + local -= xi; + if (local.infinity_norm() < min) { + min = local.infinity_norm(); + imin = i; + } + } + + // map vertex to an index + idx = static_cast(mapper_.subIndex(e, imin, codim_)); + } + else + throw std::logic_error("Only element and vertex based tensor fields are supported so far."); + + unsigned i = static_cast(mycomp); + unsigned j = static_cast(matrixColumnIdx_); + + return static_cast(static_cast(buf_[idx][i][j])); + } + +private: + const std::string name_; + const GridView gridView_; + const Mapper& mapper_; + const TensorBuffer& buf_; + unsigned codim_; + unsigned matrixColumnIdx_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/io/vtkvectorfunction.hh b/opm/models/io/vtkvectorfunction.hh new file mode 100644 index 00000000000..b4ab8dd7ead --- /dev/null +++ b/opm/models/io/vtkvectorfunction.hh @@ -0,0 +1,123 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::VtkVectorFunction + */ +#ifndef VTK_VECTOR_FUNCTION_HH +#define VTK_VECTOR_FUNCTION_HH + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Opm { + +/*! + * \brief Provides a vector-valued function using Dune::FieldVectors + * as elements. + */ +template +class VtkVectorFunction : public Dune::VTKFunction +{ + enum { dim = GridView::dimension }; + using ctype = typename GridView::ctype; + using Element = typename GridView::template Codim<0>::Entity; + + using VectorBuffer = BaseOutputWriter::VectorBuffer; + +public: + VtkVectorFunction(std::string name, + const GridView& gridView, + const Mapper& mapper, + const VectorBuffer& buf, + unsigned codim) + : name_(name) + , gridView_(gridView) + , mapper_(mapper) + , buf_(buf) + , codim_(codim) + { assert(int(buf_.size()) == int(mapper_.size())); } + + virtual std::string name() const + { return name_; } + + virtual int ncomps() const + { return static_cast(buf_[0].size()); } + + virtual double evaluate(int mycomp, + const Element& e, + const Dune::FieldVector& xi) const + { + unsigned idx; + if (codim_ == 0) { + // cells. map element to the index + idx = static_cast(mapper_.index(e)); + } + else if (codim_ == dim) { + // find vertex which is closest to xi in local + // coordinates. This code is based on Dune::P1VTKFunction + double min = 1e100; + int imin = -1; + Dune::GeometryType gt = e.type(); + int n = static_cast(e.subEntities(dim)); + for (int i = 0; i < n; ++i) { + Dune::FieldVector local = + Dune::ReferenceElements::general(gt).position(i, dim); + + local -= xi; + if (local.infinity_norm() < min) { + min = local.infinity_norm(); + imin = i; + } + } + + // map vertex to an index + idx = static_cast(mapper_.subIndex(e, imin, codim_)); + } + else + throw std::logic_error("Only element and vertex based vector fields are " + "supported so far."); + + return static_cast(static_cast(buf_[idx][static_cast(mycomp)])); + } + +private: + const std::string name_; + const GridView gridView_; + const Mapper& mapper_; + const VectorBuffer& buf_; + unsigned codim_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ncp/ncpboundaryratevector.hh b/opm/models/ncp/ncpboundaryratevector.hh new file mode 100644 index 00000000000..8f33071cb7e --- /dev/null +++ b/opm/models/ncp/ncpboundaryratevector.hh @@ -0,0 +1,203 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NcpBoundaryRateVector + */ +#ifndef EWOMS_NCP_BOUNDARY_RATE_VECTOR_HH +#define EWOMS_NCP_BOUNDARY_RATE_VECTOR_HH + +#include "ncpproperties.hh" + +#include +#include + +namespace Opm { + +/*! + * \ingroup NcpModel + * + * \brief Implements a boundary vector for the fully implicit + * compositional multi-phase NCP model. + */ +template +class NcpBoundaryRateVector : public GetPropType +{ + using ParentType = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Indices = GetPropType; + + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { enableEnergy = getPropValue() }; + + using EnergyModule = Opm::EnergyModule; + + using Toolbox = Opm::MathToolbox; + +public: + NcpBoundaryRateVector() : ParentType() + {} + + /*! + * \copydoc + * ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(Scalar) + */ + NcpBoundaryRateVector(const Evaluation& value) : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(const + * ImmiscibleBoundaryRateVector&) + */ + NcpBoundaryRateVector(const NcpBoundaryRateVector& value) = default; + NcpBoundaryRateVector& operator=(const NcpBoundaryRateVector& value) = default; + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setFreeFlow + */ + template + void setFreeFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + ExtensiveQuantities extQuants; + extQuants.updateBoundary(context, bfIdx, timeIdx, fluidState); + const auto& insideIntQuants = context.intensiveQuantities(bfIdx, timeIdx); + unsigned focusDofIdx = context.focusDofIndex(); + unsigned interiorDofIdx = context.interiorScvIndex(bfIdx, timeIdx); + + //////// + // advective fluxes of all components in all phases + //////// + (*this) = Evaluation(0.0); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Evaluation density; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + density = fluidState.density(phaseIdx); + else + density = Opm::getValue(fluidState.density(phaseIdx)); + } + else if (focusDofIdx == interiorDofIdx) + density = insideIntQuants.fluidState().density(phaseIdx); + else + density = Opm::getValue(insideIntQuants.fluidState().density(phaseIdx)); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + Evaluation molarity; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + molarity = fluidState.molarity(phaseIdx, compIdx); + else + molarity = Opm::getValue(fluidState.molarity(phaseIdx, compIdx)); + } + else if (focusDofIdx == interiorDofIdx) + molarity = insideIntQuants.fluidState().molarity(phaseIdx, compIdx); + else + molarity = Opm::getValue(insideIntQuants.fluidState().molarity(phaseIdx, compIdx)); + + // add advective flux of current component in current + // phase + (*this)[conti0EqIdx + compIdx] += extQuants.volumeFlux(phaseIdx)*molarity; + } + + if (enableEnergy) { + Evaluation specificEnthalpy; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + specificEnthalpy = fluidState.enthalpy(phaseIdx); + else + specificEnthalpy = Opm::getValue(fluidState.enthalpy(phaseIdx)); + } + else if (focusDofIdx == interiorDofIdx) + specificEnthalpy = insideIntQuants.fluidState().enthalpy(phaseIdx); + else + specificEnthalpy = Opm::getValue(insideIntQuants.fluidState().enthalpy(phaseIdx)); + + Evaluation enthalpyRate = density*extQuants.volumeFlux(phaseIdx)*specificEnthalpy; + EnergyModule::addToEnthalpyRate(*this, enthalpyRate); + } + } + + // thermal conduction + EnergyModule::addToEnthalpyRate(*this, EnergyModule::thermalConductionRate(extQuants)); + +#ifndef NDEBUG + for (unsigned i = 0; i < numEq; ++i) { + Opm::Valgrind::CheckDefined((*this)[i]); + } +#endif + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setInFlow + */ + template + void setInFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the direction opposite to the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + this->operator[](eqIdx) = Toolbox::min(0.0, this->operator[](eqIdx)); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setOutFlow + */ + template + void setOutFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the same direction as the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + this->operator[](eqIdx) = Toolbox::max(0.0, this->operator[](eqIdx)); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setNoFlow + */ + void setNoFlow() + { (*this) = Evaluation(0.0); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ncp/ncpextensivequantities.hh b/opm/models/ncp/ncpextensivequantities.hh new file mode 100644 index 00000000000..395daedd4a4 --- /dev/null +++ b/opm/models/ncp/ncpextensivequantities.hh @@ -0,0 +1,91 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NcpExtensiveQuantities + */ +#ifndef EWOMS_NCP_EXTENSIVE_QUANTITIES_HH +#define EWOMS_NCP_EXTENSIVE_QUANTITIES_HH + +#include "ncpproperties.hh" + +#include +#include +#include + +namespace Opm { + +/*! + * \ingroup NcpModel + * \ingroup ExtensiveQuantities + * + * \brief This template class represents the extensive quantities of the compositional + * NCP model. + */ +template +class NcpExtensiveQuantities + : public MultiPhaseBaseExtensiveQuantities + , public EnergyExtensiveQuantities()> + , public DiffusionExtensiveQuantities()> +{ + using ParentType = MultiPhaseBaseExtensiveQuantities; + + using FluidSystem = GetPropType; + using ElementContext = GetPropType; + + enum { enableDiffusion = getPropValue() }; + using DiffusionExtensiveQuantities = Opm::DiffusionExtensiveQuantities; + + enum { enableEnergy = getPropValue() }; + using EnergyExtensiveQuantities = Opm::EnergyExtensiveQuantities; + +public: + /*! + * \copydoc MultiPhaseBaseExtensiveQuantities::update + */ + void update(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, scvfIdx, timeIdx); + DiffusionExtensiveQuantities::update_(elemCtx, scvfIdx, timeIdx); + EnergyExtensiveQuantities::update_(elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc MultiPhaseBaseExtensiveQuantities::updateBoundary + */ + template + void updateBoundary(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + ParentType::updateBoundary(context, bfIdx, timeIdx, fluidState); + DiffusionExtensiveQuantities::updateBoundary_(context, bfIdx, timeIdx, fluidState); + EnergyExtensiveQuantities::updateBoundary_(context, bfIdx, timeIdx, fluidState); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ncp/ncpindices.hh b/opm/models/ncp/ncpindices.hh new file mode 100644 index 00000000000..cd9bbdc4315 --- /dev/null +++ b/opm/models/ncp/ncpindices.hh @@ -0,0 +1,104 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NcpIndices + */ +#ifndef EWOMS_NCP_INDICES_HH +#define EWOMS_NCP_INDICES_HH + +#include "ncpproperties.hh" +#include + +namespace Opm { + +/*! + * \ingroup NcpModel + * + * \brief The primary variable and equation indices for the + * compositional multi-phase NCP model. + */ +template +struct NcpIndices + : public EnergyIndices() + + getPropValue(), + getPropValue()> +{ +private: + using FluidSystem = GetPropType; + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + enum { enableEnergy = getPropValue() }; + + using EnergyIndices = Opm::EnergyIndices; + +public: + /*! + * \brief The number of primary variables / equations. + */ + static const int numEq = numComponents + numPhases + EnergyIndices::numEq_; + + /*! + * \brief Index of the equation for the continuity of mass of the + * first component. + * + * numComponents equations follow... + */ + static const int conti0EqIdx = PVOffset; + + /*! + * \brief Index of the first phase NCP equation. + * + * The index for the remaining phases are consecutive. + */ + static const int ncp0EqIdx = conti0EqIdx + numComponents; + + /*! + * \brief Index of the primary variable for the fugacity of the + * first component. + * + * numComponents primary variables follow... + */ + static const int fugacity0Idx = PVOffset; + + /*! + * \brief Index of the saturation of the first phase in a vector + * of primary variables. + * + * The following (numPhases - 1) primary variables represent the + * saturations for the phases [1, ..., numPhases - 1] + */ + static const int saturation0Idx = fugacity0Idx + numComponents; + + /*! + * \brief Index of the first phase' pressure in a vector of + * primary variables. + */ + static const int pressure0Idx = saturation0Idx + numPhases - 1; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ncp/ncpintensivequantities.hh b/opm/models/ncp/ncpintensivequantities.hh new file mode 100644 index 00000000000..d95353d039b --- /dev/null +++ b/opm/models/ncp/ncpintensivequantities.hh @@ -0,0 +1,245 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NcpIntensiveQuantities + */ +#ifndef EWOMS_NCP_INTENSIVE_QUANTITIES_HH +#define EWOMS_NCP_INTENSIVE_QUANTITIES_HH + +#include "ncpproperties.hh" + +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace Opm { +/*! + * \ingroup NcpModel + * \ingroup IntensiveQuantities + * + * \brief Contains the quantities which are are constant within a + * finite volume in the compositional multi-phase NCP model. + */ +template +class NcpIntensiveQuantities + : public GetPropType + , public DiffusionIntensiveQuantities() > + , public EnergyIntensiveQuantities() > + , public GetPropType::FluxIntensiveQuantities +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using ElementContext = GetPropType; + using Indices = GetPropType; + + using GridView = GetPropType; + using FluxModule = GetPropType; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { enableEnergy = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + enum { fugacity0Idx = Indices::fugacity0Idx }; + enum { saturation0Idx = Indices::saturation0Idx }; + enum { pressure0Idx = Indices::pressure0Idx }; + enum { dimWorld = GridView::dimensionworld }; + + using CompositionFromFugacitiesSolver = Opm::CompositionFromFugacities; + using FluidState = Opm::CompositionalFluidState; + using ComponentVector = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + using DiffusionIntensiveQuantities = Opm::DiffusionIntensiveQuantities; + using EnergyIntensiveQuantities = Opm::EnergyIntensiveQuantities; + using FluxIntensiveQuantities = typename FluxModule::FluxIntensiveQuantities; + +public: + NcpIntensiveQuantities() + {} + + NcpIntensiveQuantities(const NcpIntensiveQuantities& other) = default; + + NcpIntensiveQuantities& operator=(const NcpIntensiveQuantities& other) = default; + + /*! + * \brief IntensiveQuantities::update + */ + void update(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) + { + ParentType::update(elemCtx, dofIdx, timeIdx); + ParentType::checkDefined(); + + typename FluidSystem::template ParameterCache paramCache; + const auto& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + + // set the phase saturations + Evaluation sumSat = 0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases - 1; ++phaseIdx) { + const Evaluation& val = priVars.makeEvaluation(saturation0Idx + phaseIdx, timeIdx); + fluidState_.setSaturation(phaseIdx, val); + sumSat += val; + } + fluidState_.setSaturation(numPhases - 1, 1.0 - sumSat); + Opm::Valgrind::CheckDefined(sumSat); + + // set the fluid phase temperature + EnergyIntensiveQuantities::updateTemperatures_(fluidState_, elemCtx, dofIdx, timeIdx); + + // retrieve capillary pressure parameters + const auto& problem = elemCtx.problem(); + const MaterialLawParams& materialParams = + problem.materialLawParams(elemCtx, dofIdx, timeIdx); + // calculate capillary pressures + Evaluation capPress[numPhases]; + MaterialLaw::capillaryPressures(capPress, materialParams, fluidState_); + // add to the pressure of the first fluid phase + const Evaluation& pressure0 = priVars.makeEvaluation(pressure0Idx, timeIdx); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + fluidState_.setPressure(phaseIdx, pressure0 + (capPress[phaseIdx] - capPress[0])); + + ComponentVector fug; + // retrieve component fugacities + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + fug[compIdx] = priVars.makeEvaluation(fugacity0Idx + compIdx, timeIdx); + + // calculate phase compositions + const auto *hint = elemCtx.thermodynamicHint(dofIdx, timeIdx); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // initial guess + if (hint) { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + // use the hint for the initial mole fraction! + const Evaluation& moleFracIJ = hint->fluidState().moleFraction(phaseIdx, compIdx); + fluidState_.setMoleFraction(phaseIdx, compIdx, moleFracIJ); + } + } + else // !hint + CompositionFromFugacitiesSolver::guessInitial(fluidState_, phaseIdx, fug); + + // calculate the phase composition from the component + // fugacities + CompositionFromFugacitiesSolver::solve(fluidState_, paramCache, phaseIdx, fug); + } + + // porosity + porosity_ = problem.porosity(elemCtx, dofIdx, timeIdx); + Opm::Valgrind::CheckDefined(porosity_); + + // relative permeabilities + MaterialLaw::relativePermeabilities(relativePermeability_, materialParams, fluidState_); + + // dynamic viscosities + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // viscosities + const Evaluation& mu = FluidSystem::viscosity(fluidState_, paramCache, phaseIdx); + fluidState_.setViscosity(phaseIdx, mu); + + mobility_[phaseIdx] = relativePermeability_[phaseIdx]/mu; + } + + // intrinsic permeability + intrinsicPerm_ = problem.intrinsicPermeability(elemCtx, dofIdx, timeIdx); + + // update the quantities specific for the velocity model + FluxIntensiveQuantities::update_(elemCtx, dofIdx, timeIdx); + + // energy related quantities + EnergyIntensiveQuantities::update_(fluidState_, paramCache, elemCtx, dofIdx, timeIdx); + + // update the diffusion specific quantities of the intensive quantities + DiffusionIntensiveQuantities::update_(fluidState_, paramCache, elemCtx, dofIdx, timeIdx); + + checkDefined(); + } + + /*! + * \brief ImmiscibleIntensiveQuantities::fluidState + */ + const FluidState& fluidState() const + { return fluidState_; } + + /*! + * \brief ImmiscibleIntensiveQuantities::intrinsicPermeability + */ + const DimMatrix& intrinsicPermeability() const + { return intrinsicPerm_; } + + /*! + * \brief ImmiscibleIntensiveQuantities::relativePermeability + */ + const Evaluation& relativePermeability(unsigned phaseIdx) const + { return relativePermeability_[phaseIdx]; } + + /*! + * \brief ImmiscibleIntensiveQuantities::mobility + */ + const Evaluation& mobility(unsigned phaseIdx) const + { return mobility_[phaseIdx]; } + + /*! + * \brief ImmiscibleIntensiveQuantities::porosity + */ + const Evaluation& porosity() const + { return porosity_; } + + /*! + * \brief IntensiveQuantities::checkDefined + */ + void checkDefined() const + { +#if !defined NDEBUG && HAVE_VALGRIND + ParentType::checkDefined(); + + Opm::Valgrind::CheckDefined(porosity_); + Opm::Valgrind::CheckDefined(relativePermeability_); + + fluidState_.checkDefined(); +#endif + } + +private: + DimMatrix intrinsicPerm_; + FluidState fluidState_; + Evaluation porosity_; + Evaluation relativePermeability_[numPhases]; + Evaluation mobility_[numPhases]; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ncp/ncplocalresidual.hh b/opm/models/ncp/ncplocalresidual.hh new file mode 100644 index 00000000000..ae9c73455e5 --- /dev/null +++ b/opm/models/ncp/ncplocalresidual.hh @@ -0,0 +1,261 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NcpLocalResidual + */ +#ifndef EWOMS_NCP_LOCAL_RESIDUAL_HH +#define EWOMS_NCP_LOCAL_RESIDUAL_HH + +#include "ncpproperties.hh" + +#include +#include + +#include + +namespace Opm { +/*! + * \ingroup NcpModel + * + * \brief Details needed to calculate the local residual in the + * compositional multi-phase NCP-model . + */ +template +class NcpLocalResidual : public GetPropType +{ + using ParentType = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using IntensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using Indices = GetPropType; + + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { ncp0EqIdx = Indices::ncp0EqIdx }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + + enum { enableDiffusion = getPropValue() }; + using DiffusionModule = Opm::DiffusionModule; + + enum { enableEnergy = getPropValue() }; + using EnergyModule = Opm::EnergyModule; + + using EvalEqVector = Dune::FieldVector; + using ElemEvalEqVector = Dune::BlockVector; + using Toolbox = Opm::MathToolbox; + +public: + /*! + * \copydoc ImmiscibleLocalResidual::addPhaseStorage + */ + template + void addPhaseStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx, + unsigned phaseIdx) const + { + const IntensiveQuantities& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + const auto& fluidState = intQuants.fluidState(); + + // compute storage term of all components within all phases + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + unsigned eqIdx = conti0EqIdx + compIdx; + storage[eqIdx] += + Toolbox::template decay(fluidState.molarity(phaseIdx, compIdx)) + * Toolbox::template decay(fluidState.saturation(phaseIdx)) + * Toolbox::template decay(intQuants.porosity()); + } + + EnergyModule::addPhaseStorage(storage, elemCtx.intensiveQuantities(dofIdx, timeIdx), phaseIdx); + } + + /*! + * \copydoc ImmiscibleLocalResidual::computeStorage + */ + template + void computeStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + storage = 0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + addPhaseStorage(storage, elemCtx, dofIdx, timeIdx, phaseIdx); + + EnergyModule::addSolidEnergyStorage(storage, elemCtx.intensiveQuantities(dofIdx, timeIdx)); + } + + /*! + * \copydoc ImmiscibleLocalResidual::computeFlux + */ + void computeFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + flux = 0.0; + addAdvectiveFlux(flux, elemCtx, scvfIdx, timeIdx); + Opm::Valgrind::CheckDefined(flux); + + addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + Opm::Valgrind::CheckDefined(flux); + } + + /*! + * \copydoc ImmiscibleLocalResidual::addAdvectiveFlux + */ + void addAdvectiveFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + unsigned focusDofIdx = elemCtx.focusDofIndex(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // data attached to upstream and the downstream DOFs + // of the current phase + unsigned upIdx = static_cast(extQuants.upstreamIndex(phaseIdx)); + const IntensiveQuantities& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + + // this is a bit hacky because it is specific to the element-centered + // finite volume scheme. (N.B. that if finite differences are used to + // linearize the system of equations, it does not matter.) + if (upIdx == focusDofIdx) { + Evaluation tmp = + up.fluidState().molarDensity(phaseIdx) + * extQuants.volumeFlux(phaseIdx); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + flux[conti0EqIdx + compIdx] += + tmp*up.fluidState().moleFraction(phaseIdx, compIdx); + } + } + else { + Evaluation tmp = + Toolbox::value(up.fluidState().molarDensity(phaseIdx)) + * extQuants.volumeFlux(phaseIdx); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + flux[conti0EqIdx + compIdx] += + tmp*Toolbox::value(up.fluidState().moleFraction(phaseIdx, compIdx)); + } + } + } + + EnergyModule::addAdvectiveFlux(flux, elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc ImmiscibleLocalResidual::addDiffusiveFlux + */ + void addDiffusiveFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + DiffusionModule::addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + EnergyModule::addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc FvBaseLocalResidual::computeSource + * + * By default, this method only asks the problem to specify a + * source term. + */ + void computeSource(RateVector& source, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + Opm::Valgrind::SetUndefined(source); + elemCtx.problem().source(source, elemCtx, dofIdx, timeIdx); + Opm::Valgrind::CheckDefined(source); + + // evaluate the NCPs (i.e., the "phase presence" equations) + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + source[ncp0EqIdx + phaseIdx] = + phaseNcp(elemCtx, dofIdx, timeIdx, phaseIdx); + } + } + + /*! + * \brief Returns the value of the NCP-function for a phase. + */ + template + LhsEval phaseNcp(const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx, + unsigned phaseIdx) const + { + const auto& fluidState = elemCtx.intensiveQuantities(dofIdx, timeIdx).fluidState(); + using FluidState = typename std::remove_const::type>::type; + + using LhsToolbox = Opm::MathToolbox; + + const LhsEval& a = phaseNotPresentIneq_(fluidState, phaseIdx); + const LhsEval& b = phasePresentIneq_(fluidState, phaseIdx); + return LhsToolbox::min(a, b); + } + +private: + /*! + * \brief Returns the value of the inequality where a phase is + * present. + */ + template + LhsEval phasePresentIneq_(const FluidState& fluidState, unsigned phaseIdx) const + { + using FsToolbox = Opm::MathToolbox; + + return FsToolbox::template decay(fluidState.saturation(phaseIdx)); + } + + /*! + * \brief Returns the value of the inequality where a phase is not + * present. + */ + template + LhsEval phaseNotPresentIneq_(const FluidState& fluidState, unsigned phaseIdx) const + { + using FsToolbox = Opm::MathToolbox; + + // difference of sum of mole fractions in the phase from 100% + LhsEval a = 1.0; + for (unsigned i = 0; i < numComponents; ++i) + a -= FsToolbox::template decay(fluidState.moleFraction(phaseIdx, i)); + return a; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ncp/ncpmodel.hh b/opm/models/ncp/ncpmodel.hh new file mode 100644 index 00000000000..4dbaefd4dbd --- /dev/null +++ b/opm/models/ncp/ncpmodel.hh @@ -0,0 +1,481 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NcpModel + */ +#ifndef EWOMS_NCP_MODEL_HH +#define EWOMS_NCP_MODEL_HH + +#include + +#include "ncpproperties.hh" +#include "ncplocalresidual.hh" +#include "ncpextensivequantities.hh" +#include "ncpprimaryvariables.hh" +#include "ncpboundaryratevector.hh" +#include "ncpratevector.hh" +#include "ncpintensivequantities.hh" +#include "ncpnewtonmethod.hh" +#include "ncpindices.hh" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +namespace Opm { +template +class NcpModel; +} + +namespace Opm::Properties { + +namespace TTag { +/*! + * \brief Define the type tag for the compositional NCP model. + */ +struct NcpModel { using InheritsFrom = std::tuple; }; +} // namespace TTag + +//! Use the Ncp local jacobian operator for the compositional NCP model +template +struct LocalResidual { using type = NcpLocalResidual; }; + +//! Use the Ncp specific newton method for the compositional NCP model +template +struct NewtonMethod { using type = NcpNewtonMethod; }; + +//! the Model property +template +struct Model { using type = NcpModel; }; + +//! The type of the base base class for actual problems +template +struct BaseProblem { using type = MultiPhaseBaseProblem; }; + +//! Disable the energy equation by default +template +struct EnableEnergy { static constexpr bool value = false; }; + +//! disable diffusion by default +template +struct EnableDiffusion { static constexpr bool value = false; }; + +//! the RateVector property +template +struct RateVector { using type = NcpRateVector; }; + +//! the BoundaryRateVector property +template +struct BoundaryRateVector { using type = NcpBoundaryRateVector; }; + +//! the PrimaryVariables property +template +struct PrimaryVariables { using type = NcpPrimaryVariables; }; + +//! the IntensiveQuantities property +template +struct IntensiveQuantities { using type = NcpIntensiveQuantities; }; + +//! the ExtensiveQuantities property +template +struct ExtensiveQuantities { using type = NcpExtensiveQuantities; }; + +//! The indices required by the compositional NCP model +template +struct Indices { using type = NcpIndices; }; + +//! The unmodified weight for the pressure primary variable +template +struct NcpPressureBaseWeight +{ + using type = GetPropType; + static constexpr type value = 1.0; +}; +//! The weight for the saturation primary variables +template +struct NcpSaturationsBaseWeight +{ + using type = GetPropType; + static constexpr type value = 1.0; +}; +//! The unmodified weight for the fugacity primary variables +template +struct NcpFugacitiesBaseWeight +{ + using type = GetPropType; + static constexpr type value = 1.0e-6; +}; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup NcpModel + * + * \brief A compositional multi-phase model based on non-linear + * complementarity functions. + * + * This model implements a \f$M\f$-phase flow of a fluid mixture + * composed of \f$N\f$ chemical species. The phases are denoted by + * lower index \f$\alpha \in \{ 1, \dots, M \}\f$. All fluid phases + * are mixtures of \f$N \geq M - 1\f$ chemical species which are + * denoted by the upper index \f$\kappa \in \{ 1, \dots, N \} \f$. + * + * + * By default, the standard multi-phase Darcy approach is used to determine + * the velocity, i.e. + * \f[ + * \mathbf{v}_\alpha = - \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} + * \left(\mathbf{grad}\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right) \;, + * \f] + * although the actual approach which is used can be specified via the + * \c FluxModule property. For example, the velocity model can by + * changed to the Forchheimer approach by + * \code + * template +struct FluxModule { using type = ForchheimerFluxModule; }; + * \endcode + * + * The core of the model is the conservation mass of each component by + * means of the equation + * \f[ + * \sum_\alpha \frac{\partial\;\phi c_\alpha^\kappa S_\alpha }{\partial t} + * - \sum_\alpha \mathrm{div} \left\{ c_\alpha^\kappa \mathbf{v}_\alpha \right\} + * - q^\kappa = 0 \;. + * \f] + * + * For the missing \f$M\f$ model assumptions, the model uses + * non-linear complementarity functions. These are based on the + * observation that if a fluid phase is not present, the sum of the + * mole fractions of this fluid phase is smaller than \f$1\f$, i.e. + * \f[ \forall \alpha: S_\alpha = 0 \implies \sum_\kappa + * x_\alpha^\kappa \leq 1 \f] + * + * Also, if a fluid phase may be present at a given spatial location + * its saturation must be non-negative: + * \f[ \forall \alpha: \sum_\kappa x_\alpha^\kappa = 1 \implies S_\alpha \geq 0 + *\f] + * + * Since at any given spatial location, a phase is always either + * present or not present, one of the strict equalities on the + * right hand side is always true, i.e. + * \f[ + * \forall \alpha: S_\alpha \left( \sum_\kappa x_\alpha^\kappa - 1 \right) = 0 + * \f] + * always holds. + * + * These three equations constitute a non-linear complementarity + * problem, which can be solved using so-called non-linear + * complementarity functions \f$\Phi(a, b)\f$. Such functions have the property + * \f[\Phi(a,b) = 0 \iff a \geq0 \land b \geq0 \land a \cdot b = 0 \f] + * + * Several non-linear complementarity functions have been suggested, + * e.g. the Fischer-Burmeister function + * \f[ \Phi(a,b) = a + b - \sqrt{a^2 + b^2} \;. \f] + * This model uses + * \f[ \Phi(a,b) = \min \{a, b \}\;, \f] + * because of its piecewise linearity. + * + * The model assumes local thermodynamic equilibrium and uses the + * following primary variables: + * - The pressure of the first phase \f$p_1\f$ + * - The component fugacities \f$f^1, \dots, f^{N}\f$ + * - The saturations of the first \f$M-1\f$ phases \f$S_1, \dots, S_{M-1}\f$ + * - Temperature \f$T\f$ if the energy equation is enabled + */ +template +class NcpModel + : public MultiPhaseBaseModel +{ + using ParentType = MultiPhaseBaseModel; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Simulator = GetPropType; + using ElementContext = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + enum { fugacity0Idx = Indices::fugacity0Idx }; + enum { pressure0Idx = Indices::pressure0Idx }; + enum { saturation0Idx = Indices::saturation0Idx }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { ncp0EqIdx = Indices::ncp0EqIdx }; + enum { enableDiffusion = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + using ComponentVector = Dune::FieldVector; + + using Toolbox = MathToolbox; + + using EnergyModule = Opm::EnergyModule; + using DiffusionModule = Opm::DiffusionModule; + +public: + NcpModel(Simulator& simulator) + : ParentType(simulator) + {} + + /*! + * \brief Register all run-time parameters for the immiscible model. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + DiffusionModule::registerParameters(); + EnergyModule::registerParameters(); + + // register runtime parameters of the VTK output modules + VtkCompositionModule::registerParameters(); + + if (enableDiffusion) + VtkDiffusionModule::registerParameters(); + + if (enableEnergy) + VtkEnergyModule::registerParameters(); + } + + /*! + * \copydoc FvBaseDiscretization::finishInit() + */ + void finishInit() + { + ParentType::finishInit(); + + minActivityCoeff_.resize(this->numGridDof()); + std::fill(minActivityCoeff_.begin(), minActivityCoeff_.end(), 1.0); + } + + void adaptGrid() + { + ParentType::adaptGrid(); + minActivityCoeff_.resize(this->numGridDof()); + } + + /*! + * \copydoc FvBaseDiscretization::name + */ + static std::string name() + { return "ncp"; } + + /*! + * \copydoc FvBaseDiscretization::primaryVarName + */ + std::string primaryVarName(unsigned pvIdx) const + { + std::string s; + if (!(s = EnergyModule::primaryVarName(pvIdx)).empty()) + return s; + + std::ostringstream oss; + if (pvIdx == pressure0Idx) + oss << "pressure_" << FluidSystem::phaseName(/*phaseIdx=*/0); + else if (saturation0Idx <= pvIdx && pvIdx < saturation0Idx + (numPhases - 1)) + oss << "saturation_" << FluidSystem::phaseName(/*phaseIdx=*/pvIdx - saturation0Idx); + else if (fugacity0Idx <= pvIdx && pvIdx < fugacity0Idx + numComponents) + oss << "fugacity^" << FluidSystem::componentName(pvIdx - fugacity0Idx); + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::eqName + */ + std::string eqName(unsigned eqIdx) const + { + std::string s; + if (!(s = EnergyModule::eqName(eqIdx)).empty()) + return s; + + std::ostringstream oss; + if (conti0EqIdx <= eqIdx && eqIdx < conti0EqIdx + numComponents) + oss << "continuity^" << FluidSystem::componentName(eqIdx - conti0EqIdx); + else if (ncp0EqIdx <= eqIdx && eqIdx < ncp0EqIdx + numPhases) + oss << "ncp_" << FluidSystem::phaseName(/*phaseIdx=*/eqIdx - ncp0EqIdx); + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::updateBegin + */ + void updateBegin() + { + ParentType::updateBegin(); + + // find the a reference pressure. The first degree of freedom + // might correspond to non-interior entities which would lead + // to an undefined value, so we have to iterate... + for (unsigned dofIdx = 0; dofIdx < this->numGridDof(); ++ dofIdx) { + if (this->isLocalDof(dofIdx)) { + referencePressure_ = + this->solution(/*timeIdx=*/0)[dofIdx][/*pvIdx=*/Indices::pressure0Idx]; + break; + } + } + } + + /*! + * \copydoc FvBaseDiscretization::updatePVWeights + */ + void updatePVWeights(const ElementContext& elemCtx) const + { + for (unsigned dofIdx = 0; dofIdx < elemCtx.numDof(/*timeIdx=*/0); ++dofIdx) { + unsigned globalIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + minActivityCoeff_[globalIdx][compIdx] = 1e100; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + const auto& fs = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0).fluidState(); + + minActivityCoeff_[globalIdx][compIdx] = + std::min(minActivityCoeff_[globalIdx][compIdx], + Toolbox::value(fs.fugacityCoefficient(phaseIdx, compIdx)) + * Toolbox::value(fs.pressure(phaseIdx))); + Valgrind::CheckDefined(minActivityCoeff_[globalIdx][compIdx]); + } + if (minActivityCoeff_[globalIdx][compIdx] <= 0) + throw NumericalProblem("The minimum activity coefficient for component "+std::to_string(compIdx) + +" on DOF "+std::to_string(globalIdx)+" is negative or zero!"); + } + } + } + + /*! + * \copydoc FvBaseDiscretization::primaryVarWeight + */ + Scalar primaryVarWeight(unsigned globalDofIdx, unsigned pvIdx) const + { + Scalar tmp = EnergyModule::primaryVarWeight(*this, globalDofIdx, pvIdx); + Scalar result; + if (tmp > 0) + // energy related quantity + result = tmp; + else if (fugacity0Idx <= pvIdx && pvIdx < fugacity0Idx + numComponents) { + // component fugacity + unsigned compIdx = pvIdx - fugacity0Idx; + assert(compIdx <= numComponents); + + Valgrind::CheckDefined(minActivityCoeff_[globalDofIdx][compIdx]); + static const Scalar fugacityBaseWeight = + getPropValue(); + result = fugacityBaseWeight / minActivityCoeff_[globalDofIdx][compIdx]; + } + else if (Indices::pressure0Idx == pvIdx) { + static const Scalar pressureBaseWeight = getPropValue(); + result = pressureBaseWeight / referencePressure_; + } + else { +#ifndef NDEBUG + unsigned phaseIdx = pvIdx - saturation0Idx; + assert(phaseIdx < numPhases - 1); +#endif + + // saturation + static const Scalar saturationsBaseWeight = + getPropValue(); + result = saturationsBaseWeight; + } + + assert(std::isfinite(result)); + assert(result > 0); + + return result; + } + + /*! + * \copydoc FvBaseDiscretization::eqWeight + */ + Scalar eqWeight(unsigned globalDofIdx, unsigned eqIdx) const + { + Scalar tmp = EnergyModule::eqWeight(*this, globalDofIdx, eqIdx); + if (tmp > 0) + // an energy related equation + return tmp; + // an NCP + else if (ncp0EqIdx <= eqIdx && eqIdx < Indices::ncp0EqIdx + numPhases) + return 1.0; + + // a mass conservation equation + unsigned compIdx = eqIdx - Indices::conti0EqIdx; + assert(compIdx <= numComponents); + + // make all kg equal + return FluidSystem::molarMass(compIdx); + } + + /*! + * \brief Returns the smallest activity coefficient of a component for the + * most current solution at a vertex. + * + * \param globalDofIdx The global index of the vertex (i.e. finite volume) of interest. + * \param compIdx The index of the component of interest. + */ + Scalar minActivityCoeff(unsigned globalDofIdx, unsigned compIdx) const + { return minActivityCoeff_[globalDofIdx][compIdx]; } + + /*! + * \internal + */ + void registerOutputModules_() + { + ParentType::registerOutputModules_(); + + this->addOutputModule(new VtkCompositionModule(this->simulator_)); + if (enableDiffusion) + this->addOutputModule(new VtkDiffusionModule(this->simulator_)); + if (enableEnergy) + this->addOutputModule(new VtkEnergyModule(this->simulator_)); + } + + mutable Scalar referencePressure_; + mutable std::vector minActivityCoeff_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ncp/ncpnewtonmethod.hh b/opm/models/ncp/ncpnewtonmethod.hh new file mode 100644 index 00000000000..b21760c1f1f --- /dev/null +++ b/opm/models/ncp/ncpnewtonmethod.hh @@ -0,0 +1,219 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NcpNewtonMethod + */ +#ifndef EWOMS_NCP_NEWTON_METHOD_HH +#define EWOMS_NCP_NEWTON_METHOD_HH + +#include "ncpproperties.hh" + +#include + +#include + +#include + +namespace Opm::Properties { + +template +struct DiscNewtonMethod; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup NcpModel + * + * \brief A Newton solver specific to the NCP model. + */ +template +class NcpNewtonMethod : public GetPropType +{ + using ParentType = GetPropType; + + using EqVector = GetPropType; + using PrimaryVariables = GetPropType; + using Scalar = GetPropType; + using Indices = GetPropType; + using Simulator = GetPropType; + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { fugacity0Idx = Indices::fugacity0Idx }; + enum { saturation0Idx = Indices::saturation0Idx }; + enum { pressure0Idx = Indices::pressure0Idx }; + enum { ncp0EqIdx = Indices::ncp0EqIdx }; + +public: + /*! + * \copydoc FvBaseNewtonMethod::FvBaseNewtonMethod(Problem& ) + */ + NcpNewtonMethod(Simulator& simulator) : ParentType(simulator) + {} + +protected: + friend ParentType; + friend NewtonMethod; + + void preSolve_(const SolutionVector&, + const GlobalEqVector& currentResidual) + { + const auto& constraintsMap = this->model().linearizer().constraintsMap(); + this->lastError_ = this->error_; + + // calculate the error as the maximum weighted tolerance of + // the solution's residual + this->error_ = 0; + for (unsigned dofIdx = 0; dofIdx < currentResidual.size(); ++dofIdx) { + // do not consider auxiliary DOFs for the error + if (dofIdx >= this->model().numGridDof() || this->model().dofTotalVolume(dofIdx) <= 0.0) + continue; + + // also do not consider DOFs which are constraint + if (this->enableConstraints_()) { + if (constraintsMap.count(dofIdx) > 0) + continue; + } + + const auto& r = currentResidual[dofIdx]; + for (unsigned eqIdx = 0; eqIdx < r.size(); ++eqIdx) { + if (ncp0EqIdx <= eqIdx && eqIdx < Indices::ncp0EqIdx + numPhases) + continue; + this->error_ = + std::max(std::abs(r[eqIdx]*this->model().eqWeight(dofIdx, eqIdx)), + this->error_); + } + } + + // take the other processes into account + this->error_ = this->comm_.max(this->error_); + + // make sure that the error never grows beyond the maximum + // allowed one + if (this->error_ > Parameters::Get>()) + throw Opm::NumericalProblem("Newton: Error "+std::to_string(double(this->error_))+ + + " is larger than maximum allowed error of " + + std::to_string(Parameters::Get>())); + } + + /*! + * \copydoc FvBaseNewtonMethod::updatePrimaryVariables_ + */ + void updatePrimaryVariables_(unsigned globalDofIdx, + PrimaryVariables& nextValue, + const PrimaryVariables& currentValue, + const EqVector& update, + const EqVector&) + { + // normal Newton-Raphson update + nextValue = currentValue; + nextValue -= update; + + //// + // put crash barriers along the update path + //// + + // saturations: limit the change of any saturation to at most 20% + Scalar sumSatDelta = 0.0; + Scalar maxSatDelta = 0.0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases - 1; ++phaseIdx) { + maxSatDelta = std::max(std::abs(update[saturation0Idx + phaseIdx]), + maxSatDelta); + sumSatDelta += update[saturation0Idx + phaseIdx]; + } + maxSatDelta = std::max(std::abs(- sumSatDelta), maxSatDelta); + + if (maxSatDelta > 0.2) { + Scalar alpha = 0.2/maxSatDelta; + for (unsigned phaseIdx = 0; phaseIdx < numPhases - 1; ++phaseIdx) { + nextValue[saturation0Idx + phaseIdx] = + currentValue[saturation0Idx + phaseIdx] + - alpha*update[saturation0Idx + phaseIdx]; + } + } + + // limit pressure reference change to 20% of the total value per iteration + clampValue_(nextValue[pressure0Idx], + currentValue[pressure0Idx]*0.8, + currentValue[pressure0Idx]*1.2); + + // fugacities + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + Scalar& val = nextValue[fugacity0Idx + compIdx]; + Scalar oldVal = currentValue[fugacity0Idx + compIdx]; + + // get the minimum activity coefficient for the component (i.e., the activity + // coefficient of the phase for which the component has the highest affinity) + Scalar minPhi = this->problem().model().minActivityCoeff(globalDofIdx, compIdx); + // Make sure that the activity coefficient does not get too small. + minPhi = std::max(0.001*currentValue[pressure0Idx], minPhi); + + // allow the mole fraction of the component to change at most 70% in any + // phase (assuming composition independent fugacity coefficients). + Scalar maxDelta = 0.7 * minPhi; + clampValue_(val, oldVal - maxDelta, oldVal + maxDelta); + + // make sure that fugacities do not become negative + val = std::max(val, 0.0); + } + + // do not become grossly unphysical in a single iteration for the first few + // iterations of a time step + if (this->numIterations_ < 3) { + // fugacities + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + Scalar& val = nextValue[fugacity0Idx + compIdx]; + Scalar oldVal = currentValue[fugacity0Idx + compIdx]; + Scalar minPhi = this->problem().model().minActivityCoeff(globalDofIdx, compIdx); + if (oldVal < 1.0*minPhi && val > 1.0*minPhi) + val = 1.0*minPhi; + else if (oldVal > 0.0 && val < 0.0) + val = 0.0; + } + + // saturations + for (unsigned phaseIdx = 0; phaseIdx < numPhases - 1; ++phaseIdx) { + Scalar& val = nextValue[saturation0Idx + phaseIdx]; + Scalar oldVal = currentValue[saturation0Idx + phaseIdx]; + if (oldVal < 1.0 && val > 1.0) + val = 1.0; + else if (oldVal > 0.0 && val < 0.0) + val = 0.0; + } + } + } + +private: + void clampValue_(Scalar& val, Scalar minVal, Scalar maxVal) const + { val = std::max(minVal, std::min(val, maxVal)); } +}; +} // namespace Opm + +#endif diff --git a/opm/models/ncp/ncpprimaryvariables.hh b/opm/models/ncp/ncpprimaryvariables.hh new file mode 100644 index 00000000000..1961b42ed33 --- /dev/null +++ b/opm/models/ncp/ncpprimaryvariables.hh @@ -0,0 +1,192 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NcpPrimaryVariables + */ +#ifndef EWOMS_NCP_PRIMARY_VARIABLES_HH +#define EWOMS_NCP_PRIMARY_VARIABLES_HH + +#include "ncpproperties.hh" + +#include +#include + +#include +#include +#include + +#include + +namespace Opm { + +/*! + * \ingroup NcpModel + * + * \brief Represents the primary variables used by the compositional + * multi-phase NCP model. + * + * This class is basically a Dune::FieldVector which can retrieve its + * contents from an aribitatry fluid state. + */ +template +class NcpPrimaryVariables : public FvBasePrimaryVariables +{ + using ParentType = FvBasePrimaryVariables; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + + using Indices = GetPropType; + enum { pressure0Idx = Indices::pressure0Idx }; + enum { saturation0Idx = Indices::saturation0Idx }; + enum { fugacity0Idx = Indices::fugacity0Idx }; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + using ComponentVector = Dune::FieldVector; + + enum { enableEnergy = getPropValue() }; + using EnergyModule = Opm::EnergyModule; + + using NcpFlash = Opm::NcpFlash; + using Toolbox = Opm::MathToolbox; + +public: + NcpPrimaryVariables() : ParentType() + {} + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(Scalar) + */ + NcpPrimaryVariables(Scalar value) : ParentType(value) + {} + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(const + * ImmisciblePrimaryVariables& ) + */ + NcpPrimaryVariables(const NcpPrimaryVariables& value) = default; + NcpPrimaryVariables& operator=(const NcpPrimaryVariables& value) = default; + + /*! + * \copydoc ImmisciblePrimaryVariables::assignMassConservative + */ + template + void assignMassConservative(const FluidState& fluidState, + const MaterialLawParams& matParams, + bool isInEquilibrium = false) + { + using FsToolbox = Opm::MathToolbox; + +#ifndef NDEBUG + // make sure the temperature is the same in all fluid phases + for (unsigned phaseIdx = 1; phaseIdx < numPhases; ++phaseIdx) { + assert(fluidState.temperature(0) == fluidState.temperature(phaseIdx)); + } +#endif // NDEBUG + + // for the equilibrium case, we don't need complicated + // computations. + if (isInEquilibrium) { + assignNaive(fluidState); + return; + } + + // use a flash calculation to calculate a fluid state in + // thermodynamic equilibrium + typename FluidSystem::template ParameterCache paramCache; + Opm::CompositionalFluidState fsFlash; + + // use the externally given fluid state as initial value for + // the flash calculation + fsFlash.assign(fluidState); + + // calculate the phase densities + paramCache.updateAll(fsFlash); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar rho = FluidSystem::density(fsFlash, paramCache, phaseIdx); + fsFlash.setDensity(phaseIdx, rho); + } + + // calculate the "global molarities" + ComponentVector globalMolarities(0.0); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + globalMolarities[compIdx] += + FsToolbox::value(fsFlash.saturation(phaseIdx)) + * FsToolbox::value(fsFlash.molarity(phaseIdx, compIdx)); + } + } + + // run the flash calculation + NcpFlash::template solve(fsFlash, matParams, paramCache, globalMolarities); + + // use the result to assign the primary variables + assignNaive(fsFlash); + } + + /*! + * \copydoc ImmisciblePrimaryVariables::assignNaive + */ + template + void assignNaive(const FluidState& fluidState, unsigned refPhaseIdx = 0) + { + using FsToolbox = Opm::MathToolbox; + + // assign the phase temperatures. this is out-sourced to + // the energy module + EnergyModule::setPriVarTemperatures(*this, fluidState); + + // assign fugacities. + typename FluidSystem::template ParameterCache paramCache; + paramCache.updatePhase(fluidState, refPhaseIdx); + Scalar pRef = FsToolbox::value(fluidState.pressure(refPhaseIdx)); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + // we always compute the fugacities because they are quite exotic quantities + // and this easily forgotten to be specified + Scalar fugCoeff = + FluidSystem::template fugacityCoefficient(fluidState, + paramCache, + refPhaseIdx, + compIdx); + (*this)[fugacity0Idx + compIdx] = + fugCoeff*fluidState.moleFraction(refPhaseIdx, compIdx)*pRef; + } + + // assign pressure of first phase + (*this)[pressure0Idx] = FsToolbox::value(fluidState.pressure(/*phaseIdx=*/0)); + + // assign first M - 1 saturations + for (unsigned phaseIdx = 0; phaseIdx < numPhases - 1; ++phaseIdx) + (*this)[saturation0Idx + phaseIdx] = FsToolbox::value(fluidState.saturation(phaseIdx)); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ncp/ncpproperties.hh b/opm/models/ncp/ncpproperties.hh new file mode 100644 index 00000000000..afc7bf8e439 --- /dev/null +++ b/opm/models/ncp/ncpproperties.hh @@ -0,0 +1,57 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup NcpModel + * + * \brief Declares the properties required for the NCP compositional + * multi-phase model. + */ +#ifndef EWOMS_NCP_PROPERTIES_HH +#define EWOMS_NCP_PROPERTIES_HH + +#include +#include +#include +#include + +namespace Opm::Properties { + +//! The unmodified weight for the pressure primary variable +template +struct NcpPressureBaseWeight { using type = UndefinedProperty; }; +//! The weight for the saturation primary variables +template +struct NcpSaturationsBaseWeight { using type = UndefinedProperty; }; +//! The unmodified weight for the fugacity primary variables +template +struct NcpFugacitiesBaseWeight { using type = UndefinedProperty; }; + +//! The themodynamic constraint solver which calculates the +//! composition of any phase given all component fugacities. +template +struct NcpCompositionFromFugacitiesSolver { using type = UndefinedProperty; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/ncp/ncpratevector.hh b/opm/models/ncp/ncpratevector.hh new file mode 100644 index 00000000000..5c1abdf1213 --- /dev/null +++ b/opm/models/ncp/ncpratevector.hh @@ -0,0 +1,151 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NcpRateVector + */ +#ifndef EWOMS_NCP_RATE_VECTOR_HH +#define EWOMS_NCP_RATE_VECTOR_HH + +#include "ncpindices.hh" + +#include +#include + +#include + +namespace Opm { +/*! + * \ingroup NcpModel + * + * \brief Implements a vector representing mass, molar or volumetric rates. + * + * This class is basically a Dune::FieldVector which can be set using + * either mass, molar or volumetric rates. + */ +template +class NcpRateVector + : public Dune::FieldVector, + getPropValue()> +{ + enum { numEq = getPropValue() }; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using ParentType = Dune::FieldVector; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { numComponents = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + using EnergyModule = Opm::EnergyModule; + + using Toolbox = Opm::MathToolbox; + +public: + NcpRateVector() : ParentType() + { Opm::Valgrind::SetUndefined(*this); } + + /*! + * \copydoc ImmiscibleRateVector::ImmiscibleRateVector(Scalar) + */ + NcpRateVector(const Evaluation& value) + : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleRateVector::ImmiscibleRateVector(const + * ImmiscibleRateVector& ) + */ + NcpRateVector(const NcpRateVector& value) + : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleRateVector::setMassRate + */ + void setMassRate(const ParentType& value) + { + // convert to molar rates + ParentType molarRate(value); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + molarRate[conti0EqIdx + compIdx] /= FluidSystem::molarMass(compIdx); + + // set the molar rate + setMolarRate(molarRate); + } + + /*! + * \copydoc ImmiscibleRateVector::setMolarRate + */ + void setMolarRate(const ParentType& value) + { ParentType::operator=(value); } + + /*! + * \copydoc ImmiscibleRateVector::setEnthalpyRate + */ + template + void setEnthalpyRate(const RhsEval& rate) + { EnergyModule::setEnthalpyRate(*this, rate); } + + /*! + * \copydoc ImmiscibleRateVector::setVolumetricRate + */ + template + void setVolumetricRate(const FluidState& fluidState, unsigned phaseIdx, const RhsEval& volume) + { + *this = 0.0; + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + (*this)[conti0EqIdx + compIdx] = fluidState.molarity(phaseIdx, compIdx) * volume; + + EnergyModule::setEnthalpyRate(*this, fluidState, phaseIdx, volume); + Opm::Valgrind::CheckDefined(*this); + } + + /*! + * \brief Assignment operator from a scalar or a function evaluation + */ + template + NcpRateVector& operator=(const RhsEval& value) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = value; + return *this; + } + + /*! + * \brief Assignment operator from another rate vector + */ + NcpRateVector& operator=(const NcpRateVector& other) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = other[i]; + return *this; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/nonlinear/newtonmethod.hh b/opm/models/nonlinear/newtonmethod.hh new file mode 100644 index 00000000000..aa99ae354da --- /dev/null +++ b/opm/models/nonlinear/newtonmethod.hh @@ -0,0 +1,881 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::NewtonMethod + */ +#ifndef EWOMS_NEWTON_METHOD_HH +#define EWOMS_NEWTON_METHOD_HH + +#include +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +namespace Opm { +// forward declaration of classes +template +class NewtonMethod; +} + +namespace Opm { +// forward declaration of property tags +} // namespace Opm + +namespace Opm::Properties { + +namespace TTag { + +//! The type tag on which the default properties for the Newton method +//! are attached +struct NewtonMethod {}; + +} // namespace TTag + +// set default values for the properties +template +struct NewtonMethod { using type = ::Opm::NewtonMethod; }; +template +struct NewtonConvergenceWriter { using type = NullConvergenceWriter; }; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup Newton + * \brief The multi-dimensional Newton method. + * + * This class uses static polymorphism to allow implementations to + * implement different update/convergence strategies. + */ +template +class NewtonMethod +{ + using Implementation = GetPropType; + using Scalar = GetPropType; + using Simulator = GetPropType; + using Problem = GetPropType; + using Model = GetPropType; + + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + using PrimaryVariables = GetPropType; + using Constraints = GetPropType; + using EqVector = GetPropType; + using Linearizer = GetPropType; + using LinearSolverBackend = GetPropType; + using ConvergenceWriter = GetPropType; + + using Communicator = typename Dune::MPIHelper::MPICommunicator; + using CollectiveCommunication = typename Dune::Communication; + +public: + NewtonMethod(Simulator& simulator) + : simulator_(simulator) + , endIterMsgStream_(std::ostringstream::out) + , linearSolver_(simulator) + , comm_(Dune::MPIHelper::getCommunicator()) + , convergenceWriter_(asImp_()) + { + lastError_ = 1e100; + error_ = 1e100; + tolerance_ = Parameters::Get>(); + + numIterations_ = 0; + } + + /*! + * \brief Register all run-time parameters for the Newton method. + */ + static void registerParameters() + { + LinearSolverBackend::registerParameters(); + + Parameters::Register + ("Specify whether the Newton method should inform " + "the user about its progress or not"); + Parameters::Register + ("Write the convergence behaviour of the Newton " + "method to a VTK file"); + Parameters::Register + ("The 'optimum' number of Newton iterations per time step"); + Parameters::Register + ("The maximum number of Newton iterations per time step"); + Parameters::Register> + ("The maximum raw error tolerated by the Newton" + "method for considering a solution to be converged"); + Parameters::Register> + ("The maximum error tolerated by the Newton " + "method to which does not cause an abort"); + } + + /*! + * \brief Finialize the construction of the object. + * + * At this point, it can be assumed that all objects featured by the simulator have + * been allocated. (But not that they have been fully initialized yet.) + */ + void finishInit() + { } + + /*! + * \brief Returns true if the error of the solution is below the + * tolerance. + */ + bool converged() const + { return error_ <= tolerance(); } + + /*! + * \brief Returns a reference to the object describing the current physical problem. + */ + Problem& problem() + { return simulator_.problem(); } + + /*! + * \brief Returns a reference to the object describing the current physical problem. + */ + const Problem& problem() const + { return simulator_.problem(); } + + /*! + * \brief Returns a reference to the numeric model. + */ + Model& model() + { return simulator_.model(); } + + /*! + * \brief Returns a reference to the numeric model. + */ + const Model& model() const + { return simulator_.model(); } + + /*! + * \brief Returns the number of iterations done since the Newton method + * was invoked. + */ + int numIterations() const + { return numIterations_; } + + /*! + * \brief Set the index of current iteration. + * + * Normally this does not need to be called, but if the non-linear solver is + * implemented externally, it needs to be set in order for the model to do the Right + * Thing (TM) while linearizing. + */ + void setIterationIndex(int value) + { numIterations_ = value; } + + /*! + * \brief Return the current tolerance at which the Newton method considers itself to + * be converged. + */ + Scalar tolerance() const + { return tolerance_; } + + /*! + * \brief Set the current tolerance at which the Newton method considers itself to + * be converged. + */ + void setTolerance(Scalar value) + { tolerance_ = value; } + + /*! + * \brief Run the Newton method. + * + * The actual implementation can influence all the strategic + * decisions via callbacks using static polymorphism. + */ + bool apply() + { + // Clear the current line using an ansi escape + // sequence. For an explanation see + // http://en.wikipedia.org/wiki/ANSI_escape_code + const char *clearRemainingLine = "\n"; + if (isatty(fileno(stdout))) { + static const char blubb[] = { 0x1b, '[', 'K', '\r', 0 }; + clearRemainingLine = blubb; + } + + // make sure all timers are prestine + prePostProcessTimer_.halt(); + linearizeTimer_.halt(); + solveTimer_.halt(); + updateTimer_.halt(); + + SolutionVector& nextSolution = model().solution(/*historyIdx=*/0); + SolutionVector currentSolution(nextSolution); + GlobalEqVector solutionUpdate(nextSolution.size()); + + Linearizer& linearizer = model().linearizer(); + + TimerGuard prePostProcessTimerGuard(prePostProcessTimer_); + + // tell the implementation that we begin solving + prePostProcessTimer_.start(); + asImp_().begin_(nextSolution); + prePostProcessTimer_.stop(); + + try { + TimerGuard innerPrePostProcessTimerGuard(prePostProcessTimer_); + TimerGuard linearizeTimerGuard(linearizeTimer_); + TimerGuard updateTimerGuard(updateTimer_); + TimerGuard solveTimerGuard(solveTimer_); + + // execute the method as long as the implementation thinks + // that we should do another iteration + while (asImp_().proceed_()) { + // linearize the problem at the current solution + + // notify the implementation that we're about to start + // a new iteration + prePostProcessTimer_.start(); + asImp_().beginIteration_(); + prePostProcessTimer_.stop(); + + // make the current solution to the old one + currentSolution = nextSolution; + + if (asImp_().verbose_()) { + std::cout << "Linearize: r(x^k) = dS/dt + div F - q; M = grad r" + << clearRemainingLine + << std::flush; + } + + // do the actual linearization + linearizeTimer_.start(); + asImp_().linearizeDomain_(); + asImp_().linearizeAuxiliaryEquations_(); + linearizeTimer_.stop(); + + solveTimer_.start(); + auto& residual = linearizer.residual(); + const auto& jacobian = linearizer.jacobian(); + linearSolver_.prepare(jacobian, residual); + linearSolver_.setResidual(residual); + linearSolver_.getResidual(residual); + solveTimer_.stop(); + + // The preSolve_() method usually computes the errors, but it can do + // something else in addition. TODO: should its costs be counted to + // the linearization or to the update? + updateTimer_.start(); + asImp_().preSolve_(currentSolution, residual); + updateTimer_.stop(); + + if (!asImp_().proceed_()) { + if (asImp_().verbose_() && isatty(fileno(stdout))) + std::cout << clearRemainingLine + << std::flush; + + // tell the implementation that we're done with this iteration + prePostProcessTimer_.start(); + asImp_().endIteration_(nextSolution, currentSolution); + prePostProcessTimer_.stop(); + + break; + } + + // solve the resulting linear equation system + if (asImp_().verbose_()) { + std::cout << "Solve: M deltax^k = r" + << clearRemainingLine + << std::flush; + } + + solveTimer_.start(); + // solve A x = b, where b is the residual, A is its Jacobian and x is the + // update of the solution + linearSolver_.setMatrix(jacobian); + solutionUpdate = 0.0; + bool converged = linearSolver_.solve(solutionUpdate); + solveTimer_.stop(); + + if (!converged) { + solveTimer_.stop(); + if (asImp_().verbose_()) + std::cout << "Newton: Linear solver did not converge\n" << std::flush; + + prePostProcessTimer_.start(); + asImp_().failed_(); + prePostProcessTimer_.stop(); + + return false; + } + + // update the solution + if (asImp_().verbose_()) { + std::cout << "Update: x^(k+1) = x^k - deltax^k" + << clearRemainingLine + << std::flush; + } + + // update the current solution (i.e. uOld) with the delta + // (i.e. u). The result is stored in u + updateTimer_.start(); + asImp_().postSolve_(currentSolution, + residual, + solutionUpdate); + asImp_().update_(nextSolution, currentSolution, solutionUpdate, residual); + updateTimer_.stop(); + + if (asImp_().verbose_() && isatty(fileno(stdout))) + // make sure that the line currently holding the cursor is prestine + std::cout << clearRemainingLine + << std::flush; + + // tell the implementation that we're done with this iteration + prePostProcessTimer_.start(); + asImp_().endIteration_(nextSolution, currentSolution); + prePostProcessTimer_.stop(); + } + } + catch (const Dune::Exception& e) + { + if (asImp_().verbose_()) + std::cout << "Newton method caught exception: \"" + << e.what() << "\"\n" << std::flush; + + prePostProcessTimer_.start(); + asImp_().failed_(); + prePostProcessTimer_.stop(); + + return false; + } + catch (const NumericalProblem& e) + { + if (asImp_().verbose_()) + std::cout << "Newton method caught exception: \"" + << e.what() << "\"\n" << std::flush; + + prePostProcessTimer_.start(); + asImp_().failed_(); + prePostProcessTimer_.stop(); + + return false; + } + + // clear current line on terminal + if (asImp_().verbose_() && isatty(fileno(stdout))) + std::cout << clearRemainingLine + << std::flush; + + // tell the implementation that we're done + prePostProcessTimer_.start(); + asImp_().end_(); + prePostProcessTimer_.stop(); + + // print the timing summary of the time step + if (asImp_().verbose_()) { + Scalar elapsedTot = + linearizeTimer_.realTimeElapsed() + + solveTimer_.realTimeElapsed() + + updateTimer_.realTimeElapsed(); + std::cout << "Linearization/solve/update time: " + << linearizeTimer_.realTimeElapsed() << "(" + << 100 * linearizeTimer_.realTimeElapsed()/elapsedTot << "%)/" + << solveTimer_.realTimeElapsed() << "(" + << 100 * solveTimer_.realTimeElapsed()/elapsedTot << "%)/" + << updateTimer_.realTimeElapsed() << "(" + << 100 * updateTimer_.realTimeElapsed()/elapsedTot << "%)" + << "\n" << std::flush; + } + + + // if we're not converged, tell the implementation that we've failed + if (!asImp_().converged()) { + prePostProcessTimer_.start(); + asImp_().failed_(); + prePostProcessTimer_.stop(); + return false; + } + + // if we converged, tell the implementation that we've succeeded + prePostProcessTimer_.start(); + asImp_().succeeded_(); + prePostProcessTimer_.stop(); + + return true; + } + + /*! + * \brief Suggest a new time-step size based on the old time-step + * size. + * + * The default behavior is to suggest the old time-step size + * scaled by the ratio between the target iterations and the + * iterations required to actually solve the last time-step. + */ + Scalar suggestTimeStepSize(Scalar oldDt) const + { + // be aggressive reducing the time-step size but + // conservative when increasing it. the rationale is + // that we want to avoid failing in the next time + // integration which would be quite expensive + if (numIterations_ > targetIterations_()) { + Scalar percent = Scalar(numIterations_ - targetIterations_())/targetIterations_(); + Scalar nextDt = std::max(problem().minTimeStepSize(), + oldDt / (Scalar{1.0} + percent)); + return nextDt; + } + + Scalar percent = Scalar(targetIterations_() - numIterations_)/targetIterations_(); + Scalar nextDt = std::max(problem().minTimeStepSize(), + oldDt*(Scalar{1.0} + percent / Scalar{1.2})); + return nextDt; + } + + /*! + * \brief Message that should be printed for the user after the + * end of an iteration. + */ + std::ostringstream& endIterMsg() + { return endIterMsgStream_; } + + /*! + * \brief Causes the solve() method to discared the structure of the linear system of + * equations the next time it is called. + */ + void eraseMatrix() + { linearSolver_.eraseMatrix(); } + + /*! + * \brief Returns the linear solver backend object for external use. + */ + LinearSolverBackend& linearSolver() + { return linearSolver_; } + + /*! + * \copydoc linearSolver() + */ + const LinearSolverBackend& linearSolver() const + { return linearSolver_; } + + const Timer& prePostProcessTimer() const + { return prePostProcessTimer_; } + + const Timer& linearizeTimer() const + { return linearizeTimer_; } + + const Timer& solveTimer() const + { return solveTimer_; } + + const Timer& updateTimer() const + { return updateTimer_; } + +protected: + /*! + * \brief Returns true if the Newton method ought to be chatty. + */ + bool verbose_() const + { + return Parameters::Get() && (comm_.rank() == 0); + } + + /*! + * \brief Called before the Newton method is applied to an + * non-linear system of equations. + * + * \param u The initial solution + */ + void begin_(const SolutionVector&) + { + numIterations_ = 0; + + if (Parameters::Get()) { + convergenceWriter_.beginTimeStep(); + } + } + + /*! + * \brief Indicates the beginning of a Newton iteration. + */ + void beginIteration_() + { + // start with a clean message stream + endIterMsgStream_.str(""); + const auto& comm = simulator_.gridView().comm(); + bool succeeded = true; + try { + problem().beginIteration(); + } + catch (const std::exception& e) { + succeeded = false; + + std::cout << "rank " << simulator_.gridView().comm().rank() + << " caught an exception while pre-processing the problem:" << e.what() + << "\n" << std::flush; + } + + succeeded = comm.min(succeeded); + + if (!succeeded) + throw NumericalProblem("pre processing of the problem failed"); + + lastError_ = error_; + } + + /*! + * \brief Linearize the global non-linear system of equations associated with the + * spatial domain. + */ + void linearizeDomain_() + { + model().linearizer().linearizeDomain(); + } + + void linearizeAuxiliaryEquations_() + { + model().linearizer().linearizeAuxiliaryEquations(); + model().linearizer().finalize(); + } + + void preSolve_(const SolutionVector&, + const GlobalEqVector& currentResidual) + { + const auto& constraintsMap = model().linearizer().constraintsMap(); + lastError_ = error_; + Scalar newtonMaxError = Parameters::Get>(); + + // calculate the error as the maximum weighted tolerance of + // the solution's residual + error_ = 0; + for (unsigned dofIdx = 0; dofIdx < currentResidual.size(); ++dofIdx) { + // do not consider auxiliary DOFs for the error + if (dofIdx >= model().numGridDof() || model().dofTotalVolume(dofIdx) <= 0.0) + continue; + + // also do not consider DOFs which are constraint + if (enableConstraints_()) { + if (constraintsMap.count(dofIdx) > 0) + continue; + } + + const auto& r = currentResidual[dofIdx]; + for (unsigned eqIdx = 0; eqIdx < r.size(); ++eqIdx) + error_ = max(std::abs(r[eqIdx] * model().eqWeight(dofIdx, eqIdx)), error_); + } + + // take the other processes into account + error_ = comm_.max(error_); + + // make sure that the error never grows beyond the maximum + // allowed one + if (error_ > newtonMaxError) + throw NumericalProblem("Newton: Error "+std::to_string(double(error_)) + + " is larger than maximum allowed error of " + + std::to_string(double(newtonMaxError))); + } + + /*! + * \brief Update the error of the solution given the previous + * iteration. + * + * For our purposes, the error of a solution is defined as the + * maximum of the weighted residual of a given solution. + * + * \param currentSolution The solution at the beginning the current iteration + * \param currentResidual The residual (i.e., right-hand-side) of the current + * iteration's solution. + * \param solutionUpdate The difference between the current and the next solution + */ + void postSolve_(const SolutionVector&, + const GlobalEqVector&, + GlobalEqVector& solutionUpdate) + { + // loop over the auxiliary modules and ask them to post process the solution + // vector. + auto& model = simulator_.model(); + const auto& comm = simulator_.gridView().comm(); + for (unsigned i = 0; i < model.numAuxiliaryModules(); ++i) { + auto& auxMod = *model.auxiliaryModule(i); + + bool succeeded = true; + try { + auxMod.postSolve(solutionUpdate); + } + catch (const std::exception& e) { + succeeded = false; + + std::cout << "rank " << simulator_.gridView().comm().rank() + << " caught an exception while post processing an auxiliary module:" << e.what() + << "\n" << std::flush; + } + + succeeded = comm.min(succeeded); + + if (!succeeded) + throw NumericalProblem("post processing of an auxilary equation failed"); + } + } + + /*! + * \brief Update the current solution with a delta vector. + * + * Different update strategies, such as chopped updates can be + * implemented by overriding this method. The default behavior is + * use the standard Newton-Raphson update strategy, i.e. + * \f[ u^{k+1} = u^k - \Delta u^k \f] + * + * \param nextSolution The solution vector after the current iteration + * \param currentSolution The solution vector after the last iteration + * \param solutionUpdate The delta vector as calculated by solving the linear system + * of equations + * \param currentResidual The residual vector of the current Newton-Raphson iteraton + */ + void update_(SolutionVector& nextSolution, + const SolutionVector& currentSolution, + const GlobalEqVector& solutionUpdate, + const GlobalEqVector& currentResidual) + { + const auto& constraintsMap = model().linearizer().constraintsMap(); + + // first, write out the current solution to make convergence + // analysis possible + asImp_().writeConvergence_(currentSolution, solutionUpdate); + + // make sure not to swallow non-finite values at this point + if (!std::isfinite(solutionUpdate.one_norm())) + throw NumericalProblem("Non-finite update!"); + + size_t numGridDof = model().numGridDof(); + for (unsigned dofIdx = 0; dofIdx < numGridDof; ++dofIdx) { + if (enableConstraints_()) { + if (constraintsMap.count(dofIdx) > 0) { + const auto& constraints = constraintsMap.at(dofIdx); + asImp_().updateConstraintDof_(dofIdx, + nextSolution[dofIdx], + constraints); + } + else + asImp_().updatePrimaryVariables_(dofIdx, + nextSolution[dofIdx], + currentSolution[dofIdx], + solutionUpdate[dofIdx], + currentResidual[dofIdx]); + } + else + asImp_().updatePrimaryVariables_(dofIdx, + nextSolution[dofIdx], + currentSolution[dofIdx], + solutionUpdate[dofIdx], + currentResidual[dofIdx]); + } + + // update the DOFs of the auxiliary equations + size_t numDof = model().numTotalDof(); + for (size_t dofIdx = numGridDof; dofIdx < numDof; ++dofIdx) { + nextSolution[dofIdx] = currentSolution[dofIdx]; + nextSolution[dofIdx] -= solutionUpdate[dofIdx]; + } + } + + /*! + * \brief Update the primary variables for a degree of freedom which is constraint. + */ + void updateConstraintDof_(unsigned, + PrimaryVariables& nextValue, + const Constraints& constraints) + { nextValue = constraints; } + + /*! + * \brief Update a single primary variables object. + */ + void updatePrimaryVariables_(unsigned, + PrimaryVariables& nextValue, + const PrimaryVariables& currentValue, + const EqVector& update, + const EqVector&) + { + nextValue = currentValue; + nextValue -= update; + } + + /*! + * \brief Write the convergence behaviour of the newton method to + * disk. + * + * This method is called as part of the update proceedure. + */ + void writeConvergence_(const SolutionVector& currentSolution, + const GlobalEqVector& solutionUpdate) + { + if (Parameters::Get()) { + convergenceWriter_.beginIteration(); + convergenceWriter_.writeFields(currentSolution, solutionUpdate); + convergenceWriter_.endIteration(); + } + } + + /*! + * \brief Indicates that one Newton iteration was finished. + * + * \param nextSolution The solution after the current Newton iteration + * \param currentSolution The solution at the beginning of the current Newton iteration + */ + void endIteration_(const SolutionVector&, + const SolutionVector&) + { + ++numIterations_; + + const auto& comm = simulator_.gridView().comm(); + bool succeeded = true; + try { + problem().endIteration(); + } + catch (const std::exception& e) { + succeeded = false; + + std::cout << "rank " << simulator_.gridView().comm().rank() + << " caught an exception while letting the problem post-process:" << e.what() + << "\n" << std::flush; + } + + succeeded = comm.min(succeeded); + + if (!succeeded) + throw NumericalProblem("post processing of the problem failed"); + + if (asImp_().verbose_()) { + std::cout << "Newton iteration " << numIterations_ << "" + << " error: " << error_ + << endIterMsg().str() << "\n" << std::flush; + } + } + + /*! + * \brief Returns true iff another Newton iteration should be done. + */ + bool proceed_() const + { + if (asImp_().numIterations() < 1) + return true; // we always do at least one full iteration + else if (asImp_().converged()) { + // we are below the specified tolerance, so we don't have to + // do more iterations + return false; + } + else if (asImp_().numIterations() >= asImp_().maxIterations_()) { + // we have exceeded the allowed number of steps. If the + // error was reduced by a factor of at least 4, + // in the last iterations we proceed even if we are above + // the maximum number of steps + return error_ * 4.0 < lastError_; + } + + return true; + } + + /*! + * \brief Indicates that we're done solving the non-linear system + * of equations. + */ + void end_() + { + if (Parameters::Get()) { + convergenceWriter_.endTimeStep(); + } + } + + /*! + * \brief Called if the Newton method broke down. + * + * This method is called _after_ end_() + */ + void failed_() + { numIterations_ = targetIterations_() * 2; } + + /*! + * \brief Called if the Newton method was successful. + * + * This method is called _after_ end_() + */ + void succeeded_() + {} + + // optimal number of iterations we want to achieve + int targetIterations_() const + { return Parameters::Get(); } + // maximum number of iterations we do before giving up + int maxIterations_() const + { return Parameters::Get(); } + + static bool enableConstraints_() + { return getPropValue(); } + + Simulator& simulator_; + + Timer prePostProcessTimer_; + Timer linearizeTimer_; + Timer solveTimer_; + Timer updateTimer_; + + std::ostringstream endIterMsgStream_; + + Scalar error_; + Scalar lastError_; + Scalar tolerance_; + + // actual number of iterations done so far + int numIterations_; + + // the linear solver + LinearSolverBackend linearSolver_; + + // the collective communication used by the simulation (i.e. fake + // or MPI) + CollectiveCommunication comm_; + + // the object which writes the convergence behaviour of the Newton + // method to disk + ConvergenceWriter convergenceWriter_; + +private: + Implementation& asImp_() + { return *static_cast(this); } + const Implementation& asImp_() const + { return *static_cast(this); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/nonlinear/newtonmethodparameters.hh b/opm/models/nonlinear/newtonmethodparameters.hh new file mode 100644 index 00000000000..72afa9ecee4 --- /dev/null +++ b/opm/models/nonlinear/newtonmethodparameters.hh @@ -0,0 +1,64 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef EWOMS_NEWTON_METHOD_PARAMETERS_HH +#define EWOMS_NEWTON_METHOD_PARAMETERS_HH + +namespace Opm::Parameters { + +//! The maximum error which may occur in a simulation before the +//! Newton method for the time step is aborted +template +struct NewtonMaxError { static constexpr Scalar value = 1e100; }; + +//! Number of maximum iterations for the Newton method. +struct NewtonMaxIterations { static constexpr int value = 20; }; + +/*! + * \brief The number of iterations at which the Newton method + * should aim at. + * + * This is used to control the time-step size. The heuristic used + * is to scale the last time-step size by the deviation of the + * number of iterations used from the target steps. + */ +struct NewtonTargetIterations { static constexpr int value = 10; }; + +/*! + * \brief The value for the error below which convergence is declared + * + * This value can (and for the porous media models will) be changed to account for grid + * scaling and other effects. + */ +template +struct NewtonTolerance { static constexpr Scalar value = 1e-8; }; + +//! Specifies whether the Newton method should print messages or not +struct NewtonVerbose { static constexpr bool value = true; }; + +//! Specifies whether the convergence rate and the global residual +//! gets written out to disk for every Newton iteration +struct NewtonWriteConvergence { static constexpr bool value = false; }; + +} // end namespace Opm::Parameters + +#endif diff --git a/opm/models/nonlinear/newtonmethodproperties.hh b/opm/models/nonlinear/newtonmethodproperties.hh new file mode 100644 index 00000000000..2bd213cc79e --- /dev/null +++ b/opm/models/nonlinear/newtonmethodproperties.hh @@ -0,0 +1,49 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef EWOMS_NEWTON_METHOD_PROPERTIES_HH +#define EWOMS_NEWTON_METHOD_PROPERTIES_HH + +#include + +namespace Opm::Properties { + +//! Specifies the type of the actual Newton method +template +struct NewtonMethod { using type = UndefinedProperty; }; + +//! The class which linearizes the non-linear system of equations +template +struct Linearizer { using type = UndefinedProperty; }; + +//! Specifies the type of the class which writes out the Newton convergence +template +struct NewtonConvergenceWriter { using type = UndefinedProperty; }; + +//! Specifies whether the convergence rate and the global residual +//! gets written out to disk for every Newton iteration +template +struct ConvergenceWriter { using type = UndefinedProperty; }; + +} // end namespace Opm::Properties + +#endif diff --git a/opm/models/nonlinear/nullconvergencewriter.hh b/opm/models/nonlinear/nullconvergencewriter.hh new file mode 100644 index 00000000000..9fce0d4ebc9 --- /dev/null +++ b/opm/models/nonlinear/nullconvergencewriter.hh @@ -0,0 +1,108 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::NullConvergenceWriter + */ +#ifndef EWOMS_NULL_CONVERGENCE_WRITER_HH +#define EWOMS_NULL_CONVERGENCE_WRITER_HH + +#include +#include + +#include + +namespace Opm::Properties { + +template +struct NewtonMethod; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup Newton + * + * \brief A convergence writer for the Newton method which does nothing + */ +template +class NullConvergenceWriter +{ + using NewtonMethod = GetPropType; + + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + +public: + NullConvergenceWriter(NewtonMethod&) + {} + + /*! + * \brief Called by the Newton method before the actual algorithm + * is started for any given timestep. + */ + void beginTimeStep() + {} + + /*! + * \brief Called by the Newton method before an iteration of the + * Newton algorithm is started. + */ + void beginIteration() + {} + + /*! + * \brief Write the Newton update to disk. + * + * Called after the linear solution is found for an iteration. + * + * \param uLastIter The solution vector of the previous iteration. + * \param deltaU The negative difference between the solution + * vectors of the previous and the current iteration. + */ + void writeFields(const SolutionVector&, + const GlobalEqVector&) + {} + + /*! + * \brief Called by the Newton method after an iteration of the + * Newton algorithm has been completed. + */ + void endIteration() + {} + + /*! + * \brief Called by the Newton method after Newton algorithm + * has been completed for any given timestep. + * + * This method is called regardless of whether the Newton method + * converged or not. + */ + void endTimeStep() + {} +}; + +} // namespace Opm + +#endif diff --git a/opm/models/parallel/gridcommhandles.hh b/opm/models/parallel/gridcommhandles.hh new file mode 100644 index 00000000000..5596322562b --- /dev/null +++ b/opm/models/parallel/gridcommhandles.hh @@ -0,0 +1,284 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Provides data handles for parallel communication which + * operate on DOFs + */ +#ifndef EWOMS_GRID_COMM_HANDLES_HH +#define EWOMS_GRID_COMM_HANDLES_HH + +#include +#include + +namespace Opm { + +/*! + * \brief Data handle for parallel communication which sums up all + * values are attached to DOFs + */ +template +class GridCommHandleSum + : public Dune::CommDataHandleIF, + FieldType> +{ +public: + GridCommHandleSum(Container& container, const EntityMapper& mapper) + : mapper_(mapper), container_(container) + {} + + bool contains(int, int codim) const + { + // return true if the codim is the same as the codim which we + // are asked to communicate with. + return codim == commCodim; + } + +#if DUNE_VERSION_LT(DUNE_GRID, 2, 8) + bool fixedsize(int, int) const +#else + bool fixedSize(int, int) const +#endif + { + // for each DOF we communicate a single value which has a + // fixed size + return true; + } + + template + size_t size(const EntityType&) const + { + // communicate a field type per entity + return 1; + } + + template + void gather(MessageBufferImp& buff, const EntityType& e) const + { + unsigned dofIdx = static_cast(mapper_.index(e)); + buff.write(container_[dofIdx]); + } + + template + void scatter(MessageBufferImp& buff, const EntityType& e, size_t) + { + unsigned dofIdx = static_cast(mapper_.index(e)); + + FieldType tmp; + buff.read(tmp); + container_[dofIdx] += tmp; + } + +private: + const EntityMapper& mapper_; + Container& container_; +}; + +/*! + * \brief Data handle for parallel communication which can be used to + * set the values values of ghost and overlap DOFs from their + * respective master processes. + */ +template +class GridCommHandleGhostSync + : public Dune::CommDataHandleIF, + FieldType> +{ +public: + GridCommHandleGhostSync(Container& container, const EntityMapper& mapper) + : mapper_(mapper), container_(container) + { + } + + bool contains(int, int codim) const + { + // return true if the codim is the same as the codim which we + // are asked to communicate with. + return codim == commCodim; + } + +#if DUNE_VERSION_LT(DUNE_GRID, 2, 8) + bool fixedsize(int, int) const +#else + bool fixedSize(int, int) const +#endif + { + // for each DOF we communicate a single value which has a + // fixed size + return true; + } + + template + size_t size(const EntityType&) const + { + // communicate a field type per entity + return 1; + } + + template + void gather(MessageBufferImp& buff, const EntityType& e) const + { + unsigned dofIdx = static_cast(mapper_.index(e)); + buff.write(container_[dofIdx]); + } + + template + void scatter(MessageBufferImp& buff, const EntityType& e, size_t) + { + unsigned dofIdx = static_cast(mapper_.index(e)); + buff.read(container_[dofIdx]); + } + +private: + const EntityMapper& mapper_; + Container& container_; +}; + +/*! + * \brief Data handle for parallel communication which takes the + * maximum of all values that are attached to DOFs + */ +template +class GridCommHandleMax + : public Dune::CommDataHandleIF, + FieldType> +{ +public: + GridCommHandleMax(Container& container, const EntityMapper& mapper) + : mapper_(mapper), container_(container) + {} + + bool contains(int, int codim) const + { + // return true if the codim is the same as the codim which we + // are asked to communicate with. + return codim == commCodim; + } + +#if DUNE_VERSION_LT(DUNE_GRID, 2, 8) + bool fixedsize(int, int) const +#else + bool fixedSize(int, int) const +#endif + { + // for each DOF we communicate a single value which has a + // fixed size + return true; + } + + template + size_t size(const EntityType&) const + { + // communicate a field type per entity + return 1; + } + + template + void gather(MessageBufferImp& buff, const EntityType& e) const + { + unsigned dofIdx = static_cast(mapper_.index(e)); + buff.write(container_[dofIdx]); + } + + template + void scatter(MessageBufferImp& buff, const EntityType& e, size_t) + { + unsigned dofIdx = static_cast(mapper_.index(e)); + FieldType tmp; + buff.read(tmp); + container_[dofIdx] = std::max(container_[dofIdx], tmp); + } + +private: + const EntityMapper& mapper_; + Container& container_; +}; + +/*! + * \brief Provides data handle for parallel communication which takes + * the minimum of all values that are attached to DOFs + */ +template +class GridCommHandleMin + : public Dune::CommDataHandleIF, + FieldType> +{ +public: + GridCommHandleMin(Container& container, const EntityMapper& mapper) + : mapper_(mapper), container_(container) + {} + + bool contains(int, int codim) const + { + // return true if the codim is the same as the codim which we + // are asked to communicate with. + return codim == commCodim; + } + +#if DUNE_VERSION_LT(DUNE_GRID, 2, 8) + bool fixedsize(int, int) const +#else + bool fixedSize(int, int) const +#endif + { + // for each DOF we communicate a single value which has a + // fixed size + return true; + } + + template + size_t size(const EntityType&) const + { + // communicate a field type per entity + return 1; + } + + template + void gather(MessageBufferImp& buff, const EntityType& e) const + { + unsigned dofIdx = static_cast(mapper_.index(e)); + buff.write(container_[dofIdx]); + } + + template + void scatter(MessageBufferImp& buff, const EntityType& e, size_t) + { + unsigned dofIdx = static_cast(mapper_.index(e)); + FieldType tmp; + buff.read(tmp); + container_[dofIdx] = std::min(container_[dofIdx], tmp); + } + +private: + const EntityMapper& mapper_; + Container& container_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/parallel/mpibuffer.hh b/opm/models/parallel/mpibuffer.hh new file mode 100644 index 00000000000..1725574f9c6 --- /dev/null +++ b/opm/models/parallel/mpibuffer.hh @@ -0,0 +1,238 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::MpiBuffer + */ +#ifndef EWOMS_MPI_BUFFER_HH +#define EWOMS_MPI_BUFFER_HH + +#if HAVE_MPI +#include +#endif + +#include + +#include +#include + +namespace Opm { + +/*! + * \brief Simplifies handling of buffers to be used in conjunction with MPI + */ +template +class MpiBuffer +{ +public: + MpiBuffer() + { + data_ = NULL; + dataSize_ = 0; + + setMpiDataType_(); + updateMpiDataSize_(); + } + + MpiBuffer(size_t size) + { + data_ = new DataType[size]; + dataSize_ = size; + + setMpiDataType_(); + updateMpiDataSize_(); + } + + MpiBuffer(const MpiBuffer&) = default; + + ~MpiBuffer() + { delete[] data_; } + + /*! + * \brief Set the size of the buffer + */ + void resize(size_t newSize) + { + delete[] data_; + data_ = new DataType[newSize]; + dataSize_ = newSize; + updateMpiDataSize_(); + } + + /*! + * \brief Send the buffer asyncronously to a peer process. + */ + void send([[maybe_unused]] unsigned peerRank) + { +#if HAVE_MPI + MPI_Isend(data_, + static_cast(mpiDataSize_), + mpiDataType_, + static_cast(peerRank), + 0, // tag + MPI_COMM_WORLD, + &mpiRequest_); +#endif + } + + /*! + * \brief Wait until the buffer was send to the peer completely. + */ + void wait() + { +#if HAVE_MPI + MPI_Wait(&mpiRequest_, &mpiStatus_); +#endif // HAVE_MPI + } + + /*! + * \brief Receive the buffer syncronously from a peer rank + */ + void receive([[maybe_unused]] unsigned peerRank) + { +#if HAVE_MPI + MPI_Recv(data_, + static_cast(mpiDataSize_), + mpiDataType_, + static_cast(peerRank), + 0, // tag + MPI_COMM_WORLD, + MPI_STATUS_IGNORE); +#endif // HAVE_MPI + } + +#if HAVE_MPI + /*! + * \brief Returns the current MPI_Request object. + * + * This object is only well defined after the send() method. + */ + MPI_Request& request() + { return mpiRequest_; } + /*! + * \brief Returns the current MPI_Request object. + * + * This object is only well defined after the send() method. + */ + const MPI_Request& request() const + { return mpiRequest_; } + + /*! + * \brief Returns the current MPI_Status object. + * + * This object is only well defined after the receive() and wait() methods. + */ + MPI_Status& status() + { return mpiStatus_; } + /*! + * \brief Returns the current MPI_Status object. + * + * This object is only well defined after the receive() and wait() methods. + */ + const MPI_Status& status() const + { return mpiStatus_; } +#endif // HAVE_MPI + + /*! + * \brief Returns the number of data objects in the buffer + */ + size_t size() const + { return dataSize_; } + + /*! + * \brief Provide access to the buffer data. + */ + DataType& operator[](size_t i) + { + assert(i < dataSize_); + return data_[i]; + } + + /*! + * \brief Provide access to the buffer data. + */ + const DataType& operator[](size_t i) const + { + assert(i < dataSize_); + return data_[i]; + } + +private: + void setMpiDataType_() + { +#if HAVE_MPI + // set the MPI data type + if (std::is_same::value) + mpiDataType_ = MPI_CHAR; + else if (std::is_same::value) + mpiDataType_ = MPI_UNSIGNED_CHAR; + else if (std::is_same::value) + mpiDataType_ = MPI_SHORT; + else if (std::is_same::value) + mpiDataType_ = MPI_UNSIGNED_SHORT; + else if (std::is_same::value) + mpiDataType_ = MPI_INT; + else if (std::is_same::value) + mpiDataType_ = MPI_UNSIGNED; + else if (std::is_same::value) + mpiDataType_ = MPI_LONG; + else if (std::is_same::value) + mpiDataType_ = MPI_UNSIGNED_LONG; + else if (std::is_same::value) + mpiDataType_ = MPI_LONG_LONG; + else if (std::is_same::value) + mpiDataType_ = MPI_UNSIGNED_LONG_LONG; + else if (std::is_same::value) + mpiDataType_ = MPI_FLOAT; + else if (std::is_same::value) + mpiDataType_ = MPI_DOUBLE; + else if (std::is_same::value) + mpiDataType_ = MPI_LONG_DOUBLE; + else { + mpiDataType_ = MPI_BYTE; + } +#endif // HAVE_MPI + } + + void updateMpiDataSize_() + { +#if HAVE_MPI + mpiDataSize_ = dataSize_; + if (mpiDataType_ == MPI_BYTE) + mpiDataSize_ *= sizeof(DataType); +#endif // HAVE_MPI + } + + DataType *data_; + size_t dataSize_; +#if HAVE_MPI + size_t mpiDataSize_; + MPI_Datatype mpiDataType_; + MPI_Request mpiRequest_; + MPI_Status mpiStatus_; +#endif // HAVE_MPI +}; + +} // namespace Opm + +#endif diff --git a/opm/models/parallel/mpiutil.hh b/opm/models/parallel/mpiutil.hh new file mode 100644 index 00000000000..6ccc1a12c0b --- /dev/null +++ b/opm/models/parallel/mpiutil.hh @@ -0,0 +1,211 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::MpiBuffer + */ +#ifndef OPM_MATERIAL_MPIUTIL_HH +#define OPM_MATERIAL_MPIUTIL_HH + +#include + +#include +#include +#include +#include + + +#if HAVE_MPI + +#include + + + +namespace mpiutil_details +{ + + template + int packSize() + { + int pack_size; + MPI_Pack_size(1, Dune::MPITraits::getType(), MPI_COMM_WORLD, &pack_size); + return pack_size; + } + + // -------- Packer -------- + template + struct Packer + { + static int size(const T&) + { + return packSize(); + } + + static void pack(const T& content, std::vector& buf, int& offset) + { + MPI_Pack(&content, 1, Dune::MPITraits::getType(), buf.data(), buf.size(), &offset, MPI_COMM_WORLD); + } + + static T unpack(const std::vector& recv_buffer, int& offset) + { + T content; + auto* data = const_cast(recv_buffer.data()); + MPI_Unpack(data, recv_buffer.size(), &offset, &content, 1, Dune::MPITraits::getType(), MPI_COMM_WORLD); + return content; + } + }; + + // -------- Packer, string specialization -------- + template <> + struct Packer + { + static int size(const std::string& content) + { + return packSize() + content.size()*packSize(); + } + + static void pack(const std::string& content, std::vector& buf, int& offset) + { + unsigned int size = content.size(); + Packer::pack(size, buf, offset); + if (size > 0) { + MPI_Pack(const_cast(content.c_str()), size, MPI_CHAR, buf.data(), buf.size(), &offset, MPI_COMM_WORLD); + } + } + + static std::string unpack(const std::vector& recv_buffer, int& offset) + { + unsigned int size = Packer::unpack(recv_buffer, offset); + std::string text; + if (size > 0) { + auto* data = const_cast(recv_buffer.data()); + std::vector chars(size); + MPI_Unpack(data, recv_buffer.size(), &offset, chars.data(), size, MPI_CHAR, MPI_COMM_WORLD); + text = std::string(chars.data(), size); + } + return text; + } + }; + + // -------- Packer, vector partial specialization -------- + template + struct Packer> + { + static int size(const std::string& content) + { + int sz = 0; + sz += packSize(); + for (const T& elem : content) { + sz += Packer::size(elem); + } + return sz; + } + + static void pack(const std::vector& content, std::vector& buf, int& offset) + { + unsigned int size = content.size(); + Packer::pack(size, buf, offset); + for (const T& elem : content) { + Packer::pack(elem); + } + } + + static std::vector unpack(const std::vector& recv_buffer, int& offset) + { + unsigned int size = Packer::unpack(recv_buffer, offset); + std::vector content; + content.reserve(size); + for (unsigned int i = 0; i < size; ++i) { + content.push_back(Packer::unpack(recv_buffer, offset)); + } + return content; + } + }; + + +} // anonymous namespace + + +namespace Opm +{ + + /// From each rank, gather its string (if not empty) into a vector. + inline std::vector gatherStrings(const std::string& local_string) + { + using StringPacker = mpiutil_details::Packer; + + // Pack local messages. + const int message_size = StringPacker::size(local_string); + std::vector buffer(message_size); + int offset = 0; + StringPacker::pack(local_string, buffer, offset); + assert(offset == message_size); + + // Get message sizes and create offset/displacement array for gathering. + int num_processes = -1; + MPI_Comm_size(MPI_COMM_WORLD, &num_processes); + std::vector message_sizes(num_processes); + MPI_Allgather(&message_size, 1, MPI_INT, message_sizes.data(), 1, MPI_INT, MPI_COMM_WORLD); + std::vector displ(num_processes + 1, 0); + std::partial_sum(message_sizes.begin(), message_sizes.end(), displ.begin() + 1); + + // Gather. + std::vector recv_buffer(displ.back()); + MPI_Allgatherv(buffer.data(), buffer.size(), MPI_PACKED, + const_cast(recv_buffer.data()), message_sizes.data(), + displ.data(), MPI_PACKED, + MPI_COMM_WORLD); + + // Unpack and return. + std::vector ret; + for (int process = 0; process < num_processes; ++process) { + offset = displ[process]; + std::string s = StringPacker::unpack(recv_buffer, offset); + if (!s.empty()) { + ret.push_back(s); + } + assert(offset == displ[process + 1]); + } + return ret; + } + +} // namespace Opm + +#else // HAVE_MPI + +namespace Opm +{ + inline std::vector gatherStrings(const std::string& local_string) + { + if (local_string.empty()) { + return {}; + } else { + return { local_string }; + } + } +} // namespace Opm + +#endif // HAVE_MPI + +#endif // OPM_MATERIAL_MPIUTIL_HH + diff --git a/opm/models/parallel/tasklets.hh b/opm/models/parallel/tasklets.hh new file mode 100644 index 00000000000..ad3508d3c82 --- /dev/null +++ b/opm/models/parallel/tasklets.hh @@ -0,0 +1,369 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \brief Provides a mechanism to dispatch work to separate threads + */ +#ifndef EWOMS_TASKLETS_HH +#define EWOMS_TASKLETS_HH + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Opm { + +/*! + * \brief The base class for tasklets. + * + * Tasklets are a generic mechanism for potentially running work in a separate thread. + */ +class TaskletInterface +{ +public: + TaskletInterface(int refCount = 1) + : referenceCount_(refCount) + {} + virtual ~TaskletInterface() {} + virtual void run() = 0; + virtual bool isEndMarker () const { return false; } + + void dereference() + { -- referenceCount_; } + + int referenceCount() const + { return referenceCount_; } + +private: + int referenceCount_; +}; + +/*! + * \brief A simple tasklet that runs a function that returns void and does not take any + * arguments a given number of times. + */ +template +class FunctionRunnerTasklet : public TaskletInterface +{ +public: + FunctionRunnerTasklet(const FunctionRunnerTasklet&) = default; + FunctionRunnerTasklet(int numInvocations, const Fn& fn) + : TaskletInterface(numInvocations) + , fn_(fn) + {} + void run() override + { fn_(); } + +private: + const Fn& fn_; +}; + +class TaskletRunner; + +// this class stores the thread local static attributes for the TaskletRunner class. we +// cannot put them directly into TaskletRunner because defining static members for +// non-template classes in headers leads the linker to choke in case multiple compile +// units are used. +template +struct TaskletRunnerHelper_ +{ + static thread_local TaskletRunner* taskletRunner_; + static thread_local int workerThreadIndex_; +}; + +template +thread_local TaskletRunner* TaskletRunnerHelper_::taskletRunner_ = nullptr; + +template +thread_local int TaskletRunnerHelper_::workerThreadIndex_ = -1; + +/*! + * \brief Handles where a given tasklet is run. + * + * Depending on the number of worker threads, a tasklet can either be run in a separate + * worker thread or by the main thread. + */ +class TaskletRunner +{ + /// \brief Implements a barrier. This class can only be used in the asynchronous case. + class BarrierTasklet : public TaskletInterface + { + public: + BarrierTasklet(unsigned numWorkers) + : TaskletInterface(/*refCount=*/numWorkers) + { + numWorkers_ = numWorkers; + numWaiting_ = 0; + } + + void run() + { wait(); } + + void wait() + { + std::unique_lock lock(barrierMutex_); + + numWaiting_ += 1; + if (numWaiting_ >= numWorkers_ + 1) { + lock.unlock(); + barrierCondition_.notify_all(); + } + else { + const auto& areAllWaiting = + [this]() -> bool + { return this->numWaiting_ >= this->numWorkers_ + 1; }; + + barrierCondition_.wait(lock, /*predicate=*/areAllWaiting); + } + } + + private: + unsigned numWorkers_; + unsigned numWaiting_; + + std::condition_variable barrierCondition_; + std::mutex barrierMutex_; + }; + + /// \brief TerminateThreadTasklet class + /// Empty tasklet marking thread termination. + class TerminateThreadTasklet : public TaskletInterface + { + public: + void run() + { } + + bool isEndMarker() const + { return true; } + }; + +public: + // prohibit copying of tasklet runners + TaskletRunner(const TaskletRunner&) = delete; + + /*! + * \brief Creates a tasklet runner with numWorkers underling threads for doing work. + * + * The number of worker threads may be 0. In this case, all work is done by the main + * thread (synchronous mode). + */ + TaskletRunner(unsigned numWorkers) + { + threads_.resize(numWorkers); + for (unsigned i = 0; i < numWorkers; ++i) + // create a worker thread + threads_[i].reset(new std::thread(startWorkerThread_, this, i)); + } + + /*! + * \brief Destructor + * + * If worker threads were created to run the tasklets, this method waits until all + * worker threads have been terminated, i.e. all scheduled tasklets are guaranteed to + * be completed. + */ + ~TaskletRunner() + { + if (threads_.size() > 0) { + // dispatch a tasklet which will terminate the worker thread + dispatch(std::make_shared()); + + // wait until all worker threads have terminated + for (auto& thread : threads_) + thread->join(); + } + } + + bool failure() const + { + return this->failureFlag_.load(std::memory_order_relaxed); + } + + /*! + * \brief Returns the index of the current worker thread. + * + * If the current thread is not a worker thread, -1 is returned. + */ + int workerThreadIndex() const + { + if (TaskletRunnerHelper_::taskletRunner_ != this) + return -1; + return TaskletRunnerHelper_::workerThreadIndex_; + } + + /*! + * \brief Returns the number of worker threads for the tasklet runner. + */ + int numWorkerThreads() const + { return threads_.size(); } + + /*! + * \brief Add a new tasklet. + * + * The tasklet is either run immediately or deferred to a separate thread. + */ + void dispatch(std::shared_ptr tasklet) + { + if (threads_.empty()) { + // run the tasklet immediately in synchronous mode. + while (tasklet->referenceCount() > 0) { + tasklet->dereference(); + try { + tasklet->run(); + } + catch (const std::exception& e) { + std::cerr << "ERROR: Uncaught std::exception when running tasklet: " << e.what() << ". Trying to continue.\n"; + failureFlag_.store(true, std::memory_order_relaxed); + } + catch (...) { + std::cerr << "ERROR: Uncaught exception (general type) when running tasklet. Trying to continue.\n"; + failureFlag_.store(true, std::memory_order_relaxed); + } + } + } + else { + // lock mutex for the tasklet queue to make sure that nobody messes with the + // task queue + taskletQueueMutex_.lock(); + + // add the tasklet to the queue + taskletQueue_.push(tasklet); + + taskletQueueMutex_.unlock(); + + workAvailableCondition_.notify_all(); + } + } + + /*! + * \brief Convenience method to construct a new function runner tasklet and dispatch it immediately. + */ + template + std::shared_ptr > dispatchFunction(Fn &fn, int numInvocations=1) + { + using Tasklet = FunctionRunnerTasklet; + auto tasklet = std::make_shared(numInvocations, fn); + this->dispatch(tasklet); + return tasklet; + } + + /*! + * \brief Make sure that all tasklets have been completed after this method has been called + */ + void barrier() + { + unsigned numWorkers = threads_.size(); + if (numWorkers == 0) + // nothing needs to be done to implement a barrier in synchronous mode + return; + + // dispatch a barrier tasklet and wait until it has been run by the worker thread + auto barrierTasklet = std::make_shared(numWorkers); + dispatch(barrierTasklet); + + barrierTasklet->wait(); + } +private: + // Atomic flag that is set to failure if any of the tasklets run by the TaskletRunner fails. + // This flag is checked before new tasklets run or get dispatched and in case it is true, the + // thread execution will be stopped / no new tasklets will be started and the program will abort. + // To set the flag and load the flag, we use std::memory_order_relaxed. + // Atomic operations tagged memory_order_relaxed are not synchronization operations; they do not + // impose an order among concurrent memory accesses. They guarantee atomicity and modification order + // consistency. This is the right choice for the setting here, since it is enough to broadcast failure + // before new run or get dispatched. + std::atomic failureFlag_ = false; + +protected: + // main function of the worker thread + static void startWorkerThread_(TaskletRunner* taskletRunner, int workerThreadIndex) + { + TaskletRunnerHelper_::taskletRunner_ = taskletRunner; + TaskletRunnerHelper_::workerThreadIndex_ = workerThreadIndex; + + taskletRunner->run_(); + } + + //! do the work until the queue received an end tasklet + void run_() + { + while (true) { + + // wait until tasklets have been pushed to the queue. first we need to lock + // mutex for access to taskletQueue_ + std::unique_lock lock(taskletQueueMutex_); + + const auto& workIsAvailable = + [this]() -> bool + { return !taskletQueue_.empty(); }; + + if (!workIsAvailable()) + workAvailableCondition_.wait(lock, /*predicate=*/workIsAvailable); + + // remove tasklet from queue + std::shared_ptr tasklet = taskletQueue_.front(); + + // if tasklet is an end marker, terminate the thread and DO NOT remove the + // tasklet. + if (tasklet->isEndMarker()) { + if(taskletQueue_.size() > 1) + throw std::logic_error("TaskletRunner: Not all queued tasklets were executed"); + taskletQueueMutex_.unlock(); + return; + } + + tasklet->dereference(); + if (tasklet->referenceCount() == 0) + // remove tasklets from the queue as soon as their reference count + // reaches zero, i.e. the tasklet has been run often enough. + taskletQueue_.pop(); + lock.unlock(); + + // execute tasklet + try { + tasklet->run(); + } + catch (const std::exception& e) { + std::cerr << "ERROR: Uncaught std::exception when running tasklet: " << e.what() << ".\n"; + failureFlag_.store(true, std::memory_order_relaxed); + } + catch (...) { + std::cerr << "ERROR: Uncaught exception when running tasklet.\n"; + failureFlag_.store(true, std::memory_order_relaxed); + } + } + } + + std::vector > threads_; + std::queue > taskletQueue_; + std::mutex taskletQueueMutex_; + std::condition_variable workAvailableCondition_; +}; + +} // end namespace Opm +#endif diff --git a/opm/models/parallel/threadedentityiterator.hh b/opm/models/parallel/threadedentityiterator.hh new file mode 100644 index 00000000000..e57f29180d7 --- /dev/null +++ b/opm/models/parallel/threadedentityiterator.hh @@ -0,0 +1,99 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::ThreadedEntityIterator + */ +#ifndef EWOMS_THREADED_ENTITY_ITERATOR_HH +#define EWOMS_THREADED_ENTITY_ITERATOR_HH + +#include +#include + +namespace Opm { + +/*! + * \brief Provides an STL-iterator like interface to iterate over the enties of a + * GridView in OpenMP threaded applications + * + * ATTENTION: This class must be instantiated in a sequential context! + */ +template +class ThreadedEntityIterator +{ + using Entity = typename GridView::template Codim::Entity; + using EntityIterator = typename GridView::template Codim::Iterator; +public: + ThreadedEntityIterator(const GridView& gridView) + : sequentialIt_(gridView.template begin()) + , sequentialEnd_(gridView.template end()) + { } + + ThreadedEntityIterator(const ThreadedEntityIterator& other) = default; + + // begin iterating over the grid in parallel + EntityIterator beginParallel() + { + mutex_.lock(); + auto tmp = sequentialIt_; + if (sequentialIt_ != sequentialEnd_) + ++sequentialIt_; // make the next thread look at the next element + mutex_.unlock(); + + return tmp; + } + + // returns true if the last element was reached + bool isFinished(const EntityIterator& it) const + { return it == sequentialEnd_; } + + // make sure that the loop over the grid is finished + void setFinished() + { + mutex_.lock(); + sequentialIt_ = sequentialEnd_; + mutex_.unlock(); + } + + // prefix increment: goes to the next element which is not yet worked on by any + // thread + EntityIterator increment() + { + mutex_.lock(); + auto tmp = sequentialIt_; + if (sequentialIt_ != sequentialEnd_) + ++sequentialIt_; + mutex_.unlock(); + + return tmp; + } + +private: + EntityIterator sequentialIt_; + EntityIterator sequentialEnd_; + + std::mutex mutex_; +}; +} // namespace Opm + +#endif diff --git a/opm/models/parallel/threadmanager.hh b/opm/models/parallel/threadmanager.hh new file mode 100644 index 00000000000..2aa83d8fcff --- /dev/null +++ b/opm/models/parallel/threadmanager.hh @@ -0,0 +1,141 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::ThreadManager + */ +#ifndef EWOMS_THREAD_MANAGER_HH +#define EWOMS_THREAD_MANAGER_HH + +#ifdef _OPENMP +#include +#endif + +#include +#include +#include + +#include + +namespace Opm { + +/*! + * \brief Simplifies multi-threaded capabilities. + */ +template +class ThreadManager +{ +public: + enum { +#if defined(_OPENMP) || DOXYGEN + //! Specify whether OpenMP is really available or not + isFake = false +#else + isFake = true +#endif + }; + + /*! + * \brief Register all run-time parameters of the thread manager. + */ + static void registerParameters() + { + Parameters::Register + ("The maximum number of threads to be instantiated per process " + "('-1' means 'automatic')"); + } + + /*! + * \brief Initialize number of threads used thread manager. + * + * \param queryCommandLineParameter if set to true we will query ThreadsPerProcess + * and if set (disregard the environment variable OPM_NUM_THREADS). + * If false we will assume that the number of OpenMP threads is already set + * outside of this function (e.g. by OPM_NUM_THREADS or in the simulator by + * the ThreadsPerProcess parameter). + */ + static void init(bool queryCommandLineParameter = true) + { + if (queryCommandLineParameter) + { + numThreads_ = Parameters::Get(); + + // some safety checks. This is pretty ugly macro-magic, but so what? +#if !defined(_OPENMP) + if (numThreads_ != 1 && numThreads_ != -1) + throw std::invalid_argument("OpenMP is not available. The only valid values for " + "threads-per-process is 1 and -1 but it is "+std::to_string(numThreads_)+"!"); + numThreads_ = 1; +#elif !defined NDEBUG && defined DUNE_INTERFACECHECK + if (numThreads_ != 1) + throw std::invalid_argument("You explicitly enabled Barton-Nackman interface checking in Dune. " + "The Dune implementation of this is currently incompatible with " + "thread parallelism!"); + numThreads_ = 1; +#else + + if (numThreads_ == 0) + throw std::invalid_argument("Zero threads per process are not possible: It must be at least 1, " + "(or -1 for 'automatic')!"); +#endif + +#ifdef _OPENMP + // actually limit the number of threads + if (numThreads_ > 0) + omp_set_num_threads(numThreads_); +#endif + } + +#ifdef _OPENMP + // get the number of threads which are used in the end. + numThreads_ = omp_get_max_threads(); +#endif + } + + /*! + * \brief Return the maximum number of threads of the current process. + */ + static unsigned maxThreads() + { return static_cast(numThreads_); } + + /*! + * \brief Return the index of the current OpenMP thread + */ + static unsigned threadId() + { +#ifdef _OPENMP + return static_cast(omp_get_thread_num()); +#else + return 0; +#endif + } + +private: + static int numThreads_; +}; + +template +int ThreadManager::numThreads_ = 1; +} // namespace Opm + +#endif diff --git a/opm/models/ptflash/flashindices.hh b/opm/models/ptflash/flashindices.hh new file mode 100644 index 00000000000..6fc33bcbc79 --- /dev/null +++ b/opm/models/ptflash/flashindices.hh @@ -0,0 +1,73 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashIndices + */ +#ifndef OPM_PTFLASH_INDICES_HH +#define OPM_PTFLASH_INDICES_HH + +#include + +namespace Opm { + +/*! + * \ingroup FlashModel + * + * \brief Defines the primary variable and equation indices for the + * compositional multi-phase model based on PT flash calculations. + * + * \tparam PVOffset The first index in a primary variable vector. + */ +template +class FlashIndices + : public EnergyIndices(), + getPropValue()> +{ + enum { numComponents = getPropValue() }; + enum { enableEnergy = getPropValue() }; + using EnergyIndices = Opm::EnergyIndices; + +public: + //! number of equations/primary variables + static const int numEq = numComponents + EnergyIndices::numEq_; + + // Primary variable indices + + //! Index of the pressure + static constexpr int pressure0Idx = PVOffset; + + //! Index of the molefraction of the first component + static constexpr int z0Idx = pressure0Idx + 1; + + // equation indices + + //! Index of the mass conservation equation for the first + //! component. + static const int conti0EqIdx = PVOffset; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ptflash/flashintensivequantities.hh b/opm/models/ptflash/flashintensivequantities.hh new file mode 100644 index 00000000000..52f3c309b6d --- /dev/null +++ b/opm/models/ptflash/flashintensivequantities.hh @@ -0,0 +1,328 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashIntensiveQuantities + */ +#ifndef OPM_FLASH_INTENSIVE_QUANTITIES_HH +#define OPM_FLASH_INTENSIVE_QUANTITIES_HH + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +namespace Opm { + +/*! + * \ingroup FlashModel + * \ingroup IntensiveQuantities + * + * \brief Contains the intensive quantities of the ptflash-based compositional multi-phase model + */ +template +class FlashIntensiveQuantities + : public GetPropType + , public DiffusionIntensiveQuantities() > + , public EnergyIntensiveQuantities() > + , public GetPropType::FluxIntensiveQuantities +{ + using ParentType = GetPropType; + + using ElementContext = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using Indices = GetPropType; + using FluxModule = GetPropType; + using GridView = GetPropType; + using ThreadManager = GetPropType; + + // primary variable indices + enum { z0Idx = Indices::z0Idx }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + enum { enableEnergy = getPropValue() }; + enum { dimWorld = GridView::dimensionworld }; + enum { pressure0Idx = Indices::pressure0Idx }; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using FlashSolver = GetPropType; + + using ComponentVector = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + + using FluxIntensiveQuantities = typename FluxModule::FluxIntensiveQuantities; + using DiffusionIntensiveQuantities = Opm::DiffusionIntensiveQuantities; + using EnergyIntensiveQuantities = Opm::EnergyIntensiveQuantities; + +public: + //! The type of the object returned by the fluidState() method + using FluidState = Opm::CompositionalFluidState; + + FlashIntensiveQuantities() = default; + + FlashIntensiveQuantities(const FlashIntensiveQuantities& other) = default; + + FlashIntensiveQuantities& operator=(const FlashIntensiveQuantities& other) = default; + + /*! + * \copydoc IntensiveQuantities::update + */ + void update(const ElementContext& elemCtx, unsigned dofIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, dofIdx, timeIdx); + EnergyIntensiveQuantities::updateTemperatures_(fluidState_, elemCtx, dofIdx, timeIdx); + + const auto& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + const auto& problem = elemCtx.problem(); + + const Scalar flashTolerance = Parameters::Get>(); + const int flashVerbosity = Parameters::Get(); + const std::string flashTwoPhaseMethod = Parameters::Get(); + + // extract the total molar densities of the components + ComponentVector z(0.); + { + Evaluation lastZ = 1.0; + for (unsigned compIdx = 0; compIdx < numComponents - 1; ++compIdx) { + z[compIdx] = priVars.makeEvaluation(z0Idx + compIdx, timeIdx); + lastZ -= z[compIdx]; + } + z[numComponents - 1] = lastZ; + + Evaluation sumz = 0.0; + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + z[compIdx] = Opm::max(z[compIdx], 1e-8); + sumz +=z[compIdx]; + } + z /= sumz; + } + + Evaluation p = priVars.makeEvaluation(pressure0Idx, timeIdx); + for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + fluidState_.setPressure(phaseIdx, p); + + // Get initial K and L from storage initially (if enabled) + const auto *hint = elemCtx.thermodynamicHint(dofIdx, timeIdx); + if (hint) { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + const Evaluation& Ktmp = hint->fluidState().K(compIdx); + fluidState_.setKvalue(compIdx, Ktmp); + } + const Evaluation& Ltmp = hint->fluidState().L(); + fluidState_.setLvalue(Ltmp); + } + else if (timeIdx == 0 && elemCtx.thermodynamicHint(dofIdx, 1)) { + // checking the storage cache + const auto& hint2 = elemCtx.thermodynamicHint(dofIdx, 1); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + const Evaluation& Ktmp = hint2->fluidState().K(compIdx); + fluidState_.setKvalue(compIdx, Ktmp); + } + const Evaluation& Ltmp = hint2->fluidState().L(); + fluidState_.setLvalue(Ltmp); + } + else { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + const Evaluation Ktmp = fluidState_.wilsonK_(compIdx); + fluidState_.setKvalue(compIdx, Ktmp); + } + const Evaluation& Ltmp = -1.0; + fluidState_.setLvalue(Ltmp); + } + + ///////////// + // Compute the phase compositions and densities + ///////////// + if (flashVerbosity >= 1) { + const int spatialIdx = elemCtx.globalSpaceIndex(dofIdx, timeIdx); + std::cout << " updating the intensive quantities for Cell " << spatialIdx << std::endl; + } + FlashSolver::solve(fluidState_, z, flashTwoPhaseMethod, flashTolerance, flashVerbosity); + + if (flashVerbosity >= 5) { + // printing of flash result after solve + const int spatialIdx = elemCtx.globalSpaceIndex(dofIdx, timeIdx); + std::cout << " \n After flash solve for cell " << spatialIdx << std::endl; + ComponentVector x, y; + for (unsigned comp_idx = 0; comp_idx < numComponents; ++comp_idx) { + x[comp_idx] = fluidState_.moleFraction(FluidSystem::oilPhaseIdx, comp_idx); + y[comp_idx] = fluidState_.moleFraction(FluidSystem::gasPhaseIdx, comp_idx); + } + for (unsigned comp_idx = 0; comp_idx < numComponents; ++comp_idx) { + std::cout << " x for component: " << comp_idx << " is:" << std::endl; + std::cout << x[comp_idx] << std::endl; + + std::cout << " y for component: " << comp_idx << "is:" << std::endl; + std::cout << y[comp_idx] << std::endl; + } + const Evaluation& L = fluidState_.L(); + std::cout << " L is:" << std::endl; + std::cout << L << std::endl; + } + + + // Update phases + typename FluidSystem::template ParameterCache paramCache; + paramCache.updatePhase(fluidState_, FluidSystem::oilPhaseIdx); + + const Scalar R = Opm::Constants::R; + Evaluation Z_L = (paramCache.molarVolume(FluidSystem::oilPhaseIdx) * fluidState_.pressure(FluidSystem::oilPhaseIdx) )/ + (R * fluidState_.temperature(FluidSystem::oilPhaseIdx)); + paramCache.updatePhase(fluidState_, FluidSystem::gasPhaseIdx); + Evaluation Z_V = (paramCache.molarVolume(FluidSystem::gasPhaseIdx) * fluidState_.pressure(FluidSystem::gasPhaseIdx) )/ + (R * fluidState_.temperature(FluidSystem::gasPhaseIdx)); + + + // Update saturation + // \Note: the current implementation assume oil-gas system. + Evaluation L = fluidState_.L(); + Evaluation So = Opm::max((L * Z_L / ( L * Z_L + (1 - L) * Z_V)), 0.0); + Evaluation Sg = Opm::max(1 - So, 0.0); + Scalar sumS = Opm::getValue(So) + Opm::getValue(Sg); + So /= sumS; + Sg /= sumS; + + fluidState_.setSaturation(0, So); + fluidState_.setSaturation(1, Sg); + + fluidState_.setCompressFactor(0, Z_L); + fluidState_.setCompressFactor(1, Z_V); + + // Print saturation + if (flashVerbosity >= 5) { + std::cout << "So = " << So < + ("Flash solver verbosity level"); + Parameters::Register + ("Method for solving vapor-liquid composition. Available options include: " + "ssi, newton, ssi+newton"); + + Parameters::SetDefault>(1e-12); + Parameters::SetDefault(true); + + // since thermodynamic hints are basically free if the cache for intensive quantities is + // enabled, and this model usually shows quite a performance improvment if they are + // enabled, let's enable them by default. + Parameters::SetDefault(true); + } + + /*! + * \copydoc FvBaseDiscretization::primaryVarName + */ + std::string primaryVarName(unsigned pvIdx) const + { + const std::string& tmp = EnergyModule::primaryVarName(pvIdx); + if (!tmp.empty()) + return tmp; + + std::ostringstream oss; + if (Indices::z0Idx <= pvIdx && pvIdx < Indices::z0Idx + numComponents - 1) + oss << "z_," << FluidSystem::componentName(/*compIdx=*/pvIdx - Indices::z0Idx); + else if (pvIdx==Indices::pressure0Idx) + oss << "pressure_" << FluidSystem::phaseName(0); + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::eqName + */ + std::string eqName(unsigned eqIdx) const + { + const std::string& tmp = EnergyModule::eqName(eqIdx); + if (!tmp.empty()) + return tmp; + + std::ostringstream oss; + if (Indices::conti0EqIdx <= eqIdx && eqIdx < Indices::conti0EqIdx + + numComponents) { + unsigned compIdx = eqIdx - Indices::conti0EqIdx; + oss << "continuity^" << FluidSystem::componentName(compIdx); + } + else + assert(false); + + return oss.str(); + } + + void registerOutputModules_() + { + ParentType::registerOutputModules_(); + + // add the VTK output modules which are meaningful for the model + this->addOutputModule(new Opm::VtkCompositionModule(this->simulator_)); + this->addOutputModule(new Opm::VtkPTFlashModule(this->simulator_)); + if (enableDiffusion) + this->addOutputModule(new Opm::VtkDiffusionModule(this->simulator_)); + if (enableEnergy) + this->addOutputModule(new Opm::VtkEnergyModule(this->simulator_)); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/ptflash/flashnewtonmethod.hh b/opm/models/ptflash/flashnewtonmethod.hh new file mode 100644 index 00000000000..9682a4e6df8 --- /dev/null +++ b/opm/models/ptflash/flashnewtonmethod.hh @@ -0,0 +1,137 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashNewtonMethod + */ +#ifndef OPM_FLASH_NEWTON_METHOD_HH +#define OPM_FLASH_NEWTON_METHOD_HH + +#include + +#include + +#include + +namespace Opm::Properties { + +template +struct DiscNewtonMethod; + +} // namespace Opm::Properties + +namespace Opm { +/*! + * \ingroup FlashModel + * + * \brief A Newton solver specific to the PTFlash model. + */ + +template +class FlashNewtonMethod : public GetPropType +{ + using ParentType = GetPropType; + + using PrimaryVariables = GetPropType; + using EqVector = GetPropType; + using Simulator = GetPropType; + using Scalar = GetPropType; + using Indices = GetPropType; + + enum { pressure0Idx = Indices::pressure0Idx }; + enum { z0Idx = Indices::z0Idx }; + enum { numComponents = getPropValue() }; + +public: + /*! + * \copydoc FvBaseNewtonMethod::FvBaseNewtonMethod(Problem& ) + */ + FlashNewtonMethod(Simulator& simulator) : ParentType(simulator) + {} + +protected: + friend ParentType; + friend NewtonMethod; + + /*! + * \copydoc FvBaseNewtonMethod::updatePrimaryVariables_ + */ + void updatePrimaryVariables_(unsigned /* globalDofIdx */, + PrimaryVariables& nextValue, + const PrimaryVariables& currentValue, + const EqVector& update, + const EqVector& /* currentResidual */) + { + // normal Newton-Raphson update + nextValue = currentValue; + nextValue -= update; + + //// + // Pressure updates + //// + // limit pressure reference change to 20% of the total value per iteration + constexpr Scalar max_percent_change = 0.2; + constexpr Scalar upper_bound = 1. + max_percent_change; + constexpr Scalar lower_bound = 1. - max_percent_change; + clampValue_(nextValue[pressure0Idx], + currentValue[pressure0Idx] * lower_bound, + currentValue[pressure0Idx] * upper_bound); + + //// + // z updates + //// + // restrict update to at most 0.1 + Scalar maxDeltaZ = 0.0; // in update vector + Scalar sumDeltaZ = 0.0; // changes in last component (not in update vector) + for (unsigned compIdx = 0; compIdx < numComponents - 1; ++compIdx) { + maxDeltaZ = std::max(std::abs(update[z0Idx + compIdx]), maxDeltaZ); + sumDeltaZ += update[z0Idx + compIdx]; + } + maxDeltaZ = std::max(std::abs(sumDeltaZ), maxDeltaZ); + + // if max. update is above 0.2, restrict that one to 0.2 and adjust the rest accordingly (s.t. last comp. update is sum of the changes in update vector) + // \Note: original code uses 0.1, while 0.1 looks like having problem make it converged. So there is some more to investigate here + constexpr Scalar deltaz_limit = 0.2; + if (maxDeltaZ > deltaz_limit) { + Scalar alpha = deltaz_limit / maxDeltaZ; + for (unsigned compIdx = 0; compIdx < numComponents - 1; ++compIdx) { + nextValue[z0Idx + compIdx] = currentValue[z0Idx + compIdx] - alpha * update[z0Idx + compIdx]; + } + } + + // ensure that z-values are less than tol or more than 1-tol + Scalar tol = 1e-8; + for (unsigned compIdx = 0; compIdx < numComponents - 1; ++compIdx) { + clampValue_(nextValue[z0Idx + compIdx], tol, 1-tol); + } + } +private: + void clampValue_(Scalar& val, Scalar minVal, Scalar maxVal) const + { + val = std::clamp(val, minVal, maxVal); + } + +}; // class FlashNewtonMethod +} // namespace Opm +#endif diff --git a/opm/models/ptflash/flashparameters.hh b/opm/models/ptflash/flashparameters.hh new file mode 100644 index 00000000000..a1fb0e72cc1 --- /dev/null +++ b/opm/models/ptflash/flashparameters.hh @@ -0,0 +1,45 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup FlashModel + * + * \brief Declares the parameters for the compositional + * multi-phase model based on flash calculations. + */ +#ifndef EWOMS_PTFLASH_PARAMETERS_HH +#define EWOMS_PTFLASH_PARAMETERS_HH + +#include + +namespace Opm::Parameters { + +//! Two-phase flash method +struct FlashTwoPhaseMethod { static constexpr auto value = "ssi"; }; + +//! The verbosity level of the flash solver +struct FlashVerbosity { static constexpr int value = 0; }; + +} // namespace Opm::Parameters + +#endif diff --git a/opm/models/ptflash/flashprimaryvariables.hh b/opm/models/ptflash/flashprimaryvariables.hh new file mode 100644 index 00000000000..2c84db947e0 --- /dev/null +++ b/opm/models/ptflash/flashprimaryvariables.hh @@ -0,0 +1,160 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::FlashPrimaryVariables + */ +#ifndef OPM_PTFLASH_PRIMARY_VARIABLES_HH +#define OPM_PTFLASH_PRIMARY_VARIABLES_HH + +#include "flashindices.hh" + +#include +#include + +#include +#include +#include + +#include + +#include + +namespace Opm { + +/*! + * \ingroup FlashModel + * + * \brief Represents the primary variables used by the compositional + * flow model based on flash calculations. + * + * This class is basically a Dune::FieldVector which can retrieve its + * contents from an aribitatry fluid state. + */ +template +class FlashPrimaryVariables : public FvBasePrimaryVariables +{ + using ParentType = FvBasePrimaryVariables; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using MaterialLawParams = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + // primary variable indices + enum { z0Idx = Indices::z0Idx }; + enum { pressure0Idx = Indices::pressure0Idx }; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + using Toolbox = typename Opm::MathToolbox; + using EnergyModule = Opm::EnergyModule()>; + +public: + FlashPrimaryVariables() : ParentType() + { Opm::Valgrind::SetDefined(*this); } + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(Scalar) + */ + FlashPrimaryVariables(Scalar value) : ParentType(value) + { + Opm::Valgrind::CheckDefined(value); + Opm::Valgrind::SetDefined(*this); + } + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(const + * ImmisciblePrimaryVariables& ) + */ + FlashPrimaryVariables(const FlashPrimaryVariables& value) = default; + FlashPrimaryVariables& operator=(const FlashPrimaryVariables& value) = default; + + /*! + * \copydoc ImmisciblePrimaryVariables::assignMassConservative + */ + template + void assignMassConservative(const FluidState& fluidState, + const MaterialLawParams&, + bool = false) + { + // there is no difference between naive and mass conservative + // assignment in the flash model. (we only need the total + // concentrations.) + assignNaive(fluidState); + } + + /*! + * \copydoc ImmisciblePrimaryVariables::assignNaive + */ + template + void assignNaive(const FluidState& fluidState) + { + // reset everything + (*this) = 0.0; + + // assign the phase temperatures. this is out-sourced to + // the energy module + EnergyModule::setPriVarTemperatures(*this, fluidState); + + // determine the component fractions + Dune::FieldVector z(0.0); + Scalar sumMoles = 0.0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + Scalar tmp = Opm::getValue(fluidState.molarity(phaseIdx, compIdx) * fluidState.saturation(phaseIdx)); + z[compIdx] += Opm::max(tmp, 1e-8); + sumMoles += tmp; + } + } + z /= sumMoles; + + for (int i = 0; i < numComponents - 1; ++i) + (*this)[z0Idx + i] = z[i]; + + (*this)[pressure0Idx] = Opm::getValue(fluidState.pressure(0)); + } + + /*! + * \brief Prints the names of the primary variables and their values. + * + * \param os The \c std::ostream which should be used for the output. + */ + void print(std::ostream& os = std::cout) const + { + os << "(p_" << FluidSystem::phaseName(0) << " = " + << this->operator[](pressure0Idx); + for (unsigned compIdx = 0; compIdx < numComponents - 2; ++compIdx) { + os << ", z_" << FluidSystem::componentName(compIdx) << " = " + << this->operator[](z0Idx + compIdx); + } + os << ")" << std::flush; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/pvs/pvsboundaryratevector.hh b/opm/models/pvs/pvsboundaryratevector.hh new file mode 100644 index 00000000000..6ebeb127ec3 --- /dev/null +++ b/opm/models/pvs/pvsboundaryratevector.hh @@ -0,0 +1,204 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PvsBoundaryRateVector + */ +#ifndef EWOMS_PVS_BOUNDARY_RATE_VECTOR_HH +#define EWOMS_PVS_BOUNDARY_RATE_VECTOR_HH + +#include "pvsproperties.hh" + +#include +#include + +namespace Opm { + +/*! + * \ingroup PvsModel + * + * \brief Implements a rate vector on the boundary for the fully + * implicit compositional multi-phase primary variable + * switching compositional model. + */ +template +class PvsBoundaryRateVector : public GetPropType +{ + using ParentType = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Indices = GetPropType; + + enum { numEq = getPropValue() }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { enableEnergy = getPropValue() }; + + using EnergyModule = Opm::EnergyModule; + using Toolbox = Opm::MathToolbox; + +public: + PvsBoundaryRateVector() + : ParentType() + {} + + /*! + * \copydoc + * ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(Scalar) + */ + PvsBoundaryRateVector(const Evaluation& value) + : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(const + * ImmiscibleBoundaryRateVector& ) + */ + PvsBoundaryRateVector(const PvsBoundaryRateVector& value) = default; + PvsBoundaryRateVector& operator=(const PvsBoundaryRateVector& value) = default; + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setFreeFlow + */ + template + void setFreeFlow(const Context& context, unsigned bfIdx, unsigned timeIdx, const FluidState& fluidState) + { + ExtensiveQuantities extQuants; + extQuants.updateBoundary(context, bfIdx, timeIdx, fluidState); + const auto& insideIntQuants = context.intensiveQuantities(bfIdx, timeIdx); + unsigned focusDofIdx = context.focusDofIndex(); + unsigned interiorDofIdx = context.interiorScvIndex(bfIdx, timeIdx); + + //////// + // advective fluxes of all components in all phases + //////// + (*this) = Evaluation(0.0); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Evaluation density; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + density = fluidState.density(phaseIdx); + else + density = Opm::getValue(fluidState.density(phaseIdx)); + } + else if (focusDofIdx == interiorDofIdx) + density = insideIntQuants.fluidState().density(phaseIdx); + else + density = Opm::getValue(insideIntQuants.fluidState().density(phaseIdx)); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + Evaluation molarity; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + molarity = fluidState.molarity(phaseIdx, compIdx); + else + molarity = Opm::getValue(fluidState.molarity(phaseIdx, compIdx)); + } + else if (focusDofIdx == interiorDofIdx) + molarity = insideIntQuants.fluidState().molarity(phaseIdx, compIdx); + else + molarity = Opm::getValue(insideIntQuants.fluidState().molarity(phaseIdx, compIdx)); + + // add advective flux of current component in current + // phase + (*this)[conti0EqIdx + compIdx] += extQuants.volumeFlux(phaseIdx)*molarity; + } + + if (enableEnergy) { + Evaluation specificEnthalpy; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + specificEnthalpy = fluidState.enthalpy(phaseIdx); + else + specificEnthalpy = Opm::getValue(fluidState.enthalpy(phaseIdx)); + } + else if (focusDofIdx == interiorDofIdx) + specificEnthalpy = insideIntQuants.fluidState().enthalpy(phaseIdx); + else + specificEnthalpy = Opm::getValue(insideIntQuants.fluidState().enthalpy(phaseIdx)); + + Evaluation enthalpyRate = density*extQuants.volumeFlux(phaseIdx)*specificEnthalpy; + EnergyModule::addToEnthalpyRate(*this, enthalpyRate); + } + } + + if (enableEnergy) + // heat conduction + EnergyModule::addToEnthalpyRate(*this, EnergyModule::thermalConductionRate(extQuants)); + +#ifndef NDEBUG + for (unsigned i = 0; i < numEq; ++i) + Opm::Valgrind::CheckDefined((*this)[i]); +#endif + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setInFlow + */ + template + void setInFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the direction opposite to the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Evaluation& val = this->operator[](eqIdx); + val = Toolbox::min(0.0, val); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setOutFlow + */ + template + void setOutFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the same direction as the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Evaluation& val = this->operator[](eqIdx); + val = Toolbox::max(0.0, val); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setNoFlow + */ + void setNoFlow() + { (*this) = Evaluation(0.0); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/pvs/pvsextensivequantities.hh b/opm/models/pvs/pvsextensivequantities.hh new file mode 100644 index 00000000000..62b8735ca5d --- /dev/null +++ b/opm/models/pvs/pvsextensivequantities.hh @@ -0,0 +1,94 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PvsExtensiveQuantities + */ +#ifndef EWOMS_PVS_EXTENSIVE_QUANTITIES_HH +#define EWOMS_PVS_EXTENSIVE_QUANTITIES_HH + +#include "pvsproperties.hh" + +#include +#include +#include + +namespace Opm { + +/*! + * \ingroup PvsModel + * \ingroup ExtensiveQuantities + * + * \brief Contains all data which is required to calculate all fluxes at a flux + * integration point for the primary variable switching model. + * + * This means pressure and concentration gradients, phase densities at + * the integration point, etc. + */ +template +class PvsExtensiveQuantities + : public MultiPhaseBaseExtensiveQuantities + , public EnergyExtensiveQuantities()> + , public DiffusionExtensiveQuantities()> +{ + using ParentType = MultiPhaseBaseExtensiveQuantities; + + using FluidSystem = GetPropType; + using ElementContext = GetPropType; + + enum { enableDiffusion = getPropValue() }; + using DiffusionExtensiveQuantities = Opm::DiffusionExtensiveQuantities; + + enum { enableEnergy = getPropValue() }; + using EnergyExtensiveQuantities = Opm::EnergyExtensiveQuantities; + +public: + /*! + * \copydoc ParentType::update + */ + void update(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, scvfIdx, timeIdx); + DiffusionExtensiveQuantities::update_(elemCtx, scvfIdx, timeIdx); + EnergyExtensiveQuantities::update_(elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc ParentType::updateBoundary + */ + template + void updateBoundary(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + ParentType::updateBoundary(context, bfIdx, timeIdx, fluidState); + DiffusionExtensiveQuantities::updateBoundary_(context, bfIdx, timeIdx, fluidState); + EnergyExtensiveQuantities::updateBoundary_(context, bfIdx, timeIdx, fluidState); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/pvs/pvsindices.hh b/opm/models/pvs/pvsindices.hh new file mode 100644 index 00000000000..801f7935ea1 --- /dev/null +++ b/opm/models/pvs/pvsindices.hh @@ -0,0 +1,73 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PvsIndices + */ +#ifndef EWOMS_PVS_INDICES_HH +#define EWOMS_PVS_INDICES_HH + +#include "pvsproperties.hh" + +#include + +namespace Opm { +/*! + * \ingroup PvsModel + * + * \brief The indices for the compositional multi-phase primary + * variable switching model. + * + * \tparam PVOffset The first index in a primary variable vector. + */ +template +class PvsIndices + : public EnergyIndices(), + getPropValue()> +{ + enum { numComponents = getPropValue() }; + enum { enableEnergy = getPropValue() }; + using EnergyIndices = Opm::EnergyIndices; + +public: + //! Number of partial differential equations or primary variables, respectively + static const int numEq = numComponents + EnergyIndices::numEq_; + + // Primary variable indices + + //! Index for the pressure of the first phase + static const int pressure0Idx = PVOffset + 0; + //! Index of the either the saturation or the mole + //! fraction of the phase with the lowest index + static const int switch0Idx = PVOffset + 1; + + // equation indices + + //! Index of the mass conservation equation for the first component + static const int conti0EqIdx = PVOffset; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/pvs/pvsintensivequantities.hh b/opm/models/pvs/pvsintensivequantities.hh new file mode 100644 index 00000000000..2936ebc022a --- /dev/null +++ b/opm/models/pvs/pvsintensivequantities.hh @@ -0,0 +1,297 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PvsIntensiveQuantities + */ +#ifndef EWOMS_PVS_INTENSIVE_QUANTITIES_HH +#define EWOMS_PVS_INTENSIVE_QUANTITIES_HH + +#include "pvsproperties.hh" + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +namespace Opm { +/*! + * \ingroup PvsModel + * \ingroup IntensiveQuantities + * + * \brief Contains the quantities which are are constant within a + * finite volume in the compositional multi-phase primary + * variable switching model. + */ +template +class PvsIntensiveQuantities + : public GetPropType + , public DiffusionIntensiveQuantities() > + , public EnergyIntensiveQuantities() > + , public GetPropType::FluxIntensiveQuantities +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using ElementContext = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using Indices = GetPropType; + using GridView = GetPropType; + using FluxModule = GetPropType; + + enum { switch0Idx = Indices::switch0Idx }; + enum { pressure0Idx = Indices::pressure0Idx }; + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + enum { enableEnergy = getPropValue() }; + enum { dimWorld = GridView::dimensionworld }; + + using Toolbox = Opm::MathToolbox; + using MiscibleMultiPhaseComposition = Opm::MiscibleMultiPhaseComposition; + using ComputeFromReferencePhase = Opm::ComputeFromReferencePhase; + + using PhaseVector = Dune::FieldVector; + using EvalPhaseVector = Dune::FieldVector; + using DimMatrix = Dune::FieldMatrix; + + using FluxIntensiveQuantities = typename FluxModule::FluxIntensiveQuantities; + using DiffusionIntensiveQuantities = Opm::DiffusionIntensiveQuantities; + using EnergyIntensiveQuantities = Opm::EnergyIntensiveQuantities; + +public: + //! The type of the object returned by the fluidState() method + using FluidState = Opm::CompositionalFluidState; + + PvsIntensiveQuantities() + { } + + PvsIntensiveQuantities(const PvsIntensiveQuantities& other) = default; + + PvsIntensiveQuantities& operator=(const PvsIntensiveQuantities& other) = default; + + /*! + * \copydoc ImmiscibleIntensiveQuantities::update + */ + void update(const ElementContext& elemCtx, unsigned dofIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, dofIdx, timeIdx); + EnergyIntensiveQuantities::updateTemperatures_(fluidState_, elemCtx, dofIdx, timeIdx); + + const auto& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + const auto& problem = elemCtx.problem(); + + ///////////// + // set the saturations + ///////////// + Evaluation sumSat = 0.0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + fluidState_.setSaturation(phaseIdx, priVars.explicitSaturationValue(phaseIdx, timeIdx)); + Opm::Valgrind::CheckDefined(fluidState_.saturation(phaseIdx)); + sumSat += fluidState_.saturation(phaseIdx); + } + Opm::Valgrind::CheckDefined(priVars.implicitSaturationIdx()); + Opm::Valgrind::CheckDefined(sumSat); + fluidState_.setSaturation(priVars.implicitSaturationIdx(), 1.0 - sumSat); + + ///////////// + // set the pressures of the fluid phases + ///////////// + + // calculate capillary pressure + const MaterialLawParams& materialParams = + problem.materialLawParams(elemCtx, dofIdx, timeIdx); + EvalPhaseVector pC; + MaterialLaw::capillaryPressures(pC, materialParams, fluidState_); + + // set the absolute phase pressures in the fluid state + const Evaluation& p0 = priVars.makeEvaluation(pressure0Idx, timeIdx); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + fluidState_.setPressure(phaseIdx, p0 + (pC[phaseIdx] - pC[0])); + + ///////////// + // calculate the phase compositions + ///////////// + + typename FluidSystem::template ParameterCache paramCache; + unsigned lowestPresentPhaseIdx = priVars.lowestPresentPhaseIdx(); + unsigned numNonPresentPhases = 0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!priVars.phaseIsPresent(phaseIdx)) + ++numNonPresentPhases; + } + + // now comes the tricky part: calculate phase compositions + if (numNonPresentPhases == numPhases - 1) { + // only one phase is present, i.e. the primary variables + // contain the complete composition of the phase + Evaluation sumx = 0.0; + for (unsigned compIdx = 1; compIdx < numComponents; ++compIdx) { + const Evaluation& x = priVars.makeEvaluation(switch0Idx + compIdx - 1, timeIdx); + fluidState_.setMoleFraction(lowestPresentPhaseIdx, compIdx, x); + sumx += x; + } + + // set the mole fraction of the first component + fluidState_.setMoleFraction(lowestPresentPhaseIdx, 0, 1 - sumx); + + // calculate the composition of the remaining phases (as + // well as the densities of all phases). this is the job + // of the "ComputeFromReferencePhase" constraint solver + ComputeFromReferencePhase::solve(fluidState_, paramCache, + lowestPresentPhaseIdx, + /*setViscosity=*/true, + /*setEnthalpy=*/false); + } + else { + // create the auxiliary constraints + unsigned numAuxConstraints = numComponents + numNonPresentPhases - numPhases; + Opm::MMPCAuxConstraint auxConstraints[numComponents]; + + unsigned auxIdx = 0; + unsigned switchIdx = 0; + for (; switchIdx < numPhases - 1; ++switchIdx) { + unsigned compIdx = switchIdx + 1; + unsigned switchPhaseIdx = switchIdx; + if (switchIdx >= lowestPresentPhaseIdx) + switchPhaseIdx += 1; + + if (!priVars.phaseIsPresent(switchPhaseIdx)) { + auxConstraints[auxIdx].set(lowestPresentPhaseIdx, compIdx, + priVars.makeEvaluation(switch0Idx + switchIdx, timeIdx)); + ++auxIdx; + } + } + + for (; auxIdx < numAuxConstraints; ++auxIdx, ++switchIdx) { + unsigned compIdx = numPhases - numNonPresentPhases + auxIdx; + auxConstraints[auxIdx].set(lowestPresentPhaseIdx, compIdx, + priVars.makeEvaluation(switch0Idx + switchIdx, timeIdx)); + } + + // both phases are present, i.e. phase compositions are a result of the the + // gas <-> liquid equilibrium. This is the job of the + // "MiscibleMultiPhaseComposition" constraint solver + MiscibleMultiPhaseComposition::solve(fluidState_, paramCache, + priVars.phasePresence(), + auxConstraints, + numAuxConstraints, + /*setViscosity=*/true, + /*setEnthalpy=*/false); + } + +#ifndef NDEBUG + // make valgrind happy and set the enthalpies to NaN + if (!enableEnergy) { + Scalar myNan = std::numeric_limits::quiet_NaN(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + fluidState_.setEnthalpy(phaseIdx, myNan); + } +#endif + + ///////////// + // calculate the remaining quantities + ///////////// + + // calculate relative permeabilities + MaterialLaw::relativePermeabilities(relativePermeability_, + materialParams, fluidState_); + Opm::Valgrind::CheckDefined(relativePermeability_); + + // mobilities + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + mobility_[phaseIdx] = + relativePermeability_[phaseIdx] / fluidState().viscosity(phaseIdx); + + // porosity + porosity_ = problem.porosity(elemCtx, dofIdx, timeIdx); + Opm::Valgrind::CheckDefined(porosity_); + + // intrinsic permeability + intrinsicPerm_ = problem.intrinsicPermeability(elemCtx, dofIdx, timeIdx); + + // update the quantities specific for the velocity model + FluxIntensiveQuantities::update_(elemCtx, dofIdx, timeIdx); + + // energy related quantities + EnergyIntensiveQuantities::update_(fluidState_, paramCache, elemCtx, dofIdx, timeIdx); + + // update the diffusion specific quantities of the intensive quantities + DiffusionIntensiveQuantities::update_(fluidState_, paramCache, elemCtx, dofIdx, timeIdx); + + fluidState_.checkDefined(); + } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::fluidState + */ + const FluidState& fluidState() const + { return fluidState_; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::intrinsicPermeability + */ + const DimMatrix& intrinsicPermeability() const + { return intrinsicPerm_; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::relativePermeability + */ + const Evaluation& relativePermeability(unsigned phaseIdx) const + { return relativePermeability_[phaseIdx]; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::mobility + */ + const Evaluation& mobility(unsigned phaseIdx) const + { return mobility_[phaseIdx]; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::porosity + */ + const Evaluation& porosity() const + { return porosity_; } + +private: + FluidState fluidState_; + Evaluation porosity_; + DimMatrix intrinsicPerm_; + Evaluation relativePermeability_[numPhases]; + Evaluation mobility_[numPhases]; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/pvs/pvslocalresidual.hh b/opm/models/pvs/pvslocalresidual.hh new file mode 100644 index 00000000000..022bf7a01c3 --- /dev/null +++ b/opm/models/pvs/pvslocalresidual.hh @@ -0,0 +1,200 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PvsLocalResidual + */ +#ifndef EWOMS_PVS_LOCAL_RESIDUAL_HH +#define EWOMS_PVS_LOCAL_RESIDUAL_HH + +#include "pvsproperties.hh" + +#include +#include + +#include + +namespace Opm { + +/*! + * \ingroup PvsModel + * + * \brief Element-wise calculation of the local residual for the + * compositional multi-phase primary variable switching model. + */ +template +class PvsLocalResidual : public GetPropType +{ + using Evaluation = GetPropType; + using EqVector = GetPropType; + using RateVector = GetPropType; + using Indices = GetPropType; + using IntensiveQuantities = GetPropType; + using ElementContext = GetPropType; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { numEq = getPropValue() }; + enum { conti0EqIdx = Indices::conti0EqIdx }; + + enum { enableDiffusion = getPropValue() }; + using DiffusionModule = Opm::DiffusionModule; + + enum { enableEnergy = getPropValue() }; + using EnergyModule = Opm::EnergyModule; + + using Toolbox = Opm::MathToolbox; + +public: + /*! + * \copydoc ImmiscibleLocalResidual::addPhaseStorage + */ + template + void addPhaseStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx, + unsigned phaseIdx) const + { + const IntensiveQuantities& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + const auto& fs = intQuants.fluidState(); + + // compute storage term of all components within all phases + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + unsigned eqIdx = conti0EqIdx + compIdx; + storage[eqIdx] += + Toolbox::template decay(fs.molarity(phaseIdx, compIdx)) + * Toolbox::template decay(fs.saturation(phaseIdx)) + * Toolbox::template decay(intQuants.porosity()); + } + + EnergyModule::addPhaseStorage(storage, elemCtx.intensiveQuantities(dofIdx, timeIdx), phaseIdx); + } + + /*! + * \copydoc ImmiscibleLocalResidual::computeStorage + */ + template + void computeStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + storage = 0.0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + addPhaseStorage(storage, elemCtx, dofIdx, timeIdx, phaseIdx); + + EnergyModule::addSolidEnergyStorage(storage, elemCtx.intensiveQuantities(dofIdx, timeIdx)); + } + + /*! + * \copydoc ImmiscibleLocalResidual::computeFlux + */ + void computeFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + flux = 0.0; + addAdvectiveFlux(flux, elemCtx, scvfIdx, timeIdx); + Opm::Valgrind::CheckDefined(flux); + + addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + Opm::Valgrind::CheckDefined(flux); + } + + /*! + * \copydoc ImmiscibleLocalResidual::addAdvectiveFlux + */ + void addAdvectiveFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + unsigned focusDofIdx = elemCtx.focusDofIndex(); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // data attached to upstream and the downstream DOFs + // of the current phase + unsigned upIdx = static_cast(extQuants.upstreamIndex(phaseIdx)); + const IntensiveQuantities& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + + // this is a bit hacky because it is specific to the element-centered + // finite volume scheme. (N.B. that if finite differences are used to + // linearize the system of equations, it does not matter.) + if (upIdx == focusDofIdx) { + Evaluation tmp = + up.fluidState().molarDensity(phaseIdx) + * extQuants.volumeFlux(phaseIdx); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + flux[conti0EqIdx + compIdx] += + tmp*up.fluidState().moleFraction(phaseIdx, compIdx); + } + } + else { + Evaluation tmp = + Toolbox::value(up.fluidState().molarDensity(phaseIdx)) + * extQuants.volumeFlux(phaseIdx); + + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + flux[conti0EqIdx + compIdx] += + tmp*Toolbox::value(up.fluidState().moleFraction(phaseIdx, compIdx)); + } + } + } + + EnergyModule::addAdvectiveFlux(flux, elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc ImmiscibleLocalResidual::addDiffusiveFlux + */ + void addDiffusiveFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + DiffusionModule::addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + EnergyModule::addDiffusiveFlux(flux, elemCtx, scvfIdx, timeIdx); + } + + /*! + * \copydoc ImmiscibleLocalResidual::computeSource + */ + void computeSource(RateVector& source, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + Opm::Valgrind::SetUndefined(source); + elemCtx.problem().source(source, elemCtx, dofIdx, timeIdx); + Opm::Valgrind::CheckDefined(source); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/pvs/pvsmodel.hh b/opm/models/pvs/pvsmodel.hh new file mode 100644 index 00000000000..ec165efe2e4 --- /dev/null +++ b/opm/models/pvs/pvsmodel.hh @@ -0,0 +1,636 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PvsModel + */ +#ifndef EWOMS_PVS_MODEL_HH +#define EWOMS_PVS_MODEL_HH + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Opm { +template +class PvsModel; +} + +namespace Opm::Properties { + +namespace TTag { + +//! The type tag for the isothermal single phase problems +struct PvsModel +{ + using InheritsFrom = std::tuple; }; +} // namespace TTag + +//! Use the PVS local jacobian operator for the PVS model +template +struct LocalResidual +{ using type = Opm::PvsLocalResidual; }; + +//! Use the PVS specific newton method for the PVS model +template +struct NewtonMethod +{ using type = Opm::PvsNewtonMethod; }; + +//! the Model property +template +struct Model +{ using type = Opm::PvsModel; }; + +//! the PrimaryVariables property +template +struct PrimaryVariables +{ using type = Opm::PvsPrimaryVariables; }; + +//! the RateVector property +template +struct RateVector +{ using type = Opm::PvsRateVector; }; + +//! the BoundaryRateVector property +template +struct BoundaryRateVector +{ using type = Opm::PvsBoundaryRateVector; }; + +//! the IntensiveQuantities property +template +struct IntensiveQuantities +{ using type = Opm::PvsIntensiveQuantities; }; + +//! the ExtensiveQuantities property +template +struct ExtensiveQuantities +{ using type = Opm::PvsExtensiveQuantities; }; + +//! The indices required by the isothermal PVS model +template +struct Indices +{ using type = Opm::PvsIndices; }; + +//! Disable the energy equation by default +template +struct EnableEnergy +{ static constexpr bool value = false; }; + +// disable molecular diffusion by default +template +struct EnableDiffusion +{ static constexpr bool value = false; }; + +//! The basis value for the weight of the pressure primary variable +template +struct PvsPressureBaseWeight +{ + using type = GetPropType; + static constexpr type value = 1.0; +}; + +//! The basis value for the weight of the saturation primary variables +template +struct PvsSaturationsBaseWeight +{ + using type = GetPropType; + static constexpr type value = 1.0; +}; + +//! The basis value for the weight of the mole fraction primary variables +template +struct PvsMoleFractionsBaseWeight +{ + using type = GetPropType; + static constexpr type value = 1.0; +}; + +} // namespace Opm::Properties + +namespace Opm::Parameters { + +//! The verbosity of the model (0 -> do not print anything, 2 -> spam stdout a lot) +struct PvsVerbosity { static constexpr int value = 1; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! + * \ingroup PvsModel + * + * \brief A generic compositional multi-phase model using primary-variable + * switching. + * + * This model assumes a flow of \f$M \geq 1\f$ fluid phases + * \f$\alpha\f$, each of which is assumed to be a mixture \f$N \geq + * M\f$ chemical species \f$\kappa\f$. + * + * By default, the standard multi-phase Darcy approach is used to determine + * the velocity, i.e. + * \f[ + * \mathbf{v}_\alpha = - \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} + * \left(\mathbf{grad}\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right) \;, + * \f] + * although the actual approach which is used can be specified via the + * \c FluxModule property. For example, the velocity model can by + * changed to the Forchheimer approach by + * \code + * template +struct FluxModule { using type = Opm::ForchheimerFluxModule; }; + * \endcode + * + * The core of the model is the conservation mass of each component by + * means of the equation + * \f[ + * \sum_\alpha \frac{\partial\;\phi c_\alpha^\kappa S_\alpha }{\partial t} + * - \sum_\alpha \mathrm{div} \left\{ c_\alpha^\kappa \mathbf{v}_\alpha \right\} + * - q^\kappa = 0 \;. + * \f] + * + * To close the system mathematically, \f$M\f$ model equations are + * also required. This model uses the primary variable switching + * assumptions, which are given by: + * \f[ + * 0 \stackrel{!}{=} + * f_\alpha = \left\{ + * \begin{array}{cl} + * S_\alpha& \quad \text{if phase }\alpha\text{ is not present} \ \ + * 1 - \sum_\kappa x_\alpha^\kappa& \quad \text{else} + * \end{array} + * \right. + * \f] + * + * To make this approach applicable, a pseudo primary variable + * phase presence has to be introduced. Its purpose is to + * specify for each phase whether it is present or not. It is a + * pseudo primary variable because it is not directly considered when + * linearizing the system in the Newton method, but after each Newton + * iteration, it gets updated like the "real" primary variables. The + * following rules are used for this update procedure: + * + *

+ * + * The model always requires \f$N\f$ primary variables, but their + * interpretation is dependent on the phase presence: + * + *
    + * + *
  • The first primary variable is always interpreted as the + * pressure of the phase with the lowest index \f$PV_0 = + * p_0\f$.
  • + * + *
  • Then, \f$M - 1\f$ "switching primary variables" follow, which + * are interpreted depending in the presence of the first + * \f$M-1\f$ phases: If phase \f$\alpha\f$ is present, its + * saturation \f$S_\alpha = PV_i\f$ is used as primary variable; + * if it is not present, the mole fraction \f$PV_i = + * x_{\alpha^\star}^\alpha\f$ of the component with index + * \f$\alpha\f$ in the phase with the lowest index that is present + * \f$\alpha^\star\f$ is used instead.
  • + * + *
  • Finally, the mole fractions of the \f$N-M\f$ components with + * the largest index in the phase with the lowest index that is + * present \f$x_{\alpha^\star}^\kappa\f$ are used as primary + * variables.
  • + * + *
+ */ +template +class PvsModel + : public MultiPhaseBaseModel +{ + using ParentType = MultiPhaseBaseModel; + + using Scalar = GetPropType; + using Simulator = GetPropType; + using FluidSystem = GetPropType; + using GridView = GetPropType; + + using PrimaryVariables = GetPropType; + using IntensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using Indices = GetPropType; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + using Element = typename GridView::template Codim<0>::Entity; + using ElementIterator = typename GridView::template Codim<0>::Iterator; + + using EnergyModule = Opm::EnergyModule; + +public: + PvsModel(Simulator& simulator) + : ParentType(simulator) + { + verbosity_ = Parameters::Get(); + numSwitched_ = 0; + } + + /*! + * \brief Register all run-time parameters for the PVS compositional model. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + // register runtime parameters of the VTK output modules + Opm::VtkPhasePresenceModule::registerParameters(); + Opm::VtkCompositionModule::registerParameters(); + + if (enableDiffusion) + Opm::VtkDiffusionModule::registerParameters(); + + if (enableEnergy) + Opm::VtkEnergyModule::registerParameters(); + + Parameters::Register + ("The verbosity level of the primary variable " + "switching model"); + } + + /*! + * \copydoc FvBaseDiscretization::name + */ + static std::string name() + { return "pvs"; } + + /*! + * \copydoc FvBaseDiscretization::primaryVarName + */ + std::string primaryVarName(unsigned pvIdx) const + { + std::string s; + if (!(s = EnergyModule::primaryVarName(pvIdx)).empty()) + return s; + + std::ostringstream oss; + if (pvIdx == Indices::pressure0Idx) + oss << "pressure_" << FluidSystem::phaseName(/*phaseIdx=*/0); + else if (Indices::switch0Idx <= pvIdx + && pvIdx < Indices::switch0Idx + numPhases - 1) + oss << "switch_" << pvIdx - Indices::switch0Idx; + else if (Indices::switch0Idx + numPhases - 1 <= pvIdx + && pvIdx < Indices::switch0Idx + numComponents - 1) + oss << "auxMoleFrac^" << FluidSystem::componentName(pvIdx); + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::eqName + */ + std::string eqName(unsigned eqIdx) const + { + std::string s; + if (!(s = EnergyModule::eqName(eqIdx)).empty()) + return s; + + std::ostringstream oss; + if (Indices::conti0EqIdx <= eqIdx && eqIdx < Indices::conti0EqIdx + + numComponents) { + unsigned compIdx = eqIdx - Indices::conti0EqIdx; + oss << "continuity^" << FluidSystem::componentName(compIdx); + } + else + assert(false); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::updateFailed + */ + void updateFailed() + { + ParentType::updateFailed(); + numSwitched_ = 0; + } + + /*! + * \copydoc FvBaseDiscretization::updateBegin + */ + void updateBegin() + { + ParentType::updateBegin(); + + // find the a reference pressure. The first degree of freedom + // might correspond to non-interior entities which would lead + // to an undefined value, so we have to iterate... + size_t nDof = this->numTotalDof(); + for (unsigned dofIdx = 0; dofIdx < nDof; ++ dofIdx) { + if (this->dofTotalVolume(dofIdx) > 0.0) { + referencePressure_ = + this->solution(/*timeIdx=*/0)[dofIdx][/*pvIdx=*/Indices::pressure0Idx]; + if (referencePressure_ > 0.0) + break; + } + } + } + + /*! + * \copydoc FvBaseDiscretization::primaryVarWeight + */ + Scalar primaryVarWeight(unsigned globalDofIdx, unsigned pvIdx) const + { + Scalar tmp = EnergyModule::primaryVarWeight(*this, globalDofIdx, pvIdx); + if (tmp > 0) + // energy related quantity + return tmp; + + if (Indices::pressure0Idx == pvIdx) { + return 10 / referencePressure_; + } + + if (Indices::switch0Idx <= pvIdx && pvIdx < Indices::switch0Idx + + numPhases - 1) { + unsigned phaseIdx = pvIdx - Indices::switch0Idx; + + if (!this->solution(/*timeIdx=*/0)[globalDofIdx].phaseIsPresent(phaseIdx)) + // for saturations, the weight is always 1 + return 1; + + // for saturations, the PvsMoleSaturationsBaseWeight + // property determines the weight + return getPropValue(); + } + + // for mole fractions, the PvsMoleFractionsBaseWeight + // property determines the weight + return getPropValue(); + } + + /*! + * \copydoc FvBaseDiscretization::eqWeight + */ + Scalar eqWeight(unsigned globalDofIdx, unsigned eqIdx) const + { + Scalar tmp = EnergyModule::eqWeight(*this, globalDofIdx, eqIdx); + if (tmp > 0) + // energy related equation + return tmp; + + unsigned compIdx = eqIdx - Indices::conti0EqIdx; + assert(compIdx <= numComponents); + + // make all kg equal + return FluidSystem::molarMass(compIdx); + } + + /*! + * \copydoc FvBaseDiscretization::advanceTimeLevel + */ + void advanceTimeLevel() + { + ParentType::advanceTimeLevel(); + numSwitched_ = 0; + } + + /*! + * \brief Return true if the primary variables were switched for + * at least one vertex after the last timestep. + */ + bool switched() const + { return numSwitched_ > 0; } + + /*! + * \copydoc FvBaseDiscretization::serializeEntity + */ + template + void serializeEntity(std::ostream& outstream, const DofEntity& dofEntity) + { + // write primary variables + ParentType::serializeEntity(outstream, dofEntity); + + unsigned dofIdx = static_cast(this->dofMapper().index(dofEntity)); + if (!outstream.good()) + throw std::runtime_error("Could not serialize DOF "+std::to_string(dofIdx)); + + outstream << this->solution(/*timeIdx=*/0)[dofIdx].phasePresence() << " "; + } + + /*! + * \copydoc FvBaseDiscretization::deserializeEntity + */ + template + void deserializeEntity(std::istream& instream, const DofEntity& dofEntity) + { + // read primary variables + ParentType::deserializeEntity(instream, dofEntity); + + // read phase presence + unsigned dofIdx = static_cast(this->dofMapper().index(dofEntity)); + if (!instream.good()) + throw std::runtime_error("Could not deserialize DOF "+std::to_string(dofIdx)); + + short tmp; + instream >> tmp; + this->solution(/*timeIdx=*/0)[dofIdx].setPhasePresence(tmp); + this->solution(/*timeIdx=*/1)[dofIdx].setPhasePresence(tmp); + } + + /*! + * \internal + * \brief Do the primary variable switching after a Newton iteration. + * + * This is an internal method that needs to be public because it + * gets called by the Newton method after an update. + */ + void switchPrimaryVars_() + { + numSwitched_ = 0; + + int succeeded; + try { + std::vector visited(this->numGridDof(), false); + ElementContext elemCtx(this->simulator_); + + for (const auto& elem : elements(this->gridView_, Dune::Partitions::interior)) { + elemCtx.updateStencil(elem); + + size_t numLocalDof = elemCtx.stencil(/*timeIdx=*/0).numPrimaryDof(); + for (unsigned dofIdx = 0; dofIdx < numLocalDof; ++dofIdx) { + unsigned globalIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + + if (visited[globalIdx]) + continue; + visited[globalIdx] = true; + + // compute the intensive quantities of the current degree of freedom + auto& priVars = this->solution(/*timeIdx=*/0)[globalIdx]; + elemCtx.updateIntensiveQuantities(priVars, dofIdx, /*timeIdx=*/0); + const IntensiveQuantities& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0); + + // evaluate primary variable switch + short oldPhasePresence = priVars.phasePresence(); + + // set the primary variables and the new phase state + // from the current fluid state + priVars.assignNaive(intQuants.fluidState()); + + if (oldPhasePresence != priVars.phasePresence()) { + if (verbosity_ > 1) + printSwitchedPhases_(elemCtx, + dofIdx, + intQuants.fluidState(), + oldPhasePresence, + priVars); + ++numSwitched_; + } + } + } + + succeeded = 1; + } + catch (...) + { + std::cout << "rank " << this->simulator_.gridView().comm().rank() + << " caught an exception during primary variable switching" + << "\n" << std::flush; + succeeded = 0; + } + succeeded = this->simulator_.gridView().comm().min(succeeded); + + if (!succeeded) + throw NumericalProblem("A process did not succeed in adapting the primary variables"); + + // make sure that if there was a variable switch in an + // other partition we will also set the switch flag + // for our partition. + numSwitched_ = this->gridView_.comm().sum(numSwitched_); + + if (verbosity_ > 0) + this->simulator_.model().newtonMethod().endIterMsg() + << ", num switched=" << numSwitched_; + } + + template + void printSwitchedPhases_(const ElementContext& elemCtx, + unsigned dofIdx, + const FluidState& fs, + short oldPhasePresence, + const PrimaryVariables& newPv) const + { + using FsToolbox = Opm::MathToolbox; + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + bool oldPhasePresent = (oldPhasePresence& (1 << phaseIdx)) > 0; + bool newPhasePresent = newPv.phaseIsPresent(phaseIdx); + if (oldPhasePresent == newPhasePresent) + continue; + + const auto& pos = elemCtx.pos(dofIdx, /*timeIdx=*/0); + if (oldPhasePresent && !newPhasePresent) { + std::cout << "'" << FluidSystem::phaseName(phaseIdx) + << "' phase disappears at position " << pos + << ". saturation=" << fs.saturation(phaseIdx) + << std::flush; + } + else { + Scalar sumx = 0; + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + sumx += FsToolbox::value(fs.moleFraction(phaseIdx, compIdx)); + + std::cout << "'" << FluidSystem::phaseName(phaseIdx) + << "' phase appears at position " << pos + << " sum x = " << sumx << std::flush; + } + } + + std::cout << ", new primary variables: "; + newPv.print(); + std::cout << "\n" << std::flush; + } + + void registerOutputModules_() + { + ParentType::registerOutputModules_(); + + // add the VTK output modules which are meaningful for the model + this->addOutputModule(new Opm::VtkPhasePresenceModule(this->simulator_)); + this->addOutputModule(new Opm::VtkCompositionModule(this->simulator_)); + if (enableDiffusion) + this->addOutputModule(new Opm::VtkDiffusionModule(this->simulator_)); + if (enableEnergy) + this->addOutputModule(new Opm::VtkEnergyModule(this->simulator_)); + } + + mutable Scalar referencePressure_; + + // number of switches of the phase state in the last Newton + // iteration + unsigned numSwitched_; + + // verbosity of the model + int verbosity_; +}; +} + +#endif diff --git a/opm/models/pvs/pvsnewtonmethod.hh b/opm/models/pvs/pvsnewtonmethod.hh new file mode 100644 index 00000000000..99ff2534540 --- /dev/null +++ b/opm/models/pvs/pvsnewtonmethod.hh @@ -0,0 +1,138 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PvsNewtonMethod + */ +#ifndef EWOMS_PVS_NEWTON_METHOD_HH +#define EWOMS_PVS_NEWTON_METHOD_HH + +#include "pvsproperties.hh" + +#include + +namespace Opm::Properties { + +template +struct DiscNewtonMethod; + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup PvsModel + * + * \brief A newton solver which is specific to the compositional + * multi-phase PVS model. + */ +template +class PvsNewtonMethod : public GetPropType +{ + using ParentType = GetPropType; + using Simulator = GetPropType; + using SolutionVector = GetPropType; + using PrimaryVariables = GetPropType; + using EqVector = GetPropType; + using Scalar = GetPropType; + using Indices = GetPropType; + using FluidSystem = GetPropType; + + enum { numPhases = FluidSystem::numPhases }; + + // primary variable indices + enum { pressure0Idx = Indices::pressure0Idx }; + enum { switch0Idx = Indices::switch0Idx }; + +public: + PvsNewtonMethod(Simulator& simulator) : ParentType(simulator) + {} + +protected: + friend NewtonMethod; + friend ParentType; + + /*! + * \copydoc FvBaseNewtonMethod::updatePrimaryVariables_ + */ + void updatePrimaryVariables_(unsigned, + PrimaryVariables& nextValue, + const PrimaryVariables& currentValue, + const EqVector& update, + const EqVector&) + { + // normal Newton-Raphson update + nextValue = currentValue; + nextValue -= update; + + //// + // put crash barriers along the update path + //// + // saturations: limit the change of any saturation to at most 20% + Scalar sumSatDelta = 0.0; + Scalar maxSatDelta = 0.0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases - 1; ++phaseIdx) { + if (!currentValue.phaseIsPresent(phaseIdx)) + continue; + + maxSatDelta = std::max(std::abs(update[switch0Idx + phaseIdx]), + maxSatDelta); + sumSatDelta += update[switch0Idx + phaseIdx]; + } + maxSatDelta = std::max(std::abs(- sumSatDelta), maxSatDelta); + + if (maxSatDelta > 0.2) { + Scalar alpha = 0.2/maxSatDelta; + for (unsigned phaseIdx = 0; phaseIdx < numPhases - 1; ++phaseIdx) { + if (!currentValue.phaseIsPresent(phaseIdx)) + continue; + + nextValue[switch0Idx + phaseIdx] = + currentValue[switch0Idx + phaseIdx] + - alpha*update[switch0Idx + phaseIdx]; + } + } + + // limit pressure reference change to 20% of the total value per iteration + clampValue_(nextValue[pressure0Idx], + currentValue[pressure0Idx]*0.8, + currentValue[pressure0Idx]*1.2); + } + + /*! + * \copydoc NewtonMethod::endIteration_ + */ + void endIteration_(SolutionVector& uCurrentIter, + const SolutionVector& uLastIter) + { + ParentType::endIteration_(uCurrentIter, uLastIter); + this->problem().model().switchPrimaryVars_(); + } + + void clampValue_(Scalar& val, Scalar minVal, Scalar maxVal) const + { val = std::max(minVal, std::min(val, maxVal)); } +}; +} // namespace Opm + +#endif diff --git a/opm/models/pvs/pvsprimaryvariables.hh b/opm/models/pvs/pvsprimaryvariables.hh new file mode 100644 index 00000000000..e4351c34049 --- /dev/null +++ b/opm/models/pvs/pvsprimaryvariables.hh @@ -0,0 +1,379 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PvsPrimaryVariables + */ +#ifndef EWOMS_PVS_PRIMARY_VARIABLES_HH +#define EWOMS_PVS_PRIMARY_VARIABLES_HH + +#include "pvsindices.hh" +#include "pvsproperties.hh" + +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +namespace Opm { + +/*! + * \ingroup PvsModel + * + * \brief Represents the primary variables used in the primary + * variable switching compositional model. + * + * This class is basically a Dune::FieldVector which can retrieve its + * contents from an aribitatry fluid state. + */ +template +class PvsPrimaryVariables : public FvBasePrimaryVariables +{ + using ParentType = FvBasePrimaryVariables; + using ThisType = PvsPrimaryVariables; + using Implementation = GetPropType; + + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using Indices = GetPropType; + + // primary variable indices + enum { pressure0Idx = Indices::pressure0Idx }; + enum { switch0Idx = Indices::switch0Idx }; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + using Toolbox = MathToolbox; + using ComponentVector = Dune::FieldVector; + using EnergyModule = Opm::EnergyModule; + using NcpFlash = Opm::NcpFlash; + +public: + PvsPrimaryVariables() : ParentType() + { Valgrind::SetDefined(*this); } + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(Scalar) + */ + explicit PvsPrimaryVariables(Scalar value) : ParentType(value) + { + Valgrind::CheckDefined(value); + Valgrind::SetDefined(*this); + + phasePresence_ = 0; + } + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(const + * ImmisciblePrimaryVariables& ) + */ + PvsPrimaryVariables(const PvsPrimaryVariables& value) : ParentType(value) + { + Valgrind::SetDefined(*this); + + phasePresence_ = value.phasePresence_; + } + + /*! + * \copydoc ImmisciblePrimaryVariables::assignMassConservative + */ + template + void assignMassConservative(const FluidState& fluidState, + const MaterialLawParams& matParams, + bool isInEquilibrium = false) + { +#ifndef NDEBUG + // make sure the temperature is the same in all fluid phases + for (unsigned phaseIdx = 1; phaseIdx < numPhases; ++phaseIdx) { + assert(std::abs(fluidState.temperature(0) - fluidState.temperature(phaseIdx)) < 1e-30); + } +#endif // NDEBUG + + // for the equilibrium case, we don't need complicated + // computations. + if (isInEquilibrium) { + assignNaive(fluidState); + return; + } + + // use a flash calculation to calculate a fluid state in + // thermodynamic equilibrium + typename FluidSystem::template ParameterCache paramCache; + CompositionalFluidState fsFlash; + + // use the externally given fluid state as initial value for + // the flash calculation + fsFlash.assign(fluidState); + + // calculate the phase densities + paramCache.updateAll(fsFlash); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + Scalar rho = FluidSystem::density(fsFlash, paramCache, phaseIdx); + fsFlash.setDensity(phaseIdx, rho); + } + // calculate the "global molarities" + ComponentVector globalMolarities(0.0); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + globalMolarities[compIdx] += + fsFlash.saturation(phaseIdx) * fsFlash.molarity(phaseIdx, compIdx); + } + } + + // run the flash calculation + NcpFlash::template solve(fsFlash, matParams, paramCache, globalMolarities); + + // use the result to assign the primary variables + assignNaive(fsFlash); + } + + /*! + * \brief Return the fluid phases which are present in a given + * control volume. + */ + short phasePresence() const + { return phasePresence_; } + + /*! + * \brief Set which fluid phases are present in a given control volume. + * + * \param value The new phase presence. The phase with index i is + * present if the i-th bit of \c value is 1. + */ + void setPhasePresence(short value) + { phasePresence_ = value; } + + /*! + * \brief Set whether a given indivividual phase should be present + * or not. + * + * \param phaseIdx The index of the phase which's presence ought to be set or reset. + * \param yesno If true, the presence of the phase is set, else it is reset + */ + void setPhasePresent(unsigned phaseIdx, bool yesno = true) + { + if (yesno) + setPhasePresence(phasePresence_ | (1 << phaseIdx)); + else + setPhasePresence(phasePresence_& ~(1 << phaseIdx)); + } + + /*! + * \brief Returns the index of the phase with's its saturation is + * determined by the closure condition of saturation. + */ + unsigned implicitSaturationIdx() const + { return lowestPresentPhaseIdx(); } + + /*! + * \brief Returns true iff a phase is present for a given phase + * presence. + * + * \param phaseIdx The index of the phase which's presence is + * queried. + * \param phasePresence The bit-map of present phases. + */ + static bool phaseIsPresent(unsigned phaseIdx, short phasePresence) + { return phasePresence& (1 << phaseIdx); } + + /*! + * \brief Returns true iff a phase is present for the current + * phase presence. + * + * \copydoc Doxygen::phaseIdxParam + */ + bool phaseIsPresent(unsigned phaseIdx) const + { return phasePresence_& (1 << phaseIdx); } + + /*! + * \brief Returns the phase with the lowest index that is present. + */ + unsigned lowestPresentPhaseIdx() const + { return static_cast(ffs(phasePresence_) - 1); } + + /*! + * \brief Assignment operator from an other primary variables object + */ + ThisType& operator=(const Implementation& value) + { + ParentType::operator=(value); + phasePresence_ = value.phasePresence_; + + return *this; + } + + /*! + * \brief Assignment operator from a scalar value + */ + ThisType& operator=(Scalar value) + { + ParentType::operator=(value); + + phasePresence_ = 0; + return *this; + } + + /*! + * \brief Returns an explcitly stored saturation for a given phase. + * + * (or 0 if the saturation is not explicitly stored.) + * + * \copydoc Doxygen::phaseIdxParam + */ + Evaluation explicitSaturationValue(unsigned phaseIdx, unsigned timeIdx) const + { + if (!phaseIsPresent(phaseIdx) || phaseIdx == lowestPresentPhaseIdx()) + // non-present phases have saturation 0 + return 0.0; + + unsigned varIdx = switch0Idx + phaseIdx - 1; + if (std::is_same::value) + return (*this)[varIdx]; // finite differences + else { + // automatic differentiation + if (timeIdx != 0) + Toolbox::createConstant((*this)[varIdx]); + return Toolbox::createVariable((*this)[varIdx], varIdx); + } + } + + /*! + * \copydoc ImmisciblePrimaryVariables::assignNaive + */ + template + void assignNaive(const FluidState& fluidState) + { + using FsToolbox = MathToolbox; + + // assign the phase temperatures. this is out-sourced to + // the energy module + EnergyModule::setPriVarTemperatures(*this, fluidState); + + // set the pressure of the first phase + (*this)[pressure0Idx] = FsToolbox::value(fluidState.pressure(/*phaseIdx=*/0)); + Valgrind::CheckDefined((*this)[pressure0Idx]); + + // determine the phase presence. + phasePresence_ = 0; + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + // use a NCP condition to determine if the phase is + // present or not + Scalar a = 1; + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + a -= FsToolbox::value(fluidState.moleFraction(phaseIdx, compIdx)); + } + Scalar b = FsToolbox::value(fluidState.saturation(phaseIdx)); + + if (b > a) + phasePresence_ |= (1 << phaseIdx); + } + + // some phase must be present + if (phasePresence_ == 0) + throw NumericalProblem("Phase state was 0, i.e., no fluid is present"); + + // set the primary variables which correspond to mole + // fractions of the present phase which has the lowest index. + unsigned lowestPhaseIdx = lowestPresentPhaseIdx(); + for (unsigned switchIdx = 0; switchIdx < numPhases - 1; ++switchIdx) { + unsigned phaseIdx = switchIdx; + unsigned compIdx = switchIdx + 1; + if (switchIdx >= lowestPhaseIdx) + ++phaseIdx; + + if (phaseIsPresent(phaseIdx)) { + (*this)[switch0Idx + switchIdx] = FsToolbox::value(fluidState.saturation(phaseIdx)); + Valgrind::CheckDefined((*this)[switch0Idx + switchIdx]); + } + else { + (*this)[switch0Idx + switchIdx] = + FsToolbox::value(fluidState.moleFraction(lowestPhaseIdx, compIdx)); + Valgrind::CheckDefined((*this)[switch0Idx + switchIdx]); + } + } + + // set the mole fractions in of the remaining components in + // the phase with the lowest index + for (unsigned compIdx = numPhases - 1; compIdx < numComponents - 1; ++compIdx) { + (*this)[switch0Idx + compIdx] = + FsToolbox::value(fluidState.moleFraction(lowestPhaseIdx, compIdx + 1)); + Valgrind::CheckDefined((*this)[switch0Idx + compIdx]); + } + } + + /*! + * \copydoc FlashPrimaryVariables::print + */ + void print(std::ostream& os = std::cout) const + { + os << "(p_" << FluidSystem::phaseName(0) << " = " + << this->operator[](pressure0Idx); + unsigned lowestPhaseIdx = lowestPresentPhaseIdx(); + for (unsigned switchIdx = 0; switchIdx < numPhases - 1; ++switchIdx) { + unsigned phaseIdx = switchIdx; + unsigned compIdx = switchIdx + 1; + if (phaseIdx >= lowestPhaseIdx) + ++phaseIdx; // skip the saturation of the present + // phase with the lowest index + + if (phaseIsPresent(phaseIdx)) { + os << ", S_" << FluidSystem::phaseName(phaseIdx) << " = " + << (*this)[switch0Idx + switchIdx]; + } + else { + os << ", x_" << FluidSystem::phaseName(lowestPhaseIdx) << "^" + << FluidSystem::componentName(compIdx) << " = " + << (*this)[switch0Idx + switchIdx]; + } + } + for (unsigned compIdx = numPhases - 1; compIdx < numComponents - 1; + ++compIdx) { + os << ", x_" << FluidSystem::phaseName(lowestPhaseIdx) << "^" + << FluidSystem::componentName(compIdx + 1) << " = " + << (*this)[switch0Idx + compIdx]; + } + os << ")"; + os << ", phase presence: " << static_cast(phasePresence_) << std::flush; + } + +private: + short phasePresence_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/pvs/pvsproperties.hh b/opm/models/pvs/pvsproperties.hh new file mode 100644 index 00000000000..b6ef7031850 --- /dev/null +++ b/opm/models/pvs/pvsproperties.hh @@ -0,0 +1,55 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup PvsModel + * + * \brief Declares the properties required for the compositional + * multi-phase primary variable switching model. + */ +#ifndef EWOMS_PVS_PROPERTIES_HH +#define EWOMS_PVS_PROPERTIES_HH + +#include +#include +#include +#include +#include +#include +#include + +namespace Opm::Properties { + +//! The basis value for the weight of the pressure primary variable +template +struct PvsPressureBaseWeight { using type = UndefinedProperty; }; +//! The basis value for the weight of the saturation primary variables +template +struct PvsSaturationsBaseWeight { using type = UndefinedProperty; }; +//! The basis value for the weight of the mole fraction primary variables +template +struct PvsMoleFractionsBaseWeight { using type = UndefinedProperty; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/pvs/pvsratevector.hh b/opm/models/pvs/pvsratevector.hh new file mode 100644 index 00000000000..68affebe5e7 --- /dev/null +++ b/opm/models/pvs/pvsratevector.hh @@ -0,0 +1,148 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::PvsRateVector + */ +#ifndef EWOMS_PVS_RATE_VECTOR_HH +#define EWOMS_PVS_RATE_VECTOR_HH + +#include "pvsindices.hh" + +#include +#include +#include + +#include + +namespace Opm { + +/*! + * \ingroup PvsModel + * + * \brief Implements a vector representing molar, mass or volumetric rates. + * + * This class is basically a Dune::FieldVector which can be set using either mass, molar + * or volumetric rates. + */ +template +class PvsRateVector + : public Dune::FieldVector, + getPropValue()> +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { numComponents = getPropValue() }; + enum { numEq = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + using ParentType = Dune::FieldVector; + using EnergyModule = Opm::EnergyModule; + +public: + PvsRateVector() : ParentType() + { Opm::Valgrind::SetUndefined(*this); } + + /*! + * \copydoc ImmiscibleRateVector::ImmiscibleRateVector(Scalar) + */ + PvsRateVector(const Evaluation& value) + : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleRateVector::ImmiscibleRateVector(const ImmiscibleRateVector&) + */ + PvsRateVector(const PvsRateVector& value) : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleRateVector::setMassRate + */ + void setMassRate(const ParentType& value) + { + // convert to molar rates + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + (*this)[conti0EqIdx + compIdx] = value[conti0EqIdx + compIdx]; + (*this)[conti0EqIdx + compIdx] /= FluidSystem::molarMass(compIdx); + } + } + + /*! + * \copydoc ImmiscibleRateVector::setMolarRate + */ + void setMolarRate(const ParentType& value) + { ParentType::operator=(value); } + + /*! + * \copydoc ImmiscibleRateVector::setEnthalpyRate + */ + template + void setEnthalpyRate(const RhsEval& rate) + { EnergyModule::setEnthalpyRate(*this, rate); } + + /*! + * \copydoc ImmiscibleRateVector::setVolumetricRate + */ + template + void setVolumetricRate(const FluidState& fluidState, unsigned phaseIdx, const RhsEval& volume) + { + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) + (*this)[conti0EqIdx + compIdx] = + fluidState.density(phaseIdx, compIdx) + * fluidState.moleFraction(phaseIdx, compIdx) + * volume; + + EnergyModule::setEnthalpyRate(*this, fluidState, phaseIdx, volume); + } + + /*! + * \brief Assignment operator from a scalar or a function evaluation + */ + template + PvsRateVector& operator=(const RhsEval& value) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = value; + return *this; + } + + /*! + * \brief Assignment operator from another rate vector + */ + PvsRateVector& operator=(const PvsRateVector& other) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = other[i]; + return *this; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/richards/richardsboundaryratevector.hh b/opm/models/richards/richardsboundaryratevector.hh new file mode 100644 index 00000000000..52b1e227227 --- /dev/null +++ b/opm/models/richards/richardsboundaryratevector.hh @@ -0,0 +1,165 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsBoundaryRateVector + */ +#ifndef EWOMS_RICHARDS_BOUNDARY_RATE_VECTOR_HH +#define EWOMS_RICHARDS_BOUNDARY_RATE_VECTOR_HH + +#include +#include + +#include "richardsintensivequantities.hh" + +namespace Opm { + +/*! + * \ingroup RichardsModel + * + * \brief Implements a boundary vector for the fully implicit Richards model. + */ +template +class RichardsBoundaryRateVector : public GetPropType +{ + using ParentType = GetPropType; + using ExtensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Indices = GetPropType; + + enum { numEq = getPropValue() }; + enum { contiEqIdx = Indices::contiEqIdx }; + enum { liquidPhaseIdx = getPropValue() }; + + using Toolbox = Opm::MathToolbox; + +public: + RichardsBoundaryRateVector() : ParentType() + {} + + /*! + * \copydoc + * ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(Scalar) + */ + RichardsBoundaryRateVector(const Evaluation& value) + : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleBoundaryRateVector::ImmiscibleBoundaryRateVector(const + * ImmiscibleBoundaryRateVector& ) + */ + RichardsBoundaryRateVector(const RichardsBoundaryRateVector& value) = default; + RichardsBoundaryRateVector& operator=(const RichardsBoundaryRateVector& value) = default; + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setFreeFlow + */ + template + void setFreeFlow(const Context& context, unsigned bfIdx, unsigned timeIdx, const FluidState& fluidState) + { + ExtensiveQuantities extQuants; + extQuants.updateBoundary(context, bfIdx, timeIdx, fluidState); + const auto& insideIntQuants = context.intensiveQuantities(bfIdx, timeIdx); + unsigned focusDofIdx = context.focusDofIndex(); + unsigned interiorDofIdx = context.interiorScvIndex(bfIdx, timeIdx); + + //////// + // advective fluxes of all components in all phases + //////// + (*this) = Evaluation(0.0); + + unsigned phaseIdx = liquidPhaseIdx; + Evaluation density; + if (fluidState.pressure(phaseIdx) > insideIntQuants.fluidState().pressure(phaseIdx)) { + if (focusDofIdx == interiorDofIdx) + density = fluidState.density(phaseIdx); + else + density = Opm::getValue(fluidState.density(phaseIdx)); + } + else if (focusDofIdx == interiorDofIdx) + density = insideIntQuants.fluidState().density(phaseIdx); + else + density = Opm::getValue(insideIntQuants.fluidState().density(phaseIdx)); + + // add advective flux of current component in current + // phase + (*this)[contiEqIdx] += extQuants.volumeFlux(phaseIdx) * density; + +#ifndef NDEBUG + for (unsigned i = 0; i < numEq; ++i) { + Opm::Valgrind::CheckDefined((*this)[i]); + } + Opm::Valgrind::CheckDefined(*this); +#endif + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setInFlow + */ + template + void setInFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the direction opposite to the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Evaluation& val = this->operator[](eqIdx); + val = Toolbox::min(0.0, val); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setOutFlow + */ + template + void setOutFlow(const Context& context, + unsigned bfIdx, + unsigned timeIdx, + const FluidState& fluidState) + { + this->setFreeFlow(context, bfIdx, timeIdx, fluidState); + + // we only allow fluxes in the same direction as the outer unit normal + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + Evaluation& val = this->operator[](eqIdx); + val = Toolbox::max(0.0, val); + } + } + + /*! + * \copydoc ImmiscibleBoundaryRateVector::setNoFlow + */ + void setNoFlow() + { (*this) = Evaluation(0.0); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/richards/richardsextensivequantities.hh b/opm/models/richards/richardsextensivequantities.hh new file mode 100644 index 00000000000..13a0fcb5012 --- /dev/null +++ b/opm/models/richards/richardsextensivequantities.hh @@ -0,0 +1,52 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsExtensiveQuantities + */ +#ifndef EWOMS_RICHARDS_EXTENSIVE_QUANTITIES_HH +#define EWOMS_RICHARDS_EXTENSIVE_QUANTITIES_HH + +#include "richardsproperties.hh" + +#include + +namespace Opm { + +/*! + * \ingroup RichardsModel + * \ingroup ExtensiveQuantities + * + * \brief Calculates and stores the data which is required to + * calculate the flux of fluid over a face of a finite volume. + */ +template +class RichardsExtensiveQuantities + : public MultiPhaseBaseExtensiveQuantities +{ +}; + +} // namespace Opm + +#endif diff --git a/opm/models/richards/richardsindices.hh b/opm/models/richards/richardsindices.hh new file mode 100644 index 00000000000..d82ca37b5c3 --- /dev/null +++ b/opm/models/richards/richardsindices.hh @@ -0,0 +1,52 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsIndices + */ +#ifndef EWOMS_RICHARDS_INDICES_HH +#define EWOMS_RICHARDS_INDICES_HH + +namespace Opm { + +/*! + * \ingroup RichardsModel + * \brief Indices for the primary variables/conservation equations of the + * Richards model. + */ +struct RichardsIndices +{ + //! Primary variable index for the wetting phase pressure + static const int pressureWIdx = 0; + + //! Equation index for the mass conservation of the wetting phase + static const int contiEqIdx = 0; + + //! The number of equations + static const int numEq = 1; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/richards/richardsintensivequantities.hh b/opm/models/richards/richardsintensivequantities.hh new file mode 100644 index 00000000000..89b81ba5f8c --- /dev/null +++ b/opm/models/richards/richardsintensivequantities.hh @@ -0,0 +1,199 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsIntensiveQuantities + */ +#ifndef EWOMS_RICHARDS_INTENSIVE_QUANTITIES_HH +#define EWOMS_RICHARDS_INTENSIVE_QUANTITIES_HH + +#include "richardsproperties.hh" + +#include + +#include +#include + +namespace Opm { + +/*! + * \ingroup RichardsModel + * \ingroup IntensiveQuantities + * + * \brief Intensive quantities required by the Richards model. + */ +template +class RichardsIntensiveQuantities + : public GetPropType + , public GetPropType::FluxIntensiveQuantities +{ + using ParentType = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using ElementContext = GetPropType; + using GridView = GetPropType; + using FluxModule = GetPropType; + + using Indices = GetPropType; + enum { pressureWIdx = Indices::pressureWIdx }; + enum { numPhases = FluidSystem::numPhases }; + enum { liquidPhaseIdx = getPropValue() }; + enum { gasPhaseIdx = getPropValue() }; + enum { dimWorld = GridView::dimensionworld }; + + using FluxIntensiveQuantities = typename FluxModule::FluxIntensiveQuantities; + using DimMatrix = Dune::FieldMatrix; + using ScalarPhaseVector = Dune::FieldVector; + using PhaseVector = Dune::FieldVector; + using Toolbox = Opm::MathToolbox; + +public: + //! The type returned by the fluidState() method + using FluidState = Opm::ImmiscibleFluidState; + + RichardsIntensiveQuantities() + {} + + RichardsIntensiveQuantities(const RichardsIntensiveQuantities& other) = default; + + RichardsIntensiveQuantities& operator=(const RichardsIntensiveQuantities& other) = default; + + /*! + * \copydoc IntensiveQuantities::update + */ + void update(const ElementContext& elemCtx, unsigned dofIdx, unsigned timeIdx) + { + ParentType::update(elemCtx, dofIdx, timeIdx); + + const auto& T = elemCtx.problem().temperature(elemCtx, dofIdx, timeIdx); + fluidState_.setTemperature(T); + + // material law parameters + const auto& problem = elemCtx.problem(); + const typename MaterialLaw::Params& materialParams = + problem.materialLawParams(elemCtx, dofIdx, timeIdx); + const auto& priVars = elemCtx.primaryVars(dofIdx, timeIdx); + + ///////// + // calculate the pressures + ///////// + + // first, we have to find the minimum capillary pressure (i.e. Sw = 0) + fluidState_.setSaturation(liquidPhaseIdx, 1.0); + fluidState_.setSaturation(gasPhaseIdx, 0.0); + ScalarPhaseVector pC; + MaterialLaw::capillaryPressures(pC, materialParams, fluidState_); + + // non-wetting pressure can be larger than the + // reference pressure if the medium is fully + // saturated by the wetting phase + const Evaluation& pW = priVars.makeEvaluation(pressureWIdx, timeIdx); + Evaluation pN = + Toolbox::max(elemCtx.problem().referencePressure(elemCtx, dofIdx, /*timeIdx=*/0), + pW + (pC[gasPhaseIdx] - pC[liquidPhaseIdx])); + + ///////// + // calculate the saturations + ///////// + fluidState_.setPressure(liquidPhaseIdx, pW); + fluidState_.setPressure(gasPhaseIdx, pN); + + PhaseVector sat; + MaterialLaw::saturations(sat, materialParams, fluidState_); + fluidState_.setSaturation(liquidPhaseIdx, sat[liquidPhaseIdx]); + fluidState_.setSaturation(gasPhaseIdx, sat[gasPhaseIdx]); + + typename FluidSystem::template ParameterCache paramCache; + paramCache.updateAll(fluidState_); + + // compute and set the wetting phase viscosity + const Evaluation& mu = FluidSystem::viscosity(fluidState_, paramCache, liquidPhaseIdx); + fluidState_.setViscosity(liquidPhaseIdx, mu); + fluidState_.setViscosity(gasPhaseIdx, 1e-20); + + // compute and set the wetting phase density + const Evaluation& rho = FluidSystem::density(fluidState_, paramCache, liquidPhaseIdx); + fluidState_.setDensity(liquidPhaseIdx, rho); + fluidState_.setDensity(gasPhaseIdx, 1e-20); + + // relperms + MaterialLaw::relativePermeabilities(relativePermeability_, materialParams, fluidState_); + + // mobilities + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + mobility_[phaseIdx] = relativePermeability_[phaseIdx]/fluidState_.viscosity(phaseIdx); + + // porosity + porosity_ = problem.porosity(elemCtx, dofIdx, timeIdx); + + // intrinsic permeability + intrinsicPerm_ = problem.intrinsicPermeability(elemCtx, dofIdx, timeIdx); + + // update the quantities specific for the velocity model + FluxIntensiveQuantities::update_(elemCtx, dofIdx, timeIdx); + } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::fluidState + */ + const FluidState& fluidState() const + { return fluidState_; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::porosity + */ + const Evaluation& porosity() const + { return porosity_; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::intrinsicPermeability + */ + const DimMatrix& intrinsicPermeability() const + { return intrinsicPerm_; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::relativePermeability + */ + const Evaluation& relativePermeability(unsigned phaseIdx) const + { return relativePermeability_[phaseIdx]; } + + /*! + * \copydoc ImmiscibleIntensiveQuantities::mobility + */ + const Evaluation& mobility(unsigned phaseIdx) const + { return mobility_[phaseIdx]; } + +private: + FluidState fluidState_; + DimMatrix intrinsicPerm_; + std::array relativePermeability_; + std::array mobility_; + Evaluation porosity_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/richards/richardslocalresidual.hh b/opm/models/richards/richardslocalresidual.hh new file mode 100644 index 00000000000..ee0d88dc127 --- /dev/null +++ b/opm/models/richards/richardslocalresidual.hh @@ -0,0 +1,111 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsLocalResidual + */ +#ifndef EWOMS_RICHARDS_LOCAL_RESIDUAL_HH +#define EWOMS_RICHARDS_LOCAL_RESIDUAL_HH + +#include "richardsintensivequantities.hh" + +#include "richardsextensivequantities.hh" + +namespace Opm { + +/*! + * \ingroup RichardsModel + * \brief Element-wise calculation of the residual for the Richards model. + */ +template +class RichardsLocalResidual : public GetPropType +{ + using EqVector = GetPropType; + using Evaluation = GetPropType; + using RateVector = GetPropType; + using IntensiveQuantities = GetPropType; + using ElementContext = GetPropType; + using Indices = GetPropType; + + enum { contiEqIdx = Indices::contiEqIdx }; + enum { liquidPhaseIdx = getPropValue() }; + enum { numEq = getPropValue() }; + using Toolbox = Opm::MathToolbox; + +public: + /*! + * \copydoc ImmiscibleLocalResidual::computeStorage + */ + template + void computeStorage(Dune::FieldVector& storage, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { + const IntensiveQuantities& intQuants = elemCtx.intensiveQuantities(dofIdx, timeIdx); + + // partial time derivative of the wetting phase mass + storage[contiEqIdx] = + Toolbox::template decay(intQuants.fluidState().density(liquidPhaseIdx)) + *Toolbox::template decay(intQuants.fluidState().saturation(liquidPhaseIdx)) + *Toolbox::template decay(intQuants.porosity()); + } + + /*! + * \copydoc ImmiscibleLocalResidual::computeFlux + */ + void computeFlux(RateVector& flux, + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) const + { + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + unsigned focusDofIdx = elemCtx.focusDofIndex(); + unsigned upIdx = static_cast(extQuants.upstreamIndex(liquidPhaseIdx)); + + const IntensiveQuantities& up = elemCtx.intensiveQuantities(upIdx, timeIdx); + + // compute advective mass flux of the liquid phase. This is slightly hacky + // because it is specific to the element-centered finite volume method. + const Evaluation& rho = up.fluidState().density(liquidPhaseIdx); + if (focusDofIdx == upIdx) + flux[contiEqIdx] = extQuants.volumeFlux(liquidPhaseIdx)*rho; + else + flux[contiEqIdx] = extQuants.volumeFlux(liquidPhaseIdx)*Toolbox::value(rho); + } + + /*! + * \copydoc ImmiscibleLocalResidual::computeSource + */ + void computeSource(RateVector& source, + const ElementContext& elemCtx, + unsigned dofIdx, + unsigned timeIdx) const + { elemCtx.problem().source(source, elemCtx, dofIdx, timeIdx); } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/richards/richardsmodel.hh b/opm/models/richards/richardsmodel.hh new file mode 100644 index 00000000000..633a63acd32 --- /dev/null +++ b/opm/models/richards/richardsmodel.hh @@ -0,0 +1,388 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsModel + */ +#ifndef EWOMS_RICHARDS_MODEL_HH +#define EWOMS_RICHARDS_MODEL_HH + +#include + +#include "richardsproperties.hh" +#include "richardsindices.hh" +#include "richardslocalresidual.hh" +#include "richardsextensivequantities.hh" +#include "richardsratevector.hh" +#include "richardsboundaryratevector.hh" +#include "richardsprimaryvariables.hh" +#include "richardsintensivequantities.hh" +#include "richardsnewtonmethod.hh" + +#include + +#include +#include +#include +#include + +#include +#include + +namespace Opm { +template +class RichardsModel; +} + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { +//! The type tag for problems discretized using the Richards model +struct Richards { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +//! By default, assume that the first phase is the liquid one +template +struct LiquidPhaseIndex { static constexpr int value = 0; }; + +//! By default, assume that the non-liquid phase is gaseos +template +struct GasPhaseIndex { static constexpr int value = 1 - getPropValue(); }; + +/*! + * \brief By default, assume that component which the liquid is made of has + * the same index as the liquid phase. + * + * This is a convention which works for most fluid systems shipped + * with eWoms by default, but it cannot generally correct because the + * liquid can be composed of different components. (e.g., do you + * prefer Ethanol of H2O??) + */ +template +struct LiquidComponentIndex { static constexpr int value = getPropValue(); }; + +//! By default, assume that the gas component is the other than the liquid one +template +struct GasComponentIndex { static constexpr int value = 1 - getPropValue(); }; + +//! The local residual operator +template +struct LocalResidual { using type = Opm::RichardsLocalResidual; }; + +//! The global model used +template +struct Model { using type = Opm::RichardsModel; }; + +//! the RateVector property +template +struct RateVector { using type = Opm::RichardsRateVector; }; + +//! the BoundaryRateVector property +template +struct BoundaryRateVector { using type = Opm::RichardsBoundaryRateVector; }; + +//! the PrimaryVariables property +template +struct PrimaryVariables { using type = Opm::RichardsPrimaryVariables; }; + +//! The class for the intensive quantities +template +struct IntensiveQuantities { using type = Opm::RichardsIntensiveQuantities; }; + +//! The class for the quantities required for the flux calculation +template +struct ExtensiveQuantities { using type = Opm::RichardsExtensiveQuantities; }; + +//! The class of the Newton method +template +struct NewtonMethod { using type = Opm::RichardsNewtonMethod; }; + +//! The class with all index definitions for the model +template +struct Indices { using type = Opm::RichardsIndices; }; + +/*! + * \brief The wetting phase used. + * + * By default we use the null-phase, i.e. this has to be defined by + * the problem for the program to work. Please be aware that you + * should be careful to use the Richards model in conjunction with + * liquid non-wetting phases. This is only meaningful if the viscosity + * of the liquid phase is _much_ lower than the viscosity of the + * wetting phase. + */ +template +struct WettingFluid +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::LiquidPhase >; +}; + +/*! + * \brief The non-wetting phase used. + * + * By default we use the null-phase, i.e. this has to be defined by + * the problem for the program to work. This doed not need to be + * specified by the problem for the Richards model to work because the + * Richards model does not conserve the non-wetting phase. + */ +template +struct NonWettingFluid +{ +private: + using Scalar = GetPropType; + +public: + using type = Opm::GasPhase >; +}; + +/*! + *\brief The fluid system used by the model. + * + * By default this uses the immiscible twophase fluid system. The + * actual fluids used are specified using in the problem definition by + * the WettingFluid and NonWettingFluid properties. Be aware that + * using different fluid systems in conjunction with the Richards + * model only makes very limited sense. + */ +template +struct FluidSystem +{ +private: + using Scalar = GetPropType; + using WettingFluid = GetPropType; + using NonWettingFluid = GetPropType; + +public: + using type = Opm::TwoPhaseImmiscibleFluidSystem; +}; + + +} // namespace Opm::Properties + +namespace Opm { + +/*! + * \ingroup RichardsModel + * + * \brief This model implements a variant of the Richards equation for + * quasi-twophase flow. + * + * In the unsaturated zone, Richards' equation is frequently used to + * approximate the water distribution above the groundwater level. It + * can be derived from the two-phase equations, i.e. + * \f[ + * \frac{\partial\;\phi S_\alpha \rho_\alpha}{\partial t} + * - + * \mathrm{div} \left\{ + * \rho_\alpha \frac{k_{r\alpha}}{\mu_\alpha}\; \mathbf{K}\; + * \mathbf{grad}\left[ + * p_\alpha - g\rho_\alpha + * \right] + * \right\} + * = + * q_\alpha, + * \f] + * where \f$\alpha \in \{w, n\}\f$ is the index of the fluid phase, + * \f$\rho_\alpha\f$ is the fluid density, \f$S_\alpha\f$ is the fluid + * saturation, \f$\phi\f$ is the porosity of the soil, + * \f$k_{r\alpha}\f$ is the relative permeability for the fluid, + * \f$\mu_\alpha\f$ is the fluid's dynamic viscosity, \f$\mathbf{K}\f$ + * is the intrinsic permeability tensor, \f$p_\alpha\f$ is the fluid + * phase pressure and \f$g\f$ is the potential of the gravity field. + * + * In contrast to the "full" two-phase model, the Richards model + * assumes that the non-wetting fluid is gas and that it thus exhibits + * a much lower viscosity than the (liquid) wetting phase. (This + * assumption is quite realistic in many applications: For example, at + * atmospheric pressure and at room temperature, the viscosity of air + * is only about \f$1\%\f$ of the viscosity of liquid water.) As a + * consequence, the \f$\frac{k_{r\alpha}}{\mu_\alpha}\f$ term + * typically is much larger for the gas phase than for the wetting + * phase. Using this reasoning, the Richards model assumes that + * \f$\frac{k_{rn}}{\mu_n}\f$ is infinitely large compared to the same + * term of the liquid phase. This implies that the pressure of the gas + * phase is equivalent to the static pressure distribution and that + * therefore, mass conservation only needs to be considered for the + * liquid phase. + * + * The model thus choses the absolute pressure of the wetting phase + * \f$p_w\f$ as its only primary variable. The wetting phase + * saturation is calculated using the inverse of the capillary + * pressure, i.e. + * \f[ + * S_w = p_c^{-1}(p_n - p_w) + * \f] + * holds, where \f$p_n\f$ is a reference pressure given by the + * problem's \c referencePressure() method. Nota bene, that the last + * step assumes that the capillary pressure-saturation curve can be + * uniquely inverted, i.e. it is not possible to set the capillary + * pressure to zero if the Richards model ought to be used! + */ +template +class RichardsModel + : public MultiPhaseBaseModel +{ + using ParentType = MultiPhaseBaseModel; + + using Simulator = GetPropType; + + using Scalar = GetPropType; + using FluidSystem = GetPropType; + using Indices = GetPropType; + + static const unsigned numPhases = FluidSystem::numPhases; + static const unsigned numComponents = FluidSystem::numComponents; + + static const unsigned liquidPhaseIdx = getPropValue(); + static const unsigned gasPhaseIdx = getPropValue(); + + static const unsigned liquidCompIdx = getPropValue(); + static const unsigned gasCompIdx = getPropValue(); + + + // some consistency checks + static_assert(numPhases == 2, + "Exactly two fluids are required for this model"); + static_assert(numComponents == 2, + "Exactly two components are required for this model"); + static_assert(liquidPhaseIdx != gasPhaseIdx, + "The liquid and the gas phases must be different"); + static_assert(liquidCompIdx != gasCompIdx, + "The liquid and the gas components must be different"); + +public: + RichardsModel(Simulator& simulator) + : ParentType(simulator) + { + // the liquid phase must be liquid, the gas phase must be + // gaseous. Think about it! + assert(FluidSystem::isLiquid(liquidPhaseIdx)); + assert(!FluidSystem::isLiquid(gasPhaseIdx)); + } + + /*! + * \copydoc FvBaseDiscretization::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + } + + /*! + * \copydoc FvBaseDiscretization::name + */ + static std::string name() + { return "richards"; } + + /*! + * \copydoc FvBaseDiscretization::primaryVarName + */ + std::string primaryVarName(unsigned pvIdx) const + { + std::ostringstream oss; + if (pvIdx == Indices::pressureWIdx) + oss << "pressure_" << FluidSystem::phaseName(liquidPhaseIdx); + else + assert(0); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::eqName + */ + std::string eqName(unsigned eqIdx) const + { + std::ostringstream oss; + if (eqIdx == Indices::contiEqIdx) + oss << "continuity_" << FluidSystem::phaseName(liquidPhaseIdx); + else + assert(0); + + return oss.str(); + } + + /*! + * \copydoc FvBaseDiscretization::primaryVarWeight + */ + Scalar primaryVarWeight(unsigned, unsigned pvIdx) const + { + if (Indices::pressureWIdx == pvIdx) { + return 10 / referencePressure_; + } + + return 1; + } + + /*! + * \copydoc FvBaseDiscretization::eqWeight + */ + Scalar eqWeight(unsigned, [[maybe_unused]] unsigned eqIdx) const + { + assert((eqIdx - Indices::contiEqIdx) <= FluidSystem::numPhases); + + // make all kg equal + return 1.0; + } + + /*! + * \copydoc FvBaseDiscretization::updateBegin + */ + void updateBegin() + { + ParentType::updateBegin(); + + // find the a reference pressure. The first degree of freedom + // might correspond to non-interior entities which would lead + // to an undefined value, so we have to iterate... + for (unsigned dofIdx = 0; dofIdx < this->numGridDof(); ++ dofIdx) { + if (this->isLocalDof(dofIdx)) { + referencePressure_ = + this->solution(/*timeIdx=*/0)[dofIdx][/*pvIdx=*/Indices::pressureWIdx]; + break; + } + } + } + + /*! + * \copydoc FvBaseDiscretization::phaseIsConsidered + */ + bool phaseIsConsidered(unsigned phaseIdx) const + { return phaseIdx == liquidPhaseIdx; } + + void registerOutputModules_() + { + ParentType::registerOutputModules_(); + } + + mutable Scalar referencePressure_; +}; +} // namespace Opm + +#endif diff --git a/opm/models/richards/richardsnewtonmethod.hh b/opm/models/richards/richardsnewtonmethod.hh new file mode 100644 index 00000000000..e6c9b98a14d --- /dev/null +++ b/opm/models/richards/richardsnewtonmethod.hh @@ -0,0 +1,158 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsNewtonMethod + */ +#ifndef EWOMS_RICHARDS_NEWTON_METHOD_HH +#define EWOMS_RICHARDS_NEWTON_METHOD_HH + +#include "richardsproperties.hh" + +#include + +#include + +namespace Opm { + +/*! + * \ingroup RichardsModel + * + * \brief A Richards model specific Newton method. + */ +template +class RichardsNewtonMethod : public GetPropType +{ + using ParentType = GetPropType; + + using Scalar = GetPropType; + using PrimaryVariables = GetPropType; + using EqVector = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using Simulator = GetPropType; + using Linearizer = GetPropType; + + using Indices = GetPropType; + enum { pressureWIdx = Indices::pressureWIdx }; + enum { numPhases = FluidSystem::numPhases }; + enum { liquidPhaseIdx = getPropValue() }; + enum { gasPhaseIdx = getPropValue() }; + + using PhaseVector = Dune::FieldVector; + +public: + RichardsNewtonMethod(Simulator& simulator) : ParentType(simulator) + {} + +protected: + friend NewtonMethod; + friend ParentType; + + /*! + * \copydoc FvBaseNewtonMethod::updatePrimaryVariables_ + */ + void updatePrimaryVariables_(unsigned globalDofIdx, + PrimaryVariables& nextValue, + const PrimaryVariables& currentValue, + const EqVector& update, + const EqVector&) + { + // normal Newton-Raphson update + nextValue = currentValue; + nextValue -= update; + + // do not clamp anything after 4 iterations + if (this->numIterations_ > 4) + return; + + const auto& problem = this->simulator_.problem(); + + // calculate the old wetting phase saturation + const MaterialLawParams& matParams = + problem.materialLawParams(globalDofIdx, /*timeIdx=*/0); + + Opm::ImmiscibleFluidState fs; + + // set the temperature + Scalar T = problem.temperature(globalDofIdx, /*timeIdx=*/0); + fs.setTemperature(T); + + ///////// + // calculate the phase pressures of the previous iteration + ///////// + + // first, we have to find the minimum capillary pressure + // (i.e. Sw = 0) + fs.setSaturation(liquidPhaseIdx, 1.0); + fs.setSaturation(gasPhaseIdx, 0.0); + PhaseVector pC; + MaterialLaw::capillaryPressures(pC, matParams, fs); + + // non-wetting pressure can be larger than the + // reference pressure if the medium is fully + // saturated by the wetting phase + Scalar pWOld = currentValue[pressureWIdx]; + Scalar pNOld = + std::max(problem.referencePressure(globalDofIdx, /*timeIdx=*/0), + pWOld + (pC[gasPhaseIdx] - pC[liquidPhaseIdx])); + + ///////// + // find the saturations of the previous iteration + ///////// + fs.setPressure(liquidPhaseIdx, pWOld); + fs.setPressure(gasPhaseIdx, pNOld); + + PhaseVector satOld; + MaterialLaw::saturations(satOld, matParams, fs); + satOld[liquidPhaseIdx] = std::max(0.0, satOld[liquidPhaseIdx]); + + ///////// + // find the wetting phase pressures which + // corrospond to a 20% increase and a 20% decrease + // of the wetting saturation + ///////// + fs.setSaturation(liquidPhaseIdx, satOld[liquidPhaseIdx] - 0.2); + fs.setSaturation(gasPhaseIdx, 1.0 - (satOld[liquidPhaseIdx] - 0.2)); + MaterialLaw::capillaryPressures(pC, matParams, fs); + Scalar pwMin = pNOld - (pC[gasPhaseIdx] - pC[liquidPhaseIdx]); + + fs.setSaturation(liquidPhaseIdx, satOld[liquidPhaseIdx] + 0.2); + fs.setSaturation(gasPhaseIdx, 1.0 - (satOld[liquidPhaseIdx] + 0.2)); + MaterialLaw::capillaryPressures(pC, matParams, fs); + Scalar pwMax = pNOld - (pC[gasPhaseIdx] - pC[liquidPhaseIdx]); + + ///////// + // clamp the result to the minimum and the maximum + // pressures we just calculated + ///////// + Scalar pW = nextValue[pressureWIdx]; + pW = std::max(pwMin, std::min(pW, pwMax)); + nextValue[pressureWIdx] = pW; + } +}; +} // namespace Opm + +#endif diff --git a/opm/models/richards/richardsprimaryvariables.hh b/opm/models/richards/richardsprimaryvariables.hh new file mode 100644 index 00000000000..8fecd27088f --- /dev/null +++ b/opm/models/richards/richardsprimaryvariables.hh @@ -0,0 +1,192 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsPrimaryVariables + */ +#ifndef EWOMS_RICHARDS_PRIMARY_VARIABLES_HH +#define EWOMS_RICHARDS_PRIMARY_VARIABLES_HH + +#include "richardsproperties.hh" + +#include + +#include +#include +#include + +#include + +namespace Opm { + +/*! + * \ingroup RichardsModel + * + * \brief Represents the primary variables used in the Richards model. + * + * This class is basically a Dune::FieldVector which can retrieve its + * contents from an aribitatry fluid state. + */ +template +class RichardsPrimaryVariables : public FvBasePrimaryVariables +{ + using ParentType = FvBasePrimaryVariables; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using MaterialLaw = GetPropType; + using MaterialLawParams = GetPropType; + using EnergyModule = GetPropType; + using Indices = GetPropType; + + // primary variable indices + enum { pressureWIdx = Indices::pressureWIdx }; + + enum { liquidPhaseIdx = getPropValue() }; + enum { gasPhaseIdx = getPropValue() }; + + enum { numPhases = getPropValue() }; + enum { numComponents = getPropValue() }; + + using ComponentVector = Dune::FieldVector; + using PhaseVector = Dune::FieldVector; + using Toolbox = typename Opm::MathToolbox; + using ImmiscibleFlash = Opm::ImmiscibleFlash; + +public: + RichardsPrimaryVariables() : ParentType() + { Opm::Valgrind::SetUndefined(*this); } + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(Scalar) + */ + RichardsPrimaryVariables(Scalar value) : ParentType(value) + {} + + /*! + * \copydoc ImmisciblePrimaryVariables::ImmisciblePrimaryVariables(const + * ImmisciblePrimaryVariables& ) + */ + RichardsPrimaryVariables(const RichardsPrimaryVariables& value) = default; + RichardsPrimaryVariables& operator=(const RichardsPrimaryVariables& value) = default; + + /*! + * \brief Set the primary variables with the wetting phase + * pressure, saturation and temperature. + * + * \param T The temperature [K] + * \param pw The pressure of the wetting phase [Pa] + * \param Sw The saturation of the wetting phase [] + * \param matParams The capillary pressure law parameters + */ + void assignImmiscibleFromWetting(Scalar T, Scalar pw, Scalar Sw, + const MaterialLawParams& matParams) + { + Opm::ImmiscibleFluidState fs; + + fs.setTemperature(T); + fs.setSaturation(liquidPhaseIdx, Sw); + fs.setSaturation(gasPhaseIdx, 1 - Sw); + + // set phase pressures + PhaseVector pC; + MaterialLaw::capillaryPressures(pC, matParams, fs); + + fs.setPressure(liquidPhaseIdx, pw); + fs.setPressure(gasPhaseIdx, pw + (pC[gasPhaseIdx] - pC[liquidPhaseIdx])); + + assignNaive(fs); + } + + /*! + * \brief Set the primary variables with the non-wetting phase + * pressure, saturation and temperature. + * + * \param T The temperature [K] + * \param pn The pressure of the non-wetting phase [Pa] + * \param Sn The saturation of the non-wetting phase [] + * \param matParams The capillary pressure law parameters + */ + void assignImmiscibleFromNonWetting(Scalar T, Scalar pn, Scalar Sn, + const MaterialLawParams& matParams) + { + Opm::ImmiscibleFluidState fs; + + fs.setTemperature(T); + fs.setSaturation(liquidPhaseIdx, 1 - Sn); + fs.setSaturation(gasPhaseIdx, Sn); + + // set phase pressures + PhaseVector pC; + MaterialLaw::capillaryPressures(pC, matParams, fs); + + fs.setPressure(gasPhaseIdx, pn); + fs.setPressure(gasPhaseIdx, pn + (pC[liquidPhaseIdx] - pC[gasPhaseIdx])); + + assignNaive(fs); + } + + /*! + * \copydoc ImmisciblePrimaryVariables::assignMassConservative + */ + template + void assignMassConservative(const FluidState& fluidState, + const MaterialLawParams& matParams, + bool = false) + { + ComponentVector globalMolarities(0.0); + for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) { + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + globalMolarities[compIdx] += + fluidState.molarity(phaseIdx, compIdx) * fluidState.saturation(phaseIdx); + } + } + + Opm::ImmiscibleFluidState fsFlash; + fsFlash.assign(fluidState); + typename FluidSystem::ParameterCache paramCache; + ImmiscibleFlash::template solve(fsFlash, paramCache, + matParams, + globalMolarities); + + assignNaive(fsFlash); + } + + /*! + * \copydoc ImmisciblePrimaryVariables::assignNaive + */ + template + void assignNaive(const FluidState& fluidState) + { + // assign the phase temperatures. this is out-sourced to + // the energy module + EnergyModule::setPriVarTemperatures(*this, fluidState); + + (*this)[pressureWIdx] = fluidState.pressure(liquidPhaseIdx); + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/richards/richardsproperties.hh b/opm/models/richards/richardsproperties.hh new file mode 100644 index 00000000000..ed82af32d21 --- /dev/null +++ b/opm/models/richards/richardsproperties.hh @@ -0,0 +1,67 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup RichardsModel + * + * \brief Contains the property declarations for the Richards model. + */ +#ifndef EWOMS_RICHARDS_PROPERTIES_HH +#define EWOMS_RICHARDS_PROPERTIES_HH + +#include + +// \{ +namespace Opm::Properties { + +//! The fluid used as the wetting phase (by default, we set the fluid +//! system to the immiscible one, which requires this property.) +template +struct WettingFluid { using type = UndefinedProperty; }; + +//! The fluid used as the non-wetting phase (by default, we set the +//! fluid system to the immiscible one, which requires this property.) +template +struct NonWettingFluid { using type = UndefinedProperty; }; + +//! Index of the fluid which represents the wetting phase +template +struct LiquidPhaseIndex { using type = UndefinedProperty; }; + +//! Index of the fluid which represents the non-wetting phase +template +struct GasPhaseIndex { using type = UndefinedProperty; }; + +//! Index of the component which constitutes the liquid +template +struct LiquidComponentIndex { using type = UndefinedProperty; }; + +//! Index of the component which constitutes the gas +template +struct GasComponentIndex { using type = UndefinedProperty; }; + +// \} + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/richards/richardsratevector.hh b/opm/models/richards/richardsratevector.hh new file mode 100644 index 00000000000..22e80aade0e --- /dev/null +++ b/opm/models/richards/richardsratevector.hh @@ -0,0 +1,146 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::RichardsRateVector + */ +#ifndef EWOMS_RICHARDS_RATE_VECTOR_HH +#define EWOMS_RICHARDS_RATE_VECTOR_HH + +#include + +#include +#include + +#include "richardsintensivequantities.hh" + +namespace Opm { + +/*! + * \ingroup RichardsModel + * + * \brief Implements a vector representing mass, molar or volumetric rates. + * + * This class is basically a Dune::FieldVector which can be set using either mass, molar + * or volumetric rates. + */ +template +class RichardsRateVector + : public Dune::FieldVector, + getPropValue()> +{ + using Scalar = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using EnergyModule = GetPropType; + using Indices = GetPropType; + + enum { contiEqIdx = Indices::contiEqIdx }; + enum { liquidCompIdx = getPropValue() }; + enum { numEq = getPropValue() }; + + using ParentType = Dune::FieldVector; + +public: + RichardsRateVector() : ParentType() + { Opm::Valgrind::SetUndefined(*this); } + + /*! + * \copydoc ImmiscibleRateVector::ImmiscibleRateVector(Scalar) + */ + RichardsRateVector(const Evaluation& value) + : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleRateVector::ImmiscibleRateVector(const + * ImmiscibleRateVector& ) + */ + RichardsRateVector(const RichardsRateVector& value) + : ParentType(value) + {} + + /*! + * \copydoc ImmiscibleRateVector::setMassRate + */ + void setMassRate(const ParentType& value) + { ParentType::operator=(value); } + + /*! + * \copydoc ImmiscibleRateVector::setMolarRate + */ + void setMolarRate(const ParentType& value) + { + // convert to mass rates + ParentType::operator[](contiEqIdx) = + value[contiEqIdx]*FluidSystem::molarMass(liquidCompIdx); + } + + /*! + * \copydoc ImmiscibleRateVector::setEnthalpyRate + */ + template + void setEnthalpyRate(const RhsEval& rate) + { EnergyModule::setEnthalpyRate(*this, rate); } + + /*! + * \copydoc ImmiscibleRateVector::setVolumetricRate + */ + template + void setVolumetricRate(const FluidState& fluidState, unsigned phaseIdx, const RhsEval& volume) + { + (*this)[contiEqIdx] = + fluidState.density(phaseIdx) + * fluidState.massFraction(phaseIdx, liquidCompIdx) + * volume; + + EnergyModule::setEnthalpyRate(*this, fluidState, phaseIdx, volume); + } + + + /*! + * \brief Assignment operator from a scalar or a function evaluation + */ + template + RichardsRateVector& operator=(const RhsEval& value) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = value; + return *this; + } + + /*! + * \brief Assignment operator from another rate vector + */ + RichardsRateVector& operator=(const RichardsRateVector& other) + { + for (unsigned i=0; i < this->size(); ++i) + (*this)[i] = other[i]; + return *this; + } +}; + +} // namespace Opm + +#endif diff --git a/opm/models/utils/alignedallocator.hh b/opm/models/utils/alignedallocator.hh new file mode 100644 index 00000000000..b57e961464e --- /dev/null +++ b/opm/models/utils/alignedallocator.hh @@ -0,0 +1,226 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \brief This is a stand-alone version of boost::alignment::aligned_allocator from Boost + * 1.58 + * + * The file has been modified to assume a C++-2011 compatible compiler on a POSIX + * operating system to remove the boost dependencies which the original version + * contained. The original copyright notice for this file is: + * +
+ (c) 2014 Glen Joseph Fernandes
+ glenjofe at gmail dot com
+
+ Distributed under the Boost Software
+ License, Version 1.0.
+ http://boost.org/LICENSE_1_0.txt
+
+*/ +#ifndef EWOMS_ALIGNED_ALLOCATOR_HH +#define EWOMS_ALIGNED_ALLOCATOR_HH + +#include +#include +#include +#include + +namespace Opm { + +namespace detail { +constexpr inline bool is_alignment(std::size_t value) noexcept +{ + return (value > 0) && ((value & (value - 1)) == 0); +} + +template +struct is_alignment_constant + : std::integral_constant 0) && ((N & (N - 1)) == 0)> +{}; + +template +struct min_size + : std::integral_constant +{ }; + +template +struct offset_object +{ + char offset; + T object; +}; + +template +struct alignment_of + : min_size) - sizeof(T)>::type +{}; + +template +struct max_align + : std::integral_constant B) ? A : B> +{}; + +template +struct max_count_of + : std::integral_constant(0) / sizeof(T)> +{}; + +using std::addressof; +} + +inline void* aligned_alloc(std::size_t alignment, + std::size_t size) noexcept +{ + assert(detail::is_alignment(alignment)); + if (alignment < sizeof(void*)) { + alignment = sizeof(void*); + } + void* p; + if (::posix_memalign(&p, alignment, size) != 0) { + p = 0; + } + return p; +} + +inline void aligned_free(void* ptr) + noexcept +{ + ::free(ptr); +} + + +template +class aligned_allocator { + static_assert(detail::is_alignment_constant::value, "Alignment must be powers of two!"); + +public: + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using void_pointer = void*; + using const_void_pointer = const void*; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = T&; + using const_reference = const T&; + +private: + using MaxAlign = detail::max_align::value>; + +public: + template + struct rebind { + using other = aligned_allocator; + }; + + aligned_allocator() + noexcept = default; + + template + aligned_allocator(const aligned_allocator&) noexcept { + } + + pointer address(reference value) const + noexcept { + return detail::addressof(value); + } + + const_pointer address(const_reference value) const + noexcept { + return detail::addressof(value); + } + + pointer allocate(size_type size, + const_void_pointer = 0) { + void* p = aligned_alloc(MaxAlign::value, + sizeof(T) * size); + if (!p && size > 0) { + throw std::bad_alloc(); + } + return static_cast(p); + } + + void deallocate(pointer ptr, size_type) { + aligned_free(ptr); + } + + constexpr size_type max_size() const + noexcept { + return detail::max_count_of::value; + } + + template + void construct(U* ptr, Args&&... args) { + void* p = ptr; + ::new(p) U(std::forward(args)...); + } + + template + void construct(U* ptr) { + void* p = ptr; + ::new(p) U(); + } + + template + void destroy(U* ptr) { + (void)ptr; + ptr->~U(); + } +}; + +template +class aligned_allocator { + static_assert(detail::is_alignment_constant::value, + "The specified alignment is not a power of two!"); + +public: + using value_type = void; + using pointer = void*; + using const_pointer = const void*; + + template + struct rebind { + using other = aligned_allocator; + }; +}; + +template +inline bool operator==(const aligned_allocator&, const aligned_allocator&) noexcept +{ + return true; +} + +template +inline bool operator!=(const aligned_allocator&, const aligned_allocator&) noexcept +{ + return false; +} +} + +#endif diff --git a/opm/models/utils/basicparameters.hh b/opm/models/utils/basicparameters.hh new file mode 100644 index 00000000000..6b9ff8565f3 --- /dev/null +++ b/opm/models/utils/basicparameters.hh @@ -0,0 +1,83 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Defines some fundamental parameters for all models. + */ +#ifndef EWOMS_BASIC_PARAMETERS_HH +#define EWOMS_BASIC_PARAMETERS_HH + +namespace Opm::Parameters { + +//! grid resolution +struct CellsX { static constexpr unsigned value = 1; }; +struct CellsY { static constexpr unsigned value = 1; }; +struct CellsZ { static constexpr unsigned value = 1; }; + +//! domain size +template +struct DomainSizeX { static constexpr Scalar value = 1.0; }; + +template +struct DomainSizeY { static constexpr Scalar value = 1.0; }; + +template +struct DomainSizeZ { static constexpr Scalar value = 1.0; }; + +//! The default value for the simulation's end time +template +struct EndTime { static constexpr Scalar value = -1e35; }; + +//! Name of the grid file +struct GridFile { static constexpr auto value = ""; }; + +//! Property which tells the Vanguard how often the grid should be refined +//! after creation. +struct GridGlobalRefinements { static constexpr unsigned value = 0; }; + +//! The default value for the simulation's initial time step size +template +struct InitialTimeStepSize { static constexpr Scalar value = -1e35; }; + +//! Set a value for the ParameterFile property +struct ParameterFile { static constexpr auto value = ""; }; + +//! By default, do not force any time steps +struct PredeterminedTimeStepsFile { static constexpr auto value = ""; }; + +/*! + * \brief Print all parameters on startup? + * + * 0 means 'no', 1 means 'yes', 2 means 'print only to logfiles'. The + * default is 2. + */ +struct PrintParameters { static constexpr int value = 2; }; + +//! The default value for the simulation's restart time +template +struct RestartTime { static constexpr Scalar value = -1e35; }; + +} // namespace Opm:Parameters + +#endif diff --git a/opm/models/utils/basicproperties.hh b/opm/models/utils/basicproperties.hh new file mode 100644 index 00000000000..9d751ba9cee --- /dev/null +++ b/opm/models/utils/basicproperties.hh @@ -0,0 +1,180 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief Defines a type tags and some fundamental properties all models. + */ +#ifndef EWOMS_BASIC_PROPERTIES_HH +#define EWOMS_BASIC_PROPERTIES_HH + +#include + +#include +#include +#include + +#if HAVE_DUNE_FEM +#include +#endif + +namespace Opm { + +template class DgfVanguard; + +} + +namespace Opm::Properties { + +/////////////////////////////////// +// Type tag definitions: +// +// NumericModel +// | +// +-> ImplicitModel +/////////////////////////////////// + +// Create new type tags +namespace TTag { + +//! Type tag for all models. +struct NumericModel {}; + +//! Type tag for all fully coupled models. +struct ImplicitModel { using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +/////////////////////////////////// +// Property names which are always available: +// +// Scalar +/////////////////////////////////// + +//! Property to specify the type of scalar values. +template +struct Scalar { using type = UndefinedProperty; }; + +//! Number of equations in the system of PDEs +template +struct NumEq { using type = UndefinedProperty; }; + +//! Property which provides a Dune::ParameterTree. +template +struct ParameterTree { using type = UndefinedProperty; }; + +//! The type of the model +template +struct Model { using type = UndefinedProperty; }; + +//! Property which defines the group that is queried for parameters by default +template +struct ModelParameterGroup { using type = UndefinedProperty; }; + +//! Property which provides a Vanguard (manages grids) +template +struct Vanguard { using type = UndefinedProperty; }; + +//! The type of the DUNE grid +template +struct Grid { using type = UndefinedProperty; }; + +template +struct GridView { using type = UndefinedProperty; }; + +#if HAVE_DUNE_FEM +template +struct GridPart { using type = UndefinedProperty; }; +#endif + +//! level of the grid view +template +struct GridViewLevel { using type = UndefinedProperty; }; + +//! Manages the simulation time +template +struct Simulator { using type = UndefinedProperty; }; + +/*! + * \brief The class which marks the border indices associated with the + * degrees of freedom on a process boundary. + * + * This is required for the algebraic overlap stuff. + */ +template +struct BorderListCreator { using type = UndefinedProperty; }; + +/////////////////////////////////// +// Values for the properties +/////////////////////////////////// + +//! Set the default type of scalar values to double +template +struct Scalar { using type = double; }; + +//! Set the ParameterTree property +template +struct ParameterTree +{ + using type = Dune::ParameterTree; + + static Dune::ParameterTree& tree() + { + static Dune::ParameterTree obj_; + return obj_; + } +}; + +//! use the global group as default for the model's parameter group +template +struct ModelParameterGroup +{ static constexpr auto value = ""; }; + +#if HAVE_DUNE_FEM +template +struct GridPart +{ + using Grid = GetPropType; + using type = Dune::Fem::AdaptiveLeafGridPart; +}; + +template +struct GridView +{ using type = typename GetPropType::GridViewType; }; +#else +//! Use the leaf grid view by default. +//! +//! Except for spatial refinement, there is rarly a reason to use +//! anything else... +template +struct GridView +{ using type = typename GetPropType::LeafGridView; }; +#endif + +template +struct Vanguard +{ using type = Opm::DgfVanguard; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/models/utils/genericguard.hh b/opm/models/utils/genericguard.hh new file mode 100644 index 00000000000..7e9e879838c --- /dev/null +++ b/opm/models/utils/genericguard.hh @@ -0,0 +1,93 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::GenericGuard + */ +#ifndef EWOMS_GENERIC_GUARD_HH +#define EWOMS_GENERIC_GUARD_HH + +namespace Opm { +/*! + * \ingroup Common + * + * \brief A simple class which makes sure that a cleanup function is called once the + * object is destroyed. + * + * This class is particularly useful in conjunction with lambdas for code that might + * throw exceptions. + */ +template +class GenericGuard +{ +public: + GenericGuard(Callback& callback) + : callback_(callback) + , isEnabled_(true) + { } + + // allow moves + GenericGuard(GenericGuard&& other) + : callback_(other.callback_) + , isEnabled_(other.isEnabled_) + { + other.isEnabled_ = false; + } + + // disable copies + GenericGuard(const GenericGuard& other) = delete; + + ~GenericGuard() + { + if (isEnabled_) + callback_(); + } + + /*! + * \brief Specify whether the guard object is "on duty" or not. + * + * If the guard object is destroyed while it is "off-duty", the cleanup callback is + * not called. At construction, guards are on duty. + */ + void setEnabled(bool value) + { isEnabled_ = value; } + + /*! + * \brief Returns whether the guard object is "on duty" or not. + */ + bool enabled() const + { return isEnabled_; } + +private: + Callback& callback_; + bool isEnabled_; +}; + +template +GenericGuard make_guard(Callback& callback) +{ return GenericGuard(callback); } + +} // namespace Opm + +#endif diff --git a/opm/models/utils/parametersystem.hh b/opm/models/utils/parametersystem.hh new file mode 100644 index 00000000000..b5366884a3b --- /dev/null +++ b/opm/models/utils/parametersystem.hh @@ -0,0 +1,1072 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file provides the infrastructure to retrieve run-time parameters + * + * Internally, runtime parameters are implemented using + * Dune::ParameterTree with the default value taken from the parameter + * definition. + */ +#ifndef OPM_PARAMETER_SYSTEM_HH +#define OPM_PARAMETER_SYSTEM_HH + +#if HAVE_QUAD +#include +#endif // HAVE_QUAD + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Opm::Parameters { + +namespace detail { + +template +struct has_name : public std::false_type {}; + +template +struct has_name().name)>> +: public std::true_type {}; + +//! get the name data member of a parameter +template +auto getParamName() +{ + if constexpr (has_name::value) { + return Parameter::name; + } else { + std::string paramName = Dune::className(); + paramName.replace(0, std::strlen("Opm::Parameters::"), ""); + const auto pos = paramName.find_first_of('<'); + if (pos != std::string::npos) { + paramName.erase(pos); + } + return paramName; + } +} + +} + +struct ParamInfo +{ + std::string paramName; + std::string paramTypeName; + std::string typeTagName; + std::string usageString; + std::string defaultValue; + bool isHidden; + + bool operator==(const ParamInfo& other) const + { + return other.paramName == paramName + && other.paramTypeName == paramTypeName + && other.typeTagName == typeTagName + && other.usageString == usageString; + } +}; + +/*! + * \ingroup Parameter + * + * \brief Retrieve a runtime parameter. + * + * The default value is specified in the parameter struct. + * + * Example: + * + * \code + * // Retrieves value UpwindWeight, default + * // is taken from the property UpwindWeight + * ::Opm::Parameters::get<::Opm::Parameters::UpwindWeight>(); + * \endcode + */ +template +auto Get(bool errorIfNotRegistered = true); + +/*! + * \ingroup Parameter + * + * \brief Set a runtime parameter. + * + * Override the default value specified. + * + * Example: + * + * \code + * // Set the value UpwindWeight + * ::Opm::Parameters::Set<::Opm::Parameters::UpwindWeight>(3.0); + * \endcode + */ +template +auto SetDefault(decltype(Param::value) new_value); + +class ParamRegFinalizerBase_ +{ +public: + virtual ~ParamRegFinalizerBase_() + {} + virtual void retrieve() = 0; +}; + +template +class ParamRegFinalizer_ : public ParamRegFinalizerBase_ +{ +public: + void retrieve() override + { + // retrieve the parameter once to make sure that its value does + // not contain a syntax error. + std::ignore = Get(/*errorIfNotRegistered=*/true); + } +}; + +struct MetaData +{ + using type = Dune::ParameterTree; + + static Dune::ParameterTree& tree() + { return *storage_().tree; } + + static std::map& mutableRegistry() + { return storage_().registry; } + + static const std::map& registry() + { return storage_().registry; } + + static std::list> ®istrationFinalizers() + { return storage_().finalizers; } + + static bool& registrationOpen() + { return storage_().registrationOpen; } + + static void clear() + { + storage_().tree = std::make_unique(); + storage_().finalizers.clear(); + storage_().registrationOpen = true; + storage_().registry.clear(); + } + +private: + // this is not pretty, but handling these attributes as static variables inside + // member functions of the ParameterMetaData property class triggers a bug in clang + // 3.5's address sanitizer which causes these variables to be initialized multiple + // times... + struct Storage_ + { + Storage_() + { + tree = std::make_unique(); + registrationOpen = true; + } + + std::unique_ptr tree; + std::map registry; + std::list> finalizers; + bool registrationOpen; + }; + + static Storage_& storage_() + { + static Storage_ obj; + return obj; + } +}; + +// function prototype declarations +void printParamUsage_(std::ostream& os, const ParamInfo& paramInfo); +void getFlattenedKeyList_(std::list& dest, + const Dune::ParameterTree& tree, + const std::string& prefix = ""); + +inline std::string breakLines_(const std::string& msg, + int indentWidth, + int maxWidth) +{ + std::string result; + int startInPos = 0; + int inPos = 0; + int lastBreakPos = 0; + int ttyPos = 0; + for (; inPos < int(msg.size()); ++ inPos, ++ ttyPos) { + if (msg[inPos] == '\n') { + result += msg.substr(startInPos, inPos - startInPos + 1); + startInPos = inPos + 1; + lastBreakPos = startInPos + 1; + + // we need to use -1 here because ttyPos is incremented after the loop body + ttyPos = -1; + continue; + } + + if (std::isspace(msg[inPos])) + lastBreakPos = inPos; + + if (ttyPos >= maxWidth) { + if (lastBreakPos > startInPos) { + result += msg.substr(startInPos, lastBreakPos - startInPos); + startInPos = lastBreakPos + 1; + lastBreakPos = startInPos; + inPos = startInPos; + } + else { + result += msg.substr(startInPos, inPos - startInPos); + startInPos = inPos; + lastBreakPos = startInPos; + inPos = startInPos; + } + + result += "\n"; + for (int i = 0; i < indentWidth; ++i) + result += " "; + ttyPos = indentWidth; + } + } + + result += msg.substr(startInPos); + + return result; +} + +inline int getTtyWidth_() +{ + int ttyWidth = 10*1000; // effectively do not break lines at all. + if (isatty(STDOUT_FILENO)) { +#if defined TIOCGWINSZ + // This is a bit too linux specific, IMO. let's do it anyway + struct winsize ttySize; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &ttySize); + ttyWidth = std::max(80, ttySize.ws_col); +#else + // default for systems that do not implement the TIOCGWINSZ ioctl + ttyWidth = 100; +#endif + } + + return ttyWidth; +} + +inline void printParamUsage_(std::ostream& os, const ParamInfo& paramInfo) +{ + std::string paramMessage, paramType, paramDescription; + + int ttyWidth = getTtyWidth_(); + + // convert the CamelCase name to a command line --parameter-name. + std::string cmdLineName = "-"; + const std::string camelCaseName = paramInfo.paramName; + for (unsigned i = 0; i < camelCaseName.size(); ++i) { + if (isupper(camelCaseName[i])) + cmdLineName += "-"; + cmdLineName += static_cast(std::tolower(camelCaseName[i])); + } + + // assemble the printed output + paramMessage = " "; + paramMessage += cmdLineName; + + // add the =VALUE_TYPE part + bool isString = false; + if (paramInfo.paramTypeName == Dune::className() + || paramInfo.paramTypeName == "const char *") + { + paramMessage += "=STRING"; + isString = true; + } + else if (paramInfo.paramTypeName == Dune::className() + || paramInfo.paramTypeName == Dune::className() + || paramInfo.paramTypeName == Dune::className() +#if HAVE_QUAD + || paramInfo.paramTypeName == Dune::className() +#endif // HAVE_QUAD + ) + paramMessage += "=SCALAR"; + else if (paramInfo.paramTypeName == Dune::className() + || paramInfo.paramTypeName == Dune::className() + || paramInfo.paramTypeName == Dune::className() + || paramInfo.paramTypeName == Dune::className()) + paramMessage += "=INTEGER"; + else if (paramInfo.paramTypeName == Dune::className()) + paramMessage += "=BOOLEAN"; + else if (paramInfo.paramTypeName.empty()) { + // the parameter is a flag. Do nothing! + } + else { + // unknown type + paramMessage += "=VALUE"; + } + + // fill up the up help string to the 50th character + paramMessage += " "; + while (paramMessage.size() < 50) + paramMessage += " "; + + + // append the parameter usage string. + paramMessage += paramInfo.usageString; + + // add the default value + if (!paramInfo.paramTypeName.empty()) { + if (paramMessage.back() != '.') + paramMessage += '.'; + paramMessage += " Default: "; + if (paramInfo.paramTypeName == "bool") { + if (paramInfo.defaultValue == "0") + paramMessage += "false"; + else + paramMessage += "true"; + } + else if (isString) { + paramMessage += "\""; + paramMessage += paramInfo.defaultValue; + paramMessage += "\""; + } + else + paramMessage += paramInfo.defaultValue; + } + + paramMessage = breakLines_(paramMessage, /*indent=*/52, ttyWidth); + paramMessage += "\n"; + + // print everything + os << paramMessage; +} + +inline void getFlattenedKeyList_(std::list& dest, + const Dune::ParameterTree& tree, + const std::string& prefix) +{ + // add the keys of the current sub-structure + for (const auto& valueKey : tree.getValueKeys()) { + std::string newKey(prefix + valueKey); + dest.push_back(newKey); + } + + // recursively add all substructure keys + for (const auto& subKey : tree.getSubKeys()) { + std::string newPrefix(prefix + subKey + '.'); + getFlattenedKeyList_(dest, tree.sub(subKey), newPrefix); + } +} + +// print the values of a list of parameters +inline void printParamList_(std::ostream& os, + const std::list& keyList, + bool printDefaults = false) +{ + const Dune::ParameterTree& tree = MetaData::tree(); + + for (const auto& key : keyList) { + const auto& paramInfo = MetaData::registry().at(key); + const std::string& defaultValue = paramInfo.defaultValue; + std::string value = defaultValue; + if (tree.hasKey(key)) + value = tree.get(key, ""); + os << key << "=\"" << value << "\""; + if (printDefaults) + os << " # default: \"" << defaultValue << "\""; + os << "\n"; + } +} + +//! \endcond + +/*! + * \ingroup Parameter + * \brief Print a usage message for all run-time parameters. + * + * \param helpPreamble The string that is printed after the error message and before the + * list of parameters. + * \param errorMsg The error message to be printed, if any + * \param os The \c std::ostream which should be used. + */ +inline void printUsage(const std::string& helpPreamble, + const std::string& errorMsg = "", + std::ostream& os = std::cerr, + const bool showAll = false) +{ + if (!errorMsg.empty()) { + os << errorMsg << "\n\n"; + } + + os << breakLines_(helpPreamble, /*indent=*/2, /*maxWidth=*/getTtyWidth_()); + os << "\n"; + + os << "Recognized options:\n"; + + if (!helpPreamble.empty()) { + ParamInfo pInfo; + pInfo.paramName = "h,--help"; + pInfo.usageString = "Print this help message and exit"; + printParamUsage_(os, pInfo); + pInfo.paramName = "-help-all"; + pInfo.usageString = "Print all parameters, including obsolete, hidden and deprecated ones."; + printParamUsage_(os, pInfo); + } + + for (const auto& param : MetaData::registry()) { + if (showAll || !param.second.isHidden) + printParamUsage_(os, param.second); + } +} + +/// \cond 0 +inline int noPositionalParameters_(std::set&, + std::string& errorMsg, + int, + const char** argv, + int paramIdx, + int) +{ + errorMsg = std::string("Illegal parameter \"")+argv[paramIdx]+"\"."; + return 0; +} + +/// \endcond + + +inline void removeLeadingSpace_(std::string& s) +{ + unsigned i; + for (i = 0; i < s.size(); ++ i) + if (!std::isspace(s[i])) + break; + s = s.substr(i); +} + +inline std::string transformKey_(const std::string& s, + bool capitalizeFirstLetter = true, + const std::string& errorPrefix = "") +{ + std::string result; + + if (s.empty()) + throw std::runtime_error(errorPrefix+"Empty parameter names are invalid"); + + if (!std::isalpha(s[0])) + throw std::runtime_error(errorPrefix+"Parameter name '" + s + "' is invalid: First character must be a letter"); + + if (capitalizeFirstLetter) + result += static_cast(std::toupper(s[0])); + else + result += s[0]; + + for (unsigned i = 1; i < s.size(); ++i) { + if (s[i] == '-') { + ++ i; + if (s.size() <= i || !std::isalpha(s[i])) + throw std::runtime_error(errorPrefix+"Invalid parameter name '" + s + "'"); + result += static_cast(std::toupper(s[i])); + } + else if (!std::isalnum(s[i])) + throw std::runtime_error(errorPrefix+"Invalid parameter name '" + s + "'"); + else + result += s[i]; + } + + return result; +} + +inline std::string parseKey_(std::string& s) +{ + unsigned i; + for (i = 0; i < s.size(); ++ i) + if (std::isspace(s[i]) || s[i] == '=') + break; + + std::string ret = s.substr(0, i); + s = s.substr(i); + return ret; +} + +// parse a quoted string +inline std::string parseQuotedValue_(std::string& s, const std::string& errorPrefix) +{ + if (s.empty() || s[0] != '"') + throw std::runtime_error(errorPrefix+"Expected quoted string"); + + std::string result; + unsigned i = 1; + for (; i < s.size(); ++i) { + // handle escape characters + if (s[i] == '\\') { + ++ i; + if (s.size() <= i) + throw std::runtime_error(errorPrefix+"Unexpected end of quoted string"); + + if (s[i] == 'n') + result += '\n'; + else if (s[i] == 'r') + result += '\r'; + else if (s[i] == 't') + result += '\t'; + else if (s[i] == '"') + result += '"'; + else if (s[i] == '\\') + result += '\\'; + else + throw std::runtime_error(errorPrefix+"Unknown escape character '\\" + s[i] + "'"); + } + else if (s[i] == '"') + break; + else + result += s[i]; + } + + s = s.substr(i+1); + return result; +} + +inline std::string parseUnquotedValue_(std::string& s, const std::string&) +{ + unsigned i; + for (i = 0; i < s.size(); ++ i) + if (std::isspace(s[i])) + break; + + std::string ret = s.substr(0, i); + s = s.substr(i); + return ret; +} + +/*! + * \ingroup Parameter + * \brief Parse the parameters provided on the command line. + * + * This function does some basic syntax checks. + * + * \param argc The number of parameters passed by the operating system to the + * main() function + * \param argv The array of strings passed by the operating system to the main() + * function + * \param helpPreamble If non-empty, the --help and -h parameters will be recognized and + * the content of the string will be printed before the list of + * command line parameters + * \return Empty string if everything worked out. Otherwise the thing that could + * not be read. + */ +template +std::string parseCommandLineOptions(int argc, + const char **argv, + const std::string& helpPreamble = "", + const PositionalArgumentCallback& posArgCallback = noPositionalParameters_) +{ + // handle the "--help" parameter + if (!helpPreamble.empty()) { + for (int i = 1; i < argc; ++i) { + if (std::string("-h") == argv[i] + || std::string("--help") == argv[i]) { + printUsage(helpPreamble, /*errorMsg=*/"", std::cout); + return "Help called"; + } + if (std::string("--help-all") == argv[i]) { + printUsage(helpPreamble, /*errorMsg=*/"", std::cout, true); + return "Help called"; + } + } + } + + std::set seenKeys; + int numPositionalParams = 0; + for (int i = 1; i < argc; ++i) { + // All non-positional command line options need to start with '-' + if (strlen(argv[i]) < 4 + || argv[i][0] != '-' + || argv[i][1] != '-') + { + std::string errorMsg; + int numHandled = posArgCallback(seenKeys, errorMsg, argc, argv, + i, numPositionalParams); + + if (numHandled < 1) { + std::ostringstream oss; + + if (!helpPreamble.empty()) + printUsage(helpPreamble, errorMsg, std::cerr); + + return errorMsg; + } + else { + ++ numPositionalParams; + i += numHandled - 1; + continue; + } + } + + std::string paramName, paramValue; + + // read a --my-opt=abc option. This gets transformed + // into the parameter "MyOpt" with the value being + // "abc" + + // There is nothing after the '-' + if (argv[i][2] == 0 || !std::isalpha(argv[i][2])) { + std::ostringstream oss; + oss << "Parameter name of argument " << i + << " ('" << argv[i] << "') " + << "is invalid because it does not start with a letter."; + + if (!helpPreamble.empty()) + printUsage(helpPreamble, oss.str(), std::cerr); + + return oss.str(); + } + + // copy everything after the "--" into a separate string + std::string s(argv[i] + 2); + + // parse argument + paramName = transformKey_(parseKey_(s), /*capitalizeFirst=*/true); + if (seenKeys.count(paramName) > 0) { + std::string msg = + std::string("Parameter '")+paramName+"' specified multiple times as a " + "command line parameter"; + + if (!helpPreamble.empty()) + printUsage(helpPreamble, msg, std::cerr); + return msg; + } + seenKeys.insert(paramName); + + if (s.empty() || s[0] != '=') { + std::string msg = + std::string("Parameter '")+paramName+"' is missing a value. " + +" Please use "+argv[i]+"=value."; + + if (!helpPreamble.empty()) + printUsage(helpPreamble, msg, std::cerr); + return msg; + } + + paramValue = s.substr(1); + + // Put the key=value pair into the parameter tree + MetaData::tree()[paramName] = paramValue; + } + return ""; +} + +/*! + * \ingroup Parameter + * \brief Read the parameters from an INI-style file. + * + * This function does some basic syntax checks. + */ +inline void parseParameterFile(const std::string& fileName, bool overwrite = true) +{ + std::set seenKeys; + std::ifstream ifs(fileName); + unsigned curLineNum = 0; + while (ifs) { + // string and file processing in c++ is quite blunt! + std::string curLine; + std::getline(ifs, curLine); + curLineNum += 1; + std::string errorPrefix = fileName+":"+std::to_string(curLineNum)+": "; + + // strip leading white space + removeLeadingSpace_(curLine); + + // ignore empty and comment lines + if (curLine.empty() || curLine[0] == '#' || curLine[0] == ';') + continue; + + // TODO (?): support for parameter groups. + + // find the "key" of the key=value pair + std::string key = parseKey_(curLine); + std::string canonicalKey = transformKey_(key, /*capitalizeFirst=*/true, errorPrefix); + + if (seenKeys.count(canonicalKey) > 0) + throw std::runtime_error(errorPrefix+"Parameter '"+canonicalKey+"' seen multiple times in the same file"); + seenKeys.insert(canonicalKey); + + // deal with the equals sign + removeLeadingSpace_(curLine); + if (curLine.empty() || curLine[0] != '=') + std::runtime_error(errorPrefix+"Syntax error, expecting 'key=value'"); + + curLine = curLine.substr(1); + removeLeadingSpace_(curLine); + + if (curLine.empty() || curLine[0] == '#' || curLine[0] == ';') + std::runtime_error(errorPrefix+"Syntax error, expecting 'key=value'"); + + // get the value + std::string value; + if (curLine[0] == '"') + value = parseQuotedValue_(curLine, errorPrefix); + else + value = parseUnquotedValue_(curLine, errorPrefix); + + // ignore trailing comments + removeLeadingSpace_(curLine); + if (!curLine.empty() && curLine[0] != '#' && curLine[0] != ';') + std::runtime_error(errorPrefix+"Syntax error, expecting 'key=value'"); + + // all went well, add the parameter to the database object + if (overwrite || !MetaData::tree().hasKey(canonicalKey)) { + MetaData::tree()[canonicalKey] = value; + } + } +} + +/*! + * \ingroup Parameter + * \brief Print values of the run-time parameters. + * + * \param os The \c std::ostream on which the message should be printed + */ +inline void printValues(std::ostream& os = std::cout) +{ + std::list runTimeAllKeyList; + std::list runTimeKeyList; + std::list unknownKeyList; + + getFlattenedKeyList_(runTimeAllKeyList, MetaData::tree()); + for (const auto& key : runTimeAllKeyList) { + if (MetaData::registry().find(key) == MetaData::registry().end()) { + // key was not registered by the program! + unknownKeyList.push_back(key); + } + else { + // the key was specified at run-time + runTimeKeyList.push_back(key); + } + } + + // loop over all registered parameters + std::list compileTimeKeyList; + for (const auto& reg : MetaData::registry()) { + // check whether the key was specified at run-time + if (MetaData::tree().hasKey(reg.first)) { + continue; + } else { + compileTimeKeyList.push_back(reg.first); + } + } + + // report the values of all registered (and unregistered) + // parameters + if (runTimeKeyList.size() > 0) { + os << "# [known parameters which were specified at run-time]\n"; + printParamList_(os, runTimeKeyList, /*printDefaults=*/true); + } + + if (compileTimeKeyList.size() > 0) { + os << "# [parameters which were specified at compile-time]\n"; + printParamList_(os, compileTimeKeyList, /*printDefaults=*/false); + } + + if (unknownKeyList.size() > 0) { + os << "# [unused run-time specified parameters]\n"; + for (const auto& unused : unknownKeyList) { + os << unused << "=\"" << MetaData::tree().get(unused, "") << "\"\n" << std::flush; + } + } +} + +/*! + * \ingroup Parameter + * \brief Print the list of unused run-time parameters. + * + * \param os The \c std::ostream on which the message should be printed + * + * \return true if something was printed + */ +inline bool printUnused(std::ostream& os = std::cout) +{ + std::list runTimeAllKeyList; + std::list unknownKeyList; + + getFlattenedKeyList_(runTimeAllKeyList, MetaData::tree()); + for (const auto& key : runTimeAllKeyList) { + if (MetaData::registry().find(key) == MetaData::registry().end()) { + // key was not registered by the program! + unknownKeyList.push_back(key); + } + } + + if (unknownKeyList.size() > 0) { + os << "# [unused run-time specified parameters]\n"; + for (const auto& unused : unknownKeyList) { + os << unused << "=\"" + << MetaData::tree().get(unused, "") << "\"\n" << std::flush; + } + return true; + } + return false; +} + +template +auto Get(bool errorIfNotRegistered) +{ + const std::string paramName = detail::getParamName(); + if (errorIfNotRegistered) { + if (MetaData::registrationOpen()) + throw std::runtime_error("Parameters can only retrieved after _all_ of them have " + "been registered."); + + if (MetaData::registry().find(paramName) == MetaData::registry().end()) { + throw std::runtime_error("Accessing parameter " + paramName + +" without prior registration is not allowed."); + } + } + + using ParamType = std::conditional_t, std::string, + std::remove_const_t>; + ParamType defaultValue = Param::value; + + const std::string& defVal = MetaData::mutableRegistry()[paramName].defaultValue; + if constexpr (std::is_same_v) { + defaultValue = defVal; + } + else if constexpr (std::is_same_v) { + defaultValue = defVal == "1"; + } +#if HAVE_QUAD + else if constexpr (std::is_same_v) { + defaultValue = std::strtold(defVal.data(), nullptr); + } +#endif +#if !HAVE_FLOATING_POINT_FROM_CHARS + else if constexpr (std::is_floating_point_v) { + defaultValue = std::strtod(defVal.c_str(), nullptr); + } +#endif // !HAVE_FLOATING_POINT_FROM_CHARS + else { + std::from_chars(defVal.data(), defVal.data() + defVal.size(), defaultValue); + } + + // prefix the parameter name by the model's GroupName. E.g. If + // the model specifies its group name to be 'Stokes', in an + // INI file this would result in something like: + // + // [Stokes] + // NewtonWriteConvergence = true + // retrieve actual parameter from the parameter tree + return MetaData::tree().template get(paramName, defaultValue); +} + +template +auto SetDefault(decltype(Param::value) new_value) +{ + const std::string paramName = detail::getParamName(); + if (MetaData::registry().find(paramName) == MetaData::registry().end()) { + throw std::runtime_error("Accessing parameter " + paramName + + " without prior registration is not allowed."); + } + std::ostringstream oss; + oss << new_value; + MetaData::mutableRegistry()[paramName].defaultValue = oss.str(); +} + +/*! + * \brief Retrieves the lists of parameters specified at runtime and their values. + * + * The two arguments besides the TypeTag are assumed to be STL containers which store + * std::pair. + */ +template +void getLists(Container& usedParams, Container& unusedParams) +{ + usedParams.clear(); + unusedParams.clear(); + + if (MetaData::registrationOpen()) { + throw std::runtime_error("Parameter lists can only retrieved after _all_ of them have " + "been registered."); + } + + // get all parameter keys + std::list allKeysList; + getFlattenedKeyList_(allKeysList, MetaData::tree()); + + for (const auto& key : allKeysList) { + if (MetaData::registry().find(key) == MetaData::registry().end()) { + // key was not registered + unusedParams.emplace_back(key, MetaData::tree()[key]); + } + else { + // key was registered + usedParams.emplace_back(key, MetaData::tree()[key]); + } + } +} + +inline void reset() +{ + MetaData::clear(); +} + +/*! + * \brief Returns true if a parameter has been specified at runtime, false + * otherwise. + * + * If the parameter in question has not been registered, this throws an exception. + */ +template +bool IsSet(bool errorIfNotRegistered = true) +{ + const std::string paramName = detail::getParamName(); + + if (errorIfNotRegistered) { + if (MetaData::registrationOpen()) { + throw std::runtime_error("Parameters can only checked after _all_ of them have " + "been registered."); + } + + if (MetaData::registry().find(paramName) == MetaData::registry().end()) + throw std::runtime_error("Accessing parameter " + std::string(paramName) + + " without prior registration is not allowed."); + } + + // check whether the parameter is in the parameter tree + return MetaData::tree().hasKey(paramName); +} + +/*! + * \ingroup Parameter + * + * \brief Register a run-time parameter. + * + * In OPM, parameters can only be used after they have been + * registered. + * + * Example: + * + * \code + * // Registers a run-time parameter "UpwindWeight" + * and the description "Relative weight of the upwind node." + * Register("Relative weight of the upwind node."); + * \endcode + */ +template +void Register(const char* usageString) +{ + const std::string paramName = detail::getParamName(); + if (!MetaData::registrationOpen()) { + throw std::logic_error("Parameter registration was already closed before " + "the parameter '" + paramName + "' was registered."); + } + + const auto defaultValue = Param::value; + using ParamType = std::conditional_t, std::string, + std::remove_const_t>; + MetaData::registrationFinalizers().push_back( + std::make_unique>()); + + ParamInfo paramInfo; + paramInfo.paramName = paramName; + paramInfo.paramTypeName = Dune::className(); + paramInfo.usageString = usageString; + std::ostringstream oss; + oss << defaultValue; + paramInfo.defaultValue = oss.str(); + paramInfo.isHidden = false; + if (MetaData::registry().find(paramName) != MetaData::registry().end()) { + // allow to register a parameter twice, but only if the + // parameter name, type and usage string are exactly the same. + if (MetaData::registry().at(paramName) == paramInfo) { + return; + } + throw std::logic_error("Parameter " + paramName + +" registered twice with non-matching characteristics."); + } + + MetaData::mutableRegistry()[paramName] = paramInfo; +} + + +/*! + * \brief Indicate that a given parameter should not be mentioned in the help message + * + * This allows to deal with unused parameters + */ +template +void Hide() +{ + const std::string paramName = detail::getParamName(); + if (!MetaData::registrationOpen()) { + throw std::logic_error("Parameter '" +paramName + "' declared as hidden" + " when parameter registration was already closed."); + } + + auto paramInfoIt = MetaData::mutableRegistry().find(paramName); + if (paramInfoIt == MetaData::mutableRegistry().end()) { + throw std::logic_error("Tried to declare unknown parameter '" + + paramName + "' hidden."); + } + + auto& paramInfo = paramInfoIt->second; + paramInfo.isHidden = true; +} + +/*! + * \brief Indicate that all parameters are registered for a given type tag. + * + * If registerParam is called after the invocation of + * \c endParamRegistration, a std::logic_error exception + * will be thrown. + */ +inline void endRegistration() +{ + if (!MetaData::registrationOpen()) { + throw std::logic_error("Parameter registration was already closed. It is only possible " + "to close it once."); + } + + MetaData::registrationOpen() = false; + + // loop over all parameters and retrieve their values to make sure + // that there is no syntax error + for (const auto& param : MetaData::registrationFinalizers()) { + param->retrieve(); + } + MetaData::registrationFinalizers().clear(); +} +//! \endcond + +} // namespace Opm::Parameters + +#endif // OPM_PARAMETER_SYSTEM_HH diff --git a/opm/models/utils/pffgridvector.hh b/opm/models/utils/pffgridvector.hh new file mode 100644 index 00000000000..a1012a366f3 --- /dev/null +++ b/opm/models/utils/pffgridvector.hh @@ -0,0 +1,129 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc PffGridVector + */ +#ifndef EWOMS_PFF_GRID_VECTOR_HH +#define EWOMS_PFF_GRID_VECTOR_HH + +#include + +#include +#include + +#include + +namespace Opm { +/*! + * \brief A random-access container which stores data attached to a grid's degrees of + * freedom in a prefetch friendly manner. + * + * This container often reduces the number of cache faults considerably, thus improving + * performance. On the flipside data cannot be written to on an individual basis and it + * requires significantly more memory than a plain array. PffVector stands for "PreFetch + * Friendly Grid Vector". + */ +template +class PffGridVector +{ + using Element = typename GridView::template Codim<0>::Entity; + + using ElementMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + +public: + PffGridVector(const GridView& gridView, const DofMapper& dofMapper) + : gridView_(gridView) + , elementMapper_(gridView_, Dune::mcmgElementLayout()) + , dofMapper_(dofMapper) + { } + + template + void update(const DistFn& distFn) + { + unsigned numElements = gridView_.size(/*codim=*/0); + unsigned numLocalDofs = computeNumLocalDofs_(); + + elemData_.resize(numElements); + data_.resize(numLocalDofs); + + // update the pointers for the element data: for this, we need to loop over the + // whole grid and update a stencil for each element + Data *curElemDataPtr = &data_[0]; + Stencil stencil(gridView_, dofMapper_); + for (const auto& elem : elements(gridView_)) { + // set the DOF data pointer for the current element + unsigned elemIdx = elementMapper_.index(elem); + elemData_[elemIdx] = curElemDataPtr; + + stencil.update(elem); + unsigned numDof = stencil.numDof(); + for (unsigned localDofIdx = 0; localDofIdx < numDof; ++ localDofIdx) + distFn(curElemDataPtr[localDofIdx], stencil, localDofIdx); + + // update the element data pointer to make it point to the beginning of the + // data for DOFs of the next element + curElemDataPtr += numDof; + } + } + + void prefetch(const Element& elem) const + { + unsigned elemIdx = elementMapper_.index(elem); + + // we use 0 as the temporal locality, because it is reasonable to assume that an + // entry will only be accessed once. + ::Opm::prefetch(elemData_[elemIdx]); + } + + const Data& get(const Element& elem, unsigned localDofIdx) const + { + unsigned elemIdx = elementMapper_.index(elem); + return elemData_[elemIdx][localDofIdx]; + } + +private: + unsigned computeNumLocalDofs_() const + { + unsigned result = 0; + + // loop over the whole grid and sum up the number of local DOFs of all Stencils + Stencil stencil(gridView_, dofMapper_); + for (const auto& elem : elements(gridView_)) { + stencil.update(elem); + result += stencil.numDof(); + } + + return result; + } + + GridView gridView_; + ElementMapper elementMapper_; + const DofMapper& dofMapper_; + std::vector data_; + std::vector elemData_; +}; + +} // namespace Opm + +#endif diff --git a/opm/models/utils/prefetch.hh b/opm/models/utils/prefetch.hh new file mode 100644 index 00000000000..5f2c775527b --- /dev/null +++ b/opm/models/utils/prefetch.hh @@ -0,0 +1,54 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc prefetch + */ +#ifndef EWOMS_PREFETCH_HH +#define EWOMS_PREFETCH_HH + +namespace Opm { +/*! + * \brief Template function which emits prefetch instructions for a range of memory + * + * This function does not change the semantics of the code, but used correctly it will + * improve performace because the number of cache misses will be reduced. + */ +template +void prefetch(const T& val, unsigned n = 1) +{ +#if __clang__ || __GNUC__ + // this value is architecture specific, but a cache line size of 64 bytes seems to be + // used by all contemporary architectures. + static const int cacheLineSize = 64; + + const char *beginPtr = reinterpret_cast(&val); + const char *endPtr = reinterpret_cast(&val + n); + for (; beginPtr < endPtr; beginPtr += cacheLineSize) + __builtin_prefetch(beginPtr, writeOnly, temporalLocality); +#endif +} + +} // namespace Opm + +#endif diff --git a/opm/models/utils/propertysystem.hh b/opm/models/utils/propertysystem.hh new file mode 100644 index 00000000000..10b78873dec --- /dev/null +++ b/opm/models/utils/propertysystem.hh @@ -0,0 +1,250 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *****************************************************************************/ +/*! + * \file + * \ingroup Properties + * \ingroup TypeTraits + * \author Timo Koch + * \brief The Opm property system, traits with inheritance + */ +#ifndef OPM_PROPERTY_SYSTEM_HH +#define OPM_PROPERTY_SYSTEM_HH + +#include + +#include +#include +#include +#include + +namespace Opm { +namespace Properties { + +//! a tag to mark properties as undefined +struct UndefinedProperty {}; + +template +struct Splices +{ + using type = std::tuple<>; +}; + +//! implementation details for template meta programming +namespace Detail { + +//! check if a property P is defined +template +constexpr auto isDefinedProperty(int) +-> decltype(std::integral_constant::value>{}) +{ return {}; } + +//! fall back if a Property is defined +template +constexpr std::true_type isDefinedProperty(...) { return {}; } + +//! check if a TypeTag inherits from other TypeTags +template +constexpr auto hasParentTypeTag(int) +-> decltype(std::declval(), std::true_type{}) +{ return {}; } + +//! fall back if a TypeTag doesn't inherit +template +constexpr std::false_type hasParentTypeTag(...) { return {}; } + +//! helper alias to concatenate multiple tuples +template +using ConCatTuples = decltype(std::tuple_cat(std::declval()...)); + +//! helper struct to get the first property that is defined in the TypeTag hierarchy +template class Property, class TTagList> +struct GetDefined; + +//! helper struct to iterate over the TypeTag hierarchy +template class Property, class TTagList, class Enable> +struct GetNextTypeTag; + +template class Property, class LastTypeTag> +struct GetNextTypeTag, std::enable_if_t(int{}), void>> +{ using type = typename GetDefined::type; }; + +template class Property, class LastTypeTag> +struct GetNextTypeTag, std::enable_if_t(int{}), void>> +{ using type = UndefinedProperty; }; + +template class Property, class FirstTypeTag, class ...Args> +struct GetNextTypeTag, std::enable_if_t(int{}), void>> +{ using type = typename GetDefined>>::type; }; + +template class Property, class FirstTypeTag, class ...Args> +struct GetNextTypeTag, std::enable_if_t(int{}), void>> +{ using type = typename GetDefined>::type; }; + +template class Property, class LastTypeTag> +struct GetDefined> +{ +// For clang, the following alias triggers compiler warnings if instantiated +// from something like `GetPropType<..., DeprecatedProperty>`, even if that is +// contained in a diagnostic pragma construct that should prevent these warnings. +// As a workaround, also add the pragmas around this line. +// See the discussion in MR 1647 for more details. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + using LastType = Property; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + using type = std::conditional_t(int{}), LastType, + typename GetNextTypeTag, void>::type>; +}; + +template class Property, class FirstTypeTag, class ...Args> +struct GetDefined> +{ +// See the comment above. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + using FirstType = Property; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + using type = std::conditional_t(int{}), FirstType, + typename GetNextTypeTag, void>::type>; +}; + + +//! helper struct to get the first property that is defined in the TypeTag hierarchy +template +struct GetDefinedSplice; + +//! helper struct to iterate over the TypeTag hierarchy +template +struct GetNextSpliceTypeTag; + +template +struct GetNextSpliceTypeTag, std::enable_if_t(int{}), void>> +{ using type = typename GetDefinedSplice::type; }; + +template +struct GetNextSpliceTypeTag, std::enable_if_t(int{}), void>> +{ using type = std::tuple<>; }; + +template +struct GetNextSpliceTypeTag, std::enable_if_t(int{}), void>> +{ using type = typename GetDefinedSplice>>::type; }; + +template +struct GetNextSpliceTypeTag, std::enable_if_t(int{}), void>> +{ using type = typename GetDefinedSplice>::type; }; + +//! check if a splice S is defined +template +constexpr auto isDefinedSplice(int) +-> decltype(std::integral_constant>::value>{}) +{ return {}; } + +//! fall back if a splice is defined +template +constexpr std::true_type isDefinedSplice(...) { return {}; } + +template +struct GetDefinedSplice> +{ + using LastSplice = Splices; + using nexttuple = typename GetNextSpliceTypeTag, + typename LastSplice::type + >, + void>::type; + + using type = std::conditional_t(int{}), + ConCatTuples, + nexttuple>; +}; + +template +struct GetDefinedSplice> +{ + using FirstSplice = Splices; + using nexttuple = typename GetNextSpliceTypeTag, + typename FirstSplice::type + >, + void>::type; + + using type = std::conditional_t(int{}), + ConCatTuples, + nexttuple>; +}; + +//! helper struct to extract get the Property specilization given a TypeTag, asserts that the property is defined +template class Property> +struct GetPropImpl +{ + using tuple = typename Detail::GetDefinedSplice>::type; + using type = typename Detail::GetDefined, tuple> + >::type; + static_assert(!std::is_same::value, "Property is undefined!"); +}; + +template class Property> +struct GetSplicePropImpl +{ + using type = typename Detail::GetDefined>::type; + static_assert(!std::is_same>::value, "Splice is undefined!"); +}; + +} // end namespace Detail +} // end namespace Property + +//! get the type of a property (equivalent to old macro GET_PROP(...)) +template class Property> +using GetProp = typename Properties::Detail::GetPropImpl::type; + +// See the comment above. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +//! get the type alias defined in the property (equivalent to old macro GET_PROP_TYPE(...)) +template class Property> +using GetPropType = typename Properties::Detail::GetPropImpl::type::type; + +template class Property> +using GetSplicePropType = typename Properties::Detail::GetSplicePropImpl::type::type; + +//! get the value data member of a property +template class Property> +constexpr auto getPropValue() { return Properties::Detail::GetPropImpl::type::value; } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // end namespace Opm + +#endif diff --git a/opm/models/utils/quadraturegeometries.hh b/opm/models/utils/quadraturegeometries.hh new file mode 100644 index 00000000000..e0e6e8d8972 --- /dev/null +++ b/opm/models/utils/quadraturegeometries.hh @@ -0,0 +1,154 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::QuadrialteralQuadratureGeometry + */ +#ifndef EWOMS_QUADRATURE_GEOMETRIES_HH +#define EWOMS_QUADRATURE_GEOMETRIES_HH + +#include +#include +#include + +namespace Opm { +/*! + * \brief Quadrature geometry for quadrilaterals. + */ +template +class QuadrialteralQuadratureGeometry +{ +public: + enum { numCorners = (1 << dim) }; + + using LocalPosition = Dune::FieldVector; + using GlobalPosition = Dune::FieldVector; + + Dune::GeometryType type() const + { return Dune::GeometryType(/*topologyId=*/(1 << dim) - 1, dim); } + + template + void setCorners(const CornerContainer& corners, unsigned nCorners) + { + unsigned cornerIdx; + for (cornerIdx = 0; cornerIdx < nCorners; ++cornerIdx) { + for (unsigned j = 0; j < dim; ++j) + corners_[cornerIdx][j] = corners[cornerIdx][j]; + } + assert(cornerIdx == nCorners); + + center_ = 0; + for (cornerIdx = 0; cornerIdx < nCorners; ++cornerIdx) + center_ += corners_[cornerIdx]; + center_ /= nCorners; + } + + /*! + * \brief Returns the center of weight of the polyhedron. + */ + const GlobalPosition& center() const + { return center_; } + + /*! + * \brief Convert a local coordinate into a global one. + */ + GlobalPosition global(const LocalPosition& localPos) const + { + GlobalPosition globalPos(0.0); + + for (unsigned cornerIdx = 0; cornerIdx < numCorners; ++cornerIdx) + globalPos.axpy(cornerWeight(localPos, cornerIdx), + corners_[cornerIdx]); + + return globalPos; + } + + /*! + * \brief Returns the Jacobian matrix of the local to global + * mapping at a given local position. + */ + void jacobian(Dune::FieldMatrix& jac, + const LocalPosition& localPos) const + { + jac = 0.0; + for (unsigned cornerIdx = 0; cornerIdx < numCorners; ++cornerIdx) { + for (unsigned k = 0; k < dim; ++k) { + Scalar dWeight_dk = (cornerIdx& (1 << k)) ? 1 : -1; + for (unsigned j = 0; j < dim; ++j) { + if (k != j) { + if (cornerIdx& (1 << j)) + dWeight_dk *= localPos[j]; + else + dWeight_dk *= 1 - localPos[j]; + ; + } + } + + jac[k].axpy(dWeight_dk, corners_[cornerIdx]); + } + } + } + + /*! + * \brief Return the determinant of the Jacobian of the mapping + * from local to global coordinates at a given local + * position. + */ + Scalar integrationElement(const LocalPosition& localPos) const + { + Dune::FieldMatrix jac; + jacobian(jac, localPos); + return jac.determinant(); + } + + /*! + * \brief Return the position of the corner with a given index + */ + const GlobalPosition& corner(unsigned cornerIdx) const + { return corners_[cornerIdx]; } + + /*! + * \brief Return the weight of an individual corner for the local + * to global mapping. + */ + Scalar cornerWeight(const LocalPosition& localPos, unsigned cornerIdx) const + { + GlobalPosition globalPos(0.0); + + // this code is based on the Q1 finite element code from + // dune-localfunctions + Scalar weight = 1.0; + for (unsigned j = 0; j < dim; ++j) + weight *= (cornerIdx& (1 << j)) ? localPos[j] : (1 - localPos[j]); + + return weight; + } + +private: + GlobalPosition corners_[numCorners]; + GlobalPosition center_; +}; + +} // namespace Opm + +#endif // EWOMS_QUADRATURE_GEOMETRY_HH diff --git a/opm/models/utils/signum.hh b/opm/models/utils/signum.hh new file mode 100644 index 00000000000..6d51cfe5d5c --- /dev/null +++ b/opm/models/utils/signum.hh @@ -0,0 +1,45 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc sgn() + */ +#ifndef EWOMS_SIGNUM_HH +#define EWOMS_SIGNUM_HH + +namespace Opm { +/*! + * \brief Template function which returns the sign of a floating point value + * + * This is a type safe and fast implementation of a sign() function for arbitrary + * floating point values. It a slightly modified variant of + * + * https://stackoverflow.com/questions/1903954/is-there-a-standard-sign-function-signum-sgn-in-c-c + */ +template +int signum(Scalar val) +{ return (0 < val) - (val < 0); } + +} // namespace Opm + +#endif diff --git a/opm/models/utils/simulator.hh b/opm/models/utils/simulator.hh new file mode 100644 index 00000000000..116da39c67b --- /dev/null +++ b/opm/models/utils/simulator.hh @@ -0,0 +1,1014 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::Simulator + */ +#ifndef EWOMS_SIMULATOR_HH +#define EWOMS_SIMULATOR_HH + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace Opm +{ +namespace detail +{ +inline auto getMPIHelperCommunication() +{ + return Dune::MPIHelper::getCommunication(); +} +} // end namespace detail +} // end namespace Opm + +#define EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(code) \ + { \ + const auto& comm = ::Opm::detail::getMPIHelperCommunication(); \ + bool exceptionThrown = false; \ + try { code; } \ + catch (const Dune::Exception& e) { \ + exceptionThrown = true; \ + std::cerr << "Process " << comm.rank() << " threw a fatal exception: " \ + << e.what() << ". Abort!" << std::endl; \ + } \ + catch (const std::exception& e) { \ + exceptionThrown = true; \ + std::cerr << "Process " << comm.rank() << " threw a fatal exception: " \ + << e.what() << ". Abort!" << std::endl; \ + } \ + catch (...) { \ + exceptionThrown = true; \ + std::cerr << "Process " << comm.rank() << " threw a fatal exception. " \ + <<" Abort!" << std::endl; \ + } \ + \ + if (comm.max(exceptionThrown)) \ + std::abort(); \ + } + +namespace Opm { + +/*! + * \ingroup Common + * + * \brief Manages the initializing and running of time dependent + * problems. + * + * This class instantiates the grid, the model and the problem to be + * simlated and runs the simulation loop. The time axis is treated as + * a sequence of "episodes" which are defined as time intervals for + * which the problem exhibits boundary conditions and source terms + * that do not depend on time. + */ +template +class Simulator +{ + using Scalar = GetPropType; + using Vanguard = GetPropType; + using GridView = GetPropType; + using Model = GetPropType; + using Problem = GetPropType; + + using MPIComm = typename Dune::MPIHelper::MPICommunicator; + using Communication = Dune::Communication; + +public: + // do not allow to copy simulators around + Simulator(const Simulator& ) = delete; + + Simulator(bool verbose = true) + :Simulator(Communication(), verbose) + { + } + + Simulator(Communication comm, bool verbose = true) + { + TimerGuard setupTimerGuard(setupTimer_); + + setupTimer_.start(); + + verbose_ = verbose && comm.rank() == 0; + + timeStepIdx_ = 0; + startTime_ = 0.0; + time_ = 0.0; + endTime_ = Parameters::Get>(); + timeStepSize_ = Parameters::Get>(); + assert(timeStepSize_ > 0); + const std::string& predetTimeStepFile = + Parameters::Get(); + if (!predetTimeStepFile.empty()) { + std::ifstream is(predetTimeStepFile); + while (!is.eof()) { + Scalar dt; + is >> dt; + forcedTimeSteps_.push_back(dt); + } + } + + episodeIdx_ = 0; + episodeStartTime_ = 0; + episodeLength_ = std::numeric_limits::max(); + + finished_ = false; + + if (verbose_) + std::cout << "Allocating the simulation vanguard\n" << std::flush; + + int exceptionThrown = 0; + std::string what; + + auto catchAction = + [&exceptionThrown, &what, comm](const std::exception& e, + bool doPrint) { + exceptionThrown = 1; + what = e.what(); + if (comm.size() > 1) { + what += " (on rank " + std::to_string(comm.rank()) + ")"; + } + if (doPrint) + std::cerr << "Rank " << comm.rank() << " threw an exception: " << e.what() << std::endl; + }; + + auto checkParallelException = + [comm](const std::string& prefix, + int exceptionThrown_, + const std::string& what_) + { + if (comm.max(exceptionThrown_)) { + auto all_what = gatherStrings(what_); + assert(!all_what.empty()); + throw std::runtime_error(prefix + all_what.front()); + } + }; + + try + { vanguard_.reset(new Vanguard(*this)); } + catch (const std::exception& e) { + catchAction(e, verbose_); + } + checkParallelException("Allocating the simulation vanguard failed: ", + exceptionThrown, what); + + if (verbose_) + std::cout << "Distributing the vanguard's data\n" << std::flush; + + try + { vanguard_->loadBalance(); } + catch (const std::exception& e) { + catchAction(e, verbose_); + } + checkParallelException("Could not distribute the vanguard data: ", + exceptionThrown, what); + + if (verbose_) + std::cout << "Allocating the model\n" << std::flush; + try { + model_.reset(new Model(*this)); + } + catch (const std::exception& e) { + catchAction(e, verbose_); + } + checkParallelException("Could not allocate model: ", + exceptionThrown, what); + + if (verbose_) + std::cout << "Allocating the problem\n" << std::flush; + + try { + problem_.reset(new Problem(*this)); + } + catch (const std::exception& e) { + catchAction(e, verbose_); + } + checkParallelException("Could not allocate the problem: ", + exceptionThrown, what); + + if (verbose_) + std::cout << "Initializing the model\n" << std::flush; + + try + { model_->finishInit(); } + catch (const std::exception& e) { + catchAction(e, verbose_); + } + checkParallelException("Could not initialize the model: ", + exceptionThrown, what); + + if (verbose_) + std::cout << "Initializing the problem\n" << std::flush; + + try + { problem_->finishInit(); } + catch (const std::exception& e) { + catchAction(e, verbose_); + } + checkParallelException("Could not initialize the problem: ", + exceptionThrown, what); + + setupTimer_.stop(); + + if (verbose_) + std::cout << "Simulator successfully set up\n" << std::flush; + } + + /*! + * \brief Registers all runtime parameters used by the simulation. + */ + static void registerParameters() + { + Parameters::Register> + ("The simulation time at which the simulation is finished [s]"); + Parameters::Register> + ("The size of the initial time step [s]"); + Parameters::Register> + ("The simulation time at which a restart should be attempted [s]"); + Parameters::Register + ("A file with a list of predetermined time step sizes (one " + "time step per line)"); + + Vanguard::registerParameters(); + Model::registerParameters(); + Problem::registerParameters(); + } + + /*! + * \brief Return a reference to the grid manager of simulation + */ + Vanguard& vanguard() + { return *vanguard_; } + + /*! + * \brief Return a reference to the grid manager of simulation + */ + const Vanguard& vanguard() const + { return *vanguard_; } + + /*! + * \brief Return the grid view for which the simulation is done + */ + const GridView& gridView() const + { return vanguard_->gridView(); } + + /*! + * \brief Return the physical model used in the simulation + */ + Model& model() + { return *model_; } + + /*! + * \brief Return the physical model used in the simulation + */ + const Model& model() const + { return *model_; } + + /*! + * \brief Return the object which specifies the pysical setup of + * the simulation + */ + Problem& problem() + { return *problem_; } + + /*! + * \brief Return the object which specifies the pysical setup of + * the simulation + */ + const Problem& problem() const + { return *problem_; } + + /*! + * \brief Set the time of the start of the simulation. + * + * \param t The time \f$\mathrm{[s]}\f$ which should be jumped to + */ + void setStartTime(Scalar t) + { startTime_ = t; } + + /*! + * \brief Return the time of the start of the simulation. + */ + Scalar startTime() const + { return startTime_; } + + /*! + * \brief Set the current simulated time, don't change the current + * time step index. + * + * \param t The time \f$\mathrm{[s]}\f$ which should be jumped to + */ + void setTime(Scalar t) + { time_ = t; } + + /*! + * \brief Set the current simulated time and the time step index. + * + * \param t The time \f$\mathrm{[s]}\f$ which should be jumped to + * \param stepIdx The new time step index + */ + void setTime(Scalar t, unsigned stepIdx) + { + time_ = t; + timeStepIdx_ = stepIdx; + } + + /*! + * \brief Return the number of seconds of simulated time which have elapsed since the + * start time. + * + * To get the time after the time integration, you have to add + * timeStepSize() to time(). + */ + Scalar time() const + { return time_; } + + /*! + * \brief Set the time of simulated seconds at which the simulation runs. + * + * \param t The time \f$\mathrm{[s]}\f$ at which the simulation is finished + */ + void setEndTime(Scalar t) + { endTime_ = t; } + + /*! + * \brief Returns the number of (simulated) seconds which the simulation + * runs. + */ + Scalar endTime() const + { return endTime_; } + + /*! + * \brief Returns a reference to the timer object which measures the time needed to + * set up and initialize the simulation + */ + const Timer& setupTimer() const + { return setupTimer_; } + + /*! + * \brief Returns a reference to the timer object which measures the time needed to + * run the simulation + */ + const Timer& executionTimer() const + { return executionTimer_; } + Timer& executionTimer() + { return executionTimer_; } + + /*! + * \brief Returns a reference to the timer object which measures the time needed for + * pre- and postprocessing of the solutions. + */ + const Timer& prePostProcessTimer() const + { return prePostProcessTimer_; } + + /*! + * \brief Returns a reference to the timer object which measures the time needed for + * linarizing the solutions. + */ + const Timer& linearizeTimer() const + { return linearizeTimer_; } + + /*! + * \brief Returns a reference to the timer object which measures the time needed by + * the solver. + */ + const Timer& solveTimer() const + { return solveTimer_; } + + /*! + * \brief Returns a reference to the timer object which measures the time needed to + * the solutions of the non-linear system of equations. + */ + const Timer& updateTimer() const + { return updateTimer_; } + + /*! + * \brief Returns a reference to the timer object which measures the time needed to + * write the visualization output + */ + const Timer& writeTimer() const + { return writeTimer_; } + + /*! + * \brief Set the current time step size to a given value. + * + * If the step size would exceed the length of the current + * episode, the timeStep() method will take care that the step + * size won't exceed the episode or the end of the simulation, + * though. + * + * \param timeStepSize The new value for the time step size \f$\mathrm{[s]}\f$ + */ + void setTimeStepSize(Scalar value) + { + timeStepSize_ = value; + } + + /*! + * \brief Set the current time step index to a given value. + * + * \param timeStepIndex The new value for the time step index + */ + void setTimeStepIndex(unsigned value) + { timeStepIdx_ = value; } + + /*! + * \brief Returns the time step length \f$\mathrm{[s]}\f$ so that we + * don't miss the beginning of the next episode or cross + * the end of the simlation. + */ + Scalar timeStepSize() const + { return timeStepSize_; } + + /*! + * \brief Returns number of time steps which have been + * executed since the beginning of the simulation. + */ + int timeStepIndex() const + { return timeStepIdx_; } + + /*! + * \brief Specify whether the simulation is finished + * + * \param yesno If true the simulation is considered finished + * before the end time is reached, else it is only + * considered finished if the end time is reached. + */ + void setFinished(bool yesno = true) + { finished_ = yesno; } + + /*! + * \brief Returns true if the simulation is finished. + * + * This is the case if either setFinished(true) has been called or + * if the end time is reached. + */ + bool finished() const + { + assert(timeStepSize_ >= 0.0); + Scalar eps = + std::max(Scalar(std::abs(this->time())), timeStepSize()) + *std::numeric_limits::epsilon()*1e3; + return finished_ || (this->time()*(1.0 + eps) >= endTime()); + } + + /*! + * \brief Returns true if the simulation is finished after the + * time level is incremented by the current time step size. + */ + bool willBeFinished() const + { + static const Scalar eps = std::numeric_limits::epsilon()*1e3; + + return finished_ || (this->time() + timeStepSize_)*(1.0 + eps) >= endTime(); + } + + /*! + * \brief Aligns the time step size to the episode boundary and to + * the end time of the simulation. + */ + Scalar maxTimeStepSize() const + { + if (finished()) + return 0.0; + + return std::min(episodeMaxTimeStepSize(), + std::max(0.0, endTime() - this->time())); + } + + /*! + * \brief Change the current episode of the simulation. + * + * \param episodeStartTime Time when the episode began \f$\mathrm{[s]}\f$ + * \param episodeLength Length of the episode \f$\mathrm{[s]}\f$ + */ + void startNextEpisode(Scalar episodeStartTime, Scalar episodeLength) + { + ++episodeIdx_; + episodeStartTime_ = episodeStartTime; + episodeLength_ = episodeLength; + } + + /*! + * \brief Start the next episode, but don't change the episode + * identifier. + * + * \param len Length of the episode \f$\mathrm{[s]}\f$, infinite if not + * specified. + */ + void startNextEpisode(Scalar len = std::numeric_limits::max()) + { + ++episodeIdx_; + episodeStartTime_ = startTime_ + time_; + episodeLength_ = len; + } + + /*! + * \brief Sets the index of the current episode. + * + * Use this method with care! + */ + void setEpisodeIndex(int episodeIdx) + { episodeIdx_ = episodeIdx; } + + /*! + * \brief Returns the index of the current episode. + * + * The first episode has the index 0. + */ + int episodeIndex() const + { return episodeIdx_; } + + /*! + * \brief Returns the absolute time when the current episode + * started \f$\mathrm{[s]}\f$. + */ + Scalar episodeStartTime() const + { return episodeStartTime_; } + + /*! + * \brief Sets the length in seconds of the current episode. + * + * Use this method with care! + */ + void setEpisodeLength(Scalar dt) + { episodeLength_ = dt; } + + /*! + * \brief Returns the length of the current episode in + * simulated time \f$\mathrm{[s]}\f$. + */ + Scalar episodeLength() const + { return episodeLength_; } + + /*! + * \brief Returns true if the current episode has just been started at the + * current time. + */ + bool episodeStarts() const + { + static const Scalar eps = std::numeric_limits::epsilon()*1e3; + + return this->time() <= (episodeStartTime_ - startTime())*(1 + eps); + } + + /*! + * \brief Returns true if the current episode is finished at the + * current time. + */ + bool episodeIsOver() const + { + static const Scalar eps = std::numeric_limits::epsilon()*1e3; + + return this->time() >= (episodeStartTime_ - startTime() + episodeLength())*(1 - eps); + } + + /*! + * \brief Returns true if the current episode will be finished + * after the current time step. + */ + bool episodeWillBeOver() const + { + static const Scalar eps = std::numeric_limits::epsilon()*1e3; + + return this->time() + timeStepSize() + >= (episodeStartTime_ - startTime() + episodeLength())*(1 - eps); + } + + /*! + * \brief Aligns the time step size to the episode boundary if the + * current time step crosses the boundary of the current episode. + */ + Scalar episodeMaxTimeStepSize() const + { + // if the current episode is over and the simulation + // wants to give it some extra time, we will return + // the time step size it suggested instead of trying + // to align it to the end of the episode. + if (episodeIsOver()) + return 0.0; + + // make sure that we don't exceed the end of the + // current episode. + return std::max(0.0, + (episodeStartTime() + episodeLength()) + - (this->time() + this->startTime())); + } + + /* + * \} + */ + + /*! + * \brief Runs the simulation using a given problem class. + * + * This method makes sure that time steps sizes are aligned to + * episode boundaries, amongst other stuff. + */ + void run() + { + // create TimerGuard objects to hedge for exceptions + TimerGuard setupTimerGuard(setupTimer_); + TimerGuard executionTimerGuard(executionTimer_); + TimerGuard prePostProcessTimerGuard(prePostProcessTimer_); + TimerGuard writeTimerGuard(writeTimer_); + + setupTimer_.start(); + Scalar restartTime = Parameters::Get>(); + if (restartTime > -1e30) { + // try to restart a previous simulation + time_ = restartTime; + + Restart res; + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(res.deserializeBegin(*this, time_)); + if (verbose_) + std::cout << "Deserialize from file '" << res.fileName() << "'\n" << std::flush; + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(this->deserialize(res)); + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->deserialize(res)); + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(model_->deserialize(res)); + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(res.deserializeEnd()); + if (verbose_) + std::cout << "Deserialization done." + << " Simulator time: " << time() << humanReadableTime(time()) + << " Time step index: " << timeStepIndex() + << " Episode index: " << episodeIndex() + << "\n" << std::flush; + } + else { + // if no restart is done, apply the initial solution + if (verbose_) + std::cout << "Applying the initial solution of the \"" << problem_->name() + << "\" problem\n" << std::flush; + + Scalar oldTimeStepSize = timeStepSize_; + int oldTimeStepIdx = timeStepIdx_; + timeStepSize_ = 0.0; + timeStepIdx_ = -1; + + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(model_->applyInitialSolution()); + + // write initial condition + if (problem_->shouldWriteOutput()) + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->writeOutput()); + + timeStepSize_ = oldTimeStepSize; + timeStepIdx_ = oldTimeStepIdx; + } + setupTimer_.stop(); + + executionTimer_.start(); + bool episodeBegins = episodeIsOver() || (timeStepIdx_ == 0); + // do the time steps + while (!finished()) { + prePostProcessTimer_.start(); + if (episodeBegins) { + // notify the problem that a new episode has just been + // started. + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->beginEpisode()); + + if (finished()) { + // the problem can chose to terminate the simulation in + // beginEpisode(), so we have handle this case. + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->endEpisode()); + prePostProcessTimer_.stop(); + + break; + } + } + episodeBegins = false; + + if (verbose_) { + std::cout << "Begin time step " << timeStepIndex() + 1 << ". " + << "Start time: " << this->time() << " seconds" << humanReadableTime(this->time()) + << ", step size: " << timeStepSize() << " seconds" << humanReadableTime(timeStepSize()) + << "\n"; + } + + // pre-process the current solution + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->beginTimeStep()); + + if (finished()) { + // the problem can chose to terminate the simulation in + // beginTimeStep(), so we have handle this case. + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->endTimeStep()); + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->endEpisode()); + prePostProcessTimer_.stop(); + + break; + } + prePostProcessTimer_.stop(); + + try { + // execute the time integration scheme + problem_->timeIntegration(); + } + catch (...) { + // exceptions in the time integration might be recoverable. clean up in + // case they are + const auto& model = problem_->model(); + prePostProcessTimer_ += model.prePostProcessTimer(); + linearizeTimer_ += model.linearizeTimer(); + solveTimer_ += model.solveTimer(); + updateTimer_ += model.updateTimer(); + + throw; + } + + const auto& model = problem_->model(); + prePostProcessTimer_ += model.prePostProcessTimer(); + linearizeTimer_ += model.linearizeTimer(); + solveTimer_ += model.solveTimer(); + updateTimer_ += model.updateTimer(); + + // post-process the current solution + prePostProcessTimer_.start(); + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->endTimeStep()); + prePostProcessTimer_.stop(); + + // write the result to disk + writeTimer_.start(); + if (problem_->shouldWriteOutput()) + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->writeOutput()); + writeTimer_.stop(); + + // do the next time integration + Scalar oldDt = timeStepSize(); + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->advanceTimeLevel()); + + if (verbose_) { + std::cout << "Time step " << timeStepIndex() + 1 << " done. " + << "CPU time: " << executionTimer_.realTimeElapsed() << " seconds" << humanReadableTime(executionTimer_.realTimeElapsed()) + << ", end time: " << this->time() + oldDt << " seconds" << humanReadableTime(this->time() + oldDt) + << ", step size: " << oldDt << " seconds" << humanReadableTime(oldDt) + << "\n" << std::flush; + } + + // advance the simulated time by the current time step size + time_ += oldDt; + ++timeStepIdx_; + + prePostProcessTimer_.start(); + // notify the problem if an episode is finished + if (episodeIsOver()) { + // Notify the problem about the end of the current episode... + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->endEpisode()); + episodeBegins = true; + } + else { + Scalar dt; + if (timeStepIdx_ < static_cast(forcedTimeSteps_.size())) + // use the next time step size from the input file + dt = forcedTimeSteps_[timeStepIdx_]; + else + // ask the problem to provide the next time step size + dt = std::min(maxTimeStepSize(), problem_->nextTimeStepSize()); + assert(finished() || dt > 0); + setTimeStepSize(dt); + } + prePostProcessTimer_.stop(); + + // write restart file if mandated by the problem + writeTimer_.start(); + if (problem_->shouldWriteRestartFile()) + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(serialize()); + writeTimer_.stop(); + } + executionTimer_.stop(); + + EWOMS_CATCH_PARALLEL_EXCEPTIONS_FATAL(problem_->finalize()); + } + + /*! + * \brief Given a time step size in seconds, return it in a format which is more + * easily parsable by humans. + * + * e.g. 874000.0 will become "10.12 days" + */ + static std::string humanReadableTime(Scalar timeInSeconds, bool isAmendment=true) + { + std::ostringstream oss; + oss << std::setprecision(4); + if (isAmendment) + oss << " ("; + if (timeInSeconds >= 365.25*24*60*60) { + int years = static_cast(timeInSeconds/(365.25*24*60*60)); + int days = static_cast((timeInSeconds - years*(365.25*24*60*60))/(24*60*60)); + + double accuracy = 1e-2; + double hours = + std::round(1.0/accuracy* + (timeInSeconds + - years*(365.25*24*60*60) + - days*(24*60*60))/(60*60)) + *accuracy; + + oss << years << " years, " << days << " days, " << hours << " hours"; + } + else if (timeInSeconds >= 24.0*60*60) { + int days = static_cast(timeInSeconds/(24*60*60)); + int hours = static_cast((timeInSeconds - days*(24*60*60))/(60*60)); + + double accuracy = 1e-2; + double minutes = + std::round(1.0/accuracy* + (timeInSeconds + - days*(24*60*60) + - hours*(60*60))/60) + *accuracy; + + oss << days << " days, " << hours << " hours, " << minutes << " minutes"; + } + else if (timeInSeconds >= 60.0*60) { + int hours = static_cast(timeInSeconds/(60*60)); + int minutes = static_cast((timeInSeconds - hours*(60*60))/60); + + double accuracy = 1e-2; + double seconds = + std::round(1.0/accuracy* + (timeInSeconds + - hours*(60*60) + - minutes*60)) + *accuracy; + + oss << hours << " hours, " << minutes << " minutes, " << seconds << " seconds"; + } + else if (timeInSeconds >= 60.0) { + int minutes = static_cast(timeInSeconds/60); + + double accuracy = 1e-3; + double seconds = + std::round(1.0/accuracy* + (timeInSeconds + - minutes*60)) + *accuracy; + + oss << minutes << " minutes, " << seconds << " seconds"; + } + else if (!isAmendment) + oss << timeInSeconds << " seconds"; + else + return ""; + if (isAmendment) + oss << ")"; + + return oss.str(); + } + + /*! + * \name Saving/restoring the simulation state + * \{ + */ + + /*! + * \brief This method writes the complete state of the simulation + * to the harddisk. + * + * The file will start with the prefix returned by the name() + * method, has the current time of the simulation clock in it's + * name and uses the extension .ers. (Ewoms ReStart + * file.) See Opm::Restart for details. + */ + void serialize() + { + using Restarter = Restart; + Restarter res; + res.serializeBegin(*this); + if (gridView().comm().rank() == 0) + std::cout << "Serialize to file '" << res.fileName() << "'" + << ", next time step size: " << timeStepSize() + << "\n" << std::flush; + + this->serialize(res); + problem_->serialize(res); + model_->serialize(res); + res.serializeEnd(); + } + + /*! + * \brief Write the time manager's state to a restart file. + * + * \tparam Restarter The type of the object which takes care to serialize + * data + * \param restarter The serializer object + */ + template + void serialize(Restarter& restarter) + { + restarter.serializeSectionBegin("Simulator"); + restarter.serializeStream() + << episodeIdx_ << " " + << episodeStartTime_ << " " + << episodeLength_ << " " + << startTime_ << " " + << time_ << " " + << timeStepIdx_ << " "; + restarter.serializeSectionEnd(); + } + + /*! + * \brief Read the time manager's state from a restart file. + * + * \tparam Restarter The type of the object which takes care to deserialize + * data + * \param restarter The deserializer object + */ + template + void deserialize(Restarter& restarter) + { + restarter.deserializeSectionBegin("Simulator"); + restarter.deserializeStream() + >> episodeIdx_ + >> episodeStartTime_ + >> episodeLength_ + >> startTime_ + >> time_ + >> timeStepIdx_; + restarter.deserializeSectionEnd(); + } + + template + void serializeOp(Serializer& serializer) + { + serializer(*vanguard_); + serializer(*model_); + serializer(*problem_); + serializer(episodeIdx_); + serializer(episodeStartTime_); + serializer(episodeLength_); + serializer(startTime_); + serializer(time_); + serializer(timeStepIdx_); + } + +private: + std::unique_ptr vanguard_; + std::unique_ptr model_; + std::unique_ptr problem_; + + int episodeIdx_; + Scalar episodeStartTime_; + Scalar episodeLength_; + + Timer setupTimer_; + Timer executionTimer_; + Timer prePostProcessTimer_; + Timer linearizeTimer_; + Timer solveTimer_; + Timer updateTimer_; + Timer writeTimer_; + + std::vector forcedTimeSteps_; + Scalar startTime_; + Scalar time_; + Scalar endTime_; + + Scalar timeStepSize_; + int timeStepIdx_; + + bool finished_; + bool verbose_; +}; + +namespace Properties { +template +struct Simulator { using type = ::Opm::Simulator; }; +} + +} // namespace Opm + +#endif diff --git a/opm/models/utils/start.hh b/opm/models/utils/start.hh new file mode 100644 index 00000000000..62604c471a9 --- /dev/null +++ b/opm/models/utils/start.hh @@ -0,0 +1,414 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \brief Provides convenience routines to bring up the simulation at runtime. + */ +#ifndef EWOMS_START_HH +#define EWOMS_START_HH + +#include +// the following header is not required here, but it must be included before +// dune/common/densematrix.hh because of some c++ ideosyncrasies +#include + +#include "parametersystem.hh" + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#if HAVE_DUNE_FEM +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if HAVE_MPI +#include +#endif + +//! \cond SKIP_THIS + +namespace Opm { +/*! + * \brief Announce all runtime parameters to the registry but do not specify them yet. + */ +template +static inline void registerAllParameters_(bool finalizeRegistration = true) +{ + using Simulator = GetPropType; + using ThreadManager = GetPropType; + + Parameters::Register + ("An .ini file which contains a set of run-time parameters"); + Parameters::Register + ("Print the values of the run-time parameters at the " + "start of the simulation"); + + ThreadManager::registerParameters(); + Simulator::registerParameters(); + + if (finalizeRegistration) { + Parameters::endRegistration(); + } +} + +/*! + * \brief Register all runtime parameters, parse the command line + * arguments and the parameter file. + * + * \param argc The number of command line arguments + * \param argv Array with the command line argument strings + * \return A negative value if --help or --print-properties was provided, + * a positive value for errors or 0 for success. + */ +template +static inline int setupParameters_(int argc, + const char **argv, + bool registerParams=true, + bool allowUnused=false, + bool handleHelp = true) +{ + using Problem = GetPropType; + + // first, get the MPI rank of the current process + int myRank = 0; + + //////////////////////////////////////////////////////////// + // Register all parameters + //////////////////////////////////////////////////////////// + if (registerParams) + registerAllParameters_(); + + //////////////////////////////////////////////////////////// + // set the parameter values + //////////////////////////////////////////////////////////// + + // fill the parameter tree with the options from the command line + const auto& positionalParamCallback = Problem::handlePositionalParameter; + std::string helpPreamble = ""; // print help if non-empty! + if (myRank == 0 && handleHelp) + helpPreamble = Problem::helpPreamble(argc, argv); + std::string s = + Parameters::parseCommandLineOptions(argc, + argv, + helpPreamble, + positionalParamCallback); + if (!s.empty()) + { + int status = 1; + if (s == "Help called") // only on master process + status = -1; // Use negative values to indicate --help argument +#if HAVE_MPI + // Force -1 if the master process has that. + int globalStatus; + MPI_Allreduce(&status, &globalStatus, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD); + return globalStatus; +#endif + return status; + } + + const std::string paramFileName = Parameters::Get(false); + if (!paramFileName.empty()) { + //////////////////////////////////////////////////////////// + // add the parameters specified using an .ini file + //////////////////////////////////////////////////////////// + + // check whether the parameter file is readable. + std::ifstream tmp; + tmp.open(paramFileName.c_str()); + if (!tmp.is_open()) { + std::ostringstream oss; + if (myRank == 0) { + oss << "Parameter file \"" << paramFileName + << "\" does not exist or is not readable."; + Parameters::printUsage(argv[0], oss.str()); + } + return /*status=*/1; + } + + // read the parameter file. + Parameters::parseParameterFile(paramFileName, /*overwrite=*/false); + } + + // make sure that no unknown parameters are encountered + using KeyValuePair = std::pair; + using ParamList = std::list; + + ParamList usedParams; + ParamList unusedParams; + + Parameters::getLists(usedParams, unusedParams); + if (!allowUnused && !unusedParams.empty()) { + if (myRank == 0) { + if (unusedParams.size() == 1) + std::cerr << "The following explicitly specified parameter is unknown:\n"; + else + std::cerr << "The following " << unusedParams.size() + << " explicitly specified parameters are unknown:\n"; + + std::cerr << "\n"; + for (const auto& keyValue : unusedParams) + std::cerr << " " << keyValue.first << "=\"" << keyValue.second << "\"\n"; + std::cerr << "\n"; + + std::cerr << "Use\n" + << "\n" + << " " << argv[0] << " --help\n" + << "\n" + <<"to obtain the list of recognized command line parameters.\n\n"; + } + return /*status=*/1; + } + + return /*status=*/0; +} + +/*! + * \brief Resets the current TTY to a usable state if the program was aborted. + * + * This is intended to be called as part of a generic exception handler + */ +static inline void resetTerminal_() +{ + // make sure stderr and stderr do not contain any unwritten data and make sure that + // the TTY does not see any unfinished ANSI escape sequence. + std::cerr << " \r\n"; + std::cerr.flush(); + std::cout << " \r\n"; + std::cout.flush(); + + // it seems like some terminals sometimes takes their time to react, so let's + // accommodate them. + usleep(/*usec=*/500*1000); + + // this requires the 'stty' command to be available in the command search path. on + // most linux systems, is the case. (but even if the system() function fails, the + // worst thing which can happen is that the TTY stays potentially choked up...) + if (system("stty sane") != 0) + std::cout << "Executing the 'stty' command failed." + << " Terminal might be left in an undefined state!\n"; +} + +/*! + * \brief Resets the current TTY to a usable state if the program was interrupted by + * SIGABRT or SIGINT. + */ +static inline void resetTerminal_(int signum) +{ + // first thing to do when a nuke hits: restore the default signal handler + signal(signum, SIG_DFL); + +#if HAVE_MPI + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + // re-raise the signal + raise(signum); + + return; + } +#endif + + if (isatty(fileno(stdout)) && isatty(fileno(stdin))) { + std::cout << "\n\nReceived signal " << signum + << " (\"" << strsignal(signum) << "\")." + << " Trying to reset the terminal.\n"; + + resetTerminal_(); + } + + // after we did our best to clean the pedestrian way, re-raise the signal + raise(signum); +} +//! \endcond + +/*! + * \ingroup Common + * + * \brief Provides a main function which reads in parameters from the + * command line and a parameter file and runs the simulation + * + * \tparam TypeTag The type tag of the problem which needs to be solved + * + * \param argc The number of command line arguments + * \param argv The array of the command line arguments + */ +template +static inline int start(int argc, char **argv, bool registerParams=true) +{ + using Scalar = GetPropType; + using Simulator = GetPropType; + using Problem = GetPropType; + using ThreadManager = GetPropType; + + // set the signal handlers to reset the TTY to a well defined state on unexpected + // program aborts + if (isatty(STDIN_FILENO)) { + signal(SIGINT, resetTerminal_); + signal(SIGHUP, resetTerminal_); + signal(SIGABRT, resetTerminal_); + signal(SIGFPE, resetTerminal_); + signal(SIGSEGV, resetTerminal_); + signal(SIGPIPE, resetTerminal_); + signal(SIGTERM, resetTerminal_); + } + + resetLocale(); + + int myRank = 0; + try + { + int paramStatus = setupParameters_(argc, const_cast(argv), registerParams); + if (paramStatus == 1) + return 1; + if (paramStatus == 2) + return 0; + + ThreadManager::init(); + + // initialize MPI, finalize is done automatically on exit +#if HAVE_DUNE_FEM + Dune::Fem::MPIManager::initialize(argc, argv); + myRank = Dune::Fem::MPIManager::rank(); +#else + myRank = Dune::MPIHelper::instance(argc, argv).rank(); +#endif + + // read the initial time step and the end time + Scalar endTime = Parameters::Get>(); + if (endTime < -1e50) { + if (myRank == 0) + Parameters::printUsage(argv[0], + "Mandatory parameter '--end-time' not specified!"); + return 1; + } + + Scalar initialTimeStepSize = Parameters::Get>(); + if (initialTimeStepSize < -1e50) { + if (myRank == 0) + Parameters::printUsage(argv[0], + "Mandatory parameter '--initial-time-step-size' " + "not specified!"); + return 1; + } + + if (myRank == 0) { +#ifdef EWOMS_VERSION + std::string versionString = EWOMS_VERSION; +#else + std::string versionString = ""; +#endif + const std::string briefDescription = Problem::briefDescription(); + if (!briefDescription.empty()) { + std::string tmp = Parameters::breakLines_(briefDescription, + /*indentWidth=*/0, + Parameters::getTtyWidth_()); + std::cout << tmp << std::endl << std::endl; + } + else + std::cout << "opm models " << versionString + << " will now start the simulation. " << std::endl; + } + + // print the parameters if requested + int printParams = Parameters::Get(); + if (myRank == 0) { + std::string endParametersSeparator("# [end of parameters]\n"); + if (printParams) { + bool printSeparator = false; + if (printParams == 1 || !isatty(fileno(stdout))) { + Parameters::printValues(); + printSeparator = true; + } + else + // always print the list of specified but unused parameters + printSeparator = + printSeparator || + Parameters::printUnused(); + if (printSeparator) + std::cout << endParametersSeparator; + } + else + // always print the list of specified but unused parameters + if (Parameters::printUnused()) + std::cout << endParametersSeparator; + } + + // instantiate and run the concrete problem. make sure to + // deallocate the problem and before the time manager and the + // grid + Simulator simulator; + simulator.run(); + + if (myRank == 0) { + std::cout << "Simulation completed" << std::endl; + } + return 0; + } + catch (std::exception& e) + { + if (myRank == 0) { + std::cout << e.what() << ". Abort!\n" << std::flush; + + std::cout << "Trying to reset TTY.\n"; + resetTerminal_(); + } + + return 1; + } + catch (...) + { + if (myRank == 0) { + std::cout << "Unknown exception thrown!\n" << std::flush; + + std::cout << "Trying to reset TTY.\n"; + resetTerminal_(); + } + + return 3; + } +} + +} // namespace Opm + +#endif diff --git a/opm/models/utils/timer.hh b/opm/models/utils/timer.hh new file mode 100644 index 00000000000..855c88dd834 --- /dev/null +++ b/opm/models/utils/timer.hh @@ -0,0 +1,217 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::Timer + */ +#ifndef EWOMS_TIMER_HH +#define EWOMS_TIMER_HH + +#include + +#if HAVE_MPI +#include +#endif + +namespace Opm { +/*! + * \ingroup Common + * + * \brief Provides an encapsulation to measure the system time + * + * This means the wall clock time used by the simulation, the CPU time + * used by all threads of a single process and the CPU time used by + * the overall simulation. (i.e., the time used by all threads of all + * involved processes.) + */ +class Timer +{ + struct TimeData + { + std::chrono::high_resolution_clock::time_point realtimeData; + std::clock_t cputimeData; + }; +public: + Timer() + { halt(); } + + /*! + * \brief Start counting the time resources used by the simulation. + */ + void start() + { + isStopped_ = false; + measure_(startTime_); + } + + /*! + * \brief Stop counting the time resources. + * + * Returns the wall clock time the timer was active. + */ + double stop() + { + if (!isStopped_) { + TimeData stopTime; + + measure_(stopTime); + + const auto& t1 = startTime_.realtimeData; + const auto& t2 = stopTime.realtimeData; + std::chrono::duration dt = + std::chrono::duration_cast >(t2 - t1); + + realTimeElapsed_ += dt.count(); + cpuTimeElapsed_ += + static_cast(stopTime.cputimeData + - startTime_.cputimeData)/CLOCKS_PER_SEC; + } + + isStopped_ = true; + + return realTimeElapsed_; + } + + /*! + * \brief Stop the measurement reset all timing values + */ + void halt() + { + isStopped_ = true; + cpuTimeElapsed_ = 0.0; + realTimeElapsed_ = 0.0; + } + + /*! + * \brief Make the current point in time t=0 but do not change the status of the timer. + */ + void reset() + { + cpuTimeElapsed_ = 0.0; + realTimeElapsed_ = 0.0; + + measure_(startTime_); + } + + /*! + * \brief Return the real time [s] elapsed during the periods the timer was active + * since the last reset. + */ + double realTimeElapsed() const + { + if (isStopped_) + return realTimeElapsed_; + + TimeData stopTime; + + measure_(stopTime); + + const auto& t1 = startTime_.realtimeData; + const auto& t2 = stopTime.realtimeData; + std::chrono::duration dt = + std::chrono::duration_cast >(t2 - t1); + + return realTimeElapsed_ + dt.count(); + } + + /*! + * \brief This is an alias for realTimeElapsed() + * + * Its main purpose is to make the API of the class a superset of Dune::Timer + */ + double elapsed() const + { return realTimeElapsed(); } + + /*! + * \brief Return the CPU time [s] used by all threads of the local process for the + * periods the timer was active + */ + double cpuTimeElapsed() const + { + if (isStopped_) + return cpuTimeElapsed_; + + TimeData stopTime; + + measure_(stopTime); + + const auto& t1 = startTime_.cputimeData; + const auto& t2 = stopTime.cputimeData; + + return cpuTimeElapsed_ + static_cast(t2 - t1)/CLOCKS_PER_SEC; + } + + /*! + * \brief Return the CPU time [s] used by all threads of the all processes of program + * + * The value returned only differs from cpuTimeElapsed() if MPI is used. + */ + double globalCpuTimeElapsed() const + { + double val = cpuTimeElapsed(); + double globalVal = val; + +#if HAVE_MPI + MPI_Reduce(&val, + &globalVal, + /*count=*/1, + MPI_DOUBLE, + MPI_SUM, + /*rootRank=*/0, + MPI_COMM_WORLD); +#endif + + return globalVal; + } + + /*! + * \brief Adds the time of another timer to the current one + */ + Timer& operator+=(const Timer& other) + { + realTimeElapsed_ += other.realTimeElapsed(); + cpuTimeElapsed_ += other.cpuTimeElapsed(); + + return *this; + } + +private: + // measure the current time and put it into the object passed via + // the argument. + static void measure_(TimeData& timeData) + { + // Note: On Linux -- or rather fully POSIX compliant systems -- using + // clock_gettime() would be more accurate for the CPU time. + timeData.realtimeData = std::chrono::high_resolution_clock::now(); + timeData.cputimeData = std::clock(); + } + + bool isStopped_; + double cpuTimeElapsed_; + double realTimeElapsed_; + TimeData startTime_; +}; +} // namespace Opm + +#endif diff --git a/opm/models/utils/timerguard.hh b/opm/models/utils/timerguard.hh new file mode 100644 index 00000000000..9b08bbc1e62 --- /dev/null +++ b/opm/models/utils/timerguard.hh @@ -0,0 +1,58 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::TimerGuard + */ +#ifndef EWOMS_TIMER_GUARD_HH +#define EWOMS_TIMER_GUARD_HH + +#include "timer.hh" + +namespace Opm { +/*! + * \ingroup Common + * + * \brief A simple class which makes sure that a timer gets stopped if an exception is + * thrown. + */ +class TimerGuard +{ +public: + TimerGuard(Timer& timer) + : timer_(timer) + { } + + ~TimerGuard() + { + timer_.stop(); + } + +private: + Timer& timer_; +}; + +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/bicgstabsolver.hh b/opm/simulators/linalg/bicgstabsolver.hh new file mode 100644 index 00000000000..445e7faef04 --- /dev/null +++ b/opm/simulators/linalg/bicgstabsolver.hh @@ -0,0 +1,356 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::BiCGStabSolver + */ +#ifndef EWOMS_BICG_STAB_SOLVER_HH +#define EWOMS_BICG_STAB_SOLVER_HH + +#include "convergencecriterion.hh" +#include "residreductioncriterion.hh" +#include "linearsolverreport.hh" + +#include +#include + +#include + +#include + +namespace Opm { +namespace Linear { +/*! + * \brief Implements a preconditioned stabilized BiCG linear solver. + * + * This solves a linear system of equations Ax = b, where the matrix A is sparse and may + * be unsymmetric. + * + * See https://en.wikipedia.org/wiki/Biconjugate_gradient_stabilized_method, (article + * date: December 19, 2016) + */ +template +class BiCGStabSolver +{ + using ConvergenceCriterion = Opm::Linear::ConvergenceCriterion; + using Scalar = typename LinearOperator::field_type; + +public: + BiCGStabSolver(Preconditioner& preconditioner, + ConvergenceCriterion& convergenceCriterion, + Dune::ScalarProduct& scalarProduct) + : preconditioner_(preconditioner) + , convergenceCriterion_(convergenceCriterion) + , scalarProduct_(scalarProduct) + { + A_ = nullptr; + b_ = nullptr; + + maxIterations_ = 1000; + } + + /*! + * \brief Set the maximum number of iterations before we give up without achieving + * convergence. + */ + void setMaxIterations(unsigned value) + { maxIterations_ = value; } + + /*! + * \brief Return the maximum number of iterations before we give up without achieving + * convergence. + */ + unsigned maxIterations() const + { return maxIterations_; } + + /*! + * \brief Set the verbosity level of the linear solver + * + * The levels correspont to those used by the dune-istl solvers: + * + * - 0: no output + * - 1: summary output at the end of the solution proceedure (if no exception was + * thrown) + * - 2: detailed output after each iteration + */ + void setVerbosity(unsigned value) + { verbosity_ = value; } + + /*! + * \brief Return the verbosity level of the linear solver. + */ + unsigned verbosity() const + { return verbosity_; } + + /*! + * \brief Set the matrix "A" of the linear system. + */ + void setLinearOperator(const LinearOperator* A) + { A_ = A; } + + /*! + * \brief Set the right hand side "b" of the linear system. + */ + void setRhs(const Vector* b) + { b_ = b; } + + /*! + * \brief Run the stabilized BiCG solver and store the result into the "x" vector. + */ + bool apply(Vector& x) + { + // epsilon used for detecting breakdowns + const Scalar breakdownEps = std::numeric_limits::min() * Scalar(1e10); + + // start the stop watch for the solution proceedure, but make sure that it is + // turned off regardless of how we leave the stadium. (i.e., that the timer gets + // stopped in case exceptions are thrown as well as if the method returns + // regularly.) + report_.reset(); + TimerGuard reportTimerGuard(report_.timer()); + report_.timer().start(); + + // preconditioned stabilized biconjugate gradient method + // + // See https://en.wikipedia.org/wiki/Biconjugate_gradient_stabilized_method, + // (article date: December 19, 2016) + + // set the initial solution to the zero vector + x = 0.0; + + // prepare the preconditioner. to allow some optimizations, we assume that the + // preconditioner does not change the initial solution x if the initial solution + // is a zero vector. + Vector r = *b_; + preconditioner_.pre(x, r); + +#ifndef NDEBUG + // ensure that the preconditioner does not change the initial solution. since + // this is a debugging check, we don't care if it does not work properly in + // parallel. (because this goes wrong, it should be considered to be a bug in the + // code anyway.) + for (unsigned i = 0; i < x.size(); ++i) { + const auto& u = x[i]; + if (u*u != 0.0) + throw std::logic_error("The preconditioner is assumed not to modify the initial solution!"); + } +#endif // NDEBUG + + convergenceCriterion_.setInitial(x, r); + if (convergenceCriterion_.converged()) { + report_.setConverged(true); + return report_.converged(); + } + + if (verbosity_ > 0) { + std::cout << "-------- BiCGStabSolver --------" << std::endl; + convergenceCriterion_.printInitial(); + } + + // r0 = b - Ax (i.e., r -= A*x_0 = b, because x_0 == 0) + //A_->applyscaleadd(/*alpha=*/-1.0, x, r); + + // r0hat = r0 + const Vector& r0hat = *b_; + + // rho0 = alpha = omega0 = 1 + Scalar rho = 1.0; + Scalar alpha = 1.0; + Scalar omega = 1.0; + + // v_0 = p_0 = 0; + Vector v(r); + v = 0.0; + Vector p(v); + + // create all the temporary vectors which we need. Be aware that some of them + // actually point to the same object because they are not needed at the same time! + Vector y(x); + Vector& h(x); + Vector& s(r); + Vector z(x); + Vector& t(y); + unsigned n = x.size(); + + for (; report_.iterations() < maxIterations_; report_.increment()) { + // rho_i = (r0hat,r_(i-1)) + Scalar rho_i = scalarProduct_.dot(r0hat, r); + + // beta = (rho_i/rho_(i-1))*(alpha/omega_(i-1)) + if (std::abs(rho) <= breakdownEps || std::abs(omega) <= breakdownEps) + throw NumericalProblem("Breakdown of the BiCGStab solver (division by zero)"); + Scalar beta = (rho_i/rho)*(alpha/omega); + + // make rho correspond to the current iteration (i.e., forget rho_(i-1)) + rho = rho_i; + + // this loop conflates the following operations: + // + // p_i = r_(i-1) + beta*(p_(i-1) - omega_(i-1)*v_(i-1)) + // y = p + for (unsigned i = 0; i < n; ++i) { + // p_i = r_(i-1) + beta*(p_(i-1) - omega_(i-1)*v_(i-1)) + auto tmp = v[i]; + tmp *= omega; + tmp -= p[i]; + tmp *= -beta; + p[i] = r[i]; + p[i] += tmp; + + // y = p; not required because the precontioner overwrites y anyway... + // y[i] = p[i]; + } + + // y = K^-1 * p_i + preconditioner_.apply(y, p); + + // v_i = A*y + A_->apply(y, v); + + // alpha = rho_i/(r0hat,v_i) + Scalar denom = scalarProduct_.dot(r0hat, v); + if (std::abs(denom) <= breakdownEps) + throw NumericalProblem("Breakdown of the BiCGStab solver (division by zero)"); + alpha = rho_i/denom; + if (std::abs(alpha) <= breakdownEps) + throw NumericalProblem("Breakdown of the BiCGStab solver (stagnation detected)"); + + // h = x_(i-1) + alpha*y + // s = r_(i-1) - alpha*v_i + for (unsigned i = 0; i < n; ++i) { + auto tmp = y[i]; + tmp *= alpha; + tmp += x[i]; + h[i] = tmp; + + //s[i] = r[i]; // not necessary because r and s are the same object + tmp = v[i]; + tmp *= alpha; + s[i] -= tmp; + } + + // do convergence check and print terminal output + convergenceCriterion_.update(/*curSol=*/h, /*delta=*/y, s); + if (convergenceCriterion_.converged()) { + if (verbosity_ > 0) { + convergenceCriterion_.print(report_.iterations() + 0.5); + std::cout << "-------- /BiCGStabSolver --------" << std::endl; + } + + // x = h; // not necessary because x and h are the same object + preconditioner_.post(x); + report_.setConverged(true); + return report_.converged(); + } + else if (convergenceCriterion_.failed()) { + if (verbosity_ > 0) { + convergenceCriterion_.print(report_.iterations() + 0.5); + std::cout << "-------- /BiCGStabSolver --------" << std::endl; + } + + report_.setConverged(false); + return report_.converged(); + } + + if (verbosity_ > 1) + convergenceCriterion_.print(report_.iterations() + 0.5); + + // z = K^-1*s + z = s; + preconditioner_.apply(z, s); + + // t = Az + t = z; + A_->apply(z, t); + + // omega_i = (t*s)/(t*t) + denom = scalarProduct_.dot(t, t); + if (std::abs(denom) <= breakdownEps) + throw NumericalProblem("Breakdown of the BiCGStab solver (division by zero)"); + omega = scalarProduct_.dot(t, s)/denom; + if (std::abs(omega) <= breakdownEps) + throw NumericalProblem("Breakdown of the BiCGStab solver (stagnation detected)"); + + // x_i = h + omega_i*z + // x = h; // not necessary because x and h are the same object + x.axpy(/*a=*/omega, /*y=*/z); + + // do convergence check and print terminal output + convergenceCriterion_.update(/*curSol=*/x, /*delta=*/z, r); + if (convergenceCriterion_.converged()) { + if (verbosity_ > 0) { + convergenceCriterion_.print(1.0 + report_.iterations()); + std::cout << "-------- /BiCGStabSolver --------" << std::endl; + } + + preconditioner_.post(x); + report_.setConverged(true); + return report_.converged(); + } + else if (convergenceCriterion_.failed()) { + if (verbosity_ > 0) { + convergenceCriterion_.print(1.0 + report_.iterations()); + std::cout << "-------- /BiCGStabSolver --------" << std::endl; + } + + report_.setConverged(false); + return report_.converged(); + } + + if (verbosity_ > 1) + convergenceCriterion_.print(1.0 + report_.iterations()); + + // r_i = s - omega*t + // r = s; // not necessary because r and s are the same object + r.axpy(/*a=*/-omega, /*y=*/t); + } + + report_.setConverged(false); + return report_.converged(); + } + + void setConvergenceCriterion(ConvergenceCriterion& crit) + { + convergenceCriterion_ = &crit; + } + + const SolverReport& report() const + { return report_; } + +private: + const LinearOperator* A_; + const Vector* b_; + + Preconditioner& preconditioner_; + ConvergenceCriterion& convergenceCriterion_; + Dune::ScalarProduct& scalarProduct_; + SolverReport report_; + + unsigned maxIterations_; + unsigned verbosity_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/blacklist.hh b/opm/simulators/linalg/blacklist.hh new file mode 100644 index 00000000000..83d4e2f2b68 --- /dev/null +++ b/opm/simulators/linalg/blacklist.hh @@ -0,0 +1,186 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::BlackList + */ +#ifndef EWOMS_BLACK_LIST_HH +#define EWOMS_BLACK_LIST_HH + +#include "overlaptypes.hh" + +#if HAVE_MPI +#include + +#include +#include +#endif // HAVE_MPI + +#include +#include + +namespace Opm { +namespace Linear { +/*! + * \brief Expresses which degrees of freedom are blacklisted for the parallel linear + * solvers and which domestic indices they correspond to. + */ +class BlackList +{ +public: + struct PeerBlackListedEntry { + Index nativeIndexOfPeer; + Index myOwnNativeIndex; + }; + using PeerBlackList = std::vector; + using PeerBlackLists = std::map; + + BlackList() + { } + + BlackList(const BlackList&) = default; + + bool hasIndex(Index nativeIdx) const + { return nativeBlackListedIndices_.count(nativeIdx) > 0; } + + void addIndex(Index nativeIdx) + { nativeBlackListedIndices_.insert(nativeIdx); } + + Index nativeToDomestic(Index nativeIdx) const + { + auto it = nativeToDomesticMap_.find(nativeIdx); + if (it == nativeToDomesticMap_.end()) + return -1; + return it->second; + } + + void setPeerList(ProcessRank peerRank, const PeerBlackList& peerBlackList) + { peerBlackLists_[peerRank] = peerBlackList; } + + template + void updateNativeToDomesticMap([[maybe_unused]] const DomesticOverlap& domesticOverlap) + { +#if HAVE_MPI + auto peerListIt = peerBlackLists_.begin(); + const auto& peerListEndIt = peerBlackLists_.end(); + for (; peerListIt != peerListEndIt; ++peerListIt) { + sendGlobalIndices_(peerListIt->first, + peerListIt->second, + domesticOverlap); + } + + peerListIt = peerBlackLists_.begin(); + for (; peerListIt != peerListEndIt; ++peerListIt) { + receiveGlobalIndices_(peerListIt->first, domesticOverlap); + } + + peerListIt = peerBlackLists_.begin(); + for (; peerListIt != peerListEndIt; ++peerListIt) { + numGlobalIdxSendBuff_.at(peerListIt->first).wait(); + globalIdxSendBuff_.at(peerListIt->first).wait(); + } +#endif // HAVE_MPI + } + + void print() const + { + std::cout << "my own blacklisted indices:\n"; + auto idxIt = nativeBlackListedIndices_.begin(); + const auto& idxEndIt = nativeBlackListedIndices_.end(); + for (; idxIt != idxEndIt; ++idxIt) + std::cout << " (native index: " << *idxIt + << ", domestic index: " << nativeToDomestic(*idxIt) << ")\n"; + std::cout << "blacklisted indices of the peers in my own domain:\n"; + auto peerListIt = peerBlackLists_.begin(); + const auto& peerListEndIt = peerBlackLists_.end(); + for (; peerListIt != peerListEndIt; ++peerListIt) { + ProcessRank peerRank = peerListIt->first; + std::cout << " peer " << peerRank << ":\n"; + auto idx2It = peerListIt->second.begin(); + const auto& idx2EndIt = peerListIt->second.end(); + for (; idx2It != idx2EndIt; ++ idx2It) + std::cout << " (native index: " << idx2It->myOwnNativeIndex + << ", native peer index: " << idx2It->nativeIndexOfPeer << ")\n"; + } + } + +private: +#if HAVE_MPI + template + void sendGlobalIndices_(ProcessRank peerRank, + const PeerBlackList& peerIndices, + const DomesticOverlap& domesticOverlap) + { + auto& numIdxBuff = numGlobalIdxSendBuff_[peerRank]; + auto& idxBuff = globalIdxSendBuff_[peerRank]; + + numIdxBuff.resize(1); + numIdxBuff[0] = static_cast(peerIndices.size()); + numIdxBuff.send(peerRank); + + idxBuff.resize(2*peerIndices.size()); + for (size_t i = 0; i < peerIndices.size(); ++i) { + // global index + Index myNativeIdx = peerIndices[i].myOwnNativeIndex; + Index myDomesticIdx = domesticOverlap.nativeToDomestic(myNativeIdx); + idxBuff[2*i + 0] = domesticOverlap.domesticToGlobal(myDomesticIdx); + + // native peer index + idxBuff[2*i + 1] = peerIndices[i].nativeIndexOfPeer; + } + idxBuff.send(peerRank); + } + + template + void receiveGlobalIndices_(ProcessRank peerRank, + const DomesticOverlap& domesticOverlap) + { + MpiBuffer numGlobalIdxBuf(1); + numGlobalIdxBuf.receive(peerRank); + unsigned numIndices = numGlobalIdxBuf[0]; + + MpiBuffer globalIdxBuf(2*numIndices); + globalIdxBuf.receive(peerRank); + for (unsigned i = 0; i < numIndices; ++i) { + Index globalIdx = globalIdxBuf[2*i + 0]; + Index nativeIdx = globalIdxBuf[2*i + 1]; + + nativeToDomesticMap_[nativeIdx] = domesticOverlap.globalToDomestic(globalIdx); + } + } +#endif // HAVE_MPI + + std::set nativeBlackListedIndices_; + std::map nativeToDomesticMap_; +#if HAVE_MPI + std::map> numGlobalIdxSendBuff_; + std::map> globalIdxSendBuff_; +#endif // HAVE_MPI + + PeerBlackLists peerBlackLists_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/combinedcriterion.hh b/opm/simulators/linalg/combinedcriterion.hh new file mode 100644 index 00000000000..09a6f06fe9a --- /dev/null +++ b/opm/simulators/linalg/combinedcriterion.hh @@ -0,0 +1,242 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::CombinedCriterion + */ +#ifndef EWOMS_COMBINED_CRITERION_HH +#define EWOMS_COMBINED_CRITERION_HH + +#include "convergencecriterion.hh" + +#include + +namespace Opm { +namespace Linear { + +/*! \addtogroup Linear + * \{ + */ + +/*! + * \brief Convergence criterion which looks at the absolute value of the residual and + * fails if the linear solver stagnates. + * + * For the CombinedCriterion, the error of the solution is defined as \f[ e^k = \max_i\{ + * \left| r^k_i \right| \}\;, \f] + * + * where \f$r^k = \mathbf{A} x^k - b \f$ is the residual for the k-th iterative solution + * vector \f$x^k\f$. + * + * In addition, to the reduction of the maximum residual, the linear solver is aborted + * early if the residual goes below or above absolute limits. + */ +template +class CombinedCriterion : public ConvergenceCriterion +{ + using Scalar = typename Vector::field_type; + using BlockType = typename Vector::block_type; + +public: + CombinedCriterion(const CollectiveCommunication& comm) + : comm_(comm) + {} + + CombinedCriterion(const CollectiveCommunication& comm, + Scalar residualReductionTolerance, + Scalar absResidualTolerance = 0.0, + Scalar maxResidual = 0.0) + : comm_(comm), + residualReductionTolerance_(residualReductionTolerance), + absResidualTolerance_(absResidualTolerance), + maxResidual_(maxResidual) + { } + + /*! + * \brief Sets the residual reduction tolerance. + */ + void setResidualReductionTolerance(Scalar tol) + { residualReductionTolerance_ = tol; } + + /*! + * \brief Returns the tolerance of the residual reduction of the solution. + */ + Scalar residualReductionTolerance() const + { return residualReductionTolerance_; } + + /*! + * \brief Returns the reduction of the maximum of the residual compared to the + * initial solution. + */ + Scalar residualReduction() const + { return residualError_/std::max(1e-20, initialResidualError_); } + + /*! + * \brief Sets the maximum absolute tolerated residual. + */ + void setAbsResidualTolerance(Scalar tol) + { absResidualTolerance_ = tol; } + + /*! + * \brief Returns the tolerated maximum of the the infinity norm of the absolute + * residual. + */ + Scalar absResidualTolerance() const + { return absResidualTolerance_; } + + /*! + * \brief Returns the infinity norm of the absolute residual. + */ + Scalar absResidual() const + { return residualError_; } + + /*! + * \copydoc ConvergenceCriterion::setInitial(const Vector& , const Vector& ) + */ + void setInitial(const Vector& curSol, const Vector& curResid) override + { + updateErrors_(curSol, curSol, curResid); + stagnates_ = false; + + // to avoid divisions by zero, make sure that we don't use an initial error of 0 + residualError_ = std::max(residualError_, + std::numeric_limits::min()*1e10); + initialResidualError_ = residualError_; + lastResidualError_ = residualError_; + } + + /*! + * \copydoc ConvergenceCriterion::update(const Vector&, const Vector&, const Vector&) + */ + void update(const Vector& curSol, const Vector& changeIndicator, const Vector& curResid) override + { updateErrors_(curSol, changeIndicator, curResid); } + + /*! + * \copydoc ConvergenceCriterion::converged() + */ + bool converged() const override + { + // we're converged if the solution is better than the tolerance + // fix-point and residual tolerance. + return + residualReduction() <= residualReductionTolerance() || + absResidual() <= absResidualTolerance(); + } + + /*! + * \copydoc ConvergenceCriterion::failed() + */ + bool failed() const override + { return !converged() && (stagnates_ || residualError_ > maxResidual_); } + + /*! + * \copydoc ConvergenceCriterion::accuracy() + * + * For the accuracy we only take the residual into account, + */ + Scalar accuracy() const override + { return residualError_/initialResidualError_; } + + /*! + * \copydoc ConvergenceCriterion::printInitial() + */ + void printInitial(std::ostream& os = std::cout) const override + { + os << std::setw(20) << "iteration "; + os << std::setw(20) << "residual "; + os << std::setw(20) << "reduction "; + os << std::setw(20) << "rate "; + os << std::endl; + } + + /*! + * \copydoc ConvergenceCriterion::print() + */ + void print(Scalar iter, std::ostream& os = std::cout) const override + { + const Scalar eps = std::numeric_limits::min()*1e10; + + os << std::setw(20) << iter << " "; + os << std::setw(20) << absResidual() << " "; + os << std::setw(20) << accuracy() << " "; + os << std::setw(20) << lastResidualError_/std::max(residualError_, eps) << " "; + os << std::endl << std::flush; + } + +private: + // update the weighted absolute residual + void updateErrors_(const Vector&, const Vector& changeIndicator, const Vector& curResid) + { + lastResidualError_ = residualError_; + residualError_ = 0.0; + stagnates_ = true; + for (size_t i = 0; i < curResid.size(); ++i) { + for (unsigned j = 0; j < BlockType::dimension; ++j) { + residualError_ = + std::max(residualError_, + std::abs(curResid[i][j])); + + if (stagnates_ && changeIndicator[i][j] != 0.0) + // only stagnation means that we've failed! + stagnates_ = false; + } + } + + residualError_ = comm_.max(residualError_); + + // the linear solver only stagnates if all processes stagnate + stagnates_ = comm_.min(stagnates_); + } + + const CollectiveCommunication& comm_; + + // the infinity norm of the residual of the last iteration + Scalar lastResidualError_; + + // the infinity norm of the residual of the current iteration + Scalar residualError_; + + // the infinity norm of the residual of the initial solution + Scalar initialResidualError_; + + // the minimum reduction of the residual norm where the solution is to be considered + // converged + Scalar residualReductionTolerance_; + + // the maximum residual norm for the residual for the solution to be considered to be + // converged + Scalar absResidualTolerance_; + + // The maximum error which is tolerated before we fail. + Scalar maxResidual_; + + // does the linear solver seem to stagnate, i.e. were the last two solutions + // identical? + bool stagnates_; +}; + +//! \} end documentation + +}} // end namespace Linear, Opm + +#endif diff --git a/opm/simulators/linalg/convergencecriterion.hh b/opm/simulators/linalg/convergencecriterion.hh new file mode 100644 index 00000000000..c755ca6a85a --- /dev/null +++ b/opm/simulators/linalg/convergencecriterion.hh @@ -0,0 +1,154 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::ConvergenceCriterion + */ +#ifndef EWOMS_ISTL_CONVERGENCE_CRITERION_HH +#define EWOMS_ISTL_CONVERGENCE_CRITERION_HH + +#include +#include + +#include +#include +#include + +namespace Opm { +namespace Linear { + +/*! \addtogroup Linear + * \{ + */ + +/*! + * \file + * \brief Define some base class for the convergence criteria of the linear + * solvers of DUNE-ISTL. + */ + +/*! + * \brief Base class for all convergence criteria which only defines an virtual + * API. + */ +template +class ConvergenceCriterion +{ + //! \brief The real type of the field type (is the same if using real numbers, but differs for std::complex) + using real_type = typename Dune::FieldTraits::real_type; + + using Scalar = real_type; + +public: + /*! + * \brief Destructor. + * + * In the ConvergenceCriterion it does not do anything, but it is + * required to be declared virtual. + */ + virtual ~ConvergenceCriterion() + {} + + /*! + * \brief Set the initial solution of the linear system of equations. + * + * This version of the method does NOT take the two-norm of the + * residual as argument. If the two-norm of the defect is available + * for the linear solver, the version of the update() method with it + * should be called. + * + * \param curSol The current iterative solution of the linear system + * of equations + * \param curResid The residual vector of the current iterative + * solution of the linear system of equations + */ + virtual void setInitial(const Vector& curSol, const Vector& curResid) = 0; + + /*! + * \brief Update the internal members of the convergence criterion + * with the current solution. + * + * This version of the method does NOT take the two-norm of the + * residual as argument. If the two-norm of the defect is available + * for the linear solver, the version of the update() method with it + * should be called. + * + * \param curSol The current iterative solution of the linear system + * of equations + * \param changeIndicator A vector where all non-zero values indicate that the + * solution has changed since the last iteration. + * \param curResid The residual vector of the current iterative + * solution of the linear system of equations + */ + virtual void update(const Vector& curSol, const Vector& changeIndicator, const Vector& curResid) = 0; + + /*! + * \brief Returns true if and only if the convergence criterion is + * met. + */ + virtual bool converged() const = 0; + + /*! + * \brief Returns true if the convergence criterion cannot be met anymore because the + * solver has broken down. + */ + virtual bool failed() const + { return false; } + + /*! + * \brief Returns the accuracy of the solution at the last update. + * + * A value of zero means that the solution was exact. + */ + virtual Scalar accuracy() const = 0; + + /*! + * \brief Prints the initial information about the convergence behaviour. + * + * This method is called after setInitial() if the solver thinks + * it's a good idea to be verbose. In practice, "printing the + * initial information" means printing column headers and the + * initial state. + * + * \param os The output stream to which the message gets written. + */ + virtual void printInitial(std::ostream& = std::cout) const + {} + + /*! + * \brief Prints the information about the convergence behaviour for + * the current iteration. + * + * \param iter The iteration number. The semantics of this parameter + * are chosen by the linear solver. + * \param os The output stream to which the message gets written. + */ + virtual void print(Scalar, std::ostream& = std::cout) const + {} +}; + +//! \} end documentation + +}} // end namespace Linear, Opm + +#endif diff --git a/opm/simulators/linalg/domesticoverlapfrombcrsmatrix.hh b/opm/simulators/linalg/domesticoverlapfrombcrsmatrix.hh new file mode 100644 index 00000000000..b868b84a2ec --- /dev/null +++ b/opm/simulators/linalg/domesticoverlapfrombcrsmatrix.hh @@ -0,0 +1,583 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::DomesticOverlapFromBCRSMatrix + */ +#ifndef EWOMS_DOMESTIC_OVERLAP_FROM_BCRS_MATRIX_HH +#define EWOMS_DOMESTIC_OVERLAP_FROM_BCRS_MATRIX_HH + +#include "foreignoverlapfrombcrsmatrix.hh" +#include "blacklist.hh" +#include "globalindices.hh" + +#include + +#include +#include +#include +#include +#include + +namespace Opm { +namespace Linear { + +/*! + * \brief This class creates and manages the foreign overlap given an + * initial list of border indices and a BCRS matrix. + * + * The foreign overlap are all (row) indices which overlap with the + * some of the current process's local indices. + */ +class DomesticOverlapFromBCRSMatrix +{ + using ForeignOverlap = Opm::Linear::ForeignOverlapFromBCRSMatrix; + using GlobalIndices = Opm::Linear::GlobalIndices; + +public: + // overlaps should never be copied! + DomesticOverlapFromBCRSMatrix(const DomesticOverlapFromBCRSMatrix&) = delete; + + /*! + * \brief Constructs the foreign overlap given a BCRS matrix and + * an initial list of border indices. + */ + template + DomesticOverlapFromBCRSMatrix(const BCRSMatrix& A, + const BorderList& borderList, + const BlackList& blackList, + unsigned overlapSize) + : foreignOverlap_(A, borderList, blackList, overlapSize) + , blackList_(blackList) + , globalIndices_(foreignOverlap_) + { + myRank_ = 0; + worldSize_ = 1; + +#if HAVE_MPI + int tmp; + MPI_Comm_rank(MPI_COMM_WORLD, &tmp); + myRank_ = static_cast(tmp); + MPI_Comm_size(MPI_COMM_WORLD, &tmp); + worldSize_ = static_cast(tmp); +#endif // HAVE_MPI + + buildDomesticOverlap_(); + updateMasterRanks_(); + blackList_.updateNativeToDomesticMap(*this); + + setupDebugMapping_(); + } + + void check() const + { +#ifndef NDEBUG + // check consistency of global indices + for (unsigned domIdx = 0; domIdx < numDomestic(); ++domIdx) { + assert(globalToDomestic(domesticToGlobal(domIdx)) == static_cast(domIdx)); + } +#endif // NDEBUG + + // send the foreign overlap for which we are master to the + // peers + std::map *> sizeBufferMap; + + auto peerIt = peerSet_.begin(); + const auto& peerEndIt = peerSet_.end(); + for (; peerIt != peerEndIt; ++peerIt) { + auto& buffer = *(new MpiBuffer(1)); + sizeBufferMap[*peerIt] = &buffer; + buffer[0] = foreignOverlap_.foreignOverlapWithPeer(*peerIt).size(); + buffer.send(*peerIt); + } + + peerIt = peerSet_.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + MpiBuffer rcvBuffer(1); + rcvBuffer.receive(*peerIt); + + assert(rcvBuffer[0] == domesticOverlapWithPeer_.find(*peerIt)->second.size()); + } + + peerIt = peerSet_.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + sizeBufferMap[*peerIt]->wait(); + delete sizeBufferMap[*peerIt]; + } + } + + /*! + * \brief Returns the rank of the current process. + */ + ProcessRank myRank() const + { return myRank_; } + + /*! + * \brief Returns the number of processes in the global MPI communicator. + */ + unsigned worldSize() const + { return worldSize_; } + + /*! + * \brief Return the set of process ranks which share an overlap + * with the current process. + */ + const PeerSet& peerSet() const + { return peerSet_; } + + /*! + * \brief Returns true iff a domestic index is a border index. + */ + bool isBorder(Index domesticIdx) const + { + return isLocal(domesticIdx) + && foreignOverlap_.isBorder(mapExternalToInternal_(domesticIdx)); + } + + /*! + * \brief Returns true iff a domestic index is on the border with + * a given peer process. + */ + bool isBorderWith(Index domesticIdx, ProcessRank peerRank) const + { + return isLocal(domesticIdx) + && foreignOverlap_.isBorderWith(mapExternalToInternal_(domesticIdx), + peerRank); + } + + /*! + * \brief Returns the number of indices on the front within a given + * peer rank's grid partition. + */ + size_t numFront(ProcessRank peerRank) const + { return foreignOverlap_.numFront(peerRank); } + + /*! + * \brief Returns true iff a domestic index is on the front. + */ + bool isFront(Index domesticIdx) const + { + if (isLocal(domesticIdx)) + return false; + Index internalDomesticIdx = mapExternalToInternal_(domesticIdx); + + // check wether the border distance of the domestic overlap is + // maximal for the index + const auto& domOverlap = domesticOverlapByIndex_[internalDomesticIdx]; + return domOverlap.size() > 0 + && domOverlap.begin()->second == foreignOverlap_.overlapSize(); + } + + /*! + * \brief Returns the object which represents the black-listed native indices. + */ + const BlackList& blackList() const + { return blackList_; } + + /*! + * \brief Returns the number of processes which "see" a given + * index. + */ + size_t numPeers(Index domesticIdx) const + { return domesticOverlapByIndex_[mapExternalToInternal_(domesticIdx)].size(); } + + /*! + * \brief Returns the size of the overlap region + */ + unsigned overlapSize() const + { return foreignOverlap_.overlapSize(); } + + /*! + * \brief Returns the number native indices + * + * I.e. the number of indices of the "raw" grid partition of the + * local process (including the indices in ghost and overlap + * elements). + */ + size_t numNative() const + { return foreignOverlap_.numNative(); } + + /*! + * \brief Returns the number local indices + * + * I.e. indices in the interior or on the border of the process' + * domain. + */ + size_t numLocal() const + { return foreignOverlap_.numLocal(); } + + /*! + * \brief Returns the number domestic indices. + * + * The domestic indices are defined as the process' local indices + * plus its domestic overlap (i.e. indices for which it is not + * neither master nor are on the process border). + */ + size_t numDomestic() const + { return globalIndices_.numDomestic(); } + + /*! + * \brief Return true if a domestic index is local for the process + * + * I.e. the entity for this index is in the interior or on the + * border of the process' domain. + */ + bool isLocal(Index domesticIdx) const + { return mapExternalToInternal_(domesticIdx) < static_cast(numLocal()); } + + /*! + * \brief Return true iff the current process is the master of a + * given domestic index. + */ + bool iAmMasterOf(Index domesticIdx) const + { + if (!isLocal(domesticIdx)) + return false; + return foreignOverlap_.iAmMasterOf(mapExternalToInternal_(domesticIdx)); + } + + /*! + * \brief Return the rank of a master process for a domestic index + */ + ProcessRank masterRank(Index domesticIdx) const + { return masterRank_[static_cast(mapExternalToInternal_(domesticIdx))]; } + + /*! + * \brief Print the foreign overlap for debugging purposes. + */ + void print() const + { globalIndices_.print(); } + + /*! + * \brief Returns a domestic index given a global one + */ + Index globalToDomestic(Index globalIdx) const + { + Index internalIdx = globalIndices_.globalToDomestic(globalIdx); + if (internalIdx < 0) + return -1; + return mapInternalToExternal_(internalIdx); + } + + /*! + * \brief Returns a global index given a domestic one + */ + Index domesticToGlobal(Index domIdx) const + { return globalIndices_.domesticToGlobal(mapExternalToInternal_(domIdx)); } + + /*! + * \brief Returns a native index given a domestic one + */ + Index domesticToNative(Index domIdx) const + { + Index internalIdx = mapExternalToInternal_(domIdx); + if (internalIdx >= static_cast(numLocal())) + return -1; + return foreignOverlap_.localToNative(internalIdx); + } + + /*! + * \brief Returns a domestic index given a native one + */ + Index nativeToDomestic(Index nativeIdx) const + { + Index localIdx = foreignOverlap_.nativeToLocal(nativeIdx); + if (localIdx < 0) + return localIdx; + return mapInternalToExternal_(localIdx); + } + + /*! + * \brief Returns true if a given domestic index is either in the + * foreign or in the domestic overlap. + */ + bool isInOverlap(Index domesticIdx) const + { + return !this->isLocal(domesticIdx) + || this->foreignOverlap_.isInOverlap(mapExternalToInternal_(domesticIdx)); + } + + /*! + * \brief Returns true if a given domestic index is a front index + * for a peer rank. + */ + bool isFrontFor(ProcessRank peerRank, Index domesticIdx) const + { + Index internalIdx = mapExternalToInternal_(domesticIdx); + return this->foreignOverlap_.isFrontFor(peerRank, internalIdx); + } + + /*! + * \brief Returns true iff a domestic index is seen by a peer rank. + */ + bool peerHasIndex(int peerRank, Index domesticIdx) const + { + return foreignOverlap_.peerHasIndex(peerRank, + mapExternalToInternal_(domesticIdx)); + } + + /*! + * \brief Returns number of indices which are contained in the + * foreign overlap with a peer. + */ + size_t foreignOverlapSize(ProcessRank peerRank) const + { return foreignOverlap_.foreignOverlapWithPeer(peerRank).size(); } + + /*! + * \brief Returns the domestic index given an offset in the + * foreign overlap of a peer process with the local + * process. + */ + Index foreignOverlapOffsetToDomesticIdx(ProcessRank peerRank, unsigned overlapOffset) const + { + Index internalIdx = + foreignOverlap_.foreignOverlapWithPeer(peerRank)[overlapOffset].index; + return mapInternalToExternal_(internalIdx); + } + + /*! + * \brief Returns number of indices which are contained in the + * domestic overlap with a peer. + */ + size_t domesticOverlapSize(ProcessRank peerRank) const + { return domesticOverlapWithPeer_.at(peerRank).size(); } + + /*! + * \brief Returns the domestic index given an offset in the + * domestic overlap of a peer process with the local + * process. + */ + Index domesticOverlapOffsetToDomesticIdx(ProcessRank peerRank, Index overlapOffset) const + { + Index internalIdx = domesticOverlapWithPeer_.at(peerRank)[overlapOffset]; + return mapInternalToExternal_(internalIdx); + } + +protected: + void buildDomesticOverlap_() + { + // copy the set of peers from the foreign overlap + peerSet_ = foreignOverlap_.peerSet(); + + // resize the array which stores the number of peers for + // each entry. + domesticOverlapByIndex_.resize(numLocal()); + borderDistance_.resize(numLocal(), 0); + + PeerSet::const_iterator peerIt; + PeerSet::const_iterator peerEndIt = peerSet_.end(); + + // send the overlap indices to all peer processes + peerIt = peerSet_.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + sendIndicesToPeer_(peerRank); + } + + // receive our overlap from the processes to all peer processes + peerIt = peerSet_.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + receiveIndicesFromPeer_(peerRank); + } + + // wait until all send operations complete + peerIt = peerSet_.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + waitSendIndices_(peerRank); + } + } + + void updateMasterRanks_() + { + size_t nLocal = numLocal(); + size_t nDomestic = numDomestic(); + masterRank_.resize(nDomestic); + + // take the master ranks for the local indices from the + // foreign overlap + for (unsigned i = 0; i < nLocal; ++i) { + masterRank_[i] = foreignOverlap_.masterRank(static_cast(i)); + } + + // for non-local indices, initially use INT_MAX as their master + // rank + for (size_t i = nLocal; i < nDomestic; ++i) + masterRank_[i] = std::numeric_limits::max(); + + // for the non-local indices, take the peer process for which + // a given local index is in the interior + auto peerIt = peerSet_.begin(); + const auto& peerEndIt = peerSet_.end(); + for (; peerIt != peerEndIt; ++peerIt) { + const auto& overlapWithPeer = domesticOverlapWithPeer_.find(*peerIt)->second; + + auto idxIt = overlapWithPeer.begin(); + const auto& idxEndIt = overlapWithPeer.end(); + for (; idxIt != idxEndIt; ++idxIt) { + if (*idxIt >= 0 && foreignOverlap_.isLocal(*idxIt)) + continue; // ignore border indices + + masterRank_[static_cast(*idxIt)] = std::min(masterRank_[static_cast(*idxIt)], *peerIt); + } + } + } + + void sendIndicesToPeer_([[maybe_unused]] ProcessRank peerRank) + { +#if HAVE_MPI + const auto& foreignOverlap = foreignOverlap_.foreignOverlapWithPeer(peerRank); + + // first, send a message containing the number of additional + // indices stemming from the overlap (i.e. without the border + // indices) + size_t numIndices = foreignOverlap.size(); + numIndicesSendBuffer_[peerRank] = new MpiBuffer(1); + (*numIndicesSendBuffer_[peerRank])[0] = numIndices; + numIndicesSendBuffer_[peerRank]->send(peerRank); + + // create MPI buffers + indicesSendBuffer_[peerRank] = new MpiBuffer(numIndices); + + // then send the additional indices themselfs + auto overlapIt = foreignOverlap.begin(); + const auto& overlapEndIt = foreignOverlap.end(); + for (unsigned i = 0; overlapIt != overlapEndIt; ++overlapIt, ++i) { + Index localIdx = overlapIt->index; + BorderDistance borderDistance = overlapIt->borderDistance; + size_t numPeers = foreignOverlap_.foreignOverlapByLocalIndex(localIdx).size(); + + IndexDistanceNpeers tmp; + tmp.index = globalIndices_.domesticToGlobal(localIdx); + tmp.borderDistance = borderDistance; + tmp.numPeers = static_cast(numPeers); + + (*indicesSendBuffer_[peerRank])[i] = tmp; + } + + indicesSendBuffer_[peerRank]->send(peerRank); +#endif // HAVE_MPI + } + + void waitSendIndices_(ProcessRank peerRank) + { + numIndicesSendBuffer_[peerRank]->wait(); + delete numIndicesSendBuffer_[peerRank]; + + indicesSendBuffer_[peerRank]->wait(); + delete indicesSendBuffer_[peerRank]; + } + + void receiveIndicesFromPeer_([[maybe_unused]] ProcessRank peerRank) + { +#if HAVE_MPI + // receive the number of additional indices + int numIndices = -1; + MpiBuffer numIndicesRecvBuff(1); + numIndicesRecvBuff.receive(peerRank); + numIndices = static_cast(numIndicesRecvBuff[0]); + + // receive the additional indices themselfs + MpiBuffer recvBuff(static_cast(numIndices)); + recvBuff.receive(peerRank); + for (unsigned i = 0; i < static_cast(numIndices); ++i) { + Index globalIdx = recvBuff[i].index; + BorderDistance borderDistance = recvBuff[i].borderDistance; + + // if the index is not already known, add it to the + // domestic indices + if (!globalIndices_.hasGlobalIndex(globalIdx)) { + Index newDomesticIdx = static_cast(globalIndices_.numDomestic()); + globalIndices_.addIndex(newDomesticIdx, globalIdx); + + size_t newSize = globalIndices_.numDomestic(); + borderDistance_.resize(newSize, std::numeric_limits::max()); + domesticOverlapByIndex_.resize(newSize); + } + + // convert the global index into a domestic one + Index domesticIdx = globalIndices_.globalToDomestic(globalIdx); + + // extend the domestic overlap + domesticOverlapByIndex_[static_cast(domesticIdx)][static_cast(peerRank)] = borderDistance; + domesticOverlapWithPeer_[static_cast(peerRank)].push_back(domesticIdx); + + //assert(borderDistance >= 0); + assert(globalIdx >= 0); + assert(domesticIdx >= 0); + assert(!(borderDistance == 0 && !foreignOverlap_.isLocal(domesticIdx))); + assert(!(borderDistance > 0 && foreignOverlap_.isLocal(domesticIdx))); + + borderDistance_[static_cast(domesticIdx)] = std::min(borderDistance, borderDistance_[static_cast(domesticIdx)]); + } +#endif // HAVE_MPI + } + + // this method is intended to set up the code mapping code for + // mapping domestic indices to the same ones used by a sequential + // grid. this requires detailed knowledge about how a grid + // distributes the degrees of freedom over multiple processes, but + // it can simplify debugging considerably because the indices can + // be made identical for the parallel and the sequential + // computations. + // + // by default, this method does nothing + void setupDebugMapping_() + {} + + // this method is intended to map domestic indices to the ones + // used by a sequential grid. + // + // by default, this method does nothing + Index mapInternalToExternal_(Index internalIdx) const + { return internalIdx; } + + // this method is intended to map the indices used by a sequential + // to grid domestic indices ones. + // + // by default, this method does nothing + Index mapExternalToInternal_(Index externalIdx) const + { return externalIdx; } + + ProcessRank myRank_; + unsigned worldSize_; + ForeignOverlap foreignOverlap_; + + BlackList blackList_; + + DomesticOverlapByRank domesticOverlapWithPeer_; + OverlapByIndex domesticOverlapByIndex_; + std::vector borderDistance_; + std::vector masterRank_; + + std::map *> numIndicesSendBuffer_; + std::map *> indicesSendBuffer_; + GlobalIndices globalIndices_; + PeerSet peerSet_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/elementborderlistfromgrid.hh b/opm/simulators/linalg/elementborderlistfromgrid.hh new file mode 100644 index 00000000000..74716e49521 --- /dev/null +++ b/opm/simulators/linalg/elementborderlistfromgrid.hh @@ -0,0 +1,263 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::ElementBorderListFromGrid + */ +#ifndef EWOMS_ELEMENT_BORDER_LIST_FROM_GRID_HH +#define EWOMS_ELEMENT_BORDER_LIST_FROM_GRID_HH + +#include "overlaptypes.hh" +#include "blacklist.hh" + +#include +#include +#include +#include + +#include + +namespace Opm { +namespace Linear { +/*! + * \brief Uses communication on the grid to find the initial seed list + * of indices for methods which use element-based degrees of + * freedom. + */ +template +class ElementBorderListFromGrid +{ + using PeerBlackListedEntry = BlackList::PeerBlackListedEntry; + using PeerBlackList = BlackList::PeerBlackList; + using PeerBlackLists = BlackList::PeerBlackLists; + + using Element = typename GridView::template Codim<0>::Entity; + + class BorderListHandle_ + : public Dune::CommDataHandleIF + { + public: + BorderListHandle_(const GridView& gridView, + const ElementMapper& map, + BlackList& blackList, + BorderList& borderList) + : gridView_(gridView) + , map_(map) + , blackList_(blackList) + , borderList_(borderList) + { + for (const auto& elem : elements(gridView_)) { + if (elem.partitionType() != Dune::InteriorEntity) { + Index elemIdx = static_cast(map_.index(elem)); + blackList_.addIndex(elemIdx); + } + } + } + + // data handle methods + bool contains(int, int codim) const + { return codim == 0; } + +#if DUNE_VERSION_LT(DUNE_GRID, 2, 8) + bool fixedsize(int, int) const +#else + bool fixedSize(int, int) const +#endif + { return true; } + + template + size_t size(const EntityType&) const + { return 2; } + + template + void gather(MessageBufferImp& buff, const EntityType& e) const + { + unsigned myIdx = static_cast(map_.index(e)); + buff.write(static_cast(gridView_.comm().rank())); + buff.write(myIdx); + } + + template + void scatter(MessageBufferImp& buff, + const Element& e, + size_t) + { + // discard the index if it is not on the process boundary + bool isInteriorNeighbor = false; + auto isIt = gridView_.ibegin(e); + const auto& isEndIt = gridView_.iend(e); + for (; isIt != isEndIt; ++isIt) { + if (!isIt->neighbor()) + continue; + else if (isIt->outside().partitionType() == Dune::InteriorEntity) { + isInteriorNeighbor = true; + break; + } + } + if (!isInteriorNeighbor) + return; + + BorderIndex bIdx; + + bIdx.localIdx = static_cast(map_.index(e)); + { + ProcessRank tmp; + buff.read(tmp); + bIdx.peerRank = tmp; + peerSet_.insert(tmp); + } + { + unsigned tmp; + buff.read(tmp); + bIdx.peerIdx = static_cast(tmp); + } + bIdx.borderDistance = 1; + + borderList_.push_back(bIdx); + } + + // this template method is needed because the above one only works for codim-0 + // entities (i.e., elements) but the dune grid uses some code which causes the + // compiler to invoke the scatter method for every codim... + template + void scatter(MessageBufferImp&, + const EntityType&, + size_t) + { } + + const std::set& peerSet() const + { return peerSet_; } + + private: + GridView gridView_; + const ElementMapper& map_; + std::set peerSet_; + BlackList& blackList_; + BorderList& borderList_; + }; + + class PeerBlackListHandle_ + : public Dune::CommDataHandleIF + { + public: + PeerBlackListHandle_(const GridView& gridView, + const ElementMapper& map, + PeerBlackLists& peerBlackLists) + : gridView_(gridView) + , map_(map) + , peerBlackLists_(peerBlackLists) + {} + + // data handle methods + bool contains(int, int codim) const + { return codim == 0; } + +#if DUNE_VERSION_LT(DUNE_GRID, 2, 8) + bool fixedsize(int, int) const +#else + bool fixedSize(int, int) const +#endif + { return true; } + + template + size_t size(const EntityType&) const + { return 2; } + + template + void gather(MessageBufferImp& buff, const EntityType& e) const + { + buff.write(static_cast(gridView_.comm().rank())); + buff.write(static_cast(map_.index(e))); + } + + template + void scatter(MessageBufferImp& buff, const EntityType& e, size_t) + { + int peerRank; + int peerIdx; + Index localIdx; + + buff.read(peerRank); + buff.read(peerIdx); + localIdx = static_cast(map_.index(e)); + + PeerBlackListedEntry pIdx; + pIdx.nativeIndexOfPeer = static_cast(peerIdx); + pIdx.myOwnNativeIndex = static_cast(localIdx); + + peerBlackLists_[static_cast(peerRank)].push_back(pIdx); + } + + const PeerBlackList& peerBlackList(ProcessRank peerRank) const + { return peerBlackLists_.at(peerRank); } + + private: + GridView gridView_; + const ElementMapper& map_; + PeerBlackLists peerBlackLists_; + }; + +public: + ElementBorderListFromGrid(const GridView& gridView, const ElementMapper& map) + : gridView_(gridView) + , map_(map) + { + BorderListHandle_ blh(gridView, map, blackList_, borderList_); + gridView.communicate(blh, + Dune::InteriorBorder_All_Interface, + Dune::BackwardCommunication); + + PeerBlackListHandle_ pblh(gridView, map, peerBlackLists_); + gridView.communicate(pblh, + Dune::InteriorBorder_All_Interface, + Dune::BackwardCommunication); + + auto peerIt = blh.peerSet().begin(); + const auto& peerEndIt = blh.peerSet().end(); + for (; peerIt != peerEndIt; ++peerIt) { + blackList_.setPeerList(*peerIt, pblh.peerBlackList(*peerIt)); + } + } + + // Access to the border list. + const BorderList& borderList() const + { return borderList_; } + + // Access to the black-list indices. + const BlackList& blackList() const + { return blackList_; } + +private: + const GridView gridView_; + const ElementMapper& map_; + + BorderList borderList_; + + BlackList blackList_; + PeerBlackLists peerBlackLists_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/fixpointcriterion.hh b/opm/simulators/linalg/fixpointcriterion.hh new file mode 100644 index 00000000000..32c02c00e72 --- /dev/null +++ b/opm/simulators/linalg/fixpointcriterion.hh @@ -0,0 +1,185 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::FixPointCriterion + */ +#ifndef EWOMS_ISTL_FIXPOINT_CRITERION_HH +#define EWOMS_ISTL_FIXPOINT_CRITERION_HH + +#include "convergencecriterion.hh" + +#include + +namespace Opm { +namespace Linear { + +/*! \addtogroup Linear + * \{ + */ + +/*! + * \brief Provides a convergence criterion for the linear solvers + * which looks at the weighted maximum of the difference + * between two iterations. + * + * For the FixPointCriterion, the error of the solution is defined + * as + * \f[ e^k = \max_i\{ \left| w_i \delta^k_i \right| \}\;, \f] + * + * where \f$\delta = x^k - x^{k + 1} \f$ is the difference between + * two consequtive iterative solution vectors \f$x^k\f$ and \f$x^{k + 1}\f$ + * and \f$w_i\f$ is the weight of the \f$i\f$-th degree of freedom. + * + * This criterion requires that the block type of the + * vector is a Dune::FieldVector + */ +template +class FixPointCriterion : public ConvergenceCriterion +{ + using Scalar = typename Vector::field_type; + using BlockType = typename Vector::block_type; + +public: + FixPointCriterion(const CollectiveCommunication& comm) : comm_(comm) + {} + + FixPointCriterion(const CollectiveCommunication& comm, + const Vector& weightVec, Scalar reduction) + : comm_(comm), weightVec_(weightVec), tolerance_(reduction) + {} + + /*! + * \brief Sets the relative weight of a primary variable + * + * For the FixPointCriterion, the error of the solution is defined + * as + * \f[ e^k = \max_i\{ \left| w_i \delta^k_i \right| \}\;, \f] + * + * where \f$\delta = x^k - x^{k + 1} \f$ is the difference between + * two consequtive iterative solution vectors \f$x^k\f$ and \f$x^{k + 1}\f$ + * and \f$w_i\f$ is the weight of the \f$i\f$-th degree of freedom. + * + * This method is specific to the FixPointCriterion. + * + * \param weightVec A Dune::BlockVector > + * with the relative weights of the degrees of freedom + */ + void setWeight(const Vector& weightVec) + { weightVec_ = weightVec; } + + /*! + * \brief Return the relative weight of a primary variable + * + * For the FixPointCriterion, the error of the solution is defined + * as + * \f[ e^k = \max_i\{ \left| w_i \delta^k_i \right| \}\;, \f] + * + * where \f$\delta = x^k - x^{k + 1} \f$ is the difference between + * two consequtive iterative solution vectors \f$x^k\f$ and \f$x^{k + 1}\f$ + * and \f$w_i\f$ is the weight of the \f$i\f$-th degree of freedom. + * + * This method is specific to the FixPointCriterion. + * + * \param outerIdx The index of the outer vector (i.e. Dune::BlockVector) + * \param innerIdx The index of the inner vector (i.e. Dune::FieldVector) + */ + Scalar weight(int outerIdx, int innerIdx) const + { return (weightVec_.size() == 0) ? 1.0 : weightVec_[outerIdx][innerIdx]; } + + /*! + * \brief Set the maximum allowed weighted maximum difference between two + * iterations + */ + /*! + * \brief Set the maximum allowed maximum difference between two + * iterationsfor the solution considered to be converged. + */ + void setTolerance(Scalar tol) + { tolerance_ = tol; } + + /*! + * \brief Return the maximum allowed weighted difference between two + * iterations for the solution considered to be converged. + */ + Scalar tolerance() const + { return tolerance_; } + + /*! + * \copydoc ConvergenceCriterion::setInitial(const Vector&, const Vector&) + */ + void setInitial(const Vector& curSol, const Vector&) + { + lastSol_ = curSol; + delta_ = 1000 * tolerance_; + } + + /*! + * \copydoc ConvergenceCriterion::update(const Vector&, const Vector&, const Vector&) + */ + void update(const Vector& curSol, + const Vector&, + const Vector&) + { + assert(curSol.size() == lastSol_.size()); + + delta_ = 0.0; + for (size_t i = 0; i < curSol.size(); ++i) { + for (size_t j = 0; j < BlockType::dimension; ++j) { + delta_ = + std::max(delta_, weight(i, j)*std::abs(curSol[i][j] - lastSol_[i][j])); + } + } + + delta_ = comm_.max(delta_); + lastSol_ = curSol; + } + + /*! + * \copydoc ConvergenceCriterion::converged() + */ + bool converged() const + { return accuracy() < tolerance(); } + + /*! + * \copydoc ConvergenceCriterion::accuracy() + */ + Scalar accuracy() const + { return delta_; } + +private: + const CollectiveCommunication& comm_; + + Vector lastSol_; // solution of the last iteration + Vector weightVec_; // solution of the last iteration + Scalar delta_; // the maximum of the absolute weighted difference of the + // last two iterations + Scalar tolerance_; // the maximum allowed delta for the solution to be + // considered converged +}; + +//! \} end documentation + +}} // end namespace Linear, Opm + +#endif diff --git a/opm/simulators/linalg/foreignoverlapfrombcrsmatrix.hh b/opm/simulators/linalg/foreignoverlapfrombcrsmatrix.hh new file mode 100644 index 00000000000..14e0c608c7c --- /dev/null +++ b/opm/simulators/linalg/foreignoverlapfrombcrsmatrix.hh @@ -0,0 +1,710 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::ForeignOverlapFromBCRSMatrix + */ +#ifndef EWOMS_FOREIGN_OVERLAP_FROM_BCRS_MATRIX_HH +#define EWOMS_FOREIGN_OVERLAP_FROM_BCRS_MATRIX_HH + +#include "overlaptypes.hh" +#include "blacklist.hh" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#if HAVE_MPI +#include +#endif // HAVE_MPI + +namespace Opm { +namespace Linear { + +/*! + * \brief This class creates and manages the foreign overlap given an + * initial list of border indices and a BCRS matrix. + * + * The foreign overlap are all (row) indices which overlap with the + * some of the current process's local indices. + */ +class ForeignOverlapFromBCRSMatrix +{ +public: + // overlaps should never be copied! + ForeignOverlapFromBCRSMatrix(const ForeignOverlapFromBCRSMatrix&) = delete; + + /*! + * \brief Constructs the foreign overlap given a BCRS matrix and + * an initial list of border indices. + */ + template + ForeignOverlapFromBCRSMatrix(const BCRSMatrix& A, + const BorderList& borderList, + const BlackList& blackList, + unsigned overlapSize) + : borderList_(borderList), blackList_(blackList) + { + overlapSize_ = overlapSize; + + myRank_ = 0; +#if HAVE_MPI + { + int tmp; + MPI_Comm_rank(MPI_COMM_WORLD, &tmp); + myRank_ = static_cast(tmp); + } +#endif + numNative_ = A.N(); + + // Computes the local <-> native index maps + createLocalIndices_(); + + // calculate the set of local indices on the border (beware: + // _not_ the native ones) + auto it = borderList.begin(); + const auto& endIt = borderList.end(); + for (; it != endIt; ++it) { + Index localIdx = nativeToLocal(it->localIdx); + if (localIdx < 0) + continue; + + localBorderIndices_.insert(localIdx); + } + + // compute the set of processes which are neighbors of the + // local process ... + neighborPeerSet_.update(borderList); + // ... and the initial set of processes which we will have to + // communicate with. We must always communicate with our + // neighbors, but depending on the size of the overlap region, + // we might have to communicate with additional processes as + // well (these will be added later). + peerSet_ = neighborPeerSet_; + + // Create an initial seed list of indices which are in the + // overlap. + SeedList initialSeedList; + initialSeedList.update(borderList); + + // calculate the minimum distance from the border of the + // initial seed list + unsigned minBorderDist = overlapSize; + auto borderIt = borderList.begin(); + const auto& borderEndIt = borderList.end(); + for (; borderIt != borderEndIt; ++borderIt) { + minBorderDist = std::min(minBorderDist, borderIt->borderDistance); + } + + // calculate the foreign overlap for the local partition, + // i.e. find the distance of each row from the seed set. + foreignOverlapByLocalIndex_.resize(numLocal()); + extendForeignOverlap_(A, initialSeedList, minBorderDist, overlapSize); + + // computes the process with the lowest rank for all local + // indices. + computeMasterRanks_(); + + // group foreign overlap by peer process rank + groupForeignOverlapByRank_(); + } + + /*! + * \brief Returns the size of the overlap region. + */ + unsigned overlapSize() const + { return overlapSize_; } + + /*! + * \brief Returns true iff a local index is a border index. + */ + bool isBorder(Index localIdx) const + { return localBorderIndices_.count(localIdx) > 0; } + + /*! + * \brief Returns true iff a local index is a border index shared with a + * given peer process. + */ + bool isBorderWith(Index localIdx, ProcessRank peerRank) const + { + const auto& indexOverlap = foreignOverlapByLocalIndex_[static_cast(localIdx)]; + const auto& borderDistIt = indexOverlap.find(peerRank); + if (borderDistIt == indexOverlap.end()) + return false; + + // border distance of the index needs to be 0 + return borderDistIt->second == 0; + } + + /*! + * \brief Return the rank of the master process of an + * index. + */ + ProcessRank masterRank(Index localIdx) const + { return masterRank_[static_cast(localIdx)]; } + + /*! + * \brief Return true if the current rank is the "master" of an + * index. + * + * If the index is at the interior of some process, we define this + * process as its master, if the index is on the boundary, then + * the master is defined as the process with the lowest rank. + */ + bool iAmMasterOf(Index localIdx) const + { return masterRank_[static_cast(localIdx)] == myRank_; } + + /*! + * \brief Returns the list of indices which intersect the process + * border. + */ + const BorderList& borderList() const + { return borderList_; } + + /*! + * \brief Return the list of (local indices, border distance, + * number of processes) triples which are in the overlap of + * a given peer rank. + */ + const OverlapWithPeer& foreignOverlapWithPeer(ProcessRank peerRank) const + { + assert(foreignOverlapByRank_.find(peerRank) != foreignOverlapByRank_.end()); + return foreignOverlapByRank_.find(peerRank)->second; + } + + /*! + * \brief Return the map of (peer rank, border distance) for a given local + * index. + */ + const std::map & + foreignOverlapByLocalIndex(Index localIdx) const + { + assert(isLocal(localIdx)); + return foreignOverlapByLocalIndex_[static_cast(localIdx)]; + } + + /*! + * \brief Returns true iff a local index is seen by a peer rank. + */ + bool peerHasIndex(ProcessRank peerRank, Index localIdx) const + { + const auto& idxOverlap = foreignOverlapByLocalIndex_[localIdx]; + return idxOverlap.find(peerRank) != idxOverlap.end(); + } + + /*! + * \brief Returns the number of front indices of a peer process in + * the local partition. + */ + size_t numFront(ProcessRank peerRank) const + { + const auto& peerOverlap = foreignOverlapByRank_.find(peerRank)->second; + + size_t n = 0; + auto it = peerOverlap.begin(); + const auto& endIt = peerOverlap.end(); + for (; it != endIt; ++it) { + if (it->borderDistance == overlapSize_) + ++n; + } + return n; + } + + /*! + * \brief Returns whether a given local index is on the front of a + * given peer rank. + */ + bool isFrontFor(ProcessRank peerRank, Index localIdx) const + { + const auto& idxOverlap = foreignOverlapByLocalIndex_[localIdx]; + + auto it = idxOverlap.find(peerRank); + if (it == idxOverlap.end()) + return false; // index is not in overlap + + return it->second == overlapSize_; + } + + /*! + * \brief Return the set of process ranks which share an overlap + * with the current process. + */ + const PeerSet& peerSet() const + { return peerSet_; } + + /*! + * \brief Return the set of process ranks which share a border index + * with the current process. + */ + const PeerSet& neighborPeerSet() const + { return neighborPeerSet_; } + + /*! + * \brief Returns the number of native indices + */ + size_t numNative() const + { return numNative_; } + + /*! + * \brief Returns the number of local indices + */ + size_t numLocal() const + { return numLocal_; } + + /*! + * \brief Returns true iff a domestic index is local + */ + bool isLocal(Index domesticIdx) const + { return static_cast(domesticIdx) < numLocal(); } + + /*! + * \brief Convert a native index to a local one. + * + * If a given native index is not in the set of local indices, + * this method returns -1. + */ + Index nativeToLocal(Index nativeIdx) const + { return nativeToLocalIndices_[static_cast(nativeIdx)]; } + + /*! + * \brief Convert a local index to a native one. + */ + Index localToNative(Index localIdx) const + { + assert(localIdx < static_cast(localToNativeIndices_.size())); + return localToNativeIndices_[static_cast(localIdx)]; + } + + /*! + * \brief Returns the object which represents the black-listed native indices. + */ + const BlackList& blackList() const + { return blackList_; } + + /*! + * \brief Return the number of peer ranks for which a given local + * index is visible. + */ + size_t numPeers(Index localIdx) const + { return foreignOverlapByLocalIndex_[static_cast(localIdx)].size(); } + + /*! + * \brief Returns true if a given local index is in the foreign overlap of + * any rank. + */ + bool isInOverlap(Index localIdx) const + { return foreignOverlapByLocalIndex_[static_cast(localIdx)].size() > 0; } + + /*! + * \brief Print the foreign overlap for debugging purposes. + */ + void print() const + { + auto it = foreignOverlapByRank_.begin(); + const auto& endIt = foreignOverlapByRank_.end(); + for (; it != endIt; ++it) { + std::cout << "Overlap rows(distance) for rank " << it->first << ": "; + + auto rowIt = it->second.begin(); + const auto& rowEndIt = it->second.end(); + for (; rowIt != rowEndIt; ++rowIt) + std::cout << rowIt->index << "(" << rowIt->borderDistance << ") "; + std::cout << "\n" << std::flush; + } + } + +protected: + // extend the foreign overlaps by 'overlapSize' levels. this uses + // a greedy algorithm which extends the region by one level and + // then calls itself recursively... + template + void extendForeignOverlap_(const BCRSMatrix& A, + SeedList& seedList, + BorderDistance borderDistance, + BorderDistance overlapSize) + { + // communicate the non-neigbor overlap indices + addNonNeighborOverlapIndices_(A, seedList, borderDistance); + + // add all processes in the seed rows of the current overlap level + auto seedIt = seedList.begin(); + const auto& seedEndIt = seedList.end(); + for (; seedIt != seedEndIt; ++seedIt) { + Index localIdx = nativeToLocal(seedIt->index); + ProcessRank peerRank = seedIt->peerRank; + unsigned distance = borderDistance; + if (localIdx < 0) + continue; + if (foreignOverlapByLocalIndex_[static_cast(localIdx)].count(peerRank) == 0) + foreignOverlapByLocalIndex_[static_cast(localIdx)][peerRank] = distance; + } + + // if we have reached the maximum overlap distance, i.e. we're + // finished and break the recursion + if (borderDistance >= overlapSize) + return; + + // find the seed list for the next overlap level using the + // seed set for the current level + SeedList nextSeedList; + seedIt = seedList.begin(); + for (; seedIt != seedEndIt; ++seedIt) { + Index nativeRowIdx = seedIt->index; + if (nativeToLocal(nativeRowIdx) < 0) + continue; // ignore blacklisted indices + ProcessRank peerRank = seedIt->peerRank; + + // find all column indices in the row. The indices of the + // columns are the additional indices of the overlap which + // we would like to add + using ColIterator = typename BCRSMatrix::ConstColIterator; + ColIterator colIt = A[static_cast(nativeRowIdx)].begin(); + ColIterator colEndIt = A[static_cast(nativeRowIdx)].end(); + for (; colIt != colEndIt; ++colIt) { + Index nativeColIdx = static_cast(colIt.index()); + Index localColIdx = nativeToLocal(nativeColIdx); + + // ignore if the native index is not a local one + if (localColIdx < 0) + continue; + // if the process is already is in the overlap of the + // column index, ignore this column index! + else if (foreignOverlapByLocalIndex_[static_cast(localColIdx)].count(peerRank) > 0) + continue; + + // check whether the new index is already in the overlap + bool hasIndex = false; + typename SeedList::iterator sIt = nextSeedList.begin(); + typename SeedList::iterator sEndIt = nextSeedList.end(); + for (; sIt != sEndIt; ++sIt) { + if (sIt->index == nativeColIdx && sIt->peerRank == peerRank) { + hasIndex = true; + break; + } + } + if (hasIndex) + continue; // we already have this index + + // add the current processes to the seed list for the + // next overlap level + IndexRankDist newTuple; + newTuple.index = nativeColIdx; + newTuple.peerRank = peerRank; + newTuple.borderDistance = seedIt->borderDistance + 1; + nextSeedList.push_back(newTuple); + } + } + + // clear the old seed list to save some memory + seedList.clear(); + + // Perform the same excercise for the next overlap distance + extendForeignOverlap_(A, nextSeedList, borderDistance + 1, overlapSize); + } + + // Computes the local <-> native index maps + void createLocalIndices_() + { + // create the native <-> local maps + Index localIdx = 0; + for (unsigned nativeIdx = 0; nativeIdx < numNative_;) { + if (!blackList_.hasIndex(static_cast(nativeIdx))) { + localToNativeIndices_.push_back(static_cast(nativeIdx)); + nativeToLocalIndices_.push_back(static_cast(localIdx)); + ++nativeIdx; + ++localIdx; + } + else { + nativeToLocalIndices_.push_back(-1); + ++nativeIdx; + } + } + + numLocal_ = localToNativeIndices_.size(); + } + + Index localToPeerIdx_(Index localIdx, ProcessRank peerRank) const + { + auto it = borderList_.begin(); + const auto& endIt = borderList_.end(); + for (; it != endIt; ++it) { + if (it->localIdx == localIdx && it->peerRank == peerRank) + return it->peerIdx; + } + + return -1; + } + + template + void addNonNeighborOverlapIndices_(const BCRSMatrix&, + [[maybe_unused]] SeedList& seedList, + [[maybe_unused]] BorderDistance borderDist) + { + // TODO: this probably does not work! (the matrix A is unused, but it is needed + // from a logical POV.) +#if HAVE_MPI + // first, create the buffers which will contain the number of + // border indices relevant for a neighbor peer + std::map > borderIndices; + + // get all indices in the border which have borderDist as + // their distance to the closest border of their local process + auto it = seedList.begin(); + const auto& endIt = seedList.end(); + for (; it != endIt; ++it) { + Index localIdx = nativeToLocal(it->index); + if (!isBorder(localIdx)) + continue; + BorderIndex borderHandle; + borderHandle.localIdx = localIdx; + borderHandle.peerRank = it->peerRank; + borderHandle.borderDistance = it->borderDistance; + + // add the border index to all the neighboring peers + auto neighborIt = foreignOverlapByLocalIndex_[static_cast(localIdx)].begin(); + const auto& neighborEndIt = foreignOverlapByLocalIndex_[static_cast(localIdx)].end(); + for (; neighborIt != neighborEndIt; ++neighborIt) { + if (neighborIt->second != 0) + // not a border index for the neighbor + continue; + else if (neighborIt->first == borderHandle.peerRank) + // don't communicate the indices which are owned + // by the peer to itself + continue; + + Index peerIdx = localToPeerIdx_(localIdx, neighborIt->first); + if (peerIdx < 0) + // the index is on the border, but is not on the border + // with the considered neighboring process. Ignore it! + continue; + borderHandle.peerIdx = peerIdx; + borderIndices[neighborIt->first].push_back(borderHandle); + } + } + + // now borderIndices contains the lists of indices which we + // would like to send to each neighbor. Let's create the MPI + // buffers. + std::map > numIndicesSendBufs; + std::map > indicesSendBufs; + auto peerIt = neighborPeerSet().begin(); + const auto& peerEndIt = neighborPeerSet().end(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + size_t numIndices = borderIndices[peerRank].size(); + numIndicesSendBufs[peerRank].resize(1); + numIndicesSendBufs[peerRank][0] = static_cast(numIndices); + + const auto& peerBorderIndices = borderIndices[peerRank]; + indicesSendBufs[peerRank].resize(numIndices); + + auto tmpIt = peerBorderIndices.begin(); + const auto& tmpEndIt = peerBorderIndices.end(); + size_t i = 0; + for (; tmpIt != tmpEndIt; ++tmpIt, ++i) { + indicesSendBufs[peerRank][i] = *tmpIt; + } + } + + // now, send all these nice buffers to our neighbors + peerIt = neighborPeerSet().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank neighborPeer = *peerIt; + numIndicesSendBufs[neighborPeer].send(neighborPeer); + indicesSendBufs[neighborPeer].send(neighborPeer); + } + + // receive all data from the neighbors + std::map > numIndicesRcvBufs; + std::map > indicesRcvBufs; + peerIt = neighborPeerSet().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank neighborPeer = *peerIt; + auto& numIndicesRcvBuf = numIndicesRcvBufs[neighborPeer]; + auto& indicesRcvBuf = indicesRcvBufs[neighborPeer]; + + numIndicesRcvBuf.resize(1); + numIndicesRcvBuf.receive(neighborPeer); + unsigned numIndices = numIndicesRcvBufs[neighborPeer][0]; + indicesRcvBuf.resize(numIndices); + indicesRcvBuf.receive(neighborPeer); + + // filter out all indices which are already in the peer + // processes' overlap and add them to the seed list. also + // extend the set of peer processes. + for (unsigned i = 0; i < numIndices; ++i) { + // swap the local and the peer indices, because they were + // created with the point view of the sender + std::swap(indicesRcvBuf[i].localIdx, indicesRcvBuf[i].peerIdx); + + ProcessRank peerRank = indicesRcvBuf[i].peerRank; + // Index peerIdx = indicesRcvBuf[i].peerIdx; + Index localIdx = indicesRcvBuf[i].localIdx; + + // check if the index is already in the overlap for + // the peer + const auto& distIt = foreignOverlapByLocalIndex_[static_cast(localIdx)].find(peerRank); + if (distIt != foreignOverlapByLocalIndex_[static_cast(localIdx)].end()) + continue; + + // make sure the index is not already in the seed list + bool inSeedList = false; + auto seedIt = seedList.begin(); + const auto& seedEndIt = seedList.end(); + for (; seedIt != seedEndIt; ++seedIt) { + if (seedIt->index == localIdx && seedIt->peerRank == peerRank) { + inSeedList = true; + break; + } + } + if (inSeedList) + continue; + + IndexRankDist seedEntry; + seedEntry.index = localIdx; + seedEntry.peerRank = peerRank; + seedEntry.borderDistance = borderDist; + seedList.push_back(seedEntry); + + // update the peer set + peerSet_.insert(peerRank); + } + } + + // make sure all data was send + peerIt = neighborPeerSet().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank neighborPeer = *peerIt; + numIndicesSendBufs[neighborPeer].wait(); + indicesSendBufs[neighborPeer].wait(); + } +#endif // HAVE_MPI + } + + // given a list of border indices and provided that + // borderListToSeedList_() was already called, calculate the + // master process of each local index. + void computeMasterRanks_() + { + // determine the minimum rank for all indices + masterRank_.resize(numLocal_); + for (unsigned localIdx = 0; localIdx < numLocal_; ++localIdx) { + unsigned masterRank = myRank_; + if (isBorder(static_cast(localIdx))) { + // if the local index is a border index, loop over all ranks + // for which this index is also a border index. the lowest + // rank wins! + auto it = foreignOverlapByLocalIndex_[static_cast(localIdx)].begin(); + const auto& endIt = foreignOverlapByLocalIndex_[static_cast(localIdx)].end(); + for (; it != endIt; ++it) { + if (it->second == 0) { + // if the border distance is zero, the rank with the + // minimum + masterRank = std::min(masterRank, it->first); + } + } + } + masterRank_[static_cast(localIdx)] = masterRank; + } + } + + // assuming that the foreign overlap has been created for each + // local index, this method groups the foreign overlap by peer + // process rank + void groupForeignOverlapByRank_() + { + // loop over all indices which are in the overlap of some + // process + size_t numLocal = foreignOverlapByLocalIndex_.size(); + for (unsigned localIdx = 0; localIdx < numLocal; ++localIdx) { + // loop over the list of processes for the current index + auto it = foreignOverlapByLocalIndex_[localIdx].begin(); + const auto& endIt = foreignOverlapByLocalIndex_[localIdx].end(); + size_t nRanks = foreignOverlapByLocalIndex_[localIdx].size(); + for (; it != endIt; ++it) { + IndexDistanceNpeers tmp; + tmp.index = static_cast(localIdx); + tmp.borderDistance = it->second; + tmp.numPeers = static_cast(nRanks); + foreignOverlapByRank_[it->first].push_back(tmp); + } + } + } + + // set of processes with which we have to communicate + PeerSet peerSet_; + + // set of processes which are direct neighbors of us + PeerSet neighborPeerSet_; + + // the list of indices on the border + const BorderList& borderList_; + + // the set of indices which should not be considered + const BlackList& blackList_; + + // local indices are the native indices sans the black listed ones + std::vector nativeToLocalIndices_; + std::vector localToNativeIndices_; + + // an array which contains the rank of the master process for each + // index + std::vector masterRank_; + + // set of all local indices which are on the border of some remote + // process + std::set localBorderIndices_; + + // stores the set of process ranks which are in the overlap for a + // given row index "owned" by the current rank. The second value + // store the distance from the nearest process border. + OverlapByIndex foreignOverlapByLocalIndex_; + + // stores a list of foreign overlap indices for each rank + OverlapByRank foreignOverlapByRank_; + + // size of the overlap region + unsigned overlapSize_; + + // number of local indices + size_t numLocal_; + + // number of native indices + size_t numNative_; + + // the MPI rank of the local process + ProcessRank myRank_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/globalindices.hh b/opm/simulators/linalg/globalindices.hh new file mode 100644 index 00000000000..ea3ce881f86 --- /dev/null +++ b/opm/simulators/linalg/globalindices.hh @@ -0,0 +1,349 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::GlobalIndices + */ +#ifndef EWOMS_GLOBAL_INDICES_HH +#define EWOMS_GLOBAL_INDICES_HH + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if HAVE_MPI +#include +#endif + +#include "overlaptypes.hh" + +namespace Opm { +namespace Linear { +/*! + * \brief This class maps domestic row indices to and from "global" + * indices which is used to construct an algebraic overlap + * for the parallel linear solvers. + */ +template +class GlobalIndices +{ + GlobalIndices(const GlobalIndices& ) = delete; + + using GlobalToDomesticMap = std::map; + using DomesticToGlobalMap = std::map; + +public: + GlobalIndices(const ForeignOverlap& foreignOverlap) + : foreignOverlap_(foreignOverlap) + { + myRank_ = 0; + mpiSize_ = 1; + +#if HAVE_MPI + { + int tmp; + MPI_Comm_rank(MPI_COMM_WORLD, &tmp); + myRank_ = static_cast(tmp); + MPI_Comm_size(MPI_COMM_WORLD, &tmp); + mpiSize_ = static_cast(tmp); + } +#endif + + // calculate the domestic overlap (i.e. all overlap indices in + // foreign processes which the current process overlaps.) + // This requires communication via MPI. + buildGlobalIndices_(); + } + + /*! + * \brief Converts a domestic index to a global one. + */ + Index domesticToGlobal(Index domesticIdx) const + { + assert(domesticToGlobal_.find(domesticIdx) != domesticToGlobal_.end()); + + return domesticToGlobal_.find(domesticIdx)->second; + } + + /*! + * \brief Converts a global index to a domestic one. + */ + Index globalToDomestic(Index globalIdx) const + { + const auto& tmp = globalToDomestic_.find(globalIdx); + + if (tmp == globalToDomestic_.end()) + return -1; + + return tmp->second; + } + + /*! + * \brief Returns the number of indices which are in the interior or + * on the border of the current rank. + */ + size_t numLocal() const + { return foreignOverlap_.numLocal(); } + + /*! + * \brief Returns the number domestic indices. + * + * The domestic indices are defined as the process' local indices + * plus its copies of indices in the overlap regions + */ + size_t numDomestic() const + { return numDomestic_; } + + /*! + * \brief Add an index to the domestic<->global mapping. + */ + void addIndex(Index domesticIdx, Index globalIdx) + { + domesticToGlobal_[domesticIdx] = globalIdx; + globalToDomestic_[globalIdx] = domesticIdx; + numDomestic_ = domesticToGlobal_.size(); + + assert(domesticToGlobal_.size() == globalToDomestic_.size()); + } + + /*! + * \brief Send a border index to a remote process. + */ + void sendBorderIndex([[maybe_unused]] ProcessRank peerRank, + [[maybe_unused]] Index domesticIdx, + [[maybe_unused]] Index peerLocalIdx) + { +#if HAVE_MPI + PeerIndexGlobalIndex sendBuf; + sendBuf.peerIdx = peerLocalIdx; + sendBuf.globalIdx = domesticToGlobal(domesticIdx); + MPI_Send(&sendBuf, // buff + sizeof(PeerIndexGlobalIndex), // count + MPI_BYTE, // data type + static_cast(peerRank), // peer process + 0, // tag + MPI_COMM_WORLD); // communicator +#endif + } + + /*! + * \brief Receive an index on the border from a remote + * process and add it the translation maps. + */ + void receiveBorderIndex([[maybe_unused]] ProcessRank peerRank) + { +#if HAVE_MPI + PeerIndexGlobalIndex recvBuf; + MPI_Recv(&recvBuf, // buff + sizeof(PeerIndexGlobalIndex), // count + MPI_BYTE, // data type + static_cast(peerRank), // peer process + 0, // tag + MPI_COMM_WORLD, // communicator + MPI_STATUS_IGNORE); // status + + Index domesticIdx = foreignOverlap_.nativeToLocal(recvBuf.peerIdx); + if (domesticIdx >= 0) { + Index globalIdx = recvBuf.globalIdx; + addIndex(domesticIdx, globalIdx); + } +#endif // HAVE_MPI + } + + /*! + * \brief Return true iff a given global index already exists + */ + bool hasGlobalIndex(Index globalIdx) const + { return globalToDomestic_.find(globalIdx) != globalToDomestic_.end(); } + + /*! + * \brief Prints the global indices of all domestic indices + * for debugging purposes. + */ + void print() const + { + std::cout << "(domestic index, global index, domestic->global->domestic)" + << " list for rank " << myRank_ << "\n"; + + for (size_t domIdx = 0; domIdx < domesticToGlobal_.size(); ++domIdx) + std::cout << "(" << domIdx << ", " << domesticToGlobal(domIdx) + << ", " << globalToDomestic(domesticToGlobal(domIdx)) << ") "; + std::cout << "\n" << std::flush; + } + +protected: + // retrieve the offset for the indices where we are master in the + // global index list + void buildGlobalIndices_() + { +#if HAVE_MPI + numDomestic_ = 0; +#else + numDomestic_ = foreignOverlap_.numLocal(); +#endif + +#if HAVE_MPI + if (myRank_ == 0) { + // the first rank starts at index zero + domesticOffset_ = 0; + } + else { + // all other ranks retrieve their offset from the next + // lower rank + MPI_Recv(&domesticOffset_, // buffer + 1, // count + MPI_INT, // data type + static_cast(myRank_ - 1), // peer rank + 0, // tag + MPI_COMM_WORLD, // communicator + MPI_STATUS_IGNORE); + } + + // create maps for all indices for which the current process + // is the master + int numMaster = 0; + for (unsigned i = 0; i < foreignOverlap_.numLocal(); ++i) { + if (!foreignOverlap_.iAmMasterOf(static_cast(i))) + continue; + + addIndex(static_cast(i), + static_cast(domesticOffset_ + numMaster)); + ++numMaster; + } + + if (myRank_ < mpiSize_ - 1) { + // send the domestic offset plus the number of master + // indices to the process which is one rank higher + int tmp = domesticOffset_ + numMaster; + MPI_Send(&tmp, // buff + 1, // count + MPI_INT, // data type + static_cast(myRank_ + 1), // peer rank + 0, // tag + MPI_COMM_WORLD); // communicator + } + + typename PeerSet::const_iterator peerIt; + typename PeerSet::const_iterator peerEndIt = peerSet_().end(); + // receive the border indices from the lower ranks + peerIt = peerSet_().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + if (*peerIt < myRank_) + receiveBorderFrom_(*peerIt); + } + + // send the border indices to the higher ranks + peerIt = peerSet_().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + if (*peerIt > myRank_) + sendBorderTo_(*peerIt); + } + + // receive the border indices from the higher ranks + peerIt = peerSet_().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + if (*peerIt > myRank_) + receiveBorderFrom_(*peerIt); + } + + // send the border indices to the lower ranks + peerIt = peerSet_().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + if (*peerIt < myRank_) + sendBorderTo_(*peerIt); + } +#endif // HAVE_MPI + } + + void sendBorderTo_([[maybe_unused]] ProcessRank peerRank) + { +#if HAVE_MPI + // send (local index on myRank, global index) pairs to the + // peers + BorderList::const_iterator borderIt = borderList_().begin(); + BorderList::const_iterator borderEndIt = borderList_().end(); + for (; borderIt != borderEndIt; ++borderIt) { + ProcessRank borderPeer = borderIt->peerRank; + BorderDistance borderDistance = borderIt->borderDistance; + if (borderPeer != peerRank || borderDistance != 0) + continue; + + Index localIdx = foreignOverlap_.nativeToLocal(borderIt->localIdx); + Index peerIdx = borderIt->peerIdx; + assert(localIdx >= 0); + if (foreignOverlap_.iAmMasterOf(localIdx)) { + sendBorderIndex(borderPeer, localIdx, peerIdx); + } + } +#endif // HAVE_MPI + } + + void receiveBorderFrom_([[maybe_unused]] ProcessRank peerRank) + { +#if HAVE_MPI + // retrieve the global indices for which we are not master + // from the processes with lower rank + BorderList::const_iterator borderIt = borderList_().begin(); + BorderList::const_iterator borderEndIt = borderList_().end(); + for (; borderIt != borderEndIt; ++borderIt) { + ProcessRank borderPeer = borderIt->peerRank; + BorderDistance borderDistance = borderIt->borderDistance; + if (borderPeer != peerRank || borderDistance != 0) + continue; + + Index nativeIdx = borderIt->localIdx; + Index localIdx = foreignOverlap_.nativeToLocal(nativeIdx); + if (localIdx >= 0 && foreignOverlap_.masterRank(localIdx) == borderPeer) + receiveBorderIndex(borderPeer); + } +#endif // HAVE_MPI + } + + const PeerSet& peerSet_() const + { return foreignOverlap_.peerSet(); } + + const BorderList& borderList_() const + { return foreignOverlap_.borderList(); } + + ProcessRank myRank_; + size_t mpiSize_; + + int domesticOffset_; + size_t numDomestic_; + const ForeignOverlap& foreignOverlap_; + + GlobalToDomesticMap globalToDomestic_; + DomesticToGlobalMap domesticToGlobal_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/ilufirstelement.hh b/opm/simulators/linalg/ilufirstelement.hh new file mode 100644 index 00000000000..f343ca89a2b --- /dev/null +++ b/opm/simulators/linalg/ilufirstelement.hh @@ -0,0 +1,41 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ + +#ifndef EWOMS_ILU_FIRSTELEMENT_HH +#define EWOMS_ILU_FIRSTELEMENT_HH +#include +#include + +namespace Opm +{ +template +#if DUNE_VERSION_GTE(DUNE_GRID, 2, 8) +K& firstMatrixElement(MatrixBlock& A) +#else +K& firstmatrixelement(MatrixBlock& A) +#endif +{ return A[0][0]; } +} +#endif // EWOMS_ILU_FIRSTELEMENT_HH + + diff --git a/opm/simulators/linalg/istlpreconditionerwrappers.hh b/opm/simulators/linalg/istlpreconditionerwrappers.hh new file mode 100644 index 00000000000..3a0a5d45685 --- /dev/null +++ b/opm/simulators/linalg/istlpreconditionerwrappers.hh @@ -0,0 +1,195 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \brief Provides wrapper classes for the (non-AMG) preconditioners provided by + * dune-istl. + * + * In conjunction with a suitable solver backend, preconditioner wrappers work by + * specifying the "PreconditionerWrapper" property: + * \code + * template + * struct PreconditionerWrapper + * { using type = Opm::Linear::PreconditionerWrapper$PRECONDITIONER; }; + * \endcode + * + * Where the choices possible for '\c $PRECONDITIONER' are: + * - \c Jacobi: A Jacobi preconditioner + * - \c GaussSeidel: A Gauss-Seidel preconditioner + * - \c SSOR: A symmetric successive overrelaxation (SSOR) preconditioner + * - \c SOR: A successive overrelaxation (SOR) preconditioner + * - \c ILUn: An ILU(n) preconditioner + * - \c ILU0: A specialized (and optimized) ILU(0) preconditioner + */ +#ifndef EWOMS_ISTL_PRECONDITIONER_WRAPPERS_HH +#define EWOMS_ISTL_PRECONDITIONER_WRAPPERS_HH + +#include + +#include +#include + +#include +#include + +#include // definitions needed in next header +#include + +namespace Opm { +namespace Linear { +#define EWOMS_WRAP_ISTL_PRECONDITIONER(PREC_NAME, ISTL_PREC_TYPE) \ + template \ + class PreconditionerWrapper##PREC_NAME \ + { \ + using Scalar = GetPropType; \ + using SparseMatrixAdapter = GetPropType; \ + using IstlMatrix = typename SparseMatrixAdapter::IstlMatrix; \ + using OverlappingVector = GetPropType; \ + \ + public: \ + using SequentialPreconditioner = ISTL_PREC_TYPE; \ + PreconditionerWrapper##PREC_NAME() \ + {} \ + \ + static void registerParameters() \ + { \ + Parameters::Register \ + ("The order of the preconditioner"); \ + Parameters::Register> \ + ("The relaxation factor of the preconditioner"); \ + } \ + \ + void prepare(IstlMatrix& matrix) \ + { \ + int order = Parameters::Get(); \ + Scalar relaxationFactor = Parameters::Get>(); \ + seqPreCond_ = new SequentialPreconditioner(matrix, order, \ + relaxationFactor); \ + } \ + \ + SequentialPreconditioner& get() \ + { return *seqPreCond_; } \ + \ + void cleanup() \ + { delete seqPreCond_; } \ + \ + private: \ + SequentialPreconditioner *seqPreCond_; \ + }; + +// the same as the EWOMS_WRAP_ISTL_PRECONDITIONER macro, but without +// an 'order' argument for the preconditioner's constructor +#define EWOMS_WRAP_ISTL_SIMPLE_PRECONDITIONER(PREC_NAME, ISTL_PREC_TYPE) \ + template \ + class PreconditionerWrapper##PREC_NAME \ + { \ + using Scalar = GetPropType; \ + using OverlappingMatrix = GetPropType; \ + using OverlappingVector = GetPropType; \ + \ + public: \ + using SequentialPreconditioner = ISTL_PREC_TYPE; \ + PreconditionerWrapper##PREC_NAME() \ + {} \ + \ + static void registerParameters() \ + { \ + Parameters::Register> \ + ("The relaxation factor of the preconditioner"); \ + } \ + \ + void prepare(OverlappingMatrix& matrix) \ + { \ + Scalar relaxationFactor = \ + Parameters::Get>();\ + seqPreCond_ = new SequentialPreconditioner(matrix, \ + relaxationFactor); \ + } \ + \ + SequentialPreconditioner& get() \ + { return *seqPreCond_; } \ + \ + void cleanup() \ + { delete seqPreCond_; } \ + \ + private: \ + SequentialPreconditioner *seqPreCond_; \ + }; + +EWOMS_WRAP_ISTL_PRECONDITIONER(Jacobi, Dune::SeqJac) +// EWOMS_WRAP_ISTL_PRECONDITIONER(Richardson, Dune::Richardson) +EWOMS_WRAP_ISTL_PRECONDITIONER(GaussSeidel, Dune::SeqGS) +EWOMS_WRAP_ISTL_PRECONDITIONER(SOR, Dune::SeqSOR) +EWOMS_WRAP_ISTL_PRECONDITIONER(SSOR, Dune::SeqSSOR) + +// we need a custom preconditioner wrapper for ILU because the Dune::SeqILU class uses a +// non-standard extra template parameter to specify its order. +template +class PreconditionerWrapperILU +{ + using Scalar = GetPropType; + using OverlappingMatrix = GetPropType; + using OverlappingVector = GetPropType; + + static constexpr int order = 0; + +public: + using SequentialPreconditioner = Dune::SeqILU; + + PreconditionerWrapperILU() + {} + + static void registerParameters() + { + Parameters::Register> + ("The relaxation factor of the preconditioner"); + Parameters::Register + ("The order of the preconditioner"); + } + + void prepare(OverlappingMatrix& matrix) + { + Scalar relaxationFactor = Parameters::Get>(); + + // create the sequential preconditioner. + seqPreCond_ = new SequentialPreconditioner(matrix, relaxationFactor); + } + + SequentialPreconditioner& get() + { return *seqPreCond_; } + + void cleanup() + { delete seqPreCond_; } + +private: + SequentialPreconditioner *seqPreCond_; +}; + +#undef EWOMS_WRAP_ISTL_PRECONDITIONER +}} // namespace Linear, Opm + +#endif diff --git a/opm/simulators/linalg/istlsolverwrappers.hh b/opm/simulators/linalg/istlsolverwrappers.hh new file mode 100644 index 00000000000..8d1146ad897 --- /dev/null +++ b/opm/simulators/linalg/istlsolverwrappers.hh @@ -0,0 +1,166 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \brief Provides wrapper classes for the iterative linear solvers available in + * dune-istl. + * + * In conjunction with a suitable solver backend, solver wrappers work by specifying the + * "SolverWrapper" property: + * \code + * template + * struct LinearSolverWrapper + * { using type = Opm::Linear::SolverWrapper$SOLVER; }; + * \endcode + * + * The possible choices for '\c $SOLVER' are: + * - \c Richardson: A fixpoint solver using the Richardson iteration + * - \c SteepestDescent: The steepest descent solver + * - \c ConjugatedGradients: A conjugated gradients solver + * - \c BiCGStab: A stabilized bi-conjugated gradients solver + * - \c MinRes: A solver based on the minimized residual algorithm + * - \c RestartedGMRes: A restarted GMRES solver + */ +#ifndef EWOMS_ISTL_SOLVER_WRAPPERS_HH +#define EWOMS_ISTL_SOLVER_WRAPPERS_HH + +#include + +#include +#include + +#include +#include + +namespace Opm::Linear { + +/*! + * \brief Macro to create a wrapper around an ISTL solver + */ +#define EWOMS_WRAP_ISTL_SOLVER(SOLVER_NAME, ISTL_SOLVER_NAME) \ + template \ + class SolverWrapper##SOLVER_NAME \ + { \ + using Scalar = GetPropType; \ + using OverlappingMatrix = GetPropType; \ + using OverlappingVector = GetPropType; \ + \ + public: \ + using RawSolver = ISTL_SOLVER_NAME; \ + \ + SolverWrapper##SOLVER_NAME() \ + {} \ + \ + static void registerParameters() \ + {} \ + \ + template \ + std::shared_ptr get(LinearOperator& parOperator, \ + ScalarProduct& parScalarProduct, \ + Preconditioner& parPreCond) \ + { \ + Scalar tolerance = Parameters::Get>(); \ + int maxIter = Parameters::Get();\ + \ + int verbosity = 0; \ + if (parOperator.overlap().myRank() == 0) \ + verbosity = Parameters::Get(); \ + solver_ = std::make_shared(parOperator, parScalarProduct, \ + parPreCond, tolerance, maxIter, \ + verbosity); \ + \ + return solver_; \ + } \ + \ + void cleanup() \ + { solver_.reset(); } \ + \ + private: \ + std::shared_ptr solver_; \ + }; + +EWOMS_WRAP_ISTL_SOLVER(Richardson, Dune::LoopSolver) +EWOMS_WRAP_ISTL_SOLVER(SteepestDescent, Dune::GradientSolver) +EWOMS_WRAP_ISTL_SOLVER(ConjugatedGradients, Dune::CGSolver) +EWOMS_WRAP_ISTL_SOLVER(BiCGStab, Dune::BiCGSTABSolver) +EWOMS_WRAP_ISTL_SOLVER(MinRes, Dune::MINRESSolver) + +/*! + * \brief Solver wrapper for the restarted GMRES solver of dune-istl. + * + * dune-istl uses a slightly different API for this solver than for the others... + */ +template +class SolverWrapperRestartedGMRes +{ + using Scalar = GetPropType; + using OverlappingMatrix = GetPropType; + using OverlappingVector = GetPropType; + +public: + using RawSolver = Dune::RestartedGMResSolver; + + SolverWrapperRestartedGMRes() + {} + + static void registerParameters() + { + Parameters::Register + ("Number of iterations after which the GMRES linear solver is restarted"); + } + + template + std::shared_ptr get(LinearOperator& parOperator, + ScalarProduct& parScalarProduct, + Preconditioner& parPreCond) + { + Scalar tolerance = Parameters::Get>(); + int maxIter = Parameters::Get(); + + int verbosity = 0; + if (parOperator.overlap().myRank() == 0) + verbosity = Parameters::Get(); + int restartAfter = Parameters::Get(); + solver_ = std::make_shared(parOperator, + parScalarProduct, + parPreCond, + tolerance, + restartAfter, + maxIter, + verbosity); + + return solver_; + } + + void cleanup() + { solver_.reset(); } + +private: + std::shared_ptr solver_; +}; + +#undef EWOMS_WRAP_ISTL_SOLVER + +} // namespace Opm::Linear + +#endif diff --git a/opm/simulators/linalg/istlsparsematrixadapter.hh b/opm/simulators/linalg/istlsparsematrixadapter.hh new file mode 100644 index 00000000000..a2b8a44d06c --- /dev/null +++ b/opm/simulators/linalg/istlsparsematrixadapter.hh @@ -0,0 +1,201 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::IstlSparseMatrixAdapter + */ +#ifndef EWOMS_ISTL_SPARSE_MATRIX_ADAPTER_HH +#define EWOMS_ISTL_SPARSE_MATRIX_ADAPTER_HH + +#include +#include +#include + +namespace Opm { +namespace Linear { + +/*! + * \ingroup Linear + * \brief A sparse matrix interface backend for BCRSMatrix from dune-istl. + */ +template > +class IstlSparseMatrixAdapter +{ +public: + //! \brief Implementation of matrix + using IstlMatrix = Dune::BCRSMatrix; + + //! \brief block type forming the matrix entries + using MatrixBlock = typename IstlMatrix::block_type; + static_assert(std::is_same::value, + "IstlMatrix::block_type and MatrixBlockType must be identical"); + + //! \brief type of scalar + using Scalar = typename MatrixBlock::field_type; + + /*! + * \brief Constructor creating an empty matrix. + */ + IstlSparseMatrixAdapter(const size_t rows, const size_t columns) + : rows_(rows) + , columns_(columns) + , istlMatrix_() + {} + + /*! + * \brief Constructor taking simulator and creating an empty matrix . + */ + template + IstlSparseMatrixAdapter(const Simulator& simulator) + : IstlSparseMatrixAdapter(simulator.model().numTotalDof(), simulator.model().numTotalDof()) + {} + + /*! + * \brief Allocate matrix structure give a sparsity pattern. + */ + template + void reserve(const std::vector& sparsityPattern) + { + // allocate raw matrix + istlMatrix_.reset(new IstlMatrix(rows_, columns_, IstlMatrix::random)); + + // make sure sparsityPattern is consistent with number of rows + assert(rows_ == sparsityPattern.size()); + + // allocate space for the rows of the matrix + for (size_t dofIdx = 0; dofIdx < rows_; ++ dofIdx) + istlMatrix_->setrowsize(dofIdx, sparsityPattern[dofIdx].size()); + + istlMatrix_->endrowsizes(); + + // fill the rows with indices. each degree of freedom talks to + // all of its neighbors. (it also talks to itself since + // degrees of freedom are sometimes quite egocentric.) + for (size_t dofIdx = 0; dofIdx < rows_; ++ dofIdx) { + auto nIt = sparsityPattern[dofIdx].begin(); + auto nEndIt = sparsityPattern[dofIdx].end(); + for (; nIt != nEndIt; ++nIt) + istlMatrix_->addindex(dofIdx, *nIt); + } + istlMatrix_->endindices(); + } + + /*! + * \brief Return constant reference to matrix implementation. + */ + IstlMatrix& istlMatrix() + { return *istlMatrix_; } + const IstlMatrix& istlMatrix() const + { return *istlMatrix_; } + + /*! + * \brief Return number of rows of the matrix. + */ + size_t rows() const + { return rows_; } + + /*! + * \brief Return number of columns of the matrix. + */ + size_t cols() const + { return columns_; } + + /*! + * \brief Set all matrix entries to zero. + */ + void clear() + { (*istlMatrix_) = Scalar(0.0); } + + /*! + * \brief Set given row to zero except for the main-diagonal entry (if it exists). + * + * If the sparsity pattern of the matrix features an explicit block on the main + * diagonal, the diagonal on that block is set to the second agument of the function. + */ + void clearRow(const size_t row, const Scalar diag = 1.0) + { + MatrixBlock diagBlock(Scalar(0)); + for (int i = 0; i < diagBlock.rows; ++i) + diagBlock[i][i] = diag; + + auto& matRow = (*istlMatrix_)[row]; + auto colIt = matRow.begin(); + const auto& colEndIt = matRow.end(); + for (; colIt != colEndIt; ++colIt) { + if (colIt.index() == row) + *colIt = diagBlock; + else + *colIt = Scalar(0.0); + } + } + + /*! + * \brief Fill given block with entries stored in the matrix. + */ + void block(const size_t rowIdx, const size_t colIdx, MatrixBlock& value) const + { value = (*istlMatrix_)[rowIdx][colIdx]; } + + MatrixBlockType* blockAddress(const size_t rowIdx, const size_t colIdx) const + { return &(*istlMatrix_)[rowIdx][colIdx]; } + + + /*! + * \brief Set matrix block to given block. + */ + void setBlock(const size_t rowIdx, const size_t colIdx, const MatrixBlock& value) + { (*istlMatrix_)[rowIdx][colIdx] = value; } + + /*! + * \brief Add block to matrix block. + */ + void addToBlock(const size_t rowIdx, const size_t colIdx, const MatrixBlock& value) + { (*istlMatrix_)[rowIdx][colIdx] += value; } + + /*! + * \brief Commit matrix from local caches into matrix native structure. + * + * For the ISTL adapter this is unnecessary because there is no caching mechanism. + */ + void commit() + { } + + /*! + * \brief Finish modifying the matrix, i.e., convert the data structure from one + * tailored for linearization to one aimed at the linear solver. + * + * This may compress the matrix if the build mode is implicit. For the ISTL adapter + * this is not required. + */ + void finalize() + { } + +protected: + size_t rows_; + size_t columns_; + + std::unique_ptr istlMatrix_; +}; + +}} // namespace Linear, Opm + +#endif diff --git a/opm/simulators/linalg/linalgparameters.hh b/opm/simulators/linalg/linalgparameters.hh new file mode 100644 index 00000000000..bfa3985db71 --- /dev/null +++ b/opm/simulators/linalg/linalgparameters.hh @@ -0,0 +1,82 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup BlackOilModel + * + * \brief Declares the parameters for the black oil model. + */ +#ifndef EWOMS_LINALG_PARAMETERS_HH +#define EWOMS_LINALG_PARAMETERS_HH + +namespace Opm::Parameters { + +//! number of iterations between solver restarts for the GMRES solver +struct GMResRestart { static constexpr int value = 10; }; + +/*! + * \brief Maximum accepted error of the norm of the residual. + */ +template +struct LinearSolverAbsTolerance { static constexpr Scalar value = -1.0; }; + +template +struct LinearSolverMaxError { static constexpr Scalar value = 1e7; }; + +//! Maximum number of iterations eyecuted by the linear solver +struct LinearSolverMaxIterations { static constexpr int value = 1000; }; + +/*! + * \brief The size of the algebraic overlap of the linear solver. + * + * Algebraic overlaps can be thought as being the same as the overlap + * of a grid, but it is only existant for the linear system of + * equations. + */ +struct LinearSolverOverlapSize { static constexpr unsigned value = 2; }; + +/*! + * \brief Maximum accepted error of the solution of the linear solver. + */ +template +struct LinearSolverTolerance { static constexpr Scalar value = 1e-3; }; + +/*! + * \brief Specifies the verbosity of the linear solver + * + * By default it is 0, i.e. it doesn't print anything. Setting this + * property to 1 prints aggregated convergence rates, 2 prints the + * convergence rate of every iteration of the scheme. + */ +struct LinearSolverVerbosity { static constexpr int value = 0; }; + +//! The order of the sequential preconditioner +struct PreconditionerOrder { static constexpr int value = 0; }; + +//! The relaxation factor of the preconditioner +template +struct PreconditionerRelaxation { static constexpr Scalar value = 1.0; }; + +} // namespace Opm::Parameters + +#endif diff --git a/opm/simulators/linalg/linalgproperties.hh b/opm/simulators/linalg/linalgproperties.hh new file mode 100644 index 00000000000..cd63c702406 --- /dev/null +++ b/opm/simulators/linalg/linalgproperties.hh @@ -0,0 +1,76 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \ingroup BlackOilModel + * + * \brief Declares the properties required by the black oil model. + */ +#ifndef EWOMS_LINALG_PROPERTIES_HH +#define EWOMS_LINALG_PROPERTIES_HH + +#include + +namespace Opm::Properties { + +//! The type of the linear solver to be used +template +struct LinearSolverBackend { using type = UndefinedProperty; }; + +//! the preconditioner used by the linear solver +template +struct PreconditionerWrapper { using type = UndefinedProperty; }; + +//! The floating point type used internally by the linear solver +template +struct LinearSolverScalar { using type = UndefinedProperty; }; + +//! The class that allows to manipulate sparse matrices +template +struct SparseMatrixAdapter { using type = UndefinedProperty; }; + +//! Vector containing a quantity of for equation for each DOF of the whole grid +template +struct GlobalEqVector { using type = UndefinedProperty; }; + +template +struct LinearSolverWrapper { using type = UndefinedProperty; }; + +template +struct Overlap { using type = UndefinedProperty; }; + +template +struct OverlappingLinearOperator { using type = UndefinedProperty; }; + +template +struct OverlappingMatrix { using type = UndefinedProperty; }; + +template +struct OverlappingScalarProduct { using type = UndefinedProperty; }; + +template +struct OverlappingVector { using type = UndefinedProperty; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/simulators/linalg/linearsolverreport.hh b/opm/simulators/linalg/linearsolverreport.hh new file mode 100644 index 00000000000..63b10147c74 --- /dev/null +++ b/opm/simulators/linalg/linearsolverreport.hh @@ -0,0 +1,83 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::SolverReport + */ +#ifndef EWOMS_LINEAR_SOLVER_REPORT_HH +#define EWOMS_LINEAR_SOLVER_REPORT_HH + +#include "convergencecriterion.hh" + +#include +#include + +namespace Opm { +namespace Linear { + +/*! + * \brief Collects summary information about the execution of the linear solver. + */ +class SolverReport +{ +public: + SolverReport() + { reset(); } + + void reset() + { + timer_.halt(); + iterations_ = 0; + converged_ = 0; + } + + const Opm::Timer& timer() const + { return timer_; } + + Opm::Timer& timer() + { return timer_; } + + unsigned iterations() const + { return iterations_; } + + void increment() + { ++iterations_; } + + SolverReport& operator++() + { ++iterations_; return *this; } + + bool converged() const + { return converged_; } + + void setConverged(bool value) + { converged_ = value; } + +private: + Opm::Timer timer_; + unsigned iterations_; + bool converged_; +}; + +}} // end namespace Linear, Opm + +#endif diff --git a/opm/simulators/linalg/matrixblock.hh b/opm/simulators/linalg/matrixblock.hh new file mode 100644 index 00000000000..1f84bcfe011 --- /dev/null +++ b/opm/simulators/linalg/matrixblock.hh @@ -0,0 +1,320 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef EWOMS_MATRIX_BLOCK_HH +#define EWOMS_MATRIX_BLOCK_HH + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace Opm { +namespace detail { + +template +static inline void invertMatrix(Dune::FieldMatrix& matrix) +{ + matrix.invert(); +} + +template +static inline void invertMatrix(Dune::FieldMatrix& matrix) +{ + Dune::FieldMatrix tmp(matrix); + Dune::FMatrixHelp::invertMatrix(tmp,matrix); +} + +template +static inline void invertMatrix(Dune::FieldMatrix& matrix) +{ + Dune::FieldMatrix tmp(matrix); + Dune::FMatrixHelp::invertMatrix(tmp,matrix); +} + +template +static inline void invertMatrix(Dune::FieldMatrix& matrix) +{ + Dune::FieldMatrix tmp(matrix); + Dune::FMatrixHelp::invertMatrix(tmp,matrix); +} + +//! invert 4x4 Matrix without changing the original matrix +template class Matrix, typename K> +static inline K invertMatrix4(const Matrix& matrix, Matrix& inverse) +{ + inverse[0][0] = matrix[1][1] * matrix[2][2] * matrix[3][3] - + matrix[1][1] * matrix[2][3] * matrix[3][2] - + matrix[2][1] * matrix[1][2] * matrix[3][3] + + matrix[2][1] * matrix[1][3] * matrix[3][2] + + matrix[3][1] * matrix[1][2] * matrix[2][3] - + matrix[3][1] * matrix[1][3] * matrix[2][2]; + + inverse[1][0] = -matrix[1][0] * matrix[2][2] * matrix[3][3] + + matrix[1][0] * matrix[2][3] * matrix[3][2] + + matrix[2][0] * matrix[1][2] * matrix[3][3] - + matrix[2][0] * matrix[1][3] * matrix[3][2] - + matrix[3][0] * matrix[1][2] * matrix[2][3] + + matrix[3][0] * matrix[1][3] * matrix[2][2]; + + inverse[2][0] = matrix[1][0] * matrix[2][1] * matrix[3][3] - + matrix[1][0] * matrix[2][3] * matrix[3][1] - + matrix[2][0] * matrix[1][1] * matrix[3][3] + + matrix[2][0] * matrix[1][3] * matrix[3][1] + + matrix[3][0] * matrix[1][1] * matrix[2][3] - + matrix[3][0] * matrix[1][3] * matrix[2][1]; + + inverse[3][0] = -matrix[1][0] * matrix[2][1] * matrix[3][2] + + matrix[1][0] * matrix[2][2] * matrix[3][1] + + matrix[2][0] * matrix[1][1] * matrix[3][2] - + matrix[2][0] * matrix[1][2] * matrix[3][1] - + matrix[3][0] * matrix[1][1] * matrix[2][2] + + matrix[3][0] * matrix[1][2] * matrix[2][1]; + + inverse[0][1]= -matrix[0][1] * matrix[2][2] * matrix[3][3] + + matrix[0][1] * matrix[2][3] * matrix[3][2] + + matrix[2][1] * matrix[0][2] * matrix[3][3] - + matrix[2][1] * matrix[0][3] * matrix[3][2] - + matrix[3][1] * matrix[0][2] * matrix[2][3] + + matrix[3][1] * matrix[0][3] * matrix[2][2]; + + inverse[1][1] = matrix[0][0] * matrix[2][2] * matrix[3][3] - + matrix[0][0] * matrix[2][3] * matrix[3][2] - + matrix[2][0] * matrix[0][2] * matrix[3][3] + + matrix[2][0] * matrix[0][3] * matrix[3][2] + + matrix[3][0] * matrix[0][2] * matrix[2][3] - + matrix[3][0] * matrix[0][3] * matrix[2][2]; + + inverse[2][1] = -matrix[0][0] * matrix[2][1] * matrix[3][3] + + matrix[0][0] * matrix[2][3] * matrix[3][1] + + matrix[2][0] * matrix[0][1] * matrix[3][3] - + matrix[2][0] * matrix[0][3] * matrix[3][1] - + matrix[3][0] * matrix[0][1] * matrix[2][3] + + matrix[3][0] * matrix[0][3] * matrix[2][1]; + + inverse[3][1] = matrix[0][0] * matrix[2][1] * matrix[3][2] - + matrix[0][0] * matrix[2][2] * matrix[3][1] - + matrix[2][0] * matrix[0][1] * matrix[3][2] + + matrix[2][0] * matrix[0][2] * matrix[3][1] + + matrix[3][0] * matrix[0][1] * matrix[2][2] - + matrix[3][0] * matrix[0][2] * matrix[2][1]; + + inverse[0][2] = matrix[0][1] * matrix[1][2] * matrix[3][3] - + matrix[0][1] * matrix[1][3] * matrix[3][2] - + matrix[1][1] * matrix[0][2] * matrix[3][3] + + matrix[1][1] * matrix[0][3] * matrix[3][2] + + matrix[3][1] * matrix[0][2] * matrix[1][3] - + matrix[3][1] * matrix[0][3] * matrix[1][2]; + + inverse[1][2] = -matrix[0][0] * matrix[1][2] * matrix[3][3] + + matrix[0][0] * matrix[1][3] * matrix[3][2] + + matrix[1][0] * matrix[0][2] * matrix[3][3] - + matrix[1][0] * matrix[0][3] * matrix[3][2] - + matrix[3][0] * matrix[0][2] * matrix[1][3] + + matrix[3][0] * matrix[0][3] * matrix[1][2]; + + inverse[2][2] = matrix[0][0] * matrix[1][1] * matrix[3][3] - + matrix[0][0] * matrix[1][3] * matrix[3][1] - + matrix[1][0] * matrix[0][1] * matrix[3][3] + + matrix[1][0] * matrix[0][3] * matrix[3][1] + + matrix[3][0] * matrix[0][1] * matrix[1][3] - + matrix[3][0] * matrix[0][3] * matrix[1][1]; + + inverse[3][2] = -matrix[0][0] * matrix[1][1] * matrix[3][2] + + matrix[0][0] * matrix[1][2] * matrix[3][1] + + matrix[1][0] * matrix[0][1] * matrix[3][2] - + matrix[1][0] * matrix[0][2] * matrix[3][1] - + matrix[3][0] * matrix[0][1] * matrix[1][2] + + matrix[3][0] * matrix[0][2] * matrix[1][1]; + + inverse[0][3] = -matrix[0][1] * matrix[1][2] * matrix[2][3] + + matrix[0][1] * matrix[1][3] * matrix[2][2] + + matrix[1][1] * matrix[0][2] * matrix[2][3] - + matrix[1][1] * matrix[0][3] * matrix[2][2] - + matrix[2][1] * matrix[0][2] * matrix[1][3] + + matrix[2][1] * matrix[0][3] * matrix[1][2]; + + inverse[1][3] = matrix[0][0] * matrix[1][2] * matrix[2][3] - + matrix[0][0] * matrix[1][3] * matrix[2][2] - + matrix[1][0] * matrix[0][2] * matrix[2][3] + + matrix[1][0] * matrix[0][3] * matrix[2][2] + + matrix[2][0] * matrix[0][2] * matrix[1][3] - + matrix[2][0] * matrix[0][3] * matrix[1][2]; + + inverse[2][3] = -matrix[0][0] * matrix[1][1] * matrix[2][3] + + matrix[0][0] * matrix[1][3] * matrix[2][1] + + matrix[1][0] * matrix[0][1] * matrix[2][3] - + matrix[1][0] * matrix[0][3] * matrix[2][1] - + matrix[2][0] * matrix[0][1] * matrix[1][3] + + matrix[2][0] * matrix[0][3] * matrix[1][1]; + + inverse[3][3] = matrix[0][0] * matrix[1][1] * matrix[2][2] - + matrix[0][0] * matrix[1][2] * matrix[2][1] - + matrix[1][0] * matrix[0][1] * matrix[2][2] + + matrix[1][0] * matrix[0][2] * matrix[2][1] + + matrix[2][0] * matrix[0][1] * matrix[1][2] - + matrix[2][0] * matrix[0][2] * matrix[1][1]; + + K det = matrix[0][0] * inverse[0][0] + matrix[0][1] * inverse[1][0] + + matrix[0][2] * inverse[2][0] + matrix[0][3] * inverse[3][0]; + + // return identity for singular or nearly singular matrices. + if (std::abs(det) < 1e-40) { + inverse = std::numeric_limits::quiet_NaN(); + throw NumericalProblem("Singular matrix"); + } else + inverse *= 1.0 / det; + + return det; +} + +template using FMat4 = Dune::FieldMatrix; + +template +static inline void invertMatrix(Dune::FieldMatrix& matrix) +{ + FMat4 tmp(matrix); + invertMatrix4(tmp, matrix); +} + +template +static inline void invertMatrix(Dune::DynamicMatrix& matrix) +{ + // this function is only for 4 X 4 matrix + // for 4 X 4 matrix, using the invertMatrix() function above + // it is for temporary usage, mainly to reduce the huge burden of testing + // what algorithm should be used to invert 4 X 4 matrix will be handled + // as a seperate issue + if (matrix.rows() == 4) { + Dune::DynamicMatrix A = matrix; + invertMatrix4(A, matrix); + return; + } + + matrix.invert(); +} + +} // namespace detail + +template +class MatrixBlock : public Dune::FieldMatrix +{ +public: + using BaseType = Dune::FieldMatrix ; + + using BaseType::operator= ; + using BaseType::rows; + using BaseType::cols; + + MatrixBlock() + : BaseType(Scalar(0.0)) + {} + + explicit MatrixBlock(const Scalar value) + : BaseType(value) + {} + + void invert() + { detail::invertMatrix(asBase()); } + + const BaseType& asBase() const + { return static_cast(*this); } + + BaseType& asBase() + { return static_cast(*this); } +}; + +} // namespace Opm + +namespace Dune { + +template +void print_row(std::ostream& s, const Opm::MatrixBlock& A, + typename FieldMatrix::size_type I, + typename FieldMatrix::size_type J, + typename FieldMatrix::size_type therow, + int width, + int precision) +{ print_row(s, A.asBase(), I, J, therow, width, precision); } + +template +struct MatrixDimension > + : public MatrixDimension::BaseType> +{ }; + + +#if HAVE_UMFPACK +/// \brief UMFPack specialization for Opm::MatrixBlock to make AMG happy +/// +/// Without this the empty default implementation would be used. +template +class UMFPack, A> > + : public UMFPack, A> > +{ + using Base = UMFPack, A> >; + using Matrix = BCRSMatrix, A>; + +public: + using RealMatrix = BCRSMatrix, A>; + + UMFPack(const RealMatrix& matrix, int verbose, bool) + : Base(reinterpret_cast(matrix), verbose) + {} +}; +#endif + +#if HAVE_SUPERLU +/// \brief SuperLU specialization for Opm::MatrixBlock to make AMG happy +/// +/// Without this the empty default implementation would be used. +template +class SuperLU, A> > + : public SuperLU, A> > +{ + using Base = SuperLU, A> >; + using Matrix = BCRSMatrix, A>; + +public: + using RealMatrix = BCRSMatrix, A>; + + SuperLU(const RealMatrix& matrix, int verb, bool reuse=true) + : Base(reinterpret_cast(matrix), verb, reuse) + {} +}; +#endif + +template +struct IsNumber> + : public IsNumber> +{}; + +} // end namespace Dune + + +#endif diff --git a/opm/simulators/linalg/nullborderlistmanager.hh b/opm/simulators/linalg/nullborderlistmanager.hh new file mode 100644 index 00000000000..ab228d7a229 --- /dev/null +++ b/opm/simulators/linalg/nullborderlistmanager.hh @@ -0,0 +1,64 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::NullBorderListCreator + */ +#ifndef EWOMS_NULL_BORDER_LIST_MANAGER_HH +#define EWOMS_NULL_BORDER_LIST_MANAGER_HH + +#include "overlaptypes.hh" + +#include + +namespace Opm { +namespace Linear { +/*! + * \brief This is a grid manager which does not create any border list. + * + * This means that discretizations using this grid manager cannot be + * used for parallel computations! + */ +template +class NullBorderListCreator +{ +public: + NullBorderListCreator(const GridView& gridView, + const DofMapper&) + { + if (gridView.comm().size() > 1) + throw std::runtime_error("The used model is not usable for parallel computations"); + } + + // Access to the border list. + const BorderList& borderList() const + { return borderList_; } + +private: + BorderList borderList_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/overlappingbcrsmatrix.hh b/opm/simulators/linalg/overlappingbcrsmatrix.hh new file mode 100644 index 00000000000..d6f32fc3369 --- /dev/null +++ b/opm/simulators/linalg/overlappingbcrsmatrix.hh @@ -0,0 +1,692 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::OverlappingBCRSMatrix + */ +#ifndef EWOMS_OVERLAPPING_BCRS_MATRIX_HH +#define EWOMS_OVERLAPPING_BCRS_MATRIX_HH + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Opm { +namespace Linear { + +/*! + * \brief An overlap aware block-compressed row storage (BCRS) matrix. + */ +template +class OverlappingBCRSMatrix : public BCRSMatrix +{ + using ParentType = BCRSMatrix; + +public: + using Overlap = Opm::Linear::DomesticOverlapFromBCRSMatrix; + +private: + using Entries = std::vector >; + +public: + using ColIterator = typename ParentType::ColIterator; + using ConstColIterator = typename ParentType::ConstColIterator; + using block_type = typename ParentType::block_type; + using field_type = typename ParentType::field_type; + + // no real copying done at the moment + OverlappingBCRSMatrix(const OverlappingBCRSMatrix& other) + : ParentType(other) + {} + + template + OverlappingBCRSMatrix(const NativeBCRSMatrix& nativeMatrix, + const BorderList& borderList, + const BlackList& blackList, + unsigned overlapSize) + { + overlap_ = std::make_shared(nativeMatrix, borderList, blackList, overlapSize); + myRank_ = 0; +#if HAVE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &myRank_); +#endif // HAVE_MPI + + // build the overlapping matrix from the non-overlapping + // matrix and the overlap + build_(nativeMatrix); + } + + // this constructor is required to make the class compatible with the SeqILU class of + // Dune >= 2.7. + OverlappingBCRSMatrix(size_t, + size_t, + typename BCRSMatrix::BuildMode) + { throw std::logic_error("OverlappingBCRSMatrix objects cannot be build from scratch!"); } + + ~OverlappingBCRSMatrix() + { + if (overlap_.use_count() == 0) + return; + + // delete all MPI buffers + const PeerSet& peerSet = overlap_->peerSet(); + typename PeerSet::const_iterator peerIt = peerSet.begin(); + typename PeerSet::const_iterator peerEndIt = peerSet.end(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + + delete rowSizesRecvBuff_[peerRank]; + delete rowIndicesRecvBuff_[peerRank]; + delete entryColIndicesRecvBuff_[peerRank]; + delete entryValuesRecvBuff_[peerRank]; + + delete numRowsSendBuff_[peerRank]; + delete rowSizesSendBuff_[peerRank]; + delete rowIndicesSendBuff_[peerRank]; + delete entryColIndicesSendBuff_[peerRank]; + delete entryValuesSendBuff_[peerRank]; + } + } + + ParentType& asParent() + { return *this; } + + const ParentType& asParent() const + { return *this; } + + /*! + * \brief Returns the domestic overlap for the process. + */ + const Overlap& overlap() const + { return *overlap_; } + + /*! + * \brief Assign and syncronize the overlapping matrix from a non-overlapping one. + */ + void assignAdd(const ParentType& nativeMatrix) + { + // copy the native entries + assignFromNative(nativeMatrix); + + // communicate and add the contents of overlapping rows + syncAdd(); + } + + /*! + * \brief Assign and syncronize the overlapping matrix from a + * non-overlapping one. + * + * The non-master entries are copied from the master + */ + template + void assignCopy(const NativeBCRSMatrix& nativeMatrix) + { + // copy the native entries + assignFromNative(nativeMatrix); + + // communicate and add the contents of overlapping rows + syncCopy(); + } + + /*! + * \brief Set the identity matrix on the main diagonal of front indices. + */ + void resetFront() + { + // create an identity matrix + block_type idMatrix(0.0); + for (unsigned i = 0; i < idMatrix.size(); ++i) + idMatrix[i][i] = 1.0; + + int numLocal = overlap_->numLocal(); + int numDomestic = overlap_->numDomestic(); + for (int domRowIdx = numLocal; domRowIdx < numDomestic; ++domRowIdx) { + if (overlap_->isFront(domRowIdx)) { + // set the front rows to a diagonal 1 + (*this)[domRowIdx] = 0.0; + (*this)[domRowIdx][domRowIdx] = idMatrix; + } + } + } + + void print() const + { + overlap_->print(); + + for (int i = 0; i < this->N(); ++i) { + if (overlap_->isLocal(i)) + std::cout << " "; + else + std::cout << "*"; + std::cout << "row " << i << " "; + + using ColIt = typename BCRSMatrix::ConstColIterator; + ColIt colIt = (*this)[i].begin(); + ColIt colEndIt = (*this)[i].end(); + for (int j = 0; j < this->M(); ++j) { + if (colIt != colEndIt && j == colIt.index()) { + ++colIt; + if (overlap_->isBorder(j)) + std::cout << "|"; + else if (overlap_->isLocal(j)) + std::cout << "X"; + else + std::cout << "*"; + } + else + std::cout << " "; + } + std::cout << "\n" << std::flush; + } + Dune::printSparseMatrix(std::cout, + *static_cast(this), + "M", + "row"); + } + + template + void assignFromNative(const NativeBCRSMatrix& nativeMatrix) + { + // first, set everything to 0, + BCRSMatrix::operator=(0.0); + + // then copy the domestic entries of the native matrix to the overlapping matrix + for (unsigned nativeRowIdx = 0; nativeRowIdx < nativeMatrix.N(); ++nativeRowIdx) { + Index domesticRowIdx = overlap_->nativeToDomestic(static_cast(nativeRowIdx)); + if (domesticRowIdx < 0) { + continue; // row corresponds to a black-listed entry + } + + auto nativeColIt = nativeMatrix[nativeRowIdx].begin(); + const auto& nativeColEndIt = nativeMatrix[nativeRowIdx].end(); + for (; nativeColIt != nativeColEndIt; ++nativeColIt) { + Index domesticColIdx = overlap_->nativeToDomestic(static_cast(nativeColIt.index())); + + // make sure to include all off-diagonal entries, even those which belong + // to DOFs which are managed by a peer process. For this, we have to + // re-map the column index of the black-listed index to a native one. + if (domesticColIdx < 0) + domesticColIdx = overlap_->blackList().nativeToDomestic(static_cast(nativeColIt.index())); + + if (domesticColIdx < 0) + // there is no domestic index which corresponds to a black-listed + // one. this can happen if the grid overlap is larger than the + // algebraic one... + continue; + + // we need to copy the block matrices manually since it seems that (at + // least some versions of) Dune have an endless recursion bug when + // assigning dense matrices of different field type + const auto& src = *nativeColIt; + auto& dest = (*this)[static_cast(domesticRowIdx)][static_cast(domesticColIdx)]; + for (unsigned i = 0; i < src.rows; ++i) { + for (unsigned j = 0; j < src.cols; ++j) { + dest[i][j] = static_cast(src[i][j]); + } + } + } + } + } + + // communicates and adds up the contents of overlapping rows + void syncAdd() + { + // first, send all entries to the peers + const PeerSet& peerSet = overlap_->peerSet(); + typename PeerSet::const_iterator peerIt = peerSet.begin(); + typename PeerSet::const_iterator peerEndIt = peerSet.end(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + + sendEntries_(peerRank); + } + + // then, receive entries from the peers + peerIt = peerSet.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + + receiveAddEntries_(peerRank); + } + + // finally, make sure that everything which we send was + // received by the peers + peerIt = peerSet.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + entryValuesSendBuff_[peerRank]->wait(); + } + } + + // communicates and copies the contents of overlapping rows from + // the master + void syncCopy() + { + // first, send all entries to the peers + const PeerSet& peerSet = overlap_->peerSet(); + typename PeerSet::const_iterator peerIt = peerSet.begin(); + typename PeerSet::const_iterator peerEndIt = peerSet.end(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + + sendEntries_(peerRank); + } + + // then, receive entries from the peers + peerIt = peerSet.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + + receiveCopyEntries_(peerRank); + } + + // finally, make sure that everything which we send was + // received by the peers + peerIt = peerSet.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + entryValuesSendBuff_[peerRank]->wait(); + } + } + +private: + template + void build_(const NativeBCRSMatrix& nativeMatrix) + { + size_t numDomestic = overlap_->numDomestic(); + + // allocate the rows + this->setSize(numDomestic, numDomestic); + this->setBuildMode(ParentType::random); + + // communicate the entries + buildIndices_(nativeMatrix); + } + + template + void buildIndices_(const NativeBCRSMatrix& nativeMatrix) + { + ///////// + // first, add all local matrix entries + ///////// + entries_.resize(overlap_->numDomestic()); + for (unsigned nativeRowIdx = 0; nativeRowIdx < nativeMatrix.N(); ++nativeRowIdx) { + int domesticRowIdx = overlap_->nativeToDomestic(static_cast(nativeRowIdx)); + if (domesticRowIdx < 0) + continue; + + auto nativeColIt = nativeMatrix[nativeRowIdx].begin(); + const auto& nativeColEndIt = nativeMatrix[nativeRowIdx].end(); + for (; nativeColIt != nativeColEndIt; ++nativeColIt) { + int domesticColIdx = overlap_->nativeToDomestic(static_cast(nativeColIt.index())); + + // make sure to include all off-diagonal entries, even those which belong + // to DOFs which are managed by a peer process. For this, we have to + // re-map the column index of the black-listed index to a native one. + if (domesticColIdx < 0) { + domesticColIdx = overlap_->blackList().nativeToDomestic(static_cast(nativeColIt.index())); + } + + if (domesticColIdx < 0) + continue; + + entries_[static_cast(domesticRowIdx)].insert(domesticColIdx); + } + } + + ///////// + // add the indices for all additional entries + ///////// + + // first, send all our indices to all peers + const PeerSet& peerSet = overlap_->peerSet(); + typename PeerSet::const_iterator peerIt = peerSet.begin(); + typename PeerSet::const_iterator peerEndIt = peerSet.end(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + sendIndices_(nativeMatrix, peerRank); + } + + // then recieve all indices from the peers + peerIt = peerSet.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + receiveIndices_(peerRank); + } + + // wait until all send operations are completed + peerIt = peerSet.begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + + numRowsSendBuff_[peerRank]->wait(); + rowSizesSendBuff_[peerRank]->wait(); + rowIndicesSendBuff_[peerRank]->wait(); + entryColIndicesSendBuff_[peerRank]->wait(); + + // convert the global indices in the send buffers to domestic + // ones + globalToDomesticBuff_(*rowIndicesSendBuff_[peerRank]); + globalToDomesticBuff_(*entryColIndicesSendBuff_[peerRank]); + } + + ///////// + // actually initialize the BCRS matrix structure + ///////// + + // set the row sizes + size_t numDomestic = overlap_->numDomestic(); + for (unsigned rowIdx = 0; rowIdx < numDomestic; ++rowIdx) { + unsigned numCols = 0; + const auto& colIndices = entries_[rowIdx]; + auto colIdxIt = colIndices.begin(); + const auto& colIdxEndIt = colIndices.end(); + for (; colIdxIt != colIdxEndIt; ++colIdxIt) { + if (*colIdxIt < 0) + // the matrix for the local process does not know about this DOF + continue; + + ++numCols; + } + + this->setrowsize(rowIdx, numCols); + } + this->endrowsizes(); + + // set the indices + for (unsigned rowIdx = 0; rowIdx < numDomestic; ++rowIdx) { + const auto& colIndices = entries_[rowIdx]; + + auto colIdxIt = colIndices.begin(); + const auto& colIdxEndIt = colIndices.end(); + for (; colIdxIt != colIdxEndIt; ++colIdxIt) { + if (*colIdxIt < 0) + // the matrix for the local process does not know about this DOF + continue; + + this->addindex(rowIdx, static_cast(*colIdxIt)); + } + } + this->endindices(); + + // free the memory occupied by the array of the matrix entries + entries_.clear(); + } + + // send the overlap indices to a peer + template + void sendIndices_([[maybe_unused]] const NativeBCRSMatrix& nativeMatrix, + [[maybe_unused]] ProcessRank peerRank) + { +#if HAVE_MPI + // send size of foreign overlap to peer + size_t numOverlapRows = overlap_->foreignOverlapSize(peerRank); + numRowsSendBuff_[peerRank] = new MpiBuffer(1); + (*numRowsSendBuff_[peerRank])[0] = static_cast(numOverlapRows); + numRowsSendBuff_[peerRank]->send(peerRank); + + // allocate the buffers which hold the global indices of each row and the number + // of entries which need to be communicated by the respective row + rowIndicesSendBuff_[peerRank] = new MpiBuffer(numOverlapRows); + rowSizesSendBuff_[peerRank] = new MpiBuffer(numOverlapRows); + + // compute the sets of the indices of the entries which need to be send to the peer + using ColumnIndexSet = std::set; + using EntryTuples = std::map; + + EntryTuples entryIndices; + unsigned numEntries = 0; // <- total number of matrix entries to be send to the peer + for (unsigned overlapOffset = 0; overlapOffset < numOverlapRows; ++overlapOffset) { + Index domesticRowIdx = overlap_->foreignOverlapOffsetToDomesticIdx(peerRank, overlapOffset); + Index nativeRowIdx = overlap_->domesticToNative(domesticRowIdx); + Index globalRowIdx = overlap_->domesticToGlobal(domesticRowIdx); + + ColumnIndexSet& colIndices = entryIndices[globalRowIdx]; + + auto nativeColIt = nativeMatrix[static_cast(nativeRowIdx)].begin(); + const auto& nativeColEndIt = nativeMatrix[static_cast(nativeRowIdx)].end(); + for (; nativeColIt != nativeColEndIt; ++nativeColIt) { + unsigned nativeColIdx = static_cast(nativeColIt.index()); + Index domesticColIdx = overlap_->nativeToDomestic(static_cast(nativeColIdx)); + + if (domesticColIdx < 0) + // the native column index may be blacklisted, use the corresponding + // index in the domestic overlap. + domesticColIdx = overlap_->blackList().nativeToDomestic(static_cast(nativeColIdx)); + + if (domesticColIdx < 0) + // the column may still not be known locally, i.e. the corresponding + // DOF of the row is at the process's front. we don't need this + // entry. + continue; + + Index globalColIdx = overlap_->domesticToGlobal(domesticColIdx); + colIndices.insert(globalColIdx); + ++numEntries; + } + }; + + // fill the send buffers + entryColIndicesSendBuff_[peerRank] = new MpiBuffer(numEntries); + Index overlapEntryIdx = 0; + for (unsigned overlapOffset = 0; overlapOffset < numOverlapRows; ++overlapOffset) { + Index domesticRowIdx = overlap_->foreignOverlapOffsetToDomesticIdx(peerRank, overlapOffset); + Index globalRowIdx = overlap_->domesticToGlobal(domesticRowIdx); + + (*rowIndicesSendBuff_[peerRank])[overlapOffset] = globalRowIdx; + + const ColumnIndexSet& colIndexSet = entryIndices[globalRowIdx]; + auto* rssb = rowSizesSendBuff_[peerRank]; + (*rssb)[overlapOffset] = static_cast(colIndexSet.size()); + for (auto it = colIndexSet.begin(); it != colIndexSet.end(); ++it) { + int globalColIdx = *it; + + (*entryColIndicesSendBuff_[peerRank])[static_cast(overlapEntryIdx)] = globalColIdx; + ++ overlapEntryIdx; + } + } + + // actually communicate with the peer + rowSizesSendBuff_[peerRank]->send(peerRank); + rowIndicesSendBuff_[peerRank]->send(peerRank); + entryColIndicesSendBuff_[peerRank]->send(peerRank); + + // create the send buffers for the values of the matrix + // entries + entryValuesSendBuff_[peerRank] = new MpiBuffer(numEntries); +#endif // HAVE_MPI + } + + // receive the overlap indices to a peer + void receiveIndices_([[maybe_unused]] ProcessRank peerRank) + { +#if HAVE_MPI + // receive size of foreign overlap to peer + unsigned numOverlapRows; + auto& numRowsRecvBuff = numRowsRecvBuff_[peerRank]; + numRowsRecvBuff.resize(1); + numRowsRecvBuff.receive(peerRank); + numOverlapRows = numRowsRecvBuff[0]; + + // create receive buffer for the row sizes and receive them + // from the peer + rowSizesRecvBuff_[peerRank] = new MpiBuffer(numOverlapRows); + rowIndicesRecvBuff_[peerRank] = new MpiBuffer(numOverlapRows); + rowSizesRecvBuff_[peerRank]->receive(peerRank); + rowIndicesRecvBuff_[peerRank]->receive(peerRank); + + // calculate the total number of indices which are send by the + // peer + unsigned totalIndices = 0; + for (unsigned i = 0; i < numOverlapRows; ++i) + totalIndices += (*rowSizesRecvBuff_[peerRank])[i]; + + // create the buffer to store the column indices of the matrix entries + entryColIndicesRecvBuff_[peerRank] = new MpiBuffer(totalIndices); + entryValuesRecvBuff_[peerRank] = new MpiBuffer(totalIndices); + + // communicate with the peer + entryColIndicesRecvBuff_[peerRank]->receive(peerRank); + + // convert the global indices in the receive buffers to + // domestic ones + globalToDomesticBuff_(*rowIndicesRecvBuff_[peerRank]); + globalToDomesticBuff_(*entryColIndicesRecvBuff_[peerRank]); + + // add the entries to the global entry map + unsigned k = 0; + for (unsigned i = 0; i < numOverlapRows; ++i) { + Index domRowIdx = (*rowIndicesRecvBuff_[peerRank])[i]; + for (unsigned j = 0; j < (*rowSizesRecvBuff_[peerRank])[i]; ++j) { + Index domColIdx = (*entryColIndicesRecvBuff_[peerRank])[k]; + entries_[static_cast(domRowIdx)].insert(domColIdx); + ++k; + } + } +#endif // HAVE_MPI + } + + void sendEntries_([[maybe_unused]] ProcessRank peerRank) + { +#if HAVE_MPI + auto &mpiSendBuff = *entryValuesSendBuff_[peerRank]; + + auto &mpiRowIndicesSendBuff = *rowIndicesSendBuff_[peerRank]; + auto &mpiRowSizesSendBuff = *rowSizesSendBuff_[peerRank]; + auto &mpiColIndicesSendBuff = *entryColIndicesSendBuff_[peerRank]; + + // fill the send buffer + unsigned k = 0; + for (unsigned i = 0; i < mpiRowIndicesSendBuff.size(); ++i) { + Index domRowIdx = mpiRowIndicesSendBuff[i]; + + for (Index j = 0; j < static_cast(mpiRowSizesSendBuff[i]); ++j) + { + // move to the next column which is in the overlap + Index domColIdx = mpiColIndicesSendBuff[k]; + + // add the values of this column to the send buffer + mpiSendBuff[k] = (*this)[static_cast(domRowIdx)][static_cast(domColIdx)]; + ++k; + } + } + + mpiSendBuff.send(peerRank); +#endif // HAVE_MPI + } + + void receiveAddEntries_([[maybe_unused]] ProcessRank peerRank) + { +#if HAVE_MPI + auto &mpiRecvBuff = *entryValuesRecvBuff_[peerRank]; + + auto &mpiRowIndicesRecvBuff = *rowIndicesRecvBuff_[peerRank]; + auto &mpiRowSizesRecvBuff = *rowSizesRecvBuff_[peerRank]; + auto &mpiColIndicesRecvBuff = *entryColIndicesRecvBuff_[peerRank]; + + mpiRecvBuff.receive(peerRank); + + // retrieve the values from the receive buffer + unsigned k = 0; + for (unsigned i = 0; i < mpiRowIndicesRecvBuff.size(); ++i) { + Index domRowIdx = mpiRowIndicesRecvBuff[i]; + for (unsigned j = 0; j < mpiRowSizesRecvBuff[i]; ++j, ++k) { + Index domColIdx = mpiColIndicesRecvBuff[k]; + + if (domColIdx < 0) + // the matrix for the current process does not know about this DOF + continue; + + (*this)[static_cast(domRowIdx)][static_cast(domColIdx)] += mpiRecvBuff[k]; + } + } +#endif // HAVE_MPI + } + + void receiveCopyEntries_([[maybe_unused]] int peerRank) + { +#if HAVE_MPI + MpiBuffer &mpiRecvBuff = *entryValuesRecvBuff_[peerRank]; + + MpiBuffer &mpiRowIndicesRecvBuff = *rowIndicesRecvBuff_[peerRank]; + MpiBuffer &mpiRowSizesRecvBuff = *rowSizesRecvBuff_[peerRank]; + MpiBuffer &mpiColIndicesRecvBuff = *entryColIndicesRecvBuff_[peerRank]; + + mpiRecvBuff.receive(peerRank); + + // retrieve the values from the receive buffer + unsigned k = 0; + for (unsigned i = 0; i < mpiRowIndicesRecvBuff.size(); ++i) { + Index domRowIdx = mpiRowIndicesRecvBuff[i]; + for (unsigned j = 0; j < mpiRowSizesRecvBuff[i]; ++j, ++k) { + Index domColIdx = mpiColIndicesRecvBuff[k]; + + if (domColIdx < 0) + // the matrix for the current process does not know about this DOF + continue; + + (*this)[static_cast(domRowIdx)][static_cast(domColIdx)] = mpiRecvBuff[k]; + } + } +#endif // HAVE_MPI + } + + void globalToDomesticBuff_(MpiBuffer& idxBuff) + { + for (unsigned i = 0; i < idxBuff.size(); ++i) + idxBuff[i] = overlap_->globalToDomestic(idxBuff[i]); + } + + int myRank_; + Entries entries_; + std::shared_ptr overlap_; + + std::map *> numRowsSendBuff_; + std::map *> rowSizesSendBuff_; + std::map *> rowIndicesSendBuff_; + std::map *> entryColIndicesSendBuff_; + std::map *> entryValuesSendBuff_; + + std::map > numRowsRecvBuff_; + std::map *> rowSizesRecvBuff_; + std::map *> rowIndicesRecvBuff_; + std::map *> entryColIndicesRecvBuff_; + std::map *> entryValuesRecvBuff_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/overlappingblockvector.hh b/opm/simulators/linalg/overlappingblockvector.hh new file mode 100644 index 00000000000..8217da0f640 --- /dev/null +++ b/opm/simulators/linalg/overlappingblockvector.hh @@ -0,0 +1,362 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::OverlappingBlockVector + */ +#ifndef EWOMS_OVERLAPPING_BLOCK_VECTOR_HH +#define EWOMS_OVERLAPPING_BLOCK_VECTOR_HH + +#include "overlaptypes.hh" + +#include +#include + +#include +#include + +#include +#include +#include + +namespace Opm { +namespace Linear { + +/*! + * \brief An overlap aware block vector. + */ +template +class OverlappingBlockVector : public Dune::BlockVector +{ + using ParentType = Dune::BlockVector; + using BlockVector = Dune::BlockVector; + +public: + /*! + * \brief Given a domestic overlap object, create an overlapping + * block vector coherent to it. + */ + OverlappingBlockVector(const Overlap& overlap) + : ParentType(overlap.numDomestic()), overlap_(&overlap) + { createBuffers_(); } + + /*! + * \brief Copy constructor. + */ + OverlappingBlockVector(const OverlappingBlockVector& obv) + : ParentType(obv) + , numIndicesSendBuff_(obv.numIndicesSendBuff_) + , indicesSendBuff_(obv.indicesSendBuff_) + , indicesRecvBuff_(obv.indicesRecvBuff_) + , valuesSendBuff_(obv.valuesSendBuff_) + , valuesRecvBuff_(obv.valuesRecvBuff_) + , overlap_(obv.overlap_) + {} + + /*! + * \brief Default constructor. + */ + OverlappingBlockVector() + {} + + //! \cond SKIP + /*! + * \brief Recycle the assignment operators of Dune::BlockVector + */ + using ParentType::operator=; + //! \endcond + + /*! + * \brief Assignment operator. + */ + OverlappingBlockVector& operator=(const OverlappingBlockVector& obv) + { + ParentType::operator=(obv); + numIndicesSendBuff_ = obv.numIndicesSendBuff_; + indicesSendBuff_ = obv.indicesSendBuff_; + indicesRecvBuff_ = obv.indicesRecvBuff_; + valuesSendBuff_ = obv.valuesSendBuff_; + valuesRecvBuff_ = obv.valuesRecvBuff_; + overlap_ = obv.overlap_; + return *this; + } + + /*! + * \brief Assign an overlapping block vector from a + * non-overlapping one, border entries are added. + */ + template + void assignAddBorder(const BlockVector& nativeBlockVector) + { + size_t numDomestic = overlap_->numDomestic(); + + // assign the local rows from the non-overlapping block vector + for (unsigned domRowIdx = 0; domRowIdx < numDomestic; ++domRowIdx) { + Index nativeRowIdx = overlap_->domesticToNative(static_cast(domRowIdx)); + if (nativeRowIdx < 0) + (*this)[domRowIdx] = 0.0; + else + (*this)[domRowIdx] = nativeBlockVector[nativeRowIdx]; + } + + // add up the contents of the overlapping rows. Since non-native rows are set to + // zero above, the addition is only different from an assignment if the row is + // shared amongst multiple ranks. + syncAdd(); + } + + /*! + * \brief Assign an overlapping block vector from a non-overlapping one, border + * entries are assigned using their respective master ranks. + */ + template + void assign(const NativeBlockVector& nativeBlockVector) + { + Index numDomestic = overlap_->numDomestic(); + + // assign the local rows from the non-overlapping block vector + for (Index domRowIdx = 0; domRowIdx < numDomestic; ++domRowIdx) { + Index nativeRowIdx = overlap_->domesticToNative(domRowIdx); + if (nativeRowIdx < 0) + (*this)[static_cast(domRowIdx)] = 0.0; + else + (*this)[static_cast(domRowIdx)] = nativeBlockVector[static_cast(nativeRowIdx)]; + } + + // add up the contents of border rows, for the remaining rows, + // get the values from their respective master process. + sync(); + } + + /*! + * \brief Assign the local values to a non-overlapping block + * vector. + */ + template + void assignTo(NativeBlockVector& nativeBlockVector) const + { + // assign the local rows + size_t numNative = overlap_->numNative(); + nativeBlockVector.resize(numNative); + for (unsigned nativeRowIdx = 0; nativeRowIdx < numNative; ++nativeRowIdx) { + Index domRowIdx = overlap_->nativeToDomestic(static_cast(nativeRowIdx)); + + if (domRowIdx < 0) + nativeBlockVector[nativeRowIdx] = 0.0; + else + nativeBlockVector[nativeRowIdx] = (*this)[static_cast(domRowIdx)]; + } + } + + /*! + * \brief Syncronize all values of the block vector from their + * master process. + */ + void sync() + { + // send all entries to all peers + for (const auto peerRank: overlap_->peerSet()) + sendEntries_(peerRank); + + // recieve all entries to the peers + for (const auto peerRank: overlap_->peerSet()) + receiveFromMaster_(peerRank); + + // wait until we have send everything + waitSendFinished_(); + } + + /*! + * \brief Syncronize all values of the block vector by adding up + * the values of all peer ranks. + */ + void syncAdd() + { + // send all entries to all peers + for (const auto peerRank: overlap_->peerSet()) + sendEntries_(peerRank); + + // recieve all entries to the peers + for (const auto peerRank: overlap_->peerSet()) + receiveAdd_(peerRank); + + // wait until we have send everything + waitSendFinished_(); + } + + void print() const + { + for (unsigned i = 0; i < this->size(); ++i) { + std::cout << "row " << i << (overlap_->isLocal(i) ? " " : "*") + << ": " << (*this)[i] << "\n" << std::flush; + } + } + +private: + void createBuffers_() + { +#if HAVE_MPI + // create array for the front indices + typename PeerSet::const_iterator peerIt; + typename PeerSet::const_iterator peerEndIt = overlap_->peerSet().end(); + + // send all indices to the peers + peerIt = overlap_->peerSet().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + + size_t numEntries = overlap_->foreignOverlapSize(peerRank); + numIndicesSendBuff_[peerRank] = std::make_shared >(1); + indicesSendBuff_[peerRank] = std::make_shared >(numEntries); + valuesSendBuff_[peerRank] = std::make_shared >(numEntries); + + // fill the indices buffer with global indices + MpiBuffer& indicesSendBuff = *indicesSendBuff_[peerRank]; + for (unsigned i = 0; i < numEntries; ++i) { + Index domRowIdx = overlap_->foreignOverlapOffsetToDomesticIdx(peerRank, i); + indicesSendBuff[i] = overlap_->domesticToGlobal(domRowIdx); + } + + // first, send the number of indices + (*numIndicesSendBuff_[peerRank])[0] = static_cast(numEntries); + numIndicesSendBuff_[peerRank]->send(peerRank); + + // then, send the indices themselfs + indicesSendBuff.send(peerRank); + } + + // receive the indices from the peers + peerIt = overlap_->peerSet().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + + // receive size of overlap to peer + MpiBuffer numRowsRecvBuff(1); + numRowsRecvBuff.receive(peerRank); + unsigned numRows = numRowsRecvBuff[0]; + + // then, create the MPI buffers + indicesRecvBuff_[peerRank] = std::shared_ptr >( + new MpiBuffer(numRows)); + valuesRecvBuff_[peerRank] = std::shared_ptr >( + new MpiBuffer(numRows)); + MpiBuffer& indicesRecvBuff = *indicesRecvBuff_[peerRank]; + + // next, receive the actual indices + indicesRecvBuff.receive(peerRank); + + // finally, translate the global indices to domestic ones + for (unsigned i = 0; i != numRows; ++i) { + Index globalRowIdx = indicesRecvBuff[i]; + Index domRowIdx = overlap_->globalToDomestic(globalRowIdx); + + indicesRecvBuff[i] = domRowIdx; + } + } + + // wait for all send operations to complete + peerIt = overlap_->peerSet().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + numIndicesSendBuff_[peerRank]->wait(); + indicesSendBuff_[peerRank]->wait(); + + // convert the global indices of the send buffer to + // domestic ones + MpiBuffer& indicesSendBuff = *indicesSendBuff_[peerRank]; + for (unsigned i = 0; i < indicesSendBuff.size(); ++i) { + indicesSendBuff[i] = overlap_->globalToDomestic(indicesSendBuff[i]); + } + } +#endif // HAVE_MPI + } + + void sendEntries_(ProcessRank peerRank) + { + // copy the values into the send buffer + const MpiBuffer& indices = *indicesSendBuff_[peerRank]; + MpiBuffer& values = *valuesSendBuff_[peerRank]; + for (unsigned i = 0; i < indices.size(); ++i) + values[i] = (*this)[static_cast(indices[i])]; + + values.send(peerRank); + } + + void waitSendFinished_() + { + typename PeerSet::const_iterator peerIt; + typename PeerSet::const_iterator peerEndIt = overlap_->peerSet().end(); + + // send all entries to all peers + peerIt = overlap_->peerSet().begin(); + for (; peerIt != peerEndIt; ++peerIt) { + ProcessRank peerRank = *peerIt; + valuesSendBuff_[peerRank]->wait(); + } + } + + void receiveFromMaster_(ProcessRank peerRank) + { + const MpiBuffer& indices = *indicesRecvBuff_[peerRank]; + MpiBuffer& values = *valuesRecvBuff_[peerRank]; + + // receive the values from the peer + values.receive(peerRank); + + // copy them into the block vector + for (unsigned j = 0; j < indices.size(); ++j) { + Index domRowIdx = indices[j]; + if (overlap_->masterRank(domRowIdx) == peerRank) { + (*this)[static_cast(domRowIdx)] = values[j]; + } + } + } + + void receiveAdd_(ProcessRank peerRank) + { + const MpiBuffer& indices = *indicesRecvBuff_[peerRank]; + MpiBuffer& values = *valuesRecvBuff_[peerRank]; + + // receive the values from the peer + values.receive(peerRank); + + // add up the values of rows on the shared boundary + for (unsigned j = 0; j < indices.size(); ++j) { + Index domRowIdx = indices[j]; + (*this)[static_cast(domRowIdx)] += values[j]; + } + } + + std::map > > numIndicesSendBuff_; + std::map > > indicesSendBuff_; + std::map > > indicesRecvBuff_; + std::map > > valuesSendBuff_; + std::map > > valuesRecvBuff_; + + const Overlap *overlap_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/overlappingoperator.hh b/opm/simulators/linalg/overlappingoperator.hh new file mode 100644 index 00000000000..93a3fa472b0 --- /dev/null +++ b/opm/simulators/linalg/overlappingoperator.hh @@ -0,0 +1,86 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::OverlappingOperator + */ +#ifndef EWOMS_OVERLAPPING_OPERATOR_HH +#define EWOMS_OVERLAPPING_OPERATOR_HH + +#include +#include + +namespace Opm { +namespace Linear { + +/*! + * \brief An overlap aware linear operator usable by ISTL. + */ +template +class OverlappingOperator + : public Dune::AssembledLinearOperator +{ + using Overlap = typename OverlappingMatrix::Overlap; + +public: + //! export types + using domain_type = DomainVector; + using field_type = typename domain_type::field_type; + + OverlappingOperator(const OverlappingMatrix& A) : A_(A) + {} + + //! the kind of computations supported by the operator. Either overlapping or non-overlapping + Dune::SolverCategory::Category category() const override + { return Dune::SolverCategory::overlapping; } + + //! apply operator to x: \f$ y = A(x) \f$ + virtual void apply(const DomainVector& x, RangeVector& y) const override + { + A_.mv(x, y); + y.sync(); + } + + //! apply operator to x, scale and add: \f$ y = y + \alpha A(x) \f$ + virtual void applyscaleadd(field_type alpha, const DomainVector& x, + RangeVector& y) const override + { + A_.usmv(alpha, x, y); + y.sync(); + } + + //! returns the matrix + virtual const OverlappingMatrix& getmat() const override + { return A_; } + + const Overlap& overlap() const + { return A_.overlap(); } + +private: + const OverlappingMatrix& A_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/overlappingpreconditioner.hh b/opm/simulators/linalg/overlappingpreconditioner.hh new file mode 100644 index 00000000000..d09cb46dd2d --- /dev/null +++ b/opm/simulators/linalg/overlappingpreconditioner.hh @@ -0,0 +1,188 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::OverlappingPreconditioner + */ +#ifndef EWOMS_OVERLAPPING_PRECONDITIONER_HH +#define EWOMS_OVERLAPPING_PRECONDITIONER_HH + +#include "overlappingscalarproduct.hh" + +#include +#include //definitions needed in next header +#include + +#include + +namespace Opm { +namespace Linear { + +/*! + * \brief An overlap aware preconditioner for any ISTL linear solver. + */ +template +class OverlappingPreconditioner + : public Dune::Preconditioner +{ +public: + using domain_type = typename SeqPreCond::domain_type; + using range_type = typename SeqPreCond::range_type; + + //! the kind of computations supported by the operator. Either overlapping or non-overlapping + Dune::SolverCategory::Category category() const override + { return Dune::SolverCategory::overlapping; } + + OverlappingPreconditioner(SeqPreCond& seqPreCond, const Overlap& overlap) + : seqPreCond_(seqPreCond), overlap_(&overlap) + {} + + void pre(domain_type& x, range_type& y) override + { +#if HAVE_MPI + short success; + try + { + seqPreCond_.pre(x, y); + short localSuccess = 1; + MPI_Allreduce(&localSuccess, // source buffer + &success, // destination buffer + 1, // number of objects in buffers + MPI_SHORT, // data type + MPI_MIN, // operation + MPI_COMM_WORLD); // communicator + } + catch (...) + { + short localSuccess = 0; + MPI_Allreduce(&localSuccess, // source buffer + &success, // destination buffer + 1, // number of objects in buffers + MPI_SHORT, // data type + MPI_MIN, // operation + MPI_COMM_WORLD); // communicator + } + + if (success) { + x.sync(); + } + else + throw NumericalProblem("Preconditioner threw an exception in pre() method on some process."); +#else + seqPreCond_.pre(x, y); +#endif + + // communicate the results on the overlap + x.sync(); + y.sync(); + } + + void apply(domain_type& x, const range_type& d) override + { +#if HAVE_MPI + if (overlap_->peerSet().size() > 0) { + // make sure that all processes react the same if the + // sequential preconditioner on one process throws an + // exception + short success; + try + { + // execute the sequential preconditioner + seqPreCond_.apply(x, d); + short localSuccess = 1; + MPI_Allreduce(&localSuccess, // source buffer + &success, // destination buffer + 1, // number of objects in buffers + MPI_SHORT, // data type + MPI_MIN, // operation + MPI_COMM_WORLD); // communicator + } + catch (...) + { + short localSuccess = 0; + MPI_Allreduce(&localSuccess, // source buffer + &success, // destination buffer + 1, // number of objects in buffers + MPI_SHORT, // data type + MPI_MIN, // operation + MPI_COMM_WORLD); // communicator + } + + if (success) { + x.sync(); + } + else + throw NumericalProblem("Preconditioner threw an exception on some process."); + } + else +#endif // HAVE_MPI + seqPreCond_.apply(x, d); + } + + void post(domain_type& x) override + { +#if HAVE_MPI + short success; + try + { + seqPreCond_.post(x); + short localSuccess = 1; + MPI_Allreduce(&localSuccess, // source buffer + &success, // destination buffer + 1, // number of objects in buffers + MPI_SHORT, // data type + MPI_MIN, // operation + MPI_COMM_WORLD); // communicator + } + catch (...) + { + short localSuccess = 0; + MPI_Allreduce(&localSuccess, // source buffer + &success, // destination buffer + 1, // number of objects in buffers + MPI_SHORT, // data type + MPI_MIN, // operation + MPI_COMM_WORLD); // communicator + } + + if (success) { + x.sync(); + } + else + throw NumericalProblem("Preconditioner threw an exception in post() method on " + "some process."); +#else + seqPreCond_.post(x); +#endif + } + +private: + SeqPreCond& seqPreCond_; + const Overlap *overlap_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/overlappingscalarproduct.hh b/opm/simulators/linalg/overlappingscalarproduct.hh new file mode 100644 index 00000000000..0b8469a3c62 --- /dev/null +++ b/opm/simulators/linalg/overlappingscalarproduct.hh @@ -0,0 +1,83 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::OverlappingScalarProduct + */ +#ifndef EWOMS_OVERLAPPING_SCALAR_PRODUCT_HH +#define EWOMS_OVERLAPPING_SCALAR_PRODUCT_HH + +#include +#include + +namespace Opm { +namespace Linear { + +/*! + * \brief An overlap aware ISTL scalar product. + */ +template +class OverlappingScalarProduct + : public Dune::ScalarProduct +{ +public: + using field_type = typename OverlappingBlockVector::field_type; + + using CollectiveCommunication = typename Dune::Communication; + using real_type = typename Dune::ScalarProduct::real_type; + + //! the kind of computations supported by the operator. Either overlapping or non-overlapping + Dune::SolverCategory::Category category() const override + { return Dune::SolverCategory::overlapping; } + + OverlappingScalarProduct(const Overlap& overlap) + : overlap_(overlap), + comm_( Dune::MPIHelper::getCommunication() ) + {} + + field_type dot(const OverlappingBlockVector& x, + const OverlappingBlockVector& y) const override + { + field_type sum = 0; + size_t numLocal = overlap_.numLocal(); + for (unsigned localIdx = 0; localIdx < numLocal; ++localIdx) { + if (overlap_.iAmMasterOf(static_cast(localIdx))) + sum += x[localIdx] * y[localIdx]; + } + + // return the global sum + return comm_.sum( sum ); + } + + real_type norm(const OverlappingBlockVector& x) const override + { return std::sqrt(dot(x, x)); } + +private: + const Overlap& overlap_; + const CollectiveCommunication comm_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/overlaptypes.hh b/opm/simulators/linalg/overlaptypes.hh new file mode 100644 index 00000000000..7bbc5f4d1fd --- /dev/null +++ b/opm/simulators/linalg/overlaptypes.hh @@ -0,0 +1,192 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This files provides several data structures for storing + * tuples of indices of remote and/or local processes. + */ +#ifndef EWOMS_OVERLAP_TYPES_HH +#define EWOMS_OVERLAP_TYPES_HH + +#include +#include +#include +#include +#include + +namespace Opm { +namespace Linear { + +/*! + * \brief The type of an index of a degree of freedom. + */ +using Index = int; + +/*! + * \brief The type of the rank of a process. + */ +using ProcessRank = unsigned; + +/*! + * \brief The type representing the distance of an index to the border. + */ +using BorderDistance = unsigned; + +/*! + * \brief This structure stores an index and a process rank + */ +struct IndexRank +{ + Index index; + ProcessRank rank; +}; + +/*! + * \brief This structure stores a local index on a peer process and a + * global index. + */ +struct PeerIndexGlobalIndex +{ + Index peerIdx; + Index globalIdx; +}; + +/*! + * \brief This structure stores an index, a process rank, and the + * distance of the degree of freedom to the process border. + */ +struct IndexRankDist +{ + Index index; + ProcessRank peerRank; + BorderDistance borderDistance; +}; + +/*! + * \brief This structure stores an index, a process rank, and the + * number of processes which "see" the degree of freedom with + * the index. + */ +struct IndexDistanceNpeers +{ + Index index; + BorderDistance borderDistance; + unsigned numPeers; +}; + +/*! + * \brief A single index intersecting with the process boundary. + */ +struct BorderIndex +{ + //! Index of the entity for the local process + Index localIdx; + + //! Index of the entity for the peer process + Index peerIdx; + + //! Rank of the peer process + ProcessRank peerRank; + + //! Distance to the process border for the peer (in hops) + BorderDistance borderDistance; +}; + +/*! + * \brief This class managages a list of indices which are on the + * border of a process' partition of the grid + */ +using BorderList = std::list; + +/*! + * \brief The list of indices which are on the process boundary. + */ +class SeedList : public std::list +{ +public: + void update(const BorderList& borderList) + { + this->clear(); + + auto it = borderList.begin(); + const auto& endIt = borderList.end(); + for (; it != endIt; ++it) { + IndexRankDist ird; + ird.index = it->localIdx; + ird.peerRank = it->peerRank; + ird.borderDistance = it->borderDistance; + + this->push_back(ird); + } + } +}; + +/*! + * \brief A set of process ranks + */ +class PeerSet : public std::set +{ +public: + void update(const BorderList& borderList) + { + this->clear(); + + auto it = borderList.begin(); + const auto& endIt = borderList.end(); + for (; it != endIt; ++it) + this->insert(it->peerRank); + } +}; + +/*! + * \brief The list of indices which overlap with a peer rank. + */ +using OverlapWithPeer = std::vector; + +/*! + * \brief A type mapping the process rank to the list of indices + * shared with this peer. + */ +using OverlapByRank = std::map; + +/*! + * \brief Maps each index to a list of processes . + */ +using OverlapByIndex = std::vector >; + +/*! + * \brief The list of domestic indices are owned by peer rank. + */ +using DomesticOverlapWithPeer = std::vector; + +/*! + * \brief A type mapping the process rank to the list of domestic indices + * which are owned by the peer. + */ +using DomesticOverlapByRank = std::map; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/parallelamgbackend.hh b/opm/simulators/linalg/parallelamgbackend.hh new file mode 100644 index 00000000000..17523090add --- /dev/null +++ b/opm/simulators/linalg/parallelamgbackend.hh @@ -0,0 +1,330 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::ParallelAmgBackend + */ +#ifndef EWOMS_PARALLEL_AMG_BACKEND_HH +#define EWOMS_PARALLEL_AMG_BACKEND_HH + +#include "linalgproperties.hh" +#include "parallelbasebackend.hh" +#include "bicgstabsolver.hh" +#include "combinedcriterion.hh" +#include "istlsparsematrixadapter.hh" + +#include +#include +#include +#include + +#include +#include +#include + +namespace Opm::Linear { + +template +class ParallelAmgBackend; + +} // namespace Opm::Linear + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct ParallelAmgLinearSolver +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct LinearSolverBackend +{ using type = Opm::Linear::ParallelAmgBackend; }; + +} // namespace Opm::Properties + +namespace Opm::Parameters { + +//! The target number of DOFs per processor for the parallel algebraic +//! multi-grid solver +struct AmgCoarsenTarget { static constexpr int value = 5000; }; + +} + +namespace Opm::Linear { + +/*! + * \ingroup Linear + * + * \brief Provides a linear solver backend using the parallel + * algebraic multi-grid (AMG) linear solver from DUNE-ISTL. + */ +template +class ParallelAmgBackend : public ParallelBaseBackend +{ + using ParentType = ParallelBaseBackend; + + using Scalar = GetPropType; + using LinearSolverScalar = GetPropType; + using Simulator = GetPropType; + using GridView = GetPropType; + using Overlap = GetPropType; + using SparseMatrixAdapter = GetPropType; + + using ParallelOperator = typename ParentType::ParallelOperator; + using OverlappingVector = typename ParentType::OverlappingVector; + using ParallelPreconditioner = typename ParentType::ParallelPreconditioner; + using ParallelScalarProduct = typename ParentType::ParallelScalarProduct; + + static constexpr int numEq = getPropValue(); + using VectorBlock = Dune::FieldVector; + using MatrixBlock = typename SparseMatrixAdapter::MatrixBlock; + using IstlMatrix = typename SparseMatrixAdapter::IstlMatrix; + + using Vector = Dune::BlockVector; + + // define the smoother used for the AMG and specify its + // arguments + using SequentialSmoother = Dune::SeqSOR; +// using SequentialSmoother = Dune::SeqSSOR; +// using SequentialSmoother = Dune::SeqJac; +// using SequentialSmoother = Dune::SeqILU; + +#if HAVE_MPI + using OwnerOverlapCopyCommunication = Dune::OwnerOverlapCopyCommunication; + using FineOperator = Dune::OverlappingSchwarzOperator; + using FineScalarProduct = Dune::OverlappingSchwarzScalarProduct; + using ParallelSmoother = Dune::BlockPreconditioner; + using AMG = Dune::Amg::AMG; +#else + using FineOperator = Dune::MatrixAdapter; + using FineScalarProduct = Dune::SeqScalarProduct; + using ParallelSmoother = SequentialSmoother; + using AMG = Dune::Amg::AMG; +#endif + + using RawLinearSolver = BiCGStabSolver ; + + static_assert(std::is_same >::value, + "The ParallelAmgBackend linear solver backend requires the IstlSparseMatrixAdapter"); + +public: + ParallelAmgBackend(const Simulator& simulator) + : ParentType(simulator) + { } + + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register> + ("The maximum residual error which the linear solver tolerates " + "without giving up"); + Parameters::Register + ("The coarsening target for the agglomerations of " + "the AMG preconditioner"); + } + +protected: + friend ParentType; + + std::shared_ptr preparePreconditioner_() + { +#if HAVE_MPI + // create and initialize DUNE's OwnerOverlapCopyCommunication + // using the domestic overlap + istlComm_ = std::make_shared(MPI_COMM_WORLD); + setupAmgIndexSet_(this->overlappingMatrix_->overlap(), istlComm_->indexSet()); + istlComm_->remoteIndices().template rebuild(); +#endif + + // create the parallel scalar product and the parallel operator +#if HAVE_MPI + fineOperator_ = std::make_shared(*this->overlappingMatrix_, *istlComm_); +#else + fineOperator_ = std::make_shared(*this->overlappingMatrix_); +#endif + + setupAmg_(); + + return amg_; + } + + void cleanupPreconditioner_() + { /* nothing to do */ } + + std::shared_ptr prepareSolver_(ParallelOperator& parOperator, + ParallelScalarProduct& parScalarProduct, + AMG& parPreCond) + { + const auto& gridView = this->simulator_.gridView(); + using CCC = CombinedCriterion; + + Scalar linearSolverTolerance = Parameters::Get>(); + Scalar linearSolverAbsTolerance = Parameters::Get>(); + if (linearSolverAbsTolerance < 0.0) { + linearSolverAbsTolerance = this->simulator_.model().newtonMethod().tolerance()/100.0; + } + + convCrit_.reset(new CCC(gridView.comm(), + /*residualReductionTolerance=*/linearSolverTolerance, + /*absoluteResidualTolerance=*/linearSolverAbsTolerance, + Parameters::Get>())); + + auto bicgstabSolver = + std::make_shared(parPreCond, *convCrit_, parScalarProduct); + + int verbosity = 0; + if (parOperator.overlap().myRank() == 0) + verbosity = Parameters::Get(); + bicgstabSolver->setVerbosity(verbosity); + bicgstabSolver->setMaxIterations(Parameters::Get()); + bicgstabSolver->setLinearOperator(&parOperator); + bicgstabSolver->setRhs(this->overlappingb_); + + return bicgstabSolver; + } + + std::pair runSolver_(std::shared_ptr solver) + { + bool converged = solver->apply(*this->overlappingx_); + return std::make_pair(converged, int(solver->report().iterations())); + } + + void cleanupSolver_() + { /* nothing to do */ } + +#if HAVE_MPI + template + void setupAmgIndexSet_(const Overlap& overlap, ParallelIndexSet& istlIndices) + { + using GridAttributes = Dune::OwnerOverlapCopyAttributeSet; + using GridAttributeSet = Dune::OwnerOverlapCopyAttributeSet::AttributeSet; + + // create DUNE's ParallelIndexSet from a domestic overlap + istlIndices.beginResize(); + for (Index curIdx = 0; static_cast(curIdx) < overlap.numDomestic(); ++curIdx) { + GridAttributeSet gridFlag = + overlap.iAmMasterOf(curIdx) + ? GridAttributes::owner + : GridAttributes::copy; + + // an index is used by other processes if it is in the + // domestic or in the foreign overlap. + bool isShared = overlap.isInOverlap(curIdx); + + assert(curIdx == overlap.globalToDomestic(overlap.domesticToGlobal(curIdx))); + istlIndices.add(/*globalIdx=*/overlap.domesticToGlobal(curIdx), + Dune::ParallelLocalIndex(static_cast(curIdx), + gridFlag, + isShared)); + } + istlIndices.endResize(); + } +#endif + + // trailing return type with decltype used for detecting existence of setUseFixedOrder member function by overloading the setUseFixedOrder function + template + auto setUseFixedOrder(C criterion, bool booleanValue) -> decltype(criterion.setUseFixedOrder(booleanValue)) + { + return criterion.setUseFixedOrder(booleanValue); // Set flag to ensure that the matrices in the AMG hierarchy are constructed with deterministic indices. + } + template + void setUseFixedOrder(C, ...) + { + // do nothing, since the function setUseFixedOrder does not exist yet + } + + void setupAmg_() + { + if (amg_) + amg_.reset(); + + int verbosity = 0; + if (this->simulator_.vanguard().gridView().comm().rank() == 0) + verbosity = Parameters::Get(); + + using SmootherArgs = typename Dune::Amg::SmootherTraits::Arguments; + + SmootherArgs smootherArgs; + smootherArgs.iterations = 1; + smootherArgs.relaxationFactor = 1.0; + + // specify the coarsen criterion: + // + // using CoarsenCriterion = + // Dune::Amg::CoarsenCriterion> + using CoarsenCriterion = Dune::Amg:: + CoarsenCriterion >; + int coarsenTarget = Parameters::Get(); + CoarsenCriterion coarsenCriterion(/*maxLevel=*/15, coarsenTarget); + coarsenCriterion.setDefaultValuesAnisotropic(GridView::dimension, + /*aggregateSizePerDim=*/3); + if (verbosity > 0) + coarsenCriterion.setDebugLevel(1); + else + coarsenCriterion.setDebugLevel(0); // make the AMG shut up + + // reduce the minium coarsen rate (default is 1.2) + coarsenCriterion.setMinCoarsenRate(1.05); + // coarsenCriterion.setAccumulate(Dune::Amg::noAccu); + coarsenCriterion.setAccumulate(Dune::Amg::atOnceAccu); + coarsenCriterion.setSkipIsolated(false); + setUseFixedOrder(coarsenCriterion, true); // If possible, set flag to ensure that the matrices in the AMG hierarchy are constructed with deterministic indices. + +// instantiate the AMG preconditioner +#if HAVE_MPI + amg_ = std::make_shared(*fineOperator_, coarsenCriterion, smootherArgs, *istlComm_); +#else + amg_ = std::make_shared(*fineOperator_, coarsenCriterion, smootherArgs); +#endif + } + + std::unique_ptr > convCrit_; + + std::shared_ptr fineOperator_; + std::shared_ptr amg_; + +#if HAVE_MPI + std::shared_ptr istlComm_; +#endif +}; + +} // namespace Opm::Linear + +#endif diff --git a/opm/simulators/linalg/parallelbasebackend.hh b/opm/simulators/linalg/parallelbasebackend.hh new file mode 100644 index 00000000000..b79089a8450 --- /dev/null +++ b/opm/simulators/linalg/parallelbasebackend.hh @@ -0,0 +1,447 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::ParallelBaseBackend + */ +#ifndef EWOMS_PARALLEL_BASE_BACKEND_HH +#define EWOMS_PARALLEL_BASE_BACKEND_HH + +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Opm::Properties { + +namespace TTag { +struct ParallelBaseLinearSolver {}; +} + +//! Set the type of a global jacobian matrix for linear solvers that are based on +//! dune-istl. +template +struct SparseMatrixAdapter +{ +private: + using Scalar = GetPropType; + enum { numEq = getPropValue() }; + using Block = Opm::MatrixBlock; + +public: + using type = typename Opm::Linear::IstlSparseMatrixAdapter; +}; + +} // namespace Opm::Properties + +namespace Opm { +namespace Linear { +/*! + * \ingroup Linear + * + * \brief Provides the common code which is required by most linear solvers. + * + * This class provides access to all preconditioners offered by dune-istl using the + * PreconditionerWrapper property: + * \code + * template + * struct PreconditionerWrapper + * { using type = Opm::Linear::PreconditionerWrapper$PRECONDITIONER; }; + * \endcode + * + * Where the choices possible for '\c $PRECONDITIONER' are: + * - \c Jacobi: A Jacobi preconditioner + * - \c GaussSeidel: A Gauss-Seidel preconditioner + * - \c SSOR: A symmetric successive overrelaxation (SSOR) preconditioner + * - \c SOR: A successive overrelaxation (SOR) preconditioner + * - \c ILUn: An ILU(n) preconditioner + * - \c ILU0: An ILU(0) preconditioner. The results of this + * preconditioner are the same as setting the + * PreconditionerOrder property to 0 and using the ILU(n) + * preconditioner. The reason for the existence of ILU0 is + * that it is computationally cheaper because it does not + * need to consider things which are only required for + * higher orders + */ +template +class ParallelBaseBackend +{ +protected: + using Implementation = GetPropType; + + using Simulator = GetPropType; + using Scalar = GetPropType; + using LinearSolverScalar = GetPropType; + using SparseMatrixAdapter = GetPropType; + using Vector = GetPropType; + using BorderListCreator = GetPropType; + using GridView = GetPropType; + + using Overlap = GetPropType; + using OverlappingVector = GetPropType; + using OverlappingMatrix = GetPropType; + + using PreconditionerWrapper = GetPropType; + using SequentialPreconditioner = typename PreconditionerWrapper::SequentialPreconditioner; + + using ParallelPreconditioner = Opm::Linear::OverlappingPreconditioner; + using ParallelScalarProduct = Opm::Linear::OverlappingScalarProduct; + using ParallelOperator = Opm::Linear::OverlappingOperator; + + enum { dimWorld = GridView::dimensionworld }; + +public: + ParallelBaseBackend(const Simulator& simulator) + : simulator_(simulator) + , gridSequenceNumber_( -1 ) + , lastIterations_( -1 ) + { + overlappingMatrix_ = nullptr; + overlappingb_ = nullptr; + overlappingx_ = nullptr; + } + + ~ParallelBaseBackend() + { cleanup_(); } + + /*! + * \brief Register all run-time parameters for the linear solver. + */ + static void registerParameters() + { + Parameters::Register> + ("The maximum allowed error between of the linear solver"); + Parameters::Register> + ("The maximum accepted error of the norm of the residual"); + Parameters::Register + ("The size of the algebraic overlap for the linear solver"); + Parameters::Register + ("The maximum number of iterations of the linear solver"); + Parameters::Register + ("The verbosity level of the linear solver"); + + PreconditionerWrapper::registerParameters(); + } + + /*! + * \brief Causes the solve() method to discared the structure of the linear system of + * equations the next time it is called. + */ + void eraseMatrix() + { cleanup_(); } + + /*! + * \brief Set up the internal data structures required for the linear solver. + * + * This only specified the topology of the linear system of equations; it does does + * *not* assign the values of the residual vector and its Jacobian matrix. + */ + void prepare(const SparseMatrixAdapter& M, const Vector& ) + { + // if grid has changed the sequence number has changed too + int curSeqNum = simulator_.vanguard().gridSequenceNumber(); + if (gridSequenceNumber_ == curSeqNum && overlappingMatrix_) + // the grid has not changed since the overlappingMatrix_has been created, so + // there's noting to do + return; + + asImp_().cleanup_(); + gridSequenceNumber_ = curSeqNum; + + BorderListCreator borderListCreator(simulator_.gridView(), + simulator_.model().dofMapper()); + + // create the overlapping Jacobian matrix + unsigned overlapSize = Parameters::Get(); + overlappingMatrix_ = new OverlappingMatrix(M.istlMatrix(), + borderListCreator.borderList(), + borderListCreator.blackList(), + overlapSize); + + // create the overlapping vectors for the residual and the + // solution + overlappingb_ = new OverlappingVector(overlappingMatrix_->overlap()); + overlappingx_ = new OverlappingVector(*overlappingb_); + + // writeOverlapToVTK_(); + } + + /*! + * \brief Assign values to the internal data structure for the residual vector. + * + * This method also cares about synchronizing that vector with the peer processes. + */ + void setResidual(const Vector& b) + { + // copy the interior values of the non-overlapping residual vector to the + // overlapping one + overlappingb_->assignAddBorder(b); + } + + /*! + * \brief Retrieve the synchronized internal residual vector. + * + * This only deals with entries which are local to the current process. + */ + void getResidual(Vector& b) const + { + // update the non-overlapping vector with the overlapping one + overlappingb_->assignTo(b); + } + + /*! + * \brief Sets the values of the residual's Jacobian matrix. + * + * This method also synchronizes the data structure across the processes which are + * involved in the simulation run. + */ + void setMatrix(const SparseMatrixAdapter& M) + { + overlappingMatrix_->assignFromNative(M.istlMatrix()); + overlappingMatrix_->syncAdd(); + } + + /*! + * \brief Actually solve the linear system of equations. + * + * \return true if the residual reduction could be achieved, else false. + */ + bool solve(Vector& x) + { + (*overlappingx_) = 0.0; + + auto parPreCond = asImp_().preparePreconditioner_(); + auto precondCleanupFn = [this]() -> void + { this->asImp_().cleanupPreconditioner_(); }; + auto precondCleanupGuard = Opm::make_guard(precondCleanupFn); + // create the parallel scalar product and the parallel operator + ParallelScalarProduct parScalarProduct(overlappingMatrix_->overlap()); + ParallelOperator parOperator(*overlappingMatrix_); + + // retrieve the linear solver + auto solver = asImp_().prepareSolver_(parOperator, + parScalarProduct, + *parPreCond); + + auto cleanupSolverFn = + [this]() -> void + { this->asImp_().cleanupSolver_(); }; + GenericGuard solverGuard(cleanupSolverFn); + + // run the linear solver and have some fun + auto result = asImp_().runSolver_(solver); + // store number of iterations used + lastIterations_ = result.second; + + // copy the result back to the non-overlapping vector + overlappingx_->assignTo(x); + + // return the result of the solver + return result.first; + } + + /*! + * \brief Return number of iterations used during last solve. + */ + size_t iterations () const + { return lastIterations_; } + +protected: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } + + void cleanup_() + { + // create the overlapping Jacobian matrix and vectors + delete overlappingMatrix_; + delete overlappingb_; + delete overlappingx_; + + overlappingMatrix_ = 0; + overlappingb_ = 0; + overlappingx_ = 0; + } + + std::shared_ptr preparePreconditioner_() + { + int preconditionerIsReady = 1; + try { + // update sequential preconditioner + precWrapper_.prepare(*overlappingMatrix_); + } + catch (const Dune::Exception& e) { + std::cout << "Preconditioner threw exception \"" << e.what() + << " on rank " << overlappingMatrix_->overlap().myRank() + << "\n" << std::flush; + preconditionerIsReady = 0; + } + + // make sure that the preconditioner is also ready on all peer + // ranks. + preconditionerIsReady = simulator_.gridView().comm().min(preconditionerIsReady); + if (!preconditionerIsReady) + throw NumericalProblem("Creating the preconditioner failed"); + + // create the parallel preconditioner + return std::make_shared(precWrapper_.get(), overlappingMatrix_->overlap()); + } + + void cleanupPreconditioner_() + { + precWrapper_.cleanup(); + } + + void writeOverlapToVTK_() + { + for (int lookedAtRank = 0; + lookedAtRank < simulator_.gridView().comm().size(); ++lookedAtRank) { + std::cout << "writing overlap for rank " << lookedAtRank << "\n" << std::flush; + using VtkField = Dune::BlockVector >; + int n = simulator_.gridView().size(/*codim=*/dimWorld); + VtkField isInOverlap(n); + VtkField rankField(n); + isInOverlap = 0.0; + rankField = 0.0; + assert(rankField.two_norm() == 0.0); + assert(isInOverlap.two_norm() == 0.0); + auto vIt = simulator_.gridView().template begin(); + const auto& vEndIt = simulator_.gridView().template end(); + const auto& overlap = overlappingMatrix_->overlap(); + for (; vIt != vEndIt; ++vIt) { + int nativeIdx = simulator_.model().vertexMapper().map(*vIt); + int localIdx = overlap.foreignOverlap().nativeToLocal(nativeIdx); + if (localIdx < 0) + continue; + rankField[nativeIdx] = simulator_.gridView().comm().rank(); + if (overlap.peerHasIndex(lookedAtRank, localIdx)) + isInOverlap[nativeIdx] = 1.0; + } + + using VtkWriter = Dune::VTKWriter; + VtkWriter writer(simulator_.gridView(), Dune::VTK::conforming); + writer.addVertexData(isInOverlap, "overlap"); + writer.addVertexData(rankField, "rank"); + + std::ostringstream oss; + oss << "overlap_rank=" << lookedAtRank; + writer.write(oss.str().c_str(), Dune::VTK::ascii); + } + } + + const Simulator& simulator_; + int gridSequenceNumber_; + size_t lastIterations_; + + OverlappingMatrix *overlappingMatrix_; + OverlappingVector *overlappingb_; + OverlappingVector *overlappingx_; + + PreconditionerWrapper precWrapper_; +}; +}} // namespace Linear, Opm + +namespace Opm::Properties { + +//! by default use the same kind of floating point values for the linearization and for +//! the linear solve +template +struct LinearSolverScalar +{ using type = GetPropType; }; + +template +struct OverlappingMatrix +{ +private: + static constexpr int numEq = getPropValue(); + using LinearSolverScalar = GetPropType; + using MatrixBlock = Opm::MatrixBlock; + using NonOverlappingMatrix = Dune::BCRSMatrix; + +public: + using type = Opm::Linear::OverlappingBCRSMatrix; +}; + +template +struct Overlap +{ using type = typename GetPropType::Overlap; }; + +template +struct OverlappingVector +{ + static constexpr int numEq = getPropValue(); + using LinearSolverScalar = GetPropType; + using VectorBlock = Dune::FieldVector; + using Overlap = GetPropType; + using type = Opm::Linear::OverlappingBlockVector; +}; + +template +struct OverlappingScalarProduct +{ + using OverlappingVector = GetPropType; + using Overlap = GetPropType; + using type = Opm::Linear::OverlappingScalarProduct; +}; + +template +struct OverlappingLinearOperator +{ + using OverlappingMatrix = GetPropType; + using OverlappingVector = GetPropType; + using type = Opm::Linear::OverlappingOperator; +}; + +template +struct PreconditionerWrapper +{ using type = Opm::Linear::PreconditionerWrapperILU; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/simulators/linalg/parallelbicgstabbackend.hh b/opm/simulators/linalg/parallelbicgstabbackend.hh new file mode 100644 index 00000000000..8b581990deb --- /dev/null +++ b/opm/simulators/linalg/parallelbicgstabbackend.hh @@ -0,0 +1,176 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::ParallelBiCGStabSolverBackend + */ +#ifndef EWOMS_PARALLEL_BICGSTAB_BACKEND_HH +#define EWOMS_PARALLEL_BICGSTAB_BACKEND_HH + +#include +#include +#include +#include +#include +#include + +#include + +namespace Opm::Linear { + +template +class ParallelBiCGStabSolverBackend; + +} // namespace Opm::Linear + +namespace Opm::Properties { + +// Create new type tags +namespace TTag { + +struct ParallelBiCGStabLinearSolver +{ using InheritsFrom = std::tuple; }; + +} // end namespace TTag + +template +struct LinearSolverBackend +{ using type = Opm::Linear::ParallelBiCGStabSolverBackend; }; + +} // namespace Opm::Properties + +namespace Opm::Linear { + +/*! + * \ingroup Linear + * + * \brief Implements a generic linear solver abstraction. + * + * Chosing the preconditioner works by setting the "PreconditionerWrapper" property: + * + * \code + * template + * struct PreconditionerWrapper + * { using type = Opm::Linear::PreconditionerWrapper$PRECONDITIONER; }; + * \endcode + * + * Where the choices possible for '\c $PRECONDITIONER' are: + * - \c Jacobi: A Jacobi preconditioner + * - \c GaussSeidel: A Gauss-Seidel preconditioner + * - \c SSOR: A symmetric successive overrelaxation (SSOR) preconditioner + * - \c SOR: A successive overrelaxation (SOR) preconditioner + * - \c ILUn: An ILU(n) preconditioner + * - \c ILU0: An ILU(0) preconditioner. The results of this + * preconditioner are the same as setting the + * PreconditionerOrder property to 0 and using the ILU(n) + * preconditioner. The reason for the existence of ILU0 is + * that it is computationally cheaper because it does not + * need to consider things which are only required for + * higher orders + */ +template +class ParallelBiCGStabSolverBackend : public ParallelBaseBackend +{ + using ParentType = ParallelBaseBackend; + + using Scalar = GetPropType; + using Simulator = GetPropType; + using SparseMatrixAdapter = GetPropType; + + using ParallelOperator = typename ParentType::ParallelOperator; + using OverlappingVector = typename ParentType::OverlappingVector; + using ParallelPreconditioner = typename ParentType::ParallelPreconditioner; + using ParallelScalarProduct = typename ParentType::ParallelScalarProduct; + + using MatrixBlock = typename SparseMatrixAdapter::MatrixBlock; + + using RawLinearSolver = BiCGStabSolver; + + static_assert(std::is_same >::value, + "The ParallelIstlSolverBackend linear solver backend requires the IstlSparseMatrixAdapter"); + +public: + ParallelBiCGStabSolverBackend(const Simulator& simulator) + : ParentType(simulator) + { } + + static void registerParameters() + { + ParentType::registerParameters(); + + Parameters::Register> + ("The maximum residual error which the linear solver tolerates" + " without giving up"); + } + +protected: + friend ParentType; + + std::shared_ptr prepareSolver_(ParallelOperator& parOperator, + ParallelScalarProduct& parScalarProduct, + ParallelPreconditioner& parPreCond) + { + const auto& gridView = this->simulator_.gridView(); + using CCC = CombinedCriterion; + + Scalar linearSolverTolerance = Parameters::Get>(); + Scalar linearSolverAbsTolerance = Parameters::Get>(); + if(linearSolverAbsTolerance < 0.0) + linearSolverAbsTolerance = this->simulator_.model().newtonMethod().tolerance() / 100.0; + + convCrit_.reset(new CCC(gridView.comm(), + /*residualReductionTolerance=*/linearSolverTolerance, + /*absoluteResidualTolerance=*/linearSolverAbsTolerance, + Parameters::Get>())); + + auto bicgstabSolver = + std::make_shared(parPreCond, *convCrit_, parScalarProduct); + + int verbosity = 0; + if (parOperator.overlap().myRank() == 0) + verbosity = Parameters::Get(); + bicgstabSolver->setVerbosity(verbosity); + bicgstabSolver->setMaxIterations(Parameters::Get()); + bicgstabSolver->setLinearOperator(&parOperator); + bicgstabSolver->setRhs(this->overlappingb_); + + return bicgstabSolver; + } + + std::pair runSolver_(std::shared_ptr solver) + { + bool converged = solver->apply(*this->overlappingx_); + return std::make_pair(converged, int(solver->report().iterations())); + } + + void cleanupSolver_() + { /* nothing to do */ } + + std::unique_ptr > convCrit_; +}; + +} // namespace Opm::Linear + +#endif diff --git a/opm/simulators/linalg/parallelistlbackend.hh b/opm/simulators/linalg/parallelistlbackend.hh new file mode 100644 index 00000000000..314c3a1e9ab --- /dev/null +++ b/opm/simulators/linalg/parallelistlbackend.hh @@ -0,0 +1,161 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::ParallelIstlSolverBackend + */ +#ifndef EWOMS_PARALLEL_ISTL_BACKEND_HH +#define EWOMS_PARALLEL_ISTL_BACKEND_HH + +#include "linalgproperties.hh" +#include "parallelbasebackend.hh" +#include "istlsolverwrappers.hh" +#include "istlsparsematrixadapter.hh" + +#include + +namespace Opm::Properties::TTag { + +// Create new type tag +struct ParallelIstlLinearSolver { using InheritsFrom = std::tuple; }; + +} // namespace Opm::Properties::TTag + +namespace Opm::Linear { +/*! + * \ingroup Linear + * + * \brief Provides all unmodified linear solvers from dune-istl + * + * To set the linear solver, use + * \code + * template + * struct LinearSolverWrapper + * { using type = Opm::Linear::SolverWrapper$SOLVER; }; + * \endcode + * + * The possible choices for '\c $SOLVER' are: + * - \c Richardson: A fixpoint solver using the Richardson iteration + * - \c SteepestDescent: The steepest descent solver + * - \c ConjugatedGradients: A conjugated gradients solver + * - \c BiCGStab: A stabilized bi-conjugated gradients solver + * - \c MinRes: A solver based on the minimized residual algorithm + * - \c RestartedGMRes: A restarted GMRES solver + * + * Chosing the preconditioner works in an analogous way: + * \code + * template + * struct PreconditionerWrapper + * { using type = Opm::Linear::PreconditionerWrapper$PRECONDITIONER; }; + * \endcode + * + * Where the choices possible for '\c $PRECONDITIONER' are: + * - \c Jacobi: A Jacobi preconditioner + * - \c GaussSeidel: A Gauss-Seidel preconditioner + * - \c SSOR: A symmetric successive overrelaxation (SSOR) preconditioner + * - \c SOR: A successive overrelaxation (SOR) preconditioner + * - \c ILUn: An ILU(n) preconditioner + * - \c ILU0: A specialized (and optimized) ILU(0) preconditioner + */ +template +class ParallelIstlSolverBackend : public ParallelBaseBackend +{ + using ParentType = ParallelBaseBackend; + + using Scalar = GetPropType; + using Simulator = GetPropType; + using LinearSolverWrapper = GetPropType; + using SparseMatrixAdapter = GetPropType; + + using ParallelOperator = typename ParentType::ParallelOperator; + using OverlappingVector = typename ParentType::OverlappingVector; + using ParallelPreconditioner = typename ParentType::ParallelPreconditioner; + using ParallelScalarProduct = typename ParentType::ParallelScalarProduct; + + using MatrixBlock = typename SparseMatrixAdapter::MatrixBlock; + using RawLinearSolver = typename LinearSolverWrapper::RawSolver; + + static_assert(std::is_same >::value, + "The ParallelIstlSolverBackend linear solver backend requires the IstlSparseMatrixAdapter"); + +public: + ParallelIstlSolverBackend(const Simulator& simulator) + : ParentType(simulator) + { } + + /*! + * \brief Register all run-time parameters for the linear solver. + */ + static void registerParameters() + { + ParentType::registerParameters(); + + LinearSolverWrapper::registerParameters(); + } + +protected: + friend ParentType; + + std::shared_ptr prepareSolver_(ParallelOperator& parOperator, + ParallelScalarProduct& parScalarProduct, + ParallelPreconditioner& parPreCond) + { + return solverWrapper_.get(parOperator, + parScalarProduct, + parPreCond); + } + + void cleanupSolver_() + { + solverWrapper_.cleanup(); + } + + std::pair runSolver_(std::shared_ptr solver) + { + Dune::InverseOperatorResult result; + solver->apply(*this->overlappingx_, *this->overlappingb_, result); + return std::make_pair(result.converged, result.iterations); + } + + LinearSolverWrapper solverWrapper_; +}; + +} // namespace Opm::Linear + +namespace Opm::Properties { + +template +struct LinearSolverBackend +{ using type = Opm::Linear::ParallelIstlSolverBackend; }; + +template +struct LinearSolverWrapper +{ using type = Opm::Linear::SolverWrapperBiCGStab; }; + +template +struct PreconditionerWrapper +{ using type = Opm::Linear::PreconditionerWrapperILU; }; + +} // namespace Opm::Properties + +#endif diff --git a/opm/simulators/linalg/residreductioncriterion.hh b/opm/simulators/linalg/residreductioncriterion.hh new file mode 100644 index 00000000000..6d1875abc29 --- /dev/null +++ b/opm/simulators/linalg/residreductioncriterion.hh @@ -0,0 +1,150 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::ResidReductionCriterion + */ +#ifndef EWOMS_RESID_REDUCTION_CRITERION_HH +#define EWOMS_RESID_REDUCTION_CRITERION_HH + +#include "convergencecriterion.hh" + +#include + +namespace Opm { +namespace Linear { + +/*! \addtogroup Linear + * \{ + */ + +/*! + * \brief Provides a convergence criterion which looks at the + * reduction of the two-norm of the residual for the linear + * solvers. + * + * For the ResidReductionCriterion, the error of the solution is defined + * as + * \f[ e^k = \frac{\left| A x_k - b \right|}{\left| A x_0 - b \right|}\;, \f] + */ +template +class ResidReductionCriterion : public ConvergenceCriterion +{ + using Scalar = typename Vector::field_type; + +public: + ResidReductionCriterion(Dune::ScalarProduct& scalarProduct, + Scalar tolerance = 1e-6) + : scalarProduct_(scalarProduct), tolerance_(tolerance) + {} + + /*! + * \brief Set the maximum allowed weighted maximum of the reduction of the + * linear residual. + */ + void setTolerance(Scalar tol) + { tolerance_ = tol; } + + /*! + * \brief Return the maximum allowed weighted maximum of the reduction of the linear residual. + */ + Scalar tolerance() const + { return tolerance_; } + + /*! + * \copydoc ConvergenceCriterion::setInitial(const Vector& , const Vector& ) + */ + void setInitial(const Vector&, const Vector& curResid) + { + static constexpr Scalar eps = std::numeric_limits::min()*1e10; + + // make sure that we don't allow an initial error of 0 to avoid + // divisions by zero + curDefect_ = scalarProduct_.norm(curResid); + lastDefect_ = curDefect_; + initialDefect_ = std::max(curDefect_, eps); + } + + /*! + * \copydoc ConvergenceCriterion::update(const Vector& , const Vector& ) + */ + void update(const Vector&, + const Vector&, + const Vector& curResid) + { + lastDefect_ = curDefect_; + curDefect_ = scalarProduct_.norm(curResid); + } + + /*! + * \copydoc ConvergenceCriterion::converged() + */ + bool converged() const + { return accuracy() <= tolerance(); } + + /*! + * \copydoc ConvergenceCriterion::accuracy() + */ + Scalar accuracy() const + { return curDefect_/initialDefect_; } + + /*! + * \copydoc ConvergenceCriterion::printInitial() + */ + void printInitial(std::ostream& os=std::cout) const + { + os << std::setw(20) << "iteration "; + os << std::setw(20) << "residual "; + os << std::setw(20) << "accuracy "; + os << std::setw(20) << "rate "; + os << std::endl; + } + + /*! + * \copydoc ConvergenceCriterion::print() + */ + void print(Scalar iter, std::ostream& os=std::cout) const + { + static constexpr Scalar eps = std::numeric_limits::min()*1e10; + + os << std::setw(20) << iter << " "; + os << std::setw(20) << curDefect_ << " "; + os << std::setw(20) << accuracy() << " "; + os << std::setw(20) << (lastDefect_/std::max(eps, curDefect_)) << " "; + os << std::endl; + } + +private: + Dune::ScalarProduct& scalarProduct_; + + Scalar tolerance_; + Scalar initialDefect_; + Scalar curDefect_; + Scalar lastDefect_; +}; + +//! \} end documentation + +}} // end namespace Linear, Opm + +#endif diff --git a/opm/simulators/linalg/superlubackend.hh b/opm/simulators/linalg/superlubackend.hh new file mode 100644 index 00000000000..f33b36e3617 --- /dev/null +++ b/opm/simulators/linalg/superlubackend.hh @@ -0,0 +1,191 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::SuperLUBackend + */ +#ifndef EWOMS_SUPER_LU_BACKEND_HH +#define EWOMS_SUPER_LU_BACKEND_HH + +#if HAVE_SUPERLU + +#include +#include +#include + +#include + +#include + +#include + +#include + +namespace Opm::Properties::TTag { + +struct SuperLULinearSolver {}; + +} // namespace Opm::Properties::TTag + +namespace Opm::Linear { + +template +class SuperLUSolve_; + +/*! + * \ingroup Linear + * \brief A linear solver backend for the SuperLU sparse matrix library. + */ +template +class SuperLUBackend +{ + using Scalar = GetPropType; + using Simulator = GetPropType; + using SparseMatrixAdapter = GetPropType; + using Matrix = typename SparseMatrixAdapter::block_type; + + static_assert(std::is_same::value, + "The SuperLU linear solver backend requires the IstlSparseMatrixAdapter"); + +public: + SuperLUBackend(Simulator&) + {} + + static void registerParameters() + { + Parameters::Register + ("The verbosity level of the linear solver"); + } + + /*! + * \brief Causes the solve() method to discared the structure of the linear system of + * equations the next time it is called. + * + * Since the SuperLU backend does not create any internal matrices, this is a no-op. + */ + void eraseMatrix() + { } + + void prepare(const SparseMatrixAdapter& M, const Vector& b) + { } + + void setResidual(const Vector& b) + { b_ = &b; } + + void getResidual(Vector& b) const + { b = *b_; } + + void setMatrix(const SparseMatrixAdapter& M) + { M_ = &M; } + + bool solve(Vector& x) + { return SuperLUSolve_::solve_(*M_, x, *b_); } + +private: + const Matrix* M_; + Vector* b_; +}; + +template +class SuperLUSolve_ +{ +public: + static bool solve_(const Matrix& A, Vector& x, const Vector& b) + { + Vector bTmp(b); + + int verbosity = Parameters::Get(); + Dune::InverseOperatorResult result; + Dune::SuperLU solver(A, verbosity > 0); + solver.apply(x, bTmp, result); + + if (result.converged) { + // make sure that the result only contains finite values. + Scalar tmp = 0; + for (unsigned i = 0; i < x.size(); ++i) { + const auto& xi = x[i]; + for (unsigned j = 0; j < Vector::block_type::dimension; ++j) + tmp += xi[j]; + } + result.converged = std::isfinite(tmp); + } + + return result.converged; + } +}; + +// the following is required to make the SuperLU adapter of dune-istl happy with +// quadruple precision math on Dune 2.4. this is because the most which SuperLU can +// handle is double precision (i.e., the linear systems of equations are always solved +// with at most double precision if chosing SuperLU as the linear solver...) +#if HAVE_QUAD +template +class SuperLUSolve_<__float128, TypeTag, Matrix, Vector> +{ +public: + static bool solve_(const Matrix& A, + Vector& x, + const Vector& b) + { + static const int numEq = getPropValue(); + using DoubleEqVector = Dune::FieldVector; + using DoubleEqMatrix = Dune::FieldMatrix; + using DoubleVector = Dune::BlockVector; + using DoubleMatrix = Dune::BCRSMatrix; + + // copy the inputs into the double precision data structures + DoubleVector bDouble(b); + DoubleVector xDouble(x); + DoubleMatrix ADouble(A); + + bool res = + SuperLUSolve_::solve_(ADouble, + xDouble, + bDouble); + + // copy the result back into the quadruple precision vector. + x = xDouble; + + return res; + } +}; +#endif + +} // namespace Linear +} // namespace Opm + +namespace Opm::Properties { + +template +struct LinearSolverVerbosity +{ static constexpr int value = 0; }; + +template +struct LinearSolverBackend +{ using type = Opm::Linear::SuperLUBackend; }; + +} // namespace Opm::Properties + +#endif // HAVE_SUPERLU + +#endif diff --git a/opm/simulators/linalg/vertexborderlistfromgrid.hh b/opm/simulators/linalg/vertexborderlistfromgrid.hh new file mode 100644 index 00000000000..18d2d18605c --- /dev/null +++ b/opm/simulators/linalg/vertexborderlistfromgrid.hh @@ -0,0 +1,136 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::VertexBorderListFromGrid + */ +#ifndef EWOMS_VERTEX_BORDER_LIST_FROM_GRID_HH +#define EWOMS_VERTEX_BORDER_LIST_FROM_GRID_HH + +#include "overlaptypes.hh" +#include "blacklist.hh" + +#include +#include +#include + +namespace Opm { +namespace Linear { +/*! + * \brief Uses communication on the grid to find the initial seed list + * of indices. + * + * \todo implement this class generically. For this, it must be + * possible to query the mapper whether it contains entities of + * a given codimension without the need to hand it an actual + * entity. + */ +template +class VertexBorderListFromGrid + : public Dune::CommDataHandleIF, + int> +{ + static const int dimWorld = GridView::dimensionworld; + +public: + VertexBorderListFromGrid(const GridView& gridView, const VertexMapper& map) + : gridView_(gridView), map_(map) + { + gridView.communicate(*this, + Dune::InteriorBorder_InteriorBorder_Interface, + Dune::ForwardCommunication); + + auto vIt = gridView.template begin(); + const auto& vEndIt = gridView.template end(); + for (; vIt != vEndIt; ++vIt) { + if (vIt->partitionType() != Dune::InteriorEntity + && vIt->partitionType() != Dune::BorderEntity) + { + Index vIdx = static_cast(map_.index(*vIt)); + blackList_.addIndex(vIdx); + } + } + } + + // data handle methods + bool contains(int dim, int codim) const + { return dim == codim; } + +#if DUNE_VERSION_LT(DUNE_GRID, 2, 8) + bool fixedsize(int, int) const +#else + bool fixedSize(int, int) const +#endif + { return true; } + + template + size_t size(const EntityType&) const + { return 2; } + + template + void gather(MessageBufferImp& buff, const EntityType& e) const + { + buff.write(static_cast(gridView_.comm().rank())); + buff.write(static_cast(map_.index(e))); + } + + template + void scatter(MessageBufferImp& buff, const EntityType& e, size_t) + { + BorderIndex bIdx; + + bIdx.localIdx = static_cast(map_.index(e)); + { + int tmp; + buff.read(tmp); + bIdx.peerRank = static_cast(tmp); + } + { + int tmp; + buff.read(tmp); + bIdx.peerIdx = static_cast(tmp); + } + bIdx.borderDistance = 0; + + borderList_.push_back(bIdx); + } + + // Access to the border list. + const BorderList& borderList() const + { return borderList_; } + + // Access to the black-list indices. + const BlackList& blackList() const + { return blackList_; } + +private: + const GridView gridView_; + const VertexMapper& map_; + BorderList borderList_; + BlackList blackList_; +}; + +} // namespace Linear +} // namespace Opm + +#endif diff --git a/opm/simulators/linalg/weightedresidreductioncriterion.hh b/opm/simulators/linalg/weightedresidreductioncriterion.hh new file mode 100644 index 00000000000..3f1e9ea2b10 --- /dev/null +++ b/opm/simulators/linalg/weightedresidreductioncriterion.hh @@ -0,0 +1,320 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \copydoc Opm::Linear::WeightedResidualReductionCriterion + */ +#ifndef EWOMS_WEIGHTED_RESIDUAL_REDUCTION_CRITERION_HH +#define EWOMS_WEIGHTED_RESIDUAL_REDUCTION_CRITERION_HH + +#include "convergencecriterion.hh" + +#include + +namespace Opm { +namespace Linear { + +/*! \addtogroup Linear + * \{ + */ + +/*! + * \brief Convergence criterion which looks at the weighted absolute + * value of the residual + * + * For the WeightedResidualReductionCriterion, the error of the + * solution is defined as + * \f[ e^k = \max_i\{ \left| w_i r^k_i \right| \}\;, \f] + * + * where \f$r^k = \mathbf{A} x^k - b \f$ is the residual for the + * k-th iterative solution vector \f$x^k\f$ and \f$w_i\f$ is the + * weight of the \f$i\f$-th linear equation. + * + * In addition, to the reduction of the maximum defect, the linear + * solver is also considered to be converged, if the defect goes below + * a given absolute limit. + */ +template +class WeightedResidualReductionCriterion : public ConvergenceCriterion +{ + using Scalar = typename Vector::field_type; + using BlockType = typename Vector::block_type; + +public: + WeightedResidualReductionCriterion(const CollectiveCommunication& comm) + : comm_(comm) + {} + + WeightedResidualReductionCriterion(const CollectiveCommunication& comm, + const Vector& residWeights, + Scalar residualReductionTolerance, + Scalar fixPointTolerance, + Scalar absResidualTolerance = 0.0, + Scalar maxError = 0.0) + : comm_(comm), + residWeightVec_(residWeights), + fixPointTolerance_(fixPointTolerance), + residualReductionTolerance_(residualReductionTolerance), + absResidualTolerance_(absResidualTolerance), + maxError_(maxError) + { } + + /*! + * \brief Sets the relative weight of each row of the residual. + * + * For the WeightedResidualReductionCriterion, the error of the solution is + * defined as + * \f[ e^k = \max_i\{ \left| w_i r^k_i \right| \}\;, \f] + * + * where \f$r^k = \mathbf{A} x^k - b \f$ is the residual for the + * k-th iterative solution vector \f$x^k\f$ and \f$w_i\f$ is the + * weight of the \f$i\f$-th linear equation. + * + * This method is not part of the generic ConvergenceCriteria interface. + * + * \param residWeightVec A Dune::BlockVector > + * with the relative weights of the linear equations + */ + void setResidualWeight(const Vector& residWeightVec) + { residWeightVec_ = residWeightVec; } + + /*! + * \brief Return the relative weight of a row of the residual. + * + * \param outerIdx The index of the outer vector (i.e. Dune::BlockVector) + * \param innerIdx The index of the inner vector (i.e. Dune::FieldVector) + */ + Scalar residualWeight(size_t outerIdx, unsigned innerIdx) const + { + return (residWeightVec_.size() == 0) + ? 1.0 + : residWeightVec_[outerIdx][innerIdx]; + } + + /*! + * \brief Sets the residual reduction tolerance. + */ + void setResidualReductionTolerance(Scalar tol) + { residualReductionTolerance_ = tol; } + + /*! + * \brief Returns the tolerance of the residual reduction of the solution. + */ + Scalar residualReductionTolerance() const + { return residualReductionTolerance_; } + + /*! + * \brief Sets the maximum absolute tolerated residual. + */ + void setResidualTolerance(Scalar tol) + { absResidualTolerance_ = tol; } + + /*! + * \brief Returns the maximum absolute tolerated residual. + */ + Scalar absResidualTolerance() const + { return absResidualTolerance_; } + + /*! + * \brief Returns the reduction of the weighted maximum of the + * residual compared to the initial solution. + */ + Scalar residualAccuracy() const + { return residualError_/std::max(1e-20, initialResidualError_); } + + /*! + * \brief Sets the fix-point tolerance. + */ + void setFixPointTolerance(Scalar tol) + { fixPointTolerance_ = tol; } + + /*! + * \brief Returns the maximum tolerated difference between two + * iterations to be met before a solution is considered to be + * converged. + */ + Scalar fixPointTolerance() const + { return fixPointTolerance_; } + + /*! + * \brief Returns the weighted maximum of the difference + * between the last two iterative solutions. + */ + Scalar fixPointAccuracy() const + { return fixPointError_; } + + /*! + * \copydoc ConvergenceCriterion::setInitial(const Vector& , const Vector& ) + */ + void setInitial(const Vector& curSol, const Vector& curResid) + { + lastResidualError_ = 1e100; + + lastSolVec_ = curSol; + updateErrors_(curSol, curResid); + // the fix-point error is not applicable for the initial solution! + fixPointError_ = 1e100; + + // make sure that we don't allow an initial error of 0 to avoid + // divisions by zero + residualError_ = std::max(residualError_, 1e-20); + initialResidualError_ = residualError_; + } + + /*! + * \copydoc ConvergenceCriterion::update(const Vector& , const Vector& ) + */ + void update(const Vector& curSol, + const Vector&, + const Vector& curResid) + { + lastResidualError_ = residualError_; + updateErrors_(curSol, curResid); + } + + /*! + * \copydoc ConvergenceCriterion::converged() + */ + bool converged() const + { + // we're converged if the solution is better than the tolerance + // fix-point and residual tolerance. + return + residualAccuracy() <= residualReductionTolerance() || + residualError_ <= absResidualTolerance_; + } + + /*! + * \copydoc ConvergenceCriterion::failed() + */ + bool failed() const + { + return + (!converged() && fixPointError_ <= fixPointTolerance_) + || residualError_ > maxError_; + } + + /*! + * \copydoc ConvergenceCriterion::accuracy() + * + * For the accuracy we only take the residual into account, + */ + Scalar accuracy() const + { return residualError_; } + + /*! + * \copydoc ConvergenceCriterion::printInitial() + */ + void printInitial(std::ostream& os = std::cout) const + { + os << std::setw(20) << " Iter "; + os << std::setw(20) << " Delta "; + os << std::setw(20) << " Residual "; + os << std::setw(20) << " ResidRed "; + os << std::setw(20) << " Rate "; + os << std::endl; + } + + /*! + * \copydoc ConvergenceCriterion::print() + */ + void print(Scalar iter, std::ostream& os = std::cout) const + { + static constexpr Scalar eps = std::numeric_limits::min()*1e10; + + os << std::setw(20) << iter << " "; + os << std::setw(20) << fixPointAccuracy() << " "; + os << std::setw(20) << residualError_ << " "; + os << std::setw(20) << 1.0/residualAccuracy() << " "; + os << std::setw(20) << lastResidualError_ / std::max(residualError_, eps) << " "; + os << std::endl << std::flush; + } + +private: + // update the weighted absolute residual + void updateErrors_(const Vector& curSol, const Vector& curResid) + { + residualError_ = 0.0; + fixPointError_ = 0.0; + for (size_t i = 0; i < curResid.size(); ++i) { + for (unsigned j = 0; j < BlockType::dimension; ++j) { + residualError_ = + std::max(residualError_, + residualWeight(i, j)*std::abs(curResid[i][j])); + fixPointError_ = + std::max(fixPointError_, + std::abs(curSol[i][j] - lastSolVec_[i][j]) + /std::max(1.0, curSol[i][j])); + } + } + lastSolVec_ = curSol; + + residualError_ = comm_.max(residualError_); + fixPointError_ = comm_.max(fixPointError_); + } + + const CollectiveCommunication& comm_; + + // the weights of the components of the residual + Vector residWeightVec_; + + // solution vector of the last iteration + Vector lastSolVec_; + + // the maximum of the weighted difference between the last two + // iterations + Scalar fixPointError_; + + // the maximum allowed relative tolerance for difference of the + // solution of two iterations + Scalar fixPointTolerance_; + + // the maximum of the absolute weighted residual of the last + // iteration + Scalar residualError_; + + // the maximum of the absolute weighted difference of the last + // iteration + Scalar lastResidualError_; + + // the maximum of the absolute weighted residual of the initial + // solution + Scalar initialResidualError_; + + // the maximum allowed relative tolerance of the residual for the + // solution to be considered converged + Scalar residualReductionTolerance_; + + // the maximum allowed absolute tolerance of the residual for the + // solution to be considered converged + Scalar absResidualTolerance_; + + // The maximum error which is tolerated before we fail. + Scalar maxError_; +}; + +//! \} end documentation + +}} // end namespace Linear, Opm + +#endif diff --git a/parallelUnitTests.cmake b/parallelUnitTests.cmake index 7746d25063c..906b54f5db5 100644 --- a/parallelUnitTests.cmake +++ b/parallelUnitTests.cmake @@ -182,3 +182,15 @@ opm_add_test(test_rstconv_parallel PROCESSORS 4 ) + +opm_add_test(test_mpiutil + CONDITION + MPI_FOUND AND Boost_UNIT_TEST_FRAMEWORK_FOUND + SOURCES + tests/models/test_mpiutil.cpp + DRIVER_ARGS + -n 4 + -b ${PROJECT_BINARY_DIR} + PROCESSORS + 4 +) diff --git a/tests/data/co2injection.dgf b/tests/data/co2injection.dgf new file mode 100644 index 00000000000..58ada4f2a5b --- /dev/null +++ b/tests/data/co2injection.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +60 40 % second corner +24 16 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/cuvette_11x4.dgf b/tests/data/cuvette_11x4.dgf new file mode 100644 index 00000000000..099bdf5b361 --- /dev/null +++ b/tests/data/cuvette_11x4.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +1.5 0.74 % second corner +11 4 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/cuvette_44x24.dgf b/tests/data/cuvette_44x24.dgf new file mode 100644 index 00000000000..246b88a1a3e --- /dev/null +++ b/tests/data/cuvette_44x24.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +1.5 0.74 % second corner +44 24 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +#GridParameter + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/fracture-raw.art b/tests/data/fracture-raw.art new file mode 100644 index 00000000000..0502d41409a --- /dev/null +++ b/tests/data/fracture-raw.art @@ -0,0 +1,5747 @@ +%% Version 3.0 +%% VertexNumber: 980 +%% EdgeNumber: 2862 +%% FaceNumber: 1883 +%% ElementNumber: 0 +%% DO NOT CHANGE LINES ABOVE !!!! +% NET: Vertices <-> Edges <-> Faces <-> Elements +% Vertices: x y z +5.04381211659609585141e-01 4.97533231402975895108e-01 0.00000000000000000000e+00 +4.79176859439032654109e-01 5.03060894520345525116e-01 0.00000000000000000000e+00 +4.92814373423830720533e-01 5.21504981924616584088e-01 0.00000000000000000000e+00 +5.19747510003616963736e-01 5.13015726097934554595e-01 0.00000000000000000000e+00 +4.66518011741544502691e-01 5.21153439443125865438e-01 0.00000000000000000000e+00 +5.08398147789879573111e-01 5.41093531380963832511e-01 0.00000000000000000000e+00 +5.32170408797252525446e-01 4.83408699515674777913e-01 0.00000000000000000000e+00 +4.74107531636482115722e-01 4.80348392048746464233e-01 0.00000000000000000000e+00 +4.56815662339176586926e-01 4.99413646929592058843e-01 0.00000000000000000000e+00 +4.85743226794746740804e-01 5.43856025634631201626e-01 0.00000000000000000000e+00 +5.44327701000678731980e-01 5.02951411815998228327e-01 0.00000000000000000000e+00 +5.37934439961591071011e-01 5.31068761288003954313e-01 0.00000000000000000000e+00 +4.43841664435444482883e-01 5.24032601724946700550e-01 0.00000000000000000000e+00 +4.63625673796894399725e-01 5.50769184342768447493e-01 0.00000000000000000000e+00 +5.02180302786280452487e-01 4.68416079630047776927e-01 0.00000000000000000000e+00 +5.55401290108422651670e-01 4.82067724015489973421e-01 0.00000000000000000000e+00 +5.61668357120487127254e-01 5.36059473314772438890e-01 0.00000000000000000000e+00 +5.65877813692832964954e-01 5.03326678064102583932e-01 0.00000000000000000000e+00 +5.01991983945220376917e-01 5.64024198737965720696e-01 0.00000000000000000000e+00 +5.25831859165913795273e-01 5.60373336207538708109e-01 0.00000000000000000000e+00 +4.29336583800741689032e-01 4.96621843840030330330e-01 0.00000000000000000000e+00 +4.78555659699045676803e-01 5.67613261065231133884e-01 0.00000000000000000000e+00 +4.41189218335196031706e-01 5.54697006400080350197e-01 0.00000000000000000000e+00 +4.17801965548907916492e-01 5.15006262303412953862e-01 0.00000000000000000000e+00 +4.44662935795446856435e-01 4.76528890907905844365e-01 0.00000000000000000000e+00 +5.82628921756684792221e-01 4.82859882283050712015e-01 0.00000000000000000000e+00 +5.49639252831829017154e-01 5.56727447935163377579e-01 0.00000000000000000000e+00 +4.55106093965890023956e-01 5.71204351207164839899e-01 0.00000000000000000000e+00 +4.61818410740559792682e-01 4.41195947147856315507e-01 0.00000000000000000000e+00 +5.87585970076267116724e-01 5.09143017001427278245e-01 0.00000000000000000000e+00 +5.84762073006075189241e-01 5.33658233618569899370e-01 0.00000000000000000000e+00 +5.38301629191980879341e-01 4.44338786874761526580e-01 0.00000000000000000000e+00 +4.25090260252510510064e-01 5.35445394189495882387e-01 0.00000000000000000000e+00 +5.44366382816051652682e-01 5.79267505215181999212e-01 0.00000000000000000000e+00 +5.72439872866327692513e-01 5.53235737991891518561e-01 0.00000000000000000000e+00 +4.93773628507390260900e-01 5.90004477130633198101e-01 0.00000000000000000000e+00 +5.14644552652626874334e-01 5.95086870582998361812e-01 0.00000000000000000000e+00 +5.75327908935066356655e-01 4.50884993279822288148e-01 0.00000000000000000000e+00 +4.16296066369384376582e-01 5.57895500973866087513e-01 0.00000000000000000000e+00 +4.31777849320982953785e-01 5.74776862115309206125e-01 0.00000000000000000000e+00 +4.70961563845898212310e-01 5.93667140541746962690e-01 0.00000000000000000000e+00 +5.34371441462688445689e-01 5.98521913102683433827e-01 0.00000000000000000000e+00 +6.04519298740856303453e-01 4.88265508159510808195e-01 0.00000000000000000000e+00 +5.98485990313785687356e-01 4.62285217126640390894e-01 0.00000000000000000000e+00 +5.94467742476336158397e-01 5.49862367827835263334e-01 0.00000000000000000000e+00 +6.07657681437660524004e-01 5.15239026058440741096e-01 0.00000000000000000000e+00 +4.03252966024346182206e-01 4.98717208786027543788e-01 0.00000000000000000000e+00 +3.99181279801937904939e-01 5.24811796402988361976e-01 0.00000000000000000000e+00 +5.67452317817838003400e-01 5.82970975850043315880e-01 0.00000000000000000000e+00 +5.34363994103778994216e-01 4.06116698327222858111e-01 0.00000000000000000000e+00 +4.93969120242687242950e-01 4.14344426584772551614e-01 0.00000000000000000000e+00 +5.88620570536615317359e-01 5.78714845538167765859e-01 0.00000000000000000000e+00 +5.80708608231283229806e-01 4.22093547235138866203e-01 0.00000000000000000000e+00 +4.47310709863381783524e-01 5.94806491495438738326e-01 0.00000000000000000000e+00 +4.13379232870119239784e-01 4.74552787602002645873e-01 0.00000000000000000000e+00 +3.95535452948674148832e-01 5.50491926312193902149e-01 0.00000000000000000000e+00 +4.18192004997943789224e-01 4.33487709762532036351e-01 0.00000000000000000000e+00 +4.08151945609360244926e-01 5.78394956833077178437e-01 0.00000000000000000000e+00 +4.24239628615741382056e-01 5.96807715528952287265e-01 0.00000000000000000000e+00 +3.84286724211719221689e-01 4.80496736598262585982e-01 0.00000000000000000000e+00 +6.01053046731309326134e-01 4.35783825135301006171e-01 0.00000000000000000000e+00 +6.19679422178263217269e-01 4.54162136603379174016e-01 0.00000000000000000000e+00 +4.87037830727533205266e-01 6.14997570860896791700e-01 0.00000000000000000000e+00 +6.17452547197256618183e-01 5.46342451655960736900e-01 0.00000000000000000000e+00 +6.09612149420870497174e-01 5.66926038786498831534e-01 0.00000000000000000000e+00 +6.26088945326358503607e-01 5.24901299378764729653e-01 0.00000000000000000000e+00 +6.22162721061535695100e-01 4.75304824826836447293e-01 0.00000000000000000000e+00 +3.71181094823321511278e-01 5.04978577788138371929e-01 0.00000000000000000000e+00 +4.53273162745011892749e-01 4.02108620229177093908e-01 0.00000000000000000000e+00 +3.76386536778308411488e-01 5.35443390527940987766e-01 0.00000000000000000000e+00 +5.56347079343928796469e-01 6.02684559223244376014e-01 0.00000000000000000000e+00 +5.77591186958512192717e-01 6.06772968251510724791e-01 0.00000000000000000000e+00 +5.90062348587013452139e-01 3.97049748664107970963e-01 0.00000000000000000000e+00 +6.27874609985836218407e-01 5.01194891977913337122e-01 0.00000000000000000000e+00 +3.75286256542092688804e-01 5.63873929300993892433e-01 0.00000000000000000000e+00 +5.97136061252130478927e-01 5.98510527848554074737e-01 0.00000000000000000000e+00 +5.54297412461750016455e-01 3.76608824102737438366e-01 0.00000000000000000000e+00 +3.84418226169787158319e-01 5.82029562560140489857e-01 0.00000000000000000000e+00 +4.00459588739887573094e-01 6.04114928135701445555e-01 0.00000000000000000000e+00 +6.40987433164946884823e-01 4.60171291232771506596e-01 0.00000000000000000000e+00 +6.48957465078710105111e-01 5.24836784486168550501e-01 0.00000000000000000000e+00 +4.25670590418737504468e-01 6.19853908775124806674e-01 0.00000000000000000000e+00 +3.65439159703801141887e-01 4.55555902983093230496e-01 0.00000000000000000000e+00 +3.55373786281573678103e-01 4.77174909841470040739e-01 0.00000000000000000000e+00 +6.50539238021342614005e-01 4.83183340959991325381e-01 0.00000000000000000000e+00 +4.86263103858580958416e-01 3.74203100428379509257e-01 0.00000000000000000000e+00 +5.23404030034280154382e-01 3.65241062248128522949e-01 0.00000000000000000000e+00 +6.23399158943302889035e-01 5.84912742368548954630e-01 0.00000000000000000000e+00 +3.51594326884702379488e-01 5.26956887543714946887e-01 0.00000000000000000000e+00 +6.12224070375625428753e-01 4.16612090862076678288e-01 0.00000000000000000000e+00 +6.31044226461179191823e-01 4.32584629355260652517e-01 0.00000000000000000000e+00 +4.62967929588169901933e-01 6.18163590969037946010e-01 0.00000000000000000000e+00 +5.10975461993129598248e-01 6.18347469387335757496e-01 0.00000000000000000000e+00 +5.39676419615726099543e-01 6.21220596607851116566e-01 0.00000000000000000000e+00 +3.54048515966254639942e-01 5.55210057310071114500e-01 0.00000000000000000000e+00 +5.84018768506807450791e-01 3.70305938183275618059e-01 0.00000000000000000000e+00 +6.62294792519909636397e-01 5.07856182804900968542e-01 0.00000000000000000000e+00 +3.80998210953488736230e-01 4.27560939734559275482e-01 0.00000000000000000000e+00 +3.47405678581768517788e-01 4.98722599870560723279e-01 0.00000000000000000000e+00 +6.19668204477434314548e-01 3.96615839443278106469e-01 0.00000000000000000000e+00 +6.40375228491551928833e-01 5.42832049128282090322e-01 0.00000000000000000000e+00 +6.35667458070364510547e-01 5.64819103995806703900e-01 0.00000000000000000000e+00 +3.61422858604766195079e-01 5.85551096336611731630e-01 0.00000000000000000000e+00 +4.51607106588967743832e-01 3.54786837573202784579e-01 0.00000000000000000000e+00 +4.01717106565272596974e-01 3.96690777822213402892e-01 0.00000000000000000000e+00 +6.56955085717254694266e-01 4.43765842209584815414e-01 0.00000000000000000000e+00 +6.49438882811521955851e-01 4.21241903742624212992e-01 0.00000000000000000000e+00 +3.75676543196321210960e-01 6.12186764814799500023e-01 0.00000000000000000000e+00 +3.97481324859495499524e-01 6.27552389200733085062e-01 0.00000000000000000000e+00 +6.65769916345381629341e-01 5.38943081023408820940e-01 0.00000000000000000000e+00 +6.64808532748903835419e-01 4.67300406126091583126e-01 0.00000000000000000000e+00 +6.17829635435273472055e-01 6.13072308049089853554e-01 0.00000000000000000000e+00 +5.48359179999204915390e-01 3.49740766415421289182e-01 0.00000000000000000000e+00 +6.72382586333545861912e-01 4.89997706747901606850e-01 0.00000000000000000000e+00 +6.39371800503695797424e-01 6.01443008091046782404e-01 0.00000000000000000000e+00 +4.88363550336149476738e-01 6.48751566423209080625e-01 0.00000000000000000000e+00 +3.28987703415149879138e-01 5.21628794140893914388e-01 0.00000000000000000000e+00 +6.04083603456085915795e-01 3.78517986103514669782e-01 0.00000000000000000000e+00 +3.35066359093009857872e-01 4.55527931946677566710e-01 0.00000000000000000000e+00 +3.46414861804811624602e-01 4.33044715660587775652e-01 0.00000000000000000000e+00 +3.29880540083057061107e-01 4.77607818571988196332e-01 0.00000000000000000000e+00 +6.54506010631562218371e-01 5.79211833238646556232e-01 0.00000000000000000000e+00 +6.41336834001794309223e-01 3.96962350471460279078e-01 0.00000000000000000000e+00 +4.18659252366442302673e-01 6.49959891980230963782e-01 0.00000000000000000000e+00 +5.62476794564480520044e-01 6.46138377620023351966e-01 0.00000000000000000000e+00 +3.32276678642392908625e-01 5.46641462150357138050e-01 0.00000000000000000000e+00 +5.70247505151627143150e-01 3.49496337792193800809e-01 0.00000000000000000000e+00 +5.25956000912701338024e-01 3.32602918299558736326e-01 0.00000000000000000000e+00 +4.82982242669192818330e-01 3.27239900176446107949e-01 0.00000000000000000000e+00 +6.33794479521530340627e-01 3.74360043117583751737e-01 0.00000000000000000000e+00 +3.36569823389081479448e-01 5.69956897164531128830e-01 0.00000000000000000000e+00 +6.73927183906875559849e-01 5.60809748105118965888e-01 0.00000000000000000000e+00 +6.80889049672721680118e-01 4.50980734591220899787e-01 0.00000000000000000000e+00 +5.98103461638402000844e-01 3.49827733193857626226e-01 0.00000000000000000000e+00 +3.18150086338035764655e-01 4.98658415389850451227e-01 0.00000000000000000000e+00 +4.51519751398569313405e-01 3.19014908545247999339e-01 0.00000000000000000000e+00 +3.91464769720676120102e-01 6.52425474722971965313e-01 0.00000000000000000000e+00 +5.46526312779898382210e-01 3.21377843328032652970e-01 0.00000000000000000000e+00 +6.89907022298711591901e-01 4.78548226705659052183e-01 0.00000000000000000000e+00 +6.72642083473482643718e-01 4.25130915906375972391e-01 0.00000000000000000000e+00 +3.67695871368707405402e-01 6.35841922591686148358e-01 0.00000000000000000000e+00 +6.26913517842358603005e-01 6.41624056878403292714e-01 0.00000000000000000000e+00 +5.94294484862345262499e-01 6.26037308710417295110e-01 0.00000000000000000000e+00 +6.80342576341402049955e-01 5.13851549889606351584e-01 0.00000000000000000000e+00 +6.87599999999999988987e-01 5.35599999999999965006e-01 0.00000000000000000000e+00 +6.26180764207427742463e-01 3.51543887488123207863e-01 0.00000000000000000000e+00 +3.37942128124748830675e-01 5.89146959079305143625e-01 0.00000000000000000000e+00 +3.52263314573209362912e-01 6.06468114623420762044e-01 0.00000000000000000000e+00 +6.63767984646825182082e-01 4.05464607254257425328e-01 0.00000000000000000000e+00 +6.46354877058824328628e-01 6.24073748333571098890e-01 0.00000000000000000000e+00 +4.11354024016489627780e-01 3.50409381995161528511e-01 0.00000000000000000000e+00 +4.54617182040044531810e-01 6.70408330693434195702e-01 0.00000000000000000000e+00 +5.32253575865424766178e-01 6.73891243066385925431e-01 0.00000000000000000000e+00 +3.10888700849437205065e-01 5.40566960802238316575e-01 0.00000000000000000000e+00 +6.62878459782123785615e-01 6.02743474623663177958e-01 0.00000000000000000000e+00 +3.12451003553025685733e-01 4.53153622394281863084e-01 0.00000000000000000000e+00 +5.71373940086299381136e-01 3.21215865324749771936e-01 0.00000000000000000000e+00 +3.17380463002881252343e-01 4.30278104108278547368e-01 0.00000000000000000000e+00 +5.06832222855459146871e-01 3.13492198916104247708e-01 0.00000000000000000000e+00 +6.57282641935797662391e-01 3.80442328522818895564e-01 0.00000000000000000000e+00 +6.77456259001254834651e-01 5.85234920821254833712e-01 0.00000000000000000000e+00 +5.91368787436437171046e-01 3.23346042366156494197e-01 0.00000000000000000000e+00 +6.92541502394260710673e-01 5.00945205634575341236e-01 0.00000000000000000000e+00 +6.18603352563016017918e-01 3.28836523709082173550e-01 0.00000000000000000000e+00 +4.73888785250727084808e-01 3.00654086025586408937e-01 0.00000000000000000000e+00 +3.08365533519557644127e-01 5.18061936069602935184e-01 0.00000000000000000000e+00 +4.17593808022329426866e-01 6.79962874383049920013e-01 0.00000000000000000000e+00 +3.51728745109040064065e-01 3.95147468777703070320e-01 0.00000000000000000000e+00 +3.73144659080155038922e-01 3.62066780792920484000e-01 0.00000000000000000000e+00 +6.93635513648271695430e-01 4.32503103049943382619e-01 0.00000000000000000000e+00 +3.36608832834201399109e-01 6.24775512339231231529e-01 0.00000000000000000000e+00 +3.16699999999999981526e-01 5.92400000000000037659e-01 0.00000000000000000000e+00 +2.90893338093825837287e-01 5.01121820523936500891e-01 0.00000000000000000000e+00 +3.04300000000000014921e-01 4.81800000000000006040e-01 0.00000000000000000000e+00 +6.97138361185885235827e-01 5.64215083557655594504e-01 0.00000000000000000000e+00 +7.04260041401173042530e-01 4.61116484274233295881e-01 0.00000000000000000000e+00 +3.89910503963468602073e-01 6.76637192836441414556e-01 0.00000000000000000000e+00 +3.68708605628450092340e-01 6.63984692680264942943e-01 0.00000000000000000000e+00 +6.52286955421172542913e-01 6.47213890908039490846e-01 0.00000000000000000000e+00 +3.14103972426799382678e-01 5.69245108903549512647e-01 0.00000000000000000000e+00 +6.42100053370128986963e-01 3.40482414508055220015e-01 0.00000000000000000000e+00 +6.49102136974962662386e-01 3.59763288901453259783e-01 0.00000000000000000000e+00 +4.92816611808652216276e-01 6.92459170206472629516e-01 0.00000000000000000000e+00 +5.61842004562163599424e-01 2.99221672309677777513e-01 0.00000000000000000000e+00 +2.91955584830770720206e-01 4.58059900420676735511e-01 0.00000000000000000000e+00 +6.88504570230454637780e-01 4.05656867615154070883e-01 0.00000000000000000000e+00 +7.09943227553176847699e-01 5.32178803122429044947e-01 0.00000000000000000000e+00 +5.31372835387395991980e-01 3.01456711248911379819e-01 0.00000000000000000000e+00 +6.79980743082914229625e-01 3.83017991087658160598e-01 0.00000000000000000000e+00 +5.12841307959504000280e-01 2.87506275035863012590e-01 0.00000000000000000000e+00 +2.89212915714820661961e-01 5.37513454489585429386e-01 0.00000000000000000000e+00 +5.88596801163886129693e-01 3.02185613723264745456e-01 0.00000000000000000000e+00 +5.74824079186157410604e-01 6.83566426372128388422e-01 0.00000000000000000000e+00 +4.52681447027930838889e-01 2.96311042177083983074e-01 0.00000000000000000000e+00 +6.71490171548383196765e-01 6.29511614392353369141e-01 0.00000000000000000000e+00 +7.10030419535119916574e-01 4.85471522919503295501e-01 0.00000000000000000000e+00 +7.23985275660610883008e-01 5.06877217970274518422e-01 0.00000000000000000000e+00 +6.85616416703124009757e-01 6.11395467330110964355e-01 0.00000000000000000000e+00 +3.50341552400244282683e-01 6.48471273105501788692e-01 0.00000000000000000000e+00 +3.19327344910306010206e-01 6.15731280728556207293e-01 0.00000000000000000000e+00 +2.92182277657787425884e-01 5.59963359668088522803e-01 0.00000000000000000000e+00 +6.11597411111166722186e-01 3.07841694784995401069e-01 0.00000000000000000000e+00 +7.17154876938135066311e-01 4.40382947006451475058e-01 0.00000000000000000000e+00 +4.91944061549409450418e-01 2.80678084812701345019e-01 0.00000000000000000000e+00 +7.04492457048151554666e-01 5.86277371144454551022e-01 0.00000000000000000000e+00 +6.71202226200921736599e-01 3.58758990192418969478e-01 0.00000000000000000000e+00 +4.33488921477347521893e-01 2.85188529827435088837e-01 0.00000000000000000000e+00 +4.22493740477964063729e-01 3.11992859039725900505e-01 0.00000000000000000000e+00 +2.71342717652271137041e-01 4.87768977155040861504e-01 0.00000000000000000000e+00 +4.04831215845736058778e-01 7.05080925893761478740e-01 0.00000000000000000000e+00 +6.31483867666127185458e-01 3.17022960176478796335e-01 0.00000000000000000000e+00 +7.27543581367111613645e-01 4.62308362202000167063e-01 0.00000000000000000000e+00 +3.22365303815667691989e-01 6.42708885299916210521e-01 0.00000000000000000000e+00 +2.93013875471922313753e-01 5.96027335142443215332e-01 0.00000000000000000000e+00 +6.21749135424839005637e-01 6.73851029218092589801e-01 0.00000000000000000000e+00 +2.67330373707654611604e-01 4.63302281351228806283e-01 0.00000000000000000000e+00 +6.54072915945424049333e-01 6.72409530470097771548e-01 0.00000000000000000000e+00 +7.10481533612232318831e-01 4.13069127439018968229e-01 0.00000000000000000000e+00 +5.52643099942716320427e-01 2.73004719400854578559e-01 0.00000000000000000000e+00 +3.11096090142606196416e-01 3.90738254683129559997e-01 0.00000000000000000000e+00 +7.07567085558870823014e-01 3.87789725964607701059e-01 0.00000000000000000000e+00 +7.19581593067373681549e-01 5.69073480004123166687e-01 0.00000000000000000000e+00 +7.25412685706484539061e-01 5.48124269768105687461e-01 0.00000000000000000000e+00 +2.74844642667312510920e-01 5.16945446120845142346e-01 0.00000000000000000000e+00 +5.78564602818714468491e-01 2.78583038883813915998e-01 0.00000000000000000000e+00 +4.68840753281611843928e-01 2.76757418995782933369e-01 0.00000000000000000000e+00 +3.45979786830574231882e-01 6.75160141634731814086e-01 0.00000000000000000000e+00 +5.32665825916834423381e-01 2.64785565062721939267e-01 0.00000000000000000000e+00 +2.71069814132678943430e-01 4.42894997785084620912e-01 0.00000000000000000000e+00 +2.93693494113583031258e-01 4.34111167993696867029e-01 0.00000000000000000000e+00 +6.62829252777362931681e-01 3.30866173624282056132e-01 0.00000000000000000000e+00 +6.75627049413434432878e-01 6.54612572450151586345e-01 0.00000000000000000000e+00 +6.97512150795682295268e-01 6.36187601499954902273e-01 0.00000000000000000000e+00 +7.35281173224859752047e-01 5.28299054755291486174e-01 0.00000000000000000000e+00 +3.82846964320650906544e-01 3.19721722525090956690e-01 0.00000000000000000000e+00 +3.03404785665557397145e-01 6.33027730099698149324e-01 0.00000000000000000000e+00 +2.79354476244122740258e-01 5.78347626944284654016e-01 0.00000000000000000000e+00 +7.13542839055855138142e-01 6.13428517167565523494e-01 0.00000000000000000000e+00 +7.41366347803510850056e-01 4.90630413015151922718e-01 0.00000000000000000000e+00 +2.64955985713272723636e-01 5.41683359451955781516e-01 0.00000000000000000000e+00 +2.62829410697496990146e-01 5.63097250218168143832e-01 0.00000000000000000000e+00 +5.08188367283913589034e-01 2.61802202898152869270e-01 0.00000000000000000000e+00 +6.94542943613003282977e-01 3.63657597950871103176e-01 0.00000000000000000000e+00 +5.25914168221841449302e-01 7.13892206009450580595e-01 0.00000000000000000000e+00 +6.03988510461830996334e-01 2.85039967346777944890e-01 0.00000000000000000000e+00 +4.39035412865005469918e-01 7.06332339289360122692e-01 0.00000000000000000000e+00 +4.47612103576231434854e-01 2.66156747300420271074e-01 0.00000000000000000000e+00 +3.76549368281440588468e-01 6.97269707470392186011e-01 0.00000000000000000000e+00 +2.55139566061321820811e-01 5.02146755959473889419e-01 0.00000000000000000000e+00 +7.38340185022862804942e-01 4.40782066745927625373e-01 0.00000000000000000000e+00 +7.32151633073843011168e-01 4.17341238941262848705e-01 0.00000000000000000000e+00 +2.84863179111631670271e-01 6.17836734967454170864e-01 0.00000000000000000000e+00 +4.86925696629804616311e-01 2.53563502199652901492e-01 0.00000000000000000000e+00 +3.37282257171675425855e-01 3.40748119100675594950e-01 0.00000000000000000000e+00 +4.06415926378646874539e-01 7.31262128785571952072e-01 0.00000000000000000000e+00 +6.36494891718686894855e-01 2.95323428839192603235e-01 0.00000000000000000000e+00 +7.50242289520464011865e-01 4.68663476137526791021e-01 0.00000000000000000000e+00 +3.99729369749566798564e-01 2.70017526502451810089e-01 0.00000000000000000000e+00 +4.23144971447719675517e-01 2.62726111466899003855e-01 0.00000000000000000000e+00 +3.80451418672519137143e-01 7.21624279251218525566e-01 0.00000000000000000000e+00 +7.31770894087175438614e-01 5.88594336300531373318e-01 0.00000000000000000000e+00 +3.25107699559538665390e-01 6.67061837482962438628e-01 0.00000000000000000000e+00 +6.90143544439552725223e-01 3.40502771338569432125e-01 0.00000000000000000000e+00 +5.66849027037647279492e-01 7.21908075442716068437e-01 0.00000000000000000000e+00 +3.49840075986467879954e-01 7.00984490809450466742e-01 0.00000000000000000000e+00 +2.49827063076685784715e-01 4.79441009627407987992e-01 0.00000000000000000000e+00 +7.29786533772729839598e-01 3.88459846903060901369e-01 0.00000000000000000000e+00 +5.74309286991681822165e-01 2.55873967719046024882e-01 0.00000000000000000000e+00 +2.67085106360604118692e-01 5.99998112856048981811e-01 0.00000000000000000000e+00 +3.27949178054378065461e-01 6.92294661763932839271e-01 0.00000000000000000000e+00 +4.65049692229763056428e-01 2.45170353457384193607e-01 0.00000000000000000000e+00 +6.81944808738551233596e-01 6.78816441980194529293e-01 0.00000000000000000000e+00 +7.49981626673562939978e-01 5.46800365202721239122e-01 0.00000000000000000000e+00 +7.46387870609728554250e-01 5.70119301636520359189e-01 0.00000000000000000000e+00 +5.26830107710301254187e-01 2.43237776697702218209e-01 0.00000000000000000000e+00 +5.50530779549736615230e-01 2.49545514557331482575e-01 0.00000000000000000000e+00 +4.71889625100296750126e-01 7.33125511837888099720e-01 0.00000000000000000000e+00 +2.44323023338995221065e-01 5.22563785086247789557e-01 0.00000000000000000000e+00 +6.58017064517773531129e-01 3.03459833696173297835e-01 0.00000000000000000000e+00 +5.96199999999999952216e-01 2.61699999999999988187e-01 0.00000000000000000000e+00 +7.01925087603708863782e-01 6.62614308134073071166e-01 0.00000000000000000000e+00 +7.11590716949895063514e-01 3.43008365819436944477e-01 0.00000000000000000000e+00 +7.19141397852047448147e-01 3.67777715596145249322e-01 0.00000000000000000000e+00 +6.13510964772476352636e-01 2.66312827661016493508e-01 0.00000000000000000000e+00 +2.50830586834929869511e-01 4.51062598773407152919e-01 0.00000000000000000000e+00 +7.58912285117810725410e-01 5.24680656919559118556e-01 0.00000000000000000000e+00 +3.03390440855015208754e-01 6.58168042225285354618e-01 0.00000000000000000000e+00 +3.72455329847361016338e-01 2.72889952438220839692e-01 0.00000000000000000000e+00 +7.36737254969801691473e-01 6.10893784360651781462e-01 0.00000000000000000000e+00 +2.52932733438081314148e-01 5.82875642989493569779e-01 0.00000000000000000000e+00 +6.07009938420816297366e-01 7.13634126586083050725e-01 0.00000000000000000000e+00 +5.03359392562525154169e-01 2.36991239910341350283e-01 0.00000000000000000000e+00 +6.34913816637019734479e-01 2.72016014537254380290e-01 0.00000000000000000000e+00 +2.72425983914569902478e-01 3.94469435688177383348e-01 0.00000000000000000000e+00 +4.38701716693366328137e-01 2.40397343894715787016e-01 0.00000000000000000000e+00 +7.21081367612972590209e-01 6.36044102838917657650e-01 0.00000000000000000000e+00 +7.41029478601307745045e-01 6.31356159594571808569e-01 0.00000000000000000000e+00 +4.16322846295879622769e-01 7.53025482280976099503e-01 0.00000000000000000000e+00 +7.61659520323524463770e-01 4.98416928484026633406e-01 0.00000000000000000000e+00 +7.49866820559208391295e-01 3.99106717625437146246e-01 0.00000000000000000000e+00 +2.83727218385034340997e-01 6.45274051834572759390e-01 0.00000000000000000000e+00 +4.80171534768305208196e-01 2.30819983269045014840e-01 0.00000000000000000000e+00 +6.82579717649463346696e-01 3.16407863909495268295e-01 0.00000000000000000000e+00 +3.92650444824419520007e-01 7.48565994480832053171e-01 0.00000000000000000000e+00 +3.93433997136648450077e-01 2.48543068970905528525e-01 0.00000000000000000000e+00 +2.88349580739633959414e-01 3.56620389092344614035e-01 0.00000000000000000000e+00 +2.59195328907177224842e-01 6.21462728757210980390e-01 0.00000000000000000000e+00 +6.56996650283822369865e-01 2.77900395172212211214e-01 0.00000000000000000000e+00 +7.55601568061837758350e-01 4.23554389450853607801e-01 0.00000000000000000000e+00 +7.63708910401989737871e-01 4.45442527364436513526e-01 0.00000000000000000000e+00 +5.64103580978392638023e-01 2.31166413299291689798e-01 0.00000000000000000000e+00 +2.42083481193799299191e-01 5.47813265189537323963e-01 0.00000000000000000000e+00 +3.71584727293816863281e-01 7.42030568218508435052e-01 0.00000000000000000000e+00 +3.56347289718075721510e-01 7.25580465739206870168e-01 0.00000000000000000000e+00 +5.89138034236508789654e-01 2.40554502971331490846e-01 0.00000000000000000000e+00 +2.95460227271073350153e-01 6.83032356203451485044e-01 0.00000000000000000000e+00 +3.09113804150717319530e-01 7.01867437012512529471e-01 0.00000000000000000000e+00 +7.73586193366132879312e-01 5.44259833715003638055e-01 0.00000000000000000000e+00 +5.04007593616419113935e-01 7.58780731315849443774e-01 0.00000000000000000000e+00 +6.10985656747676908473e-01 2.40851960186238112094e-01 0.00000000000000000000e+00 +7.72644894213937116767e-01 4.77877230764038152255e-01 0.00000000000000000000e+00 +4.13096941532962114163e-01 2.40290591519379925778e-01 0.00000000000000000000e+00 +4.56529878992361637380e-01 2.24527952046785994611e-01 0.00000000000000000000e+00 +2.42712856611867727752e-01 6.03730522432347371797e-01 0.00000000000000000000e+00 +5.26551064279842662152e-01 2.20655676039726800219e-01 0.00000000000000000000e+00 +7.03685937609437428542e-01 6.89368511619129598067e-01 0.00000000000000000000e+00 +3.45665048507196204230e-01 3.01747575384453459169e-01 0.00000000000000000000e+00 +3.30719584203846006520e-01 7.16896351230667527865e-01 0.00000000000000000000e+00 +6.45255537348623842142e-01 7.08156133865602610555e-01 0.00000000000000000000e+00 +7.09975369153261759436e-01 3.23208799899763199459e-01 0.00000000000000000000e+00 +7.69572693262168483486e-01 5.74947272348809601539e-01 0.00000000000000000000e+00 +3.97417562910561739109e-01 7.66388364991803405779e-01 0.00000000000000000000e+00 +3.50599460119232042565e-01 2.57022429325331147609e-01 0.00000000000000000000e+00 +6.80408372290948304517e-01 2.84138882760044675369e-01 0.00000000000000000000e+00 +6.80872413469631454497e-01 7.12917266624029544175e-01 0.00000000000000000000e+00 +2.23798377621086230782e-01 5.62663892442381463610e-01 0.00000000000000000000e+00 +7.41949659249572013309e-01 3.66550010480550636505e-01 0.00000000000000000000e+00 +7.32454611932688570697e-01 3.40034448389279153613e-01 0.00000000000000000000e+00 +3.71763112116195115764e-01 2.49869635706898574057e-01 0.00000000000000000000e+00 +4.33177655271637818135e-01 2.18312950773081548839e-01 0.00000000000000000000e+00 +7.83717303318089197894e-01 5.20882510451556313136e-01 0.00000000000000000000e+00 +2.26587877550183752762e-01 5.85967990730399068688e-01 0.00000000000000000000e+00 +2.26252339196902685359e-01 4.67312521490173549132e-01 0.00000000000000000000e+00 +2.32133768952546615250e-01 4.97560327849905226216e-01 0.00000000000000000000e+00 +7.47737680598872356263e-01 6.50666936247318083986e-01 0.00000000000000000000e+00 +7.30080606515345897911e-01 6.63041819546037580757e-01 0.00000000000000000000e+00 +5.00235092761442956544e-01 2.12383650811458213337e-01 0.00000000000000000000e+00 +2.52424231963282286184e-01 4.25865875263175319709e-01 0.00000000000000000000e+00 +7.63282907680180922050e-01 3.79805092654633524862e-01 0.00000000000000000000e+00 +3.06699362829838617639e-01 3.10728627613528207707e-01 0.00000000000000000000e+00 +7.86326381516502026514e-01 4.54180015187080565742e-01 0.00000000000000000000e+00 +7.75109949360679895136e-01 4.03988747648924728662e-01 0.00000000000000000000e+00 +7.88069784950933160950e-01 4.30468175885379611412e-01 0.00000000000000000000e+00 +3.29013600308761977598e-01 2.61253177443844741834e-01 0.00000000000000000000e+00 +6.31880242180597462287e-01 2.50383865640304348332e-01 0.00000000000000000000e+00 +6.51849680989046498247e-01 2.54715830642311125143e-01 0.00000000000000000000e+00 +3.93299772083542331647e-01 2.26097414033737453831e-01 0.00000000000000000000e+00 +7.57180264573697470176e-01 5.95005274954897189765e-01 0.00000000000000000000e+00 +7.60873589047047715894e-01 6.18401836152265405033e-01 0.00000000000000000000e+00 +3.53093746511165540447e-01 7.58773878434325976805e-01 0.00000000000000000000e+00 +3.74250138200200643812e-01 7.65168763503789373104e-01 0.00000000000000000000e+00 +2.71449918069141038490e-01 6.70137708168298762423e-01 0.00000000000000000000e+00 +4.19647011809730352550e-01 7.78663347365392777277e-01 0.00000000000000000000e+00 +2.49964906582117785128e-01 6.50117974479981874936e-01 0.00000000000000000000e+00 +7.88298222625810618425e-01 5.62253774766328984214e-01 0.00000000000000000000e+00 +7.28826341886432027550e-01 3.17733019286658924241e-01 0.00000000000000000000e+00 +5.82059849250276428734e-01 2.19360441061754618541e-01 0.00000000000000000000e+00 +2.19614783318646838994e-01 5.21577745496386024904e-01 0.00000000000000000000e+00 +7.90702340749021903576e-01 4.97880659298119976341e-01 0.00000000000000000000e+00 +6.73140542134703090404e-01 2.60049548561755938803e-01 0.00000000000000000000e+00 +7.03990996452922179749e-01 7.14020187515303161874e-01 0.00000000000000000000e+00 +2.34411479894439306326e-01 6.29280303856152745112e-01 0.00000000000000000000e+00 +4.76425063103148749288e-01 2.09087937518045968721e-01 0.00000000000000000000e+00 +4.09556143394894267562e-01 2.12026280683050599718e-01 0.00000000000000000000e+00 +7.04005085132065500098e-01 2.90426664586949601077e-01 0.00000000000000000000e+00 +7.97813273906040287820e-01 5.40102104044980713127e-01 0.00000000000000000000e+00 +2.06592110941042295114e-01 5.45436503786936977001e-01 0.00000000000000000000e+00 +4.56523070227387472997e-01 2.00760649807308261261e-01 0.00000000000000000000e+00 +6.01959797803595542121e-01 2.18975528030827287784e-01 0.00000000000000000000e+00 +2.23180521155877359396e-01 4.36885071929999257989e-01 0.00000000000000000000e+00 +5.50210423914154089786e-01 7.60049699843917236919e-01 0.00000000000000000000e+00 +7.37515843788360991873e-01 6.85347531365082862642e-01 0.00000000000000000000e+00 +6.27143033731532462127e-01 2.26921899018178468976e-01 0.00000000000000000000e+00 +3.06286247023812296941e-01 2.66670810503816191339e-01 0.00000000000000000000e+00 +3.11358833258225553564e-01 7.38107902770522406044e-01 0.00000000000000000000e+00 +3.33526616149824872704e-01 7.41823284154000450741e-01 0.00000000000000000000e+00 +3.55083472947193057312e-01 2.36830557139989739124e-01 0.00000000000000000000e+00 +2.17299923793058236132e-01 6.07622304060741025289e-01 0.00000000000000000000e+00 +5.21029672412243849422e-01 1.97894282454852965536e-01 0.00000000000000000000e+00 +8.00769337014944904141e-01 4.76785435382721001218e-01 0.00000000000000000000e+00 +5.90762999654978804287e-01 7.59575199736933770467e-01 0.00000000000000000000e+00 +6.29414079355492095402e-01 7.56504368882495170645e-01 0.00000000000000000000e+00 +6.79406841639347391926e-01 7.46487557164128401332e-01 0.00000000000000000000e+00 +6.98137128602541734423e-01 7.34789672649500236190e-01 0.00000000000000000000e+00 +6.99941688002646555944e-01 2.66882649393056614606e-01 0.00000000000000000000e+00 +5.53223021360127686918e-01 2.08367450040094132069e-01 0.00000000000000000000e+00 +5.75034096702545016733e-01 1.98323376510938592654e-01 0.00000000000000000000e+00 +7.92535507462001520551e-01 5.86182525649467156548e-01 0.00000000000000000000e+00 +3.97849105222048193831e-01 7.88259302553335094998e-01 0.00000000000000000000e+00 +7.24976458729957529137e-01 7.03183185025658352707e-01 0.00000000000000000000e+00 +7.49984960586367988000e-01 3.24732448974618004289e-01 0.00000000000000000000e+00 +3.71001477347319752464e-01 2.20185009844255735745e-01 0.00000000000000000000e+00 +2.42875523717513808197e-01 3.67573854725050719328e-01 0.00000000000000000000e+00 +5.27122171460049804281e-01 7.92170261497096794834e-01 0.00000000000000000000e+00 +7.72546579215067308333e-01 3.57190756610028359308e-01 0.00000000000000000000e+00 +8.08302084455705838195e-01 5.17118086701066514976e-01 0.00000000000000000000e+00 +7.27197304799991450786e-01 2.96606661705000218543e-01 0.00000000000000000000e+00 +7.48217468283780107186e-01 3.02207874163780920718e-01 0.00000000000000000000e+00 +7.71550100695952756347e-01 6.48751603598916037896e-01 0.00000000000000000000e+00 +7.57105646096944306933e-01 6.73699747633002332670e-01 0.00000000000000000000e+00 +7.82983783485152073922e-01 6.29442522332970377796e-01 0.00000000000000000000e+00 +2.04049417614848072056e-01 5.91165607978864637850e-01 0.00000000000000000000e+00 +6.68824763591036042243e-01 2.37840814216525170410e-01 0.00000000000000000000e+00 +3.87468264929774819372e-01 2.06147774445877063343e-01 0.00000000000000000000e+00 +3.58508789966280161376e-01 7.81455895531998456782e-01 0.00000000000000000000e+00 +4.96189953685540530159e-01 1.88964222232153172820e-01 0.00000000000000000000e+00 +4.53525309069809035023e-01 7.77185968094187384914e-01 0.00000000000000000000e+00 +4.85757020348519474862e-01 8.00574783422765778873e-01 0.00000000000000000000e+00 +3.35454726011355175430e-01 2.40800859703490743868e-01 0.00000000000000000000e+00 +3.15357803286356153105e-01 2.45256669464150256488e-01 0.00000000000000000000e+00 +5.41128772861228624080e-01 1.89215553262593427775e-01 0.00000000000000000000e+00 +2.96439881559530049593e-01 7.19391463969885291974e-01 0.00000000000000000000e+00 +7.89134561421443447848e-01 3.83187290247387124520e-01 0.00000000000000000000e+00 +4.32157661759391631406e-01 1.99110704043240466232e-01 0.00000000000000000000e+00 +2.68031902650511910657e-01 3.19147411550521786783e-01 0.00000000000000000000e+00 +7.95634367643890616506e-01 6.08672129729440181123e-01 0.00000000000000000000e+00 +7.26373985217691142680e-01 7.25582559516571024005e-01 0.00000000000000000000e+00 +6.90866560910023741471e-01 2.43701301228354594297e-01 0.00000000000000000000e+00 +2.00572223515346981237e-01 5.69161147560284907954e-01 0.00000000000000000000e+00 +7.31916437047591794673e-01 2.77117037999393522885e-01 0.00000000000000000000e+00 +8.09886580946381218205e-01 5.66140320192836443702e-01 0.00000000000000000000e+00 +7.72505001392127854665e-01 3.27156608189865194536e-01 0.00000000000000000000e+00 +4.24279614615228395369e-01 8.06262151103926072437e-01 0.00000000000000000000e+00 +8.18410078700591014922e-01 4.96113585697126768803e-01 0.00000000000000000000e+00 +6.50851095834553250441e-01 2.23886664090152454065e-01 0.00000000000000000000e+00 +2.86302550327173555011e-01 7.41242890694247535954e-01 0.00000000000000000000e+00 +8.08635036444765753494e-01 4.45364989477878947532e-01 0.00000000000000000000e+00 +8.00117883951963260181e-01 4.08699477669976496408e-01 0.00000000000000000000e+00 +4.10564367276457786726e-01 1.91451710831794769119e-01 0.00000000000000000000e+00 +3.03778162802048889990e-01 7.60195243147443688869e-01 0.00000000000000000000e+00 +3.36179145168578441538e-01 7.65378204154958052818e-01 0.00000000000000000000e+00 +4.73929210203209494434e-01 1.80022152441920851862e-01 0.00000000000000000000e+00 +8.20544090706794793100e-01 5.39927457421454426445e-01 0.00000000000000000000e+00 +5.97750436860761125146e-01 1.97667903168589065288e-01 0.00000000000000000000e+00 +7.13686580365378242519e-01 7.46624085991451469368e-01 0.00000000000000000000e+00 +6.19445305682551516924e-01 1.97837288823242085645e-01 0.00000000000000000000e+00 +6.38582247957270698890e-01 2.05585021267710804649e-01 0.00000000000000000000e+00 +7.57618088876592432612e-01 6.98718235626689998519e-01 0.00000000000000000000e+00 +3.76359301753243113975e-01 8.04789447814852731966e-01 0.00000000000000000000e+00 +1.91030166910015591908e-01 4.74721028950740309149e-01 0.00000000000000000000e+00 +1.82676094321115201069e-01 5.19074395132934185426e-01 0.00000000000000000000e+00 +5.68206499070322279543e-01 1.77879643097292178266e-01 0.00000000000000000000e+00 +3.98453990276385727753e-01 8.13308656907444915696e-01 0.00000000000000000000e+00 +3.63283018333740048700e-01 1.99711071021105607270e-01 0.00000000000000000000e+00 +2.37476084413819538144e-01 6.74637487958240522623e-01 0.00000000000000000000e+00 +2.75110721206778874315e-01 6.99167214239956646971e-01 0.00000000000000000000e+00 +3.38782562604042269960e-01 7.88497004736549933490e-01 0.00000000000000000000e+00 +3.54978153954268293990e-01 8.01695615438508979445e-01 0.00000000000000000000e+00 +5.19992104471707627944e-01 1.73750454005312859884e-01 0.00000000000000000000e+00 +7.67773938042526582493e-01 3.07419058279781720611e-01 0.00000000000000000000e+00 +2.92576868305153170446e-01 2.49183369281473054979e-01 0.00000000000000000000e+00 +2.07200100583356111583e-01 6.28705665914975897479e-01 0.00000000000000000000e+00 +2.21494410406577135753e-01 6.50466956098608939385e-01 0.00000000000000000000e+00 +1.92847772321668914364e-01 6.11366949928636160827e-01 0.00000000000000000000e+00 +7.83591302269843970052e-01 6.74514524454100539153e-01 0.00000000000000000000e+00 +4.50109831144928862479e-01 1.79503404777802522130e-01 0.00000000000000000000e+00 +1.79560146529638808843e-01 5.47704343682764771550e-01 0.00000000000000000000e+00 +8.04702001439760783441e-01 6.34496290139132446129e-01 0.00000000000000000000e+00 +7.94579335240232609294e-01 6.53470488863875265118e-01 0.00000000000000000000e+00 +3.40994679583399851186e-01 2.14626406369849920264e-01 0.00000000000000000000e+00 +7.47999999999999998224e-01 7.16799999999999992717e-01 0.00000000000000000000e+00 +3.20961020242319650109e-01 2.14687647419784993774e-01 0.00000000000000000000e+00 +3.01214970582440788593e-01 2.28896670360365095398e-01 0.00000000000000000000e+00 +8.26004130355933008367e-01 4.68094322025936704357e-01 0.00000000000000000000e+00 +7.17437896007764308592e-01 2.52814254788642556182e-01 0.00000000000000000000e+00 +8.31856536861404038952e-01 5.13511426972454110285e-01 0.00000000000000000000e+00 +7.90116309433319452538e-01 3.39304793730895781767e-01 0.00000000000000000000e+00 +1.78357898934625697107e-01 5.90884974555327291412e-01 0.00000000000000000000e+00 +2.04497815947340844112e-01 4.01233210383897864748e-01 0.00000000000000000000e+00 +2.27867381744902564034e-01 3.30532681277118267626e-01 0.00000000000000000000e+00 +8.00286620620241806456e-01 3.62147683630144157085e-01 0.00000000000000000000e+00 +3.81531009187032255081e-01 1.86981093049556956753e-01 0.00000000000000000000e+00 +7.36653272520121582545e-01 7.44433280022309351587e-01 0.00000000000000000000e+00 +4.28334027222872948393e-01 1.68842805316759075884e-01 0.00000000000000000000e+00 +8.26357403538617529826e-01 4.27101904408554722270e-01 0.00000000000000000000e+00 +4.92320657873033917795e-01 1.64471677601097432397e-01 0.00000000000000000000e+00 +6.95879702070881123177e-01 7.64413078735193152191e-01 0.00000000000000000000e+00 +7.52676988416625780687e-01 2.83180520734997553944e-01 0.00000000000000000000e+00 +5.89882632147299190706e-01 8.00850501883312970719e-01 0.00000000000000000000e+00 +8.43567508467020443774e-01 4.95456641167419276073e-01 0.00000000000000000000e+00 +6.07553145348606604692e-01 1.76793546243268068219e-01 0.00000000000000000000e+00 +8.43756276194119969603e-01 5.42358998318386631432e-01 0.00000000000000000000e+00 +1.83674633967113032362e-01 6.34387600845632393387e-01 0.00000000000000000000e+00 +4.03988260748150029844e-01 1.68868140740079913931e-01 0.00000000000000000000e+00 +8.16598245650258958150e-01 5.90594944652787390282e-01 0.00000000000000000000e+00 +3.42200000000000004174e-01 1.94099999999999994760e-01 0.00000000000000000000e+00 +3.04856774378836237194e-01 7.85458273749681801235e-01 0.00000000000000000000e+00 +2.78748684713922723066e-01 2.78098111287744198528e-01 0.00000000000000000000e+00 +6.72414622356453994811e-01 2.13182666807291781863e-01 0.00000000000000000000e+00 +5.41498434544966378290e-01 1.65395659028246932998e-01 0.00000000000000000000e+00 +5.84297348008687267296e-01 1.63869827221973018982e-01 0.00000000000000000000e+00 +5.60752901969314265784e-01 1.55561492509948912089e-01 0.00000000000000000000e+00 +3.77382099798362735665e-01 8.26469563091773995467e-01 0.00000000000000000000e+00 +3.96907018636098563036e-01 8.34418521619192721239e-01 0.00000000000000000000e+00 +3.21897260350412439678e-01 1.94662553754737149836e-01 0.00000000000000000000e+00 +8.17262852849944265721e-01 3.81127644235150109253e-01 0.00000000000000000000e+00 +8.30351892707607452948e-01 4.02802638104858612422e-01 0.00000000000000000000e+00 +7.71943435059334603920e-01 7.17177754033324399430e-01 0.00000000000000000000e+00 +1.65734972424435245131e-01 5.70628724613521765008e-01 0.00000000000000000000e+00 +8.40282629680071058154e-01 4.47411108009878211167e-01 0.00000000000000000000e+00 +2.00330145153472582997e-01 3.61884680068022712707e-01 0.00000000000000000000e+00 +5.15821960409530988478e-01 1.49699322488661606956e-01 0.00000000000000000000e+00 +3.02045068808510175362e-01 2.06533859898732241600e-01 0.00000000000000000000e+00 +7.40418361150535830895e-01 2.56356191070857664105e-01 0.00000000000000000000e+00 +4.25281080181255710659e-01 8.35201316606937416154e-01 0.00000000000000000000e+00 +6.52254541537066301338e-01 1.91836636664591608792e-01 0.00000000000000000000e+00 +6.33411297100065162624e-01 1.79332911091990843122e-01 0.00000000000000000000e+00 +2.52785875871840537421e-01 7.30593240851180936168e-01 0.00000000000000000000e+00 +2.80515184972570830180e-01 7.64927412298234243870e-01 0.00000000000000000000e+00 +1.67718851667398166638e-01 6.15215237430158490284e-01 0.00000000000000000000e+00 +7.86563757855697653909e-01 6.98316223803821745086e-01 0.00000000000000000000e+00 +4.69839701449271007760e-01 1.55204005874294281853e-01 0.00000000000000000000e+00 +4.47113507191867820545e-01 1.51771336434138232052e-01 0.00000000000000000000e+00 +7.60876329268607598522e-01 2.56447223978374772013e-01 0.00000000000000000000e+00 +1.51529970136978414663e-01 5.52597013431336936407e-01 0.00000000000000000000e+00 +8.53307974998961604740e-01 4.70869880140287244696e-01 0.00000000000000000000e+00 +1.86373255303152823847e-01 6.58095141003470329721e-01 0.00000000000000000000e+00 +7.83755537668755830616e-01 2.93420774088836933124e-01 0.00000000000000000000e+00 +7.87544447418043969122e-01 3.12687277221846304798e-01 0.00000000000000000000e+00 +6.63845901359634948946e-01 7.77367998863263021114e-01 0.00000000000000000000e+00 +6.30699471819275991891e-01 8.01465054124289255633e-01 0.00000000000000000000e+00 +8.56043909070698272323e-01 5.09807854510412217230e-01 0.00000000000000000000e+00 +3.61307055728805115447e-01 1.78526199630082482717e-01 0.00000000000000000000e+00 +5.53609247479789012836e-01 8.20684346456018953830e-01 0.00000000000000000000e+00 +4.64348654804642047011e-01 8.34865619257908919693e-01 0.00000000000000000000e+00 +5.11110421105807732900e-01 8.31171161015431647634e-01 0.00000000000000000000e+00 +7.56399627453862355431e-01 7.43353839465413002152e-01 0.00000000000000000000e+00 +7.20652177045461583837e-01 7.67682486471473191081e-01 0.00000000000000000000e+00 +7.44895545218120291864e-01 7.64938048748226240292e-01 0.00000000000000000000e+00 +7.09963104288321322777e-01 2.30980071222093824579e-01 0.00000000000000000000e+00 +7.35967887075752891235e-01 2.33339563906692998296e-01 0.00000000000000000000e+00 +2.07954614847533386657e-01 6.73355148527897906519e-01 0.00000000000000000000e+00 +1.54193201989781702999e-01 5.95740523659505138987e-01 0.00000000000000000000e+00 +8.63832900958931948487e-01 5.30143311076815670901e-01 0.00000000000000000000e+00 +2.40054544429621569135e-01 2.82425479784732869071e-01 0.00000000000000000000e+00 +8.63242246527495704633e-01 5.52608145924890936662e-01 0.00000000000000000000e+00 +8.35971495599110991925e-01 5.65940891237211030074e-01 0.00000000000000000000e+00 +3.83481925721473271285e-01 1.59030390564094292749e-01 0.00000000000000000000e+00 +1.81573122012639032175e-01 4.35146922369980870826e-01 0.00000000000000000000e+00 +3.99295585290792509614e-01 1.46554263832652242883e-01 0.00000000000000000000e+00 +4.20684435013415569671e-01 1.47493387230945127708e-01 0.00000000000000000000e+00 +1.50990438820375494355e-01 4.88604778263428518592e-01 0.00000000000000000000e+00 +1.51693245327907455522e-01 5.26854076429301487750e-01 0.00000000000000000000e+00 +4.89202635676532326947e-01 1.37917218195761970279e-01 0.00000000000000000000e+00 +8.06547382189455874801e-01 6.73306608398229200496e-01 0.00000000000000000000e+00 +3.23577710542484409117e-01 8.01177460029119603568e-01 0.00000000000000000000e+00 +3.41516134414189820312e-01 8.12771598850191345775e-01 0.00000000000000000000e+00 +8.15713484538978850225e-01 3.41250127707498407403e-01 0.00000000000000000000e+00 +5.09577318834196768549e-01 1.27167318926483308150e-01 0.00000000000000000000e+00 +7.83133982858800647975e-01 2.72285819992547251100e-01 0.00000000000000000000e+00 +8.17610732815790219874e-01 6.52792278571768136430e-01 0.00000000000000000000e+00 +8.19536308985868044985e-01 6.14950767019886046505e-01 0.00000000000000000000e+00 +3.00655101110953004717e-01 8.09509982599761590549e-01 0.00000000000000000000e+00 +8.55044638696624503815e-01 4.28437523951382681631e-01 0.00000000000000000000e+00 +5.33084612204180463557e-01 1.32455096272546507530e-01 0.00000000000000000000e+00 +5.53529668447984457558e-01 1.33933114792191526599e-01 0.00000000000000000000e+00 +3.44053931239028332190e-01 8.35307676972286694550e-01 0.00000000000000000000e+00 +3.33167352278806339072e-01 1.72225438070297220960e-01 0.00000000000000000000e+00 +8.64707285779373502344e-01 4.90194005184292402877e-01 0.00000000000000000000e+00 +5.88450581237973002757e-01 1.40207031082849514769e-01 0.00000000000000000000e+00 +8.31720468333430051899e-01 3.61473855280027234205e-01 0.00000000000000000000e+00 +8.26535186454352976959e-01 3.23077082868965803630e-01 0.00000000000000000000e+00 +8.06270785336866135928e-01 3.17677257311160976538e-01 0.00000000000000000000e+00 +3.63438043594870530129e-01 8.43782287558753396617e-01 0.00000000000000000000e+00 +3.84848943078584471156e-01 8.52539232988666295654e-01 0.00000000000000000000e+00 +8.50466672298037962285e-01 3.77317797397439091966e-01 0.00000000000000000000e+00 +8.56010690919542294175e-01 4.02856867993860889854e-01 0.00000000000000000000e+00 +3.01377974724919117655e-01 1.86758136159682675004e-01 0.00000000000000000000e+00 +4.05517739836564250488e-01 8.55363264078944585123e-01 0.00000000000000000000e+00 +2.18110141261344092634e-01 7.08121711946679055494e-01 0.00000000000000000000e+00 +8.00269238306539620886e-01 7.19584785159958828515e-01 0.00000000000000000000e+00 +2.79892060174779411241e-01 1.98795121367234151055e-01 0.00000000000000000000e+00 +2.77636011397240367593e-01 2.28229464251536479091e-01 0.00000000000000000000e+00 +7.84252029586295140717e-01 2.52030278264676943500e-01 0.00000000000000000000e+00 +7.79881709396997968575e-01 7.40416639951390065377e-01 0.00000000000000000000e+00 +4.69895492474912634950e-01 1.25136830554414990591e-01 0.00000000000000000000e+00 +1.34086487351307376281e-01 5.69946770924477097431e-01 0.00000000000000000000e+00 +2.81577400466116845035e-01 7.90493494577169886206e-01 0.00000000000000000000e+00 +1.43694180781112790246e-01 6.18894418162665105321e-01 0.00000000000000000000e+00 +1.63414866900983307696e-01 6.47341040970401571109e-01 0.00000000000000000000e+00 +8.81122675738194027772e-01 5.43595269037158379533e-01 0.00000000000000000000e+00 +8.78299999999999969624e-01 5.06399999999999961275e-01 0.00000000000000000000e+00 +3.65414764996798535623e-01 1.46066111941917070594e-01 0.00000000000000000000e+00 +3.47941249563549936941e-01 1.56343577430881203183e-01 0.00000000000000000000e+00 +8.12450995701643363667e-01 2.88050071577716781945e-01 0.00000000000000000000e+00 +4.45590586830149959496e-01 1.29441111528457275526e-01 0.00000000000000000000e+00 +8.69037106132671155478e-01 4.48243360422465042525e-01 0.00000000000000000000e+00 +3.16257758199363414597e-01 1.58044479551609745593e-01 0.00000000000000000000e+00 +8.53113181663938013877e-01 6.02050529898490616176e-01 0.00000000000000000000e+00 +8.79001790568904706191e-01 5.66527490482587992027e-01 0.00000000000000000000e+00 +1.28759303027748572834e-01 5.95747481582549309742e-01 0.00000000000000000000e+00 +8.76084036519763742490e-01 4.69874532625284058174e-01 0.00000000000000000000e+00 +8.48527023834308891104e-01 3.28937215860751974716e-01 0.00000000000000000000e+00 +6.08546936902119695034e-01 1.50015536932140813864e-01 0.00000000000000000000e+00 +7.99166578759500256801e-01 7.43200745271069607156e-01 0.00000000000000000000e+00 +8.03671334610192356074e-01 2.56249850459568495609e-01 0.00000000000000000000e+00 +1.64673784875256690174e-01 6.72969896611473772374e-01 0.00000000000000000000e+00 +5.68551371330245691738e-01 1.20454016433308316869e-01 0.00000000000000000000e+00 +3.70968166202649995977e-01 8.71570951459945675310e-01 0.00000000000000000000e+00 +5.25592443255581298622e-01 1.11860260343058570687e-01 0.00000000000000000000e+00 +5.46201230728722175733e-01 1.11989726335315620265e-01 0.00000000000000000000e+00 +3.91186702711643519148e-01 8.73651091218440978281e-01 0.00000000000000000000e+00 +1.35768333071161928505e-01 6.39736070040969440065e-01 0.00000000000000000000e+00 +7.68724838275973931800e-01 7.69658089701162118068e-01 0.00000000000000000000e+00 +4.90116391732263734227e-01 1.13717164873400333747e-01 0.00000000000000000000e+00 +7.34806244574877420739e-01 1.98534566934084194534e-01 0.00000000000000000000e+00 +7.59388933856273040668e-01 2.30941867520611132658e-01 0.00000000000000000000e+00 +2.35895820324870858498e-01 7.65798118033151342665e-01 0.00000000000000000000e+00 +5.94939185037027362668e-01 1.20148400649383410577e-01 0.00000000000000000000e+00 +6.16315333562880929286e-01 1.26336612835201034244e-01 0.00000000000000000000e+00 +3.03277778304393175191e-01 8.34255339756555303055e-01 0.00000000000000000000e+00 +2.35603129823091506090e-01 2.42284264976737967467e-01 0.00000000000000000000e+00 +3.27324992532110170540e-01 8.48969489245064767857e-01 0.00000000000000000000e+00 +3.46774636398031577134e-01 8.59468012981213114188e-01 0.00000000000000000000e+00 +8.38019551388048844487e-01 3.00060922024170173561e-01 0.00000000000000000000e+00 +6.63645985316476449967e-01 1.63068333811712262715e-01 0.00000000000000000000e+00 +6.93630132445947400477e-01 1.85171836426115093976e-01 0.00000000000000000000e+00 +2.01477682020937770568e-01 3.00449127562834428229e-01 0.00000000000000000000e+00 +4.93520853472954601493e-01 8.79998802043887806690e-01 0.00000000000000000000e+00 +6.95035111052736964687e-01 8.05743325794731846123e-01 0.00000000000000000000e+00 +7.34845852872339833084e-01 7.99904194852078420652e-01 0.00000000000000000000e+00 +8.75874762569088693986e-01 3.83786815737274189519e-01 0.00000000000000000000e+00 +8.80625878263773587129e-01 4.13937454407655003319e-01 0.00000000000000000000e+00 +4.17915903943518574071e-01 8.85529489240550082840e-01 0.00000000000000000000e+00 +4.50408453936182451294e-01 8.70931584423588978083e-01 0.00000000000000000000e+00 +9.00305511385917411893e-01 5.34838605414876377964e-01 0.00000000000000000000e+00 +8.91289174695479302635e-01 4.89674514215760137148e-01 0.00000000000000000000e+00 +8.99843722934948408998e-01 5.09971339861935768489e-01 0.00000000000000000000e+00 +4.62117572108292407052e-01 1.04020193287952852912e-01 0.00000000000000000000e+00 +2.99785781972780052129e-01 1.63446795294600016879e-01 0.00000000000000000000e+00 +2.81069587445510804535e-01 1.72362130176101219226e-01 0.00000000000000000000e+00 +4.18509391682535036860e-01 1.23484925474984977289e-01 0.00000000000000000000e+00 +1.14222573467040983131e-01 5.08603241614186019071e-01 0.00000000000000000000e+00 +1.25311833530270783088e-01 5.43937558350586547462e-01 0.00000000000000000000e+00 +1.19143591143925794973e-01 6.22654139025953901054e-01 0.00000000000000000000e+00 +8.94164002734755380963e-01 4.57723219286492510882e-01 0.00000000000000000000e+00 +9.06204756959347323964e-01 4.75765160285412824770e-01 0.00000000000000000000e+00 +3.82008490883967488649e-01 1.33633886093500020564e-01 0.00000000000000000000e+00 +8.66013885144182538056e-01 3.57778319290305513167e-01 0.00000000000000000000e+00 +1.34542132272034126172e-01 6.64446019142941657343e-01 0.00000000000000000000e+00 +1.07025997545651938014e-01 6.04491017383550377495e-01 0.00000000000000000000e+00 +8.63728741180397885380e-01 3.15474673002158256807e-01 0.00000000000000000000e+00 +8.71966726401216640774e-01 3.35183159387743734303e-01 0.00000000000000000000e+00 +1.08600854753252026863e-01 5.76231908426070860152e-01 0.00000000000000000000e+00 +8.18083857672972647457e-01 6.99956798880197350599e-01 0.00000000000000000000e+00 +8.62613477627180103013e-01 6.42064188202491559210e-01 0.00000000000000000000e+00 +1.14263848157065334155e-01 6.47076424912478520568e-01 0.00000000000000000000e+00 +5.35953418462480568607e-01 8.77742654523488097240e-01 0.00000000000000000000e+00 +5.51607805684076746289e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +2.77244007766762001754e-01 8.19721591159739637611e-01 0.00000000000000000000e+00 +2.01281805009405845297e-01 2.58550997336416177319e-01 0.00000000000000000000e+00 +5.62646152272724853205e-01 9.32391567368614365696e-02 0.00000000000000000000e+00 +7.88714787519943283378e-01 2.29125749324397748330e-01 0.00000000000000000000e+00 +3.49755012713292401383e-01 1.27194728102651338775e-01 0.00000000000000000000e+00 +3.30547409749563592118e-01 1.47350437264553019157e-01 0.00000000000000000000e+00 +8.44243990244109232535e-01 6.76288663778541021543e-01 0.00000000000000000000e+00 +5.17068103792959976595e-01 9.19125965731620170329e-02 0.00000000000000000000e+00 +5.38552589355533406490e-01 8.90875587453444223174e-02 0.00000000000000000000e+00 +7.93065534162189056389e-01 7.66647944890757160330e-01 0.00000000000000000000e+00 +4.35424457375760520783e-01 1.00836537074786486423e-01 0.00000000000000000000e+00 +8.97389360023261750676e-01 5.56259709090629828054e-01 0.00000000000000000000e+00 +2.43898538042408408177e-01 1.98451819288706476918e-01 0.00000000000000000000e+00 +4.95723614351992192262e-01 8.54433886594939406844e-02 0.00000000000000000000e+00 +8.14039936495802529670e-01 2.35596146488714075495e-01 0.00000000000000000000e+00 +8.26527859761314598330e-01 2.65216758317432632630e-01 0.00000000000000000000e+00 +3.01481946404078382518e-01 8.65000395007045508322e-01 0.00000000000000000000e+00 +5.92902586083354776747e-01 9.80469385093702683331e-02 0.00000000000000000000e+00 +9.05602861810837045375e-01 4.10578611037518526050e-01 0.00000000000000000000e+00 +8.99122275090017630816e-01 4.33741394005571878623e-01 0.00000000000000000000e+00 +4.50226584873493873573e-01 8.44584015536339327568e-02 0.00000000000000000000e+00 +4.71542037032277927544e-01 7.94496071219881333514e-02 0.00000000000000000000e+00 +1.17904272744844662579e-01 4.53614760112220627430e-01 0.00000000000000000000e+00 +3.75361379749120371851e-01 8.92809263817250342576e-01 0.00000000000000000000e+00 +3.98807528724585136359e-01 9.00157221942824881111e-01 0.00000000000000000000e+00 +3.01997173016420705771e-01 1.45402788757581363788e-01 0.00000000000000000000e+00 +8.92137777331050707552e-01 6.07116596320397050590e-01 0.00000000000000000000e+00 +8.15729677706689026984e-01 7.59528296883966835829e-01 0.00000000000000000000e+00 +8.19050381987614928292e-01 7.33714730441330398314e-01 0.00000000000000000000e+00 +8.60315411602674662284e-01 2.90823014658988354686e-01 0.00000000000000000000e+00 +1.78376395746846660995e-01 7.08386800896861901222e-01 0.00000000000000000000e+00 +1.91410123443089885686e-01 7.47131594211481675494e-01 0.00000000000000000000e+00 +8.78761322249145715446e-01 3.02168492701496982367e-01 0.00000000000000000000e+00 +9.57166793397672410748e-02 6.26241777871870186623e-01 0.00000000000000000000e+00 +6.17119526809526752764e-01 1.01832639856926379651e-01 0.00000000000000000000e+00 +9.02617179854290463403e-01 3.84430028249615141078e-01 0.00000000000000000000e+00 +8.90373157645730772813e-01 3.64504091475866653926e-01 0.00000000000000000000e+00 +8.37018477113675302137e-02 5.98669233884642215671e-01 0.00000000000000000000e+00 +3.23052885193141514630e-01 8.76697891693050457285e-01 0.00000000000000000000e+00 +3.49563302267611619722e-01 8.84231851945065372433e-01 0.00000000000000000000e+00 +2.41737498916720167097e-01 8.04676089347476919400e-01 0.00000000000000000000e+00 +1.39709315807115097563e-01 6.99669566552853616770e-01 0.00000000000000000000e+00 +6.50006699218928951645e-01 1.20581049401962958623e-01 0.00000000000000000000e+00 +5.52150419080684184969e-01 6.90673475112786733332e-02 0.00000000000000000000e+00 +5.77299501400024728248e-01 7.53993057129040550324e-02 0.00000000000000000000e+00 +2.71398475392022942021e-01 1.44894963267525322959e-01 0.00000000000000000000e+00 +3.52345520210362472824e-01 9.08938432194852818924e-01 0.00000000000000000000e+00 +5.10114857194838844379e-01 6.77772650178036784041e-02 0.00000000000000000000e+00 +6.97086042869649924469e-01 1.32643908960164313982e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 3.57783346726327944332e-01 0.00000000000000000000e+00 +1.64584917674768210771e-01 3.37430349456218359183e-01 0.00000000000000000000e+00 +1.58419863450514730729e-01 3.90027356833884897291e-01 0.00000000000000000000e+00 +2.99408437575504171591e-01 8.91473188034816232950e-01 0.00000000000000000000e+00 +9.21246702439116993411e-01 4.94005127570626312306e-01 0.00000000000000000000e+00 +9.19495343406383169871e-01 5.23854053198048341677e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 5.09334394694701320105e-01 0.00000000000000000000e+00 +9.42409512461790110915e-02 6.47179994036560568560e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 4.36767310262497210349e-01 0.00000000000000000000e+00 +7.87329883705739919098e-02 4.88173176283421494936e-01 0.00000000000000000000e+00 +6.92013958308533433383e-02 4.03725073439118120611e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 4.99463953627130308188e-01 0.00000000000000000000e+00 +3.97670140553906259484e-02 4.94478635471648497735e-01 0.00000000000000000000e+00 +7.79812819574599580541e-02 5.29969428257500374535e-01 0.00000000000000000000e+00 +7.71987076390229648437e-02 5.72668274429486978683e-01 0.00000000000000000000e+00 +8.88167819038836237056e-01 3.19325207235107044745e-01 0.00000000000000000000e+00 +4.73814802459376960275e-01 9.16482631992366680862e-01 0.00000000000000000000e+00 +4.86423993922034281212e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +5.14596479934139483525e-01 9.17930795498778206998e-01 0.00000000000000000000e+00 +2.04924745190216861523e-01 2.07021737049589371615e-01 0.00000000000000000000e+00 +2.80836091078704608304e-01 8.79864432701795151637e-01 0.00000000000000000000e+00 +2.79550716996206816134e-01 8.48206413036425299623e-01 0.00000000000000000000e+00 +6.49243079409203449615e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +5.74386818536645638034e-01 9.25230905173954565335e-01 0.00000000000000000000e+00 +6.61499031339796705886e-01 8.41954195524657666994e-01 0.00000000000000000000e+00 +6.05052201313613147171e-01 8.48008360803893079805e-01 0.00000000000000000000e+00 +4.02331174260244561758e-01 9.21296602850680601549e-01 0.00000000000000000000e+00 +4.27291029869198979441e-01 9.13112462485944131885e-01 0.00000000000000000000e+00 +9.17398008802636000247e-01 5.49901640133550140277e-01 0.00000000000000000000e+00 +9.59320387031479260287e-01 5.25293247189246970130e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 5.52945035126415462834e-01 0.00000000000000000000e+00 +4.55533544871253137654e-01 6.15853024731150278104e-02 0.00000000000000000000e+00 +4.32476241073554867889e-01 6.90898469880149024291e-02 0.00000000000000000000e+00 +6.10728091593218502808e-01 7.42674082133173707465e-02 0.00000000000000000000e+00 +9.18618680379897467603e-01 3.47614437862498681930e-01 0.00000000000000000000e+00 +8.95083601060437161756e-01 3.41343079430666274110e-01 0.00000000000000000000e+00 +3.65837593345159417524e-01 9.68306666374106339568e-02 0.00000000000000000000e+00 +3.58269686586521474148e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +4.04501255679903859441e-01 8.83915027959481652831e-02 0.00000000000000000000e+00 +1.08166990647455149577e-01 6.69156570637500780485e-01 0.00000000000000000000e+00 +7.25732924960940106729e-02 6.29785997202208513990e-01 0.00000000000000000000e+00 +6.22993941360411931929e-02 6.01613613954101067449e-01 0.00000000000000000000e+00 +5.30698114432946677077e-01 6.55690680446468776577e-02 0.00000000000000000000e+00 +7.77130034283387116645e-01 1.91508544283946202658e-01 0.00000000000000000000e+00 +7.30228673117324866837e-01 1.58584454208218872395e-01 0.00000000000000000000e+00 +8.85585055384421115932e-01 2.77888130938035216033e-01 0.00000000000000000000e+00 +8.98305355574635533422e-01 2.98136871133436842651e-01 0.00000000000000000000e+00 +1.99600689865455732663e-01 7.86302430838363042298e-01 0.00000000000000000000e+00 +9.28920130968608481759e-01 3.71155618476840254427e-01 0.00000000000000000000e+00 +9.25838249977195770768e-01 3.95881796219214709964e-01 0.00000000000000000000e+00 +4.87880742331241457066e-01 6.22125452132182912846e-02 0.00000000000000000000e+00 +8.62425122601681026779e-01 7.33188703755363269110e-01 0.00000000000000000000e+00 +9.27245044700804710303e-01 5.89428018290935828993e-01 0.00000000000000000000e+00 +9.59998212783693460892e-01 5.65664387791874223588e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 5.94540480310562280408e-01 0.00000000000000000000e+00 +8.27607587958636703451e-02 6.66533116724926877339e-01 0.00000000000000000000e+00 +2.37730623537755314523e-01 8.44881557826328122296e-01 0.00000000000000000000e+00 +7.82321181157169420750e-01 8.01709386070779461697e-01 0.00000000000000000000e+00 +3.54967048388301675832e-01 9.32218059326182402202e-01 0.00000000000000000000e+00 +3.74824486433452952472e-01 9.23722213437095041044e-01 0.00000000000000000000e+00 +3.90016427775748408813e-01 9.39777784508269986574e-01 0.00000000000000000000e+00 +8.98362760373293101068e-01 6.64769857746237868845e-01 0.00000000000000000000e+00 +8.85645965001981338993e-01 7.01929080114006453961e-01 0.00000000000000000000e+00 +9.23670727666336843775e-01 6.32004350498707623274e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 6.44867024618030804106e-01 0.00000000000000000000e+00 +9.62317917812094192342e-01 6.11004312218851874938e-01 0.00000000000000000000e+00 +9.40048716262698591528e-01 6.85162485100168971996e-01 0.00000000000000000000e+00 +3.11075494707706956543e-01 9.08382954856731372750e-01 0.00000000000000000000e+00 +3.32209127058001263411e-01 9.22960358866444674497e-01 0.00000000000000000000e+00 +1.60481324606355751916e-01 7.76079070094344403152e-01 0.00000000000000000000e+00 +2.14329916334345033091e-01 1.69226586584410171321e-01 0.00000000000000000000e+00 +5.08800000000000030020e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +5.02189144420354116605e-01 4.58628748436341951433e-02 0.00000000000000000000e+00 +5.30026897525519125054e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +5.23652758827780617779e-01 4.44733064671646791122e-02 0.00000000000000000000e+00 +5.16234266236117012028e-01 2.22602685811419120920e-02 0.00000000000000000000e+00 +9.33510509386383136032e-01 4.22197770889665535687e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 4.60471586623426576423e-01 0.00000000000000000000e+00 +9.22052668495870308973e-01 4.51021364681263414731e-01 0.00000000000000000000e+00 +9.53957583877330095312e-01 4.81568746541588765808e-01 0.00000000000000000000e+00 +8.49262169943901468194e-01 2.35265017541888860064e-01 0.00000000000000000000e+00 +5.53182113925569929336e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +5.40889591003391734247e-01 3.10763025398399575505e-02 0.00000000000000000000e+00 +0.00000000000000000000e+00 5.42550099156761667096e-01 0.00000000000000000000e+00 +5.12476798565044827649e-02 5.82678101654070412785e-01 0.00000000000000000000e+00 +4.07917908267658793631e-02 5.32802877683608677728e-01 0.00000000000000000000e+00 +5.79885118494349316443e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +5.63774465805387148265e-01 2.56916067224316896356e-02 0.00000000000000000000e+00 +5.94573501502676071695e-01 5.56675520361267495151e-02 0.00000000000000000000e+00 +5.62528188372026560771e-01 5.01423493181969714971e-02 0.00000000000000000000e+00 +5.99442983073682378325e-01 2.99581553320635782245e-02 0.00000000000000000000e+00 +4.84775058354763588309e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +4.91611646296975790538e-01 2.64596099992800495859e-02 0.00000000000000000000e+00 +3.24944799936021933018e-01 1.01785163628560756943e-01 0.00000000000000000000e+00 +3.40152321437450833042e-01 5.95197676901321748311e-02 0.00000000000000000000e+00 +6.51424453433016720227e-02 6.50736935138776173559e-01 0.00000000000000000000e+00 +4.44832742957419091656e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +4.17492215230234442469e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +4.29454737897149385617e-01 4.06966122642248712138e-02 0.00000000000000000000e+00 +4.64597766675413248461e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +4.56663551716205318165e-01 3.94063030180644355283e-02 0.00000000000000000000e+00 +4.70922430571149019940e-01 2.02687117377267286833e-02 0.00000000000000000000e+00 +4.41180641171218856922e-01 1.92543315327097681877e-02 0.00000000000000000000e+00 +1.29233742812931484689e-01 7.47894930726418771805e-01 0.00000000000000000000e+00 +9.41018318103355344562e-01 3.53583236820012469792e-01 0.00000000000000000000e+00 +2.85910004990743593645e-01 1.11544046618876180332e-01 0.00000000000000000000e+00 +2.93472473475296313783e-01 6.59865478759161794287e-02 0.00000000000000000000e+00 +2.78094052788662726705e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +4.05281447270030736885e-01 9.53616636652160343246e-01 0.00000000000000000000e+00 +4.34564400752265433958e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +4.59382535832810579013e-01 9.60150315513407015544e-01 0.00000000000000000000e+00 +9.53151253118280972210e-01 3.98654874141198989346e-01 0.00000000000000000000e+00 +9.70906158738582880652e-01 4.17644507380569185440e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 4.16604046208810474550e-01 0.00000000000000000000e+00 +6.18046858915707431059e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +6.28189507622191389835e-01 5.19958002963564758225e-02 0.00000000000000000000e+00 +6.58007922663534583485e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +8.52543845252984433536e-01 7.71347254445222674235e-01 0.00000000000000000000e+00 +1.01695596970237236989e-01 7.17061078087295133976e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 5.85024555657705502831e-01 0.00000000000000000000e+00 +2.76309689064760874433e-02 5.84962656688845283881e-01 0.00000000000000000000e+00 +2.96281863479232687730e-01 9.23784088479962073315e-01 0.00000000000000000000e+00 +9.33178734639553586483e-01 3.24582451488299228437e-01 0.00000000000000000000e+00 +9.18134021927933030582e-01 3.04478121608330098979e-01 0.00000000000000000000e+00 +4.03988571735545032304e-01 9.77613448839757315234e-01 0.00000000000000000000e+00 +3.82384979757673326706e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +4.03849959628050703131e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +7.20466430409897662379e-01 8.55418305926666011629e-01 0.00000000000000000000e+00 +6.82314072074203026297e-01 9.25925652282479361155e-01 0.00000000000000000000e+00 +7.32402607873412381245e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +0.00000000000000000000e+00 6.12113912367309498386e-01 0.00000000000000000000e+00 +2.29313173256225806551e-02 6.06937374495755022075e-01 0.00000000000000000000e+00 +4.86437542861177657505e-02 6.33450609147847454317e-01 0.00000000000000000000e+00 +4.38210760321474102064e-02 6.08523236882376972190e-01 0.00000000000000000000e+00 +7.13563748516313073322e-02 6.94272295111795334499e-01 0.00000000000000000000e+00 +2.84637195486002025913e-01 9.06005468584112461095e-01 0.00000000000000000000e+00 +3.57519286432345873017e-01 9.54882377991812525408e-01 0.00000000000000000000e+00 +3.83841433526509456797e-01 9.60114971562261554361e-01 0.00000000000000000000e+00 +3.84547322043995554175e-01 9.79453172617931966393e-01 0.00000000000000000000e+00 +3.62599999999999977884e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +3.60279788543672707668e-01 9.79396117873660099207e-01 0.00000000000000000000e+00 +3.11992970836479921459e-01 9.38985079313989334793e-01 0.00000000000000000000e+00 +9.57479971721052036671e-01 3.77828465595412998912e-01 0.00000000000000000000e+00 +9.75429193831534924186e-01 3.93041496021352854129e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 3.69300000000000017142e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 3.90942821626122927192e-01 0.00000000000000000000e+00 +6.60033889000155027382e-01 8.27664299568954542341e-02 0.00000000000000000000e+00 +6.98987181422730641245e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +6.61438733153086877437e-01 3.99830280208338731396e-02 0.00000000000000000000e+00 +2.57952430197100103515e-02 6.36949670709011872560e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 6.40900000000000025224e-01 0.00000000000000000000e+00 +7.01613847782113109197e-01 4.04848099332731567457e-02 0.00000000000000000000e+00 +6.99396230844093746981e-01 9.23098916494108218123e-02 0.00000000000000000000e+00 +7.50679494967333216238e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +0.00000000000000000000e+00 2.66029021139231713899e-01 0.00000000000000000000e+00 +8.11085720366382517676e-02 3.13539181553460610807e-01 0.00000000000000000000e+00 +1.44780426710160642356e-01 2.81219227231399804268e-01 0.00000000000000000000e+00 +1.64581063989742842901e-01 2.25202690731392318746e-01 0.00000000000000000000e+00 +2.42065843406980146790e-01 1.24204624842324987122e-01 0.00000000000000000000e+00 +2.44522872440240290892e-01 8.75676682188998439793e-02 0.00000000000000000000e+00 +9.61782227751457829967e-01 3.59116165691076960620e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 3.44781329829984883872e-01 0.00000000000000000000e+00 +9.81675092437331642259e-01 3.64416988475128489355e-01 0.00000000000000000000e+00 +8.89304558113417797927e-01 2.40900606299813413447e-01 0.00000000000000000000e+00 +1.96968058230480753956e-02 6.54283003643854832454e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 6.62221046980474126187e-01 0.00000000000000000000e+00 +4.27084866525142756166e-02 6.54409331068912414331e-01 0.00000000000000000000e+00 +5.72972287680291805789e-02 6.74316343546104257634e-01 0.00000000000000000000e+00 +3.43408802128722434599e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +3.43106750882230415733e-01 9.68561178800225963670e-01 0.00000000000000000000e+00 +3.34399542532525406724e-01 9.46740381321477419974e-01 0.00000000000000000000e+00 +8.18810292704523656759e-01 7.97176647082519673759e-01 0.00000000000000000000e+00 +1.93813005631358392655e-01 8.32553695225502266020e-01 0.00000000000000000000e+00 +2.00885163175588687956e-01 1.27422301953588473777e-01 0.00000000000000000000e+00 +8.14969377506680192624e-01 2.01178436126715098897e-01 0.00000000000000000000e+00 +8.95487913001240976030e-01 7.59443262828094045602e-01 0.00000000000000000000e+00 +9.17224391971076302710e-01 7.26152529434745508397e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 7.18319593097668906978e-01 0.00000000000000000000e+00 +8.76270964595844858991e-01 2.04659088289964430363e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 2.96850208191874076480e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 3.21206339695853382299e-01 0.00000000000000000000e+00 +9.53248055607145539092e-01 3.08414364121571804578e-01 0.00000000000000000000e+00 +9.79187540464611094748e-01 3.33818838016098129451e-01 0.00000000000000000000e+00 +9.56225569193261515366e-01 3.36070073196970409146e-01 0.00000000000000000000e+00 +9.79779741254011127261e-01 3.09454051819414655000e-01 0.00000000000000000000e+00 +2.41804025125540250629e-01 8.84098830876698604797e-01 0.00000000000000000000e+00 +2.03106158401960029680e-01 8.72256252522846331487e-01 0.00000000000000000000e+00 +3.05707371222781076892e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +3.24723134925933687445e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +3.28434151960668163639e-01 9.81465439717446819223e-01 0.00000000000000000000e+00 +1.21134715075356697023e-01 7.90910816871726485289e-01 0.00000000000000000000e+00 +1.56381549033935418613e-01 8.22385795017326781675e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 6.83698099623542998593e-01 0.00000000000000000000e+00 +2.15582902128448467005e-02 6.77802490578340743888e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 7.05705124201350453284e-01 0.00000000000000000000e+00 +4.59588243760187323272e-02 6.95366241674505003445e-01 0.00000000000000000000e+00 +2.07001939740942796087e-02 6.98166874414742788524e-01 0.00000000000000000000e+00 +2.97015364243899737673e-01 9.56876003036158606641e-01 0.00000000000000000000e+00 +3.04581416319494924227e-01 9.79585588709716015288e-01 0.00000000000000000000e+00 +2.84273717504762768460e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +9.14319223167324612866e-01 2.76409272563788610544e-01 0.00000000000000000000e+00 +9.38870581909584500657e-01 2.87431939459574348206e-01 0.00000000000000000000e+00 +9.68261685876632860470e-01 2.89067928242541294370e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 2.63403053734977576905e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 7.38071359756236056171e-01 0.00000000000000000000e+00 +3.79879665305420388677e-02 7.16508986594743157994e-01 0.00000000000000000000e+00 +6.43317154268549934448e-02 7.20721184174804352374e-01 0.00000000000000000000e+00 +2.44444136098793035616e-01 9.23700793138168663177e-01 0.00000000000000000000e+00 +2.80409653344328879676e-01 9.39378772062682720900e-01 0.00000000000000000000e+00 +2.81591447672121575341e-01 9.74281969666393798946e-01 0.00000000000000000000e+00 +2.49582632674074794688e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +1.68804560420401678078e-01 1.77833008030894718088e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 1.83507505248314106705e-01 0.00000000000000000000e+00 +7.40084442146804188933e-02 2.27343499856671654724e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 7.79051982154413291681e-01 0.00000000000000000000e+00 +3.66136709304839338963e-02 7.54012331776297561881e-01 0.00000000000000000000e+00 +8.80624802405344103695e-02 7.55086594820196910227e-01 0.00000000000000000000e+00 +8.26841992462065911162e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +8.09503905926210243216e-01 1.45355134597800722540e-01 0.00000000000000000000e+00 +7.45448689292483535063e-01 5.87076497250768047920e-02 0.00000000000000000000e+00 +7.44527978828051661964e-01 1.13151245301878741745e-01 0.00000000000000000000e+00 +9.24963442012181946694e-01 7.87823679527505871256e-01 0.00000000000000000000e+00 +8.84651486257979291494e-01 8.00252404703809427922e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 8.08574465283171228691e-01 0.00000000000000000000e+00 +8.17603963735502259347e-01 8.55062473964179114994e-01 0.00000000000000000000e+00 +7.61710736253362497017e-01 9.18117788319712246903e-01 0.00000000000000000000e+00 +8.22785250509635468497e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +1.90163027332763467925e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +2.04854496593113000191e-01 8.92282426558996116794e-02 0.00000000000000000000e+00 +9.26548004906176725370e-01 2.24224340672947997088e-01 0.00000000000000000000e+00 +9.60959652971944300326e-01 2.53055886751256453593e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 2.22400328979324213297e-01 0.00000000000000000000e+00 +2.04663798887100789603e-01 9.23459140704835701463e-01 0.00000000000000000000e+00 +1.60964803457535166054e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +2.37624596691124623238e-01 9.61274043589369520113e-01 0.00000000000000000000e+00 +2.07919611683112570688e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +1.91663206694508536643e-01 9.62359784795448702788e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 1.70040100271491884110e-01 0.00000000000000000000e+00 +9.65263470228709952181e-01 2.04884895285383034080e-01 0.00000000000000000000e+00 +9.11138380745217801859e-01 1.88900330544341799044e-01 0.00000000000000000000e+00 +4.09317995278662485892e-02 7.94431780136132470460e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 8.29759772284212937521e-01 0.00000000000000000000e+00 +8.08632682680703179789e-02 7.97690672895056351877e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 8.96771821550774617116e-01 0.00000000000000000000e+00 +0.00000000000000000000e+00 1.00000000000000000000e+00 0.00000000000000000000e+00 +1.51058498593809847899e-01 9.32317472000455671477e-01 0.00000000000000000000e+00 +8.38513296379886918785e-02 1.00000000000000000000e+00 0.00000000000000000000e+00 +5.49497346855442811431e-02 8.37175058065190880185e-01 0.00000000000000000000e+00 +1.51797672817844897519e-01 8.75035345884154436291e-01 0.00000000000000000000e+00 +1.07571760461458665326e-01 8.38190226402547033935e-01 0.00000000000000000000e+00 +6.61322677771054578955e-02 9.30609310472446216167e-01 0.00000000000000000000e+00 +9.15839396417495232861e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +8.47067672425777073997e-01 7.76616903638159272738e-02 0.00000000000000000000e+00 +8.71488732645265273824e-01 1.46040566136300897782e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 0.00000000000000000000e+00 0.00000000000000000000e+00 +9.45768165448659714478e-01 1.66106914990729409576e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 1.04547924404394892983e-01 0.00000000000000000000e+00 +9.34259953822590638595e-01 6.94462533106913887382e-02 0.00000000000000000000e+00 +0.00000000000000000000e+00 0.00000000000000000000e+00 0.00000000000000000000e+00 +0.00000000000000000000e+00 8.28040222058002567396e-02 0.00000000000000000000e+00 +1.08403364314653194445e-01 0.00000000000000000000e+00 0.00000000000000000000e+00 +8.58933813750019925504e-02 1.47988787050287506908e-01 0.00000000000000000000e+00 +1.65056386415355610398e-01 6.49002842364131971031e-02 0.00000000000000000000e+00 +1.52664873269943296430e-01 1.22486150986413008801e-01 0.00000000000000000000e+00 +6.96276578041511023942e-02 6.30742091657358616708e-02 0.00000000000000000000e+00 +9.15608116241186320572e-01 1.00000000000000000000e+00 0.00000000000000000000e+00 +1.00000000000000000000e+00 8.89994192524539928790e-01 0.00000000000000000000e+00 +1.00000000000000000000e+00 1.00000000000000000000e+00 0.00000000000000000000e+00 +8.50883107490278178808e-01 9.21070954250662099483e-01 0.00000000000000000000e+00 +9.33486330864336300017e-01 8.34967134531070631631e-01 0.00000000000000000000e+00 +8.76755953734244863895e-01 8.46113965556964386927e-01 0.00000000000000000000e+00 +9.38277170641557356490e-01 9.30478036514372885968e-01 0.00000000000000000000e+00 +$ +% Edges (Indices to List of Points): +-3: 165 173 +-2: 381 470 +0: 0 2 +0: 0 1 +0: 0 7 +0: 14 0 +0: 0 6 +0: 3 0 +0: 1 2 +0: 3 2 +0: 1 7 +0: 1 4 +0: 8 1 +0: 3 6 +0: 2 4 +0: 2 9 +0: 5 2 +0: 5 3 +0: 3 11 +0: 10 3 +0: 10 6 +0: 7 8 +0: 4 8 +0: 4 12 +0: 4 13 +0: 9 4 +0: 5 9 +0: 5 11 +0: 5 18 +0: 19 5 +0: 10 15 +0: 14 6 +0: 6 31 +0: 15 6 +0: 7 24 +0: 7 28 +0: 14 7 +0: 8 24 +0: 8 20 +0: 12 8 +0: 13 9 +0: 9 21 +0: 18 9 +0: 10 11 +0: 10 17 +0: 16 10 +0: 19 11 +0: 11 26 +0: 16 11 +0: 13 12 +0: 22 12 +0: 12 20 +0: 12 23 +0: 32 12 +0: 13 22 +0: 13 21 +0: 27 13 +0: 17 15 +0: 14 28 +0: 14 31 +0: 50 14 +0: 17 25 +0: 26 16 +0: 15 31 +0: 15 37 +0: 25 15 +-1: 18 21 +0: 20 24 +-1: 26 19 +0: 34 16 +0: 16 30 +0: 17 16 +0: 17 29 +0: 30 17 +0: 18 35 +-1: 18 19 +0: 36 18 +0: 19 36 +0: 33 19 +0: 20 54 +0: 20 46 +0: 23 20 +-1: 21 27 +0: 21 40 +0: 35 21 +0: 31 50 +0: 22 27 +0: 22 32 +0: 22 39 +0: 38 22 +0: 23 46 +0: 23 47 +0: 32 23 +0: 36 33 +0: 29 25 +0: 24 54 +0: 24 56 +0: 28 24 +0: 25 37 +0: 25 42 +0: 43 25 +0: 30 34 +-1: 34 26 +0: 26 48 +0: 33 26 +-1: 27 39 +0: 27 53 +0: 40 27 +0: 28 56 +0: 28 50 +0: 68 28 +0: 29 42 +0: 29 30 +0: 45 29 +0: 30 45 +0: 44 30 +0: 31 49 +0: 31 52 +0: 37 31 +0: 35 40 +0: 32 47 +0: 32 38 +0: 55 32 +0: 41 33 +0: 33 70 +0: 48 33 +0: 46 54 +0: 48 34 +0: 34 51 +-1: 44 34 +0: 35 62 +0: 35 92 +0: 36 35 +0: 36 92 +0: 41 36 +0: 52 37 +0: 37 60 +0: 43 37 +0: 38 55 +0: 38 57 +0: 39 38 +-1: 39 57 +0: 39 58 +0: 53 39 +0: 40 53 +0: 40 62 +0: 91 40 +0: 43 42 +0: 44 45 +0: 50 68 +0: 41 92 +0: 41 93 +0: 70 41 +0: 66 42 +0: 42 73 +0: 45 42 +0: 43 60 +0: 43 61 +0: 66 43 +-1: 63 44 +0: 44 64 +0: 51 44 +0: 73 45 +0: 45 65 +0: 63 45 +0: 55 47 +0: 46 59 +0: 46 67 +0: 47 46 +0: 47 69 +0: 67 47 +0: 48 70 +0: 48 71 +0: 51 48 +0: 52 49 +0: 76 49 +0: 50 85 +0: 49 86 +0: 50 49 +0: 86 50 +0: 52 76 +0: 51 71 +0: 51 75 +0: 64 51 +0: 54 56 +0: 52 72 +0: 52 60 +0: 89 52 +0: 55 69 +0: 58 53 +0: 53 81 +0: 91 53 +0: 54 97 +0: 54 59 +0: 82 54 +0: 55 74 +0: 55 77 +0: 57 55 +0: 56 97 +0: 56 104 +0: 68 56 +-1: 77 57 +0: 57 78 +0: 58 57 +0: 58 78 +0: 81 58 +0: 59 82 +0: 59 83 +0: 67 59 +0: 66 61 +0: 63 65 +0: 60 89 +0: 60 90 +0: 61 60 +0: 61 90 +0: 79 61 +0: 91 62 +0: 62 115 +0: 92 62 +0: 75 64 +0: 64 63 +0: 63 101 +-1: 100 63 +0: 64 101 +0: 87 64 +0: 69 67 +0: 73 65 +0: 65 80 +0: 100 65 +0: 79 66 +0: 66 84 +0: 73 66 +0: 67 83 +0: 67 98 +0: 88 67 +0: 85 68 +0: 68 103 +0: 104 68 +0: 72 76 +0: 69 88 +0: 69 94 +0: 74 69 +0: 75 71 +0: 76 86 +0: 89 72 +0: 70 93 +0: 70 124 +0: 71 70 +0: 71 124 +0: 142 71 +0: 72 95 +0: 72 117 +0: 99 72 +0: 73 84 +0: 73 80 +0: 96 73 +0: 94 74 +0: 74 102 +0: 77 74 +0: 142 75 +0: 75 111 +0: 87 75 +0: 81 78 +0: 76 112 +0: 76 126 +0: 95 76 +0: 79 90 +-1: 77 102 +0: 77 78 +0: 107 77 +0: 78 107 +0: 108 78 +0: 82 97 +0: 79 105 +0: 79 84 +0: 110 79 +0: 105 90 +0: 112 86 +0: 100 80 +0: 80 109 +0: 96 80 +0: 81 108 +0: 81 123 +0: 91 81 +0: 119 82 +0: 82 118 +0: 83 82 +0: 83 118 +0: 83 120 +0: 98 83 +0: 88 98 +0: 87 111 +0: 110 84 +0: 84 113 +0: 96 84 +0: 103 85 +0: 85 128 +0: 86 85 +0: 86 127 +0: 128 86 +0: 87 101 +0: 87 114 +0: 121 87 +0: 103 104 +0: 88 116 +0: 88 125 +0: 94 88 +0: 89 99 +0: 90 106 +0: 89 122 +0: 90 89 +0: 122 90 +0: 91 123 +0: 91 115 +0: 151 91 +0: 92 115 +0: 93 124 +0: 92 152 +0: 93 92 +0: 152 93 +0: 94 125 +0: 94 130 +0: 102 94 +0: 95 126 +0: 95 133 +0: 117 95 +0: 96 113 +0: 96 143 +0: 109 96 +0: 97 104 +0: 97 167 +0: 119 97 +0: 98 120 +0: 98 134 +0: 116 98 +0: 107 102 +0: 99 117 +0: 99 129 +0: 122 99 +-1: 100 109 +0: 101 121 +0: 100 131 +0: 101 100 +0: 131 101 +0: 112 127 +0: 102 130 +0: 102 147 +-1: 146 102 +0: 127 128 +0: 123 108 +0: 103 128 +0: 103 135 +0: 150 103 +0: 104 167 +0: 104 168 +0: 150 104 +-2: 110 105 +-2: 106 105 +0: 105 139 +0: 132 105 +-2: 106 122 +0: 106 148 +0: 139 106 +0: 107 147 +0: 107 140 +0: 108 107 +0: 108 136 +0: 140 108 +0: 142 111 +0: 109 143 +-1: 109 144 +0: 131 109 +0: 110 132 +0: 110 138 +-2: 113 110 +0: 114 111 +0: 111 149 +0: 141 111 +0: 116 134 +0: 112 137 +0: 112 126 +0: 156 112 +0: 138 113 +0: 113 162 +-2: 143 113 +0: 114 121 +0: 114 154 +0: 149 114 +0: 151 115 +0: 115 182 +0: 152 115 +0: 120 118 +0: 116 165 +0: 116 153 +0: 125 116 +0: 117 133 +0: 117 129 +0: 145 117 +0: 118 155 +0: 118 157 +0: 119 118 +0: 119 157 +0: 167 119 +0: 120 155 +0: 120 173 +0: 134 120 +0: 131 121 +0: 121 160 +0: 154 121 +-2: 122 129 +0: 122 159 +0: 148 122 +0: 123 136 +0: 123 166 +0: 151 123 +0: 152 124 +0: 124 192 +0: 142 124 +0: 130 125 +0: 125 179 +0: 153 125 +0: 126 156 +0: 126 161 +0: 133 126 +0: 127 158 +0: 127 187 +0: 137 127 +0: 128 135 +0: 128 164 +0: 158 128 +0: 150 135 +-2: 145 129 +0: 129 181 +0: 159 129 +0: 132 139 +0: 130 179 +0: 130 171 +0: 146 130 +0: 140 147 +0: 131 144 +0: 131 174 +0: 160 131 +0: 132 169 +0: 132 175 +0: 138 132 +0: 166 136 +0: 133 161 +0: 133 163 +0: 145 133 +0: 134 173 +0: 165 134 +0: 207 135 +0: 135 193 +0: 164 135 +0: 136 140 +0: 136 177 +0: 176 136 +0: 137 187 +0: 137 183 +0: 156 137 +0: 175 138 +0: 138 195 +0: 162 138 +0: 139 148 +0: 139 169 +0: 185 139 +0: 140 170 +0: 140 198 +0: 177 140 +0: 141 149 +0: 173 155 +0: 141 178 +0: 141 214 +0: 142 141 +0: 142 192 +0: 214 142 +-2: 143 144 +0: 143 186 +0: 162 143 +-1: 144 186 +-2: 174 144 +0: 187 158 +-2: 145 163 +0: 145 180 +0: 181 145 +-3: 153 165 +-1: 146 171 +0: 146 199 +0: 147 146 +0: 147 170 +0: 199 147 +0: 148 159 +0: 148 185 +0: 188 148 +0: 178 149 +0: 149 194 +0: 154 149 +0: 150 207 +0: 150 168 +0: 234 150 +0: 151 166 +0: 151 245 +0: 182 151 +0: 152 182 +0: 152 192 +0: 243 152 +0: 153 190 +-3: 153 179 +0: 200 153 +0: 154 160 +0: 154 197 +0: 194 154 +0: 155 184 +0: 155 229 +0: 157 155 +0: 156 183 +0: 156 191 +0: 161 156 +0: 157 229 +0: 157 219 +0: 167 157 +0: 158 164 +0: 158 189 +0: 203 158 +0: 159 181 +0: 159 205 +0: 188 159 +0: 160 174 +0: 160 204 +0: 197 160 +0: 191 161 +0: 161 201 +0: 163 161 +0: 162 195 +0: 162 196 +0: 186 162 +0: 207 193 +0: 175 169 +-2: 201 163 +0: 163 210 +0: 180 163 +0: 164 193 +0: 164 203 +0: 225 164 +0: 165 190 +0: 165 172 +0: 223 165 +0: 166 176 +0: 166 209 +0: 245 166 +0: 167 219 +0: 168 234 +0: 167 253 +0: 168 167 +0: 253 168 +0: 169 185 +0: 169 217 +0: 202 169 +0: 170 199 +0: 170 212 +0: 198 170 +-3: 171 179 +-1: 171 213 +-3: 199 171 +0: 172 173 +0: 172 223 +0: 208 172 +0: 173 208 +0: 184 173 +-2: 204 174 +0: 174 186 +0: 174 222 +0: 221 174 +0: 175 202 +0: 175 211 +0: 195 175 +0: 214 178 +0: 192 243 +0: 176 177 +0: 176 247 +0: 209 176 +0: 187 183 +0: 177 198 +0: 177 226 +0: 247 177 +0: 216 178 +0: 178 231 +0: 194 178 +0: 179 200 +0: 179 213 +0: 236 179 +0: 180 210 +0: 180 181 +0: 230 180 +0: 181 230 +0: 205 181 +0: 182 245 +0: 182 276 +0: 243 182 +0: 183 218 +0: 183 224 +0: 191 183 +0: 208 184 +0: 184 229 +0: 184 228 +0: 215 184 +0: 188 185 +0: 185 220 +0: 217 185 +0: 196 186 +-1: 186 233 +0: 222 186 +-3: 212 199 +0: 187 189 +0: 187 218 +0: 227 187 +0: 188 205 +0: 188 242 +0: 220 188 +0: 189 203 +0: 189 241 +0: 227 189 +0: 190 223 +0: 190 239 +0: 200 190 +0: 191 224 +0: 191 244 +0: 201 191 +0: 263 192 +0: 192 290 +0: 214 192 +0: 193 206 +0: 193 246 +0: 225 193 +0: 194 197 +0: 194 232 +0: 231 194 +0: 211 195 +0: 195 238 +0: 196 195 +0: 196 238 +0: 233 196 +0: 232 197 +0: 197 237 +0: 204 197 +0: 198 212 +0: 198 261 +0: 226 198 +0: 223 208 +0: 199 213 +0: 199 251 +0: 235 199 +0: 239 200 +0: 200 240 +0: 236 200 +0: 211 202 +0: 210 201 +0: 201 255 +-2: 244 201 +0: 202 217 +0: 202 250 +0: 249 202 +0: 203 225 +0: 203 252 +0: 241 203 +0: 204 221 +0: 204 260 +-2: 237 204 +0: 205 230 +0: 205 262 +0: 242 205 +0: 246 206 +0: 245 209 +0: 229 219 +0: 247 226 +0: 258 206 +0: 206 257 +0: 207 206 +0: 207 234 +0: 257 207 +0: 215 208 +0: 208 265 +0: 248 208 +0: 234 253 +0: 209 247 +0: 209 254 +0: 259 209 +0: 210 255 +0: 210 278 +0: 230 210 +0: 211 249 +0: 211 256 +0: 238 211 +0: 212 235 +0: 212 286 +-3: 261 212 +0: 213 236 +-1: 213 268 +0: 251 213 +0: 214 290 +0: 214 216 +0: 328 214 +0: 215 265 +0: 215 228 +0: 284 215 +0: 222 233 +0: 328 216 +0: 216 271 +0: 231 216 +0: 224 218 +0: 217 250 +0: 217 266 +0: 220 217 +0: 218 267 +0: 218 227 +0: 275 218 +0: 219 293 +0: 219 305 +0: 253 219 +0: 220 242 +0: 220 282 +0: 266 220 +0: 221 222 +0: 221 273 +0: 260 221 +0: 222 273 +0: 272 222 +0: 223 248 +0: 223 277 +0: 239 223 +0: 224 267 +0: 224 279 +0: 244 224 +0: 225 252 +0: 225 246 +0: 270 225 +0: 226 261 +0: 226 269 +0: 264 226 +0: 227 275 +0: 227 274 +0: 241 227 +0: 228 284 +0: 229 293 +0: 228 347 +0: 229 228 +0: 347 229 +0: 243 276 +0: 230 278 +0: 230 302 +0: 262 230 +0: 231 232 +0: 231 280 +0: 271 231 +0: 237 232 +0: 232 295 +0: 280 232 +-2: 244 279 +0: 272 233 +-1: 233 285 +0: 238 233 +0: 234 326 +0: 234 287 +0: 257 234 +0: 235 251 +0: 235 300 +0: 286 235 +0: 236 240 +0: 236 289 +0: 268 236 +0: 260 237 +0: 237 288 +-2: 295 237 +0: 238 256 +0: 238 298 +0: 285 238 +0: 239 277 +0: 239 240 +0: 311 239 +0: 240 311 +0: 289 240 +0: 241 252 +0: 241 274 +0: 291 241 +0: 262 242 +0: 242 281 +0: 282 242 +0: 263 243 +0: 243 380 +0: 318 243 +0: 244 283 +0: 244 255 +0: 292 244 +0: 245 254 +0: 245 297 +0: 276 245 +0: 246 270 +0: 246 258 +0: 294 246 +0: 249 250 +0: 264 247 +0: 247 313 +0: 259 247 +0: 248 265 +0: 248 277 +0: 343 248 +0: 308 249 +0: 249 309 +0: 256 249 +0: 266 250 +0: 250 299 +0: 308 250 +0: 251 268 +0: 251 306 +0: 300 251 +0: 270 252 +0: 252 301 +0: 291 252 +0: 253 305 +0: 253 349 +0: 326 253 +0: 259 254 +0: 254 303 +0: 297 254 +0: 255 292 +0: 255 307 +0: 278 255 +0: 257 287 +0: 256 309 +0: 256 320 +0: 298 256 +0: 257 304 +0: 257 321 +0: 258 257 +0: 258 294 +0: 321 258 +0: 259 313 +0: 259 312 +0: 303 259 +0: 288 260 +0: 260 357 +0: 273 260 +-4: 279 267 +0: 261 286 +0: 261 315 +-3: 269 261 +0: 262 302 +0: 262 329 +0: 281 262 +0: 295 288 +0: 290 263 +0: 263 390 +0: 380 263 +0: 264 269 +0: 264 313 +0: 327 264 +0: 265 284 +0: 265 342 +0: 343 265 +0: 266 282 +0: 266 336 +0: 299 266 +-4: 267 275 +0: 267 310 +0: 314 267 +0: 272 285 +0: 268 289 +0: 268 306 +-1: 323 268 +0: 269 315 +0: 269 316 +-3: 327 269 +0: 280 271 +0: 270 294 +0: 270 301 +0: 322 270 +0: 271 328 +0: 271 334 +0: 325 271 +0: 272 273 +0: 272 330 +0: 317 272 +0: 273 330 +0: 357 273 +-4: 274 291 +0: 274 324 +-4: 275 274 +0: 275 324 +0: 310 275 +0: 297 276 +0: 276 416 +0: 318 276 +0: 287 304 +0: 277 343 +0: 277 367 +0: 311 277 +0: 278 307 +0: 278 333 +0: 302 278 +-2: 279 314 +0: 279 319 +-4: 283 279 +0: 280 295 +0: 280 345 +0: 325 280 +0: 281 329 +0: 281 282 +0: 337 281 +0: 282 337 +0: 336 282 +0: 283 319 +0: 283 354 +-4: 292 283 +0: 289 311 +0: 347 284 +0: 284 379 +0: 342 284 +0: 285 298 +-1: 285 340 +0: 317 285 +0: 286 300 +0: 286 361 +0: 315 286 +0: 287 338 +0: 287 332 +0: 326 287 +0: 297 303 +0: 357 288 +0: 288 358 +0: 296 288 +0: 289 335 +0: 289 323 +0: 341 289 +0: 328 290 +0: 290 391 +0: 390 290 +-4: 291 301 +0: 291 346 +0: 324 291 +0: 292 354 +0: 292 355 +-4: 307 292 +0: 305 293 +0: 293 402 +0: 347 293 +0: 299 336 +0: 294 321 +0: 294 339 +0: 322 294 +0: 296 295 +0: 295 344 +-2: 345 295 +0: 296 344 +0: 358 296 +0: 320 309 +0: 297 331 +0: 297 362 +0: 416 297 +0: 298 320 +0: 298 368 +0: 340 298 +0: 348 299 +0: 299 351 +0: 308 299 +0: 300 306 +0: 300 363 +0: 361 300 +-4: 301 322 +0: 301 372 +0: 346 301 +0: 314 310 +0: 302 333 +0: 302 374 +0: 329 302 +0: 337 329 +0: 318 380 +0: 303 312 +0: 303 331 +0: 360 303 +0: 313 327 +0: 326 332 +0: 321 304 +0: 304 356 +0: 338 304 +0: 305 349 +0: 305 402 +0: 424 305 +0: 317 330 +0: 306 323 +0: 306 363 +0: 371 306 +0: 325 334 +0: 307 355 +0: 307 369 +-4: 333 307 +0: 308 351 +0: 308 309 +0: 352 308 +0: 309 352 +0: 350 309 +0: 310 324 +0: 310 395 +0: 366 310 +0: 311 367 +0: 311 335 +0: 376 311 +0: 312 360 +0: 313 385 +0: 312 359 +0: 313 312 +0: 359 313 +-2: 314 366 +0: 314 378 +0: 319 314 +0: 316 327 +0: 361 315 +0: 315 455 +0: 316 315 +0: 316 421 +0: 455 316 +0: 332 338 +0: 340 317 +0: 317 375 +0: 364 317 +0: 318 416 +0: 318 403 +0: 417 318 +0: 319 378 +0: 319 382 +0: 354 319 +0: 345 325 +0: 350 320 +0: 320 389 +0: 368 320 +0: 321 356 +0: 321 373 +0: 339 321 +-4: 322 339 +0: 322 377 +0: 372 322 +0: 323 341 +-1: 323 387 +0: 371 323 +0: 324 346 +0: 324 388 +0: 395 324 +0: 381 325 +0: 325 399 +0: 370 325 +0: 326 349 +0: 326 353 +0: 383 326 +0: 327 421 +0: 327 384 +-3: 385 327 +0: 328 334 +0: 328 392 +0: 391 328 +0: 329 374 +0: 329 365 +0: 406 329 +0: 364 330 +0: 330 397 +0: 357 330 +0: 337 365 +0: 331 360 +0: 331 398 +0: 362 331 +0: 332 353 +0: 332 386 +0: 418 332 +0: 369 333 +0: 333 394 +-4: 374 333 +0: 370 334 +0: 334 393 +0: 392 334 +0: 335 376 +0: 335 428 +0: 341 335 +0: 348 336 +0: 336 404 +0: 337 336 +0: 337 400 +0: 404 337 +0: 386 338 +0: 338 401 +0: 356 338 +-4: 339 373 +0: 339 377 +0: 423 339 +0: 340 368 +-1: 340 405 +0: 375 340 +0: 344 358 +0: 341 428 +0: 341 387 +0: 411 341 +0: 342 379 +0: 342 343 +0: 449 342 +0: 343 449 +0: 367 343 +0: 408 344 +0: 344 409 +0: 345 344 +-2: 345 381 +0: 409 345 +0: 366 395 +0: 346 372 +0: 346 388 +0: 415 346 +0: 408 358 +0: 347 402 +0: 347 379 +0: 478 347 +0: 369 355 +0: 348 404 +0: 348 351 +0: 422 348 +0: 359 385 +0: 349 424 +0: 349 383 +0: 497 349 +0: 352 350 +0: 350 436 +0: 389 350 +0: 395 388 +0: 422 351 +0: 351 437 +0: 352 351 +0: 352 437 +0: 436 352 +0: 390 380 +0: 353 418 +0: 353 383 +0: 419 353 +0: 382 354 +0: 354 434 +0: 355 354 +0: 355 434 +0: 412 355 +0: 356 373 +0: 356 401 +0: 413 356 +0: 357 397 +0: 358 410 +0: 357 425 +0: 358 357 +0: 425 358 +0: 359 440 +0: 359 360 +0: 414 359 +0: 360 414 +0: 398 360 +0: 361 363 +0: 361 454 +0: 455 361 +0: 398 362 +0: 362 432 +0: 416 362 +0: 371 363 +0: 363 462 +0: 454 363 +0: 375 364 +0: 364 430 +0: 397 364 +0: 365 406 +0: 365 407 +0: 400 365 +-2: 366 396 +0: 366 443 +0: 378 366 +0: 367 449 +0: 367 376 +0: 450 367 +0: 368 389 +0: 368 433 +0: 405 368 +0: 369 412 +0: 369 427 +0: 394 369 +0: 370 393 +0: 370 426 +0: 399 370 +0: 371 387 +0: 371 461 +0: 462 371 +0: 402 424 +0: 372 377 +0: 372 441 +0: 415 372 +-4: 413 373 +0: 373 438 +0: 423 373 +-4: 406 374 +0: 374 429 +0: 394 374 +0: 392 391 +0: 384 421 +0: 404 400 +0: 375 405 +0: 375 430 +0: 442 375 +0: 376 450 +0: 376 428 +0: 466 376 +0: 423 377 +0: 377 465 +0: 441 377 +0: 378 443 +0: 378 445 +0: 382 378 +0: 379 478 +0: 379 449 +0: 548 379 +0: 403 380 +0: 380 533 +0: 488 380 +0: 399 381 +0: 381 447 +0: 409 381 +0: 382 445 +0: 382 446 +0: 434 382 +0: 383 419 +0: 383 497 +0: 460 383 +0: 435 384 +0: 384 439 +0: 385 384 +-3: 385 440 +0: 439 385 +0: 426 393 +0: 386 418 +0: 386 469 +0: 401 386 +0: 387 411 +-1: 387 463 +0: 461 387 +0: 388 415 +0: 388 458 +0: 420 388 +0: 389 436 +0: 389 473 +0: 433 389 +0: 391 390 +0: 390 530 +0: 488 390 +0: 391 530 +0: 529 391 +0: 398 414 +0: 392 393 +0: 392 486 +0: 529 392 +0: 393 486 +0: 444 393 +0: 394 427 +0: 394 474 +0: 429 394 +0: 395 396 +0: 395 420 +0: 451 395 +-2: 396 451 +0: 443 396 +0: 397 430 +0: 397 425 +0: 494 397 +0: 398 448 +0: 398 452 +0: 432 398 +0: 399 426 +0: 399 470 +0: 407 400 +0: 400 459 +0: 431 400 +0: 401 469 +0: 401 413 +0: 453 401 +0: 402 479 +0: 402 478 +0: 510 402 +0: 422 404 +0: 414 440 +0: 403 417 +0: 403 535 +0: 533 403 +0: 404 431 +0: 404 476 +0: 480 404 +0: 405 433 +-1: 405 475 +0: 442 405 +0: 410 408 +0: 406 429 +0: 406 487 +-4: 407 406 +0: 407 487 +-4: 459 407 +0: 468 408 +0: 408 464 +0: 409 408 +0: 409 464 +0: 447 409 +0: 410 425 +0: 410 468 +0: 467 410 +0: 411 428 +0: 411 477 +0: 463 411 +0: 436 437 +0: 412 434 +0: 412 498 +0: 427 412 +-4: 413 453 +0: 413 438 +0: 481 413 +0: 414 456 +0: 414 448 +0: 457 414 +0: 415 441 +0: 415 485 +0: 458 415 +0: 419 460 +0: 432 416 +0: 416 534 +0: 417 416 +0: 417 534 +0: 535 417 +0: 469 418 +0: 418 471 +0: 419 418 +0: 419 472 +0: 471 419 +0: 420 458 +0: 420 451 +0: 499 420 +0: 421 455 +0: 421 517 +0: 435 421 +0: 422 480 +0: 422 437 +0: 505 422 +0: 423 438 +0: 423 483 +0: 465 423 +0: 424 479 +0: 424 544 +0: 497 424 +0: 494 425 +0: 425 561 +0: 467 425 +0: 454 462 +0: 444 426 +0: 426 482 +0: 470 426 +0: 445 443 +0: 474 427 +0: 427 539 +0: 498 427 +0: 428 477 +0: 428 508 +0: 466 428 +0: 429 474 +0: 429 513 +0: 487 429 +0: 430 442 +0: 430 546 +0: 494 430 +0: 431 459 +0: 431 528 +0: 476 431 +0: 432 452 +0: 432 514 +0: 534 432 +0: 473 433 +0: 433 489 +0: 475 433 +0: 461 463 +0: 434 446 +0: 434 498 +0: 515 434 +0: 435 517 +0: 435 518 +0: 439 435 +0: 453 469 +0: 484 436 +0: 436 509 +0: 473 436 +0: 505 437 +0: 437 506 +0: 484 437 +0: 438 483 +0: 438 493 +0: 481 438 +0: 439 518 +0: 439 496 +0: 440 439 +0: 440 496 +-3: 456 440 +0: 441 485 +0: 441 465 +0: 521 441 +0: 475 442 +0: 442 491 +0: 546 442 +0: 443 451 +0: 443 490 +0: 500 443 +0: 444 486 +0: 444 537 +0: 482 444 +0: 445 490 +0: 445 516 +0: 446 445 +0: 446 516 +0: 515 446 +0: 470 447 +0: 507 447 +0: 447 520 +0: 464 447 +0: 452 448 +0: 448 502 +0: 457 448 +0: 456 496 +0: 548 449 +0: 449 551 +0: 450 449 +0: 450 551 +0: 450 552 +0: 466 450 +0: 499 451 +-2: 451 501 +0: 500 451 +0: 502 452 +0: 452 503 +0: 514 452 +-4: 453 495 +0: 453 532 +0: 481 453 +0: 454 541 +0: 455 517 +0: 454 579 +0: 455 454 +0: 579 455 +0: 456 555 +-3: 456 556 +0: 457 456 +0: 457 556 +0: 502 457 +0: 458 485 +0: 458 511 +0: 499 458 +0: 459 487 +0: 459 527 +-4: 528 459 +0: 472 460 +0: 460 582 +0: 497 460 +0: 461 492 +0: 462 541 +0: 461 526 +0: 462 461 +0: 526 462 +0: 463 477 +-1: 463 519 +0: 492 463 +0: 464 468 +0: 464 554 +0: 520 464 +0: 465 483 +0: 465 521 +0: 522 465 +0: 466 552 +0: 466 508 +0: 524 466 +0: 467 561 +0: 467 468 +0: 560 467 +0: 468 560 +0: 554 468 +0: 480 476 +0: 469 471 +0: 469 495 +0: 504 469 +0: 470 482 +0: 470 536 +0: 507 470 +0: 471 504 +0: 471 512 +0: 472 471 +0: 472 582 +0: 512 472 +0: 473 509 +0: 473 489 +0: 525 473 +0: 539 474 +0: 474 540 +0: 513 474 +0: 475 489 +-1: 475 531 +0: 491 475 +0: 528 476 +0: 476 572 +0: 557 476 +0: 487 513 +0: 477 508 +0: 477 519 +0: 542 477 +0: 478 510 +0: 478 710 +0: 548 478 +0: 479 544 +0: 479 627 +0: 510 479 +0: 557 480 +0: 480 570 +0: 505 480 +0: 481 532 +0: 481 493 +0: 547 481 +0: 482 537 +0: 482 538 +0: 536 482 +0: 483 493 +0: 483 550 +0: 522 483 +0: 484 506 +0: 484 509 +0: 563 484 +0: 521 485 +0: 485 553 +0: 511 485 +0: 529 486 +0: 486 629 +0: 537 486 +0: 487 523 +0: 487 559 +0: 527 487 +0: 496 518 +0: 488 533 +0: 488 530 +0: 733 488 +0: 489 525 +0: 489 568 +0: 531 489 +0: 491 531 +0: 500 490 +0: 490 603 +0: 516 490 +0: 491 546 +0: 491 545 +0: 543 491 +0: 492 519 +0: 492 526 +0: 589 492 +0: 547 493 +0: 493 549 +0: 550 493 +0: 561 494 +0: 494 598 +0: 546 494 +0: 514 503 +0: 495 532 +0: 495 504 +0: 567 495 +0: 496 587 +0: 496 562 +0: 555 496 +0: 536 507 +0: 563 506 +0: 499 511 +0: 497 544 +0: 497 582 +0: 621 497 +0: 498 515 +0: 498 626 +0: 539 498 +0: 502 556 +0: 502 566 +0: 499 564 +0: 499 501 +0: 565 499 +0: 500 501 +0: 500 569 +0: 603 500 +0: 501 569 +-2: 565 501 +0: 502 573 +0: 502 574 +0: 503 502 +0: 503 578 +0: 574 503 +0: 512 504 +0: 504 577 +0: 567 504 +0: 570 505 +0: 505 575 +0: 506 505 +0: 506 575 +0: 576 506 +0: 557 572 +0: 584 507 +0: 507 580 +0: 520 507 +0: 508 524 +0: 508 586 +0: 542 508 +0: 563 509 +0: 509 596 +0: 525 509 +0: 510 710 +0: 510 627 +0: 709 510 +0: 511 553 +0: 511 564 +0: 558 511 +0: 512 577 +0: 512 581 +0: 582 512 +0: 513 540 +0: 513 616 +0: 523 513 +0: 534 514 +0: 514 634 +0: 578 514 +0: 550 522 +0: 516 515 +0: 515 625 +0: 626 515 +0: 516 625 +0: 603 516 +0: 518 517 +0: 517 617 +0: 579 517 +0: 518 617 +0: 587 518 +0: 542 519 +-1: 519 588 +0: 589 519 +0: 520 554 +0: 520 654 +0: 580 520 +0: 532 567 +0: 521 553 +0: 522 595 +0: 521 585 +0: 522 521 +0: 585 522 +0: 616 523 +0: 523 583 +0: 559 523 +0: 524 552 +0: 524 586 +0: 643 524 +0: 596 525 +0: 525 601 +0: 568 525 +0: 526 589 +0: 526 606 +0: 541 526 +0: 559 527 +0: 527 594 +0: 528 527 +-4: 528 572 +0: 594 528 +0: 529 530 +0: 529 732 +0: 629 529 +0: 530 732 +0: 733 530 +0: 531 568 +-1: 531 591 +0: 543 531 +0: 532 593 +0: 532 547 +0: 592 532 +0: 535 533 +0: 533 657 +0: 733 533 +0: 634 534 +0: 534 628 +0: 535 534 +0: 535 628 +0: 657 535 +0: 536 584 +0: 536 538 +0: 613 536 +0: 629 537 +0: 537 630 +0: 538 537 +0: 538 613 +0: 630 538 +0: 539 626 +0: 539 540 +0: 615 539 +0: 540 615 +0: 616 540 +0: 541 606 +0: 541 689 +0: 579 541 +0: 542 586 +0: 542 588 +0: 600 542 +0: 545 543 +0: 543 590 +0: 591 543 +0: 544 621 +0: 544 660 +0: 627 544 +0: 545 590 +0: 545 599 +0: 546 545 +0: 546 598 +0: 599 546 +0: 547 592 +0: 547 549 +0: 647 547 +0: 548 710 +0: 548 551 +0: 681 548 +0: 549 647 +0: 549 641 +0: 550 549 +0: 550 595 +0: 641 550 +0: 580 584 +0: 569 565 +0: 551 681 +0: 551 642 +0: 552 551 +0: 552 642 +0: 643 552 +0: 553 585 +0: 553 614 +0: 558 553 +0: 554 560 +0: 554 665 +0: 654 554 +0: 555 562 +0: 555 620 +0: 556 555 +0: 556 620 +-3: 566 556 +0: 577 567 +0: 571 557 +0: 557 602 +0: 570 557 +0: 564 558 +0: 558 609 +0: 614 558 +0: 574 578 +0: 566 620 +0: 559 583 +0: 559 605 +0: 594 559 +0: 665 560 +0: 560 655 +0: 561 560 +0: 561 598 +0: 655 561 +0: 562 587 +0: 562 659 +0: 620 562 +0: 589 588 +0: 563 576 +0: 563 632 +0: 596 563 +0: 564 609 +0: 564 610 +0: 565 564 +-2: 565 610 +0: 607 565 +0: 566 622 +-3: 566 623 +0: 573 566 +0: 567 597 +0: 567 664 +0: 593 567 +0: 569 603 +0: 568 601 +0: 568 636 +0: 591 568 +0: 569 607 +0: 569 619 +0: 618 569 +0: 577 597 +0: 570 602 +0: 570 648 +0: 575 570 +-4: 571 602 +0: 572 594 +0: 571 624 +-4: 572 571 +0: 624 572 +0: 623 573 +0: 573 608 +0: 574 573 +0: 574 608 +0: 611 574 +0: 576 575 +0: 575 631 +0: 648 575 +0: 576 631 +0: 632 576 +0: 577 639 +0: 577 581 +0: 640 577 +0: 611 578 +0: 578 633 +0: 634 578 +0: 579 689 +0: 579 690 +0: 617 579 +0: 580 654 +0: 580 687 +0: 604 580 +0: 581 640 +0: 582 621 +0: 581 671 +0: 582 581 +0: 671 582 +0: 583 616 +0: 583 662 +0: 605 583 +0: 613 584 +0: 584 668 +0: 604 584 +0: 601 596 +0: 585 595 +0: 585 638 +0: 614 585 +0: 643 586 +0: 586 653 +0: 600 586 +0: 587 617 +0: 587 699 +0: 659 587 +0: 591 636 +0: 588 600 +0: 588 612 +-1: 644 588 +0: 589 612 +0: 589 649 +0: 606 589 +0: 599 590 +0: 670 590 +0: 591 637 +0: 590 635 +0: 591 590 +0: 635 591 +0: 592 647 +0: 593 664 +0: 592 663 +0: 593 592 +0: 663 593 +0: 594 605 +0: 594 674 +0: 624 594 +0: 595 641 +0: 595 669 +0: 638 595 +0: 596 632 +0: 596 678 +0: 645 596 +0: 639 597 +0: 597 684 +0: 664 597 +0: 598 599 +0: 598 685 +0: 655 598 +0: 599 685 +0: 670 599 +0: 600 653 +0: 600 650 +0: 644 600 +0: 648 602 +0: 601 645 +0: 601 646 +0: 636 601 +0: 624 602 +0: 602 651 +-4: 652 602 +0: 603 619 +0: 603 625 +0: 701 603 +0: 614 609 +0: 604 687 +0: 604 668 +0: 686 604 +0: 605 662 +0: 605 673 +0: 674 605 +0: 607 610 +0: 615 626 +0: 606 649 +0: 606 700 +0: 689 606 +0: 611 608 +0: 620 659 +0: 607 661 +0: 607 676 +0: 618 607 +0: 608 623 +0: 608 698 +0: 682 608 +0: 609 666 +0: 610 661 +0: 609 667 +0: 610 609 +-2: 667 610 +0: 611 682 +0: 611 633 +0: 683 611 +0: 644 612 +0: 612 656 +0: 649 612 +0: 613 630 +0: 613 765 +0: 668 613 +0: 631 648 +0: 666 614 +0: 614 672 +0: 638 614 +0: 615 752 +0: 615 751 +0: 616 615 +0: 616 751 +0: 662 616 +0: 699 617 +0: 617 755 +0: 690 617 +0: 618 676 +0: 618 693 +0: 619 618 +0: 619 693 +0: 701 619 +0: 678 632 +0: 620 729 +0: 620 675 +0: 622 620 +0: 621 671 +0: 621 727 +0: 660 621 +0: 675 622 +0: 622 697 +0: 623 622 +0: 623 697 +-3: 698 623 +0: 651 624 +0: 624 688 +0: 674 624 +0: 625 701 +0: 625 707 +0: 626 625 +0: 626 752 +0: 707 626 +0: 627 709 +0: 627 660 +0: 864 627 +0: 634 628 +0: 724 628 +0: 628 726 +0: 657 628 +3: 658 725 +0: 630 629 +0: 629 835 +0: 732 629 +0: 630 835 +0: 765 630 +0: 638 669 +0: 631 695 +0: 631 694 +0: 632 631 +0: 632 677 +0: 694 632 +0: 683 633 +0: 633 735 +0: 634 633 +0: 634 735 +0: 724 634 +0: 635 637 +0: 670 635 +0: 635 736 +0: 713 635 +0: 646 636 +0: 636 712 +0: 637 636 +0: 637 712 +0: 713 637 +0: 642 681 +0: 638 679 +0: 638 672 +0: 680 638 +0: 639 640 +0: 639 684 +0: 704 639 +0: 640 704 +0: 671 640 +0: 669 641 +0: 641 746 +0: 647 641 +0: 642 717 +0: 642 721 +0: 643 642 +0: 643 721 +0: 653 643 +0: 644 650 +0: 644 656 +-1: 692 644 +0: 645 678 +0: 645 786 +0: 646 645 +0: 646 712 +0: 786 646 +0: 647 746 +0: 647 663 +0: 744 647 +0: 648 652 +0: 648 695 +0: 743 648 +0: 649 656 +0: 649 747 +0: 700 649 +0: 651 688 +0: 650 692 +0: 650 653 +0: 696 650 +0: 651 652 +0: 651 723 +0: 691 651 +0: 652 723 +-4: 743 652 +0: 653 721 +0: 653 696 +0: 722 653 +0: 654 687 +0: 654 759 +0: 665 654 +0: 685 655 +0: 655 769 +0: 665 655 +0: 676 661 +0: 656 692 +0: 656 715 +0: 747 656 +0: 733 657 +0: 657 731 +0: 726 657 +0: 726 731 +0: 658 726 +0: 658 731 +3: 730 658 +0: 666 672 +0: 735 683 +0: 659 699 +0: 659 764 +0: 729 659 +0: 660 864 +0: 660 727 +0: 865 660 +0: 661 667 +0: 661 703 +0: 702 661 +0: 662 751 +0: 662 882 +0: 673 662 +0: 682 698 +0: 663 744 +0: 663 801 +0: 664 663 +0: 664 684 +0: 801 664 +0: 769 665 +0: 665 770 +0: 759 665 +0: 666 706 +0: 666 667 +0: 750 666 +-2: 667 750 +0: 702 667 +0: 668 686 +0: 668 879 +0: 765 668 +0: 669 746 +0: 669 679 +0: 740 669 +0: 685 670 +0: 670 760 +0: 736 670 +0: 727 671 +0: 671 778 +0: 704 671 +0: 690 755 +0: 672 680 +0: 672 758 +0: 706 672 +0: 673 882 +0: 673 788 +0: 674 673 +0: 674 788 +0: 688 674 +0: 677 694 +0: 675 729 +0: 675 728 +0: 675 697 +0: 711 675 +0: 676 703 +0: 676 741 +0: 693 676 +0: 677 678 +0: 677 757 +0: 784 677 +0: 678 784 +0: 786 678 +0: 740 679 +0: 679 739 +0: 680 679 +0: 680 739 +0: 758 680 +4: 716 708 +0: 681 717 +0: 681 710 +0: 718 681 +0: 683 682 +0: 682 767 +0: 705 682 +0: 683 767 +0: 734 683 +0: 684 801 +0: 684 704 +0: 813 684 +0: 760 685 +0: 685 771 +0: 769 685 +0: 879 686 +0: 686 825 +0: 687 686 +0: 687 759 +0: 825 687 +0: 691 688 +0: 688 753 +0: 788 688 +0: 743 695 +0: 689 700 +0: 690 777 +0: 689 811 +0: 690 689 +0: 811 690 +0: 691 753 +0: 691 754 +0: 723 691 +0: 692 696 +0: 692 715 +-1: 748 692 +0: 701 693 +0: 693 854 +0: 741 693 +0: 694 757 +0: 695 742 +0: 694 756 +0: 695 694 +0: 756 695 +0: 747 715 +0: 696 722 +0: 696 749 +0: 748 696 +0: 697 711 +0: 697 775 +0: 698 697 +0: 698 775 +-3: 705 698 +0: 755 699 +0: 699 880 +0: 764 699 +0: 734 767 +0: 700 747 +0: 700 826 +0: 811 700 +0: 701 854 +0: 701 860 +0: 707 701 +0: 706 758 +0: 703 702 +0: 702 797 +0: 750 702 +0: 703 797 +0: 703 796 +0: 741 703 +0: 712 786 +2: 714 785 +0: 705 775 +0: 704 778 +0: 704 813 +0: 866 704 +0: 705 776 +0: 705 767 +-3: 766 705 +0: 706 780 +0: 706 750 +0: 782 706 +0: 749 722 +0: 707 860 +0: 707 928 +0: 752 707 +4: 708 862 +0: 709 864 +0: 708 863 +0: 718 708 +0: 709 863 +0: 710 709 +0: 718 863 +0: 710 718 +0: 863 710 +0: 711 728 +0: 711 843 +0: 775 711 +0: 712 787 +0: 712 737 +0: 713 712 +0: 713 737 +0: 736 713 +0: 787 737 +0: 714 787 +2: 714 738 +0: 737 714 +0: 715 748 +0: 715 803 +0: 763 715 +4: 719 716 +0: 721 717 +0: 716 720 +0: 718 716 +0: 717 720 +0: 718 717 +0: 720 718 +0: 720 719 +0: 719 793 +4: 791 719 +0: 720 793 +0: 721 720 +0: 721 793 +0: 722 721 +0: 722 793 +0: 792 722 +0: 723 754 +0: 723 831 +0: 743 723 +0: 735 724 +3: 817 725 +0: 724 818 +0: 726 724 +0: 725 818 +0: 726 725 +0: 818 726 +0: 727 865 +0: 727 778 +0: 919 727 +0: 758 739 +0: 843 728 +0: 729 764 +0: 728 893 +0: 729 728 +0: 893 729 +0: 730 731 +0: 732 835 +0: 730 836 +3: 837 730 +0: 731 836 +0: 733 731 +0: 732 836 +0: 733 732 +0: 836 733 +0: 768 734 +0: 734 816 +0: 735 734 +0: 735 818 +0: 816 735 +0: 784 757 +0: 736 737 +0: 736 761 +0: 760 736 +0: 737 761 +0: 738 737 +2: 738 762 +0: 761 738 +0: 756 742 +0: 739 808 +0: 739 740 +0: 806 739 +1: 745 805 +0: 740 806 +0: 746 740 +0: 741 796 +0: 741 823 +0: 854 741 +-4: 812 742 +0: 743 831 +0: 742 830 +-4: 743 742 +0: 830 743 +0: 753 788 +1: 745 815 +0: 744 801 +0: 744 802 +0: 746 744 +0: 745 802 +0: 806 745 +0: 746 802 +0: 806 746 +0: 802 806 +0: 749 792 +0: 775 843 +0: 747 763 +0: 747 826 +0: 842 747 +0: 803 748 +0: 749 841 +-1: 748 840 +0: 749 748 +0: 840 749 +1: 789 781 +-2: 750 782 +0: 750 797 +0: 790 750 +0: 882 751 +0: 752 928 +0: 751 926 +0: 752 751 +0: 926 752 +0: 753 871 +0: 753 908 +0: 754 753 +0: 754 908 +0: 831 754 +0: 755 777 +0: 755 899 +0: 880 755 +0: 825 759 +0: 812 756 +0: 756 850 +0: 757 756 +0: 757 819 +0: 850 757 +1: 779 799 +0: 758 780 +0: 758 808 +0: 800 758 +0: 816 768 +0: 759 770 +0: 759 884 +0: 883 759 +0: 760 761 +0: 760 771 +0: 773 760 +0: 761 773 +0: 762 761 +0: 762 773 +2: 772 762 +0: 763 803 +0: 763 842 +0: 875 763 +0: 764 880 +0: 764 893 +0: 894 764 +0: 765 879 +0: 765 932 +0: 835 765 +0: 766 776 +0: 766 878 +-3: 766 844 +0: 767 766 +0: 768 845 +0: 767 844 +0: 768 767 +0: 844 768 +0: 769 771 +0: 769 770 +0: 774 769 +0: 770 774 +0: 884 770 +2: 885 772 +0: 884 774 +0: 774 885 +0: 771 774 +0: 773 771 +0: 772 774 +0: 773 772 +0: 774 773 +0: 775 829 +0: 775 849 +0: 776 775 +0: 776 849 +0: 878 776 +0: 811 777 +0: 777 898 +0: 899 777 +0: 778 919 +0: 778 866 +0: 881 778 +0: 779 800 +1: 779 781 +-2: 783 779 +0: 800 783 +0: 780 800 +0: 780 783 +0: 782 780 +0: 781 783 +0: 790 781 +-2: 782 783 +0: 790 782 +0: 783 790 +0: 784 819 +2: 785 821 +0: 784 820 +0: 786 784 +0: 785 820 +0: 787 785 +0: 786 820 +0: 787 786 +0: 820 787 +0: 871 788 +0: 788 886 +0: 882 788 +1: 794 789 +0: 789 795 +0: 790 789 +0: 790 797 +0: 795 790 +4: 827 791 +0: 792 841 +0: 791 828 +0: 793 791 +0: 792 828 +0: 793 792 +0: 828 793 +1: 822 794 +0: 794 798 +0: 795 794 +0: 822 798 +0: 823 798 +0: 796 823 +0: 795 798 +0: 797 795 +0: 796 798 +0: 797 796 +0: 798 797 +1: 807 799 +0: 799 809 +0: 800 799 +0: 800 808 +0: 809 800 +0: 801 813 +0: 802 815 +0: 801 814 +0: 802 801 +0: 814 802 +0: 803 840 +0: 803 874 +0: 875 803 +0: 812 850 +1: 804 805 +0: 804 810 +1: 807 804 +0: 805 810 +0: 806 805 +0: 806 810 +0: 808 806 +0: 807 810 +0: 809 807 +0: 808 810 +0: 809 808 +0: 810 809 +0: 811 826 +0: 811 898 +0: 924 811 +0: 812 830 +-4: 812 868 +0: 891 812 +0: 813 814 +0: 813 866 +0: 867 813 +0: 814 867 +0: 815 814 +0: 815 867 +1: 935 815 +0: 816 845 +3: 817 834 +0: 816 832 +0: 818 816 +0: 817 832 +0: 818 817 +0: 832 818 +0: 819 850 +0: 819 820 +0: 851 819 +0: 820 851 +0: 821 820 +2: 821 853 +0: 851 821 +1: 824 822 +0: 822 856 +0: 823 822 +0: 823 856 +0: 854 823 +0: 824 856 +1: 855 824 +0: 831 908 +0: 883 825 +0: 825 930 +0: 879 825 +0: 826 842 +0: 826 914 +0: 924 826 +4: 827 838 +0: 827 839 +0: 828 827 +0: 828 841 +0: 839 828 +0: 829 843 +0: 829 916 +0: 849 829 +0: 891 830 +0: 830 889 +0: 831 830 +0: 831 909 +0: 889 831 +0: 832 845 +3: 833 847 +0: 832 846 +0: 834 832 +0: 833 846 +3: 834 833 +0: 846 834 +0: 836 835 +0: 835 933 +0: 932 835 +0: 836 933 +0: 837 836 +0: 837 933 +3: 934 837 +0: 874 840 +4: 838 858 +0: 838 839 +0: 857 838 +0: 839 857 +0: 841 839 +-1: 840 857 +0: 841 840 +0: 857 841 +0: 844 878 +0: 842 875 +0: 842 903 +0: 914 842 +0: 843 893 +0: 843 915 +0: 916 843 +0: 877 844 +-3: 844 848 +0: 845 844 +0: 845 848 +0: 846 845 +0: 846 848 +0: 847 846 +0: 877 848 +-3: 847 848 +3: 876 847 +0: 848 876 +0: 849 916 +0: 849 905 +0: 878 849 +0: 850 868 +0: 850 870 +0: 851 850 +2: 869 852 +0: 851 870 +0: 853 851 +-4: 852 870 +2: 853 852 +0: 870 853 +1: 855 861 +0: 860 854 +0: 854 859 +0: 856 854 +0: 855 859 +0: 856 855 +0: 859 856 +-1: 857 858 +0: 857 874 +0: 872 857 +0: 858 872 +4: 873 858 +0: 859 861 +0: 859 927 +0: 860 859 +0: 860 927 +0: 928 860 +0: 861 927 +1: 925 861 +4: 920 862 +0: 862 921 +0: 863 862 +0: 863 921 +0: 864 863 +0: 864 921 +0: 865 864 +0: 865 921 +0: 919 865 +0: 866 867 +0: 866 881 +0: 936 866 +0: 867 936 +0: 935 867 +0: 868 891 +2: 869 888 +0: 868 890 +-4: 870 868 +0: 869 890 +0: 870 869 +0: 890 870 +0: 871 886 +0: 871 937 +0: 908 871 +0: 914 903 +4: 902 912 +0: 872 874 +0: 872 901 +0: 873 872 +4: 873 900 +0: 901 873 +0: 874 901 +0: 875 874 +0: 875 903 +0: 901 875 +3: 876 896 +0: 876 877 +0: 897 876 +0: 877 897 +0: 878 877 +0: 878 905 +0: 897 878 +0: 879 932 +0: 879 930 +0: 978 879 +0: 880 899 +0: 880 956 +0: 894 880 +0: 881 919 +0: 881 936 +0: 971 881 +0: 882 926 +0: 882 886 +0: 961 882 +0: 883 930 +0: 883 929 +0: 884 883 +0: 884 929 +0: 885 884 +2: 885 931 +0: 929 885 +0: 889 909 +2: 887 911 +0: 898 924 +0: 886 961 +0: 886 947 +0: 937 886 +0: 889 910 +0: 910 892 +0: 887 910 +0: 888 890 +0: 887 892 +2: 888 887 +0: 892 888 +0: 889 892 +0: 891 889 +0: 890 892 +0: 891 890 +0: 892 891 +0: 894 893 +0: 893 940 +0: 915 893 +0: 894 940 +0: 956 894 +3: 895 907 +0: 895 906 +3: 896 895 +0: 897 905 +0: 896 906 +0: 897 896 +0: 906 897 +0: 898 950 +0: 898 957 +0: 899 898 +0: 899 956 +0: 957 899 +4: 902 900 +0: 900 904 +0: 901 900 +0: 901 904 +0: 903 901 +0: 902 904 +0: 913 902 +0: 903 904 +0: 913 903 +0: 904 913 +0: 905 916 +0: 905 917 +0: 906 905 +0: 906 917 +0: 907 906 +3: 907 918 +0: 917 907 +0: 908 937 +0: 908 938 +0: 909 908 +0: 909 938 +0: 910 909 +0: 910 938 +0: 911 910 +2: 911 939 +0: 938 911 +4: 922 912 +0: 912 923 +0: 913 912 +0: 913 923 +0: 914 913 +0: 914 923 +0: 924 914 +0: 915 940 +0: 915 942 +0: 916 915 +0: 916 942 +0: 917 916 +0: 917 942 +0: 918 917 +3: 918 943 +0: 942 918 +0: 919 971 +4: 920 967 +0: 919 969 +0: 921 919 +0: 920 969 +0: 921 920 +0: 969 921 +0: 923 922 +0: 922 948 +4: 949 922 +0: 924 950 +0: 923 948 +0: 924 923 +0: 948 924 +1: 925 959 +0: 961 926 +0: 925 960 +0: 927 925 +0: 926 960 +0: 928 926 +0: 927 960 +0: 928 927 +0: 960 928 +0: 929 930 +0: 929 977 +0: 931 929 +0: 930 977 +0: 978 930 +0: 931 977 +2: 974 931 +0: 932 933 +0: 932 976 +0: 978 932 +0: 933 976 +0: 934 933 +3: 934 973 +0: 976 934 +1: 935 968 +0: 936 971 +0: 935 970 +0: 936 935 +0: 970 936 +0: 947 937 +0: 937 946 +0: 938 937 +2: 939 945 +0: 938 946 +0: 939 938 +0: 946 939 +3: 941 954 +0: 940 956 +0: 941 953 +0: 953 944 +0: 940 953 +0: 940 944 +0: 942 940 +0: 941 944 +3: 943 941 +0: 942 944 +0: 943 942 +0: 944 943 +2: 945 964 +0: 945 963 +0: 946 945 +0: 947 961 +0: 946 963 +0: 947 946 +0: 963 947 +0: 950 948 +0: 948 955 +0: 949 948 +4: 949 951 +0: 955 949 +0: 950 955 +0: 957 950 +4: 951 952 +0: 951 958 +0: 955 951 +0: 953 956 +0: 952 958 +3: 954 952 +0: 953 958 +0: 954 953 +0: 958 954 +0: 955 958 +0: 957 955 +0: 956 958 +0: 957 956 +0: 958 957 +1: 959 962 +0: 959 965 +0: 960 959 +0: 960 965 +0: 961 960 +0: 961 965 +0: 963 961 +0: 962 965 +2: 964 962 +0: 963 965 +0: 964 963 +0: 965 964 +4: 967 966 +0: 966 972 +1: 968 966 +0: 967 972 +0: 969 967 +0: 968 972 +0: 970 968 +0: 969 972 +0: 971 969 +0: 970 972 +0: 971 970 +0: 972 971 +0: 973 976 +0: 974 977 +0: 973 979 +3: 975 973 +0: 974 979 +2: 975 974 +0: 979 975 +0: 976 979 +0: 978 976 +0: 977 979 +0: 978 977 +0: 979 978 +$ +% Faces (Indices to List of Edges): +1: 545 564 0 +1: 450 449 0 +1: 1219 1272 1 +1: 1220 1417 1 +1: 8 3 2 +1: 9 2 7 +1: 10 3 4 +1: 4 5 36 +1: 6 5 31 +1: 6 7 13 +1: 14 8 11 +1: 16 9 17 +1: 21 10 12 +1: 22 11 12 +1: 20 13 19 +1: 25 14 15 +1: 15 16 26 +1: 18 17 27 +1: 18 19 43 +1: 33 20 30 +1: 37 21 34 +1: 39 22 23 +1: 49 23 24 +1: 24 25 40 +1: 42 26 28 +1: 46 27 29 +1: 75 28 29 +1: 57 30 44 +1: 32 31 59 +1: 32 33 63 +1: 97 34 35 +1: 35 36 58 +1: 67 37 38 +1: 38 39 51 +1: 41 40 55 +1: 41 42 66 +1: 48 43 45 +1: 71 44 45 +1: 47 46 68 +1: 47 48 62 +1: 50 49 54 +1: 53 50 87 +1: 81 51 52 +1: 52 53 92 +1: 86 54 56 +1: 82 55 56 +1: 65 57 61 +1: 109 58 60 +1: 85 59 60 +1: 94 61 72 +1: 69 62 102 +1: 118 63 64 +1: 64 65 98 +1: 84 66 74 +1: 95 67 79 +1: 78 68 104 +1: 70 69 101 +1: 70 71 73 +1: 112 72 73 +1: 132 74 76 +1: 77 75 76 +1: 77 78 93 +1: 126 79 80 +1: 80 81 90 +1: 107 82 83 +1: 83 84 119 +1: 178 85 116 +1: 105 86 88 +1: 121 87 89 +1: 140 88 89 +1: 168 90 91 +1: 91 92 120 +1: 123 93 134 +1: 99 94 111 +1: 184 95 96 +1: 96 97 108 +1: 137 98 100 +1: 147 99 100 +1: 129 101 115 +1: 103 102 127 +1: 103 104 125 +1: 143 105 106 +1: 106 107 144 +1: 200 108 110 +1: 149 109 110 +1: 155 111 113 +1: 114 112 113 +1: 114 115 148 +1: 174 116 117 +1: 117 118 135 +1: 145 119 130 +1: 165 120 122 +1: 138 121 122 +1: 124 123 152 +1: 124 125 171 +1: 193 126 166 +1: 128 127 173 +1: 128 129 161 +1: 218 130 131 +1: 131 132 133 +1: 133 134 150 +1: 136 135 186 +1: 136 137 156 +1: 197 138 139 +1: 139 140 141 +1: 203 141 142 +1: 142 143 189 +1: 191 144 146 +1: 216 145 146 +1: 153 147 158 +1: 164 148 159 +1: 235 149 176 +1: 318 150 151 +1: 151 152 245 +1: 154 153 231 +1: 154 155 162 +1: 213 156 157 +1: 157 158 209 +1: 160 159 220 +1: 160 161 183 +1: 163 162 226 +1: 163 164 210 +1: 169 165 188 +1: 208 166 167 +1: 167 168 170 +1: 225 169 170 +1: 247 171 172 +1: 172 173 181 +1: 175 174 180 +1: 177 175 243 +1: 297 176 179 +1: 177 178 179 +1: 238 180 185 +1: 242 181 182 +1: 182 183 219 +1: 198 184 192 +1: 244 185 187 +1: 211 186 187 +1: 241 188 195 +1: 190 189 205 +1: 190 191 283 +1: 272 192 194 +1: 206 193 194 +1: 258 195 196 +1: 196 197 201 +1: 329 198 199 +1: 199 200 237 +1: 202 201 268 +1: 202 203 204 +1: 204 205 262 +1: 286 206 207 +1: 207 208 232 +1: 215 209 229 +1: 228 210 222 +1: 310 211 212 +1: 212 213 214 +1: 214 215 266 +1: 217 216 313 +1: 217 218 315 +1: 224 219 261 +1: 221 220 223 +1: 221 222 342 +1: 223 224 300 +1: 234 225 239 +1: 227 226 254 +1: 227 228 278 +1: 230 229 274 +1: 230 231 253 +1: 289 232 233 +1: 233 234 290 +1: 236 235 295 +1: 236 237 303 +1: 265 238 250 +1: 306 239 240 +1: 240 241 256 +1: 249 242 259 +1: 277 243 263 +1: 252 244 307 +1: 316 245 246 +1: 246 247 248 +1: 248 249 417 +1: 325 250 251 +1: 251 252 336 +1: 294 253 255 +1: 280 254 255 +1: 257 256 322 +1: 257 258 267 +1: 260 259 368 +1: 260 261 291 +1: 271 262 281 +1: 380 263 264 +1: 264 265 323 +1: 276 266 273 +1: 335 267 269 +1: 270 268 269 +1: 270 271 365 +1: 331 272 284 +1: 356 273 275 +1: 292 274 275 +1: 308 276 357 +1: 298 277 344 +1: 279 278 339 +1: 279 280 328 +1: 349 281 282 +1: 282 283 312 +1: 285 284 400 +1: 285 286 287 +1: 391 287 288 +1: 288 289 332 +1: 334 290 304 +1: 375 291 301 +1: 293 292 374 +1: 293 294 326 +1: 296 295 350 +1: 296 297 299 +1: 348 298 299 +1: 340 300 302 +1: 385 301 302 +1: 355 303 352 +1: 394 304 305 +1: 305 306 320 +1: 338 307 309 +1: 360 308 311 +1: 309 310 311 +1: 414 312 314 +1: 388 313 314 +1: 390 315 317 +1: 415 316 319 +1: 317 318 319 +1: 418 320 321 +1: 321 322 345 +1: 423 323 324 +1: 324 325 395 +1: 384 326 327 +1: 327 328 369 +1: 353 329 330 +1: 330 331 402 +1: 405 332 333 +1: 333 334 378 +1: 346 335 363 +1: 396 336 337 +1: 337 338 409 +1: 371 339 341 +1: 406 340 343 +1: 341 342 343 +1: 426 344 379 +1: 437 345 347 +1: 488 346 347 +1: 429 348 424 +1: 366 349 412 +1: 427 350 351 +1: 351 352 430 +1: 553 353 354 +1: 354 355 498 +1: 359 356 372 +1: 358 357 362 +1: 358 359 434 +1: 411 360 361 +1: 361 362 463 +1: 438 363 364 +1: 364 365 367 +1: 454 366 367 +1: 377 368 473 +1: 476 369 370 +1: 370 371 439 +1: 444 372 373 +1: 373 374 382 +1: 376 375 387 +1: 376 377 469 +1: 450 378 392 +1: 459 379 381 +1: 421 380 381 +1: 383 382 462 +1: 383 384 478 +1: 408 385 386 +1: 386 387 496 +1: 389 388 502 +1: 389 390 503 +1: 398 391 403 +1: 485 392 393 +1: 393 394 420 +1: 448 395 397 +1: 431 396 397 +1: 514 398 399 +1: 399 400 401 +1: 401 402 520 +1: 470 403 404 +1: 404 405 449 +1: 407 406 441 +1: 407 408 509 +1: 433 409 410 +1: 410 411 491 +1: 445 412 413 +1: 413 414 500 +1: 416 415 504 +1: 416 417 474 +1: 419 418 435 +1: 419 420 507 +1: 517 421 422 +1: 422 423 446 +1: 481 424 425 +1: 425 426 457 +1: 453 427 428 +1: 428 429 521 +1: 451 430 497 +1: 432 431 484 +1: 432 433 524 +1: 464 434 442 +1: 561 435 436 +1: 436 437 486 +1: 489 438 466 +1: 480 439 440 +1: 440 441 527 +1: 537 442 443 +1: 443 444 460 +1: 456 445 547 +1: 532 446 447 +1: 447 448 482 +1: 452 451 536 +1: 452 453 541 +1: 468 454 455 +1: 455 456 578 +1: 581 457 458 +1: 458 459 515 +1: 461 460 575 +1: 461 462 533 +1: 492 463 465 +1: 555 464 465 +1: 560 466 467 +1: 467 468 582 +1: 494 469 471 +1: 512 470 568 +1: 576 471 472 +1: 472 473 475 +1: 630 474 475 +1: 479 476 477 +1: 477 478 535 +1: 479 480 570 +1: 522 481 613 +1: 540 482 483 +1: 483 484 592 +1: 544 485 506 +1: 563 486 487 +1: 487 488 490 +1: 558 489 490 +1: 526 491 493 +1: 606 492 493 +1: 495 494 587 +1: 495 496 511 +1: 678 497 499 +1: 551 498 499 +1: 549 500 501 +1: 501 502 596 +1: 598 503 505 +1: 577 504 505 +1: 624 506 508 +1: 588 507 508 +1: 529 509 510 +1: 510 511 634 +1: 603 512 513 +1: 513 514 518 +1: 601 515 516 +1: 516 517 530 +1: 673 518 519 +1: 519 520 550 +1: 542 521 523 +1: 619 522 523 +1: 595 524 525 +1: 525 526 616 +1: 569 527 528 +1: 528 529 644 +1: 531 530 627 +1: 531 532 538 +1: 639 533 534 +1: 534 535 609 +1: 631 536 677 +1: 557 537 573 +1: 539 538 656 +1: 539 540 591 +1: 633 541 543 +1: 662 542 543 +1: 622 544 546 +1: 565 545 546 +1: 580 547 548 +1: 548 549 672 +1: 718 550 552 +1: 683 551 554 +1: 552 553 554 +1: 608 555 556 +1: 556 557 659 +1: 612 558 559 +1: 559 560 645 +1: 589 561 562 +1: 562 563 649 +1: 567 564 566 +1: 648 565 566 +1: 567 568 602 +1: 572 569 665 +1: 611 570 571 +1: 571 572 722 +1: 655 573 574 +1: 574 575 637 +1: 585 576 700 +1: 787 577 628 +1: 584 578 579 +1: 579 580 684 +1: 599 581 614 +1: 647 582 583 +1: 583 584 674 +1: 586 585 708 +1: 586 587 636 +1: 654 588 590 +1: 696 589 590 +1: 689 591 593 +1: 594 592 593 +1: 594 595 668 +1: 795 596 597 +1: 597 598 747 +1: 709 599 600 +1: 600 601 625 +1: 605 602 680 +1: 745 603 604 +1: 604 605 703 +1: 607 606 618 +1: 607 608 712 +1: 610 609 641 +1: 610 611 705 +1: 651 612 693 +1: 621 613 615 +1: 714 614 615 +1: 670 616 617 +1: 617 618 719 +1: 664 619 620 +1: 620 621 741 +1: 729 622 623 +1: 623 624 652 +1: 732 625 626 +1: 626 627 658 +1: 629 628 850 +1: 629 630 699 +1: 671 631 632 +1: 632 633 734 +1: 642 634 635 +1: 635 636 751 +1: 638 637 692 +1: 638 639 640 +1: 640 641 760 +1: 643 642 754 +1: 643 644 667 +1: 695 645 646 +1: 646 647 736 +1: 682 648 727 +1: 698 649 650 +1: 650 651 764 +1: 653 652 777 +1: 653 654 767 +1: 661 655 690 +1: 657 656 687 +1: 657 658 791 +1: 710 659 660 +1: 660 661 799 +1: 733 662 663 +1: 663 664 781 +1: 724 665 666 +1: 666 667 770 +1: 750 668 669 +1: 669 670 784 +1: 675 671 797 +1: 685 672 793 +1: 716 673 743 +1: 738 674 800 +1: 676 675 833 +1: 676 677 679 +1: 763 678 679 +1: 681 680 702 +1: 681 682 803 +1: 820 683 761 +1: 802 684 686 +1: 821 685 686 +1: 826 687 688 +1: 688 689 748 +1: 808 690 691 +1: 691 692 773 +1: 766 693 694 +1: 694 695 843 +1: 769 696 697 +1: 697 698 812 +1: 933 699 701 +1: 706 700 701 +1: 856 702 704 +1: 742 703 704 +1: 758 705 726 +1: 707 706 876 +1: 707 708 753 +1: 713 709 730 +1: 809 710 711 +1: 711 712 721 +1: 862 713 715 +1: 739 714 715 +1: 942 716 717 +1: 717 718 818 +1: 786 719 720 +1: 720 721 859 +1: 725 722 723 +1: 723 724 841 +1: 725 726 879 +1: 804 727 728 +1: 728 729 776 +1: 842 730 731 +1: 731 732 757 +1: 815 733 735 +1: 796 734 735 +1: 845 736 737 +1: 737 738 853 +1: 886 739 740 +1: 740 741 782 +1: 914 742 744 +1: 944 743 746 +1: 744 745 746 +1: 891 747 789 +1: 898 748 749 +1: 749 750 846 +1: 756 751 752 +1: 752 753 872 +1: 755 754 772 +1: 755 756 902 +1: 901 757 790 +1: 759 758 865 +1: 759 760 775 +1: 925 761 762 +1: 762 763 827 +1: 814 764 765 +1: 765 766 920 +1: 780 767 768 +1: 768 769 866 +1: 771 770 839 +1: 771 772 849 +1: 830 773 774 +1: 774 775 917 +1: 895 776 778 +1: 779 777 778 +1: 779 780 913 +1: 817 781 783 +1: 884 782 783 +1: 785 784 848 +1: 785 786 906 +1: 788 787 852 +1: 788 789 975 +1: 912 790 792 +1: 824 791 792 +1: 823 793 794 +1: 794 795 889 +1: 873 796 798 +1: 834 797 798 +1: 811 799 806 +1: 801 800 854 +1: 801 802 836 +1: 858 803 805 +1: 893 804 805 +1: 807 806 996 +1: 807 808 828 +1: 810 809 861 +1: 810 811 963 +1: 867 812 813 +1: 813 814 964 +1: 816 815 874 +1: 816 817 936 +1: 984 818 819 +1: 819 820 1049 +1: 822 821 838 +1: 822 823 926 +1: 941 824 825 +1: 825 826 896 +1: 892 827 831 +1: 954 828 829 +1: 829 830 958 +1: 981 831 832 +1: 832 833 835 +1: 946 834 835 +1: 1009 836 837 +1: 837 838 976 +1: 840 839 927 +1: 840 841 883 +1: 864 842 899 +1: 922 843 844 +1: 844 845 869 +1: 973 846 847 +1: 847 848 905 +1: 929 849 949 +1: 851 850 935 +1: 851 852 1133 +1: 871 853 855 +1: 979 854 855 +1: 916 856 857 +1: 857 858 1099 +1: 909 859 860 +1: 860 861 945 +1: 888 862 863 +1: 863 864 970 +1: 919 865 881 +1: 931 866 868 +1: 988 867 868 +1: 1017 869 870 +1: 870 871 1014 +1: 878 872 904 +1: 948 873 875 +1: 967 874 875 +1: 1055 876 877 +1: 877 878 991 +1: 882 879 880 +1: 880 881 987 +1: 882 883 1063 +1: 938 884 885 +1: 885 886 887 +1: 887 888 1000 +1: 890 889 957 +1: 890 891 1024 +1: 983 892 923 +1: 1102 893 894 +1: 894 895 1003 +1: 994 896 897 +1: 897 898 971 +1: 1013 899 900 +1: 900 901 910 +1: 951 902 903 +1: 903 904 1030 +1: 974 905 907 +1: 908 906 907 +1: 908 909 1082 +1: 1029 910 911 +1: 911 912 939 +1: 1004 913 930 +1: 915 914 1114 +1: 915 916 1098 +1: 960 917 918 +1: 918 919 1021 +1: 966 920 921 +1: 921 922 1015 +1: 1020 923 924 +1: 924 925 980 +1: 977 926 955 +1: 928 927 1148 +1: 928 929 953 +1: 1079 930 932 +1: 1040 931 932 +1: 934 933 1057 +1: 934 935 1246 +1: 969 936 937 +1: 937 938 1043 +1: 1139 939 940 +1: 940 941 992 +1: 943 942 985 +1: 943 944 1113 +1: 1080 945 961 +1: 1036 946 947 +1: 947 948 1037 +1: 950 949 952 +1: 950 951 1105 +1: 952 953 1094 +1: 999 954 1031 +1: 1067 955 956 +1: 956 957 1160 +1: 1033 958 959 +1: 959 960 1091 +1: 962 961 1118 +1: 962 963 995 +1: 989 964 965 +1: 965 966 1155 +1: 1039 967 968 +1: 968 969 1109 +1: 1002 970 1011 +1: 1073 971 972 +1: 972 973 1058 +1: 1059 974 1064 +1: 1216 975 1025 +1: 1006 976 978 +1: 1065 977 978 +1: 1054 979 1007 +1: 1068 980 1050 +1: 982 981 1034 +1: 982 983 1087 +1: 1121 984 986 +1: 1188 985 986 +1: 1061 987 1023 +1: 1042 988 990 +1: 1161 989 990 +1: 1074 991 1048 +1: 1116 992 993 +1: 993 994 1071 +1: 1130 995 997 +1: 998 996 997 +1: 998 999 1124 +1: 1045 1000 1001 +1: 1001 1002 1108 +1: 1174 1003 1005 +1: 1077 1004 1005 +1: 1151 1006 1008 +1: 1120 1007 1010 +1: 1008 1009 1010 +1: 1172 1011 1012 +1: 1012 1013 1027 +1: 1052 1014 1018 +1: 1016 1015 1157 +1: 1016 1017 1019 +1: 1337 1018 1019 +1: 1085 1020 1069 +1: 1022 1021 1093 +1: 1022 1023 1164 +1: 1326 1024 1026 +1: 1284 1025 1026 +1: 1212 1027 1028 +1: 1028 1029 1137 +1: 1046 1030 1106 +1: 1032 1031 1126 +1: 1032 1033 1176 +1: 1142 1034 1035 +1: 1035 1036 1088 +1: 1089 1037 1038 +1: 1038 1039 1189 +1: 1096 1040 1041 +1: 1041 1042 1185 +1: 1110 1043 1044 +1: 1044 1045 1127 +1: 1047 1046 1219 +1: 1047 1048 1184 +1: 1122 1049 1051 +1: 1135 1050 1051 +1: 1199 1052 1053 +1: 1053 1054 1230 +1: 1076 1055 1056 +1: 1056 1057 1198 +1: 1195 1058 1060 +1: 1167 1059 1060 +1: 1062 1061 1166 +1: 1062 1063 1145 +1: 1169 1064 1083 +1: 1154 1065 1066 +1: 1066 1067 1158 +1: 1134 1068 1070 +1: 1234 1069 1070 +1: 1072 1071 1181 +1: 1072 1073 1197 +1: 1075 1074 1182 +1: 1075 1076 1252 +1: 1205 1077 1078 +1: 1078 1079 1095 +1: 1081 1080 1117 +1: 1081 1082 1084 +1: 1200 1083 1084 +1: 1086 1085 1236 +1: 1086 1087 1143 +1: 1194 1088 1090 +1: 1207 1089 1090 +1: 1178 1091 1092 +1: 1092 1093 1201 +1: 1112 1094 1103 +1: 1307 1095 1097 +1: 1237 1096 1097 +1: 1214 1098 1100 +1: 1101 1099 1100 +1: 1101 1102 1173 +1: 1104 1103 1301 +1: 1104 1105 1107 +1: 1221 1106 1107 +1: 1260 1108 1170 +1: 1191 1109 1111 +1: 1240 1110 1111 +1: 1146 1112 1293 +1: 1280 1113 1115 +1: 1213 1114 1115 +1: 1141 1116 1179 +1: 1282 1117 1119 +1: 1128 1118 1119 +1: 1231 1120 1150 +1: 1348 1121 1123 +1: 1226 1122 1123 +1: 1125 1124 1132 +1: 1125 1126 1243 +1: 1242 1127 1261 +1: 1129 1128 1341 +1: 1129 1130 1131 +1: 1131 1132 1310 +1: 1218 1133 1248 +1: 1331 1134 1136 +1: 1225 1135 1136 +1: 1138 1137 1224 +1: 1138 1139 1140 +1: 1140 1141 1311 +1: 1192 1142 1144 +1: 1277 1143 1144 +1: 1266 1145 1147 +1: 1304 1146 1149 +1: 1147 1148 1149 +1: 1283 1150 1152 +1: 1153 1151 1152 +1: 1153 1154 1251 +1: 1163 1155 1156 +1: 1156 1157 1443 +1: 1159 1158 1270 +1: 1159 1160 1324 +1: 1162 1161 1187 +1: 1162 1163 1352 +1: 1165 1164 1202 +1: 1165 1166 1265 +1: 1296 1167 1168 +1: 1168 1169 1273 +1: 1264 1170 1171 +1: 1171 1172 1210 +1: 1427 1173 1175 +1: 1204 1174 1175 +1: 1245 1176 1177 +1: 1177 1178 1290 +1: 1313 1179 1180 +1: 1180 1181 1257 +1: 1233 1182 1183 +1: 1183 1184 1271 +1: 1239 1185 1186 +1: 1186 1187 1462 +1: 1346 1188 1279 +1: 1209 1189 1190 +1: 1190 1191 1320 +1: 1193 1192 1315 +1: 1193 1194 1343 +1: 1196 1195 1294 +1: 1196 1197 1259 +1: 1250 1198 1254 +1: 1339 1199 1228 +1: 1275 1200 1287 +1: 1292 1201 1203 +1: 1366 1202 1203 +1: 1430 1204 1206 +1: 1362 1205 1206 +1: 1208 1207 1345 +1: 1208 1209 1401 +1: 1356 1210 1211 +1: 1211 1212 1222 +1: 1511 1213 1215 +1: 1425 1214 1215 +1: 1217 1216 1286 +1: 1217 1218 1540 +1: 1220 1221 1303 +1: 1414 1222 1223 +1: 1223 1224 1379 +1: 1323 1225 1227 +1: 1458 1226 1227 +1: 1229 1228 1384 +1: 1229 1230 1232 +1: 1397 1231 1232 +1: 1256 1233 1353 +1: 1329 1234 1235 +1: 1235 1236 1276 +1: 1309 1237 1238 +1: 1238 1239 1378 +1: 1322 1240 1241 +1: 1241 1242 1334 +1: 1388 1243 1244 +1: 1244 1245 1375 +1: 1247 1246 1249 +1: 1247 1248 1541 +1: 1249 1250 1666 +1: 1318 1251 1268 +1: 1255 1252 1253 +1: 1253 1254 1533 +1: 1255 1256 1409 +1: 1357 1257 1258 +1: 1258 1259 1363 +1: 1263 1260 1262 +1: 1335 1261 1262 +1: 1263 1264 1406 +1: 1368 1265 1267 +1: 1349 1266 1267 +1: 1421 1268 1269 +1: 1269 1270 1372 +1: 1355 1271 1272 +1: 1274 1273 1298 +1: 1274 1275 1369 +1: 1385 1276 1278 +1: 1314 1277 1278 +1: 1514 1279 1281 +1: 1509 1280 1281 +1: 1289 1282 1340 +1: 1399 1283 1317 +1: 1328 1284 1285 +1: 1285 1286 1677 +1: 1371 1287 1288 +1: 1288 1289 1481 +1: 1377 1290 1291 +1: 1291 1292 1403 +1: 1299 1293 1305 +1: 1365 1294 1295 +1: 1295 1296 1297 +1: 1297 1298 1453 +1: 1300 1299 1467 +1: 1300 1301 1302 +1: 1302 1303 1420 +1: 1351 1304 1306 +1: 1477 1305 1306 +1: 1360 1307 1308 +1: 1308 1309 1464 +1: 1391 1310 1386 +1: 1380 1311 1312 +1: 1312 1313 1359 +1: 1439 1314 1316 +1: 1394 1315 1316 +1: 1447 1317 1319 +1: 1423 1318 1319 +1: 1400 1320 1321 +1: 1321 1322 1450 +1: 1456 1323 1332 +1: 1325 1324 1374 +1: 1325 1326 1327 +1: 1327 1328 1682 +1: 1330 1329 1482 +1: 1330 1331 1333 +1: 1490 1332 1333 +1: 1452 1334 1336 +1: 1431 1335 1336 +1: 1441 1337 1338 +1: 1338 1339 1382 +1: 1517 1340 1342 +1: 1389 1341 1342 +1: 1392 1343 1344 +1: 1344 1345 1470 +1: 1512 1346 1347 +1: 1347 1348 1572 +1: 1350 1349 1559 +1: 1350 1351 1476 +1: 1460 1352 1440 +1: 1354 1353 1411 +1: 1354 1355 1485 +1: 1407 1356 1412 +1: 1358 1357 1496 +1: 1358 1359 1577 +1: 1506 1360 1361 +1: 1361 1362 1474 +1: 1498 1363 1364 +1: 1364 1365 1505 +1: 1405 1366 1367 +1: 1367 1368 1561 +1: 1455 1369 1370 +1: 1370 1371 1502 +1: 1436 1372 1373 +1: 1373 1374 1623 +1: 1376 1375 1494 +1: 1376 1377 1499 +1: 1466 1378 1459 +1: 1416 1379 1381 +1: 1575 1380 1381 +1: 1632 1382 1383 +1: 1383 1384 1395 +1: 1483 1385 1437 +1: 1387 1386 1528 +1: 1387 1388 1493 +1: 1390 1389 1598 +1: 1390 1391 1527 +1: 1524 1392 1393 +1: 1393 1394 1519 +1: 1539 1395 1396 +1: 1396 1397 1398 +1: 1398 1399 1424 +1: 1530 1400 1402 +1: 1471 1401 1402 +1: 1404 1403 1501 +1: 1404 1405 1550 +1: 1433 1406 1408 +1: 1547 1407 1408 +1: 1535 1409 1410 +1: 1410 1411 1521 +1: 1549 1412 1413 +1: 1413 1414 1415 +1: 1415 1416 1627 +1: 1418 1417 1487 +1: 1419 1418 1604 +1: 1419 1420 1469 +1: 1422 1421 1434 +1: 1422 1423 1449 +1: 1568 1424 1445 +1: 1426 1425 1719 +1: 1426 1427 1428 +1: 1730 1428 1429 +1: 1429 1430 1473 +1: 1432 1431 1581 +1: 1432 1433 1583 +1: 1435 1434 1590 +1: 1435 1436 1562 +1: 1563 1437 1438 +1: 1438 1439 1518 +1: 1700 1440 1442 +1: 1634 1441 1444 +1: 1442 1443 1444 +1: 1741 1445 1446 +1: 1446 1447 1448 +1: 1448 1449 1578 +1: 1532 1450 1451 +1: 1451 1452 1571 +1: 1538 1453 1454 +1: 1454 1455 1663 +1: 1457 1456 1491 +1: 1457 1458 1573 +1: 1554 1459 1461 +1: 1660 1460 1463 +1: 1461 1462 1463 +1: 1507 1464 1465 +1: 1465 1466 1553 +1: 1480 1467 1468 +1: 1468 1469 1640 +1: 1526 1470 1472 +1: 1647 1471 1472 +1: 1652 1473 1475 +1: 1605 1474 1475 +1: 1758 1476 1478 +1: 1479 1477 1478 +1: 1479 1480 1736 +1: 1504 1481 1515 +1: 1488 1482 1484 +1: 1564 1483 1484 +1: 1523 1485 1486 +1: 1486 1487 1569 +1: 1593 1488 1489 +1: 1489 1490 1492 +1: 1619 1491 1492 +1: 1610 1493 1495 +1: 1543 1494 1495 +1: 1497 1496 1694 +1: 1497 1498 1620 +1: 1545 1499 1500 +1: 1500 1501 1546 +1: 1503 1502 1664 +1: 1503 1504 1601 +1: 1622 1505 1536 +1: 1607 1506 1508 +1: 1637 1507 1508 +1: 1611 1509 1510 +1: 1510 1511 1718 +1: 1709 1512 1513 +1: 1513 1514 1612 +1: 1516 1515 1747 +1: 1516 1517 1596 +1: 1675 1518 1520 +1: 1556 1519 1520 +1: 1690 1521 1522 +1: 1522 1523 1686 +1: 1558 1524 1525 +1: 1525 1526 1626 +1: 1570 1527 1529 +1: 1608 1528 1529 +1: 1531 1530 1644 +1: 1531 1532 1614 +1: 1534 1533 1668 +1: 1534 1535 1688 +1: 1651 1536 1537 +1: 1537 1538 1661 +1: 1636 1539 1566 +1: 1679 1540 1542 +1: 1670 1541 1542 +1: 1657 1543 1544 +1: 1544 1545 1671 +1: 1673 1546 1552 +1: 1548 1547 1585 +1: 1548 1549 1631 +1: 1712 1550 1551 +1: 1551 1552 1704 +1: 1639 1553 1555 +1: 1658 1554 1555 +1: 1557 1556 1716 +1: 1557 1558 1723 +1: 1560 1559 1759 +1: 1560 1561 1713 +1: 1591 1562 1625 +1: 1643 1563 1565 +1: 1595 1564 1565 +1: 1761 1566 1567 +1: 1567 1568 1739 +1: 1602 1569 1685 +1: 1600 1570 1765 +1: 1615 1571 1580 +1: 1707 1572 1574 +1: 1818 1573 1574 +1: 1629 1575 1576 +1: 1576 1577 1693 +1: 1743 1578 1579 +1: 1775 1579 1588 +1: 1770 1580 1582 +1: 1587 1581 1582 +1: 1586 1583 1584 +1: 1584 1585 1779 +1: 1586 1587 1727 +1: 1797 1588 1589 +1: 1589 1590 1592 +1: 1751 1591 1592 +1: 1594 1593 1617 +1: 1594 1595 1744 +1: 1597 1596 1789 +1: 1597 1598 1599 +1: 1599 1600 1800 +1: 1793 1601 1745 +1: 1603 1602 1726 +1: 1603 1604 1642 +1: 1653 1605 1606 +1: 1606 1607 1701 +1: 1609 1608 1767 +1: 1609 1610 1655 +1: 2210 1611 1613 +1: 1957 1612 1613 +1: 1735 1614 1616 +1: 1748 1615 1616 +1: 1806 1617 1618 +1: 1618 1619 1820 +1: 1697 1620 1621 +1: 1621 1622 1649 +1: 1624 1623 1680 +1: 1624 1625 1810 +1: 1645 1626 1724 +1: 1628 1627 1630 +1: 1628 1629 1954 +1: 1630 1631 1884 +1: 1633 1632 1635 +1: 1633 1634 1813 +1: 1635 1636 1835 +1: 1638 1637 1702 +1: 1638 1639 1764 +1: 1738 1640 1641 +1: 1641 1642 1814 +1: 1778 1643 1674 +1: 1733 1644 1646 +1: 1829 1645 1648 +1: 1646 1647 1648 +1: 1650 1649 1822 +1: 1650 1651 1753 +1: 1732 1652 1654 +1: 1832 1653 1654 +1: 1656 1655 1828 +1: 1656 1657 1780 +1: 1844 1658 1659 +1: 1659 1660 1698 +1: 1662 1661 1755 +1: 1662 1663 1665 +1: 1791 1664 1665 +1: 1669 1666 1667 +1: 1667 1668 1967 +1: 1669 1670 2271 +1: 1782 1671 1672 +1: 1672 1673 1706 +1: 1854 1674 1676 +1: 1715 1675 1676 +1: 1678 1677 1684 +1: 1678 1679 2046 +1: 1681 1680 1960 +1: 1681 1682 1683 +1: 1683 1684 1963 +1: 1825 1685 1687 +1: 1691 1686 1687 +1: 1689 1688 1965 +1: 1689 1690 1692 +1: 1917 1691 1692 +1: 1894 1693 1695 +1: 1696 1694 1695 +1: 1696 1697 1926 +1: 1897 1698 1699 +1: 1699 1700 1811 +1: 1834 1701 1703 +1: 1839 1702 1703 +1: 1705 1704 1710 +1: 1705 1706 1849 +1: 1943 1707 1708 +1: 1708 1709 1958 +1: 1845 1710 1711 +1: 1711 1712 1714 +1: 1868 1713 1714 +1: 1851 1715 1717 +1: 1721 1716 1717 +1: 2122 1718 1720 +1: 1728 1719 1720 +1: 2001 1721 1722 +1: 1722 1723 1725 +1: 1859 1724 1725 +1: 1827 1726 1816 +1: 1772 1727 1783 +1: 1990 1728 1729 +1: 1729 1730 1731 +1: 1731 1732 2004 +1: 1831 1733 1734 +1: 1734 1735 1750 +1: 1756 1736 1737 +1: 1737 1738 2038 +1: 1763 1739 1740 +1: 1740 1741 1742 +1: 1742 1743 1752 +1: 1776 1744 1786 +1: 1746 1745 1790 +1: 1746 1747 1787 +1: 1749 1748 1768 +1: 1749 1750 1886 +1: 1808 1751 1799 +1: 1940 1752 1773 +1: 1824 1753 1754 +1: 1754 1755 1856 +1: 1757 1756 2041 +1: 1757 1758 1760 +1: 1870 1759 1760 +1: 1837 1761 1762 +1: 1762 1763 1899 +1: 1840 1764 1842 +1: 1804 1765 1766 +1: 1766 1767 1862 +1: 1909 1768 1769 +1: 1769 1770 1771 +1: 1771 1772 1893 +1: 1946 1773 1774 +1: 1774 1775 1795 +1: 1867 1776 1777 +1: 1777 1778 1852 +1: 1883 1779 1784 +1: 1879 1780 1781 +1: 1781 1782 1838 +1: 1902 1783 1785 +1: 1934 1784 1785 +1: 1865 1786 1805 +1: 1876 1787 1788 +1: 1788 1789 1802 +1: 1880 1790 1792 +1: 1858 1791 1794 +1: 1792 1793 1794 +1: 1796 1795 1903 +1: 1796 1797 1798 +1: 1798 1799 1898 +1: 1801 1800 1803 +1: 1801 1802 1920 +1: 1803 1804 1973 +1: 1994 1805 1807 +1: 1817 1806 1807 +1: 1809 1808 1912 +1: 1809 1810 1978 +1: 2147 1811 1812 +1: 1812 1813 1931 +1: 2036 1814 1815 +1: 1815 1816 1887 +1: 1998 1817 1819 +1: 1941 1818 1821 +1: 1819 1820 1821 +1: 1928 1822 1823 +1: 1823 1824 1890 +1: 1826 1825 1919 +1: 1826 1827 1888 +1: 1864 1828 1877 +1: 1861 1829 1830 +1: 1830 1831 1923 +1: 1833 1832 2006 +1: 1833 1834 1873 +1: 1929 1835 1836 +1: 1836 1837 2055 +1: 1987 1838 1847 +1: 1875 1839 1841 +1: 1914 1840 1841 +1: 1916 1842 1843 +1: 1843 1844 1895 +1: 1846 1845 1872 +1: 1848 1846 1982 +1: 1981 1847 1850 +1: 1848 1849 1850 +1: 2016 1851 1853 +1: 2070 1852 1855 +1: 1853 1854 1855 +1: 1892 1856 1857 +1: 1857 1858 1951 +1: 1999 1859 1860 +1: 1860 1861 1970 +1: 1937 1862 1863 +1: 1863 1864 2010 +1: 1866 1865 1995 +1: 1866 1867 2071 +1: 1871 1868 1869 +1: 1869 1870 2039 +1: 1871 1872 2087 +1: 2026 1873 1874 +1: 1874 1875 2007 +1: 1882 1876 2018 +1: 2012 1877 1878 +1: 1878 1879 1985 +1: 1881 1880 1949 +1: 1881 1882 2028 +1: 1936 1883 1885 +1: 1952 1884 1885 +1: 1906 1886 1921 +1: 2137 1887 1889 +1: 2081 1888 1889 +1: 2066 1890 1891 +1: 1891 1892 2099 +1: 1907 1893 1900 +1: 1955 1894 1924 +1: 2023 1895 1896 +1: 1896 1897 2144 +1: 1905 1898 1911 +1: 2057 1899 1938 +1: 2042 1900 1901 +1: 1901 1902 1932 +1: 1948 1903 1904 +1: 1904 1905 2067 +1: 2077 1906 1908 +1: 2061 1907 1910 +1: 1908 1909 1910 +1: 2124 1911 1913 +1: 1976 1912 1913 +1: 1915 1914 2008 +1: 1915 1916 2021 +1: 1969 1917 1918 +1: 1918 1919 2083 +1: 2019 1920 1971 +1: 1922 1921 2053 +1: 1922 1923 1992 +1: 2328 1924 1925 +1: 1925 1926 1927 +1: 1927 1928 2064 +1: 1930 1929 2172 +1: 1930 1931 2093 +1: 2109 1932 1933 +1: 1933 1934 1935 +1: 1935 1936 2155 +1: 1974 1937 2110 +1: 2103 1938 1939 +1: 1939 1940 1944 +1: 2090 1941 1942 +1: 1942 1943 2059 +1: 1945 1944 2105 +1: 1945 1946 1947 +1: 1947 1948 2169 +1: 1950 1949 2024 +1: 1950 1951 2101 +1: 2181 1952 1953 +1: 1953 1954 1956 +1: 2204 1955 1956 +1: 2206 1957 1959 +1: 2058 1958 1959 +1: 1961 1960 1980 +1: 1962 1961 2251 +1: 1962 1963 2048 +1: 2253 1964 2050 +1: 1966 1965 1968 +1: 1966 1967 2265 +1: 1968 1969 2367 +1: 2085 1970 1991 +1: 2161 1971 1972 +1: 1972 1973 1975 +1: 2102 1974 1975 +1: 1977 1976 2054 +1: 1977 1978 1979 +1: 1979 1980 2248 +1: 1989 1981 1984 +1: 1983 1982 2089 +1: 1983 1984 2221 +1: 1986 1985 2013 +1: 1986 1987 1988 +1: 1988 1989 2219 +1: 2121 1990 2002 +1: 2117 1991 1993 +1: 2094 1992 1993 +1: 1997 1994 1996 +1: 2130 1995 1996 +1: 1997 1998 2092 +1: 2000 1999 2084 +1: 2000 2001 2015 +1: 2230 2002 2003 +1: 2003 2004 2005 +1: 2005 2006 2033 +1: 2025 2007 2009 +1: 2043 2008 2009 +1: 2114 2010 2011 +1: 2011 2012 2014 +1: 2189 2013 2014 +1: 2305 2015 2017 +1: 2068 2016 2017 +1: 2032 2018 2020 +1: 2143 2019 2020 +1: 2045 2021 2022 +1: 2022 2023 2176 +1: 2140 2024 2030 +1: 2152 2025 2027 +1: 2034 2026 2027 +1: 2031 2028 2029 +1: 2029 2030 2151 +1: 2031 2032 2247 +1: 2242 2033 2035 +1: 2164 2034 2035 +1: 2138 2036 2037 +1: 2037 2038 2075 +1: 2040 2039 2134 +1: 2040 2041 2073 +1: 2062 2042 2107 +1: 2153 2043 2044 +1: 2044 2045 2163 +1: 2047 2046 2269 +1: 2047 2048 2049 +1: 2049 2050 2051 +1: 2051 2052 2264 +1: 2096 2053 2076 +1: 2128 2054 2275 +1: 2174 2055 2056 +1: 2056 2057 2260 +1: 2605 2058 2060 +1: 2255 2059 2060 +1: 2080 2061 2063 +1: 2183 2062 2063 +1: 2325 2064 2065 +1: 2065 2066 2097 +1: 2171 2067 2126 +1: 2303 2068 2069 +1: 2069 2070 2072 +1: 2129 2071 2072 +1: 2074 2073 2377 +1: 2074 2075 2349 +1: 2199 2076 2078 +1: 2079 2077 2078 +1: 2079 2080 2185 +1: 2135 2081 2082 +1: 2082 2083 2365 +1: 2292 2084 2086 +1: 2115 2085 2086 +1: 2088 2087 2132 +1: 2088 2089 2281 +1: 2091 2090 2256 +1: 2091 2092 2192 +1: 2335 2093 2145 +1: 2119 2094 2095 +1: 2095 2096 2182 +1: 2423 2097 2098 +1: 2098 2099 2100 +1: 2100 2101 2142 +1: 2158 2102 2111 +1: 2262 2103 2104 +1: 2214 2104 2106 +1: 2167 2105 2106 +1: 2188 2107 2108 +1: 2108 2109 2157 +1: 2113 2110 2112 +1: 2278 2111 2112 +1: 2113 2114 2415 +1: 2116 2115 2288 +1: 2116 2117 2118 +1: 2118 2119 2258 +1: 2208 2120 2232 +1: 2234 2121 2123 +1: 2212 2122 2123 +1: 2125 2124 2127 +1: 2125 2126 2196 +1: 2127 2128 2175 +1: 2452 2129 2131 +1: 2193 2130 2131 +1: 2133 2132 2353 +1: 2133 2134 2376 +1: 2136 2135 2510 +1: 2136 2137 2139 +1: 2338 2138 2139 +1: 2141 2140 2149 +1: 2141 2142 2301 +1: 2159 2143 2299 +1: 2178 2144 2146 +1: 2394 2145 2148 +1: 2146 2147 2148 +1: 2332 2149 2150 +1: 2150 2151 2245 +1: 2166 2152 2154 +1: 2226 2153 2154 +1: 2156 2155 2179 +1: 2156 2157 2295 +1: 2341 2158 2160 +1: 2286 2159 2162 +1: 2160 2161 2162 +1: 2228 2163 2313 +1: 2201 2164 2165 +1: 2165 2166 2319 +1: 2216 2167 2168 +1: 2168 2169 2170 +1: 2170 2171 2191 +1: 2173 2172 2337 +1: 2173 2174 2362 +1: 2374 2175 2273 +1: 2314 2176 2177 +1: 2177 2178 2473 +1: 2581 2179 2180 +1: 2180 2181 2202 +1: 2345 2182 2198 +1: 2184 2183 2186 +1: 2184 2185 2323 +1: 2445 2186 2187 +1: 2187 2188 2293 +1: 2419 2189 2217 +1: 2417 2190 2223 +1: 2391 2191 2195 +1: 2398 2192 2194 +1: 2480 2193 2194 +1: 2368 2195 2197 +1: 2371 2196 2197 +1: 2406 2198 2200 +1: 2322 2199 2200 +1: 2244 2201 2311 +1: 2596 2202 2203 +1: 2203 2204 2326 +1: 2601 2205 2207 +1: 2603 2206 2209 +1: 2207 2208 2211 +1: 2209 2210 2213 +1: 2211 2212 2213 +1: 2259 2214 2215 +1: 2215 2216 2312 +1: 2222 2217 2218 +1: 2218 2219 2220 +1: 2220 2221 2279 +1: 2222 2223 2225 +1: 2283 2224 2225 +1: 2316 2226 2227 +1: 2227 2228 2359 +1: 2231 2229 2236 +1: 2233 2230 2240 +1: 2231 2232 2235 +1: 2233 2234 2235 +1: 2237 2236 2239 +1: 2237 2238 2432 +1: 2239 2240 2241 +1: 2241 2242 2243 +1: 2243 2244 2434 +1: 2334 2245 2246 +1: 2246 2247 2297 +1: 2250 2248 2276 +1: 2252 2249 2491 +1: 2250 2251 2254 +1: 2252 2253 2254 +1: 2607 2255 2257 +1: 2397 2256 2257 +1: 2287 2258 2346 +1: 2261 2259 2554 +1: 2363 2260 2263 +1: 2261 2262 2263 +1: 2268 2264 2266 +1: 2534 2265 2270 +1: 2266 2267 2538 +1: 2268 2269 2272 +1: 2270 2271 2272 +1: 2274 2273 2348 +1: 2274 2275 2277 +1: 2489 2276 2277 +1: 2342 2278 2412 +1: 2282 2279 2280 +1: 2280 2281 2352 +1: 2282 2283 2285 +1: 2356 2284 2285 +1: 2296 2286 2339 +1: 2467 2287 2289 +1: 2291 2288 2289 +1: 2465 2290 2307 +1: 2291 2292 2309 +1: 2441 2293 2294 +1: 2294 2295 2504 +1: 2298 2296 2476 +1: 2524 2297 2300 +1: 2298 2299 2300 +1: 2421 2301 2330 +1: 2453 2302 2306 +1: 2455 2303 2304 +1: 2304 2305 2308 +1: 2306 2307 2310 +1: 2308 2309 2310 +1: 2430 2311 2317 +1: 2519 2312 2389 +1: 2360 2313 2315 +1: 2511 2314 2315 +1: 2318 2316 2457 +1: 2548 2317 2320 +1: 2318 2319 2320 +1: 2408 2321 2426 +1: 2410 2322 2324 +1: 2427 2323 2324 +1: 2327 2325 2650 +1: 2756 2326 2329 +1: 2327 2328 2329 +1: 2622 2330 2331 +1: 2331 2332 2333 +1: 2333 2334 2507 +1: 2396 2335 2336 +1: 2336 2337 2644 +1: 2351 2338 2508 +1: 2340 2339 2460 +1: 2340 2341 2343 +1: 2493 2342 2343 +1: 2449 2344 2400 +1: 2404 2345 2347 +1: 2450 2346 2347 +1: 2372 2348 2486 +1: 2380 2349 2350 +1: 2350 2351 2655 +1: 2355 2352 2354 +1: 2385 2353 2354 +1: 2355 2356 2357 +1: 2357 2358 2387 +1: 2459 2359 2361 +1: 2551 2360 2361 +1: 2646 2362 2364 +1: 2678 2363 2364 +1: 2641 2365 2366 +1: 2366 2367 2536 +1: 2393 2368 2369 +1: 2550 2369 2370 +1: 2370 2371 2373 +1: 2559 2372 2375 +1: 2373 2374 2375 +1: 2384 2376 2378 +1: 2379 2377 2378 +1: 2379 2380 2382 +1: 2386 2381 2383 +1: 2382 2657 2383 +1: 2384 2385 2388 +1: 2386 2387 2388 +1: 2521 2389 2390 +1: 2390 2391 2392 +1: 2392 2393 2570 +1: 2395 2394 2474 +1: 2395 2396 2692 +1: 2647 2397 2399 +1: 2609 2398 2399 +1: 2403 2400 2402 +1: 2407 2401 2402 +1: 2403 2404 2405 +1: 2405 2406 2409 +1: 2407 2408 2411 +1: 2409 2410 2411 +1: 2494 2412 2414 +1: 2497 2413 2416 +1: 2414 2415 2418 +1: 2416 2417 2420 +1: 2418 2419 2420 +1: 2422 2421 2620 +1: 2422 2423 2651 +1: 2425 2424 2438 +1: 2425 2426 2428 +1: 2443 2427 2428 +1: 2431 2429 2516 +1: 2517 2430 2433 +1: 2431 2432 2435 +1: 2433 2434 2435 +1: 2437 2436 2439 +1: 2437 2438 2442 +1: 2439 2502 2440 +1: 2440 2441 2444 +1: 2442 2443 2446 +1: 2444 2445 2446 +1: 2448 2447 2469 +1: 2448 2449 2451 +1: 2471 2450 2451 +1: 2479 2452 2454 +1: 2483 2453 2456 +1: 2454 2455 2456 +1: 2541 2457 2458 +1: 2458 2459 2631 +1: 2571 2460 2477 +1: 2464 2461 2462 +1: 2462 2463 2468 +1: 2464 2465 2466 +1: 2466 2467 2470 +1: 2468 2469 2472 +1: 2470 2471 2472 +1: 2513 2473 2475 +1: 2662 2474 2475 +1: 2522 2476 2478 +1: 2613 2477 2478 +1: 2482 2479 2481 +1: 2608 2480 2481 +1: 2482 2483 2484 +1: 2484 2485 2612 +1: 2527 2486 2488 +1: 2530 2487 2490 +1: 2488 2489 2492 +1: 2490 2491 2492 +1: 2573 2493 2495 +1: 2496 2494 2495 +1: 2496 2497 2499 +1: 2576 2498 2499 +1: 2501 2500 2505 +1: 2501 2502 2503 +1: 2503 2504 2583 +1: 2505 2506 2585 +1: 2714 2507 2525 +1: 2509 2508 2653 +1: 2509 2510 2642 +1: 2553 2511 2512 +1: 2512 2513 2727 +1: 2543 2514 2515 +1: 2515 2516 2518 +1: 2546 2517 2518 +1: 2556 2519 2520 +1: 2520 2521 2568 +1: 2523 2522 2674 +1: 2523 2524 2526 +1: 2660 2525 2526 +1: 2561 2527 2529 +1: 2563 2528 2531 +1: 2529 2530 2533 +1: 2531 2532 2533 +1: 2535 2534 2537 +1: 2535 2536 2767 +1: 2537 2538 2539 +1: 2539 2540 2771 +1: 2547 2541 2588 +1: 2587 2542 2544 +1: 2545 2543 2544 +1: 2545 2546 2549 +1: 2547 2548 2549 +1: 2638 2550 2557 +1: 2632 2551 2552 +1: 2552 2553 2623 +1: 2680 2554 2555 +1: 2555 2556 2730 +1: 2558 2557 2564 +1: 2558 2559 2560 +1: 2560 2561 2562 +1: 2562 2563 2565 +1: 2564 2635 2567 +1: 2565 2566 2567 +1: 2705 2568 2569 +1: 2569 2570 2639 +1: 2616 2571 2572 +1: 2572 2573 2575 +1: 2577 2574 2618 +1: 2575 2576 2579 +1: 2577 2578 2579 +1: 2592 2580 2584 +1: 2582 2581 2594 +1: 2582 2583 2586 +1: 2584 2585 2586 +1: 2590 2587 2589 +1: 2625 2588 2589 +1: 2590 2591 2627 +1: 2597 2592 2593 +1: 2593 2594 2595 +1: 2595 2596 2758 +1: 2597 2598 2754 +1: 2600 2599 2742 +1: 2600 2601 2602 +1: 2602 2603 2604 +1: 2604 2605 2606 +1: 2606 2607 2740 +1: 2611 2608 2610 +1: 2648 2609 2610 +1: 2611 2612 2777 +1: 2676 2613 2615 +1: 2669 2614 2617 +1: 2615 2616 2619 +1: 2617 2618 2619 +1: 2665 2620 2621 +1: 2621 2622 2712 +1: 2703 2623 2725 +1: 2723 2624 2701 +1: 2630 2625 2626 +1: 2626 2627 2629 +1: 2697 2628 2629 +1: 2630 2631 2633 +1: 2699 2632 2633 +1: 2688 2634 2636 +1: 2637 2635 2636 +1: 2637 2638 2640 +1: 2686 2639 2640 +1: 2769 2641 2643 +1: 2764 2642 2643 +1: 2693 2644 2645 +1: 2645 2646 2682 +1: 2737 2647 2649 +1: 2775 2648 2649 +1: 2752 2650 2652 +1: 2663 2651 2652 +1: 2760 2653 2654 +1: 2654 2655 2656 +1: 2656 2657 2659 +1: 2762 2658 2659 +1: 2716 2660 2666 +1: 2718 2661 2668 +1: 2747 2662 2690 +1: 2801 2663 2664 +1: 2664 2665 2779 +1: 2667 2666 2673 +1: 2667 2668 2670 +1: 2675 2669 2672 +1: 2670 2671 2672 +1: 2673 2674 2677 +1: 2675 2676 2677 +1: 2679 2678 2681 +1: 2679 2680 2728 +1: 2681 2682 2787 +1: 2709 2683 2684 +1: 2684 2685 2687 +1: 2707 2686 2689 +1: 2687 2688 2689 +1: 2811 2690 2691 +1: 2691 2692 2694 +1: 2824 2693 2694 +1: 2696 2695 2700 +1: 2696 2697 2698 +1: 2698 2699 2702 +1: 2700 2701 2704 +1: 2702 2703 2704 +1: 2732 2705 2706 +1: 2706 2707 2708 +1: 2708 2709 2711 +1: 2734 2710 2711 +1: 2781 2712 2713 +1: 2713 2714 2715 +1: 2715 2716 2717 +1: 2717 2718 2720 +1: 2784 2719 2720 +1: 2722 2721 2744 +1: 2722 2723 2724 +1: 2724 2725 2726 +1: 2726 2727 2749 +1: 2792 2728 2729 +1: 2729 2730 2731 +1: 2731 2732 2733 +1: 2733 2734 2736 +1: 2796 2735 2736 +1: 2846 2737 2739 +1: 2842 2738 2741 +1: 2739 2740 2743 +1: 2741 2742 2743 +1: 2745 2744 2748 +1: 2745 2746 2807 +1: 2805 2747 2750 +1: 2748 2749 2750 +1: 2828 2751 2753 +1: 2755 2752 2830 +1: 2753 2754 2757 +1: 2755 2756 2759 +1: 2757 2758 2759 +1: 2763 2760 2761 +1: 2761 2762 2765 +1: 2763 2764 2860 +1: 2765 2766 2851 +1: 2770 2767 2768 +1: 2768 2769 2858 +1: 2770 2771 2773 +1: 2850 2772 2773 +1: 2844 2774 2776 +1: 2848 2775 2778 +1: 2776 2777 2778 +1: 2780 2779 2803 +1: 2780 2781 2783 +1: 2800 2782 2785 +1: 2783 2784 2785 +1: 2819 2786 2788 +1: 2815 2787 2790 +1: 2789 2788 2793 +1: 2789 2790 2791 +1: 2791 2792 2795 +1: 2793 2794 2797 +1: 2795 2796 2797 +1: 2836 2798 2799 +1: 2799 2800 2802 +1: 2832 2801 2804 +1: 2802 2803 2804 +1: 2806 2805 2810 +1: 2806 2807 2809 +1: 2814 2808 2809 +1: 2810 2811 2822 +1: 2816 2812 2813 +1: 2813 2814 2821 +1: 2823 2815 2818 +1: 2816 2817 2820 +1: 2818 2819 2820 +1: 2821 2822 2825 +1: 2823 2824 2825 +1: 2833 2826 2827 +1: 2827 2828 2829 +1: 2829 2830 2831 +1: 2831 2832 2835 +1: 2833 2834 2837 +1: 2835 2836 2837 +1: 2839 2838 2841 +1: 2839 2840 2843 +1: 2841 2842 2845 +1: 2843 2844 2847 +1: 2845 2846 2849 +1: 2847 2848 2849 +1: 2857 2850 2852 +1: 2859 2851 2854 +1: 2852 2853 2856 +1: 2854 2855 2856 +1: 2857 2858 2861 +1: 2859 2860 2861 +$ +% Checking Net ... +% F: 6.966008 +% GN: 48.229437 +% Unused Vertices: none +% Unused Edges: none +% Inconsistent Vertices: none +% Inconsistent Vertices (boundary): none +% Done. +% diff --git a/tests/data/fracture.art.dgf b/tests/data/fracture.art.dgf new file mode 100644 index 00000000000..4ea79057c3f --- /dev/null +++ b/tests/data/fracture.art.dgf @@ -0,0 +1,2882 @@ +DGF + +GridParameter +overlap 1 +closure green +# + +Vertex +parameters 1 +5.0438121165960959e-01 4.9753323140297590e-01 0 +4.7917685943903265e-01 5.0306089452034553e-01 0 +4.9281437342383072e-01 5.2150498192461658e-01 0 +5.1974751000361696e-01 5.1301572609793455e-01 0 +4.6651801174154450e-01 5.2115343944312587e-01 0 +5.0839814778987957e-01 5.4109353138096383e-01 0 +5.3217040879725253e-01 4.8340869951567478e-01 0 +4.7410753163648212e-01 4.8034839204874646e-01 0 +4.5681566233917659e-01 4.9941364692959206e-01 0 +4.8574322679474674e-01 5.4385602563463120e-01 0 +5.4432770100067873e-01 5.0295141181599823e-01 0 +5.3793443996159107e-01 5.3106876128800395e-01 0 +4.4384166443544448e-01 5.2403260172494670e-01 0 +4.6362567379689440e-01 5.5076918434276845e-01 0 +5.0218030278628045e-01 4.6841607963004778e-01 0 +5.5540129010842265e-01 4.8206772401548997e-01 0 +5.6166835712048713e-01 5.3605947331477244e-01 0 +5.6587781369283296e-01 5.0332667806410258e-01 0 +5.0199198394522038e-01 5.6402419873796572e-01 1 +5.2583185916591380e-01 5.6037333620753871e-01 1 +4.2933658380074169e-01 4.9662184384003033e-01 0 +4.7855565969904568e-01 5.6761326106523113e-01 1 +4.4118921833519603e-01 5.5469700640008035e-01 0 +4.1780196554890792e-01 5.1500626230341295e-01 0 +4.4466293579544686e-01 4.7652889090790584e-01 0 +5.8262892175668479e-01 4.8285988228305071e-01 0 +5.4963925283182902e-01 5.5672744793516338e-01 1 +4.5510609396589002e-01 5.7120435120716484e-01 1 +4.6181841074055979e-01 4.4119594714785632e-01 0 +5.8758597007626712e-01 5.0914301700142728e-01 0 +5.8476207300607519e-01 5.3365823361856990e-01 0 +5.3830162919198088e-01 4.4433878687476153e-01 0 +4.2509026025251051e-01 5.3544539418949588e-01 0 +5.4436638281605165e-01 5.7926750521518200e-01 0 +5.7243987286632769e-01 5.5323573799189152e-01 1 +4.9377362850739026e-01 5.9000447713063320e-01 0 +5.1464455265262687e-01 5.9508687058299836e-01 0 +5.7532790893506636e-01 4.5088499327982229e-01 0 +4.1629606636938438e-01 5.5789550097386609e-01 0 +4.3177784932098295e-01 5.7477686211530921e-01 1 +4.7096156384589821e-01 5.9366714054174696e-01 0 +5.3437144146268845e-01 5.9852191310268343e-01 0 +6.0451929874085630e-01 4.8826550815951081e-01 0 +5.9848599031378569e-01 4.6228521712664039e-01 0 +5.9446774247633616e-01 5.4986236782783526e-01 1 +6.0765768143766052e-01 5.1523902605844074e-01 0 +4.0325296602434618e-01 4.9871720878602754e-01 0 +3.9918127980193790e-01 5.2481179640298836e-01 0 +5.6745231781783800e-01 5.8297097585004332e-01 0 +5.3436399410377899e-01 4.0611669832722286e-01 0 +4.9396912024268724e-01 4.1434442658477255e-01 0 +5.8862057053661532e-01 5.7871484553816777e-01 0 +5.8070860823128323e-01 4.2209354723513887e-01 0 +4.4731070986338178e-01 5.9480649149543874e-01 0 +4.1337923287011924e-01 4.7455278760200265e-01 0 +3.9553545294867415e-01 5.5049192631219390e-01 0 +4.1819200499794379e-01 4.3348770976253204e-01 0 +4.0815194560936024e-01 5.7839495683307718e-01 1 +4.2423962861574138e-01 5.9680771552895229e-01 0 +3.8428672421171922e-01 4.8049673659826259e-01 0 +6.0105304673130933e-01 4.3578382513530101e-01 0 +6.1967942217826322e-01 4.5416213660337917e-01 0 +4.8703783072753321e-01 6.1499757086089679e-01 0 +6.1745254719725662e-01 5.4634245165596074e-01 1 +6.0961214942087050e-01 5.6692603878649883e-01 0 +6.2608894532635850e-01 5.2490129937876473e-01 0 +6.2216272106153570e-01 4.7530482482683645e-01 0 +3.7118109482332151e-01 5.0497857778813837e-01 0 +4.5327316274501189e-01 4.0210862022917709e-01 0 +3.7638653677830841e-01 5.3544339052794099e-01 0 +5.5634707934392880e-01 6.0268455922324438e-01 0 +5.7759118695851219e-01 6.0677296825151072e-01 0 +5.9006234858701345e-01 3.9704974866410797e-01 0 +6.2787460998583622e-01 5.0119489197791334e-01 0 +3.7528625654209269e-01 5.6387392930099389e-01 0 +5.9713606125213048e-01 5.9851052784855407e-01 0 +5.5429741246175002e-01 3.7660882410273744e-01 0 +3.8441822616978716e-01 5.8202956256014049e-01 1 +4.0045958873988757e-01 6.0411492813570145e-01 0 +6.4098743316494688e-01 4.6017129123277151e-01 0 +6.4895746507871011e-01 5.2483678448616855e-01 0 +4.2567059041873750e-01 6.1985390877512481e-01 0 +3.6543915970380114e-01 4.5555590298309323e-01 0 +3.5537378628157368e-01 4.7717490984147004e-01 0 +6.5053923802134261e-01 4.8318334095999133e-01 0 +4.8626310385858096e-01 3.7420310042837951e-01 0 +5.2340403003428015e-01 3.6524106224812852e-01 0 +6.2339915894330289e-01 5.8491274236854895e-01 0 +3.5159432688470238e-01 5.2695688754371495e-01 0 +6.1222407037562543e-01 4.1661209086207668e-01 0 +6.3104422646117919e-01 4.3258462935526065e-01 0 +4.6296792958816990e-01 6.1816359096903795e-01 0 +5.1097546199312960e-01 6.1834746938733576e-01 0 +5.3967641961572610e-01 6.2122059660785112e-01 0 +3.5404851596625464e-01 5.5521005731007111e-01 0 +5.8401876850680745e-01 3.7030593818327562e-01 0 +6.6229479251990964e-01 5.0785618280490097e-01 0 +3.8099821095348874e-01 4.2756093973455928e-01 0 +3.4740567858176852e-01 4.9872259987056072e-01 0 +6.1966820447743431e-01 3.9661583944327811e-01 0 +6.4037522849155193e-01 5.4283204912828209e-01 1 +6.3566745807036451e-01 5.6481910399580670e-01 0 +3.6142285860476620e-01 5.8555109633661173e-01 1 +4.5160710658896774e-01 3.5478683757320278e-01 0 +4.0171710656527260e-01 3.9669077782221340e-01 0 +6.5695508571725469e-01 4.4376584220958482e-01 1 +6.4943888281152196e-01 4.2124190374262421e-01 1 +3.7567654319632121e-01 6.1218676481479950e-01 0 +3.9748132485949550e-01 6.2755238920073309e-01 0 +6.6576991634538163e-01 5.3894308102340882e-01 1 +6.6480853274890384e-01 4.6730040612609158e-01 1 +6.1782963543527347e-01 6.1307230804908985e-01 0 +5.4835917999920492e-01 3.4974076641542129e-01 0 +6.7238258633354586e-01 4.8999770674790161e-01 1 +6.3937180050369580e-01 6.0144300809104678e-01 0 +4.8836355033614948e-01 6.4875156642320908e-01 0 +3.2898770341514988e-01 5.2162879414089391e-01 0 +6.0408360345608592e-01 3.7851798610351467e-01 0 +3.3506635909300986e-01 4.5552793194667757e-01 0 +3.4641486180481162e-01 4.3304471566058778e-01 0 +3.2988054008305706e-01 4.7760781857198820e-01 0 +6.5450601063156222e-01 5.7921183323864656e-01 0 +6.4133683400179431e-01 3.9696235047146028e-01 1 +4.1865925236644230e-01 6.4995989198023096e-01 0 +5.6247679456448052e-01 6.4613837762002335e-01 0 +3.3227667864239291e-01 5.4664146215035714e-01 0 +5.7024750515162714e-01 3.4949633779219380e-01 0 +5.2595600091270134e-01 3.3260291829955874e-01 0 +4.8298224266919282e-01 3.2723990017644611e-01 0 +6.3379447952153034e-01 3.7436004311758375e-01 1 +3.3656982338908148e-01 5.6995689716453113e-01 0 +6.7392718390687556e-01 5.6080974810511897e-01 0 +6.8088904967272168e-01 4.5098073459122090e-01 0 +5.9810346163840200e-01 3.4982773319385763e-01 0 +3.1815008633803576e-01 4.9865841538985045e-01 0 +4.5151975139856931e-01 3.1901490854524800e-01 0 +3.9146476972067612e-01 6.5242547472297197e-01 0 +5.4652631277989838e-01 3.2137784332803265e-01 0 +6.8990702229871159e-01 4.7854822670565905e-01 0 +6.7264208347348264e-01 4.2513091590637597e-01 0 +3.6769587136870741e-01 6.3584192259168615e-01 0 +6.2691351784235860e-01 6.4162405687840329e-01 0 +5.9429448486234526e-01 6.2603730871041730e-01 0 +6.8034257634140205e-01 5.1385154988960635e-01 1 +6.8759999999999999e-01 5.3559999999999997e-01 1 +6.2618076420742774e-01 3.5154388748812321e-01 1 +3.3794212812474883e-01 5.8914695907930514e-01 1 +3.5226331457320936e-01 6.0646811462342076e-01 0 +6.6376798464682518e-01 4.0546460725425743e-01 0 +6.4635487705882433e-01 6.2407374833357110e-01 0 +4.1135402401648963e-01 3.5040938199516153e-01 0 +4.5461718204004453e-01 6.7040833069343420e-01 0 +5.3225357586542477e-01 6.7389124306638593e-01 0 +3.1088870084943721e-01 5.4056696080223832e-01 1 +6.6287845978212379e-01 6.0274347462366318e-01 0 +3.1245100355302569e-01 4.5315362239428186e-01 0 +5.7137394008629938e-01 3.2121586532474977e-01 0 +3.1738046300288125e-01 4.3027810410827855e-01 0 +5.0683222285545915e-01 3.1349219891610425e-01 0 +6.5728264193579766e-01 3.8044232852281890e-01 0 +6.7745625900125483e-01 5.8523492082125483e-01 0 +5.9136878743643717e-01 3.2334604236615649e-01 0 +6.9254150239426071e-01 5.0094520563457534e-01 0 +6.1860335256301602e-01 3.2883652370908217e-01 1 +4.7388878525072708e-01 3.0065408602558641e-01 0 +3.0836553351955764e-01 5.1806193606960294e-01 1 +4.1759380802232943e-01 6.7996287438304992e-01 0 +3.5172874510904006e-01 3.9514746877770307e-01 0 +3.7314465908015504e-01 3.6206678079292048e-01 0 +6.9363551364827170e-01 4.3250310304994338e-01 0 +3.3660883283420140e-01 6.2477551233923123e-01 0 +3.1669999999999998e-01 5.9240000000000004e-01 1 +2.9089333809382584e-01 5.0112182052393650e-01 0 +3.0430000000000001e-01 4.8180000000000001e-01 1 +6.9713836118588524e-01 5.6421508355765559e-01 1 +7.0426004140117304e-01 4.6111648427423330e-01 0 +3.8991050396346860e-01 6.7663719283644141e-01 0 +3.6870860562845009e-01 6.6398469268026494e-01 0 +6.5228695542117254e-01 6.4721389090803949e-01 0 +3.1410397242679938e-01 5.6924510890354951e-01 1 +6.4210005337012899e-01 3.4048241450805522e-01 0 +6.4910213697496266e-01 3.5976328890145326e-01 0 +4.9281661180865222e-01 6.9245917020647263e-01 0 +5.6184200456216360e-01 2.9922167230967778e-01 0 +2.9195558483077072e-01 4.5805990042067674e-01 0 +6.8850457023045464e-01 4.0565686761515407e-01 0 +7.0994322755317685e-01 5.3217880312242904e-01 1 +5.3137283538739599e-01 3.0145671124891138e-01 0 +6.7998074308291423e-01 3.8301799108765816e-01 0 +5.1284130795950400e-01 2.8750627503586301e-01 0 +2.8921291571482066e-01 5.3751345448958543e-01 0 +5.8859680116388613e-01 3.0218561372326475e-01 0 +5.7482407918615741e-01 6.8356642637212839e-01 0 +4.5268144702793084e-01 2.9631104217708398e-01 0 +6.7149017154838320e-01 6.2951161439235337e-01 0 +7.1003041953511992e-01 4.8547152291950330e-01 0 +7.2398527566061088e-01 5.0687721797027452e-01 0 +6.8561641670312401e-01 6.1139546733011096e-01 0 +3.5034155240024428e-01 6.4847127310550179e-01 0 +3.1932734491030601e-01 6.1573128072855621e-01 1 +2.9218227765778743e-01 5.5996335966808852e-01 0 +6.1159741111116672e-01 3.0784169478499540e-01 1 +7.1715487693813507e-01 4.4038294700645148e-01 0 +4.9194406154940945e-01 2.8067808481270135e-01 0 +7.0449245704815155e-01 5.8627737114445455e-01 1 +6.7120222620092174e-01 3.5875899019241897e-01 0 +4.3348892147734752e-01 2.8518852982743509e-01 0 +4.2249374047796406e-01 3.1199285903972590e-01 0 +2.7134271765227114e-01 4.8776897715504086e-01 0 +4.0483121584573606e-01 7.0508092589376148e-01 0 +6.3148386766612719e-01 3.1702296017647880e-01 0 +7.2754358136711161e-01 4.6230836220200017e-01 0 +3.2236530381566769e-01 6.4270888529991621e-01 1 +2.9301387547192231e-01 5.9602733514244322e-01 1 +6.2174913542483901e-01 6.7385102921809259e-01 0 +2.6733037370765461e-01 4.6330228135122881e-01 0 +6.5407291594542405e-01 6.7240953047009777e-01 0 +7.1048153361223232e-01 4.1306912743901897e-01 0 +5.5264309994271632e-01 2.7300471940085458e-01 0 +3.1109609014260620e-01 3.9073825468312956e-01 0 +7.0756708555887082e-01 3.8778972596460770e-01 0 +7.1958159306737368e-01 5.6907348000412317e-01 0 +7.2541268570648454e-01 5.4812426976810569e-01 0 +2.7484464266731251e-01 5.1694544612084514e-01 0 +5.7856460281871447e-01 2.7858303888381392e-01 0 +4.6884075328161184e-01 2.7675741899578293e-01 0 +3.4597978683057423e-01 6.7516014163473181e-01 0 +5.3266582591683442e-01 2.6478556506272194e-01 0 +2.7106981413267894e-01 4.4289499778508462e-01 0 +2.9369349411358303e-01 4.3411116799369687e-01 0 +6.6282925277736293e-01 3.3086617362428206e-01 0 +6.7562704941343443e-01 6.5461257245015159e-01 0 +6.9751215079568230e-01 6.3618760149995490e-01 0 +7.3528117322485975e-01 5.2829905475529149e-01 1 +3.8284696432065091e-01 3.1972172252509096e-01 0 +3.0340478566555740e-01 6.3302773009969815e-01 0 +2.7935447624412274e-01 5.7834762694428465e-01 0 +7.1354283905585514e-01 6.1342851716756552e-01 1 +7.4136634780351085e-01 4.9063041301515192e-01 0 +2.6495598571327272e-01 5.4168335945195578e-01 0 +2.6282941069749699e-01 5.6309725021816814e-01 0 +5.0818836728391359e-01 2.6180220289815287e-01 0 +6.9454294361300328e-01 3.6365759795087110e-01 0 +5.2591416822184145e-01 7.1389220600945058e-01 0 +6.0398851046183100e-01 2.8503996734677794e-01 1 +4.3903541286500547e-01 7.0633233928936012e-01 0 +4.4761210357623143e-01 2.6615674730042027e-01 0 +3.7654936828144059e-01 6.9726970747039219e-01 0 +2.5513956606132182e-01 5.0214675595947389e-01 0 +7.3834018502286280e-01 4.4078206674592763e-01 0 +7.3215163307384301e-01 4.1734123894126285e-01 0 +2.8486317911163167e-01 6.1783673496745417e-01 0 +4.8692569662980462e-01 2.5356350219965290e-01 0 +3.3728225717167543e-01 3.4074811910067559e-01 0 +4.0641592637864687e-01 7.3126212878557195e-01 0 +6.3649489171868689e-01 2.9532342883919260e-01 0 +7.5024228952046401e-01 4.6866347613752679e-01 0 +3.9972936974956680e-01 2.7001752650245181e-01 0 +4.2314497144771968e-01 2.6272611146689900e-01 0 +3.8045141867251914e-01 7.2162427925121853e-01 0 +7.3177089408717544e-01 5.8859433630053137e-01 0 +3.2510769955953867e-01 6.6706183748296244e-01 1 +6.9014354443955273e-01 3.4050277133856943e-01 0 +5.6684902703764728e-01 7.2190807544271607e-01 0 +3.4984007598646788e-01 7.0098449080945047e-01 0 +2.4982706307668578e-01 4.7944100962740799e-01 0 +7.2978653377272984e-01 3.8845984690306090e-01 0 +5.7430928699168182e-01 2.5587396771904602e-01 1 +2.6708510636060412e-01 5.9999811285604898e-01 1 +3.2794917805437807e-01 6.9229466176393284e-01 1 +4.6504969222976306e-01 2.4517035345738419e-01 0 +6.8194480873855123e-01 6.7881644198019453e-01 0 +7.4998162667356294e-01 5.4680036520272124e-01 0 +7.4638787060972855e-01 5.7011930163652036e-01 0 +5.2683010771030125e-01 2.4323777669770222e-01 1 +5.5053077954973662e-01 2.4954551455733148e-01 1 +4.7188962510029675e-01 7.3312551183788810e-01 0 +2.4432302333899522e-01 5.2256378508624779e-01 0 +6.5801706451777353e-01 3.0345983369617330e-01 0 +5.9619999999999995e-01 2.6169999999999999e-01 1 +7.0192508760370886e-01 6.6261430813407307e-01 0 +7.1159071694989506e-01 3.4300836581943694e-01 0 +7.1914139785204745e-01 3.6777771559614525e-01 0 +6.1351096477247635e-01 2.6631282766101649e-01 1 +2.5083058683492987e-01 4.5106259877340715e-01 0 +7.5891228511781073e-01 5.2468065691955912e-01 1 +3.0339044085501521e-01 6.5816804222528535e-01 0 +3.7245532984736102e-01 2.7288995243822084e-01 0 +7.3673725496980169e-01 6.1089378436065178e-01 0 +2.5293273343808131e-01 5.8287564298949357e-01 0 +6.0700993842081630e-01 7.1363412658608305e-01 0 +5.0335939256252515e-01 2.3699123991034135e-01 1 +6.3491381663701973e-01 2.7201601453725438e-01 1 +2.7242598391456990e-01 3.9446943568817738e-01 0 +4.3870171669336633e-01 2.4039734389471579e-01 0 +7.2108136761297259e-01 6.3604410283891766e-01 1 +7.4102947860130775e-01 6.3135615959457181e-01 0 +4.1632284629587962e-01 7.5302548228097610e-01 0 +7.6165952032352446e-01 4.9841692848402663e-01 0 +7.4986682055920839e-01 3.9910671762543715e-01 0 +2.8372721838503434e-01 6.4527405183457276e-01 0 +4.8017153476830521e-01 2.3081998326904501e-01 1 +6.8257971764946335e-01 3.1640786390949527e-01 0 +3.9265044482441952e-01 7.4856599448083205e-01 0 +3.9343399713664845e-01 2.4854306897090553e-01 0 +2.8834958073963396e-01 3.5662038909234461e-01 0 +2.5919532890717722e-01 6.2146272875721098e-01 0 +6.5699665028382237e-01 2.7790039517221221e-01 1 +7.5560156806183776e-01 4.2355438945085361e-01 0 +7.6370891040198974e-01 4.4544252736443651e-01 0 +5.6410358097839264e-01 2.3116641329929169e-01 0 +2.4208348119379930e-01 5.4781326518953732e-01 0 +3.7158472729381686e-01 7.4203056821850844e-01 0 +3.5634728971807572e-01 7.2558046573920687e-01 0 +5.8913803423650879e-01 2.4055450297133149e-01 1 +2.9546022727107335e-01 6.8303235620345149e-01 0 +3.0911380415071732e-01 7.0186743701251253e-01 0 +7.7358619336613288e-01 5.4425983371500364e-01 0 +5.0400759361641911e-01 7.5878073131584944e-01 0 +6.1098565674767691e-01 2.4085196018623811e-01 0 +7.7264489421393712e-01 4.7787723076403815e-01 0 +4.1309694153296211e-01 2.4029059151937993e-01 0 +4.5652987899236164e-01 2.2452795204678599e-01 1 +2.4271285661186773e-01 6.0373052243234737e-01 1 +5.2655106427984266e-01 2.2065567603972680e-01 0 +7.0368593760943743e-01 6.8936851161912960e-01 0 +3.4566504850719620e-01 3.0174757538445346e-01 0 +3.3071958420384601e-01 7.1689635123066753e-01 1 +6.4525553734862384e-01 7.0815613386560261e-01 0 +7.0997536915326176e-01 3.2320879989976320e-01 0 +7.6957269326216848e-01 5.7494727234880960e-01 0 +3.9741756291056174e-01 7.6638836499180341e-01 0 +3.5059946011923204e-01 2.5702242932533115e-01 0 +6.8040837229094830e-01 2.8413888276004468e-01 1 +6.8087241346963145e-01 7.1291726662402954e-01 0 +2.2379837762108623e-01 5.6266389244238146e-01 0 +7.4194965924957201e-01 3.6655001048055064e-01 0 +7.3245461193268857e-01 3.4003444838927915e-01 0 +3.7176311211619512e-01 2.4986963570689857e-01 0 +4.3317765527163782e-01 2.1831295077308155e-01 1 +7.8371730331808920e-01 5.2088251045155631e-01 1 +2.2658787755018375e-01 5.8596799073039907e-01 0 +2.2625233919690269e-01 4.6731252149017355e-01 0 +2.3213376895254662e-01 4.9756032784990523e-01 0 +7.4773768059887236e-01 6.5066693624731808e-01 0 +7.3008060651534590e-01 6.6304181954603758e-01 1 +5.0023509276144296e-01 2.1238365081145821e-01 0 +2.5242423196328229e-01 4.2586587526317532e-01 0 +7.6328290768018092e-01 3.7980509265463352e-01 0 +3.0669936282983862e-01 3.1072862761352821e-01 0 +7.8632638151650203e-01 4.5418001518708057e-01 0 +7.7510994936067990e-01 4.0398874764892473e-01 0 +7.8806978495093316e-01 4.3046817588537961e-01 0 +3.2901360030876198e-01 2.6125317744384474e-01 0 +6.3188024218059746e-01 2.5038386564030435e-01 0 +6.5184968098904650e-01 2.5471583064231113e-01 0 +3.9329977208354233e-01 2.2609741403373745e-01 0 +7.5718026457369747e-01 5.9500527495489719e-01 0 +7.6087358904704772e-01 6.1840183615226541e-01 0 +3.5309374651116554e-01 7.5877387843432598e-01 0 +3.7425013820020064e-01 7.6516876350378937e-01 0 +2.7144991806914104e-01 6.7013770816829876e-01 0 +4.1964701180973035e-01 7.7866334736539278e-01 0 +2.4996490658211779e-01 6.5011797447998187e-01 0 +7.8829822262581062e-01 5.6225377476632898e-01 0 +7.2882634188643203e-01 3.1773301928665892e-01 0 +5.8205984925027643e-01 2.1936044106175462e-01 1 +2.1961478331864684e-01 5.2157774549638602e-01 0 +7.9070234074902190e-01 4.9788065929811998e-01 0 +6.7314054213470309e-01 2.6004954856175594e-01 0 +7.0399099645292218e-01 7.1402018751530316e-01 0 +2.3441147989443931e-01 6.2928030385615275e-01 0 +4.7642506310314875e-01 2.0908793751804597e-01 0 +4.0955614339489427e-01 2.1202628068305060e-01 1 +7.0400508513206550e-01 2.9042666458694960e-01 1 +7.9781327390604029e-01 5.4010210404498071e-01 0 +2.0659211094104230e-01 5.4543650378693698e-01 0 +4.5652307022738747e-01 2.0076064980730826e-01 0 +6.0195979780359554e-01 2.1897552803082729e-01 0 +2.2318052115587736e-01 4.3688507192999926e-01 0 +5.5021042391415409e-01 7.6004969984391724e-01 0 +7.3751584378836099e-01 6.8534753136508286e-01 1 +6.2714303373153246e-01 2.2692189901817847e-01 0 +3.0628624702381230e-01 2.6667081050381619e-01 0 +3.1135883325822555e-01 7.3810790277052241e-01 0 +3.3352661614982487e-01 7.4182328415400045e-01 1 +3.5508347294719306e-01 2.3683055713998974e-01 0 +2.1729992379305824e-01 6.0762230406074103e-01 1 +5.2102967241224385e-01 1.9789428245485297e-01 0 +8.0076933701494490e-01 4.7678543538272100e-01 0 +5.9076299965497880e-01 7.5957519973693377e-01 0 +6.2941407935549210e-01 7.5650436888249517e-01 0 +6.7940684163934739e-01 7.4648755716412840e-01 0 +6.9813712860254173e-01 7.3478967264950024e-01 0 +6.9994168800264656e-01 2.6688264939305661e-01 0 +5.5322302136012769e-01 2.0836745004009413e-01 0 +5.7503409670254502e-01 1.9832337651093859e-01 1 +7.9253550746200152e-01 5.8618252564946716e-01 0 +3.9784910522204819e-01 7.8825930255333509e-01 0 +7.2497645872995753e-01 7.0318318502565835e-01 0 +7.4998496058636799e-01 3.2473244897461800e-01 0 +3.7100147734731975e-01 2.2018500984425574e-01 0 +2.4287552371751381e-01 3.6757385472505072e-01 0 +5.2712217146004980e-01 7.9217026149709679e-01 0 +7.7254657921506731e-01 3.5719075661002836e-01 0 +8.0830208445570584e-01 5.1711808670106651e-01 1 +7.2719730479999145e-01 2.9660666170500022e-01 1 +7.4821746828378011e-01 3.0220787416378092e-01 1 +7.7155010069595276e-01 6.4875160359891604e-01 0 +7.5710564609694431e-01 6.7369974763300233e-01 0 +7.8298378348515207e-01 6.2944252233297038e-01 0 +2.0404941761484807e-01 5.9116560797886464e-01 0 +6.6882476359103604e-01 2.3784081421652517e-01 0 +3.8746826492977482e-01 2.0614777444587706e-01 1 +3.5850878996628016e-01 7.8145589553199846e-01 0 +4.9618995368554053e-01 1.8896422223215317e-01 0 +4.5352530906980904e-01 7.7718596809418738e-01 0 +4.8575702034851947e-01 8.0057478342276578e-01 0 +3.3545472601135518e-01 2.4080085970349074e-01 0 +3.1535780328635615e-01 2.4525666946415026e-01 0 +5.4112877286122862e-01 1.8921555326259343e-01 0 +2.9643988155953005e-01 7.1939146396988529e-01 0 +7.8913456142144345e-01 3.8318729024738712e-01 0 +4.3215766175939163e-01 1.9911070404324047e-01 0 +2.6803190265051191e-01 3.1914741155052179e-01 0 +7.9563436764389062e-01 6.0867212972944018e-01 0 +7.2637398521769114e-01 7.2558255951657102e-01 0 +6.9086656091002374e-01 2.4370130122835459e-01 0 +2.0057222351534698e-01 5.6916114756028491e-01 0 +7.3191643704759179e-01 2.7711703799939352e-01 0 +8.0988658094638122e-01 5.6614032019283644e-01 0 +7.7250500139212785e-01 3.2715660818986519e-01 0 +4.2427961461522840e-01 8.0626215110392607e-01 0 +8.1841007870059101e-01 4.9611358569712677e-01 0 +6.5085109583455325e-01 2.2388666409015245e-01 0 +2.8630255032717356e-01 7.4124289069424754e-01 0 +8.0863503644476575e-01 4.4536498947787895e-01 0 +8.0011788395196326e-01 4.0869947766997650e-01 0 +4.1056436727645779e-01 1.9145171083179477e-01 0 +3.0377816280204889e-01 7.6019524314744369e-01 0 +3.3617914516857844e-01 7.6537820415495805e-01 1 +4.7392921020320949e-01 1.8002215244192085e-01 0 +8.2054409070679479e-01 5.3992745742145443e-01 0 +5.9775043686076113e-01 1.9766790316858907e-01 0 +7.1368658036537824e-01 7.4662408599145147e-01 0 +6.1944530568255152e-01 1.9783728882324209e-01 0 +6.3858224795727070e-01 2.0558502126771080e-01 0 +7.5761808887659243e-01 6.9871823562669000e-01 0 +3.7635930175324311e-01 8.0478944781485273e-01 0 +1.9103016691001559e-01 4.7472102895074031e-01 0 +1.8267609432111520e-01 5.1907439513293419e-01 0 +5.6820649907032228e-01 1.7787964309729218e-01 1 +3.9845399027638573e-01 8.1330865690744492e-01 0 +3.6328301833374005e-01 1.9971107102110561e-01 1 +2.3747608441381954e-01 6.7463748795824052e-01 0 +2.7511072120677887e-01 6.9916721423995665e-01 0 +3.3878256260404227e-01 7.8849700473654993e-01 1 +3.5497815395426829e-01 8.0169561543850898e-01 0 +5.1999210447170763e-01 1.7375045400531286e-01 0 +7.6777393804252658e-01 3.0741905827978172e-01 1 +2.9257686830515317e-01 2.4918336928147305e-01 0 +2.0720010058335611e-01 6.2870566591497590e-01 0 +2.2149441040657714e-01 6.5046695609860894e-01 0 +1.9284777232166891e-01 6.1136694992863616e-01 1 +7.8359130226984397e-01 6.7451452445410054e-01 0 +4.5010983114492886e-01 1.7950340477780252e-01 0 +1.7956014652963881e-01 5.4770434368276477e-01 0 +8.0470200143976078e-01 6.3449629013913245e-01 0 +7.9457933524023261e-01 6.5347048886387527e-01 0 +3.4099467958339985e-01 2.1462640636984992e-01 0 +7.4800000000000000e-01 7.1679999999999999e-01 1 +3.2096102024231965e-01 2.1468764741978499e-01 0 +3.0121497058244079e-01 2.2889667036036510e-01 0 +8.2600413035593301e-01 4.6809432202593670e-01 0 +7.1743789600776431e-01 2.5281425478864256e-01 0 +8.3185653686140404e-01 5.1351142697245411e-01 1 +7.9011630943331945e-01 3.3930479373089578e-01 0 +1.7835789893462570e-01 5.9088497455532729e-01 0 +2.0449781594734084e-01 4.0123321038389786e-01 0 +2.2786738174490256e-01 3.3053268127711827e-01 0 +8.0028662062024181e-01 3.6214768363014416e-01 0 +3.8153100918703226e-01 1.8698109304955696e-01 0 +7.3665327252012158e-01 7.4443328002230935e-01 0 +4.2833402722287295e-01 1.6884280531675908e-01 0 +8.2635740353861753e-01 4.2710190440855472e-01 0 +4.9232065787303392e-01 1.6447167760109743e-01 0 +6.9587970207088112e-01 7.6441307873519315e-01 0 +7.5267698841662578e-01 2.8318052073499755e-01 0 +5.8988263214729919e-01 8.0085050188331297e-01 0 +8.4356750846702044e-01 4.9545664116741928e-01 0 +6.0755314534860660e-01 1.7679354624326807e-01 0 +8.4375627619411997e-01 5.4235899831838663e-01 0 +1.8367463396711303e-01 6.3438760084563239e-01 0 +4.0398826074815003e-01 1.6886814074007991e-01 0 +8.1659824565025896e-01 5.9059494465278739e-01 0 +3.4220000000000000e-01 1.9409999999999999e-01 1 +3.0485677437883624e-01 7.8545827374968180e-01 0 +2.7874868471392272e-01 2.7809811128774420e-01 0 +6.7241462235645399e-01 2.1318266680729178e-01 0 +5.4149843454496638e-01 1.6539565902824693e-01 0 +5.8429734800868727e-01 1.6386982722197302e-01 0 +5.6075290196931427e-01 1.5556149250994891e-01 1 +3.7738209979836274e-01 8.2646956309177400e-01 0 +3.9690701863609856e-01 8.3441852161919272e-01 0 +3.2189726035041244e-01 1.9466255375473715e-01 0 +8.1726285284994427e-01 3.8112764423515011e-01 0 +8.3035189270760745e-01 4.0280263810485861e-01 0 +7.7194343505933460e-01 7.1717775403332440e-01 0 +1.6573497242443525e-01 5.7062872461352177e-01 0 +8.4028262968007106e-01 4.4741110800987821e-01 0 +2.0033014515347258e-01 3.6188468006802271e-01 0 +5.1582196040953099e-01 1.4969932248866161e-01 0 +3.0204506880851018e-01 2.0653385989873224e-01 0 +7.4041836115053583e-01 2.5635619107085766e-01 0 +4.2528108018125571e-01 8.3520131660693742e-01 0 +6.5225454153706630e-01 1.9183663666459161e-01 0 +6.3341129710006516e-01 1.7933291109199084e-01 0 +2.5278587587184054e-01 7.3059324085118094e-01 0 +2.8051518497257083e-01 7.6492741229823424e-01 0 +1.6771885166739817e-01 6.1521523743015849e-01 1 +7.8656375785569765e-01 6.9831622380382175e-01 0 +4.6983970144927101e-01 1.5520400587429428e-01 0 +4.4711350719186782e-01 1.5177133643413823e-01 0 +7.6087632926860760e-01 2.5644722397837477e-01 0 +1.5152997013697841e-01 5.5259701343133694e-01 0 +8.5330797499896160e-01 4.7086988014028724e-01 0 +1.8637325530315282e-01 6.5809514100347033e-01 0 +7.8375553766875583e-01 2.9342077408883693e-01 0 +7.8754444741804397e-01 3.1268727722184630e-01 1 +6.6384590135963495e-01 7.7736799886326302e-01 0 +6.3069947181927599e-01 8.0146505412428926e-01 0 +8.5604390907069827e-01 5.0980785451041222e-01 1 +3.6130705572880512e-01 1.7852619963008248e-01 0 +5.5360924747978901e-01 8.2068434645601895e-01 0 +4.6434865480464205e-01 8.3486561925790892e-01 0 +5.1111042110580773e-01 8.3117116101543165e-01 0 +7.5639962745386236e-01 7.4335383946541300e-01 0 +7.2065217704546158e-01 7.6768248647147319e-01 0 +7.4489554521812029e-01 7.6493804874822624e-01 0 +7.0996310428832132e-01 2.3098007122209382e-01 0 +7.3596788707575289e-01 2.3333956390669300e-01 0 +2.0795461484753339e-01 6.7335514852789791e-01 0 +1.5419320198978170e-01 5.9574052365950514e-01 0 +8.6383290095893195e-01 5.3014331107681567e-01 0 +2.4005454442962157e-01 2.8242547978473287e-01 0 +8.6324224652749570e-01 5.5260814592489094e-01 0 +8.3597149559911099e-01 5.6594089123721103e-01 0 +3.8348192572147327e-01 1.5903039056409429e-01 0 +1.8157312201263903e-01 4.3514692236998087e-01 0 +3.9929558529079251e-01 1.4655426383265224e-01 0 +4.2068443501341557e-01 1.4749338723094513e-01 0 +1.5099043882037549e-01 4.8860477826342852e-01 0 +1.5169324532790746e-01 5.2685407642930149e-01 0 +4.8920263567653233e-01 1.3791721819576197e-01 0 +8.0654738218945587e-01 6.7330660839822920e-01 0 +3.2357771054248441e-01 8.0117746002911960e-01 0 +3.4151613441418982e-01 8.1277159885019135e-01 1 +8.1571348453897885e-01 3.4125012770749841e-01 0 +5.0957731883419677e-01 1.2716731892648331e-01 0 +7.8313398285880065e-01 2.7228581999254725e-01 0 +8.1761073281579022e-01 6.5279227857176814e-01 0 +8.1953630898586804e-01 6.1495076701988605e-01 0 +3.0065510111095300e-01 8.0950998259976159e-01 0 +8.5504463869662450e-01 4.2843752395138268e-01 0 +5.3308461220418046e-01 1.3245509627254651e-01 0 +5.5352966844798446e-01 1.3393311479219153e-01 1 +3.4405393123902833e-01 8.3530767697228669e-01 1 +3.3316735227880634e-01 1.7222543807029722e-01 0 +8.6470728577937350e-01 4.9019400518429240e-01 0 +5.8845058123797300e-01 1.4020703108284951e-01 0 +8.3172046833343005e-01 3.6147385528002723e-01 0 +8.2653518645435298e-01 3.2307708286896580e-01 1 +8.0627078533686614e-01 3.1767725731116098e-01 1 +3.6343804359487053e-01 8.4378228755875340e-01 0 +3.8484894307858447e-01 8.5253923298866630e-01 0 +8.5046667229803796e-01 3.7731779739743909e-01 0 +8.5601069091954229e-01 4.0285686799386089e-01 0 +3.0137797472491912e-01 1.8675813615968268e-01 0 +4.0551773983656425e-01 8.5536326407894459e-01 0 +2.1811014126134409e-01 7.0812171194667906e-01 0 +8.0026923830653962e-01 7.1958478515995883e-01 0 +2.7989206017477941e-01 1.9879512136723415e-01 0 +2.7763601139724037e-01 2.2822946425153648e-01 0 +7.8425202958629514e-01 2.5203027826467694e-01 0 +7.7988170939699797e-01 7.4041663995139007e-01 0 +4.6989549247491263e-01 1.2513683055441499e-01 0 +1.3408648735130738e-01 5.6994677092447710e-01 0 +2.8157740046611685e-01 7.9049349457716989e-01 0 +1.4369418078111279e-01 6.1889441816266511e-01 1 +1.6341486690098331e-01 6.4734104097040157e-01 0 +8.8112267573819403e-01 5.4359526903715838e-01 0 +8.7829999999999997e-01 5.0639999999999996e-01 1 +3.6541476499679854e-01 1.4606611194191707e-01 0 +3.4794124956354994e-01 1.5634357743088120e-01 0 +8.1245099570164336e-01 2.8805007157771678e-01 0 +4.4559058683014996e-01 1.2944111152845728e-01 0 +8.6903710613267116e-01 4.4824336042246504e-01 0 +3.1625775819936341e-01 1.5804447955160975e-01 0 +8.5311318166393801e-01 6.0205052989849062e-01 0 +8.7900179056890471e-01 5.6652749048258799e-01 0 +1.2875930302774857e-01 5.9574748158254931e-01 0 +8.7608403651976374e-01 4.6987453262528406e-01 0 +8.4852702383430889e-01 3.2893721586075197e-01 1 +6.0854693690211970e-01 1.5001553693214081e-01 0 +7.9916657875950026e-01 7.4320074527106961e-01 0 +8.0367133461019236e-01 2.5624985045956850e-01 0 +1.6467378487525669e-01 6.7296989661147377e-01 0 +5.6855137133024569e-01 1.2045401643330832e-01 0 +3.7096816620265000e-01 8.7157095145994568e-01 0 +5.2559244325558130e-01 1.1186026034305857e-01 0 +5.4620123072872218e-01 1.1198972633531562e-01 1 +3.9118670271164352e-01 8.7365109121844098e-01 0 +1.3576833307116193e-01 6.3973607004096944e-01 0 +7.6872483827597393e-01 7.6965808970116212e-01 0 +4.9011639173226373e-01 1.1371716487340033e-01 0 +7.3480624457487742e-01 1.9853456693408419e-01 0 +7.5938893385627304e-01 2.3094186752061113e-01 0 +2.3589582032487086e-01 7.6579811803315134e-01 0 +5.9493918503702736e-01 1.2014840064938341e-01 0 +6.1631533356288093e-01 1.2633661283520103e-01 0 +3.0327777830439318e-01 8.3425533975655530e-01 0 +2.3560312982309151e-01 2.4228426497673797e-01 0 +3.2732499253211017e-01 8.4896948924506477e-01 0 +3.4677463639803158e-01 8.5946801298121311e-01 1 +8.3801955138804884e-01 3.0006092202417017e-01 0 +6.6364598531647645e-01 1.6306833381171226e-01 0 +6.9363013244594740e-01 1.8517183642611509e-01 0 +2.0147768202093777e-01 3.0044912756283443e-01 0 +4.9352085347295460e-01 8.7999880204388781e-01 0 +6.9503511105273696e-01 8.0574332579473185e-01 0 +7.3484585287233983e-01 7.9990419485207842e-01 0 +8.7587476256908869e-01 3.8378681573727419e-01 0 +8.8062587826377359e-01 4.1393745440765500e-01 0 +4.1791590394351857e-01 8.8552948924055008e-01 0 +4.5040845393618245e-01 8.7093158442358898e-01 0 +9.0030551138591741e-01 5.3483860541487638e-01 0 +8.9128917469547930e-01 4.8967451421576014e-01 0 +8.9984372293494841e-01 5.0997133986193577e-01 0 +4.6211757210829241e-01 1.0402019328795285e-01 0 +2.9978578197278005e-01 1.6344679529460002e-01 0 +2.8106958744551080e-01 1.7236213017610122e-01 0 +4.1850939168253504e-01 1.2348492547498498e-01 0 +1.1422257346704098e-01 5.0860324161418602e-01 0 +1.2531183353027078e-01 5.4393755835058655e-01 0 +1.1914359114392579e-01 6.2265413902595390e-01 1 +8.9416400273475538e-01 4.5772321928649251e-01 0 +9.0620475695934732e-01 4.7576516028541282e-01 0 +3.8200849088396749e-01 1.3363388609350002e-01 0 +8.6601388514418254e-01 3.5777831929030551e-01 0 +1.3454213227203413e-01 6.6444601914294166e-01 0 +1.0702599754565194e-01 6.0449101738355038e-01 0 +8.6372874118039789e-01 3.1547467300215826e-01 0 +8.7196672640121664e-01 3.3518315938774373e-01 1 +1.0860085475325203e-01 5.7623190842607086e-01 0 +8.1808385767297265e-01 6.9995679888019735e-01 0 +8.6261347762718010e-01 6.4206418820249156e-01 0 +1.1426384815706533e-01 6.4707642491247852e-01 0 +5.3595341846248057e-01 8.7774265452348810e-01 0 +5.5160780568407675e-01 1.0000000000000000e+00 0 +2.7724400776676200e-01 8.1972159115973964e-01 0 +2.0128180500940585e-01 2.5855099733641618e-01 0 +5.6264615227272485e-01 9.3239156736861437e-02 0 +7.8871478751994328e-01 2.2912574932439775e-01 0 +3.4975501271329240e-01 1.2719472810265134e-01 0 +3.3054740974956359e-01 1.4735043726455302e-01 0 +8.4424399024410923e-01 6.7628866377854102e-01 0 +5.1706810379295998e-01 9.1912596573162017e-02 0 +5.3855258935553341e-01 8.9087558745344422e-02 1 +7.9306553416218906e-01 7.6664794489075716e-01 0 +4.3542445737576052e-01 1.0083653707478649e-01 0 +8.9738936002326175e-01 5.5625970909062983e-01 0 +2.4389853804240841e-01 1.9845181928870648e-01 0 +4.9572361435199219e-01 8.5443388659493941e-02 0 +8.1403993649580253e-01 2.3559614648871408e-01 0 +8.2652785976131460e-01 2.6521675831743263e-01 0 +3.0148194640407838e-01 8.6500039500704551e-01 0 +5.9290258608335478e-01 9.8046938509370268e-02 0 +9.0560286181083705e-01 4.1057861103751853e-01 0 +8.9912227509001763e-01 4.3374139400557188e-01 0 +4.5022658487349387e-01 8.4458401553633933e-02 0 +4.7154203703227793e-01 7.9449607121988133e-02 0 +1.1790427274484466e-01 4.5361476011222063e-01 0 +3.7536137974912037e-01 8.9280926381725034e-01 0 +3.9880752872458514e-01 9.0015722194282488e-01 0 +3.0199717301642071e-01 1.4540278875758136e-01 0 +8.9213777733105071e-01 6.0711659632039705e-01 0 +8.1572967770668903e-01 7.5952829688396684e-01 0 +8.1905038198761493e-01 7.3371473044133040e-01 0 +8.6031541160267466e-01 2.9082301465898835e-01 0 +1.7837639574684666e-01 7.0838680089686190e-01 0 +1.9141012344308989e-01 7.4713159421148168e-01 0 +8.7876132224914572e-01 3.0216849270149698e-01 0 +9.5716679339767241e-02 6.2624177787187019e-01 1 +6.1711952680952675e-01 1.0183263985692638e-01 0 +9.0261717985429046e-01 3.8443002824961514e-01 0 +8.9037315764573077e-01 3.6450409147586665e-01 0 +8.3701847711367530e-02 5.9866923388464222e-01 0 +3.2305288519314151e-01 8.7669789169305046e-01 0 +3.4956330226761162e-01 8.8423185194506537e-01 1 +2.4173749891672017e-01 8.0467608934747692e-01 0 +1.3970931580711510e-01 6.9966956655285362e-01 0 +6.5000669921892895e-01 1.2058104940196296e-01 0 +5.5215041908068418e-01 6.9067347511278673e-02 0 +5.7729950140002473e-01 7.5399305712904055e-02 0 +2.7139847539202294e-01 1.4489496326752532e-01 0 +3.5234552021036247e-01 9.0893843219485282e-01 1 +5.1011485719483884e-01 6.7777265017803678e-02 0 +6.9708604286964992e-01 1.3264390896016431e-01 0 +0.0000000000000000e+00 3.5778334672632794e-01 0 +1.6458491767476821e-01 3.3743034945621836e-01 0 +1.5841986345051473e-01 3.9002735683388490e-01 0 +2.9940843757550417e-01 8.9147318803481623e-01 0 +9.2124670243911699e-01 4.9400512757062631e-01 0 +9.1949534340638317e-01 5.2385405319804834e-01 0 +1.0000000000000000e+00 5.0933439469470132e-01 0 +9.4240951246179011e-02 6.4717999403656057e-01 0 +0.0000000000000000e+00 4.3676731026249721e-01 0 +7.8732988370573992e-02 4.8817317628342149e-01 0 +6.9201395830853343e-02 4.0372507343911812e-01 0 +0.0000000000000000e+00 4.9946395362713031e-01 0 +3.9767014055390626e-02 4.9447863547164850e-01 0 +7.7981281957459958e-02 5.2996942825750037e-01 0 +7.7198707639022965e-02 5.7266827442948698e-01 0 +8.8816781903883624e-01 3.1932520723510704e-01 0 +4.7381480245937696e-01 9.1648263199236668e-01 0 +4.8642399392203428e-01 1.0000000000000000e+00 0 +5.1459647993413948e-01 9.1793079549877821e-01 0 +2.0492474519021686e-01 2.0702173704958937e-01 0 +2.8083609107870461e-01 8.7986443270179515e-01 0 +2.7955071699620682e-01 8.4820641303642530e-01 0 +6.4924307940920345e-01 1.0000000000000000e+00 0 +5.7438681853664564e-01 9.2523090517395457e-01 0 +6.6149903133979671e-01 8.4195419552465767e-01 0 +6.0505220131361315e-01 8.4800836080389308e-01 0 +4.0233117426024456e-01 9.2129660285068060e-01 0 +4.2729102986919898e-01 9.1311246248594413e-01 0 +9.1739800880263600e-01 5.4990164013355014e-01 0 +9.5932038703147926e-01 5.2529324718924697e-01 0 +1.0000000000000000e+00 5.5294503512641546e-01 0 +4.5553354487125314e-01 6.1585302473115028e-02 0 +4.3247624107355487e-01 6.9089846988014902e-02 0 +6.1072809159321850e-01 7.4267408213317371e-02 0 +9.1861868037989747e-01 3.4761443786249868e-01 1 +8.9508360106043716e-01 3.4134307943066627e-01 1 +3.6583759334515942e-01 9.6830666637410634e-02 0 +3.5826968658652147e-01 0.0000000000000000e+00 0 +4.0450125567990386e-01 8.8391502795948165e-02 0 +1.0816699064745515e-01 6.6915657063750078e-01 0 +7.2573292496094011e-02 6.2978599720220851e-01 1 +6.2299394136041193e-02 6.0161361395410107e-01 0 +5.3069811443294668e-01 6.5569068044646878e-02 1 +7.7713003428338712e-01 1.9150854428394620e-01 0 +7.3022867311732487e-01 1.5858445420821887e-01 0 +8.8558505538442112e-01 2.7788813093803522e-01 0 +8.9830535557463553e-01 2.9813687113343684e-01 0 +1.9960068986545573e-01 7.8630243083836304e-01 0 +9.2892013096860848e-01 3.7115561847684025e-01 0 +9.2583824997719577e-01 3.9588179621921471e-01 0 +4.8788074233124146e-01 6.2212545213218291e-02 0 +8.6242512260168103e-01 7.3318870375536327e-01 0 +9.2724504470080471e-01 5.8942801829093583e-01 0 +9.5999821278369346e-01 5.6566438779187422e-01 0 +1.0000000000000000e+00 5.9454048031056228e-01 0 +8.2760758795863670e-02 6.6653311672492688e-01 0 +2.3773062353775531e-01 8.4488155782632812e-01 0 +7.8232118115716942e-01 8.0170938607077946e-01 0 +3.5496704838830168e-01 9.3221805932618240e-01 1 +3.7482448643345295e-01 9.2372221343709504e-01 0 +3.9001642777574841e-01 9.3977778450826999e-01 0 +8.9836276037329310e-01 6.6476985774623787e-01 0 +8.8564596500198134e-01 7.0192908011400645e-01 0 +9.2367072766633684e-01 6.3200435049870762e-01 0 +1.0000000000000000e+00 6.4486702461803080e-01 0 +9.6231791781209419e-01 6.1100431221885187e-01 0 +9.4004871626269859e-01 6.8516248510016897e-01 0 +3.1107549470770696e-01 9.0838295485673137e-01 0 +3.3220912705800126e-01 9.2296035886644467e-01 0 +1.6048132460635575e-01 7.7607907009434440e-01 0 +2.1432991633434503e-01 1.6922658658441017e-01 0 +5.0880000000000003e-01 0.0000000000000000e+00 1 +5.0218914442035412e-01 4.5862874843634195e-02 0 +5.3002689752551913e-01 0.0000000000000000e+00 0 +5.2365275882778062e-01 4.4473306467164679e-02 1 +5.1623426623611701e-01 2.2260268581141912e-02 1 +9.3351050938638314e-01 4.2219777088966554e-01 0 +1.0000000000000000e+00 4.6047158662342658e-01 0 +9.2205266849587031e-01 4.5102136468126341e-01 0 +9.5395758387733010e-01 4.8156874654158877e-01 0 +8.4926216994390147e-01 2.3526501754188886e-01 0 +5.5318211392556993e-01 0.0000000000000000e+00 0 +5.4088959100339173e-01 3.1076302539839958e-02 0 +0.0000000000000000e+00 5.4255009915676167e-01 0 +5.1247679856504483e-02 5.8267810165407041e-01 0 +4.0791790826765879e-02 5.3280287768360868e-01 0 +5.7988511849434932e-01 0.0000000000000000e+00 0 +5.6377446580538715e-01 2.5691606722431690e-02 0 +5.9457350150267607e-01 5.5667552036126750e-02 0 +5.6252818837202656e-01 5.0142349318196971e-02 0 +5.9944298307368238e-01 2.9958155332063578e-02 0 +4.8477505835476359e-01 0.0000000000000000e+00 0 +4.9161164629697579e-01 2.6459609999280050e-02 0 +3.2494479993602193e-01 1.0178516362856076e-01 0 +3.4015232143745083e-01 5.9519767690132175e-02 0 +6.5142445343301672e-02 6.5073693513877617e-01 0 +4.4483274295741909e-01 0.0000000000000000e+00 0 +4.1749221523023444e-01 0.0000000000000000e+00 0 +4.2945473789714939e-01 4.0696612264224871e-02 0 +4.6459776667541325e-01 0.0000000000000000e+00 0 +4.5666355171620532e-01 3.9406303018064436e-02 0 +4.7092243057114902e-01 2.0268711737726729e-02 0 +4.4118064117121886e-01 1.9254331532709768e-02 0 +1.2923374281293148e-01 7.4789493072641877e-01 0 +9.4101831810335534e-01 3.5358323682001247e-01 1 +2.8591000499074359e-01 1.1154404661887618e-01 0 +2.9347247347529631e-01 6.5986547875916179e-02 0 +2.7809405278866273e-01 0.0000000000000000e+00 0 +4.0528144727003074e-01 9.5361663665216034e-01 0 +4.3456440075226543e-01 1.0000000000000000e+00 0 +4.5938253583281058e-01 9.6015031551340702e-01 0 +9.5315125311828097e-01 3.9865487414119899e-01 0 +9.7090615873858288e-01 4.1764450738056919e-01 0 +1.0000000000000000e+00 4.1660404620881047e-01 0 +6.1804685891570743e-01 0.0000000000000000e+00 0 +6.2818950762219139e-01 5.1995800296356476e-02 0 +6.5800792266353458e-01 0.0000000000000000e+00 0 +8.5254384525298443e-01 7.7134725444522267e-01 0 +1.0169559697023724e-01 7.1706107808729513e-01 0 +0.0000000000000000e+00 5.8502455565770550e-01 0 +2.7630968906476087e-02 5.8496265668884528e-01 0 +2.9628186347923269e-01 9.2378408847996207e-01 0 +9.3317873463955359e-01 3.2458245148829923e-01 0 +9.1813402192793303e-01 3.0447812160833010e-01 0 +4.0398857173554503e-01 9.7761344883975732e-01 0 +3.8238497975767333e-01 1.0000000000000000e+00 0 +4.0384995962805070e-01 1.0000000000000000e+00 0 +7.2046643040989766e-01 8.5541830592666601e-01 0 +6.8231407207420303e-01 9.2592565228247936e-01 0 +7.3240260787341238e-01 1.0000000000000000e+00 0 +0.0000000000000000e+00 6.1211391236730950e-01 0 +2.2931317325622581e-02 6.0693737449575502e-01 0 +4.8643754286117766e-02 6.3345060914784745e-01 1 +4.3821076032147410e-02 6.0852323688237697e-01 0 +7.1356374851631307e-02 6.9427229511179533e-01 0 +2.8463719548600203e-01 9.0600546858411246e-01 0 +3.5751928643234587e-01 9.5488237799181253e-01 1 +3.8384143352650946e-01 9.6011497156226155e-01 0 +3.8454732204399555e-01 9.7945317261793197e-01 0 +3.6259999999999998e-01 1.0000000000000000e+00 1 +3.6027978854367271e-01 9.7939611787366010e-01 1 +3.1199297083647992e-01 9.3898507931398933e-01 0 +9.5747997172105204e-01 3.7782846559541300e-01 0 +9.7542919383153492e-01 3.9304149602135285e-01 0 +1.0000000000000000e+00 3.6930000000000002e-01 1 +1.0000000000000000e+00 3.9094282162612293e-01 0 +6.6003388900015503e-01 8.2766429956895454e-02 0 +6.9898718142273064e-01 0.0000000000000000e+00 0 +6.6143873315308688e-01 3.9983028020833873e-02 0 +2.5795243019710010e-02 6.3694967070901187e-01 1 +0.0000000000000000e+00 6.4090000000000003e-01 1 +7.0161384778211311e-01 4.0484809933273157e-02 0 +6.9939623084409375e-01 9.2309891649410822e-02 0 +7.5067949496733322e-01 0.0000000000000000e+00 0 +0.0000000000000000e+00 2.6602902113923171e-01 0 +8.1108572036638252e-02 3.1353918155346061e-01 0 +1.4478042671016064e-01 2.8121922723139980e-01 0 +1.6458106398974284e-01 2.2520269073139232e-01 0 +2.4206584340698015e-01 1.2420462484232499e-01 0 +2.4452287244024029e-01 8.7567668218899844e-02 0 +9.6178222775145783e-01 3.5911616569107696e-01 1 +1.0000000000000000e+00 3.4478132982998488e-01 0 +9.8167509243733164e-01 3.6441698847512849e-01 1 +8.8930455811341780e-01 2.4090060629981341e-01 0 +1.9696805823048075e-02 6.5428300364385483e-01 0 +0.0000000000000000e+00 6.6222104698047413e-01 0 +4.2708486652514276e-02 6.5440933106891241e-01 0 +5.7297228768029181e-02 6.7431634354610426e-01 0 +3.4340880212872243e-01 1.0000000000000000e+00 0 +3.4310675088223042e-01 9.6856117880022596e-01 0 +3.3439954253252541e-01 9.4674038132147742e-01 0 +8.1881029270452366e-01 7.9717664708251967e-01 0 +1.9381300563135839e-01 8.3255369522550227e-01 0 +2.0088516317558869e-01 1.2742230195358847e-01 0 +8.1496937750668019e-01 2.0117843612671510e-01 0 +8.9548791300124098e-01 7.5944326282809405e-01 0 +9.1722439197107630e-01 7.2615252943474551e-01 0 +1.0000000000000000e+00 7.1831959309766891e-01 0 +8.7627096459584486e-01 2.0465908828996443e-01 0 +1.0000000000000000e+00 2.9685020819187408e-01 0 +1.0000000000000000e+00 3.2120633969585338e-01 0 +9.5324805560714554e-01 3.0841436412157180e-01 0 +9.7918754046461109e-01 3.3381883801609813e-01 0 +9.5622556919326152e-01 3.3607007319697041e-01 0 +9.7977974125401113e-01 3.0945405181941466e-01 0 +2.4180402512554025e-01 8.8409883087669860e-01 0 +2.0310615840196003e-01 8.7225625252284633e-01 0 +3.0570737122278108e-01 1.0000000000000000e+00 0 +3.2472313492593369e-01 1.0000000000000000e+00 0 +3.2843415196066816e-01 9.8146543971744682e-01 0 +1.2113471507535670e-01 7.9091081687172649e-01 0 +1.5638154903393542e-01 8.2238579501732678e-01 0 +0.0000000000000000e+00 6.8369809962354300e-01 0 +2.1558290212844847e-02 6.7780249057834074e-01 0 +0.0000000000000000e+00 7.0570512420135045e-01 0 +4.5958824376018732e-02 6.9536624167450500e-01 0 +2.0700193974094280e-02 6.9816687441474279e-01 0 +2.9701536424389974e-01 9.5687600303615861e-01 0 +3.0458141631949492e-01 9.7958558870971602e-01 0 +2.8427371750476277e-01 1.0000000000000000e+00 0 +9.1431922316732461e-01 2.7640927256378861e-01 0 +9.3887058190958450e-01 2.8743193945957435e-01 0 +9.6826168587663286e-01 2.8906792824254129e-01 0 +1.0000000000000000e+00 2.6340305373497758e-01 0 +0.0000000000000000e+00 7.3807135975623606e-01 0 +3.7987966530542039e-02 7.1650898659474316e-01 0 +6.4331715426854993e-02 7.2072118417480435e-01 0 +2.4444413609879304e-01 9.2370079313816866e-01 0 +2.8040965334432888e-01 9.3937877206268272e-01 0 +2.8159144767212158e-01 9.7428196966639380e-01 0 +2.4958263267407479e-01 1.0000000000000000e+00 0 +1.6880456042040168e-01 1.7783300803089472e-01 0 +0.0000000000000000e+00 1.8350750524831411e-01 0 +7.4008444214680419e-02 2.2734349985667165e-01 0 +0.0000000000000000e+00 7.7905198215441329e-01 0 +3.6613670930483934e-02 7.5401233177629756e-01 0 +8.8062480240534410e-02 7.5508659482019691e-01 0 +8.2684199246206591e-01 0.0000000000000000e+00 0 +8.0950390592621024e-01 1.4535513459780072e-01 0 +7.4544868929248354e-01 5.8707649725076805e-02 0 +7.4452797882805166e-01 1.1315124530187874e-01 0 +9.2496344201218195e-01 7.8782367952750587e-01 0 +8.8465148625797929e-01 8.0025240470380943e-01 0 +1.0000000000000000e+00 8.0857446528317123e-01 0 +8.1760396373550226e-01 8.5506247396417911e-01 0 +7.6171073625336250e-01 9.1811778831971225e-01 0 +8.2278525050963547e-01 1.0000000000000000e+00 0 +1.9016302733276347e-01 0.0000000000000000e+00 0 +2.0485449659311300e-01 8.9228242655899612e-02 0 +9.2654800490617673e-01 2.2422434067294800e-01 0 +9.6095965297194430e-01 2.5305588675125645e-01 0 +1.0000000000000000e+00 2.2240032897932421e-01 0 +2.0466379888710079e-01 9.2345914070483570e-01 0 +1.6096480345753517e-01 1.0000000000000000e+00 0 +2.3762459669112462e-01 9.6127404358936952e-01 0 +2.0791961168311257e-01 1.0000000000000000e+00 0 +1.9166320669450854e-01 9.6235978479544870e-01 0 +1.0000000000000000e+00 1.7004010027149188e-01 0 +9.6526347022870995e-01 2.0488489528538303e-01 0 +9.1113838074521780e-01 1.8890033054434180e-01 0 +4.0931799527866249e-02 7.9443178013613247e-01 0 +0.0000000000000000e+00 8.2975977228421294e-01 0 +8.0863268268070318e-02 7.9769067289505635e-01 0 +0.0000000000000000e+00 8.9677182155077462e-01 0 +0.0000000000000000e+00 1.0000000000000000e+00 0 +1.5105849859380985e-01 9.3231747200045567e-01 0 +8.3851329637988692e-02 1.0000000000000000e+00 0 +5.4949734685544281e-02 8.3717505806519088e-01 0 +1.5179767281784490e-01 8.7503534588415444e-01 0 +1.0757176046145867e-01 8.3819022640254703e-01 0 +6.6132267777105458e-02 9.3060931047244622e-01 0 +9.1583939641749523e-01 0.0000000000000000e+00 0 +8.4706767242577707e-01 7.7661690363815927e-02 0 +8.7148873264526527e-01 1.4604056613630090e-01 0 +1.0000000000000000e+00 0.0000000000000000e+00 0 +9.4576816544865971e-01 1.6610691499072941e-01 0 +1.0000000000000000e+00 1.0454792440439489e-01 0 +9.3425995382259064e-01 6.9446253310691389e-02 0 +0.0000000000000000e+00 0.0000000000000000e+00 0 +0.0000000000000000e+00 8.2804022205800257e-02 0 +1.0840336431465319e-01 0.0000000000000000e+00 0 +8.5893381375001993e-02 1.4798878705028751e-01 0 +1.6505638641535561e-01 6.4900284236413197e-02 0 +1.5266487326994330e-01 1.2248615098641301e-01 0 +6.9627657804151102e-02 6.3074209165735862e-02 0 +9.1560811624118632e-01 1.0000000000000000e+00 0 +1.0000000000000000e+00 8.8999419252453993e-01 0 +1.0000000000000000e+00 1.0000000000000000e+00 0 +8.5088310749027818e-01 9.2107095425066210e-01 0 +9.3348633086433630e-01 8.3496713453107063e-01 0 +8.7675595373424486e-01 8.4611396555696439e-01 0 +9.3827717064155736e-01 9.3047803651437289e-01 0 +# + +Simplex +165 172 173 +165 173 134 +399 381 470 +381 447 470 +1 0 2 +3 2 0 +1 7 0 +0 7 14 +0 14 6 +0 6 3 +2 4 1 +5 2 3 +7 1 8 +4 8 1 +10 3 6 +9 4 2 +2 5 9 +3 11 5 +3 10 11 +15 10 6 +8 24 7 +12 8 4 +13 12 4 +4 9 13 +18 9 5 +19 5 11 +18 5 19 +17 10 15 +6 14 31 +6 31 15 +28 7 24 +7 28 14 +20 24 8 +8 12 20 +9 21 13 +9 18 21 +16 11 10 +17 16 10 +11 26 19 +11 16 26 +22 12 13 +32 12 22 +23 20 12 +12 32 23 +22 13 27 +21 27 13 +25 17 15 +28 50 14 +31 14 50 +29 17 25 +34 26 16 +37 15 31 +15 37 25 +35 21 18 +24 20 54 +33 19 26 +16 30 34 +16 17 30 +29 30 17 +36 35 18 +19 36 18 +19 33 36 +46 54 20 +20 23 46 +40 27 21 +21 35 40 +50 49 31 +27 39 22 +32 22 38 +39 38 22 +47 46 23 +23 32 47 +41 36 33 +25 42 29 +54 56 24 +24 56 28 +43 25 37 +43 42 25 +44 34 30 +26 34 48 +26 48 33 +53 39 27 +27 40 53 +68 28 56 +50 28 68 +45 29 42 +30 29 45 +30 45 44 +52 31 49 +31 52 37 +40 35 62 +55 47 32 +38 55 32 +33 70 41 +33 48 70 +54 46 59 +34 51 48 +34 44 51 +92 62 35 +35 36 92 +36 41 92 +37 52 60 +37 60 43 +57 55 38 +38 39 57 +58 57 39 +39 53 58 +91 53 40 +91 40 62 +66 42 43 +63 44 45 +85 50 68 +93 92 41 +41 70 93 +42 66 73 +42 73 45 +61 43 60 +43 61 66 +44 63 64 +44 64 51 +45 73 65 +45 65 63 +47 55 69 +67 59 46 +46 47 67 +69 67 47 +71 70 48 +48 51 71 +76 52 49 +49 86 76 +86 50 85 +49 50 86 +72 52 76 +75 71 51 +51 64 75 +56 54 97 +89 52 72 +60 52 89 +74 69 55 +53 81 58 +53 91 81 +82 97 54 +59 82 54 +77 74 55 +55 57 77 +97 104 56 +56 104 68 +57 78 77 +57 58 78 +58 81 78 +83 82 59 +59 67 83 +79 66 61 +100 63 65 +90 60 89 +60 90 61 +61 90 79 +62 115 91 +62 92 115 +87 75 64 +63 101 64 +63 100 101 +64 101 87 +88 67 69 +65 73 80 +65 80 100 +66 79 84 +66 84 73 +98 83 67 +67 88 98 +68 103 85 +68 104 103 +95 72 76 +94 88 69 +69 74 94 +142 71 75 +112 76 86 +99 89 72 +93 70 124 +70 71 124 +71 142 124 +117 72 95 +72 117 99 +96 73 84 +96 80 73 +74 102 94 +74 77 102 +75 111 142 +75 87 111 +108 78 81 +112 126 76 +76 126 95 +105 79 90 +107 102 77 +78 107 77 +78 108 107 +119 97 82 +110 79 105 +110 84 79 +90 106 105 +86 127 112 +80 109 100 +80 96 109 +123 108 81 +81 91 123 +82 118 119 +82 83 118 +120 118 83 +83 98 120 +116 98 88 +114 111 87 +84 110 113 +84 113 96 +85 103 128 +85 128 86 +127 86 128 +101 121 87 +114 87 121 +150 103 104 +125 116 88 +88 94 125 +122 89 99 +106 90 122 +89 122 90 +151 123 91 +151 91 115 +152 115 92 +152 93 124 +92 93 152 +130 125 94 +94 102 130 +133 95 126 +95 133 117 +143 96 113 +96 143 109 +104 97 167 +97 119 167 +134 120 98 +98 116 134 +102 107 147 +117 129 99 +99 129 122 +131 100 109 +131 121 101 +100 131 101 +137 112 127 +146 130 102 +147 146 102 +158 127 128 +108 123 136 +128 103 135 +103 150 135 +168 104 167 +104 168 150 +132 110 105 +105 106 139 +105 139 132 +148 106 122 +106 148 139 +140 147 107 +107 108 140 +136 140 108 +141 142 111 +143 144 109 +109 144 131 +138 110 132 +110 138 113 +111 114 149 +111 149 141 +165 134 116 +156 112 137 +126 112 156 +113 138 162 +113 162 143 +154 114 121 +114 154 149 +115 182 151 +115 152 182 +118 120 155 +153 165 116 +116 125 153 +145 117 133 +145 129 117 +157 118 155 +118 157 119 +119 157 167 +173 155 120 +120 134 173 +121 131 160 +121 160 154 +159 122 129 +122 159 148 +166 136 123 +123 151 166 +124 192 152 +124 142 192 +125 130 179 +125 179 153 +161 126 156 +126 161 133 +187 127 158 +127 187 137 +164 128 135 +128 164 158 +207 135 150 +129 145 181 +129 181 159 +139 169 132 +171 179 130 +130 146 171 +147 140 170 +174 131 144 +131 174 160 +175 132 169 +132 175 138 +176 136 166 +163 133 161 +133 163 145 +135 207 193 +135 193 164 +177 140 136 +136 176 177 +187 183 137 +137 183 156 +138 175 195 +138 195 162 +148 185 139 +169 139 185 +198 170 140 +140 177 198 +178 141 149 +155 173 184 +214 141 178 +141 214 142 +214 192 142 +144 143 186 +143 162 186 +144 186 174 +158 189 187 +180 145 163 +145 180 181 +165 153 190 +199 171 146 +146 147 199 +170 199 147 +188 148 159 +188 185 148 +149 194 178 +149 154 194 +207 150 234 +168 234 150 +245 166 151 +151 182 245 +243 182 152 +192 243 152 +200 190 153 +179 200 153 +197 154 160 +154 197 194 +184 229 155 +155 229 157 +191 156 183 +156 191 161 +229 219 157 +157 219 167 +164 203 158 +189 158 203 +205 159 181 +159 205 188 +204 160 174 +160 204 197 +161 191 201 +161 201 163 +196 162 195 +162 196 186 +193 207 206 +202 175 169 +163 201 210 +163 210 180 +225 164 193 +203 164 225 +190 223 165 +172 165 223 +209 176 166 +166 245 209 +253 167 219 +234 168 253 +167 253 168 +217 169 185 +169 217 202 +212 199 170 +170 198 212 +179 171 213 +171 199 213 +173 172 208 +223 208 172 +173 208 184 +221 204 174 +222 174 186 +174 222 221 +211 175 202 +175 211 195 +216 214 178 +263 243 192 +247 177 176 +176 209 247 +183 187 218 +226 198 177 +177 247 226 +178 231 216 +178 194 231 +236 200 179 +213 236 179 +230 180 210 +181 180 230 +181 230 205 +276 245 182 +182 243 276 +224 183 218 +183 224 191 +215 184 208 +229 184 228 +184 215 228 +185 188 220 +185 220 217 +186 196 233 +186 233 222 +235 199 212 +227 187 189 +218 187 227 +242 188 205 +188 242 220 +241 189 203 +189 241 227 +239 223 190 +190 200 239 +244 191 224 +191 244 201 +192 290 263 +192 214 290 +246 193 206 +193 246 225 +232 194 197 +194 232 231 +195 211 238 +195 238 196 +196 238 233 +197 237 232 +197 204 237 +261 212 198 +198 226 261 +248 208 223 +251 213 199 +199 235 251 +200 240 239 +200 236 240 +249 211 202 +201 255 210 +201 244 255 +217 250 202 +202 250 249 +225 252 203 +203 252 241 +260 204 221 +204 260 237 +262 205 230 +205 262 242 +258 246 206 +209 245 254 +219 229 293 +264 226 247 +206 257 258 +206 207 257 +257 207 234 +208 265 215 +208 248 265 +326 234 253 +259 247 209 +259 209 254 +278 210 255 +210 278 230 +256 211 249 +211 256 238 +286 235 212 +212 261 286 +268 236 213 +213 251 268 +328 290 214 +328 214 216 +265 284 215 +228 215 284 +272 222 233 +216 271 328 +216 231 271 +218 267 224 +266 250 217 +217 220 266 +267 218 275 +227 275 218 +305 219 293 +219 305 253 +282 220 242 +220 282 266 +222 273 221 +221 273 260 +222 272 273 +248 223 277 +223 239 277 +279 224 267 +224 279 244 +270 252 225 +246 270 225 +269 261 226 +226 264 269 +275 227 274 +227 241 274 +347 228 284 +347 293 229 +228 347 229 +318 276 243 +302 230 278 +230 302 262 +280 231 232 +231 280 271 +232 237 295 +232 295 280 +283 244 279 +233 285 272 +233 238 285 +326 287 234 +234 287 257 +300 251 235 +235 286 300 +289 240 236 +236 268 289 +237 260 288 +237 288 295 +298 238 256 +238 298 285 +311 277 239 +240 311 239 +240 289 311 +291 241 252 +274 241 291 +242 262 281 +242 281 282 +243 263 380 +243 380 318 +292 244 283 +255 244 292 +297 254 245 +245 276 297 +270 246 294 +258 294 246 +308 249 250 +247 313 264 +247 259 313 +343 265 248 +277 343 248 +249 308 309 +249 309 256 +250 266 299 +250 299 308 +268 251 306 +251 300 306 +252 270 301 +252 301 291 +305 349 253 +253 349 326 +254 303 259 +254 297 303 +307 255 292 +255 307 278 +287 304 257 +320 256 309 +256 320 298 +321 257 304 +257 321 258 +294 258 321 +313 259 312 +259 303 312 +260 357 288 +260 273 357 +314 279 267 +315 286 261 +261 269 315 +329 262 302 +262 329 281 +296 295 288 +263 290 390 +263 390 380 +327 269 264 +313 327 264 +342 284 265 +265 343 342 +336 266 282 +266 336 299 +310 267 275 +267 310 314 +317 272 285 +289 268 323 +306 323 268 +316 315 269 +269 327 316 +325 271 280 +322 270 294 +301 270 322 +328 271 334 +271 325 334 +273 272 330 +272 317 330 +273 330 357 +324 274 291 +274 324 275 +275 324 310 +276 416 297 +276 318 416 +338 304 287 +367 343 277 +277 311 367 +333 278 307 +278 333 302 +319 279 314 +279 319 283 +345 280 295 +280 345 325 +337 281 329 +282 281 337 +282 337 336 +354 283 319 +283 354 292 +311 289 335 +284 379 347 +284 342 379 +340 285 298 +285 340 317 +361 300 286 +286 315 361 +332 338 287 +287 326 332 +303 297 331 +288 357 358 +288 358 296 +341 335 289 +323 341 289 +290 328 391 +290 391 390 +346 291 301 +291 346 324 +355 292 354 +292 355 307 +293 402 305 +293 347 402 +348 299 336 +339 294 321 +294 339 322 +295 296 344 +295 344 345 +296 358 344 +350 320 309 +362 331 297 +297 416 362 +368 298 320 +298 368 340 +299 348 351 +299 351 308 +306 300 363 +300 361 363 +372 301 322 +301 372 346 +366 314 310 +374 302 333 +302 374 329 +329 365 337 +403 318 380 +312 303 360 +331 360 303 +385 327 313 +332 326 353 +304 356 321 +304 338 356 +349 305 424 +402 424 305 +364 330 317 +371 323 306 +371 306 363 +370 334 325 +369 307 355 +307 369 333 +352 308 351 +309 308 352 +309 352 350 +395 310 324 +310 395 366 +367 311 376 +335 376 311 +359 312 360 +359 385 313 +312 359 313 +378 314 366 +314 378 319 +327 421 316 +315 455 361 +315 316 455 +421 455 316 +386 338 332 +317 340 375 +317 375 364 +417 416 318 +403 417 318 +382 319 378 +319 382 354 +381 325 345 +320 350 389 +320 389 368 +356 373 321 +321 373 339 +339 377 322 +322 377 372 +341 323 387 +323 371 387 +346 388 324 +324 388 395 +325 381 399 +325 399 370 +349 383 326 +353 326 383 +384 421 327 +327 385 384 +392 328 334 +328 392 391 +406 329 374 +365 329 406 +330 364 397 +330 397 357 +400 337 365 +398 360 331 +331 362 398 +353 418 332 +386 332 418 +333 369 394 +333 394 374 +334 370 393 +334 393 392 +376 335 428 +335 341 428 +336 404 348 +336 337 404 +404 337 400 +338 386 401 +338 401 356 +423 339 373 +423 377 339 +405 340 368 +340 405 375 +408 344 358 +411 428 341 +387 411 341 +379 342 449 +343 449 342 +343 367 449 +344 408 409 +344 409 345 +409 381 345 +395 396 366 +415 346 372 +388 346 415 +358 410 408 +402 347 478 +379 478 347 +412 369 355 +422 348 404 +422 351 348 +385 359 440 +497 349 424 +383 349 497 +350 352 436 +350 436 389 +420 395 388 +351 422 437 +351 437 352 +352 437 436 +488 380 390 +419 418 353 +383 419 353 +354 382 434 +354 434 355 +355 434 412 +413 373 356 +401 413 356 +397 425 357 +410 358 425 +357 425 358 +414 440 359 +360 414 359 +360 398 414 +454 363 361 +361 455 454 +362 432 398 +362 416 432 +363 462 371 +363 454 462 +364 375 430 +364 430 397 +407 365 406 +365 407 400 +443 366 396 +366 443 378 +450 449 367 +376 450 367 +433 368 389 +368 433 405 +427 369 412 +369 427 394 +426 393 370 +370 399 426 +461 387 371 +371 462 461 +424 402 479 +441 372 377 +372 441 415 +373 413 438 +373 438 423 +374 429 406 +374 394 429 +529 391 392 +435 421 384 +431 404 400 +442 375 405 +430 375 442 +466 450 376 +466 376 428 +377 423 465 +377 465 441 +445 378 443 +378 445 382 +548 478 379 +548 379 449 +380 533 403 +380 488 533 +381 409 447 +446 382 445 +382 446 434 +419 383 460 +497 460 383 +384 439 435 +384 385 439 +440 439 385 +444 393 426 +469 386 418 +386 469 401 +463 411 387 +387 461 463 +458 388 415 +388 458 420 +473 389 436 +389 473 433 +390 391 530 +390 530 488 +391 529 530 +414 398 448 +393 486 392 +392 486 529 +393 444 486 +474 394 427 +394 474 429 +396 395 451 +420 451 395 +396 451 443 +494 397 430 +494 425 397 +452 448 398 +398 432 452 +470 426 399 +400 407 459 +400 459 431 +453 401 469 +413 401 453 +510 479 402 +478 510 402 +480 422 404 +456 440 414 +535 417 403 +403 533 535 +476 404 431 +404 476 480 +475 405 433 +405 475 442 +468 408 410 +487 406 429 +406 487 407 +407 487 459 +408 468 464 +408 464 409 +409 464 447 +467 410 425 +467 468 410 +428 411 477 +411 463 477 +484 436 437 +434 498 412 +412 498 427 +481 413 453 +481 438 413 +457 456 414 +457 414 448 +441 485 415 +415 485 458 +472 419 460 +416 534 432 +416 417 534 +417 535 534 +418 471 469 +418 419 471 +472 471 419 +499 420 458 +499 451 420 +455 421 517 +421 435 517 +505 422 480 +505 437 422 +438 483 423 +423 483 465 +479 544 424 +424 544 497 +425 494 561 +425 561 467 +462 454 541 +426 482 444 +426 470 482 +443 490 445 +427 539 474 +427 498 539 +477 508 428 +428 508 466 +513 429 474 +429 513 487 +546 430 442 +430 546 494 +528 431 459 +431 528 476 +514 452 432 +432 534 514 +433 473 489 +433 489 475 +492 463 461 +515 434 446 +498 434 515 +518 517 435 +435 439 518 +469 495 453 +436 484 509 +436 509 473 +437 505 506 +437 506 484 +483 438 493 +438 481 493 +496 518 439 +439 440 496 +440 456 496 +521 485 441 +465 521 441 +442 475 491 +442 491 546 +500 443 451 +500 490 443 +537 486 444 +444 482 537 +516 445 490 +445 516 446 +446 516 515 +507 470 447 +447 520 507 +447 464 520 +448 452 502 +448 502 457 +555 496 456 +449 551 548 +449 450 551 +552 551 450 +450 466 552 +451 499 501 +451 501 500 +452 503 502 +452 514 503 +495 532 453 +453 532 481 +579 541 454 +579 455 517 +454 455 579 +556 555 456 +456 457 556 +457 502 556 +511 458 485 +458 511 499 +527 459 487 +459 527 528 +460 582 472 +460 497 582 +492 461 526 +541 526 462 +461 462 526 +477 463 519 +463 492 519 +554 464 468 +464 554 520 +522 465 483 +522 521 465 +524 552 466 +508 524 466 +561 560 467 +468 467 560 +468 560 554 +557 480 476 +471 504 469 +495 469 504 +536 482 470 +470 507 536 +512 504 471 +471 472 512 +582 512 472 +525 473 509 +489 473 525 +474 539 540 +474 540 513 +531 475 489 +475 531 491 +476 528 572 +476 572 557 +523 487 513 +542 508 477 +542 477 519 +510 478 710 +478 548 710 +627 544 479 +479 510 627 +480 557 570 +480 570 505 +532 547 481 +547 493 481 +538 537 482 +482 536 538 +550 483 493 +483 550 522 +563 484 506 +563 509 484 +485 521 553 +485 553 511 +486 629 529 +486 537 629 +559 487 523 +487 559 527 +587 518 496 +733 533 488 +733 488 530 +568 489 525 +489 568 531 +543 491 531 +490 500 603 +490 603 516 +546 491 545 +491 543 545 +589 519 492 +526 589 492 +493 547 549 +493 549 550 +494 598 561 +494 546 598 +503 514 578 +532 495 567 +567 495 504 +562 587 496 +496 555 562 +584 536 507 +576 563 506 +511 564 499 +544 621 497 +582 497 621 +626 498 515 +498 626 539 +566 556 502 +573 566 502 +565 499 564 +565 501 499 +501 569 500 +500 569 603 +501 565 569 +574 573 502 +502 503 574 +574 503 578 +504 512 577 +504 577 567 +505 570 575 +505 575 506 +506 575 576 +572 571 557 +507 580 584 +507 520 580 +524 508 586 +508 542 586 +509 563 596 +509 596 525 +710 709 510 +627 510 709 +558 511 553 +564 511 558 +577 512 581 +512 582 581 +616 513 540 +513 616 523 +514 534 634 +514 634 578 +522 550 595 +515 516 625 +515 625 626 +516 603 625 +517 518 617 +517 617 579 +518 587 617 +519 588 542 +519 589 588 +654 520 554 +520 654 580 +593 532 567 +553 521 585 +585 522 595 +521 522 585 +523 616 583 +523 583 559 +643 552 524 +643 524 586 +525 596 601 +525 601 568 +606 589 526 +526 541 606 +527 559 594 +527 594 528 +572 528 594 +530 529 732 +529 629 732 +530 732 733 +591 531 568 +531 591 543 +593 592 532 +547 532 592 +533 657 535 +533 733 657 +534 628 634 +534 535 628 +535 657 628 +613 536 584 +538 536 613 +537 630 629 +537 538 630 +613 630 538 +615 539 626 +540 539 615 +540 615 616 +689 606 541 +541 579 689 +600 586 542 +588 600 542 +543 590 545 +543 591 590 +660 621 544 +544 627 660 +599 545 590 +545 599 546 +598 546 599 +592 647 547 +549 547 647 +681 710 548 +551 681 548 +647 641 549 +549 641 550 +595 550 641 +604 584 580 +607 569 565 +642 681 551 +551 552 642 +552 643 642 +614 553 585 +553 614 558 +665 554 560 +554 665 654 +620 562 555 +555 556 620 +556 566 620 +567 577 597 +557 571 602 +557 602 570 +558 609 564 +558 614 609 +611 574 578 +622 620 566 +605 559 583 +559 605 594 +560 655 665 +560 561 655 +655 561 598 +659 587 562 +562 620 659 +588 589 612 +632 563 576 +563 632 596 +610 564 609 +564 610 565 +565 610 607 +623 622 566 +566 573 623 +664 567 597 +567 664 593 +603 569 619 +636 568 601 +568 636 591 +618 569 607 +619 569 618 +639 597 577 +648 570 602 +570 648 575 +624 602 571 +624 572 594 +571 572 624 +573 608 623 +573 574 608 +574 611 608 +575 631 576 +575 648 631 +576 631 632 +639 577 640 +581 640 577 +578 633 611 +578 634 633 +690 689 579 +579 617 690 +654 687 580 +580 687 604 +671 640 581 +621 671 582 +581 582 671 +662 583 616 +583 662 605 +584 668 613 +584 604 668 +645 601 596 +638 585 595 +585 638 614 +586 653 643 +586 600 653 +699 617 587 +587 659 699 +637 591 636 +644 600 588 +644 588 612 +649 612 589 +589 606 649 +670 599 590 +590 635 670 +635 591 637 +590 591 635 +647 592 663 +664 663 593 +592 593 663 +674 594 605 +594 674 624 +669 595 641 +595 669 638 +678 596 632 +596 678 645 +597 639 684 +597 684 664 +599 685 598 +598 685 655 +599 670 685 +650 653 600 +600 644 650 +652 648 602 +646 601 645 +601 646 636 +602 624 651 +602 651 652 +701 603 619 +625 603 701 +609 614 666 +687 686 604 +668 604 686 +673 605 662 +605 673 674 +610 661 607 +626 752 615 +700 649 606 +606 689 700 +682 608 611 +729 659 620 +676 607 661 +607 676 618 +698 623 608 +608 682 698 +666 667 609 +661 610 667 +609 667 610 +683 682 611 +683 611 633 +612 656 644 +612 649 656 +765 630 613 +613 668 765 +648 695 631 +614 672 666 +614 638 672 +752 751 615 +615 751 616 +616 751 662 +617 699 755 +617 755 690 +693 618 676 +618 693 619 +619 693 701 +632 677 678 +675 729 620 +620 622 675 +727 671 621 +621 660 727 +622 697 675 +622 623 697 +623 698 697 +624 688 651 +624 674 688 +707 625 701 +625 707 626 +752 626 707 +709 864 627 +660 627 864 +724 634 628 +628 726 724 +628 657 726 +726 658 725 +629 630 835 +629 835 732 +630 765 835 +669 679 638 +695 694 631 +631 694 632 +677 632 694 +633 735 683 +633 634 735 +634 724 735 +713 635 637 +635 736 670 +635 713 736 +636 646 712 +636 712 637 +637 712 713 +681 642 717 +680 638 679 +672 638 680 +640 704 639 +684 639 704 +640 671 704 +641 746 669 +641 647 746 +721 717 642 +642 643 721 +643 653 721 +650 644 692 +656 692 644 +786 645 678 +645 786 646 +712 646 786 +746 647 744 +663 744 647 +743 648 652 +743 695 648 +747 656 649 +649 700 747 +691 651 688 +692 696 650 +653 650 696 +652 651 723 +651 691 723 +652 723 743 +722 721 653 +696 722 653 +687 654 759 +654 665 759 +655 685 769 +655 769 665 +661 703 676 +692 656 715 +656 747 715 +657 733 731 +657 731 726 +726 731 658 +658 731 730 +706 666 672 +734 683 735 +764 699 659 +659 729 764 +865 660 864 +727 660 865 +702 661 667 +703 661 702 +882 662 751 +662 882 673 +705 698 682 +744 663 801 +663 664 801 +684 801 664 +665 769 770 +665 770 759 +706 750 666 +667 666 750 +667 750 702 +879 668 686 +668 879 765 +746 740 669 +740 679 669 +670 760 685 +670 736 760 +671 727 778 +671 778 704 +755 777 690 +758 672 680 +672 758 706 +882 788 673 +673 788 674 +674 788 688 +694 757 677 +729 675 728 +711 728 675 +697 711 675 +741 676 703 +676 741 693 +678 677 784 +784 677 757 +678 784 786 +679 740 739 +679 739 680 +680 739 758 +718 716 708 +718 681 717 +710 681 718 +682 683 767 +682 767 705 +683 734 767 +801 684 813 +704 813 684 +685 760 771 +685 771 769 +686 825 879 +686 687 825 +825 687 759 +688 753 691 +688 788 753 +695 743 742 +811 700 689 +811 690 777 +689 690 811 +754 691 753 +691 754 723 +748 696 692 +715 748 692 +693 854 701 +693 741 854 +757 694 756 +756 695 742 +694 695 756 +763 715 747 +749 722 696 +696 748 749 +775 711 697 +697 698 775 +698 705 775 +699 880 755 +699 764 880 +768 767 734 +747 700 826 +700 811 826 +860 701 854 +701 860 707 +758 780 706 +702 797 703 +702 750 797 +797 796 703 +703 796 741 +787 712 786 +787 785 714 +776 775 705 +778 866 704 +813 704 866 +766 776 705 +767 766 705 +782 706 780 +750 706 782 +792 722 749 +928 707 860 +707 928 752 +863 708 862 +864 709 863 +708 863 718 +709 710 863 +718 863 710 +843 728 711 +711 775 843 +787 737 712 +712 737 713 +713 737 736 +787 714 737 +738 737 714 +803 748 715 +715 763 803 +716 720 719 +717 721 720 +716 718 720 +717 720 718 +719 720 793 +719 793 791 +720 721 793 +721 722 793 +722 792 793 +831 723 754 +723 831 743 +724 818 735 +725 817 818 +724 726 818 +725 818 726 +919 727 865 +778 727 919 +739 808 758 +728 843 893 +764 729 893 +728 893 729 +731 836 730 +836 732 835 +730 836 837 +731 733 836 +732 836 733 +734 816 768 +734 735 816 +818 816 735 +757 819 784 +737 761 736 +736 761 760 +737 738 761 +762 761 738 +812 756 742 +808 739 806 +740 806 739 +806 745 805 +740 746 806 +796 823 741 +741 823 854 +742 830 812 +831 830 743 +742 743 830 +871 753 788 +802 815 745 +802 744 801 +744 802 746 +745 806 802 +746 802 806 +792 749 841 +829 843 775 +763 747 842 +826 842 747 +748 803 840 +841 749 840 +748 840 749 +790 781 789 +790 750 782 +790 797 750 +751 926 882 +928 926 752 +751 752 926 +908 753 871 +753 908 754 +754 908 831 +899 777 755 +755 880 899 +883 825 759 +756 812 850 +756 850 757 +819 757 850 +800 799 779 +780 758 800 +800 758 808 +768 816 845 +884 759 770 +759 884 883 +761 773 760 +773 771 760 +761 762 773 +762 772 773 +875 803 763 +842 875 763 +894 880 764 +894 764 893 +879 932 765 +765 932 835 +878 776 766 +844 878 766 +766 767 844 +845 844 768 +767 768 844 +771 774 769 +770 769 774 +770 774 884 +772 885 774 +884 774 885 +771 773 774 +772 774 773 +849 829 775 +775 776 849 +776 878 849 +777 898 811 +777 899 898 +881 778 919 +866 778 881 +800 779 783 +781 783 779 +800 783 780 +780 783 782 +781 790 783 +782 783 790 +819 820 784 +821 785 820 +784 820 786 +785 787 820 +786 820 787 +788 886 871 +788 882 886 +789 794 795 +789 795 790 +797 790 795 +791 828 827 +828 792 841 +791 793 828 +792 828 793 +794 822 798 +794 798 795 +822 823 798 +823 796 798 +795 798 797 +796 797 798 +799 809 807 +799 800 809 +809 800 808 +813 814 801 +815 802 814 +801 814 802 +874 840 803 +803 875 874 +850 812 868 +805 804 810 +804 807 810 +805 810 806 +806 810 808 +807 809 810 +808 810 809 +924 826 811 +898 924 811 +891 812 830 +868 812 891 +814 813 867 +866 867 813 +814 867 815 +815 867 935 +832 845 816 +834 832 817 +816 818 832 +817 832 818 +851 819 850 +820 819 851 +820 851 821 +853 821 851 +822 824 856 +822 856 823 +823 856 854 +824 855 856 +909 831 908 +825 883 930 +825 930 879 +914 842 826 +826 924 914 +838 827 839 +827 828 839 +841 839 828 +916 843 829 +829 849 916 +830 889 891 +830 831 889 +889 831 909 +846 845 832 +847 846 833 +832 834 846 +833 846 834 +835 933 836 +835 932 933 +836 933 837 +837 933 934 +840 874 857 +857 858 838 +839 857 838 +839 841 857 +840 857 841 +878 844 877 +875 842 903 +842 914 903 +915 893 843 +843 916 915 +844 848 877 +844 845 848 +845 846 848 +846 847 848 +877 848 876 +847 876 848 +905 916 849 +849 878 905 +870 850 868 +850 870 851 +852 870 869 +851 870 853 +852 853 870 +859 855 861 +854 859 860 +854 856 859 +855 859 856 +858 857 872 +872 857 874 +858 872 873 +861 927 859 +859 927 860 +860 927 928 +861 925 927 +862 920 921 +862 921 863 +863 921 864 +864 921 865 +865 921 919 +867 866 936 +881 936 866 +867 936 935 +891 890 868 +888 869 890 +868 890 870 +869 870 890 +937 871 886 +871 937 908 +913 903 914 +913 912 902 +874 901 872 +872 901 873 +901 900 873 +874 875 901 +903 901 875 +897 876 896 +877 876 897 +877 897 878 +897 905 878 +978 932 879 +978 879 930 +899 880 956 +880 894 956 +919 971 881 +936 881 971 +961 882 926 +886 882 961 +929 930 883 +883 884 929 +884 885 929 +931 929 885 +910 889 909 +911 887 910 +924 898 950 +947 886 961 +886 947 937 +910 892 889 +910 887 892 +890 892 888 +887 888 892 +889 892 891 +890 891 892 +893 940 894 +893 915 940 +894 940 956 +907 906 895 +895 906 896 +906 905 897 +896 906 897 +957 950 898 +898 899 957 +957 899 956 +900 904 902 +900 901 904 +901 903 904 +902 904 913 +903 913 904 +917 916 905 +905 906 917 +906 907 917 +918 917 907 +938 908 937 +908 938 909 +909 938 910 +910 938 911 +939 911 938 +912 923 922 +912 913 923 +913 914 923 +914 924 923 +942 940 915 +915 916 942 +916 917 942 +917 918 942 +943 942 918 +971 919 969 +969 920 967 +919 921 969 +920 969 921 +922 923 948 +922 948 949 +950 948 924 +923 924 948 +960 925 959 +926 960 961 +925 960 927 +926 928 960 +927 960 928 +930 929 977 +929 931 977 +930 977 978 +931 974 977 +933 932 976 +932 978 976 +933 976 934 +973 934 976 +970 968 935 +971 970 936 +935 936 970 +937 947 946 +937 946 938 +946 945 939 +938 946 939 +954 953 941 +953 956 940 +953 944 941 +953 940 944 +940 942 944 +941 944 943 +942 943 944 +964 945 963 +945 946 963 +963 947 961 +946 947 963 +948 950 955 +948 955 949 +955 951 949 +950 957 955 +952 951 958 +951 955 958 +956 953 958 +952 958 954 +953 954 958 +955 957 958 +956 958 957 +962 965 959 +959 965 960 +960 965 961 +961 965 963 +962 964 965 +963 965 964 +966 972 967 +966 968 972 +967 972 969 +968 970 972 +969 972 971 +970 971 972 +976 979 973 +977 974 979 +973 979 975 +974 975 979 +976 978 979 +977 979 978 +# + +BoundaryDomain +default 1 +# + +# diff --git a/tests/data/groundwater_1d.dgf b/tests/data/groundwater_1d.dgf new file mode 100644 index 00000000000..7353d16cccc --- /dev/null +++ b/tests/data/groundwater_1d.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 % first corner +1 % second corner +10 % cells in x direction +# + +GridParameter +overlap 1 +closure green +#GridParameter + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/groundwater_2d.dgf b/tests/data/groundwater_2d.dgf new file mode 100644 index 00000000000..369ff99f0f2 --- /dev/null +++ b/tests/data/groundwater_2d.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +1 1 % second corner +10 10 % cells in x and y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/groundwater_3d.dgf b/tests/data/groundwater_3d.dgf new file mode 100644 index 00000000000..8f72e831763 --- /dev/null +++ b/tests/data/groundwater_3d.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 0 % first corner +1 1 1 % second corner +20 20 20 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/infiltration_250x20.dgf b/tests/data/infiltration_250x20.dgf new file mode 100644 index 00000000000..f51853d059d --- /dev/null +++ b/tests/data/infiltration_250x20.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +500 10 % second corner +250 20 % cells in x and y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/infiltration_50x3.dgf b/tests/data/infiltration_50x3.dgf new file mode 100644 index 00000000000..263d6282cad --- /dev/null +++ b/tests/data/infiltration_50x3.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +500 10 % second corner +50 3 % cells in x and y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/obstacle_24x16.dgf b/tests/data/obstacle_24x16.dgf new file mode 100644 index 00000000000..9f9f18df9a0 --- /dev/null +++ b/tests/data/obstacle_24x16.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +60 40 % second corner +24 16 % 24 cells in x and 16 in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/obstacle_48x32.dgf b/tests/data/obstacle_48x32.dgf new file mode 100644 index 00000000000..e95b1c49578 --- /dev/null +++ b/tests/data/obstacle_48x32.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +60 40 % second corner +48 32 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/outflow.dgf b/tests/data/outflow.dgf new file mode 100644 index 00000000000..a53d5ba36e6 --- /dev/null +++ b/tests/data/outflow.dgf @@ -0,0 +1,17 @@ +DGF +Interval +0 0 % first corner +1 1 % second corner +20 20 % number of cells in x and y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/reservoir.dgf b/tests/data/reservoir.dgf new file mode 100644 index 00000000000..828b4d16d10 --- /dev/null +++ b/tests/data/reservoir.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +6000 20 % second corner +100 10 % 100 cells in x and 10 in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/richardslens_24x16.dgf b/tests/data/richardslens_24x16.dgf new file mode 100644 index 00000000000..7ad09660ad9 --- /dev/null +++ b/tests/data/richardslens_24x16.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +6 4 % second corner +24 16 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/richardslens_48x32.dgf b/tests/data/richardslens_48x32.dgf new file mode 100644 index 00000000000..907f619dc72 --- /dev/null +++ b/tests/data/richardslens_48x32.dgf @@ -0,0 +1,17 @@ +DGF + +Interval +0 0 % first corner +6 4 % second corner +48 32 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# +# diff --git a/tests/data/richardslens_96x64.dgf b/tests/data/richardslens_96x64.dgf new file mode 100644 index 00000000000..15d677f8422 --- /dev/null +++ b/tests/data/richardslens_96x64.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +6 4 % second corner +96 64 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/test_stokes.dgf b/tests/data/test_stokes.dgf new file mode 100644 index 00000000000..6b16d1163de --- /dev/null +++ b/tests/data/test_stokes.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +1.0 1.0 % second corner +32 32 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/test_stokes2c.dgf b/tests/data/test_stokes2c.dgf new file mode 100644 index 00000000000..21fab59fa2c --- /dev/null +++ b/tests/data/test_stokes2c.dgf @@ -0,0 +1,20 @@ +DGF + +Interval +0 0 % first corner +1.0 1.0 % second corner +32 32 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +copies none +heapsize 500 +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/test_stokes2cni.dgf b/tests/data/test_stokes2cni.dgf new file mode 100644 index 00000000000..dc6db3ab49d --- /dev/null +++ b/tests/data/test_stokes2cni.dgf @@ -0,0 +1,18 @@ +DGF + +Interval +0 0 % first corner +1.0 1.0 % second corner +18 18 % cells in x and in y direction +# + +GridParameter +overlap 1 +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/data/waterair.dgf b/tests/data/waterair.dgf new file mode 100644 index 00000000000..07b10af5b74 --- /dev/null +++ b/tests/data/waterair.dgf @@ -0,0 +1,19 @@ +DGF + +Interval +0 0 % first corner +40 40 % second corner +8 8 % cells in x and y direction +# + +GridParameter +overlap 1 +periodic +closure green +# + +BOUNDARYDOMAIN +default 1 % all boundaries have id 1 +# + +# diff --git a/tests/models/test_mpiutil.cpp b/tests/models/test_mpiutil.cpp new file mode 100644 index 00000000000..327575b435f --- /dev/null +++ b/tests/models/test_mpiutil.cpp @@ -0,0 +1,103 @@ +/* + Copyright 2020 Equinor ASA. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include + +#include +#include + +#include + +#if HAVE_MPI +struct MPIError +{ + MPIError(std::string s, int e) : errorstring(std::move(s)), errorcode(e){} + std::string errorstring; + int errorcode; +}; + +void MPI_err_handler(MPI_Comm*, int* err_code, ...) +{ + std::vector err_string(MPI_MAX_ERROR_STRING); + int err_length; + MPI_Error_string(*err_code, err_string.data(), &err_length); + std::string s(err_string.data(), err_length); + std::cerr << "An MPI Error ocurred:" << std::endl << s << std::endl; + throw MPIError(s, *err_code); +} +#endif + +bool noStrings(int, int) +{ + std::string empty; + auto res = Opm::gatherStrings(empty); + assert(res.empty()); + return true; +} + +bool oddRankStrings(int size, int rank) +{ + std::string what = (rank % 2 == 1) ? "An error on rank " + std::to_string(rank) : std::string(); + auto res = Opm::gatherStrings(what); + assert(int(res.size()) == size/2); + for (int i = 0; i < size/2; ++i) { + assert(res[i] == "An error on rank " + std::to_string(2*i + 1)); + } + return true; +} + +bool allRankStrings(int size, int rank) +{ + std::string what = "An error on rank " + std::to_string(rank); + auto res = Opm::gatherStrings(what); + assert(int(res.size()) == size); + for (int i = 0; i < size; ++i) { + assert(res[i] == "An error on rank " + std::to_string(i)); + } + return true; +} + + +int testMain(int size, int rank) +{ + bool ok = noStrings(size, rank); + ok = ok && oddRankStrings(size, rank); + ok = ok && allRankStrings(size, rank); + if (ok) { + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } +} + + +int main(int argc, char** argv) +{ + const auto& mpiHelper = Dune::MPIHelper::instance(argc, argv); + int mpiSize = mpiHelper.size(); + int mpiRank = mpiHelper.rank(); +#if HAVE_MPI + // register a throwing error handler to allow for + // debugging with "catch throw" in gdb + MPI_Errhandler handler; + MPI_Comm_create_errhandler(MPI_err_handler, &handler); + MPI_Comm_set_errhandler(MPI_COMM_WORLD, handler); +#endif + return testMain(mpiSize, mpiRank); +} diff --git a/tests/models/test_propertysystem.cpp b/tests/models/test_propertysystem.cpp new file mode 100644 index 00000000000..af2dea7f76d --- /dev/null +++ b/tests/models/test_propertysystem.cpp @@ -0,0 +1,204 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file tests the properties system. + * + * We define a few type tags and property tags, then we attach values + * to (TypeTag, PropertyTag) tuples and finally we use them in the + * main function and print some diagnostic messages. + */ +#include "config.h" + +#include + +#include + +namespace Opm::Properties { + +/////////////////// +// Define some hierarchy of type tags: +// +// Vehicle -- CompactCar -- Sedan -_ +// \ \. +// \ +- Pickup ---_ +// \ / \. +// +-- Truck ---------------^ \. +// \ \. +// +- Tank ----------------------------------+- HummerH1 +/////////////////// +namespace TTag { +struct Vehicle {}; + +struct CompactCar { using InheritsFrom = std::tuple; }; +struct Truck { using InheritsFrom = std::tuple; }; +struct Tank { using InheritsFrom = std::tuple; }; + +struct Sedan { using InheritsFrom = std::tuple; }; +struct Pickup { using InheritsFrom = std::tuple; }; + +struct HummerH1 { using InheritsFrom = std::tuple; }; +} // end namespace TTag + +/////////////////// +// Define the property tags: +// TopSpeed, NumSeats, CanonCaliber, GasUsage, AutomaticTransmission, Payload +/////////////////// +template +struct TopSpeed { using type = UndefinedProperty; }; // [km/h] +template +struct NumSeats { using type = UndefinedProperty; }; // [] +template +struct CanonCaliber { using type = UndefinedProperty; }; // [mm] +template +struct GasUsage { using type = UndefinedProperty; }; // [l/100km] +template +struct AutomaticTransmission { using type = UndefinedProperty; }; // true/false +template +struct Payload { using type = UndefinedProperty; }; // [t] + +/////////////////// +// Make the AutomaticTransmission default to false +template +struct AutomaticTransmission { static constexpr bool value = false; }; + +/////////////////// +// Define some values for the properties on the type tags: +// +// (CompactCar, TopSpeed) = GasUsage*35 +// (CompactCar, NumSeats) = 5 +// (CompactCar, GasUsage) = 4 +// +// (Truck, TopSpeed) = 100 +// (Truck, NumSeats) = 2 +// (Truck, GasUsage) = 12 +// (Truck, Payload) = 35 +// +// (Tank, TopSpeed) = 60 +// (Tank, GasUsage) = 65 +// (Tank, CanonCaliber) = 120 +// +// (Sedan, GasUsage) = 7 +// (Sedan, AutomaticTransmission) = true +// +// (Pickup, TopSpeed) = 120 +// (Pickup, Payload) = 5 +// +// (HummmerH1, TopSpeed) = (Pickup, TopSpeed) +/////////////////// + +template +struct TopSpeed { static constexpr int value = getPropValue() * 30; }; +template +struct NumSeats { static constexpr int value = 5; }; +template +struct GasUsage { static constexpr int value = 4; }; + +template +struct TopSpeed { static constexpr int value = 100; }; +template +struct NumSeats { static constexpr int value = 2; }; +template +struct GasUsage { static constexpr int value = 12; }; +template +struct Payload { static constexpr int value = 35; }; + +template +struct TopSpeed { static constexpr int value = 60; }; +template +struct GasUsage { static constexpr int value = 65; }; +template +struct CanonCaliber { static constexpr int value = 120; }; + +template +struct GasUsage { static constexpr int value = 7; }; +template +struct AutomaticTransmission { static constexpr bool value = true; }; + +template +struct TopSpeed { static constexpr int value = 120; }; +template +struct Payload { static constexpr int value = 5; }; + +template +struct TopSpeed { static constexpr int value = getPropValue(); }; + +} // namespace Opm::Properties + + +int main() +{ + using namespace Opm; + using namespace Opm::Properties; + + // print all properties for all type tags + std::cout << "---------------------------------------\n"; + std::cout << "-- Property values\n"; + std::cout << "---------------------------------------\n"; + + std::cout << "---------- Values for CompactCar ----------\n"; + + std::cout << "(CompactCar, TopSpeed) = " << getPropValue() << "\n"; + std::cout << "(CompactCar, NumSeats) = " << getPropValue() << "\n"; + std::cout << "(CompactCar, GasUsage) = " << getPropValue() << "\n"; + std::cout << "(CompactCar, AutomaticTransmission) = " << getPropValue() << "\n"; + + std::cout << "---------- Values for Truck ----------\n"; + + std::cout << "(Truck, TopSpeed) = " << getPropValue() << "\n"; + std::cout << "(Truck, NumSeats) = " << getPropValue() << "\n"; + std::cout << "(Truck, GasUsage) = " << getPropValue() << "\n"; + std::cout << "(Truck, Payload) = " << getPropValue() << "\n"; + std::cout << "(Truck, AutomaticTransmission) = " << getPropValue() << "\n"; + + std::cout << "---------- Values for Tank ----------\n"; + + std::cout << "(Tank, TopSpeed) = " << getPropValue() << "\n"; + std::cout << "(Tank, GasUsage) = " << getPropValue() << "\n"; + std::cout << "(Tank, AutomaticTransmission) = " << getPropValue() << "\n"; + std::cout << "(Tank, CanonCaliber) = " << getPropValue() << "\n"; + + std::cout << "---------- Values for Sedan ----------\n"; + + std::cout << "(Sedan, TopSpeed) = " << getPropValue() << "\n"; + std::cout << "(Sedan, NumSeats) = " << getPropValue() << "\n"; + std::cout << "(Sedan, GasUsage) = " << getPropValue() << "\n"; + std::cout << "(Sedan, AutomaticTransmission) = " << getPropValue() << "\n"; + + std::cout << "---------- Values for Pickup ----------\n"; + std::cout << "(Pickup, TopSpeed) = " << getPropValue() << "\n"; + std::cout << "(Pickup, NumSeats) = " << getPropValue() << "\n"; + std::cout << "(Pickup, GasUsage) = " << getPropValue() << "\n"; + std::cout << "(Pickup, Payload) = " << getPropValue() << "\n"; + std::cout << "(Pickup, AutomaticTransmission) = " << getPropValue() << "\n"; + + std::cout << "---------- Values for HummerH1 ----------\n"; + std::cout << "(HummerH1, TopSpeed) = " << getPropValue() << "\n"; + std::cout << "(HummerH1, NumSeats) = " << getPropValue() << "\n"; + std::cout << "(HummerH1, GasUsage) = " << getPropValue() << "\n"; + std::cout << "(HummerH1, Payload) = " << getPropValue() << "\n"; + std::cout << "(HummerH1, AutomaticTransmission) = " << getPropValue() << "\n"; + + return 0; +} diff --git a/tests/models/test_quadrature.cpp b/tests/models/test_quadrature.cpp new file mode 100644 index 00000000000..970cb443a4f --- /dev/null +++ b/tests/models/test_quadrature.cpp @@ -0,0 +1,357 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * \brief A test for numerical integration using the vertex-centered finite + * volume geometries. + */ +#include "config.h" + +#include +#include +#include +#include + +#if HAVE_DUNE_ALUGRID +#include +#if HAVE_MPI +template +using AluGrid = Dune::ALUGrid; +#else +template +using AluGrid = Dune::ALUGrid; +#endif //HAVE_MPI +#endif + +#include +#include + +#include + +const unsigned dim = 3; +using Scalar = double; +using QuadratureGeom = Opm::QuadrialteralQuadratureGeometry; +using LocalPosition = QuadratureGeom::LocalPosition; +using GlobalPosition = QuadratureGeom::GlobalPosition; + +// function prototypes +GlobalPosition::field_type f(const GlobalPosition &pos); +void testIdenityMapping(); +template +void writeTetrahedronSubControlVolumes(const Grid &grid); +void testTetrahedron(); +template +void writeCubeSubControlVolumes(const Grid &grid); +void testCube(); +void testQuadrature(); + +GlobalPosition::field_type f(const GlobalPosition &pos) +{ + GlobalPosition::field_type result = 1; + for (unsigned i = 0; i < GlobalPosition::dimension; ++i) + result *= pos[i]; + return result; +} + +void testIdenityMapping() +{ + QuadratureGeom foo; + + Scalar corners[][3] = { { 0, 0, 0 }, + { 1, 0, 0 }, + { 0, 1, 0 }, + { 1, 1, 0 }, + { 0, 0, 1 }, + { 1, 0, 1 }, + { 0, 1, 1 }, + { 1, 1, 1 } }; + foo.setCorners(corners, 8); + + std::cout << "testing identity mapping...\n"; + unsigned n = 100; + for (unsigned i = 0; i < n; ++i) { + for (unsigned j = 0; j < n; ++j) { + for (unsigned k = 0; k < n; ++k) { + LocalPosition localPos; + + localPos[0] = Scalar(i) / (n - 1); + localPos[1] = Scalar(j) / (n - 1); + localPos[2] = Scalar(k) / (n - 1); + + GlobalPosition globalPos = foo.global(localPos); + + GlobalPosition diff(localPos); + diff -= globalPos; + assert(diff.two_norm() < 1e-10); + } + } + } +} + +template +void writeTetrahedronSubControlVolumes([[maybe_unused]] const Grid& grid) +{ +#if HAVE_DUNE_ALUGRID + using GridView = typename Grid::LeafGridView; + + using Grid2 = AluGrid; + using GridView2 = typename Grid2::LeafGridView; + using GridFactory2 = Dune::GridFactory; + + GridFactory2 gf2; + const auto &gridView = grid.leafView(); + using Stencil = Opm::VcfvStencil; + using Mapper = typename Stencil :: Mapper; + + Mapper mapper(gridView, Dune::mcmgVertexLayout()); + Stencil stencil(gridView, mapper); + + auto eIt = gridView.template begin<0>(); + const auto &eEndIt = gridView.template end<0>(); + for (; eIt != eEndIt; ++eIt) { + stencil.update(*eIt); + for (unsigned scvIdx = 0; scvIdx < stencil.numDof(); ++scvIdx) { + const auto &scvLocalGeom = stencil.subControlVolume(scvIdx).localGeometry(); + + for (unsigned i = 0; i < scvLocalGeom.numCorners; ++i) { + GlobalPosition pos( + eIt->geometry().global(scvLocalGeom.corner(i))); + gf2.insertVertex(pos); + } + } + } + + unsigned cornerOffset = 0; + eIt = gridView.template begin<0>(); + for (; eIt != eEndIt; ++eIt) { + stencil.update(*eIt); + for (unsigned scvIdx = 0; scvIdx < stencil.numDof(); ++scvIdx) { + const auto &scvLocalGeom = stencil.subControlVolume(scvIdx).localGeometry(); + + std::vector vertexIndices; + for (unsigned i = 0; i < scvLocalGeom.numCorners; ++i) { + vertexIndices.push_back(cornerOffset); + ++cornerOffset; + } + + gf2.insertElement(Dune::GeometryType(/*topologyId=*/(1 << dim) - 1, dim), + vertexIndices); + } + } + + const auto grid2 = gf2.createGrid(); + using VtkWriter = Dune::VTKWriter; + VtkWriter writer(grid2->leafView(), Dune::VTK::conforming); + writer.write("tetrahedron-scvs", Dune::VTK::ascii); +#endif // HAVE_DUNE_ALUGRID +} + +void testTetrahedron() +{ +#if HAVE_DUNE_ALUGRID + using Grid = AluGrid; + using GridFactory = Dune::GridFactory; + GridFactory gf; + Scalar corners[][3] = { { 0, 0, 0 }, { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + for (unsigned i = 0; i < sizeof(corners) / sizeof(corners[0]); ++i) { + GlobalPosition pos; + for (unsigned j = 0; j < dim; ++j) + pos[j] = corners[i][j]; + gf.insertVertex(pos); + } + std::vector v = { 0, 1, 2, 3 }; + // in Dune >= 2.6 topologyIds seem to be opaque integers. WTF!? + gf.insertElement(Dune::GeometryType(/*topologyId=*/0, dim), v); + const auto grid = gf.createGrid(); + + // write the sub-control volumes to a VTK file. + writeTetrahedronSubControlVolumes(*grid); +#endif // HAVE_DUNE_ALUGRID +} + +template +void writeCubeSubControlVolumes([[maybe_unused]] const Grid& grid) +{ +#if HAVE_DUNE_ALUGRID + using GridView = typename Grid::LeafGridView; + using Grid2 = AluGrid; + using GridView2 = typename Grid2::LeafGridView; + using GridFactory2 = Dune::GridFactory; + using Stencil = Opm::VcfvStencil; + + GridFactory2 gf2; + const auto &gridView = grid.leafView(); + + using VertexMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + VertexMapper vertexMapper(gridView, Dune::mcmgVertexLayout()); + Stencil stencil(gridView, vertexMapper); + auto eIt = gridView.template begin<0>(); + const auto &eEndIt = gridView.template end<0>(); + for (; eIt != eEndIt; ++eIt) { + stencil.update(*eIt); + for (unsigned scvIdx = 0; scvIdx < stencil.numDof(); ++scvIdx) { + const auto &scvLocalGeom = stencil.subControlVolume(scvIdx).localGeometry(); + + for (unsigned i = 0; i < scvLocalGeom.numCorners; ++i) { + GlobalPosition pos( + eIt->geometry().global(scvLocalGeom.corner(i))); + gf2.insertVertex(pos); + } + } + } + + unsigned cornerOffset = 0; + eIt = gridView.template begin<0>(); + for (; eIt != eEndIt; ++eIt) { + stencil.update(*eIt); + for (unsigned scvIdx = 0; scvIdx < stencil.numDof(); ++scvIdx) { + const auto &scvLocalGeom = stencil.subControlVolume(scvIdx).localGeometry(); + + std::vector vertexIndices; + for (unsigned i = 0; i < scvLocalGeom.numCorners; ++i) { + vertexIndices.push_back(cornerOffset); + ++cornerOffset; + } + + gf2.insertElement(Dune::GeometryType(/*topologyId=*/(1 << dim) - 1, dim), + vertexIndices); + } + } + + const auto grid2 = gf2.createGrid(); + using VtkWriter = Dune::VTKWriter; + VtkWriter writer(grid2->leafView(), Dune::VTK::conforming); + writer.write("cube-scvs", Dune::VTK::ascii); +#endif // HAVE_DUNE_ALUGRID +} + +void testCube() +{ +#if HAVE_DUNE_ALUGRID + using Grid = AluGrid; + using GridFactory = Dune::GridFactory; + GridFactory gf; + Scalar corners[][3] = { { 0, 0, 0 }, + { 1, 0, 0 }, + { 0, 2, 0 }, + { 3, 3, 0 }, + { 0, 0, 4 }, + { 5, 0, 5 }, + { 0, 6, 6 }, + { 7, 7, 7 }, }; + + for (unsigned i = 0; i < sizeof(corners) / sizeof(corners[0]); ++i) { + GlobalPosition pos; + for (unsigned j = 0; j < dim; ++j) + pos[j] = corners[i][j]; + gf.insertVertex(pos); + } + std::vector v = { 0, 1, 2, 3, 4, 5, 6, 7 }; + // in Dune >= 2.6 topologyIds seem to be opaque integers. WTF!? + gf.insertElement(Dune::GeometryType((1 << dim) - 1, dim), v); + const auto grid = gf.createGrid(); + + // write the sub-control volumes to a VTK file. + writeCubeSubControlVolumes(*grid); +#endif // HAVE_DUNE_ALUGRID +} + +void testQuadrature() +{ + std::cout << "testing SCV quadrature...\n"; + + std::array cellRes; + + std::fill(cellRes.begin(), cellRes.end(), 10); + + GlobalPosition upperRight(1.0); + + using Grid = Dune::YaspGrid; + using GridView = Grid::LeafGridView; + Grid grid(upperRight, cellRes); + + // compute approximate integral + auto gridView = grid.leafGridView(); + auto eIt = gridView.begin<0>(); + const auto eEndIt = gridView.end<0>(); + Scalar result = 0; + // instanciate a stencil + using Stencil = Opm::VcfvStencil; + using Mapper = typename Stencil :: Mapper; + + Mapper mapper(gridView, Dune::mcmgVertexLayout()); + Stencil stencil(gridView, mapper); + for (; eIt != eEndIt; ++eIt) { + const auto &elemGeom = eIt->geometry(); + + stencil.update(*eIt); + + // loop over all sub-control volumes + for (unsigned scvIdx = 0; scvIdx < stencil.numDof(); ++scvIdx) { + const auto &scvLocalGeom = stencil.subControlVolume(scvIdx).localGeometry(); + + Dune::GeometryType geomType = scvLocalGeom.type(); + static const unsigned quadratureOrder = 2; + const auto &rule + = Dune::QuadratureRules::rule(geomType, + quadratureOrder); + + // integrate f over the sub-control volume + for (auto it = rule.begin(); it != rule.end(); ++it) { + auto posScvLocal = it->position(); + auto posElemLocal = scvLocalGeom.global(posScvLocal); + auto posGlobal = elemGeom.global(posScvLocal); + + Scalar fval = f(posGlobal); + Scalar weight = it->weight(); + Scalar detjac = scvLocalGeom.integrationElement(posScvLocal) + * elemGeom.integrationElement(posElemLocal); + + result += fval * weight * detjac; + } + } + } + + std::cout << "result: " << result + << " (expected value: " << 1.0 / (1 << dim) << ")\n"; +} + +int main(int argc, char **argv) +{ + // initialize MPI, finalize is done automatically on exit + Dune::MPIHelper::instance(argc, argv); + + testIdenityMapping(); +// test the quadrature in a tetrahedron. since the CLang compiler +// prior to version 3.2 generates incorrect code here, we do not +// do it if the compiler is clang 3.1 or older. +#if !__clang__ || __clang_major__ > 3 \ + || (__clang_major__ == 3 && __clang_minor__ >= 3) + testTetrahedron(); + testCube(); +#endif + testQuadrature(); + + return 0; +} diff --git a/tests/models/test_tasklets.cpp b/tests/models/test_tasklets.cpp new file mode 100644 index 00000000000..be54ffa8bd6 --- /dev/null +++ b/tests/models/test_tasklets.cpp @@ -0,0 +1,100 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file serves as an example of how to use the tasklet mechanism for + * asynchronous work. + */ +#include "config.h" + +#include + +#include +#include + +std::mutex outputMutex; + +// The runner is created on the heap for the assertion and outputs in the run function of the tasklets. +std::unique_ptr runner{}; + +class SleepTasklet : public Opm::TaskletInterface +{ +public: + SleepTasklet(int mseconds) + : mseconds_(mseconds) + { + n_ = numInstantiated_; + ++ numInstantiated_; + } + + void run() + { + assert(0 <= runner->workerThreadIndex() && runner->workerThreadIndex() < runner->numWorkerThreads()); + std::this_thread::sleep_for(std::chrono::milliseconds(mseconds_)); + std::lock_guard guard(outputMutex); + std::cout << "Sleep tasklet " << n_ << " of " << mseconds_ << " ms completed by worker thread " << runner->workerThreadIndex() << std::endl; + } + +private: + static int numInstantiated_; + int n_; + int mseconds_; +}; + +void sleepAndPrintFunction(); +void sleepAndPrintFunction() +{ + int ms = 100; + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + std::lock_guard guard(outputMutex); + std::cout << "Sleep completed by worker thread " << runner->workerThreadIndex() << std::endl; +} + +int SleepTasklet::numInstantiated_ = 0; + +int main() +{ + int numWorkers = 2; + runner = std::make_unique(numWorkers); + + // the master thread is not a worker thread + assert(runner->workerThreadIndex() < 0); + assert(runner->numWorkerThreads() == numWorkers); + + for (int i = 0; i < 5; ++ i) { + //auto st = std::make_shared((i + 1)*1000); + auto st = std::make_shared(100); + runner->dispatch(st); + } + + std::cout << "before barrier" << std::endl; + runner->barrier(); + std::cout << "after barrier" << std::endl; + + runner->dispatchFunction(sleepAndPrintFunction); + runner->dispatchFunction(sleepAndPrintFunction, /*numInvokations=*/6); + + return 0; +} + diff --git a/tests/models/test_tasklets_failure.cpp b/tests/models/test_tasklets_failure.cpp new file mode 100644 index 00000000000..c0eb6c7d050 --- /dev/null +++ b/tests/models/test_tasklets_failure.cpp @@ -0,0 +1,148 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file serves as an example of how to use the tasklet mechanism for + * asynchronous work, especially for tasklets that fail. + */ +#define BOOST_TEST_MODULE TASKLETS_FAILURE + +//#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include + +std::mutex outputMutex; + +// The runner is created on the heap for the assertion and outputs in the run function of the tasklets. +std::unique_ptr runner{}; + +class SleepTasklet : public Opm::TaskletInterface +{ +public: + SleepTasklet(int mseconds, int id) + : mseconds_(mseconds), + id_(id) + {} + + void run() override + { + assert(0 <= runner->workerThreadIndex() && runner->workerThreadIndex() < runner->numWorkerThreads()); + std::cout << "Sleep tasklet " << id_ << " of " << mseconds_ << " ms starting sleep on worker thread " << runner->workerThreadIndex() << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(mseconds_)); + std::lock_guard guard(outputMutex); + std::cout << "Sleep tasklet " << id_ << " of " << mseconds_ << " ms completed by worker thread " << runner->workerThreadIndex() << std::endl; + } + +private: + int mseconds_; + int id_; +}; + +class FailingSleepTasklet : public Opm::TaskletInterface +{ +public: + FailingSleepTasklet(int mseconds) + : mseconds_(mseconds) + {} + void run() override + { + std::this_thread::sleep_for(std::chrono::milliseconds(mseconds_)); + std::lock_guard guard(outputMutex); + std::cout << "Failing sleep tasklet of " << mseconds_ << " ms failing now, on work thread " << runner->workerThreadIndex() << std::endl; + throw std::logic_error("Intentional failure for testing"); + } + +private: + int mseconds_; +}; + +void execute () { + int numWorkers = 2; + runner = std::make_unique(numWorkers); + + // the master thread is not a worker thread + BOOST_REQUIRE_LT(runner->workerThreadIndex(), 0); + BOOST_REQUIRE_EQUAL(runner->numWorkerThreads(), numWorkers); + + // Dispatch some successful tasklets + for (int i = 0; i < 5; ++i) { + runner->barrier(); + + if (runner->failure()) { + exit(EXIT_FAILURE); + } + auto st = std::make_shared(10,i); + runner->dispatch(st); + } + + runner->barrier(); + if (runner->failure()) { + exit(EXIT_FAILURE); + } + // Dispatch a failing tasklet + auto failingSleepTasklet = std::make_shared(100); + runner->dispatch(failingSleepTasklet); + + // Dispatch more successful tasklets + for (int i = 5; i < 10; ++i) { + runner->barrier(); + + if (runner->failure()) { + exit(EXIT_FAILURE); + } + auto st = std::make_shared(10,i); + runner->dispatch(st); + } + + std::cout << "before barrier" << std::endl; + runner->barrier(); +} +BOOST_AUTO_TEST_SUITE(Tasklets) +BOOST_AUTO_TEST_CASE(TASKLETS_FAILURE) { + pid_t pid = fork(); // Create a new process, such that this child process can call exit(EXIT_FAILURE) + if (pid == -1) { + BOOST_FAIL("Fork failed"); + } else if (pid == 0) { + // Child process + execute(); + _exit(0); // Should never reach here + } else { + // Parent process + std::cout << "Checking failure of child process with parent process, process id " << pid << std::endl; + int status; + waitpid(pid, &status, 0); + BOOST_CHECK(WIFEXITED(status)); // Check if the child process exited + BOOST_CHECK_EQUAL(WEXITSTATUS(status), EXIT_FAILURE); // Check if the exit status is EXIT_FAILURE + } +} +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/run-vtu-test.sh b/tests/run-vtu-test.sh new file mode 100755 index 00000000000..eaee9f51163 --- /dev/null +++ b/tests/run-vtu-test.sh @@ -0,0 +1,243 @@ +#! /bin/bash +# +# Runs a test from the test directory and compare the resulting VTU files. +# +# Usage: +# +# runTest.sh TEST_TYPE [TEST_ARGS] +# +MY_DIR="$(dirname "$0")" + +usage() { + echo "Usage:" + echo + echo "runTest.sh TEST_TYPE -e binary -- [TEST_ARGS]" + echo "where TEST_TYPE can either be --plain, --simulation, --spe1 or --parallel-simulation=\$NUM_CORES (is '$TEST_TYPE')." +}; + +# this function clips the help message printed by an ewoms simulation +# to what is actually printed, throwing away all garbage which is +# printed before or after the "meat" +clipToHelpMessage() +{ + STATUS="not started" + while read CUR_LINE; do + if echo $CUR_LINE | grep -q "Usage: "; then + STATUS="started" + elif test "$STATUS" = "started" && echo $CUR_LINE | grep -q "^--"; then + STATUS="params" + elif test "$STATUS" = "params" && echo $CUR_LINE | grep -q "^[^-]"; then + STATUS="stopped" + fi + + if test "$STATUS" != "not started" && test "$STATUS" != "stopped"; then + echo "$CUR_LINE" + fi + done +} + +TEST_TYPE="$1" +if test "$2" != "-e"; then + echo "Expects second option to be -e" +fi +TEST_BINARY="$3" +TEST_ARGS="${@:5:100}" + +# make sure we have at least 2 parameters +if test "$#" -lt 2; then + echo "Wrong number of parameters" + echo + usage + exit 1 +fi + +#run the test +echo "######################" +echo "# Running test '$TEST_NAME'" +echo "######################" + + +RND="$(dd if=/dev/urandom bs=20 count=1 2> /dev/null | md5sum | cut -d" " -f1)" +case "$TEST_TYPE" in + "--simulation") + echo "executing \"$TEST_BINARY $TEST_ARGS\"" + "$TEST_BINARY" $TEST_ARGS | tee "test-$RND.log" + RET="${PIPESTATUS[0]}" + if test "$RET" != "0"; then + echo "Executing the binary failed!" + rm "test-$RND.log" + exit 1 + fi + + # compare the results + echo "######################" + echo "# Comparing results" + echo "######################" + echo "RND: '$RND'" + + SIM_NAME=$(grep "Applying the initial solution of the" "test-$RND.log" | sed "s/.*\"\(.*\)\".*/\1/" | head -n1) + NUM_TIMESTEPS=$(( $(grep "Time step [0-9]* done" "test-$RND.log" | wc -l))) + TEST_RESULT=$(printf "%s-%05i" "$SIM_NAME" "$NUM_TIMESTEPS") + TEST_RESULT=$(ls -- "$TEST_RESULT".*) + rm "test-$RND.log" + if ! test -r "$TEST_RESULT"; then + echo "File $TEST_RESULT does not exist or is not readable" + exit 1 + fi + + echo "Simulation name: '$SIM_NAME'" + echo "Number of timesteps: '$NUM_TIMESTEPS'" + + exit 0 + ;; + + "--parallel-program="*) + NUM_PROCS="${TEST_TYPE/--parallel-program=/}" + + echo "executing \"mpirun -np \"$NUM_PROCS\" $TEST_BINARY $TEST_ARGS\"" + mpirun -np "$NUM_PROCS" "$TEST_BINARY" $TEST_ARGS | tee "test-$RND.log" + RET="${PIPESTATUS[0]}" + if test "$RET" != "0"; then + echo "Executing the binary failed!" + rm "test-$RND.log" + exit 1 + fi + + exit 0 + ;; + + "--parallel-simulation="*) + NUM_PROCS="${TEST_TYPE/--parallel-simulation=/}" + + echo "executing \"mpirun -np \"$NUM_PROCS\" $TEST_BINARY $TEST_ARGS\"" + mpirun -np "$NUM_PROCS" "$TEST_BINARY" $TEST_ARGS | tee "test-$RND.log" + RET="${PIPESTATUS[0]}" + if test "$RET" != "0"; then + echo "Executing the binary failed!" + rm "test-$RND.log" + exit 1 + fi + + SIM_NAME=$(grep "Applying the initial solution of the" "test-$RND.log" | sed "s/.*\"\(.*\)\".*/\1/" | head -n1) + NUM_TIMESTEPS=$(( $(grep "Time step [0-9]* done" "test-$RND.log" | wc -l))) + rm "test-$RND.log" + + echo "Simulation name: '$SIM_NAME'" + echo "Number of timesteps: '$NUM_TIMESTEPS'" + for PROC_NUM in 0 1 2 3; do + REF_FILE=$(printf "s%04d-p%04d-%s" "$NUM_PROCS" "$PROC_NUM" "$SIM_NAME") + TEST_RESULT=$(printf "s%04d-p%04d-%s-%05i" "$NUM_PROCS" "$PROC_NUM" "$SIM_NAME" "$NUM_TIMESTEPS") + TEST_RESULT=$(ls -- "$TEST_RESULT".*) + if ! test -r "$TEST_RESULT"; then + echo "File $TEST_RESULT does not exist or is not readable" + exit 1 + fi + done + exit 0 + ;; + + "--spe1") + echo "Running the ebos simulator for SPE1CASE1" + + EBOS_COMMAND=$(find . -type f -perm -0111 -name "ebos") + + NUM_BINARIES=$(echo "$EBOS_COMMAND" | wc -w | tr -d '[:space:]') + if test "$NUM_BINARIES" != "1"; then + echo "No ebos executable found (is: $EBOS_COMMAND)" + echo + usage + exit 1 + fi + + ######### + # Run the simulator + if ! "$EBOS_COMMAND" "data/SPE1CASE1" ; then + exit 1 + fi + ######### + + # make sure that the simulator produced non-empty output files + for EXT in "INIT" "EGRID" "UNRST" "UNSMRY" "SMSPEC"; do + if ! test -s "SPE1CASE1.$EXT"; then + echo "The simulator did not produce a non-empty SPE1CASE1.$EXT file" + exit 1 + fi + done + + exit 0 + ;; + + "--restart") + echo "executing \"$TEST_BINARY $TEST_ARGS\"" + "$TEST_BINARY" $TEST_ARGS | tee "test-$RND.log" + RET="${PIPESTATUS[0]}" + if test "$RET" != "0"; then + echo "Executing the binary failed!" + rm "test-$RND.log" + exit 1 + fi + RESTART_TIME=$(grep "Serialize" "test-$RND.log" | tail -n 1 | sed "s/.*time=\([0-9.e+\-]*\).*/\1/") + rm "test-$RND.log" + + if ! "$TEST_BINARY" $TEST_ARGS --restart-time="$RESTART_TIME" --newton-write-convergence=true; then + echo "Restarting $TEST_BINARY failed" + exit 1; + fi + exit 0 + ;; + + "--parameters") + HELP_MSG="$($TEST_BINARY --help | clipToHelpMessage)" + if test "$(echo "$HELP_MSG" | grep -i usage)" == ''; then + echo "$TEST_BINARY did not accept '--help' parameter" + exit 1 + fi + HELP_MSG2="$($TEST_BINARY -h | clipToHelpMessage)" + if test "$HELP_MSG" != "$HELP_MSG2"; then + echo "Output of $TEST_BINARY different when passing '--help' and '-h'" + exit 1 + fi + + cat > "paramfile-$RND.ini" <&1 > /dev/null; then + echo "$TEST_BINARY does not correctly read a parameter file" + exit 1 + elif $TEST_BINARY --parameter-file="foobar.ini" 2>&1 > /dev/null; then + echo "$TEST_BINARY does not abort even though the specified parameter file does not exist" + exit 1 + elif $TEST_BINARY --undefined-param="bla" 2>&1 > /dev/null; then + echo "$TEST_BINARY does not signal an error even though an undefined command line parameter was passed" + exit 1 + elif $TEST_BINARY --foo 2>&1 > /dev/null; then + echo "$TEST_BINARY accepts flag parameters besides --help" + exit 1 + fi + + # test some invalid parameter names + for PARAM in foo -- -0foo --0foo --foo--bar --foo- -foo --foo-barĀ§=abc ; do + if $TEST_BINARY "$PARAM" --end-time=100 2>&1 > /dev/null; then + echo "$TEST_BINARY accepted invalid command line option '$PARAM'" + exit 1 + fi + done + echo "Test successful" + exit 0 + + ;; + + "--plain") + echo "executing \"$TEST_BINARY $TEST_ARGS\"" + if ! "$TEST_BINARY" $TEST_ARGS; then + exit 1 + fi + + exit 0 + ;; +esac

,"t!?s8 + 8_<%Hj*45s4-fil@s9tA8CL(@7S8]J_Ae@]YJb.:e5@9Z->eHFsfF(J?/\3FP(>FNUeXh3Q + *]hZuUcK/8=mD)L%+<[Ld>V,G2:*U%0`7>+3>p[j,D:.?d-=VEQ@ZDpIL+?`)/)m]_@br\G + mO[As/kHf,>rjfpB\jB@e`0B^cu%ZGl;HHggg/brj.85?XuU"#3_E,tj)oe1#,.j]G61hJME-@i8/C86CKa=I)Q]U2!'N[9h[JZ8M(2m3beJ6aDE-h^^0ECFe'qP/ + NNasS&H\DhsI"Hld8D4dSl_<7?'??.mdDsUh3@+4f,#SrMM + K[6!BO?<&7>.`XC9pjA]_#e8>fNj[e>6Y[JQW@5Qs%7,qKH(#`s"6oqb)Skr:7kCl7 + :'N-=BmglN_&#u8S#8%fUbc72GIq7mqR\\?Ndo;ISjGs^1B/Oj[Ji?beN7]^h&3QW+Gej/M + Q(iR`,I.jj!cuQ2f<^?l<9ZCRtT>2?PDB"COXEg-j?%Sd.EmB@I>u<$#a'ILAl)CYH45e6W + VaG&M!YkI$J[i>_&p*O>o$9Af"Go(h[sSB[5IJ`ntXihsKjiDB@bS0&Z.]ibh6GDGK>3>l&-rNfCB+C">Ub[d.Y- + Id_1Bn2_?PmaZAr`gcEYV=(5RC#+@$5+h*(1L=?+jCY,mL.CN(Uf_d*C/Z2=D + =#\?:#u#IAH&Uj$bYcB.:58;j\>C/?O(.ZpIl@-jaEJ,T!K7L>2R'%CJFb1`a36gDCO0Nm`-_aRZ4XgWn@.cR`pe>?hpdEp^a7r.B + &$KPf1`o='HqT8)s@LNnl9Gb9cdYDIM5MKlmqGgDCp^q0-X4H5[joWLl.m2,DU5l"1IFDmI + e2j\W!QhbQe@s=2`7n?qsP53Fe--#]Tme9?!T^SLJA8\.1^JJjdZ#ANr;C.6IBi0"H[W!=f + F`.@TVc_Ug"Zu_g=Z9R<:L*CE?1m%ME4qZYbqeeC3:Q?AFu.D5%_(@,Amc\oCV;fA7qk[.C + 2o#EC^paEcPlN5WP0/g2(_8AY&3=Y*p32=pH.VU[j"<),?3gM2B>Ta2IG`k/n(dmAhL5TlNJE + Uj?B;?]SU!0T\o4'eDjpZTS,SK3I[B8ITBgdCSc2^Wr_H45s6e:<9kkF+H7&Mdk>;"EV#"E + s4&8e$IoLoI!Hi#VFa!q1Q`P>%"n65k)8L/=Z7%RC(@7qomp(JWY176;JdojW2K0#SgkB#nSrIW(^;(-AV! + ud:oUseMWg4_;Y:1(85oif%4Z!4>3CGkeV&cZ:EfW:"n*KJT^[9IL6`U0P^s1,=8=%g6\nU + %rUQ^<'rrr[nh?l1jS3qF#??*GFGgjV=.PK>KJq.8DcFdgT?o6K]jImoo5YFkYs5+-H)e*A + l;h1iU^]RT;A]1o`sEP'uosJ.TmV:ErcKXu4SURlrVqQXp(96C9Gh/Gs49*a9>Pc%IcViNXo3)"*YQ)B#mIj`_5GakG=[4YYrrGBh+_4bAYZ)6bQ6i%A + #XRs=.1*lC?/5HpIV1_ncQ18Bl(6ujaru&h]Uhq*l&_[.aQ + -nrJ+%R"aKd+Smq,gZ>nk>Ar8WnIl.%fU(="2:tUoc2k'&9Z?PsnRM0?c3Q=#*ItSBit8'3 + s'Gbqjb#3).k\krI&)^/J1p!22+^`s?:.-DqnCda`-%E*R/]YKaC"h!#L(BGhuMm + l$D1gB];CI`j,Plhm__GJD_7bs+6[$\c7<@3(=X;iL#'1LuI!P+c542XtbfD=M?_QiC8C&J + )hI_6]pH_/l@dC=[#oOjdCR'R,tlM@@Ce2Y&Tro=giUGkIKGtT]d:8J"ktl5!>_%\mCMrBj + ,DDJRH'TWIelrSuo*[)W%W/hjX*8Y\[MBaft*nY/@Ua]2mUlmD`k>YjOT-jgsl7B&gNGqpt + SUn(Z_!^@3*a"iB`j=f-]=**3fmn_A%+`c>=6-ckk5;5A^5*7m!ko>ZhKcl$+*8^R+c+&C6 + $4]ba8omM$gfCiL,U"=Ib:N+sT*S5OcpO2dXBD:6pOlCWB+-GRY#![h(+9$JWB6jK=O6V&J + T<[\Y"uiCH+9)#MB)DT?NUhJS+49ZX#\k-.rL\SpJKi1`1&:gi_F0Lc8P2_Vs,[)DP6,(k8 + ]`n!C_RY*2Ac3SlFX4'e)RjYJH#O=.mBB/B+!a_@kZ1Mh,o%`ULOWQr%,5+b + DDP\el8?(1I]22):9GaVe#1%a>laLX>`;)FIL9c&&TZd1kH,W02'C<(],_!8niAUiN2tc\f + b2lG\sl(67q>4(jF)*a$:*cU$bXnXTE56ff5I_>B4s]_rr9jF@e(F[4@1"+rLHtj"Y#e76@4^Mf[?a_E>"a-`/nO+RQdY;/bLqG#jqPe%1EL"!M2a9&ABUCi.VMN/U + GM%seXD\.C!E'J2Pm/7?-Cr@:)3'.(XlQ&o:C"uQCU(jK28&eZ?OUIiB#[+7@pjAU/-X55% + isRT@<%s:FZN1BI*2BF;fb8WR06E<=>MM2Ma%f`n@;e'5UCN[C5od7N7m=DU`sD\/Ico.3" + #;Pb#WN8lXoOA!Nn?XiWTd4&"Gk20BVhdaNtM:>MDDVptF54%F-ah8Ma*oc;o%Wg*&=>KNk + *2Q/b7U@tU^f;c#clb3V9> + #!f++?A]=aYL$%[6#-O$i7o3&Q5m+po#kLcHmGu9[(=+pq[0Ep.ttE<(RV.8AusK@Zd@hTR + @?NVD'V?QVL6lsP>r4>IBBo3Z6mN5AJ39&:q!W,g`25oEFQK*Mg#$a*LOK`@f0]Fa-ncTW6 + BUkA8Jg(\iALZq&IaMeW.]T4eUI/G2quFOEZ.?a@%s2&O_SjaMf"<'R>##I$;7lS2SWiBp$t3BsL%*Y + 6_jGo_iZ(+;Ym)GkfU!:E=hQf"Ah=Icb&YkQAOH!Y&Sgq[*)N/J68@U_a11R[1&:]3EX"tQ + q&^a?Cm1#1/i:]&gF-Q:V+$k+<2-a;NFeF?QK\TZV%f7MAc0M=WD+tA)4r]sRU]&@Pr<]jk + ;ZDOFQt=b5B]'DT=^PKSuP/ncda)7eEmE5KcrEQE=G=ZEDbku6/P^Sh!qW + XHurZBf;J(;G5="(4I6,3-f63AYt76W78bD>nS`K[+_cGgDi2NDXGEVMU?1smKg@BcLbO-H_)tIPLg] + `L!MrL6[2*'s1Id6ooBA3HsNeb^>o->]JkH4,"p$$jB_!-W3Z,9P^.Y$Aai8-f(j;'3IZ4K + TM#oXrSj>CM)aZ;j4nF3ESa('q%k@LCQ + e8JWoG-^3(X>h!>oTSN*,h"OAk_IB0;s!LUXT(+L#0#TcJI_$=.dPBh#LIf'%^>6DZNtN.#Z-QfOHBgjjTk4g$*^#D0R!k!aR&?=m*= + &f#3tU$faEnN$c_2oY3??ik,$Amc-58;OO0nd!=b"T%F57J9_bS@k4'#JnP:"KY8ZsB*76. + @nnf<9d/q_YeFO7Zd`:G?OV79.d.@c.oPHo@$Kfj!9_C+i-EgJ#&Mj%(Zqm5/%@[,dDOc + fh#Eg_IC+0K$SHpu0,B/hl&=4Y=PpDMZaRVJ=Z;W!2AcVU96$.a\CiYm.:,%eET.+O<,# + &JQI,3t]Wq/C>aq%oCd>$PO0,-VmprOZbD(UG2#/ZcC![+eH6gOkghmCsMJ_YgOWgs\GLOdt6CRQ^,pNKL1t$`8rFk8 + Q.^)f>I1$b[MnB"VYQ!"KGXOj,gQBcV.(P->Z\%NtlCi(Gff+`i\fdubgfP<"hn3Ra6S':V + $=T/ooO3g6a.F0dpV[6#&244Cf($r%pD-Kn>X-5%mn%`]e-eB63:-'D7ke)Hor9g4_15,m^ + /;r9fFQ1U3>FbE + 3\68@c'#rY46.o3!"Am5=FI:;epG1MG]FCqR)o:XB7c&.#t5HNsdS;Um'l'e<3?02kk4;jBRGF[K*X70U$<4H*GQJDg`4$RR[W5]crgE\BY`pHB= + -C6aYslW:b18YdsYn0%8bRg*\\^1%P^qKQW/ + -t<#2$=QChnmUM?*gC=]:tp8_sE[qf$VCGrqCQG?O$9iUAr#:B+GcIZngBioL]UD*`02.9R + Pk?%BA=17R8f(3db=h0[@8r^31CGo2QPS-u8sUn%0;E"DV_U%Q9@UcpZSNHl75L'j\@,*LP=6B#fk + #>1r\:ST[UDq>CM&5K:JnqJFT49W"b)T[:mQ](=9o:)D56G$fYUAD\*iICJ*H(Z + OBIk@aL:IeJPHFUruRqg[r-K"56?q.#$0B9jELQ5jT<'N#gT`QUmb)P3`2^+hkcasV:jn*q9PRV^S[F6#\u"gF + HuYT\Y'HOq!?'sX5AZ.g)"q[aQJ-*i*XZ?n1/6%Q>r1`'@VZbbE54#OB,6*sig*Pla%>>_d + );mi)i(].LH!HGOEA[X[5[bGT,)elccDmt(7[Gc!>_;2U?Kt'4"\D)qP)i;+0O1@Pm(STZq + gS)1TU)(Rj\f7c_]+2WTa(*RV5gbH\D1W + $tF_n8/K4?^VF6,UNI`4ShcgeGa2CW1[)(*$Jrr*@9m?cIJ.*SbAKr,'IT?5B&La1Q]b0"\?-7Pqb$;(aTjf%tZcda''qo'ZI)4#(_p"Xkb[FT + giSD`ue]g5.c.p_^I,W@:g_F)CcFiDeh!>d2l-Ed_,8X/1`bC3'`hCb]g:-G3YbpleA4r1]hfa*=7r1ip"s-M+(P"OBD.s5p>9f_+*70`GP@ + _VpYUJq++s>qL\RL"ptq/.+-ZM-Qhd8Cq;7h@+/A[>Vu!$dqVSLR+1(iO\,2f0qqo0d+2e" + `a8DRQr85j!+4L0qfDV>rrSQN3+63?-kPh+>rnm2E+7oM>p]$l_s2"a9JO&.T=>V'2$&(_) + OP"Y=+VBQ!ifR?M?83=W7cTW`oR9(LO\"Pr=Yu^I.@!NeP1ark@540Dk*&rThJkQH:?Rhoo + _sD-Thss;=u@@`8Yo>LPhL7DTi%dglBPP\?EmY8;`#"Bsh.3QJ6PriG + lD5m[%.chXPm)?LO68p&>&D_-lc">W*Z9M8`roR+ujL+d'lWnsNak?SRtnB(MGGp4#B%d:i + 0@>rJQ +Q +showpage +%%Trailer +count op_count sub {pop} repeat +countdictstack dict_count sub {end} repeat +cairo_eps_state restore +%%EOF diff --git a/doc/handbook/EPS/dunedesign.eps b/doc/handbook/EPS/dunedesign.eps new file mode 100644 index 00000000000..e65354fa611 --- /dev/null +++ b/doc/handbook/EPS/dunedesign.eps @@ -0,0 +1,931 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: cairo 1.10.2 (http://cairographics.org) +%%CreationDate: Thu Jun 21 11:29:49 2012 +%%Pages: 1 +%%BoundingBox: 0 -1 370 203 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%EndComments +%%BeginProlog +/cairo_eps_state save def +/dict_count countdictstack def +/op_count count 1 sub def +userdict begin +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/pdfmark where { pop globaldict /?pdfmark /exec load put } + { globaldict begin /?pdfmark /pop load def /pdfmark + /cleartomark load def end } ifelse +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +%%EndProlog +%!FontType1-1.1 f-0-0 1.0 +11 dict begin +/FontName /f-0-0 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/FontBBox {0 -217 824 729 } readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 1 /uni0061 put +dup 2 /uni0070 put +dup 3 /uni006C put +dup 4 /uni0069 put +dup 5 /uni0063 put +dup 6 /uni0074 put +dup 7 /uni006F put +dup 8 /uni006E put +dup 9 /uni0073 put +dup 10 /uni0065 put +dup 11 /uni0078 put +dup 12 /uni0072 put +dup 13 /uni0020 put +dup 14 /uni0067 put +dup 15 /uni0064 put +dup 16 /uni006D put +dup 17 /uni0075 put +dup 18 /uni007A put +readonly def +currentdict end +currentfile eexec +f983ef0097ece636fb4a96c74d26ab84185f6dfa4a16a7a1c27bbe3f1156aea698df336d20b467 +b10e7f33846656653c5ac6962759d3056cbdb3190bac614b984bf5a132dc418192443014ba63de +800d392b6fea026574bb2535fd7bb5338f35bf15a88ea328fdaa49670c7852e3d060f3c5d6b07f +2ef6d0f22646c5d18e19a2ae3ee120390f6dd96f76dcf1e127de5e9299077a00c17c0d71e36e5b +9d5ec58fceda57739a6a4214d4b79d6c48d2784b60c320323c7acddddf34db833cac0cf109f799 +69d114a330d372e5c978a66acc84e3fe5557f6240856a013ffaa0199444e5c5036f775eba4a5c5 +8cde66cf604b9aca2178431127b8a1ff7ed633a65c04600af5f573483112251caf31d0c7c7e1a8 +decc942f4bccae86153efd43475966e5202fd074417312c7ef5f32a8a3c64c0d31e6cf05a8395b +2ec3c61df5842085ae3dcacc966e269bbdacf7237c70186a824529500005ed5b5b73afe50c2f0b +5a121454a496ea47e14b6787f14162b260cb91c49ed9b471f2b40180a9a92eaa9e7dca9bf280f0 +e1d53c47d8125db101643e7fa0fa3870458b1c6cd3d3acf90cc884cc3593f15ae6c9fc1fbebac1 +824d9f9824520c846901fa8051e658bd15f58b60a6fcd54b666ca1212d3e0f80252798f78a57c2 +502e12149e84672d18956d90f23b04e67d98f032acc84557fd8395fb01b83624fab41ebd5b80fe +211c6b3af6bef8a6cdc78d5a37f1be2b7f96f95f51c4ab3301d6037bf4beb7fd5733829289ae7f +892192eab10c172ae6d2c711ca395cca4c94a82a5318c4d3e6f1cd231eff43967e08ae53a44e37 +955ff6a1ef639cbe97451c6d457f272c62bc855f0bfec5223be6559b04935ce7d117b05dd7c82c +9358e68434fc4dd88b34800c0060e8df0224b5f495326188e4727070edf06104b9005eda4790ad +1ac793bd28fc607fcd8fb0777e49187363881eadabe6b090aa43b089252d3e58badf380cc8cd82 +3fb2cafa68acbb4a71327adf1891447add56fb1730748076ff2a28e59a8d0aa88002ff1e642048 +4a5b173d2f1a80ef8e9408163f50cbd4804d82a8c9fe69d41f146bd85d1f001a729f1973ffb1b9 +69ba898d50fa879ad2fe472a9910f94f2d852720422ea1ab2ef93bcb21a03fa3be7810ab3cb799 +07c5c6e4ef9c5fea7e05230472f7a5f26ae58b5f92acb881efa0c24ee81a7db41ffa3d47f73ab9 +32fcd737d76bf4a4dae8c14380d410042e8acb5011b2c6a382da7633bf7b515bd20b5f3a15f949 +a7c535bd506b2bb05284a3433b1a6289a911a67415e95b1d2cc9c27c8e4e65e43ef94371b42657 +bd767e5d26a6e5351c50d8a9722470cf0db64bf5cf5bdaa7070c6785297c59324f75337371425d +29fefc41b7973876cacefd511eb15fc59360792780823d00afb427535f9925211a0553b47ae20e +0cdeff1dee1fea484222c476cb70233e4f0d3658bd12a981e5ceb706280f4b2ed7d8d6f630a8d7 +fbcb3bd242f31ce80548a8d4a39cbf5e9b87cd9136afb3b32b0e0dc53d4c45e243ace71640f6b3 +9ed13457605806edb2ad5b5cc894aa8aeca482fe6c23c17787c412e7273dec43ba61d6e335fcc5 +dd76227e6cc0378bdba487821a7550ad8635ff2766f1f78bf993a32447b744d70192bf49bb126b +d4f5472a0b7d7e12ac9de080246182af4b1ba5e2e55d3af38fd85fb3785dddbfdccd5309446833 +f5b8e384b8feea425e21919f2b4cc5e9a0613e6ac7d8510d2ddd51b266006dc5c7268d9bb66d5c +553a2c5c1b19128088bd0dba462d42427acdd6b7070c35211799b146fc0b8df474eea6cac619a2 +4201f5e3b4c0ebf1195492e46b91d8aa348cee1b46d5e42b1e9e793b8be3aa42b55059a7af1393 +e7a10637c75f7fb0c6ed0af276044850bfe6046baff7cb7bb0ced50cb85a43f018615af02c5872 +4cdb27db6bbd81b3371665def0dd5c2dd4cd992c987de6000fe3b4c31413f5d6baa34bd9ce28d3 +e8c85f175704e10ac7f8c9c2068b7d7d52bb13f1cfb74fb186247198419d6e00b6d5d13fcb241c +9e6848a0f78693f8e7d2767daf4136288404eeb66ee530d49019c64e3fb85930b389c6144d35cb +fe58833a4c27af41585b5b5737fe783798f33d0fed960c4009c9ba3b87eb0a3130a43b822ca070 +a0b1c65ccb88ebe42d2c3fe3e1df54ac7d4c17a4f868be1b9f69ae83ce67ba202178c1532eb2ff +7934b597318ebfb174afca3561f7fee2840b7309fd39b4341967f584a9326975e6df200e3df424 +9178e08ced5f50df68bfd1dc610a29a87b8b4618eda2f335bf5917fdb23775ee6006d5c6ca347f +d9923f993f5cd894e2a62689accdba180e1d734641e560e761d1e5c4df99fd8a37efda8abc9d20 +eb30982380e4a81146191c13e1d8d70a7b180566d887c411a4c185b8781e31400554676ef69899 +e17e73950096eac1ad2a3726f3dd790470712b81db0d69b27636a71327e7ad401aeacc8fa00e43 +ccb1e50a090af5feaad4ed6c7e46a355cf84a8bfd8f35bbb19d6d5c874b46f9fa932ecaed3501d +353b8708523f5b10da00ae2fca7daa0d237adcc1a9ce90867ccea1a31876a6d3dec73bf061207f +edd7bd2a7675571e283114d22e27ebb78a56e588eda28d35d73282d452e61d2c082b22723131d0 +6c1a4aff0dbd3f0729ce32148b94b63f0ea1063f41177b0731a4e4989087032f21eef4b47eda0a +deb61fd53bc777125f0f006cc922c1a87ca41ff63d1a6902426888e381e43f4c195b813f7c1c42 +09088a3a1cf40d6ec9941d17807c5de5a1d822983555930ca1b0b7f9f678890b8de836f9b079f4 +b352eb7934697b04a05b67454d3e12c751674c2f93c5d00221ac7b8acf465d957c7feed8c73c33 +90fef823fd2b6513db81a108436f87bdd8a2c6221b827605ffbc7801d3f844c559ef8dc741ba58 +6f7398e1f005b60609c39913412b6cd87b95ac66adecc780324c115b2108fb45da39be32ae87bf +23ac353f1cead52535fdfa07fe94011a0b3bbb13be10b8a698be4e16271cd0ce324cd29bb1d25c +9d63f253d22d12b57440c32548586bf84c1edbf41a969db0dfa2162933d19d244440164ba4bd70 +cafd4d3be81c19caa2bbc7684cb5873ab2e3e2570e79f8b3f2666f8d40e8c30572ad06d6444f3e +a50a9ea6ffe1346e72a04758b8c787a9fd25dd55a9fb51bcb081a16cb44b43914ae8b749c6527b +e6a1df291c2d386aa4ba017628193c803a6d28949c0128e998dd643280138b9a8e9483aac32d5a +b8d7059abb22f711dd1282ecf3399f07b6d58e9a18de37195ae4a0106ca1576bad424ff70ba59f +6f8da327a7e951d8be7a6a616c13b968d6810590c6ee39753aa54ad8ea32331ef2edf4eadebc42 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%!FontType1-1.1 f-1-0 1.0 +11 dict begin +/FontName /f-1-0 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/FontBBox {14 -217 762 732 } readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 1 /uni006C put +dup 2 /uni006F put +dup 3 /uni0063 put +dup 4 /uni0061 put +dup 5 /uni0066 put +dup 6 /uni0075 put +dup 7 /uni006E put +dup 8 /uni0074 put +dup 9 /uni0069 put +dup 10 /uni0073 put +dup 11 /uni0067 put +dup 12 /uni0072 put +dup 13 /uni0064 put +dup 14 /uni0070 put +dup 15 /uni0065 put +dup 16 /uni0062 put +dup 17 /uni006D put +readonly def +currentdict end +currentfile eexec +f983ef0097ece636fb4a96c74d26ab84185f6dfa4a16a7a1c27bbe3f1156aea698df336d20b467 +b10e7f33846656653c5ac6962759d3056cbdb3190bac614b984bf5a132dc418192443014ba63de +800d392b6fea026574bb2535fd7bb5338f35bf15a88ea328fdaa49670c7852e3d060f3c5d6b07f +2ef6d0f22646c5d18e19a2ae3ee120390f6dd96f76dcf1e127de5e9299077a00c17c0d71e36e5b +9d5ec58fceda57739a6a4214d4b79d6c48d2784b60c320323c7acddddf34db833cac0cf109f799 +69d114a330d372e5c978a66acc84e3fe5557f6240856a013ffaa0199444e5c5036f775eba4a5c5 +8cde66cf604b9aca2178431127b8a1ff7ed633a65c04600af5f573483112251cacaf9a06b6a42b +0a4043938e24416c9f587b348e8ab9cce1d9dd4807509a1c1b397781329867de3ca9f303cfbffa +a130d475ee35ec2251bc9584bd0404a85f27405bc083207d3fffce07e03331d584eeb7bd31eee5 +25f9183a8a41a0127d7e779ebcfed7027d94228df77c6e9e5b6c942061c9a19b698a2641ce7bf1 +91281cf95e724ce492b1d21ef000c582487da7f6d539b8c411eebd07899b75f9bfa0809d462ab9 +cce2cb32c530cde4c42266720372cd239af22523ae47d06bf8cc3596bc1eef98a412c44b1222f9 +ca897cafa3bf6b64772ce9dd6f35806aa5b948ee4cac9456ae50c22c4abe9ca06a9bfb5450f31e +c16472c2a64eb6233dc84f6f40a88b7d3f7eebf236b4d6f4854ea206979c79dc5626164cb0a755 +c0814108344168ca6ff0998bdeddb3103855d7076b8e57fb68f7569267a11c79fc1096d1ab8b13 +df8ec589de12837c1f4809ab165de160449d694d6f42ed4ff2afbcd03f6b99ef40051072a173dc +983f7656eb7d84b6dc9d4b80665948141ca2ff9552489b219753772484f740f3037707e42a5cbb +288209290082c5f04a01d7c731dfd345961c2e1b7322579f5ab0bae6855810669ebbbc1fe995a4 +009443e1fee246f72bc4112ddbab213c1aaee0f050b1d439156931e5c2d41b381f9dbd47de627e +3b9dc8863e5dfe89eaa16c8f1f31c7e17fcfe3fc0bddae7520868c60dcf4e17cae3f56815c0ac3 +611010eb9e134112c87d43068619cd461972da89e5143afd979c826cc14d479c6f4123ea81b849 +8e92a4205f2e86c0de68682247bee6ff1a4ed0b7e08d06a2c96ebd837e73d613f568eac6eb18a3 +b89068df3cc092116ef03514ff236df49bd9d9502491921a4aa204e89ca06eb49856680f86e7a7 +0f9b17273fa93e7082ebfee9a25821dee4186926592e0914070c2dab258e547e3955e163377588 +8d2e2e70be7d7c47c7fc65bc4861d8695661ac3ef1abd5f46df6240307eab8ddd0d20f24e06fce +bddbfdff1500251740c8b0f3ad8960612749d5774fdf3912f91d0402ecd105a169ac0997003ec5 +cf0ace9ee4957975ffee48605df13c089139be632ce4b6ef62d2ff9e10f40cfd07be0f82c9cd72 +b1ef70082b86b969755b5f88a24cc3bee2abe90689adf1041b37cf0a6dc73dec3dcbfe6ea14099 +9c45bf11d7e0b65e32a8eea602f1be2ee0c60ea7489afdda08edd1e25d14a0bfe6f751a7d90139 +b8ecbdc685a8eb920fba25f075093ad0a7cfeb898a77914282e805f5264b9f469a6c9d76c4b5ce +77f3940810dadad376e412a8a385546f7dd84b53467ab0b45b5f98e378d70c97cc9e7a0ad30aa6 +e235bf486e99bf1608b7f6b651bb18c32cef510ff88431e592739be53c76d8adef804e8e275007 +500d25bdb5381ba8a74980f0a6ad2fb4a1120eeb299c276199f64ff9b45a1406d3915d690347f8 +31935b3227bf87b29c35f0e91d07fca44a08a195a7951a35b6d83245e97f3f4db48e3576f0a42b +706c0ef9a95e118c6af7274f33f21275eda22e3a3053fe5d7244004f71253dafb15fe53a1f29f6 +7c283df5ffc02877d98b4052461684a88e0bd0adaac3e18654bcbff930a57c99b2b48aa607d5bd +e600efe3f9e6d6626cc108f47d2fe14a91c1e10bc6fa92d3a711eb1066dc378b0475945a077965 +4e1c3eb9b5fcb4e3ed1e8225c47d281bff10ee6df45997b77365cf2b4209ba2280bfeb270eb84e +ac07a6fc476acc50197e057bf10e8f048afeda4ad8d82ef873abffd8c2e9dc3901d498f65dddf1 +92d9e87a36dc5d76a3be7883f9237be83b1e7a5aca3444a8a70881926750c054b88827795542b6 +924a1c7fa639434334b8c23b409b7b37d22bb471ec58a3d1ec9bb56b240b75200e53bc35ec277a +b8ceb37cf1872c651dc541075f606819f3c162547c332b04e669ec2f414b2af8994becc206131e +1bacfb99edba3a1f35404393ae15fe866f5836a00a95ea57ee2a4a55d55380a03ef12530d6e3ad +d0bbbdf4cd0287a3c7af7f378396591c7eb2136724ce97b4c11f2dcff55a0052c08e9165eec865 +ed805dc5968dbd605591067d9db20a9da3359ef101aa869cc40ea683beb2965ce9c5d17655cde3 +7b818483c9e9bea8e6b97a927750d73308db4eb5ece596316c1fcc57968fb3b4240ec5c86022a2 +2caa207be1c96404ca7edc735a574122678ac58404d47f29c66a6f5df1fc619ad0cd2c0aa32cec +391bdec3bce8528e95604dbf43b0954ccb486d81c47a3e29590d0e967e0ef1fee344d2192f9c94 +ec14034796295a63458308e8cfb67ecd40fb5dca4c9895eadf191fd37c59ef5f44f78cff4c3d79 +1d6a8b21197477e1a5138ddc12bad7635d114e02e038adbe9ab2a1dbf6155270660d8de8694cf1 +08bca1daef26b9973e31e25bf81e5f28eee5d07ff87fccafe30cc51754e5a1fbf03501cd31aa9a +a223faf9c031017ae45a5c1de674953f97e60c92a792c706acbd6a60320e1f6b8b2020a6ad53df +e832fdcce63cb15a4fad891db8a844731829dc22d630e6199c1d64043f7fd32908ae7c5ad0a636 +3875ff6eb4e87d7c473b9eaf00d3b193a895cc62c31633807cf1211ab8332a22f720e442bf32a4 +af2b647694d527a5aa02cc4e0f119c4bb7d93abb7ad6259943d058ce3f340591a3bf89a2e8dc51 +4d6a9fcb00e577c7027553f44806b01d5d3349b6f868ec6d9488f3b458b9d3b7cb3786f78bec53 +2569a306ea15c856c36017297a8f0a86c6fb6e7a390cbbd5b398e1fa4a8f29aeb455c491c30257 +10893969d559ca61b7a19494161f163d0349acaf3a272528e4fe7e75674b9a720fca53cd02d1c6 +62f766cd5df5fcc54af862b3a313673662751c7f10ed1af61f38802ef6c072cb040aabd2c271be +e864597dc0707748eab4b4ddc2a7874bbf61488f465a4095aedaf6f8d692f0996d29edaca0e439 +f34a9d72d030b1635733d05ac333649d4e24f6328b4efb4578ac1c2a8b9a15009774aaaf66a60000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%%Page: 1 1 +%%BeginPageSetup +%%PageBoundingBox: 0 -1 370 203 +%%EndPageSetup +q 0 -1 370 204 rectclip q +0 202.46 370 -203 re W n +0.952941 g +13.602 170.405 m 355.996 170.405 l 362.645 170.405 368 165.053 368 +158.401 c 368 13.608 l 368 6.96 362.645 1.604 355.996 1.604 c 13.602 +1.604 l 6.953 1.604 1.598 6.96 1.598 13.608 c 1.598 158.401 l 1.598 +165.053 6.953 170.405 13.602 170.405 c h +13.602 170.405 m f* +0.792157 g +3.2 w +0 J +0 j +[] 0.0 d +4 M q 1 0 0 -1 0 202.459579 cm +13.602 32.055 m 355.996 32.055 l 362.645 32.055 368 37.406 368 44.059 c +368 188.852 l 368 195.5 362.645 200.855 355.996 200.855 c 13.602 +200.855 l 6.953 200.855 1.598 195.5 1.598 188.852 c 1.598 44.059 l +1.598 37.406 6.953 32.055 13.602 32.055 c h +13.602 32.055 m S Q +0.909804 0.556863 0.247059 rg +37.422 153.022 m 33.551 152.014 28.281 159.635 26.195 163.042 c 23.191 +167.956 17.285 177.87 18.812 183.424 c 19.875 187.276 29.109 188.03 +33.105 188.131 c 38.859 188.28 50.402 188.436 54.445 184.338 c 57.254 +181.491 53.289 173.116 51.379 169.604 c 48.629 164.546 42.992 154.475 +37.422 153.022 c h +37.422 153.022 m f +0.721569 0.376471 0.0588235 rg +q 1 0 0 -1 0 202.459579 cm +37.422 49.438 m 33.551 50.445 28.281 42.824 26.195 39.418 c 23.191 +34.504 17.285 24.59 18.812 19.035 c 19.875 15.184 29.109 14.43 33.105 +14.328 c 38.859 14.18 50.402 14.023 54.445 18.121 c 57.254 20.969 +53.289 29.344 51.379 32.855 c 48.629 37.914 42.992 47.984 37.422 49.438 +c h +37.422 49.438 m S Q +0.909804 0.556863 0.247059 rg +168.75 153.022 m 164.883 152.014 159.613 159.635 157.527 163.042 c +154.52 167.956 148.613 177.87 150.141 183.424 c 151.203 187.276 160.438 +188.03 164.434 188.131 c 170.191 188.28 181.73 188.436 185.773 184.338 +c 188.582 181.491 184.617 173.116 182.707 169.604 c 179.957 164.546 +174.324 154.475 168.75 153.022 c h +168.75 153.022 m f +0.721569 0.376471 0.0588235 rg +q 1 0 0 -1 0 202.459579 cm +168.75 49.438 m 164.883 50.445 159.613 42.824 157.527 39.418 c 154.52 +34.504 148.613 24.59 150.141 19.035 c 151.203 15.184 160.438 14.43 +164.434 14.328 c 170.191 14.18 181.73 14.023 185.773 18.121 c 188.582 +20.969 184.617 29.344 182.707 32.855 c 179.957 37.914 174.324 47.984 +168.75 49.438 c h +168.75 49.438 m S Q +0.909804 0.556863 0.247059 rg +124.973 153.022 m 121.105 152.014 115.836 159.635 113.75 163.042 c +110.742 167.956 104.836 177.87 106.367 183.424 c 107.426 187.276 116.66 +188.03 120.656 188.131 c 126.414 188.28 137.953 188.436 142 184.338 c +144.805 181.491 140.84 173.116 138.93 169.604 c 136.18 164.546 130.547 +154.475 124.973 153.022 c h +124.973 153.022 m f +0.721569 0.376471 0.0588235 rg +q 1 0 0 -1 0 202.459579 cm +124.973 49.438 m 121.105 50.445 115.836 42.824 113.75 39.418 c 110.742 +34.504 104.836 24.59 106.367 19.035 c 107.426 15.184 116.66 14.43 +120.656 14.328 c 126.414 14.18 137.953 14.023 142 18.121 c 144.805 +20.969 140.84 29.344 138.93 32.855 c 136.18 37.914 130.547 47.984 +124.973 49.438 c h +124.973 49.438 m S Q +0.909804 0.556863 0.247059 rg +81.195 153.022 m 77.328 152.014 72.059 159.635 69.973 163.042 c 66.965 +167.956 61.062 177.87 62.59 183.424 c 63.652 187.276 72.887 188.03 +76.883 188.131 c 82.637 188.28 94.18 188.436 98.223 184.338 c 101.031 +181.491 97.066 173.116 95.156 169.604 c 92.402 164.546 86.77 154.475 +81.195 153.022 c h +81.195 153.022 m f +0.721569 0.376471 0.0588235 rg +q 1 0 0 -1 0 202.459579 cm +81.195 49.438 m 77.328 50.445 72.059 42.824 69.973 39.418 c 66.965 +34.504 61.062 24.59 62.59 19.035 c 63.652 15.184 72.887 14.43 76.883 +14.328 c 82.637 14.18 94.18 14.023 98.223 18.121 c 101.031 20.969 +97.066 29.344 95.156 32.855 c 92.402 37.914 86.77 47.984 81.195 49.438 +c h +81.195 49.438 m S Q +0.909804 0.556863 0.247059 rg +202.836 170.561 m 202.836 168.983 201.559 167.702 199.98 167.702 c +198.402 167.702 197.121 168.983 197.121 170.561 c 197.121 172.139 +198.402 173.417 199.98 173.417 c 201.559 173.417 202.836 172.139 +202.836 170.561 c h +202.836 170.561 m f +0.721569 0.376471 0.0588235 rg +q 1 0 0 -1 0 202.459579 cm +202.836 31.898 m 202.836 33.477 201.559 34.758 199.98 34.758 c 198.402 +34.758 197.121 33.477 197.121 31.898 c 197.121 30.32 198.402 29.043 +199.98 29.043 c 201.559 29.043 202.836 30.32 202.836 31.898 c h +202.836 31.898 m S Q +0.909804 0.556863 0.247059 rg +213.406 170.561 m 213.406 168.983 212.129 167.702 210.551 167.702 c +208.973 167.702 207.691 168.983 207.691 170.561 c 207.691 172.139 +208.973 173.417 210.551 173.417 c 212.129 173.417 213.406 172.139 +213.406 170.561 c h +213.406 170.561 m f +0.721569 0.376471 0.0588235 rg +q 1 0 0 -1 0 202.459579 cm +213.406 31.898 m 213.406 33.477 212.129 34.758 210.551 34.758 c 208.973 +34.758 207.691 33.477 207.691 31.898 c 207.691 30.32 208.973 29.043 +210.551 29.043 c 212.129 29.043 213.406 30.32 213.406 31.898 c h +213.406 31.898 m S Q +0.909804 0.556863 0.247059 rg +223.98 170.561 m 223.98 168.983 222.699 167.702 221.121 167.702 c +219.543 167.702 218.266 168.983 218.266 170.561 c 218.266 172.139 +219.543 173.417 221.121 173.417 c 222.699 173.417 223.98 172.139 223.98 +170.561 c h +223.98 170.561 m f +0.721569 0.376471 0.0588235 rg +q 1 0 0 -1 0 202.459579 cm +223.98 31.898 m 223.98 33.477 222.699 34.758 221.121 34.758 c 219.543 +34.758 218.266 33.477 218.266 31.898 c 218.266 30.32 219.543 29.043 +221.121 29.043 c 222.699 29.043 223.98 30.32 223.98 31.898 c h +223.98 31.898 m S Q +0.196078 g +BT +11.2 0 0 11.2 17.560657 194.294779 Tm +/f-0-0 1 Tf +[<01>-1<02>6<02>15<03>16<04>21<05>9<01>18<06>15<04>20<07>29<08>15<09>]TJ +ET +0.905882 0.952941 1 rg +176.797 145.604 m 236 145.604 l 240.43 145.604 244 142.038 244 137.604 +c 244 89.604 l 244 85.174 240.43 81.604 236 81.604 c 176.797 81.604 l +172.367 81.604 168.797 85.174 168.797 89.604 c 168.797 137.604 l +168.797 142.038 172.367 145.604 176.797 145.604 c h +176.797 145.604 m f* +0.690196 0.737255 0.894118 rg +q 1 0 0 -1 0 202.459579 cm +176.797 56.855 m 236 56.855 l 240.43 56.855 244 60.422 244 64.855 c 244 +112.855 l 244 117.285 240.43 120.855 236 120.855 c 176.797 120.855 l +172.367 120.855 168.797 117.285 168.797 112.855 c 168.797 64.855 l +168.797 60.422 172.367 56.855 176.797 56.855 c h +176.797 56.855 m S Q +0.196078 g +BT +11.2 0 0 11.2 175.998181 129.605192 Tm +/f-0-0 1 Tf +[<0a>25<0b>9<06>9<0c>19<010d0e>10<0c04>16<0f>19<09>]TJ +ET +0.611765 0.678431 0.847059 rg +17.555 73.604 m 352 73.604 l 356.43 73.604 360 70.038 360 65.604 c 360 +17.604 l 360 13.174 356.43 9.604 352 9.604 c 17.555 9.604 l 13.121 +9.604 9.555 13.174 9.555 17.604 c 9.555 65.604 l 9.555 70.038 13.121 +73.604 17.555 73.604 c h +17.555 73.604 m f* +0.266667 0.411765 0.721569 rg +q 1 0 0 -1 0 202.459579 cm +17.555 128.855 m 352 128.855 l 356.43 128.855 360 132.422 360 136.855 c +360 184.855 l 360 189.285 356.43 192.855 352 192.855 c 17.555 192.855 l +13.121 192.855 9.555 189.285 9.555 184.855 c 9.555 136.855 l 9.555 +132.422 13.121 128.855 17.555 128.855 c h +17.555 128.855 m S Q +0.34902 0.454902 0.709804 rg +230.398 49.604 m 308.84 49.604 l 313.273 49.604 316.84 46.038 316.84 +41.604 c 316.84 25.604 l 316.84 21.174 313.273 17.604 308.84 17.604 c +230.398 17.604 l 225.965 17.604 222.398 21.174 222.398 25.604 c 222.398 +41.604 l 222.398 46.038 225.965 49.604 230.398 49.604 c h +230.398 49.604 m f* +0.215686 0.294118 0.490196 rg +q 1 0 0 -1 0 202.459579 cm +230.398 152.855 m 308.84 152.855 l 313.273 152.855 316.84 156.422 +316.84 160.855 c 316.84 176.855 l 316.84 181.285 313.273 184.855 308.84 +184.855 c 230.398 184.855 l 225.965 184.855 222.398 181.285 222.398 +176.855 c 222.398 160.855 l 222.398 156.422 225.965 152.855 230.398 +152.855 c h +230.398 152.855 m S Q +0 g +BT +11.2 0 0 11.2 235.879565 29.63477 Tm +/f-1-0 1 Tf +[<01>-1<020304>-1<01>-1<05>7<06>-1<07>19<0308>1<09>-1<02>19<07>19<0a>]TJ +ET +0.16 w +q 1 0 0 -1 0 202.459579 cm +237.582 164.652 m 236.645 164.652 l 236.645 172.824 l 237.582 172.824 l +h +241.422 166.793 m 239.781 166.793 238.781 167.965 238.781 169.934 c +238.781 171.902 239.766 173.074 241.438 173.074 c 243.078 173.074 +244.094 171.902 244.094 169.98 c 244.094 167.949 243.109 166.793 +241.422 166.793 c h +241.438 167.652 m 242.484 167.652 243.109 168.512 243.109 169.965 c +243.109 171.34 242.469 172.215 241.438 172.215 c 240.391 172.215 239.75 +171.355 239.75 169.934 c 239.75 168.527 240.391 167.652 241.438 167.652 +c h +249.891 168.918 m 249.781 167.527 248.906 166.793 247.562 166.793 c +245.984 166.793 244.953 168.043 244.953 169.996 c 244.953 171.887 +245.969 173.074 247.562 173.074 c 248.953 173.074 249.844 172.246 +249.953 170.809 c 249.016 170.809 l 248.859 171.746 248.375 172.215 +247.578 172.215 c 246.547 172.215 245.938 171.387 245.938 169.996 c +245.938 168.527 246.531 167.652 247.562 167.652 c 248.344 167.652 +248.828 168.105 248.938 168.918 c h +250.941 168.684 m 251.879 168.684 l 251.957 167.98 252.379 167.652 +253.254 167.652 c 254.098 167.652 254.566 167.965 254.566 168.527 c +254.566 168.762 l 254.566 169.168 254.332 169.324 253.598 169.418 c +251.879 169.637 250.676 169.777 250.676 171.34 c 250.676 172.402 251.41 +173.074 252.598 173.074 c 253.348 173.074 253.941 172.824 254.598 +172.215 c 254.66 172.809 254.957 173.074 255.566 173.074 c 255.754 +173.074 255.895 173.059 256.207 172.98 c 256.207 172.277 l 256.098 +172.293 256.051 172.293 256.004 172.293 c 255.676 172.293 255.488 +172.137 255.488 171.84 c 255.488 168.387 l 255.488 167.34 254.738 +166.793 253.285 166.793 c 252.238 166.793 250.988 167.105 250.941 +168.684 c h +252.801 172.262 m 252.082 172.262 251.645 171.918 251.645 171.324 c +251.645 170.027 253.613 170.355 254.566 169.918 c 254.566 170.98 l +254.566 171.543 253.91 172.262 252.801 172.262 c h +258.145 164.652 m 257.207 164.652 l 257.207 172.824 l 258.145 172.824 l +h +261.828 166.949 m 260.859 166.949 l 260.859 166.043 l 260.859 165.637 +261.078 165.449 261.5 165.449 c 261.578 165.449 261.609 165.449 261.828 +165.449 c 261.828 164.684 l 261.609 164.637 261.484 164.621 261.297 +164.621 c 260.438 164.621 259.922 165.121 259.922 165.949 c 259.922 +166.949 l 259.141 166.949 l 259.141 167.715 l 259.922 167.715 l 259.922 +172.824 l 260.859 172.824 l 260.859 167.715 l 261.828 167.715 l h +267.383 172.824 m 267.383 166.949 l 266.445 166.949 l 266.445 170.277 l +266.445 171.48 265.82 172.262 264.852 172.262 c 264.102 172.262 263.633 +171.809 263.633 171.105 c 263.633 166.949 l 262.711 166.949 l 262.711 +171.48 l 262.711 172.449 263.43 173.074 264.57 173.074 c 265.445 +173.074 265.992 172.777 266.539 172.012 c 266.539 172.824 l h +268.992 166.949 m 268.992 172.824 l 269.93 172.824 l 269.93 169.59 l +269.93 168.387 270.57 167.605 271.523 167.605 c 272.273 167.605 272.742 +168.059 272.742 168.762 c 272.742 172.824 l 273.664 172.824 l 273.664 +168.387 l 273.664 167.418 272.945 166.793 271.805 166.793 c 270.93 +166.793 270.367 167.121 269.852 167.934 c 269.852 166.949 l h +279.508 168.918 m 279.398 167.527 278.523 166.793 277.18 166.793 c +275.602 166.793 274.57 168.043 274.57 169.996 c 274.57 171.887 275.586 +173.074 277.18 173.074 c 278.57 173.074 279.461 172.246 279.57 170.809 +c 278.633 170.809 l 278.477 171.746 277.992 172.215 277.195 172.215 c +276.164 172.215 275.555 171.387 275.555 169.996 c 275.555 168.527 +276.148 167.652 277.18 167.652 c 277.961 167.652 278.445 168.105 +278.555 168.918 c h +282.672 166.949 m 281.703 166.949 l 281.703 165.34 l 280.781 165.34 l +280.781 166.949 l 279.984 166.949 l 279.984 167.715 l 280.781 167.715 l +280.781 172.152 l 280.781 172.746 281.188 173.074 281.906 173.074 c +282.141 173.074 282.359 173.059 282.672 172.996 c 282.672 172.215 l +282.547 172.246 282.406 172.262 282.219 172.262 c 281.828 172.262 +281.703 172.152 281.703 171.73 c 281.703 167.715 l 282.672 167.715 l h +284.621 166.949 m 283.684 166.949 l 283.684 172.824 l 284.621 172.824 l +h +284.621 164.652 m 283.668 164.652 l 283.668 165.84 l 284.621 165.84 l h +288.473 166.793 m 286.832 166.793 285.832 167.965 285.832 169.934 c +285.832 171.902 286.816 173.074 288.488 173.074 c 290.129 173.074 +291.145 171.902 291.145 169.98 c 291.145 167.949 290.16 166.793 288.473 +166.793 c h +288.488 167.652 m 289.535 167.652 290.16 168.512 290.16 169.965 c +290.16 171.34 289.52 172.215 288.488 172.215 c 287.441 172.215 286.801 +171.355 286.801 169.934 c 286.801 168.527 287.441 167.652 288.488 +167.652 c h +292.223 166.949 m 292.223 172.824 l 293.16 172.824 l 293.16 169.59 l +293.16 168.387 293.801 167.605 294.754 167.605 c 295.504 167.605 +295.973 168.059 295.973 168.762 c 295.973 172.824 l 296.895 172.824 l +296.895 168.387 l 296.895 167.418 296.176 166.793 295.035 166.793 c +294.16 166.793 293.598 167.121 293.082 167.934 c 293.082 166.949 l h +298.816 171.074 m 297.832 171.074 l 297.879 172.434 298.645 173.074 +300.176 173.074 c 301.66 173.074 302.598 172.355 302.598 171.215 c +302.598 170.355 302.113 169.871 300.941 169.59 c 300.051 169.371 l +299.285 169.199 298.957 168.949 298.957 168.527 c 298.957 167.996 +299.441 167.652 300.207 167.652 c 300.957 167.652 301.363 167.98 +301.379 168.59 c 302.363 168.59 l 302.348 167.434 301.598 166.793 +300.238 166.793 c 298.863 166.793 297.988 167.496 297.988 168.574 c +297.988 169.496 298.457 169.934 299.848 170.277 c 300.723 170.48 l +301.363 170.637 301.629 170.871 301.629 171.293 c 301.629 171.855 +301.082 172.215 300.254 172.215 c 299.113 172.215 298.895 171.652 +298.816 171.074 c h +298.816 171.074 m S Q +0.34902 0.454902 0.709804 rg +128 49.604 m 206.441 49.604 l 210.875 49.604 214.441 46.038 214.441 +41.604 c 214.441 25.604 l 214.441 21.174 210.875 17.604 206.441 17.604 +c 128 17.604 l 123.566 17.604 120 21.174 120 25.604 c 120 41.604 l 120 +46.038 123.566 49.604 128 49.604 c h +128 49.604 m f* +0.215686 0.294118 0.490196 rg +3.2 w +q 1 0 0 -1 0 202.459579 cm +128 152.855 m 206.441 152.855 l 210.875 152.855 214.441 156.422 214.441 +160.855 c 214.441 176.855 l 214.441 181.285 210.875 184.855 206.441 +184.855 c 128 184.855 l 123.566 184.855 120 181.285 120 176.855 c 120 +160.855 l 120 156.422 123.566 152.855 128 152.855 c h +128 152.855 m S Q +0 g +BT +11.2 0 0 11.2 160.508398 29.651567 Tm +/f-1-0 1 Tf +[<09>-1<0a>20<0801>]TJ +ET +0.16 w +q 1 0 0 -1 0 202.459579 cm +162.195 166.934 m 161.258 166.934 l 161.258 172.809 l 162.195 172.809 l +h +162.195 164.637 m 161.242 164.637 l 161.242 165.824 l 162.195 165.824 l +h +164.363 171.059 m 163.379 171.059 l 163.426 172.418 164.191 173.059 +165.723 173.059 c 167.207 173.059 168.145 172.34 168.145 171.199 c +168.145 170.34 167.66 169.855 166.488 169.574 c 165.598 169.355 l +164.832 169.184 164.504 168.934 164.504 168.512 c 164.504 167.98 +164.988 167.637 165.754 167.637 c 166.504 167.637 166.91 167.965 +166.926 168.574 c 167.91 168.574 l 167.895 167.418 167.145 166.777 +165.785 166.777 c 164.41 166.777 163.535 167.48 163.535 168.559 c +163.535 169.48 164.004 169.918 165.395 170.262 c 166.27 170.465 l +166.91 170.621 167.176 170.855 167.176 171.277 c 167.176 171.84 166.629 +172.199 165.801 172.199 c 164.66 172.199 164.441 171.637 164.363 +171.059 c h +171.227 166.934 m 170.258 166.934 l 170.258 165.324 l 169.336 165.324 l +169.336 166.934 l 168.539 166.934 l 168.539 167.699 l 169.336 167.699 l +169.336 172.137 l 169.336 172.73 169.742 173.059 170.461 173.059 c +170.695 173.059 170.914 173.043 171.227 172.98 c 171.227 172.199 l +171.102 172.23 170.961 172.246 170.773 172.246 c 170.383 172.246 +170.258 172.137 170.258 171.715 c 170.258 167.699 l 171.227 167.699 l h +173.191 164.637 m 172.254 164.637 l 172.254 172.809 l 173.191 172.809 l +h +173.191 164.637 m S Q +0.34902 0.454902 0.709804 rg +25.555 49.604 m 104 49.604 l 108.43 49.604 112 46.038 112 41.604 c 112 +25.604 l 112 21.174 108.43 17.604 104 17.604 c 25.555 17.604 l 21.121 +17.604 17.555 21.174 17.555 25.604 c 17.555 41.604 l 17.555 46.038 +21.121 49.604 25.555 49.604 c h +25.555 49.604 m f* +0.215686 0.294118 0.490196 rg +3.2 w +q 1 0 0 -1 0 202.459579 cm +25.555 152.855 m 104 152.855 l 108.43 152.855 112 156.422 112 160.855 c +112 176.855 l 112 181.285 108.43 184.855 104 184.855 c 25.555 184.855 l +21.121 184.855 17.555 181.285 17.555 176.855 c 17.555 160.855 l 17.555 +156.422 21.121 152.855 25.555 152.855 c h +25.555 152.855 m S Q +0 g +BT +11.2 0 0 11.2 55.607773 30.743583 Tm +/f-1-0 1 Tf +[<0b>-1<0c>-1<090d>]TJ +ET +0.16 w +q 1 0 0 -1 0 202.459579 cm +58.359 171.965 m 59.094 171.965 59.609 171.652 60.141 170.918 c 60.141 +171.215 l 60.141 172.559 59.828 173.371 58.5 173.371 c 58.203 173.371 +57.234 173.34 57.078 172.387 c 56.125 172.387 l 56.219 173.496 57.094 +174.152 58.469 174.152 c 60.703 174.152 61.094 172.793 61.094 170.746 c +61.094 165.84 l 60.219 165.84 l 60.219 166.699 l 59.75 165.996 59.188 +165.684 58.438 165.684 c 56.938 165.684 55.938 166.98 55.938 168.887 c +55.938 170.793 57.016 171.965 58.359 171.965 c h +58.531 166.543 m 59.547 166.543 60.141 167.387 60.141 168.855 c 60.141 +170.262 59.531 171.105 58.547 171.105 c 57.531 171.105 56.906 170.246 +56.906 168.824 c 56.906 167.418 57.531 166.543 58.531 166.543 c h +65.438 166.668 m 65.438 165.715 l 65.281 165.684 65.203 165.684 65.078 +165.684 c 64.469 165.684 64.016 166.027 63.484 166.902 c 63.484 165.84 +l 62.609 165.84 l 62.609 171.715 l 63.562 171.715 l 63.562 168.668 l +63.562 167.027 64.312 166.684 65.438 166.668 c h +67.27 165.84 m 66.332 165.84 l 66.332 171.715 l 67.27 171.715 l h +67.27 163.543 m 66.316 163.543 l 66.316 164.73 l 67.27 164.73 l h +73.625 163.543 m 72.688 163.543 l 72.688 166.59 l 72.297 165.996 71.672 +165.684 70.891 165.684 c 69.359 165.684 68.375 166.902 68.375 168.762 c +68.375 170.746 69.344 171.965 70.922 171.965 c 71.734 171.965 72.297 +171.668 72.797 170.949 c 72.797 171.715 l 73.625 171.715 l h +71.047 166.543 m 72.062 166.543 72.688 167.449 72.688 168.84 c 72.688 +170.199 72.047 171.105 71.062 171.105 c 70.031 171.105 69.344 170.184 +69.344 168.824 c 69.344 167.465 70.031 166.543 71.047 166.543 c h +71.047 166.543 m S Q +0.196078 g +BT +11.2 0 0 11.2 17.554914 57.605192 Tm +/f-0-0 1 Tf +[<05>5<07>15<0c>1<0a>-1<0d>1<10>6<07>5<0f>10<11>19<03>10<0a>9<09>]TJ +ET +0.905882 0.952941 1 rg +17.598 145.604 m 153.598 145.604 l 158.031 145.604 161.598 142.038 +161.598 137.604 c 161.598 89.604 l 161.598 85.174 158.031 81.604 +153.598 81.604 c 17.598 81.604 l 13.168 81.604 9.598 85.174 9.598 +89.604 c 9.598 137.604 l 9.598 142.038 13.168 145.604 17.598 145.604 c +h +17.598 145.604 m f* +0.690196 0.737255 0.894118 rg +3.2 w +q 1 0 0 -1 0 202.459579 cm +17.598 56.855 m 153.598 56.855 l 158.031 56.855 161.598 60.422 161.598 +64.855 c 161.598 112.855 l 161.598 117.285 158.031 120.855 153.598 +120.855 c 17.598 120.855 l 13.168 120.855 9.598 117.285 9.598 112.855 c +9.598 64.855 l 9.598 60.422 13.168 56.855 17.598 56.855 c h +17.598 56.855 m S Q +0.784314 0.835294 1 rg +25.598 121.604 m 57.598 121.604 l 62.031 121.604 65.598 118.038 65.598 +113.604 c 65.598 97.604 l 65.598 93.174 62.031 89.604 57.598 89.604 c +25.598 89.604 l 21.168 89.604 17.598 93.174 17.598 97.604 c 17.598 +113.604 l 17.598 118.038 21.168 121.604 25.598 121.604 c h +25.598 121.604 m f* +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +25.598 80.855 m 57.598 80.855 l 62.031 80.855 65.598 84.422 65.598 +88.855 c 65.598 104.855 l 65.598 109.285 62.031 112.855 57.598 112.855 +c 25.598 112.855 l 21.168 112.855 17.598 109.285 17.598 104.855 c +17.598 88.855 l 17.598 84.422 21.168 80.855 25.598 80.855 c h +25.598 80.855 m S Q +0 g +BT +11.2 0 0 11.2 24.617719 102.515336 Tm +/f-1-0 1 Tf +[<0e>-1<0d>9<0f01>-1<04>-1<10>]TJ +ET +0.16 w +q 1 0 0 -1 0 202.459579 cm +25.227 102.383 m 26.164 102.383 l 26.164 99.336 l 26.648 99.93 27.211 +100.195 27.961 100.195 c 29.477 100.195 30.477 98.977 30.477 97.117 c +30.477 95.133 29.508 93.914 27.961 93.914 c 27.164 93.914 26.523 94.258 +26.086 94.961 c 26.086 94.07 l 25.227 94.07 l h +27.805 94.773 m 28.836 94.773 29.508 95.695 29.508 97.086 c 29.508 +98.414 28.82 99.336 27.805 99.336 c 26.82 99.336 26.164 98.43 26.164 +97.055 c 26.164 95.68 26.82 94.773 27.805 94.773 c h +36.398 91.773 m 35.461 91.773 l 35.461 94.82 l 35.07 94.227 34.445 +93.914 33.664 93.914 c 32.133 93.914 31.148 95.133 31.148 96.992 c +31.148 98.977 32.117 100.195 33.695 100.195 c 34.508 100.195 35.07 +99.898 35.57 99.18 c 35.57 99.945 l 36.398 99.945 l h +33.82 94.773 m 34.836 94.773 35.461 95.68 35.461 97.07 c 35.461 98.43 +34.82 99.336 33.836 99.336 c 32.805 99.336 32.117 98.414 32.117 97.055 +c 32.117 95.695 32.805 94.773 33.82 94.773 c h +42.727 97.32 m 42.727 96.43 42.664 95.883 42.492 95.445 c 42.102 94.492 +41.211 93.914 40.117 93.914 c 38.477 93.914 37.43 95.164 37.43 97.086 c +37.43 99.008 38.445 100.195 40.086 100.195 c 41.43 100.195 42.367 +99.445 42.602 98.164 c 41.664 98.164 l 41.398 98.93 40.883 99.336 +40.117 99.336 c 39.523 99.336 39.023 99.07 38.711 98.586 c 38.492 +98.242 38.414 97.914 38.398 97.32 c h +38.414 96.555 m 38.508 95.477 39.164 94.773 40.102 94.773 c 41.023 +94.773 41.727 95.539 41.727 96.492 c 41.727 96.523 41.727 96.539 41.711 +96.555 c h +44.914 91.773 m 43.977 91.773 l 43.977 99.945 l 44.914 99.945 l h +46.441 95.805 m 47.379 95.805 l 47.457 95.102 47.879 94.773 48.754 +94.773 c 49.598 94.773 50.066 95.086 50.066 95.648 c 50.066 95.883 l +50.066 96.289 49.832 96.445 49.098 96.539 c 47.379 96.758 46.176 96.898 +46.176 98.461 c 46.176 99.523 46.91 100.195 48.098 100.195 c 48.848 +100.195 49.441 99.945 50.098 99.336 c 50.16 99.93 50.457 100.195 51.066 +100.195 c 51.254 100.195 51.395 100.18 51.707 100.102 c 51.707 99.398 l +51.598 99.414 51.551 99.414 51.504 99.414 c 51.176 99.414 50.988 99.258 +50.988 98.961 c 50.988 95.508 l 50.988 94.461 50.238 93.914 48.785 +93.914 c 47.738 93.914 46.488 94.227 46.441 95.805 c h +48.301 99.383 m 47.582 99.383 47.145 99.039 47.145 98.445 c 47.145 +97.148 49.113 97.477 50.066 97.039 c 50.066 98.102 l 50.066 98.664 +49.41 99.383 48.301 99.383 c h +52.551 91.773 m 52.551 99.945 l 53.379 99.945 l 53.379 99.195 l 53.832 +99.883 54.426 100.195 55.254 100.195 c 56.785 100.195 57.801 98.93 +57.801 96.992 c 57.801 95.086 56.832 93.914 55.285 93.914 c 54.488 +93.914 53.91 94.211 53.473 94.867 c 53.473 91.773 l h +55.113 94.773 m 56.16 94.773 56.832 95.695 56.832 97.086 c 56.832 +98.414 56.129 99.336 55.113 99.336 c 54.129 99.336 53.473 98.43 53.473 +97.055 c 53.473 95.68 54.129 94.773 55.113 94.773 c h +55.113 94.773 m S Q +0.784314 0.835294 1 rg +80 121.604 m 112 121.604 l 116.43 121.604 120 118.038 120 113.604 c 120 +97.604 l 120 93.174 116.43 89.604 112 89.604 c 80 89.604 l 75.566 +89.604 72 93.174 72 97.604 c 72 113.604 l 72 118.038 75.566 121.604 80 +121.604 c h +80 121.604 m f* +0.34902 0.454902 0.709804 rg +3.2 w +q 1 0 0 -1 0 202.459579 cm +80 80.855 m 112 80.855 l 116.43 80.855 120 84.422 120 88.855 c 120 +104.855 l 120 109.285 116.43 112.855 112 112.855 c 80 112.855 l 75.566 +112.855 72 109.285 72 104.855 c 72 88.855 l 72 84.422 75.566 80.855 80 +80.855 c h +80 80.855 m S Q +0 g +BT +11.2 0 0 11.2 87.259125 101.429777 Tm +/f-1-0 1 Tf +[<05>26<0f>19<11>]TJ +ET +0.16 w +q 1 0 0 -1 0 202.459579 cm +90.148 95.156 m 89.18 95.156 l 89.18 94.25 l 89.18 93.844 89.398 93.656 +89.82 93.656 c 89.898 93.656 89.93 93.656 90.148 93.656 c 90.148 92.891 +l 89.93 92.844 89.805 92.828 89.617 92.828 c 88.758 92.828 88.242 +93.328 88.242 94.156 c 88.242 95.156 l 87.461 95.156 l 87.461 95.922 l +88.242 95.922 l 88.242 101.031 l 89.18 101.031 l 89.18 95.922 l 90.148 +95.922 l h +95.832 98.406 m 95.832 97.516 95.77 96.969 95.598 96.531 c 95.207 +95.578 94.316 95 93.223 95 c 91.582 95 90.535 96.25 90.535 98.172 c +90.535 100.094 91.551 101.281 93.191 101.281 c 94.535 101.281 95.473 +100.531 95.707 99.25 c 94.77 99.25 l 94.504 100.016 93.988 100.422 +93.223 100.422 c 92.629 100.422 92.129 100.156 91.816 99.672 c 91.598 +99.328 91.52 99 91.504 98.406 c h +91.52 97.641 m 91.613 96.562 92.27 95.859 93.207 95.859 c 94.129 95.859 +94.832 96.625 94.832 97.578 c 94.832 97.609 94.832 97.625 94.816 97.641 +c h +96.879 95.156 m 96.879 101.031 l 97.816 101.031 l 97.816 97.344 l +97.816 96.5 98.441 95.812 99.207 95.812 c 99.895 95.812 100.285 96.234 +100.285 96.984 c 100.285 101.031 l 101.223 101.031 l 101.223 97.344 l +101.223 96.5 101.848 95.812 102.613 95.812 c 103.285 95.812 103.691 +96.25 103.691 96.984 c 103.691 101.031 l 104.629 101.031 l 104.629 +96.625 l 104.629 95.578 104.035 95 102.926 95 c 102.145 95 101.676 +95.234 101.129 95.891 c 100.785 95.266 100.316 95 99.551 95 c 98.77 95 +98.254 95.281 97.738 95.984 c 97.738 95.156 l h +96.879 95.156 m S Q +0.196078 g +BT +11.2 0 0 11.2 17.598188 129.60518 Tm +/f-0-0 1 Tf +[<0f>19<04>11<09>9<05>9<0c0a>5<06>15<0412>20<01>19<06>14<04>21<07>29<08>-1<0d>1<10>6<07>5<0f>10<11>19<03>10<0a>9<09>]TJ +ET +0.905882 0.952941 1 rg +260 145.604 m 352 145.604 l 356.43 145.604 360 142.038 360 137.604 c +360 89.604 l 360 85.174 356.43 81.604 352 81.604 c 260 81.604 l 255.566 +81.604 252 85.174 252 89.604 c 252 137.604 l 252 142.038 255.566 +145.604 260 145.604 c h +260 145.604 m f* +0.690196 0.737255 0.894118 rg +3.2 w +q 1 0 0 -1 0 202.459579 cm +260 56.855 m 352 56.855 l 356.43 56.855 360 60.422 360 64.855 c 360 +112.855 l 360 117.285 356.43 120.855 352 120.855 c 260 120.855 l +255.566 120.855 252 117.285 252 112.855 c 252 64.855 l 252 60.422 +255.566 56.855 260 56.855 c h +260 56.855 m S Q +0.196078 g +BT +11.2 0 0 11.2 259.998218 129.60518 Tm +/f-0-0 1 Tf +[<0a>25<0b>9<06>5<0a>5<0c>6<08>9<01>15<03>1<0d>1<10>6<07>5<0f>10<11>19<03>10<0a>9<09>]TJ +ET +0.34902 0.454902 0.709804 rg +340.969 33.604 m 340.969 32.026 339.691 30.749 338.113 30.749 c 336.535 +30.749 335.254 32.026 335.254 33.604 c 335.254 35.182 336.535 36.463 +338.113 36.463 c 339.691 36.463 340.969 35.182 340.969 33.604 c h +340.969 33.604 m f +0.215686 0.294118 0.490196 rg +q 1 0 0 -1 0 202.459579 cm +340.969 168.855 m 340.969 170.434 339.691 171.711 338.113 171.711 c +336.535 171.711 335.254 170.434 335.254 168.855 c 335.254 167.277 +336.535 165.996 338.113 165.996 c 339.691 165.996 340.969 167.277 +340.969 168.855 c h +340.969 168.855 m S Q +0.34902 0.454902 0.709804 rg +351.543 33.604 m 351.543 32.026 350.262 30.749 348.684 30.749 c 347.105 +30.749 345.828 32.026 345.828 33.604 c 345.828 35.182 347.105 36.463 +348.684 36.463 c 350.262 36.463 351.543 35.182 351.543 33.604 c h +351.543 33.604 m f +0.215686 0.294118 0.490196 rg +q 1 0 0 -1 0 202.459579 cm +351.543 168.855 m 351.543 170.434 350.262 171.711 348.684 171.711 c +347.105 171.711 345.828 170.434 345.828 168.855 c 345.828 167.277 +347.105 165.996 348.684 165.996 c 350.262 165.996 351.543 167.277 +351.543 168.855 c h +351.543 168.855 m S Q +0.34902 0.454902 0.709804 rg +330.398 33.604 m 330.398 32.026 329.117 30.749 327.543 30.749 c 325.965 +30.749 324.684 32.026 324.684 33.604 c 324.684 35.182 325.965 36.463 +327.543 36.463 c 329.117 36.463 330.398 35.182 330.398 33.604 c h +330.398 33.604 m f +0.215686 0.294118 0.490196 rg +q 1 0 0 -1 0 202.459579 cm +330.398 168.855 m 330.398 170.434 329.117 171.711 327.543 171.711 c +325.965 171.711 324.684 170.434 324.684 168.855 c 324.684 167.277 +325.965 165.996 327.543 165.996 c 329.117 165.996 330.398 167.277 +330.398 168.855 c h +330.398 168.855 m S Q +0.784314 0.835294 1 rg +143.371 105.604 m 143.371 104.026 142.09 102.749 140.512 102.749 c +138.934 102.749 137.656 104.026 137.656 105.604 c 137.656 107.182 +138.934 108.463 140.512 108.463 c 142.09 108.463 143.371 107.182 +143.371 105.604 c h +143.371 105.604 m f +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +143.371 96.855 m 143.371 98.434 142.09 99.711 140.512 99.711 c 138.934 +99.711 137.656 98.434 137.656 96.855 c 137.656 95.277 138.934 93.996 +140.512 93.996 c 142.09 93.996 143.371 95.277 143.371 96.855 c h +143.371 96.855 m S Q +0.784314 0.835294 1 rg +153.941 105.604 m 153.941 104.026 152.66 102.749 151.082 102.749 c +149.508 102.749 148.227 104.026 148.227 105.604 c 148.227 107.182 +149.508 108.463 151.082 108.463 c 152.66 108.463 153.941 107.182 +153.941 105.604 c h +153.941 105.604 m f +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +153.941 96.855 m 153.941 98.434 152.66 99.711 151.082 99.711 c 149.508 +99.711 148.227 98.434 148.227 96.855 c 148.227 95.277 149.508 93.996 +151.082 93.996 c 152.66 93.996 153.941 95.277 153.941 96.855 c h +153.941 96.855 m S Q +0.784314 0.835294 1 rg +132.797 105.604 m 132.797 104.026 131.52 102.749 129.941 102.749 c +128.363 102.749 127.082 104.026 127.082 105.604 c 127.082 107.182 +128.363 108.463 129.941 108.463 c 131.52 108.463 132.797 107.182 +132.797 105.604 c h +132.797 105.604 m f +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +132.797 96.855 m 132.797 98.434 131.52 99.711 129.941 99.711 c 128.363 +99.711 127.082 98.434 127.082 96.855 c 127.082 95.277 128.363 93.996 +129.941 93.996 c 131.52 93.996 132.797 95.277 132.797 96.855 c h +132.797 96.855 m S Q +0.784314 0.835294 1 rg +209.254 105.604 m 209.254 104.026 207.977 102.749 206.398 102.749 c +204.82 102.749 203.543 104.026 203.543 105.604 c 203.543 107.182 204.82 +108.463 206.398 108.463 c 207.977 108.463 209.254 107.182 209.254 +105.604 c h +209.254 105.604 m f +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +209.254 96.855 m 209.254 98.434 207.977 99.711 206.398 99.711 c 204.82 +99.711 203.543 98.434 203.543 96.855 c 203.543 95.277 204.82 93.996 +206.398 93.996 c 207.977 93.996 209.254 95.277 209.254 96.855 c h +209.254 96.855 m S Q +0.784314 0.835294 1 rg +219.828 105.604 m 219.828 104.026 218.547 102.749 216.969 102.749 c +215.391 102.749 214.113 104.026 214.113 105.604 c 214.113 107.182 +215.391 108.463 216.969 108.463 c 218.547 108.463 219.828 107.182 +219.828 105.604 c h +219.828 105.604 m f +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +219.828 96.855 m 219.828 98.434 218.547 99.711 216.969 99.711 c 215.391 +99.711 214.113 98.434 214.113 96.855 c 214.113 95.277 215.391 93.996 +216.969 93.996 c 218.547 93.996 219.828 95.277 219.828 96.855 c h +219.828 96.855 m S Q +0.784314 0.835294 1 rg +198.684 105.604 m 198.684 104.026 197.406 102.749 195.828 102.749 c +194.25 102.749 192.969 104.026 192.969 105.604 c 192.969 107.182 194.25 +108.463 195.828 108.463 c 197.406 108.463 198.684 107.182 198.684 +105.604 c h +198.684 105.604 m f +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +198.684 96.855 m 198.684 98.434 197.406 99.711 195.828 99.711 c 194.25 +99.711 192.969 98.434 192.969 96.855 c 192.969 95.277 194.25 93.996 +195.828 93.996 c 197.406 93.996 198.684 95.277 198.684 96.855 c h +198.684 96.855 m S Q +0.784314 0.835294 1 rg +308.855 105.604 m 308.855 104.026 307.574 102.749 306 102.749 c 304.422 +102.749 303.141 104.026 303.141 105.604 c 303.141 107.182 304.422 +108.463 306 108.463 c 307.574 108.463 308.855 107.182 308.855 105.604 c +h +308.855 105.604 m f +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +308.855 96.855 m 308.855 98.434 307.574 99.711 306 99.711 c 304.422 +99.711 303.141 98.434 303.141 96.855 c 303.141 95.277 304.422 93.996 +306 93.996 c 307.574 93.996 308.855 95.277 308.855 96.855 c h +308.855 96.855 m S Q +0.784314 0.835294 1 rg +319.426 105.604 m 319.426 104.026 318.148 102.749 316.57 102.749 c +314.992 102.749 313.711 104.026 313.711 105.604 c 313.711 107.182 +314.992 108.463 316.57 108.463 c 318.148 108.463 319.426 107.182 +319.426 105.604 c h +319.426 105.604 m f +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +319.426 96.855 m 319.426 98.434 318.148 99.711 316.57 99.711 c 314.992 +99.711 313.711 98.434 313.711 96.855 c 313.711 95.277 314.992 93.996 +316.57 93.996 c 318.148 93.996 319.426 95.277 319.426 96.855 c h +319.426 96.855 m S Q +0.784314 0.835294 1 rg +298.285 105.604 m 298.285 104.026 297.004 102.749 295.426 102.749 c +293.848 102.749 292.57 104.026 292.57 105.604 c 292.57 107.182 293.848 +108.463 295.426 108.463 c 297.004 108.463 298.285 107.182 298.285 +105.604 c h +298.285 105.604 m f +0.34902 0.454902 0.709804 rg +q 1 0 0 -1 0 202.459579 cm +298.285 96.855 m 298.285 98.434 297.004 99.711 295.426 99.711 c 293.848 +99.711 292.57 98.434 292.57 96.855 c 292.57 95.277 293.848 93.996 +295.426 93.996 c 297.004 93.996 298.285 95.277 298.285 96.855 c h +298.285 96.855 m S Q +Q Q +showpage +%%Trailer +count op_count sub {pop} repeat +countdictstack dict_count sub {end} repeat +cairo_eps_state restore +%%EOF diff --git a/doc/handbook/EPS/ewoms_structure.eps b/doc/handbook/EPS/ewoms_structure.eps new file mode 100644 index 00000000000..eb29e4060d9 --- /dev/null +++ b/doc/handbook/EPS/ewoms_structure.eps @@ -0,0 +1,1137 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: cairo 1.12.8 (http://cairographics.org) +%%CreationDate: Mon Jul 8 20:28:03 2013 +%%Pages: 1 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%BoundingBox: 0 -1 719 546 +%%EndComments +%%BeginProlog +save +50 dict begin +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/pdfmark where { pop globaldict /?pdfmark /exec load put } + { globaldict begin /?pdfmark /pop load def /pdfmark + /cleartomark load def end } ifelse +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +%%EndProlog +11 dict begin +/FontType 42 def +/FontName /DejaVuSans def +/PaintType 0 def +/FontMatrix [ 1 0 0 1 0 0 ] def +/FontBBox [ 0 0 0 0 ] def +/Encoding 256 array def +0 1 255 { Encoding exch /.notdef put } for +Encoding 32 /space put +Encoding 40 /parenleft put +Encoding 41 /parenright put +Encoding 44 /comma put +Encoding 45 /hyphen put +Encoding 46 /period put +Encoding 47 /slash put +Encoding 65 /A put +Encoding 66 /B put +Encoding 67 /C put +Encoding 69 /E put +Encoding 71 /G put +Encoding 72 /H put +Encoding 73 /I put +Encoding 75 /K put +Encoding 76 /L put +Encoding 77 /M put +Encoding 78 /N put +Encoding 80 /P put +Encoding 82 /R put +Encoding 83 /S put +Encoding 84 /T put +Encoding 86 /V put +Encoding 87 /W put +Encoding 97 /a put +Encoding 98 /b put +Encoding 99 /c put +Encoding 100 /d put +Encoding 101 /e put +Encoding 102 /f put +Encoding 103 /g put +Encoding 104 /h put +Encoding 105 /i put +Encoding 107 /k put +Encoding 108 /l put +Encoding 109 /m put +Encoding 110 /n put +Encoding 111 /o put +Encoding 112 /p put +Encoding 113 /q put +Encoding 114 /r put +Encoding 115 /s put +Encoding 116 /t put +Encoding 117 /u put +Encoding 118 /v put +Encoding 119 /w put +Encoding 120 /x put +Encoding 121 /y put +Encoding 122 /z put +/CharStrings 50 dict dup begin +/.notdef 0 def +/e 1 def +/w 2 def +/o 3 def +/m 4 def +/s 5 def +/b 6 def +/x 7 def +/d 8 def +/l 9 def +/c 10 def +/n 11 def +/i 12 def +/a 13 def +/t 14 def +/r 15 def +/p 16 def +/space 17 def +/y 18 def +/u 19 def +/P 20 def +/h 21 def +/G 22 def +/f 23 def +/hyphen 24 def +/v 25 def +/C 26 def +/parenleft 27 def +/g 28 def +/comma 29 def +/period 30 def +/parenright 31 def +/I 32 def +/slash 33 def +/k 34 def +/V 35 def +/T 36 def +/K 37 def +/B 38 def +/H 39 def +/M 40 def +/E 41 def +/q 42 def +/N 43 def +/A 44 def +/z 45 def +/R 46 def +/S 47 def +/W 48 def +/L 49 def +end readonly def +/sfnts [ +<0001000000090080000300106376742000691d3900002388000001fe6670676d7134766a0000 +2588000000ab676c79666cbc80570000009c000022ec68656164f79ac5e70000263400000036 +686865610cb806830000266c00000024686d7478e7b319db00002690000000c86c6f63610003 +856400002758000000cc6d617870049f06710000282400000020707265703b07f10000002844 +0000056800020066fe96046605a400030007001a400c04fb0006fb0108057f0204002fc4d4ec +310010d4ecd4ec301311211125211121660400fc73031bfce5fe96070ef8f272062900020071 +ffe3047f047b0014001b00704024001501098608880515a90105b90c01bb18b912b80c8c1c1b +1502081508004b02120f451c10fcecf4ecc4111239310010e4f4ece410ee10ee10f4ee111239 +3040293f1d701da01dd01df01d053f003f013f023f153f1b052c072f082f092c0a6f006f016f +026f156f1b095d71015d0115211e0133323637150e01232000111000333200072e0123220607 +047ffcb20ccdb76ac76263d06bfef4fec70129fce20107b802a5889ab90e025e5abec73434ae +2a2c0138010a01130143feddc497b4ae9e0000010056000006350460000c01eb404905550605 +090a0904550a0903550a0b0a025501020b0b0a061107080705110405080807021103020c000c +011100000c420a050203060300bf0b080c0b0a09080605040302010b07000d10d44bb00a544b +b011545b4bb012545b4bb013545b4bb00b545b58b9000000403859014bb00c544bb00d545b4b +b010545b58b90000ffc03859cc173931002f3cec32321739304b5358071005ed071008ed0710 +08ed071005ed071008ed071005ed0705ed071008ed59220140ff050216021605220a350a4902 +4905460a400a5b025b05550a500a6e026e05660a79027f0279057f05870299029805940abc02 +bc05ce02c703cf051d0502090306040b050a080b09040b050c1502190316041a051b081b0914 +0b150c2500250123022703210425052206220725082709240a210b230c390336043608390c30 +0e460248034604400442054006400740084409440a440b400e400e5600560156025004510552 +06520750085309540a550b6300640165026a0365046a056a066a076e09610b670c6f0e750075 +0179027d0378047d057a067f067a077f07780879097f097b0a760b7d0c870288058f0e970097 +01940293039c049b05980698079908402f960c9f0ea600a601a402a403ab04ab05a906a907ab +08a40caf0eb502b103bd04bb05b809bf0ec402c303cc04ca05795d005d13331b01331b013301 +230b012356b8e6e5d9e6e5b8fedbd9f1f2d90460fc96036afc96036afba00396fc6a00020071 +ffe30475047b000b0017004a401306b91200b90cb8128c1809120f51031215451810fcecf4ec +310010e4f4ec10ee3040233f197b007b067f077f087f097f0a7f0b7b0c7f0d7f0e7f0f7f107f +117b12a019f01911015d012206151416333236353426273200111000232200111000027394ac +ab9593acac93f00112feeef0f1feef011103dfe7c9c9e7e8c8c7e99cfec8feecfeedfec70139 +0113011401380000000100ba0000071d047b0022005a4026061209180f00061d07150c871d20 +03b81bbc19100700110f0808065011080f501c18081a462310fcec32fcfcfcec11123931002f +3c3ce4f43cc4ec32111217393040133024502470249024a024a024bf24df24ff2409015d013e +013332161511231134262322061511231134262322061511231133153e01333216042945c082 +afbeb972758fa6b972778da6b9b93fb0797aab03897c76f5e2fd5c029ea19cbea4fd87029ea2 +9bbfa3fd870460ae67627c0000000001006fffe303c7047b002700e7403c0d0c020e0b531f1e +080902070a531f1f1e420a0b1e1f041500860189041486158918b91104b925b8118c281e0a0b +1f1b0700521b080e07081422452810fcc4ecd4ece4111239393939310010e4f4ec10fef5ee10 +f5ee121739304b535807100eed111739070eed1117395922b2002701015d406d1c0a1c0b1c0c +2e092c0a2c0b2c0c3b093b0a3b0b3b0c0b200020012402280a280b2a132f142f152a16281e28 +1f292029212427860a860b860c860d12000000010202060a060b030c030d030e030f03100319 +031a031b031c041d09272f293f295f297f2980299029a029f029185d005d7101152e01232206 +1514161f011e0115140623222627351e013332363534262f012e01353436333216038b4ea85a +898962943fc4a5f7d85ac36c66c661828c65ab40ab98e0ce66b4043fae282854544049210e2a +99899cb62323be353559514b50250f2495829eac1e000000000200baffe304a40614000b001c +0038401903b90c0f09b918158c0fb81b971900121247180c06081a461d10fcec3232f4ec3100 +2fece4f4c4ec10c6ee30b6601e801ea01e03015d013426232206151416333236013e01333200 +111002232226271523113303e5a79292a7a79292a7fd8e3ab17bcc00ffffcc7bb13ab9b9022f +cbe7e7cbcbe7e702526461febcfef8fef8febc6164a806140001003b000004790460000b0143 +40460511060706041103040707060411050401020103110202010b110001000a11090a010100 +0a110b0a0708070911080807420a070401040800bf05020a0704010408000208060c10d44bb0 +0a544bb00f545b4bb010545b4bb011545b58b90006004038594bb0145458b90006ffc03859c4 +d4c411173931002f3cec321739304b5358071005ed071008ed071008ed071005ed071005ed07 +1008ed071008ed071005ed59220140980a04040a1a04150a260a3d04310a55045707580a660a +76017a047607740a8d04820a99049f049707920a900aa601a904af04a507a30aa00a1c0a0304 +0505090a0b1a03150515091a0b2903260525092a0b200d3a013903370534073609390b300d49 +03460545094a0b400d590056015902590357055606590756085609590b500d6f0d78017f0d9b +019407ab01a407b00dcf0ddf0dff0d2f5d005d09022309012309013309010464fe6b01aad9fe +bafebad901b3fe72d9012901290460fddffdc101b8fe48024a0216fe71018f0000020071ffe3 +045a06140010001c003840191ab9000e14b905088c0eb801970317040008024711120b451d10 +fcecf4ec323231002fece4f4c4ec10c4ee30b6601e801ea01e03015d0111331123350e012322 +0211100033321601141633323635342623220603a2b8b83ab17ccbff00ffcb7cb1fdc7a79292 +a8a89292a703b6025ef9eca86461014401080108014461fe15cbe7e7cbcbe7e7000100c10000 +0179061400030022b7009702010800460410fcec31002fec30400d10054005500560057005f0 +0506015d13331123c1b8b80614f9ec0000010071ffe303e7047b0019003f401b00860188040e +860d880ab91104b917b8118c1a07120d004814451a10fce432ec310010e4f4ec10fef4ee10f5 +ee30400b0f1b101b801b901ba01b05015d01152e0123220615141633323637150e0123220011 +100021321603e74e9d50b3c6c6b3509d4e4da55dfdfed6012d010655a20435ac2b2be3cdcde3 +2b2baa2424013e010e0112013a230000000100ba00000464047b001300364019030900030e01 +06870e11b80cbc0a010208004e0d09080b461410fcec32f4ec31002f3ce4f4c4ec1112173930 +b46015cf1502015d0111231134262322061511231133153e013332160464b87c7c95acb9b942 +b375c1c602a4fd5c029e9f9ebea4fd870460ae6564ef000200c100000179061400030007002b +400e06be04b100bc020501080400460810fc3cec3231002fe4fcec30400b1009400950096009 +700905015d1333112311331523c1b8b8b8b80460fba00614e9000002007bffe3042d047b000a +002500bc4027191f0b17090e00a91706b90e1120861fba1cb923b8118c170c001703180d0908 +0b1f030814452610fcecccd4ec323211393931002fc4e4f4fcf4ec10c6ee10ee113911391239 +30406e301d301e301f3020302130223f27401d401e401f402040214022501d501e501f502050 +21502250277027851d871e871f8720872185229027a027f0271e301e301f30203021401e401f +40204021501e501f50205021601e601f60206021701e701f70207021801e801f80208021185d +015d0122061514163332363d01371123350e01232226353436332135342623220607353e0133 +321602bedfac816f99b9b8b83fbc88accbfdfb0102a79760b65465be5af3f00233667b6273d9 +b4294cfd81aa6661c1a2bdc0127f8b2e2eaa2727fc0000010037000002f2059e001300384019 +0e05080f03a9001101bc08870a0b08090204000810120e461410fc3cc4fc3cc432393931002f +ecf43cc4ec3211393930b2af1501015d01112115211114163b01152322263511233533110177 +017bfe854b73bdbdd5a28787059efec28ffda0894e9a9fd202608f013e000000000100ba0000 +034a047b001100304014060b0700110b03870eb809bc070a06080008461210fcc4ec3231002f +e4f4ecc4d4cc11123930b450139f1302015d012e012322061511231133153e0133321617034a +1f492c9ca7b9b93aba85132e1c03b41211cbbefdb20460ae666305050000000200bafe5604a4 +047b0010001c003e401b1ab9000e14b90508b80e8c01bd03bc1d11120b471704000802461d10 +fcec3232f4ec310010e4e4e4f4c4ec10c4ee304009601e801ea01ee01e04015d251123113315 +3e013332001110022322260134262322061514163332360173b9b93ab17bcc00ffffcc7bb102 +38a79292a7a79292a7a8fdae060aaa6461febcfef8fef8febc6101ebcbe7e7cbcbe7e7000000 +0001003dfe56047f0460000f018b40430708020911000f0a110b0a00000f0e110f000f0d110c +0d00000f0d110e0d0a0b0a0c110b0b0a420d0b0910000b058703bd0e0bbc100e0d0c0a090603 +00080f040f0b1010d44bb00a544bb008545b58b9000b004038594bb0145458b9000bffc03859 +c4c4111739310010e432f4ec113911391239304b5358071005ed071008ed071008ed071005ed +071008ed0705ed173259220140f0060005080609030d160a170d100d230d350d490a4f0a4e0d +5a095a0a6a0a870d800d930d120a000a09060b050c0b0e0b0f1701150210041005170a140b14 +0c1a0e1a0f2700240124022004200529082809250a240b240c270d2a0e2a0f20113700350135 +0230043005380a360b360c380d390e390f301141004001400240034004400540064007400842 +09450a470d490e490f40115400510151025503500450055606550756085709570a550b550c59 +0e590f501166016602680a690e690f60117b08780e780f89008a09850b850c890d890e890f99 +09950b950c9a0e9a0fa40ba40cab0eab0fb011cf11df11ff11655d005d050e012b0135333236 +3f01013309013302934e947c936c4c543321fe3bc3015e015ec368c87a9a488654044efc9403 +6c000000000200aeffe30458047b00130014003b401c030900030e0106870e118c0a01bc14b8 +0c0d0908140b4e020800461510fcecf439ec3231002fe4e432f4c4ec1112173930b46f15c015 +02015d1311331114163332363511331123350e0123222601aeb87c7c95adb8b843b175c1c801 +cf01ba02a6fd619f9fbea4027bfba0ac6663f003a800000200c90000048d05d500080013003a +40180195100095098112100a0802040005190d3f11001c09041410fcec32fcec11173931002f +f4ecd4ec30400b0f151f153f155f15af1505015d011133323635342623252132041514042b01 +11230193fe8d9a9a8dfe3801c8fb0101fefffbfeca052ffdcf92878692a6e3dbdde2fda80001 +00ba000004640614001300344019030900030e0106870e11b80c970a010208004e0d09080b46 +1410fcec32f4ec31002f3cecf4c4ec1112173930b2601501015d011123113426232206151123 +1133113e013332160464b87c7c95acb9b942b375c1c602a4fd5c029e9f9ebea4fd870614fd9e +6564ef0000010073ffe3058b05f0001d0039402000051b0195031b950812a111ae15950e9108 +8c1e02001c1134043318190b101e10fcecfce4fcc4310010e4f4ecf4ec10fed4ee1139393025 +1121352111060423200011100021320417152e0123200011100021323604c3feb6021275fee6 +a0fea2fe75018b015e9201076f70fc8bfeeefeed011301126ba8d50191a6fd7f53550199016d +016e01994846d75f60fecefed1fed2fece2500000001002f000002f8061400130059401c0510 +010c08a906018700970e06bc0a02130700070905080d0f0b4c1410fc4bb00a5458b9000b0040 +38594bb00e5458b9000bffc038593cc4fc3cc4c412393931002fe432fcec10ee321239393001 +b640155015a015035d01152322061d012115211123112335333534363302f8b0634d012ffed1 +b9b0b0aebd0614995068638ffc2f03d18f4ebbab0001006401df027f028300030011b6009c02 +0401000410dccc310010d4ec301321152164021bfde50283a4000001003d0000047f04600006 +00fb402703110405040211010205050402110302060006011100000642020300bf0506050302 +010504000710d44bb00a5458b90000004038594bb014544bb015545b58b90000ffc03859c417 +3931002fec3239304b5358071005ed071008ed071008ed071005ed592201408e48026a027b02 +7f02860280029102a402080600060109030904150015011a031a042600260129032904200835 +0035013a033a0430084600460149034904460548064008560056015903590450086600660169 +036904670568066008750074017b037b0475057a068500850189038904890586069600960197 +029a03980498059706a805a706b008c008df08ff083e5d005d133309013301233dc3015e015e +c3fe5cfa0460fc5403acfba0000000010073ffe3052705f000190036401a0da10eae0a951101 +a100ae04951791118c1a07190d003014101a10fcec32ec310010e4f4ecf4ec10eef6ee30b40f +1b1f1b02015d01152e0123200011100021323637150e01232000111000213216052766e782ff +00fef00110010082e7666aed84feadfe7a0186015386ed0562d55f5efec7fed8fed9fec75e5f +d34848019f01670168019f470000000100b0fef2027b0612000d0037400f069800970e0d0700 +03120600130a0e10dc4bb0135458b9000affc038594bb00f5458b9000a00403859e432ec1139 +39310010fcec300106021514121723260235341237027b86828385a0969594970612e6fe3ee7 +e7fe3be5eb01c6e0df01c4ec00020071fe56045a047b000b0028004a4023190c1d0912861316 +b90f03b92623b827bc09b90fbd1a1d261900080c4706121220452910fcc4ecf4ec323231002f +c4e4ece4f4c4ec10fed5ee1112393930b6602a802aa02a03015d013426232206151416333236 +17100221222627351e013332363d010e0123220211101233321617353303a2a59594a5a59495 +a5b8fefefa61ac51519e52b5b439b27ccefcfcce7cb239b8023dc8dcdcc8c7dcdcebfee2fee9 +1d1eb32c2abdbf5b6362013a01030104013a6263aa000001009eff1201c300fe00050019400c +039e0083060304011900180610fcecd4cc310010fcec30373315032313f0d3a48152feacfec0 +0140000100db000001ae00fe00030011b7008302011900180410fcec31002fec3037331523db +d3d3fefe000100a4fef2026f0612000d001f400f079800970e0701000b12041308000e10dc3c +f4ec113939310010fcec301333161215140207233612353402a4a096959596a08583830612ec +fe3cdfe0fe3aebe501c5e7e701c20000000100c90000019305d50003002eb700af02011c0004 +0410fc4bb0105458b9000000403859ec31002fec3001400d30054005500560058f059f05065d +13331123c9caca05d5fa2b0000010000ff4202b205d50003002d4014001a010201021a030003 +42029f008104020001032fc43939310010f4ec304b5358071005ed071005ed59220133012302 +08aafdf8aa05d5f96d000000000100ba0000049c0614000a00bc402908110506050711060605 +03110405040211050504420805020303bc009709060501040608010800460b10fcec32d4c411 +3931002f3cece41739304b5358071004ed071005ed071005ed071004ed5922b2100c01015d40 +5f04020a081602270229052b0856026602670873027705820289058e08930296059708a30212 +09050906020b030a072803270428052b062b07400c6803600c8903850489058d068f079a0397 +07aa03a705b607c507d607f703f003f704f0041a5d71005d1333110133090123011123bab902 +25ebfdae026bf0fdc7b90614fc6901e3fdf4fdac0223fddd000100100000056805d5000600b7 +402704110506050311020306060503110403000100021101010042030401af00060403020005 +05010710d4c4173931002fec3239304b5358071005ed071008ed071008ed071005ed5922b250 +0801015d406200032a03470447055a037d038303070600070208040906150114021a041a052a +002601260229042905250620083800330133023c043c05370648004501450249044905470659 +0056066602690469057a0076017602790479057506800898009706295d005d21013309013301 +024afdc6d301d901dad2fdc705d5fb1704e9fa2b0001fffa000004e905d50007004a400e0602 +950081040140031c0040050810d4e4fce431002ff4ec3230014bb00a5458bd00080040000100 +080008ffc03811373859401300091f00100110021f071009400970099f09095d032115211123 +11210604effdeecbfdee05d5aafad5052b00000100c90000056a05d5000a00ef402808110506 +0507110606050311040504021105050442080502030300af09060501040608011c00040b10fc +ec32d4c4113931002f3cec321739304b5358071004ed071005ed071005ed071004ed5922b208 +0301015d40921402010402090816022805280837023605340847024605430855026702760277 +05830288058f0894029b08e702150603090509061b031907050a030a07180328052b062a0736 +04360536063507300c41034004450540064007400c62036004680567077705700c8b038b058e +068f078f0c9a039d069d07b603b507c503c507d703d607e803e904e805ea06f703f805f9062c +5d71005d711333110121090121011123c9ca029e0104fd1b031afef6fd33ca05d5fd890277fd +48fce302cffd31000000000300c9000004ec05d5000800110020004340231900950a09951281 +01950aad1f110b080213191f05000e1c1605191c2e09001c12042110fcec32fcecd4ec111739 +393931002fececf4ec10ee3930b20f2201015d01112132363534262301112132363534262325 +213216151406071e01151404232101930144a39d9da3febc012b94919194fe0b0204e7fa807c +95a5fef0fbfde802c9fddd878b8c850266fe3e6f727170a6c0b189a21420cb98c8da000100c9 +0000053b05d5000b002c4014089502ad0400810a0607031c053809011c00040c10fcec32fcec +3231002f3ce432fcec30b2500d01015d133311211133112311211123c9ca02decacafd22ca05 +d5fd9c0264fa2b02c7fd3900000100c90000061f05d5000c00bf403403110708070211010208 +080702110302090a0901110a0a09420a070203080300af080b050908030201050a061c043e0a +1c00040d10fcecfcec11173931002f3cc4ec32111739304b5358071005ed071008ed071008ed +071005ed5922b2700e01015d405603070f080f09020a15021407130a260226072007260a200a +3407350a69027c027b07790a80028207820a90021604010b0313011b0323012c032708280934 +013c035608590965086a097608790981018d0395019b03145d005d1321090121112311012301 +1123c9012d017d017f012dc5fe7fcbfe7fc405d5fc0803f8fa2b051ffc000400fae100000001 +00c90000048b05d5000b002e401506950402950081089504ad0a05010907031c00040c10fcec +32d4c4c431002fececf4ec10ee30b21f0d01015d132115211121152111211521c903b0fd1a02 +c7fd3902f8fc3e05d5aafe46aafde3aa000000020071fe56045a047b000b001c003e401b03b9 +0c0f09b91815b80f8c1bbd19bc1d180c06081a47001212451d10fcecf4ec3232310010e4e4e4 +f4c4ec10c6ee304009601e801ea01ee01e04015d011416333236353426232206010e01232202 +1110003332161735331123012fa79292a8a89292a702733ab17ccbff00ffcb7cb13ab8b8022f +cbe7e7cbcbe7e7fdae646101440108010801446164aaf9f60000000100c90000053305d50009 +0079401e071101020102110607064207020300af0805060107021c0436071c00040a10fcecfc +ec11393931002f3cec323939304b5358071004ed071004ed5922b21f0b01015d403036023807 +48024707690266078002070601090615011a06460149065701580665016906790685018a0695 +019a069f0b105d005d13210111331121011123c901100296c4fef0fd6ac405d5fb1f04e1fa2b +04e1fb1f000200100000056805d50002000a00c2404100110100040504021105050401110a03 +0a0011020003030a0711050406110505040911030a08110a030a420003079501038109050908 +0706040302010009050a0b10d4c4173931002f3ce4d4ec1239304b5358071005ed0705ed0710 +05ed0705ed071008ed071005ed071005ed071008ed5922b2200c01015d40420f010f020f070f +080f005800760070008c000907010802060309041601190256015802500c6701680278017602 +7c0372047707780887018802800c980299039604175d005d090121013301230321032302bcfe +ee0225fe7be50239d288fd5f88d5050efd1903aefa2b017ffe81000000010058000003db0460 +0009009d401a081102030203110708074208a900bc03a905080301000401060a10dc4bb00b54 +4bb00c545b58b90006ffc038594bb0135458b9000600403859c432c411393931002fecf4ec30 +4b5358071005ed071005ed592201404205021602260247024907050b080f0b18031b082b0820 +0b36033908300b400140024503400440054308570359085f0b6001600266036004600562087f +0b800baf0b1b5d005d1321150121152135012171036afd4c02b4fc7d02b4fd650460a8fcdb93 +a8032500000200c90000055405d50013001c00b14035090807030a0611030403051104040342 +06040015030415950914950d810b040506031109001c160e050a191904113f140a1c0c041d10 +fcec32fcc4ec1117391139393931002f3cf4ecd4ec123912391239304b5358071005ed071005 +ed1117395922b2401e01015d40427a1301050005010502060307041500150114021603170425 +002501250226032706260726082609201e360136024601460268057504750577138806880798 +0698071f5d005d011e01171323032e012b01112311212016151406011133323635342623038d +417b3ecdd9bf4a8b78dcca01c80100fc83fd89fe9295959202bc16907efe68017f9662fd8905 +d5d6d88dba024ffdee878383850000010087ffe304a205f00027007e403c0d0c020e0b021e1f +1e080902070a021f1f1e420a0b1e1f0415010015a11494189511049500942591118c281e0a0b +1f1b0700221b190e2d071914222810dcc4ecfcece4111239393939310010e4f4e4ec10eef6ee +10c6111739304b535807100eed11173907100eed1117395922b20f2901015db61f292f294f29 +035d01152e012322061514161f011e0115140421222627351e013332363534262f012e013534 +24333216044873cc5fa5b377a67ae2d7feddfee76aef807bec72adbc879a7be2ca0117f569da +05a4c53736807663651f192bd9b6d9e0302fd04546887e6e7c1f182dc0abc6e4260000010044 +000007a605d5000c017b4049051a0605090a09041a0a09031a0a0b0a021a01020b0b0a061107 +080705110405080807021103020c000c011100000c420a050203060300af0b080c0b0a090806 +05040302010b07000d10d4cc173931002f3cec32321739304b5358071005ed071008ed071008 +ed071005ed071008ed071005ed0705ed071008ed5922b2000e01015d40f206020605020a000a +000a120a2805240a200a3e023e05340a300a4c024d05420a400a59026a026b05670a600a7b02 +7f027c057f05800a960295051d07000902080300040605000500060107040800080709000904 +0a0a0c000e1a0315041508190c100e200421052006200720082309240a250b200e200e3c023a +033504330530083609390b3f0c300e460046014a024004450540054206420742084008400944 +0a4d0c400e400e58025608590c500e66026703610462056006600760086409640a640b770076 +017b027803770474057906790777087008780c7f0c7f0e860287038804890585098a0b8f0e97 +049f0eaf0e5b5d005d1333090133090133012309012344cc013a0139e3013a0139cdfe89fefe +c5fec2fe05d5fb1204eefb1204eefa2b0510faf00000000100c90000046a05d500050025400c +0295008104011c033a00040610fcecec31002fe4ec304009300750078003800404015d133311 +211521c9ca02d7fc5f05d5fad5aa013500b800cb00cb00c100aa009c01a600b8006600000071 +00cb00a002b20085007500b800c301cb0189022d00cb00a600f000d300aa008700cb03aa0400 +014a003300cb000000d9050200f4015400b4009c01390114013907060400044e04b4045204b8 +04e704cd0037047304cd04600473013303a2055605a60556053903c5021200c9001f00b801df +007300ba03e9033303bc0444040e00df03cd03aa00e503aa0404000000cb008f00a4007b00b8 +0014016f007f027b0252008f00c705cd009a009a006f00cb00cd019e01d300f000ba018300d5 +009803040248009e01d500c100cb00f600830354027f00000333026600d300c700a400cd008f +009a0073040005d5010a00fe022b00a400b4009c00000062009c0000001d032d05d505d505d5 +05f0007f007b005400a406b80614072301d300b800cb00a601c301ec069300a000d3035c0371 +03db0185042304a80448008f0139011401390360008f05d5019a061407230666017904600460 +0460047b009c00000277046001aa00e904600762007b00c5007f027b000000b4025205cd0066 +00bc00660077061000cd013b01850389008f007b0000001d00cd074a042f009c009c0000077d +006f0000006f0335006a006f007b00ae00b2002d0396008f027b00f600830354063705f6008f +009c04e10266008f018d02f600cd03440029006604ee00730000140000960000b70706050403 +0201002c2010b002254964b040515820c859212d2cb002254964b040515820c859212d2c2010 +0720b00050b00d7920b8ffff5058041b0559b0051cb0032508b0042523e120b00050b00d7920 +b8ffff5058041b0559b0051cb0032508e12d2c4b505820b0fd454459212d2cb002254560442d +2c4b5358b00225b0022545445921212d2c45442d2cb00225b0022549b00525b005254960b020 +6368208a108a233a8a10653a2d00000100000002547ae8bb8dea5f0f3cf5001f080000000000 +c990133600000000c9901336f7d6fcae0d72095500000008000000010000000000010000076d +fe1d00000de2f7d6fa510d7200010000000000000000000000000000003204cd006604ec0071 +068b005604e5007107cb00ba042b006f051400ba04bc003b05140071023900c1046600710512 +00ba023900c104e7007b03230037034a00ba051400ba028b000004bc003d051200ae04d300c9 +051200ba0633007302d1002f02e3006404bc003d05960073031f00b005140071028b009e028b +00db031f00a4025c00c902b2000004a200ba0579001004e3fffa053f00c9057d00c9060400c9 +06e700c9050e00c90514007105fc00c90579001004330058058f00c90514008707e900440475 +00c90000000000000044000001180000033c000003e0000004a4000006040000069c00000820 +000008b8000008f40000098c00000a0400000a5400000b8000000bfc00000c6c00000d0c0000 +0d0c00000ed800000f5c00000fdc00001054000010fc00001194000011c0000012e40000137c +000013ec000014b4000014ec000015140000156c000015b400001600000016f0000017d00000 +18400000196800001a1800001a7400001b7000001bd000001c7000001d1800001e1400001ee0 +00001ff4000020ec000022a8000022ec0001000000320354002b0068000c0002001000990008 +00000415021600080004b8028040fffbfe03fa1403f92503f83203f79603f60e03f5fe03f4fe +03f32503f20e03f19603f02503ef8a4105effe03ee9603ed9603ecfa03ebfa03eafe03e93a03 +e84203e7fe03e63203e5e45305e59603e48a4105e45303e3e22f05e3fa03e22f03e1fe03e0fe +03df3203de1403dd9603dcfe03db1203da7d03d9bb03d8fe03d68a4105d67d03d5d44705d57d +03d44703d3d21b05d3fe03d21b03d1fe03d0fe03cffe03cefe03cd9603cccb1e05ccfe03cb1e +03ca3203c9fe03c6851105c61c03c51603c4fe03c3fe03c2fe03c1fe03c0fe03bffe03befe03 +bdfe03bcfe03bbfe03ba1103b9862505b9fe03b8b7bb05b8fe03b7b65d05b7bb03b78004b6b5 +2505b65d40ff03b64004b52503b4fe03b39603b2fe03b1fe03b0fe03affe03ae6403ad0e03ac +ab2505ac6403abaa1205ab2503aa1203a98a4105a9fa03a8fe03a7fe03a6fe03a51203a4fe03 +a3a20e05a33203a20e03a16403a08a4105a096039ffe039e9d0c059efe039d0c039c9b19059c +64039b9a10059b19039a1003990a0398fe0397960d0597fe03960d03958a410595960394930e +05942803930e0392fa039190bb0591fe03908f5d0590bb039080048f8e25058f5d038f40048e +25038dfe038c8b2e058cfe038b2e038a8625058a410389880b05891403880b03878625058764 +038685110586250385110384fe038382110583fe0382110381fe0380fe037ffe0340ff7e7d7d +057efe037d7d037c64037b5415057b25037afe0379fe03780e03770c03760a0375fe0374fa03 +73fa0372fa0371fa0370fe036ffe036efe036c21036bfe036a1142056a530369fe03687d0367 +11420566fe0365fe0364fe0363fe0362fe03613a0360fa035e0c035dfe035bfe035afe035958 +0a0559fa03580a035716190557320356fe035554150555420354150353011005531803521403 +514a130551fe03500b034ffe034e4d10054efe034d10034cfe034b4a13054bfe034a4910054a +1303491d0d05491003480d0347fe0346960345960344fe0343022d0543fa0342bb03414b0340 +fe033ffe033e3d12053e14033d3c0f053d12033c3b0d053c40ff0f033b0d033afe0339fe0338 +37140538fa033736100537140336350b05361003350b03341e03330d0332310b0532fe03310b +03302f0b05300d032f0b032e2d09052e10032d09032c32032b2a25052b64032a2912052a2503 +2912032827250528410327250326250b05260f03250b0324fe0323fe03220f03210110052112 +032064031ffa031e1d0d051e64031d0d031c1142051cfe031bfa031a42031911420519fe0318 +64031716190517fe031601100516190315fe0314fe0313fe031211420512fe0311022d051142 +03107d030f64030efe030d0c16050dfe030c0110050c16030bfe030a100309fe0308022d0508 +fe030714030664030401100504fe03401503022d0503fe0302011005022d0301100300fe0301 +b80164858d012b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b002b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b1d00> +] def +/f-0-0 currentdict end definefont pop +11 dict begin +/FontType 42 def +/FontName /DejaVuSans def +/PaintType 0 def +/FontMatrix [ 1 0 0 1 0 0 ] def +/FontBBox [ 0 0 0 0 ] def +/Encoding 256 array def +0 1 255 { Encoding exch /.notdef put } for +Encoding 1 /uniFB01 put +Encoding 2 /uniFB03 put +Encoding 3 /uniFB02 put +Encoding 4 /uniFB00 put +/CharStrings 5 dict dup begin +/.notdef 0 def +/uniFB01 1 def +/uniFB03 2 def +/uniFB02 3 def +/uniFB00 4 def +end readonly def +/sfnts [ +<0001000000090080000300106376742000691d3900000394000001fe6670676d7134766a0000 +0594000000ab676c796614829e620000009c000002f868656164f79ac5e70000064000000036 +686865610cb806560000067800000024686d74781c2001220000069c000000146c6f63610000 +0834000006b0000000186d61787004720671000006c800000020707265703b07f100000006e8 +0000056800020066fe96046605a400030007001a400c04fb0006fb0108057f0204002fc4d4ec +310010d4ecd4ec301311211125211121660400fc73031bfce5fe96070ef8f27206290002002f +0000044a061400150019005240111b46001708160f1404080803160a064c1a10fc3cc432c4fc +3cc410fe3cec310040120803a90010870e18be16b10e970900bc05012f3ce632eefeee10ee10 +ee3230400bff1ba01b901b801b101b05015d01112311211123112335333534363b0115232206 +1d0101331523044ab9fe07b9b0b0adb3b9b0634d01f9b9b90460fba003d1fc2f03d18f4eb7af +9950686301b2e9000002002f000006fc06140029002d005a40182f46172b082a101b15081a2a +09001f0608241e0922264c2e10fc3cc432c4fc3cc410c432fc3cc410fc3cec310040171b1f23 +a924110187002dbe2ab1100097160724bc191d212f3c3ce43232e432f4ec10ec3210ec323230 +0115232207061d01213534373637363b01152322061d01211123112111231121112311233533 +353436330533152302f8b063272601f9571c274e83aeb0634d02b2b9fe07b9fe07b9b0b0aebd +03f9b9b9061499282868634ebb551c132799506863fba003d1fc2f03d1fc2f03d18f4ebbab02 +e90000000001002f0000044a061400150037400f17460108040a0c08081004120e4c1610fc3c +c4c4fc3cc410feec3100400d0f0ba909048700971109bc0d022f3ce632feee10ee3230012111 +23112122061d01211521112311233533353436024a0200b9feb7634d012ffed1b9b0b0ae0614 +f9ec057b5068638ffc2f03d18f4ebbab00000001002f000005aa061400240048401326000709 +05080c21180d1e08110c2110144c2510fc3cc432c4fc3cc4103cfc3cc4c4c431004011090d11 +a912021a87001897061f12bc0b0f2f3ce63232fe3cee3210ee32323001152322061d01211521 +112311211123112335333534363b0115232207061d01213534363305aab0634d012ffed1b9fe +07b9b0b0aebdaeb063272601f9aebd0614995068638ffc2f03d1fc2f03d18f4ebbab99282868 +634ebbab013500b800cb00cb00c100aa009c01a600b800660000007100cb00a002b200850075 +00b800c301cb0189022d00cb00a600f000d300aa008700cb03aa0400014a003300cb000000d9 +050200f4015400b4009c01390114013907060400044e04b4045204b804e704cd0037047304cd +04600473013303a2055605a60556053903c5021200c9001f00b801df007300ba03e9033303bc +0444040e00df03cd03aa00e503aa0404000000cb008f00a4007b00b80014016f007f027b0252 +008f00c705cd009a009a006f00cb00cd019e01d300f000ba018300d5009803040248009e01d5 +00c100cb00f600830354027f00000333026600d300c700a400cd008f009a0073040005d5010a +00fe022b00a400b4009c00000062009c0000001d032d05d505d505d505f0007f007b005400a4 +06b80614072301d300b800cb00a601c301ec069300a000d3035c037103db0185042304a80448 +008f0139011401390360008f05d5019a0614072306660179046004600460047b009c00000277 +046001aa00e904600762007b00c5007f027b000000b4025205cd006600bc00660077061000cd +013b01850389008f007b0000001d00cd074a042f009c009c0000077d006f0000006f0335006a +006f007b00ae00b2002d0396008f027b00f600830354063705f6008f009c04e10266008f018d +02f600cd03440029006604ee00730000140000960000b707060504030201002c2010b0022549 +64b040515820c859212d2cb002254964b040515820c859212d2c20100720b00050b00d7920b8 +ffff5058041b0559b0051cb0032508b0042523e120b00050b00d7920b8ffff5058041b0559b0 +051cb0032508e12d2c4b505820b0fd454459212d2cb002254560442d2c4b5358b00225b00225 +45445921212d2c45442d2cb00225b0022549b00525b005254960b0206368208a108a233a8a10 +653a2d00000100000002547a30b7a2805f0f3cf5001f080000000000c990133600000000c990 +1336f7d6fcae0d72095500000008000000010000000000010000076dfe1d00000de2f7d6fa51 +0d7200010000000000000000000000000000000504cd0066050a002f07bc002f050a002f0583 +002f0000000000000044000000e8000001c800000248000002f80001000000050354002b0068 +000c000200100099000800000415021600080004b8028040fffbfe03fa1403f92503f83203f7 +9603f60e03f5fe03f4fe03f32503f20e03f19603f02503ef8a4105effe03ee9603ed9603ecfa +03ebfa03eafe03e93a03e84203e7fe03e63203e5e45305e59603e48a4105e45303e3e22f05e3 +fa03e22f03e1fe03e0fe03df3203de1403dd9603dcfe03db1203da7d03d9bb03d8fe03d68a41 +05d67d03d5d44705d57d03d44703d3d21b05d3fe03d21b03d1fe03d0fe03cffe03cefe03cd96 +03cccb1e05ccfe03cb1e03ca3203c9fe03c6851105c61c03c51603c4fe03c3fe03c2fe03c1fe +03c0fe03bffe03befe03bdfe03bcfe03bbfe03ba1103b9862505b9fe03b8b7bb05b8fe03b7b6 +5d05b7bb03b78004b6b52505b65d40ff03b64004b52503b4fe03b39603b2fe03b1fe03b0fe03 +affe03ae6403ad0e03acab2505ac6403abaa1205ab2503aa1203a98a4105a9fa03a8fe03a7fe +03a6fe03a51203a4fe03a3a20e05a33203a20e03a16403a08a4105a096039ffe039e9d0c059e +fe039d0c039c9b19059c64039b9a10059b19039a1003990a0398fe0397960d0597fe03960d03 +958a410595960394930e05942803930e0392fa039190bb0591fe03908f5d0590bb039080048f +8e25058f5d038f40048e25038dfe038c8b2e058cfe038b2e038a8625058a410389880b058914 +03880b03878625058764038685110586250385110384fe038382110583fe0382110381fe0380 +fe037ffe0340ff7e7d7d057efe037d7d037c64037b5415057b25037afe0379fe03780e03770c +03760a0375fe0374fa0373fa0372fa0371fa0370fe036ffe036efe036c21036bfe036a114205 +6a530369fe03687d036711420566fe0365fe0364fe0363fe0362fe03613a0360fa035e0c035d +fe035bfe035afe0359580a0559fa03580a035716190557320356fe0355541505554203541503 +53011005531803521403514a130551fe03500b034ffe034e4d10054efe034d10034cfe034b4a +13054bfe034a4910054a1303491d0d05491003480d0347fe0346960345960344fe0343022d05 +43fa0342bb03414b0340fe033ffe033e3d12053e14033d3c0f053d12033c3b0d053c40ff0f03 +3b0d033afe0339fe033837140538fa033736100537140336350b05361003350b03341e03330d +0332310b0532fe03310b03302f0b05300d032f0b032e2d09052e10032d09032c32032b2a2505 +2b64032a2912052a25032912032827250528410327250326250b05260f03250b0324fe0323fe +03220f03210110052112032064031ffa031e1d0d051e64031d0d031c1142051cfe031bfa031a +42031911420519fe031864031716190517fe031601100516190315fe0314fe0313fe03121142 +0512fe0311022d05114203107d030f64030efe030d0c16050dfe030c0110050c16030bfe030a +100309fe0308022d0508fe030714030664030401100504fe03401503022d0503fe0302011005 +022d0301100300fe0301b80164858d012b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b002b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b +2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b1d00> +] def +/f-0-1 currentdict end definefont pop +%%Page: 1 1 +%%BeginPageSetup +%%PageBoundingBox: 0 -1 719 546 +%%EndPageSetup +q 0 -1 719 547 rectclip q +0 g +1.199999 w +0 J +1 j +[] 0.0 d +4 M q -0.00111561 1 -1 -0.00111561 0 545.871033 cm +-293.082 -24.384 m -200.965 -24.381 l -199.89 -24.382 -199.022 -23.516 +-199.025 -22.442 c -199.022 -2.325 l -199.025 -1.251 -199.891 -0.382 -200.965 + -0.381 c -293.082 -0.384 l -294.157 -0.383 -295.025 -1.249 -295.022 -2.327 + c -295.025 -22.44 l -295.022 -23.515 -294.156 -24.383 -293.082 -24.384 +c h +S Q +BT +-0.0107099 9.599994 -9.599994 -0.0107099 16.012019 281.760378 Tm +/f-0-0 1 Tf +(ewoms)Tj +9.6 0 0 9.6 86.545664 512.527282 Tm +[(bo)31(xmodels)]TJ +0.578369 -12.015951 Td +[(common)]TJ +1.698975 -3.499323 Td +(io)Tj +-1.622315 -18.666667 Td +[(material)]TJ +-0.31372 -10.059407 Td +[(nonlinear)]TJ +ET +1.2 w +q 1 0 0 -1 0 545.871033 cm +66.656 18.051 m 158.77 18.051 l 159.844 18.051 160.711 18.918 160.711 19.992 + c 160.711 40.105 l 160.711 41.184 159.844 42.051 158.77 42.051 c 66.656 + 42.051 l 65.578 42.051 64.711 41.184 64.711 40.105 c 64.711 19.992 l 64.711 + 18.918 65.578 18.051 66.656 18.051 c h +S Q +q 1 0 0 1 0 545.871033 cm +66.656 -157.957 m 158.77 -157.957 l 159.844 -157.957 160.711 -157.09 160.711 + -156.012 c 160.711 -135.898 l 160.711 -134.824 159.844 -133.957 158.77 +-133.957 c 66.656 -133.957 l 65.578 -133.957 64.711 -134.824 64.711 -135.898 + c 64.711 -156.012 l 64.711 -157.09 65.578 -157.957 66.656 -157.957 c h +S Q +q 1 0 0 -1 0 545.871033 cm +66.656 166.996 m 158.77 166.996 l 159.844 166.996 160.711 167.863 160.711 + 168.938 c 160.711 189.055 l 160.711 190.129 159.844 190.996 158.77 190.996 + c 66.656 190.996 l 65.578 190.996 64.711 190.129 64.711 189.055 c 64.711 + 168.938 l 64.711 167.863 65.578 166.996 66.656 166.996 c h +S Q +q 1 0 0 -1 0 545.871033 cm +68.254 346.195 m 160.371 346.195 l 161.445 346.195 162.312 347.062 162.312 + 348.141 c 162.312 368.254 l 162.312 369.332 161.445 370.195 160.371 370.195 + c 68.254 370.195 l 67.18 370.195 66.312 369.332 66.312 368.254 c 66.312 + 348.141 l 66.312 347.062 67.18 346.195 68.254 346.195 c h +S Q +q 1 0 0 -1 0 545.871033 cm +66.656 442.195 m 158.77 442.195 l 159.844 442.195 160.711 443.062 160.711 + 444.141 c 160.711 464.254 l 160.711 465.332 159.844 466.195 158.77 466.195 + c 66.656 466.195 l 65.578 466.195 64.711 465.332 64.711 464.254 c 64.711 + 444.141 l 64.711 443.062 65.578 442.195 66.656 442.195 c h +S Q +BT +9.6 0 0 9.6 211.725357 531.31363 Tm +/f-0-0 1 Tf +[(common)]TJ +-1.730957 -10.280479 Td +[(speci)]TJ +/f-0-1 1 Tf +<01>Tj +/f-0-0 1 Tf +[(c)-3( models )]TJ +ET +q 1 0 0 -1 0 545.871033 cm +186.656 259.797 m 278.77 259.797 l 279.844 259.797 280.711 260.664 280.711 + 261.738 c 280.711 281.855 l 280.711 282.93 279.844 283.797 278.77 283.797 + c 186.656 283.797 l 185.578 283.797 184.711 282.93 184.711 281.855 c 184.711 + 261.738 l 184.711 260.664 185.578 259.797 186.656 259.797 c h +S Q +q 1 0 0 -1 0 545.871033 cm +186.656 288.598 m 278.77 288.598 l 279.844 288.598 280.711 289.465 280.711 + 290.539 c 280.711 310.652 l 280.711 311.73 279.844 312.598 278.77 312.598 + c 186.656 312.598 l 185.578 312.598 184.711 311.73 184.711 310.652 c 184.711 + 290.539 l 184.711 289.465 185.578 288.598 186.656 288.598 c h +S Q +q 1 0 0 -1 0 545.871033 cm +186.656 408.598 m 278.77 408.598 l 279.844 408.598 280.711 409.465 280.711 + 410.539 c 280.711 430.652 l 280.711 431.73 279.844 432.598 278.77 432.598 + c 186.656 432.598 l 185.578 432.598 184.711 431.73 184.711 430.652 c 184.711 + 410.539 l 184.711 409.465 185.578 408.598 186.656 408.598 c h +S Q +0.949084 w +0 j +q 1 0 0 -1 0 545.871033 cm +65.445 545.398 m 41.445 545.398 l 40.711 30.195 l 64.711 30.195 l S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +24.711 243.797 m 40.711 243.797 l S Q +q 1 0 0 -1 0 545.871033 cm +41.582 358.586 m 66.312 358.586 l S Q +q 1 0 0 -1 0 545.871033 cm +41.445 146.051 m 64.711 146.051 l S Q +BT +9.6 0 0 9.6 189.572231 271.810339 Tm +/f-0-0 1 Tf +[(binaryc)-3(oe)]TJ +/f-0-1 1 Tf +<02>Tj +/f-0-0 1 Tf +[(cients)]TJ +1.377197 -2.972397 Td +[(component)-3(s)]TJ +/f-0-1 1 Tf +-0.0512695 -12.534839 Td +<03>Tj +/f-0-0 1 Tf +[(uidsyst)-3(ems)]TJ +ET +q 1 0 0 -1 0 545.871033 cm +280.711 272.598 m 328.711 272.598 l S Q +325.512 273.273 m 323.91 271.676 l 329.512 273.273 l 323.91 274.875 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -272.598 m -323.91 -274.195 l -329.512 -272.598 l -323.91 -270.996 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +280.711 358.996 m 328.711 358.996 l S Q +325.512 186.875 m 323.91 185.273 l 329.512 186.875 l 323.91 188.473 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -358.996 m -323.91 -360.598 l -329.512 -358.996 l -323.91 -357.398 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +280.711 392.598 m 328.711 392.598 l S Q +325.512 153.273 m 323.91 151.676 l 329.512 153.273 l 323.91 154.875 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -392.598 m -323.91 -394.195 l -329.512 -392.598 l -323.91 -390.996 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +160.711 178.195 m 328.711 178.195 l S Q +325.512 367.676 m 323.91 366.074 l 329.512 367.676 l 323.91 369.273 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -178.195 m -323.91 -179.797 l -329.512 -178.195 l -323.91 -176.598 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +160.711 146.758 m 328.711 146.758 l S Q +325.512 399.113 m 323.91 397.516 l 329.512 399.113 l 323.91 400.715 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -146.758 m -323.91 -148.355 l -329.512 -146.758 l -323.91 -145.156 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +160.711 454.996 m 328.711 454.996 l S Q +325.512 90.875 m 323.91 89.273 l 329.512 90.875 l 323.91 92.473 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -454.996 m -323.91 -456.598 l -329.512 -454.996 l -323.91 -453.398 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +280.711 421.398 m 328.711 421.398 l S Q +325.512 124.473 m 323.91 122.875 l 329.512 124.473 l 323.91 126.074 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -421.398 m -323.91 -422.996 l -329.512 -421.398 l -323.91 -419.797 + l h +S Q +BT +8.8 0 0 8.8 334.317154 432.621034 Tm +/f-0-0 1 Tf +[(Physica)-3(l models)]TJ +0.0419918 10.909091 Td +[(Genera)-3(l infrastr)-3(uct)-3(ur)20(e of the fully implicit c)-3(ell-c\ +enter)19(ed )]TJ +/f-0-1 1 Tf +<01>Tj +/f-0-0 1 Tf +[(n)-3(ite-)27(volume meth)-3(od)]TJ +0 -14.97787 Td +[(Common in)-3(frastr)-3(uctu)-3(r)21(e \(time m)-3(anagemen)-3(t, st)-3 +(art-u)-3(p r)21(outines,)-3( pr)21(opterty s)-3(ytem, ..)-3(.\))]TJ +-0.0419918 -3.568492 Td +[(In-/ou)-3(tput in)-3(frastr)-3(uctu)-3(r)21(e \(wr)-3(iting/r)20(eading\ + c)-3(heckpoint)-3( )]TJ +/f-0-1 1 Tf +<01>Tj +/f-0-0 1 Tf +[(les, w)-3(riting VTK )]TJ +/f-0-1 1 Tf +[<>-3<01>]TJ +/f-0-0 1 Tf +(les\))Tj +0 -10.844145 Td +[(Binary th)-3(er)16(modynamic )-3(coe)]TJ +/f-0-1 1 Tf +<02>Tj +/f-0-0 1 Tf +[(cients)-3( \(binar)-3(y di)]TJ +/f-0-1 1 Tf +<04>Tj +/f-0-0 1 Tf +[(usion c)-3(oe)]TJ +/f-0-1 1 Tf +<02>Tj +/f-0-0 1 Tf +[(cients, )-3(Henry c)-3(oe)]TJ +/f-0-1 1 Tf +<02>Tj +/f-0-0 1 Tf +[(cients,)-3( ...\))]TJ +0 -3.148775 Td +[(Material pr)19(operties of a pu)-3(r)21(e ch)-3(emical su)-3(bstanc)-3 +(es or ps)-3(eudo subs)-3(tances)]TJ +0 -13.621791 Td +[(Expr)20(ess th)-3(e ther)15(modynamic)-3( r)21(elations betw)-3(een qua\ +)-3(ntities )]TJ +0 -3.933356 Td +[(Implementat)-3(ion of the Newt)-3(on meth)-3(od)]TJ +9.6 0 0 9.6 98.833943 299.580651 Tm +(linear)Tj +ET +1.2 w +1 j +q 1 0 0 -1 0 545.871033 cm +66.656 230.996 m 158.77 230.996 l 159.844 230.996 160.711 231.863 160.711 + 232.938 c 160.711 253.055 l 160.711 254.129 159.844 254.996 158.77 254.996 + c 66.656 254.996 l 65.578 254.996 64.711 254.129 64.711 253.055 c 64.711 + 232.938 l 64.711 231.863 65.578 230.996 66.656 230.996 c h +S Q +0.8 w +0 j +q 1 0 0 -1 0 545.871033 cm +160.711 242.195 m 328.711 242.195 l S Q +325.512 303.676 m 323.91 302.074 l 329.512 303.676 l 323.91 305.273 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -242.195 m -323.91 -243.797 l -329.512 -242.195 l -323.91 -240.598 + l h +S Q +BT +9.6 0 0 9.6 95.018323 51.010339 Tm +/f-0-0 1 Tf +[(parallel)]TJ +ET +1.2 w +1 j +q 1 0 0 -1 0 545.871033 cm +66.656 478.996 m 158.77 478.996 l 159.844 478.996 160.711 479.863 160.711 + 480.938 c 160.711 501.055 l 160.711 502.129 159.844 502.996 158.77 502.996 + c 66.656 502.996 l 65.578 502.996 64.711 502.129 64.711 501.055 c 64.711 + 480.938 l 64.711 479.863 65.578 478.996 66.656 478.996 c h +S Q +0.773689 w +0 j +q 1 0 0 -1 0 545.871033 cm +41.582 454.586 m 64.711 454.586 l S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +160.711 486.996 m 328.711 486.996 l S Q +325.512 58.875 m 323.91 57.273 l 329.512 58.875 l 323.91 60.473 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -486.996 m -323.91 -488.598 l -329.512 -486.996 l -323.91 -485.398 + l h +S Q +BT +8.8 0 0 8.8 335.112074 57.274402 Tm +/f-0-0 1 Tf +[(Auxiliary infras)-3(truc)-3(tur)19(e for usefu)-3(l )]TJ +/f-0-1 1 Tf +<01>Tj +/f-0-0 1 Tf +[(r pa)-3(rallelization)]TJ +ET +1.2 w +1 j +q 1 0 0 -1 0 545.871033 cm +186.656 317.398 m 278.77 317.398 l 279.844 317.398 280.711 318.262 280.711 + 319.34 c 280.711 339.453 l 280.711 340.531 279.844 341.398 278.77 341.398 + c 186.656 341.398 l 185.578 341.398 184.711 340.531 184.711 339.453 c 184.711 + 319.34 l 184.711 318.262 185.578 317.398 186.656 317.398 c h +S Q +BT +9.6 0 0 9.6 191.224577 213.256699 Tm +/f-0-0 1 Tf +[(constr)-3(aintsolvers)]TJ +ET +0.8 w +0 j +q 1 0 0 -1 0 545.871033 cm +40.711 178.195 m 64.711 178.195 l S Q +q 1 0 0 -1 0 545.871033 cm +40.711 243.797 m 64.711 243.797 l S Q +1.2 w +1 j +q 1 0 0 -1 0 545.871033 cm +186.656 346.195 m 278.77 346.195 l 279.844 346.195 280.711 347.062 280.711 + 348.141 c 280.711 368.254 l 280.711 369.332 279.844 370.195 278.77 370.195 + c 186.656 370.195 l 185.578 370.195 184.711 369.332 184.711 368.254 c 184.711 + 348.141 l 184.711 347.062 185.578 346.195 186.656 346.195 c h +S Q +BT +9.6 0 0 9.6 224.074577 185.403723 Tm +/f-0-0 1 Tf +(eos)Tj +ET +q 1 0 0 -1 0 545.871033 cm +186.656 379.797 m 278.77 379.797 l 279.844 379.797 280.711 380.664 280.711 + 381.738 c 280.711 401.855 l 280.711 402.93 279.844 403.797 278.77 403.797 + c 186.656 403.797 l 185.578 403.797 184.711 402.93 184.711 401.855 c 184.711 + 381.738 l 184.711 380.664 185.578 379.797 186.656 379.797 c h +S Q +BT +9.6 0 0 9.6 207.49489 150.821987 Tm +/f-0-1 1 Tf +<03>Tj +/f-0-0 1 Tf +[(uidsta)-3(tes)]TJ +ET +0.8 w +0 j +q 1 0 0 -1 0 545.871033 cm +280.711 301.398 m 328.711 301.398 l S Q +325.512 244.473 m 323.91 242.875 l 329.512 244.473 l 323.91 246.074 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -301.398 m -323.91 -302.996 l -329.512 -301.398 l -323.91 -299.797 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +280.711 328.598 m 328.711 328.598 l S Q +325.512 217.273 m 323.91 215.676 l 329.512 217.273 l 323.91 218.875 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -328.598 m -323.91 -330.195 l -329.512 -328.598 l -323.91 -326.996 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +160.711 360.598 m 176.711 360.598 l S Q +q 1 0 0 -1 0 545.871033 cm +184.711 275.797 m 176.711 275.797 l S Q +q 1 0 0 -1 0 545.871033 cm +184.711 304.598 m 176.711 304.598 l S Q +q 1 0 0 -1 0 545.871033 cm +184.711 336.598 m 176.711 336.598 l S Q +q 1 0 0 -1 0 545.871033 cm +176.711 275.797 m 176.711 419.797 l S Q +q 1 0 0 -1 0 545.871033 cm +184.711 419.797 m 176.711 419.797 l S Q +q 1 0 0 -1 0 545.871033 cm +184.711 390.996 m 176.711 390.996 l S Q +BT +8.8 0 0 8.8 334.317152 150.258378 Tm +/f-0-0 1 Tf +[(R)44(epr)21(esentations)-3( of the th)-3(er)16(modynamic )-3(con)]TJ +/f-0-1 1 Tf +[<01>]TJ +/f-0-0 1 Tf +[(gu)-3(ration )-3(of a system)]TJ +0.0419922 7.205348 Td +[(Constr)-3(aint s)-3(olvers \(Solve system)-3(s of ther)15(modynamic)-3 +( equations)-3(\))]TJ +0.059082 -3.339937 Td +[(Ther)15(modynamic )-3(equations )-3(of state \(a)-3(uxiliary clas)-3(se\ +s\))]TJ +-0.0581058 13.311257 Td +[(eW)59(oms speci)]TJ +/f-0-1 1 Tf +[<01>]TJ +/f-0-0 1 Tf +[()-3(c linear)-3( solver infra)-3(stuc)-3(tur)20(e \(bac)-3(k)35(end, al\ +gebr)-3(aic overlap c)-3(ode, etc\))]TJ +ET +q 1 0 0 -1 0 545.871033 cm +175.98 76.5 m 184.711 76.5 l S Q +1.2 w +1 j +q 1 0 0 -1 0 545.871033 cm +186.656 66.102 m 278.77 66.102 l 279.844 66.102 280.711 66.965 280.711 +68.043 c 280.711 88.156 l 280.711 89.234 279.844 90.102 278.77 90.102 c +186.656 90.102 l 185.578 90.102 184.711 89.234 184.711 88.156 c 184.711 +68.043 l 184.711 66.965 185.578 66.102 186.656 66.102 c h +S Q +0.8 w +0 j +q 1 0 0 -1 0 545.871033 cm +280.711 78.352 m 329.668 78.352 l S Q +326.469 467.519 m 324.867 465.922 l 330.469 467.519 l 324.867 469.121 l + h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-326.469 -78.352 m -324.867 -79.949 l -330.469 -78.352 l -324.867 -76.75 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +175.98 43.75 m 184.711 43.75 l S Q +1.2 w +1 j +q 1 0 0 -1 0 545.871033 cm +186.656 33.352 m 278.77 33.352 l 279.844 33.352 280.711 34.219 280.711 +35.293 c 280.711 55.406 l 280.711 56.484 279.844 57.352 278.77 57.352 c +186.656 57.352 l 185.578 57.352 184.711 56.484 184.711 55.406 c 184.711 +35.293 l 184.711 34.219 185.578 33.352 186.656 33.352 c h +S Q +0.8 w +0 j +q 1 0 0 -1 0 545.871033 cm +280.711 45.602 m 329.668 45.602 l S Q +326.469 500.269 m 324.867 498.672 l 330.469 500.269 l 324.867 501.871 l + h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-326.469 -45.602 m -324.867 -47.199 l -330.469 -45.602 l -324.867 -44 l + h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +175.98 11 m 184.711 11 l S Q +1.2 w +1 j +q 1 0 0 -1 0 545.871033 cm +186.656 0.602 m 278.77 0.602 l 279.844 0.602 280.711 1.469 280.711 2.543 + c 280.711 22.656 l 280.711 23.734 279.844 24.602 278.77 24.602 c 186.656 + 24.602 l 185.578 24.602 184.711 23.734 184.711 22.656 c 184.711 2.543 l + 184.711 1.469 185.578 0.602 186.656 0.602 c h +S Q +0.8 w +0 j +q 1 0 0 -1 0 545.871033 cm +280.711 12.852 m 329.668 12.852 l S Q +326.469 533.019 m 324.867 531.422 l 330.469 533.019 l 324.867 534.621 l + h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-326.469 -12.852 m -324.867 -14.449 l -330.469 -12.852 l -324.867 -11.25 + l h +S Q +0.8 w +q 1 0 0 -1 0 545.871033 cm +175.98 109.25 m 184.711 109.25 l S Q +1.2 w +1 j +q 1 0 0 -1 0 545.871033 cm +186.656 98.852 m 278.77 98.852 l 279.844 98.852 280.711 99.715 280.711 +100.793 c 280.711 120.906 l 280.711 121.984 279.844 122.852 278.77 122.852 + c 186.656 122.852 l 185.578 122.852 184.711 121.984 184.711 120.906 c 184.711 + 100.793 l 184.711 99.715 185.578 98.852 186.656 98.852 c h +S Q +0.8 w +0 j +q 1 0 0 -1 0 545.871033 cm +280.711 111.098 m 329.668 111.098 l S Q +326.469 434.773 m 324.867 433.172 l 330.469 434.773 l 324.867 436.371 l + h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-326.469 -111.098 m -324.867 -112.699 l -330.469 -111.098 l -324.867 -109.5 + l h +S Q +BT +9.6 0 0 9.6 224.859735 499.021033 Tm +/f-0-0 1 Tf +(vtk)Tj +8.8 0 0 8.8 334.317154 496.621033 Tm +[(Modules for wr)-3(iting VTK \()-3(visualization\) )-3(output)]TJ +9.6 0 0 9.6 211.80036 467.021035 Tm +(modules)Tj +8.8 0 0 8.8 334.317154 464.621034 Tm +[(Plug-ins )-3(for the ph)-3(ysical models \()-3(ener)16(gy, velocity, di\ +)]TJ +/f-0-1 1 Tf +[<04>]TJ +/f-0-0 1 Tf +[(u)-3(sion, ..)-3(.\))]TJ +ET +0.8 w +q 1 0 0 -1 0 545.871033 cm +160.711 30.852 m 175.91 30.852 l S Q +q 1 0 0 -1 0 545.871033 cm +175.91 109.25 m 175.91 10.852 l S Q +BT +9.6 0 0 9.6 98.833943 332.380651 Tm +/f-0-0 1 Tf +(linear)Tj +ET +1.2 w +1 j +q 1 0 0 -1 0 545.871033 cm +66.656 198.195 m 158.77 198.195 l 159.844 198.195 160.711 199.062 160.711 + 200.141 c 160.711 220.254 l 160.711 221.332 159.844 222.195 158.77 222.195 + c 66.656 222.195 l 65.578 222.195 64.711 221.332 64.711 220.254 c 64.711 + 200.141 l 64.711 199.062 65.578 198.195 66.656 198.195 c h +S Q +0.8 w +0 j +q 1 0 0 -1 0 545.871033 cm +160.711 209.398 m 328.711 209.398 l S Q +325.512 336.473 m 323.91 334.875 l 329.512 336.473 l 323.91 338.074 l h +f* +0.4 w +q -1 0 0 1 0 545.871033 cm +-325.512 -209.398 m -323.91 -210.996 l -329.512 -209.398 l -323.91 -207.797 + l h +S Q +BT +8.8 0 0 8.8 334.600746 334.21305 Tm +/f-0-0 1 Tf +(Slightly modi)Tj +/f-0-1 1 Tf +<01>Tj +/f-0-0 1 Tf +[(ed copy of ISTL s)-3(olvers \(for impr)19(oved stability and )-3(perfor\ +)17(manc)-3(e)]TJ +ET +Q Q +showpage +%%Trailer +end restore +%%EOF diff --git a/doc/handbook/EPS/exercise1_c.eps b/doc/handbook/EPS/exercise1_c.eps new file mode 100644 index 00000000000..38ccf34e96e --- /dev/null +++ b/doc/handbook/EPS/exercise1_c.eps @@ -0,0 +1,154 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: exercise1_c.fig +%%Creator: fig2dev Version 3.2 Patchlevel 5 +%%CreationDate: Mon Nov 10 16:50:24 2008 +%%For: melanie@alvine (Melanie Darcis) +%%BoundingBox: 0 0 283 107 +%Magnification: 1.0000 +%%EndComments +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +newpath 0 107 moveto 0 0 lineto 283 0 lineto 283 107 lineto closepath clip newpath +-169.1 349.4 translate +1 -1 scale + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def + +$F2psBegin +10 setmiterlimit +0 slj 0 slc + 0.06299 0.06299 sc +% +% Fig objects follow +% +% +% here starts figure with depth 50 +% Polyline +0 slj +0 slc +7.500 slw +n 3285 5310 m + 3285 5535 l gs col0 s gr +% Polyline +n 3285 5445 m + 4905 5445 l gs col0 s gr +% Polyline +n 5715 5445 m + 7155 5445 l gs col0 s gr +% Polyline +n 7155 5310 m + 7155 5535 l gs col0 s gr +% Polyline +n 3060 4725 m + 3060 5040 l gs col0 s gr +% Polyline +n 2970 5130 m + 3150 5130 l gs col0 s gr +% Polyline +n 3060 4680 m + 3060 5130 l gs col0 s gr +% Polyline +n 3240 3870 m 7155 3870 l 7155 5130 l 3240 5130 l + cp gs col0 s gr +% Polyline +n 5265 3870 m 7155 3870 l 7155 5130 l 5265 5130 l + cp gs col15 1.00 shd ef gr gs col0 s gr +% Polyline +n 3060 3870 m + 3060 4320 l gs col0 s gr +% Polyline +n 2970 3870 m + 3150 3870 l gs col0 s gr +/Times-Roman ff 190.50 scf sf +3870 4365 m +gs 1 -1 sc (K1 =) col0 sh gr +/Times-Roman ff 190.50 scf sf +3870 4590 m +gs 1 -1 sc (phi1 =) col0 sh gr +/Times-Roman ff 190.50 scf sf +5760 4365 m +gs 1 -1 sc (K2 =) col7 sh gr +/Times-Roman ff 190.50 scf sf +5760 4545 m +gs 1 -1 sc (phi2 =) col7 sh gr +/Times-Roman ff 190.50 scf sf +2700 4590 m +gs 1 -1 sc (300 m) col0 sh gr +/Times-Roman ff 190.50 scf sf +5085 5490 m +gs 1 -1 sc (600 m) col0 sh gr +% here ends figure; +$F2psEnd +rs +showpage +%%Trailer +%EOF diff --git a/doc/handbook/EPS/masstransfer.eps b/doc/handbook/EPS/masstransfer.eps new file mode 100644 index 00000000000..9400e6f7af9 --- /dev/null +++ b/doc/handbook/EPS/masstransfer.eps @@ -0,0 +1,1228 @@ +%!PS-Adobe-1.0 EPSF-3.0 +%%BoundingBox: 0 160 1058 842 +%%Creator: KDE 3.5.7 "release 72.9" +%%CreationDate: Wed Jun 3 08:42:58 2009 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2006 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.000 3.000 ] [ 3.000 10.000 ] [ 3.000 3.000 ] [ 3.000 3.000 ] [ 5.000 3.000 3.000 3.000 ] [ 3.000 5.000 3.000 3.000 ] [ 5.000 3.000 3.000 3.000 3.000 ] [ 3.000 5.000 3.000 3.000 3.000 3.000 ] ] d +/pageinit { +% 210*297mm (portrait) +0 842 translate 1 -1 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 0 0]ST +CLSTART +0 0 1058 682 ACR +CLEND +B P1 +NB +W BC +/sl 60306 string uc +īżł1¾õ½¼ķżśÜż÷»żńłüåõūĶķłIŻõ½¼ķżśÜż÷»żńłüåõūĶķłIŻõ½¼ķżśÜż÷»żńłüåõūĶķłIŻõ½¼ķż +śÜż÷»żńłüåõūĶķłIŻõ½¼ķżśÜż÷»żńłüåõūĶķłIŻõ½¼ķżś¼OĆ·üģ1µOT>QÅšUŌ**ŲNäŅR*+.åV¾åL +QŠRĻNõš0ģŻż4¼ūķ-öķľĮåū“UĻUWČņH·Š>SĶH¹,ŪHŗŁÄ?Ł@WDĶ?WŲ³ēĄėŲäŃWE@źB»śö·»ų2ČŅSŅ +PĒT>SĻµ9łüüõöõĮĶįµ9H»õīŪńōW÷7¹?é;FĻ¶ŠM¾.OČ?V*¾WŃÅĶŽU+:ųÄĆ3öW×*3,ņęŠNXPŌUŃęßV +éŪøQķG»ųśŻżEü÷ęńģA5Ė@TŠN¾>ĪOßāļ=Bķ*öYĻ@ēĒ>SŠZN:Süķ½8Iłī·ļĮ·°ŅĘ:Iø8ģÖ³éŽOÉ“ĖŽ +ŃõŹż±Ų*:B4¾āĒŁ@ź:0Žć÷MĘ:MČUE-łķŻ;½¶é·ņŽUÕģT°×VFčŃNĻTWMõŠżį**Ż¹7**ķüŽÓ/ö³įPWČ +>čŅÆŹĢęąÓUM1żū¹šõ@G0Tģ=JĒPĒEŽ1,±ßż.±*śÓ,¶øē@G<>4ććķŻ=Iōä·ńU÷WÉ“UŲ³RĢFŗųöOäż· +0æńŃÅXE·éĄ/±įāĻVłūŻšĮXF1ĶśīÜQ+ģČćL/ŁÄY<ģ1,ńĘæŚXŽ÷/É?čŌ³.õŲąĖ×įżüæ÷A-ĢÕ¼øĒ8JĒ +āē¹Ģł*ZXüI-*śĪõĢėĒJ¼ś8D+ŽĶß,ĢUJTI*Į9PĮ5²µĖ: +KŁRÜ@ģė½ĶHźQGĒTDż=9RņĻóö;NķK×Õ,VŠFJļŁéŽ,ó7Ń=3ßēÉ>ÅõI5½3Ī*üB*1ĶŚN¼ćÄ?-Qöļ¹ŹōĒ +P,īĶ·HŽīŁéīśöĻŁŌVŲ²4ćXńĶš1ŃXŪõė/ĢŻõćWÕć4O9*ŗ5ō±*AĘŽQA¼ÉŪūżżĆŃą¹ŪIī»éŁÕµųļ*Ä» +ńŁ¹M*ŗķćģļżżŚöėĮ×JŚ9ń8±¶;*ĀŗóćÅBZ0*4Żż0äµVŅZ:åŌ¼Ī6Y<+Ę·ģÕŁR**łåŌ½ŹIøĆ×.WŃõÜé +į*X>æQĮ,JUķżżEN¶*ņG8Īöƶ*æ:õ¾*¹UĘ+*+*XæĪßņżåĪŌņņ³Ļ*¾Ń½7ż>÷ŪßÄWMõ¾äĶ9Ž/=öÆX*ŹŲųż½Ć.1¾ÜĻ+Ź»³-0:ĀTF<ŽéH¹įīūXK0P³ŪŲ?M“+0Żż×Ā2¶ō58ÜFGÉŽAüŲŌµ +ÜÉ<ēćģżĮµYWĪ¹łź1ŽīNčæ÷W5“ŹāŻÜżBūOŚ/4IŪZŪŲXėJ+Jß“ē5/ūć,*÷5Ź?ĒÜFĪ¼0ī,4śB?*JVéś +ŻLĄ-Jķß»Õ1*4G*ĀŠż9żÕ»N¾Ž*+åWÄĮRż±K+īź@F»CĒęūAõĘ:A·4*:.Qżżķ@É;Āļ×9¾ąēŚ-čÉ0ĘÜ× +żŹü<Ė,±Y9GĘW²=ÄĻHøśŁęPøńOŅ·ĢÉLOįA7Ņ5¼,YRæQĮ,ŽŌÆšü=N¶*ņ77ŽNć*ĒP<¾³KöōŌ:-ü,.ŗ³ +C4LūĶOĀ*÷żżĖÕ·õÉ/Źą÷ŻFFVĀ.;Z**C?ÄŽęĒĘĆłÓ¼¾ß÷Ś-ńįµSŠRāßż°ūÉ=46¼øųB“ĻĖBÄņņ×ķLE +ēÉAßņ»É>AéB»÷BŪ2āõ,5śBA*²PńĮGKÅ-JķżżŪ±ŅßAßF5Ž>ĻżżłŲÅ.śT¹õTĆ6ĮĘ²ż±K+īČAFõŌ*ŠÓ +ūYĢ71;ŹJĢD²Ž÷ĶżEÕēįŠßö9++>ĢSH²ŲW×2ŌóūÅ÷YŠSZōóĻ-×Ć“ąBčMŽ5ėNAÜNCŪŲåŪ@ĪRćüĶOĀ¾-VRŻīÉ?,JÕö½·TF¾ŃŚļŁ;Kā½ū9 +ūX:¾Ā@åÓ9ĘżĮŃŲ1ƵŹ8¹¹ß½Y*Ź×53G»6ѵĮóģĀÆNQĶūóHMł9-šŚ01G»S;Ū2āõL5śBA*ŚYĖ³V>Å-J +ķżżĒĢ,ŽÕšPģÄÄQżżŁŲÅ.*2*ī0J*ćū½@>*XūĄŻ?1¾Üążćæļ*ĘżżY22;żó½õ¹ĆRĻõČńJļŚōK/ŲÉ/ĘP +ģY6*ńĀ,±Y9GĘW²=ÄĻHøśŁęPDšOB·Äå“7Ń1Z°N½ļ/H74*ģA±²,ÄĮ+Īõżżśś*īø<Ć,K+ĢēōūWK0ĀŗŲ +>6G@ųDūż×Ā2¶?6¼õD*<׿AQĆ-īūI¼KDĀśåżĻõ/,0GłÅ¾*0å³9Ź9Ż“įĄ;Śųæ/ZŠSZöēēįB¹E¶ķ»Į7 +ŁŌAŪÆĢĪ·ņŃē7ÉOĢĶOüŻūē<µćL×ĘŌÜŅĖ:ÄņņŪ<ćY?+ęŗėVĻÄć/ņ±*AŽŽÅ·+ŽG÷Ü4¾¶:*ųüŽ54-8, +.18.¶×4ÜÕ»NŽ<Ē>Ł@0īį2R@>*X3Įķ?1¾Pöżćæļ*ĘżżY22;żļ½õ¹Ć¶·ä·Ī¶Lł*,U+ĪÜRŁöÓüŽ0BQ> +BłųęæŹŗ7FśÜųJĘ¾Ą941ļ2µģ>ß*ļß»ŽUłŻõÓĄ¾J5@“W³¼QEÆ6óĻį½QŹö@:ßśWDPYLJ½š*AŽŽŃK+Žū +½õ°J¶**ųŲõX-,2XĘ:KZJęĪÅŚė?ā¾Ż=ŅJ×Ā2¶O6¼õD*ņ4żAQĆ-īūI¼KDĀśÕżĻķ/,0ŪUź1Įńł1āQģ@ +KĶżÕ+ä3/0IG»6=Y-YF7**Rņ¾:ŽK2ąĖ>.@ŃŲ·2D@ūÕC4¾ÆBė>*ĒRģēP2MøųXĒšĶČ*NYīŲÆųšń·;Ęķ +/CöÆX*¶żķÜJC8*¾ÜźķąĪ<ŹĶX»ų²J:āī“³93:öĀ?Z@>*āW5+Jśäø97ī»÷+ĪÕö½·ZF*šżż5,Lā½üķū +X;øĀą½OĘC0ĀߣĆ8ēķĆ¾ĒPæ×ÕÅ8āāéŽS¼,Jķčć鏶2Å4=Ć-±ķčX/0Cżóß+*=ōćWäAĆ¹Ų3įR9Gü¹ąĀ +I+32śģVįāćÅńÆ*AäŽŃK+Žū½ś»A·.*ųÜ@3,:ı,HŹ/6Åå4°ŽķĒUŽVL*Rļ<,¾8SāHDŽQŃ.:ėģżšŅ¶* +ÜżįANĮĘżųŻ·ŠD@ó»ć·Z¶Ä+ĻųŌĖŠÕBüåć¾·L+ßQQY»Yą-īųī*¶5FūײF.KÉŻÜąĘ=QAL+Źż,ļ*Ę0O“ +7OķčGėĀÆNQĶūGÆąĶ¾.MDź<·3šŗ6ŽĆCÖÆX*¶90īö²*ęŁ÷B2ĻJķC+åå0¾ŲL93:öā?R@>*4*22»äM-Ś +łä*°ļśŻ761¾śżżĄ¾2÷9żńP1įŌCĀ=,*Ć=Č1:öR-RŌRßĢßßéŃ*±Y9GĘĒÕ+ėĄ,ŽB¾Å,ļ2õäō<+3AņZü +ŁF/**.@āÖŲÉčFįR9Gü¹ąĀA+3ÄīÖNų<ą÷±5BÆ*öįłĆ·ńĻ*üŻ¹ņāļŁõģŚ¶µ½VŽēMµģÜ»ņäßIæĆB,J¼ +,*ĪI*JÄ8P<¼żßMIµīģĖ±żż½ÆGø¾ąCĀ=,*ĮńF2ŅAĖ:æ»żŌ-ä3/0IG»6=Y-ŲO.¾×¹9/Ą:»ŃF2ĒāÅæ+ ++ä½łÅ÷SKL*R:=ėĢ“ĄBŠŃÕ@ųU7*øI»éŌÆSčŗ6Ž<Š+ė1=²ČūKą-,¾īõčÖŌó×9¾ą·8Ā4NAV@ČŚ½@.ä4 +·N9GĘĒÕæɱ-J³ōśE5š:8ćš-¾HĖÆŪ65ZüĶXÉĪPNR8.9¾ō²=ĀĻHøÅĀ÷KK¾Ė0TäÕN³Xö3ŚĀ²RłFļ¾æ8 +żĖŻKńH/ęVūź1ŽīV5,÷ēד@üĶ?æ·LæßQQ9įāéŽ?4,Jņ²ņ>Ó¶2Ą8K+ĪD»ņX0.BżĒęķFŽDÖß>XŠåł70:Ģé.Gą¾ŠĘ.9šą+VBGĮĒ½V4Ź=4Ź»»ņJPÜ0¼öD**Āö+*¾ +Į¶ņ5:*šÄĆš**¾÷ķĶ9*ē:G»*ĪāŲŃĖBÄųęļŻ=Pś0Ę¾śß5F“UÜB1,Õß7“·ėąņķZ¾Óü¹ŌłĮĄŹõKīŲWēų +ĄżŌ@Bē>BśņņÆŹŗ7Ś·Š×:Ö*šĘ.1KĘā³@Ņ*ČāūĘĪą¹YĆøŽJBZĪ6=øQEÆ6ēē±½QŹö7:+ŪĶ8?ÉŪŗÆXö× +ŻM*¾ß÷HĀšM1ĘNģśŻ?0ä3/äÜHø:=Y-ŽDQCTźRņąKNø5Ņ:?ĄÄ·¼Ē.Ó“ÜCIE2/ČZLUķēP2M»ņŌæĄ%% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 0 di +/sl 60306 string uc +īżŻ**ÅżŻłŲVŌē=2öćE*šõīā-*0Ķ,¾ąS?+2Ė·L:ŪQÉŚŁżBNµßćZöēēįB÷D¶īÉõÜō8ŻłNäą>ķöA·=Ā +;G»ķč.WU¼ŃęćźšĄĶāŚ+*RÄ*F¶¾×¾ńI¶**JÆÜ1ß,÷+6ō5OšŻT-ńĀ,±Y9GĘĒÕæÕ0SäBĄDēKKJ:DŻćĖ +BÄųęļŻOY²ŗWRüś/üDÄYä@ķ@WZšÄ7łśā°üŅ4äP.0IG»6=Y-õĖĻ4?ĄLßæĄ8Ó/½ÉEÆ6ēē±½ÉÜę÷ŲJģÅ +OčNüĶć0¾Ž=3JöņVüŅDBē>BłųęæŹŪ7ś²ÉPJB*;5ć»ģĀÆŅÅ8ż»,MŁńĄCQµUÖ¼Ī¼Žµ?æZō°ļéųQ·ZŠS +ZöēēįB¹E¶łģYŻXĖE¶õłŚ3įĘĢHłł.ÄXå/“ŻøŠVßęĒÉ“*¾ŚŗŁ:ūPNF6PģŌY.Ż>1PUūN9GĘW²=ÄĻHøś +ŁęPøėO*üK*óÜBŹPŻ“Ė*¾ZÓ*éI¾Ń÷*ŁŠX-,²ĖūķņæC¹æßQQ9įņėīP,Ģ6üē-źÄ+2MøųüŅ2ČÓš,꣰* +źÕÆöŹ½S:Ź=4Ź»»ņJ“ĻĖBÄųęļķLEēøAÆ>QĒŁŌC-¾ŠĆLüOæZĢRZöēēįBC<ŚYæ>ł¼1ćµ6ēē±½/¹öCģ* +ē5ŹĀĶŽä6*Ų0Ró.@ųÉĻ¾×ÕÅ8ā@Ų3įR9GüW²=7õ<0ŻÜ²ßųČż±E,Įµ5ŪŌ9T.=Ė,ĮĶĢHÄēCßĆ+źŁ*6Mø +ųźYūFDöEŹūUćņó?żQµ.ĀX@Ė9T.=Ė,±Y9GĘW²=ÄĻHøśŁęPųėO6ŗšĄŠó?üQÖ.ņ3Kņ9č.ńĀ,ĮĶĢHÄ3ā +¾ļÅĢŪł.°JļMå8ü½śå±*źĶ6Ś1*M9G¹łCöÓJåüŽ<ģä9ÆNQY0±ö·TŽPéŗ³W¹÷½“MJŁęļśĶNBē>Błųęæ +ŹJ0Ź¹ŠL1Q:-ÄFSĻÉ=Ę+UĖŅęč3ā¾WPT>÷.°ŽųB +śņņÆŗT,16ŽŗYŲNZ,±ZĆA»°÷Ā98ä¾ĖŪ²Sé.䎲XųŅ¶,CZ.AWšGæ6:Äņņ»R7NƱļéYüĶė2ŹH¶×öżQ= +Ź=4Ź»»ņJP:-ŌD4;ē<Ę+é5Ć3Ą3ā¾ļĀLĀę.°Ž2LÆŠµ,CZįĪ2ßGæ6:Äųę»R7>NA³BżQµ.ŅV<łżē.ńĀ, +ĮĶĢHÄ3ā¾ĖÄJKŅ.°ŽčL¾JX,CZ>;R:Cæ6Bø2>ŹĖJ0ź>.4HQ:-Ā;G»9T-/SKūĆż÷?+O=Āż½DßFĻŽźéM +1š»6ęē<7B¾ńó÷Īįąś:22óÜł³ķÆ26NŁłŗ0ņźQ2.*VśD0Bū׶6B:×÷»æ:ÄųęūQ7Nō·4čYśĶY3RBIģż +=4ä3/0IG»6ķį*YG0¾*;ųĪŻĢ+Ó·4*+°ņGµD³ö2*+Ą>Hķ.³11*+Ļ²ÜÄ+ōX94*,Ą³»Y:Ä;G»ōķįŽåK+ +ą8=ĪüążśČ¾ĄĒÆüIĻZĢRZōóĻ-÷H0BME**Ö*āśó6RøT**ī+ę÷CęVL**¾<é³6ZüĄ²***ÄŚéCŽā/Ē*** +8Ć32.ēMÅż»6śF>J³ē+čYśĶY3VBGU·½Q>Ź=46¼øųBHóśB*Įķł>*-*ŠAłŃZJō»įSC:ÕŪIĖ+,ķŁÄæ+š +æŁūŃC:ģ·AC:FÕļĶ0FSż·*æܵć*ŚøĶ¼Ā;G»µ?ļūŗ*;ÓAT½ļ½¼OZéLKWūGĻZĢRZōóĻ-÷Ų/F½Ė¾.**J +¾*ĻÖöē6:õDŪĀ6B-,¶¼¼D-ĀŚP6æūä*0ĢÜĶä*ĖŌēŽā*SßĒūē6Ęą.U02:ĢöY=ĀÆŅÅ8żé@ö¹N*OŠ5Ä9Ż +üšżźÄŽā/M×÷¹LßFĻŽīŃŃÅļ³5:łßĪ6Ū=.ŃĘHĖ+šGŹÄČ+3“õē6ĪćßJŹ622ßģ0ĢÕ7P¾***.ö:ßĘIĖ+; +øTć*K3äü³Ą:ÄņņGµæŻM,²źB·ūĆż÷?æēŁōééõĀĄ·Læ×ÕÅ8āé@JņOJ2źµ**ÖĪõ²0N³51°JZĪŗD-ŅŌ. +---N*ÄŲQ±J²ŲR°J,ZŹ¼D-L9ĀČ+Ą*-śé.Æ6óĻįżŲ/ü83ĪWßöėIń+ŽūÓ,ļ5Óó¹LßFĻŽīŃŃÅļļ4Bī³G +2¶Jõ<éķĮCJŚ·TRBZ*:ĘUśZ0걥840Ź*.4EIŹ+׌ėæCB-2ĀŌ¼K,1šķī9Ģ/ßĆ6ĘŪÜ9AĀÆNQĶūõ²ŗ°U +ŲūS»8C*,AD6<ččõĀĄ·Læ×ÕÅ8āį?ZĆ;DZī¾ģ/:Ä1±¾čŠ=īJ02ĄRF9ä¾GGä¾¾-C*ßX4Ģę6ó*06*.ņō +HŹ+ß2ęĒ62ĄĆ9AĀÆŅÅ8żõ²ŗ°VŲĆČūä¹KT»ūĢRBQ>BśņņÆŗŚ,Ą>Ī-¶µ**ö:ņZ0ĀLFQ°JĪāøB-ĄĢČ=C +:ZāGŹ+×OāĒ62F0ķß*ßANįĒ62JL9AĀÆNQĶūõ²ŗ<é×ĆČGöŁåXFŁ*:¶4JLWūĢRBQ>BłųęæŗÉ,ęŚH14J +>*J14FöPż202½šŁ8ć¾B+üčķ3¼š¾8ć*+,ĢżÓZ2šLL0¾Į-C:Ż¾Ż°0,ĶFĄ5C¾8FJĪÅśWĮ:ÄųęŪøĀQ>+ +ņUB»¹N¹ÕĒ=SĻNĒBLT0ĀOńµ7ĄBŠŃõč;HĻ +.ĪÆįŲė9éå*VķēŚ»I9*Sŗō0įßņWł³ō¹LßFĻŽźéM1šW4¶õŃłģėUŅ7·Lńģ¶*ŽüTē3½õĖM0ŹNCśĀÄ6Īß +ģ÷IÉ+RF°Žö×ĻĘ²ā¾Vōü4=2¼õ½M0źDJ4ŁÄ6ĘäźĶė-¶IŻZŁBśņņÆŗģ,ųI¼ķĄ²XH1¶F-ŌÖĪÓÓ=Ę+éTęXķÓĀµ±ŽRÄŲéŗ,± +ZśĄėóÜT÷Ž-X+¾ÆNQĶūõ²ŗ²ÓÜOéńųT*Sė»,ń:Ü-ķZFāŲOæĶķ8*Ź»»ņJP:-äł=Ć»=Ę+Uś3ćć3ā*»ūĀ +±0¼±D±ļNHæ6:æį¶äĪŹÄĻHøś=ĘæÅŃ,ŅÕHJEņøAĘY³ņżżĮÅXźį½ąĒąŚū-2JÜłšŁŽ·ÖZøÜųüĢRBQ>Bś +ņņÆŹJ06÷VųĘP:-Ō·@¼³YRŽćŽŁŲ×2@ĢŽ6ŹęX,UŻ>æ5¹Z6Å;²HæC:ÄņņŪöäÅŅ*āŪPJłįųC*Xėūżżņ* +ė7/B+¾ę1Įę4¼O*÷ģéķ,įæĄĒßüIĻZĢRZōóĻ-Cæ6BōDÅSŹJ0ŹµĖŁ¾P:-ō×äS³=Ę+UVšŅć3ā¾ÓÓ¶4÷. +°ŽBčMŽ,Cö¶T¾īń>²½VI-ŽÓ×ń½¹É:ńAę8ėŻĢĀYéČ*ļŪõ0įæÄ+,żIĻZĢRZöēēįBæ6RłĖQ/Į8ż÷6Jö +Ė+ēĮüü6:łäRN°½I-ęśX>UŪŻĶ+ņW>Čąīķå*ÆNQķĆŗüN/:N=ė9éå*ŗ³õō3Äųóż½õÜøļćĖėįõĢęūąÕ* +9»1äQĪŻóĆĮN-Ņ¼ļē7¾×ÕÅ8ā.°*÷ŃĶP8÷ł7ĮöēÅLĘśūCJłņóę²Hæ6Bģ¶äLĶJ0źZGĒŪŻĶ+ąR9Ū1ÓæĄ +ņŠėSūæ,öčęßĮ÷ćļ½õ¼Õ:.8ĀFÄ8ÜWć=3łKEļŪõ0įæĄēÆżIĖZĢRZöēēįBĆ72ģÕÉ+XAŪŹōGĆCĘćä±¾+ +°½×ł6°¾ŚY0Ż8īōC2MøųHŅ0Š×ŁņW9éæŽR*YĖś²1šųüAčµæB?3ŹNW÷ŁåĄŽźGĢMÜłųŁŪ¶J¼,óWÉóųMŻ +×2BłųęæŹį0šö·ZIÅŠ+ų·āŽŚÆ,,*ņ±¶+ąR9Ū1Óæ“AŪ²ųŌåóÆ*Üøóß*ę?üĶĢé;,=ßWT¾øÅę/KKē»ĘĮ +öģ¹æķJéŌßÉMOIÖ5BśņņÆź<4·4,Ā;G»9T-ė²ĖÕŌW:źŪŽčĖŁ,Jµ÷½ŻNODōŚĶHŠSĒż9/ĀHńK8ą¹ī/¼- +ėLÅń½<ßNŃŽźéM1°5ėĀÆŅÅ8ż@ŲĒ¼¹3ĮķGĪ²5.¹åÕųUĄ*¶ŲżķóJKåG¼łģėUśżA+KåŗĘĮöģ¹æķJÉ6Ųż +ĶNBUH<øųB“ĻĖ:ÄņņūR8²ō½OMµ½Zõ,Uķć2¾Pśżū;>šŚŁµÜÕµćż½>:ųŹÅ¶2ķ»ÕÕ7PĶŚZLIŅ2äøÅN9G +Ęēś¾·YėEū.°¾ŹńōGĮŻEšŚ»ńĶJ0ʵģܱ¶0Rūø02M»ņ°¶ĀÕŃ,0IŻ²ß“1ōÆ*ÜųYĪ*Røż½3¹3äFöąšōÄ +.::ņŹĶņæÜU40*öT-6ŪWśč¼R;Ź²46¼øųBPń-ĀEēŲMõ*żļ6ŽäQéDżżų/źżżĮÕÄ>Ü1*ŲŁ³ėłÆ¾żżµėŲ +õė3BÄņņ×ĶJ5HĶ42ĄÅ“.ōAIŽUYģ4*ŪćżCųN@Šć<į.åżö½>;4Ļŵ2ķæÕÕ7°ĖĢßęÅQ*äÜHø:Ń÷+UVķŅ +Ų3ā¾ĖOµčš.°¾ŗČE?-ā;ÄéPćZüĀYŲZµQŽBčMŽ,WöŗTŽ²ģŲPķōāģ÷Óō»D¾²³żłą6×¾Ļ,/ßć4ŻµKZŲ +Ą9ķ:ÜõģXEVŁ²×Ź½S:Ź=46¼øųBPń-ÄŗY;D=Ę+į»ÓĘŲ3ā¾ĖPSāņ.°¾@5>°śöŲ*ęī³°ģĢ¾ÆNQĶū.Ųī÷ +Ņ¾Ó?B5ÜćĒŪń=ÖŲ¹õ@Ą;óżė9H00ĢMÓC¹ł½+ēSė¼ŗĮöģ¹¹ėZįÄSāüOæZĢRZōóĻ-CĶ92ō,°ö+-½Ų¾×3 +ā¾·IUŽņ6°¾N4JIæ6:ōņVŚG+įR9Gü3²ßńS+Į½DĢ²É=¹AĘY³ōōÅ7=Rüį½L½N;ĪV/+ą¹ķė³-OŲäüÉųŽ +VńĘęēįBĖ9BõŚWĘŹßņēY2±=Ę+±ü>Āå3ā¾;å2Ął.°¾ŗŃ=šō.2MøųHÓ2DļĻĄWŁE×¹¼,U¹µĶņŅR*üĒ°B +ńĒŌēV2:Ü-ķėD2ŻÆåšĻč9“-äÜHø:±ļ+ņėńÓV³ķå*šäöTŌćåÆJļńäAU@šå±*ēńÜ@UŌłńÅģ·ķČ³źė4B +ÄųęļżŃĪåŹ=Ś?źģęģ/šU7Gżäš°+šAT1÷łĢNšļ-¶.»õ“7Äģ?Ą¹ż>1PD/0IG»6±ļ+VĖŠ:ĢĶķå*“@¶3Q +ķåÆJļµ@A5Qņ屎ēµÜ³6Qłż÷ÉżöĶä*øķŗSEŠµŻ¾BŠŃõLHŻŃ,Jõå<3ņµµ“õÓŪżżŻAēćņY.9÷PÕųæ.2 +ķ»Õ@7Ä9HIÉIÓ0PD/äÜHø:±ļ+LÜÕä6°JŗÕ»VųĆ*0ŻõŌ³=Ę+±ķEēß3ā¾æõW±ńį?Z6óĻįżSO9šQBVB¼ +¹±¹SĖ»6<·µ9ż/°:łźėŻīKę¹³ēZīÄļUūS³ZŠSZöēēįB792łģ±Ę+ÆĶüāŽĒ6H±*°ĶHśFä:Å6*õA°÷V+ +2MøųXÕŽ94+ŚÉī=ø*õĆōÆ*ŅBµńõ+J@ĶŪײJ¾Ż¼ÓćüU³ZĢRZōóĻ-×Ć“ąBčMŽ5ėNõŪ.øY5äD»5±¹?Ę +YéŁĮżH9*CÉńäŠæę»4óąÆÜÓ3äP.0IG»6=MĄÓĻįĆ+F½æU,Z6ēē±½±÷ö1F+ė±Ż>4Ū»×Gš=RĪJ½C6ŽÜĢ ++÷Ļ±ĮŃłµ³æĒPæ×ÕÅ8ā>“ŽŹYŌĒ¼,CJŽ1éŹß6ĘõŪN=¾+įķłźŲCŽ?÷Üųõ»6BÄųęļżŌŌ1÷Ł1CėŅ»ßūĢNü@IQøXW<ĢN6Eį½G-ŲCIFĒ¶Ō¼G-Ź½ń²ø<» +ćūø0BūäŃ*WųÉśļ6BÄųęļż×Śå¶Ķ?±Uéö½@E*śT+:J¼øŌĖū×:Bē>BśņņÆŹ-1Ö»õ.AæÖźśū6Jø¾ĀVōü +ø0BŗķņŽī×6ü÷6ęäņĀŹŲ6żó6Rõ½ēßęŌ÷W5BÄņņׯ/ė*%% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 57 di +/sl 60306 string uc +īżõ**“Ķś×WŻö3F+*üT+Ųé*ĪŚVßĀDżHDµŻāZōóĻ-CÉ8JėNÕĆDł¼0ī²ÉēH?¼H-4ü²Ćį@÷¼7-ŹŻłēśĄ +»°żŗ0:ųēš¼ėøé,ÄĻHøśż@öKÉī@ĻŃÕUśż¼/*Ž53R1ZņŠW<÷¼DIŚ3BśņņÆŹä1ŹõŌT¶P:-“DQ>µ=Ę+¹ +Ź;Čč3ā¾óć1/ś.°¾čPĮńō.6MøųźQĪ/ķR/6ēZÉāó-żQū/@U@¶ģIA-ä3/äÜHø:=4,Ļµ¶0BJģēÓĢK0ʵ +»ä±Ę+׏X4ńA¹/Ü?+įR9GüC²ßA°+ā¼·ŅZšųĀżń4,Ņµė>æܽA-ä3/0IG»6=4,KģūW3*ś14JĘĮĶæ6Ę» +šō±Ę++ĶśæGFP*ļģśW3:ÄņņOļ2ķĘ/:»ÄéĄēé:üĶV2ĪH·×2ŌūŪ2Bē>BłųęæźLEÆ6óĻįŻĆ“Š9YąśWDÄ +Y¹ö½“MZš“3ųś½7ZĢRZöēēį¶ŪäĘH76MøųźķōPDŻBĪūćß@ūč5śĶė2ĪÜQ×.øżŻ.Bē>BłųęæŹK0ęņäÜ= +Ę+ńHø±ń3ā¾KIŪCł.°ŽĘĶģŹ¼,CZÜQµ“Iæ6BÄųęļĶJ0ÜŠ4ZōUē/Łć8ŻÄīżŚĄJ÷Oł³7äܵ÷ąŗYXŁµ** +ŌäėE·ö9Ź1ķƾßQQ9į.°*Ū5ć×ōżłKµā»šVF6īų0V³ĻDIQĪ3“×QĖÜ=Ę+éÉŃPņ3FĮÆNQĶū.äīCÓ*Å9J¼śŲųܹ5łäķH¼łĢŻ»łō÷įå9Żśåż½OĻ×ÕÅ8ā.°*ĖńÉP<üłCZÓ÷7MDæ +6Öą*ĮėĖJ0źV»ß@Q:-P>ÜZÜ=Ę+ąR9Ū1ļ±*ęŗYGÉÄó,żå?.ĘģBC,QIÖČé/ņģķģG¹ÓH»ņāŅ°Æ*HČHø +.Sģ»łAZĶŽ;ķŃŽĶĒOĮĶĢHÄCā*ܼNÄLõå±*ėńōLĘąłßĄī·Į2/ńūPJō½K.Mų¼0Ī·Š:āÓ»I-Ä;G»ōķįŽ +¹L+“ķFS¼×°Ēé+üĶV2ZÖV׶X¼äĶäHłŌāĮ-7÷ŲNIĶ0¶ńāĒłÜŚöšŪÜöļć¹ł@HŚŌÅŪ¹?QŅUĒ°ņRĶ4?ø³ +éŌ»WńčÜĖ½RRNųęæŹJ0F½ųFćOLÖ@łįCŽöēÉŚ@3āŻåśōNļB»æįLæŚŹüó6Rš,µć,×ų÷6ęč,Y7-ēŗīķē +*BÄųęļ½Ś,ŻS.*üT*ĆÜóÖQįīėŻżŃų+åQ9ŪR×ČC1ż6WŹłåų81IŲå>ÕėGG?īļIRŹ-.īī»AXŚÓPÄ6GßÆ +.?š=Ę:MQʱéŪ¹AEµ8ĒEµģFöB*²8HŗųōI:PDśņņÆŹą0ŅµŠĄ<;ŗśŗ0>µČÅ@Ēźśŗ0²³æÅ@;WūD0Öūčė +8OTéłó6ZöÓµOŲŅB÷įBZ6ēē±żżĪXČQ¾öūćČNķģūIĶ8*æÓ*RYDFņäHĖOIÉX±öżżŃåWB@87Jģł,Ķć0G +żäĮłģÓĘĢHĶ@ėB»į°ĻŃÅóSźŲõXģźż9ęGčŅS;KĖDĒŲÕĖAĶü°ĘĘęēįB182üśM×<ÖOĮżDµķ3ā¾×½ĖEö.°ŽŽŻäėŗ,CZģYńÄü +Ö/6MøųźŻżĄQĆ,źŗÆ»ēŠWįµZ8.XÖż=5,OµėC·Q1Č +:PĢLCĄāē³čNųōźÓÓUįčÉUĶHG6J³=å8Ū¹ųIĀ2Ė»»ņJ“ĻĖ:ÄųęOõ½ķĘ2F½üģ?ä<²ĖÜGł½°KĪų°O4ŌB +ś×ĒMPKņN“¼DA*½ēIÆõė»PßSQ¶·*ģ¹ńŁ¹SčMQĶYE³ĖŚÆņęēŃņĀÜŪŁBõ9LĀ0IG»6Ń3ā³³²čEZ6ēē±½ +UżöK>ßŪłHŗėŅNųöėÕżŃų+ŌAVŠWD5±½č¼ŠŹę.:2æ1ōLšóź=čėŁQ9Høõź×¹ńźĻ±ÅģŪ÷×ÜōģÓ:3T>QĶ +UŅRģBĆ4OX³»5AōüOęŃ×ÕÅ8ā@Ų3įR9GüW²=Ūų<ģÜ·Š<óOEōŹęįµĶśĶV2FI·įÄĒįűƎüŌR.ēą3ę/° +żŹXWńÉ3:YŪųņä³å“ćēĆĢėT>ć×QÕøŠ³Ż<»¹öīÕ¼µļÜ»ł@HŚCŪPĢśļĒ²·2BśņņÆŹĄ0ĪłåL»ŃĶJäźÆĻ +äėŻĶ+ĄÜõč.°*ļ±YŁŌ÷*ż:¾ŃKOęŁō»ķŗĀ;G»3:įķL+ĆÜ÷ŅNĄņėŃż±E,ÓµŹĢS6Qõ/0·łŽłģŪČĪHøĢĢ +ĪÆņŠ7ŁÄĢčѶóģąĶłĄOŲŽGŹ»»ņJPÆ-ÖŲP:³æŻĶ+EĖ:ĘćõSYżY01Q±õåÆ*÷YĢ³SńłīÕņA-DŌCüéBZ6 +óĻįż1ŽźąŁß÷ńYŹ<8IĒņ3¼*YŲūē1VL-*Żé?¶³,ģģDG¼“3/żż·.HAæUQZä5UĀ¼Y7Löżń9ŻJö÷įY-īĖ +5ŻŪÉŠH¹éŗײņŌĖŠåĢH²¶óėÜĶ=ęķ/ģźŠÓļHĮ2G»6=Ę+³½×:C.×IĶ+óüÖ4K.ļIĶ+Ā¼×ĆR>ķÅ0:ÄĖ +ķå*@@3Ęęčõ±*õL.²ŌõłCJ6óĻ¹ż9Yź.ŗ±ĢĻD/±QĶHŽµ±üĶč1FI¹ųęOŲ>Wė6ČķŹÕü5½Hµæņ1åčÉśČĒ +āė¹ż¹öXŠ43øĄéā;NĘXDĄXĪLĖ³éŌĆģŪ¶åÜ.JŪ¹QķÜ»łE±éĖ·LąWZöēēįBæ6JņüÄCNżÜäī³æ߶Š½½0 +ÖÖŻWYėI“æ28¹ÉķÕßÕ±:.ŽŌŠŁÓø,CJ6ēēÉ*0HC4ęÜ4IOŲ³ė7Ü-æÉļżŹ¼Žū3āQķŲQĒPæŌĒ2Żß²Q9śP +4=å¼øķŪLēēMåļĖŲÄXD·ųęS»>łģWś·ÕČĪ×ÕÅ8ā6°*»5ŠĮ<ūńC¾ņ»U×ĀšūCJõÜKśLų¼0:ųą:āĖ»I-ę +śT2,łÜĶ+įR9GüõÆīĒĻ¾EåūÕR½į“ė<ßņWÅżY¼**ŗŚ,XĶŚW×°7±·@?“8¼8=ƽéܳIEŪBųĆÕĄŻ»Aøłī +ĖMõ»øńēÜ·ļćŃ9½üŗŲIėøõVĢ>ĆČņ8Ć4OēĶRĻNļėąĖQMĶģYŲĘ26¼øųBäJ-0üTČYķŻĖļ1ĶŃäłėÕöļĮå +Ęńģ.Ģߏ±NŲ¶,ą°ÓCHėś¼02MøųŲĻŌøĪWŗóŌ.óWÉPĢF;ÅģKä-½3÷½šGī*ēĻ=QģŲ²éŹHĄŃĻQ-<ÄFµńö +ĶĢ*ļHŚ¶.*ī½åÓ>@IńŪßĆ¼ųļä»ÕŠŅĒ::6-īšRą>SņéŪWūščąQńōĖEµōėŁ¶īźŪŃ½Ķč»²,äÜHø:ŃÖ+, +Üųļ¹-¹>+āłņćSś<ŹĢJłļ½üÓMÕĢJ0ŹūčĖ»ŻÅ+ąR9Ū6üļÓÓ¾Õ¼DUŠNÉāēŁ.żå“.ĢģÕNĄLĻéɼųJ;Ń* +6**ńõPŽŌČ¹źūÓµģ3Į*üWźæĮåüōģŌ4óēńņ<抵źÖÆų>GĄQõüŪśŗ>ųRø7¼øųBP:ųE0Ę=å¶ū7Ä;G»ōµ +Ś:IĮCŚ»õŚ·õĆBU>É>UD»åR9.äēżCū¾éÉ?TŠUō¶ŌPÜæŃĶ?DĆ¹ņA8ŽćÖĪ°÷°Ą*ŹIź++ģüDB½ĮŻōņŪ¹ +µģF·šÕĒµšāĮ*ŗļŁ¹õXORŠLQĘ:NÄų>Š>SĆ¼ŪŚBµń»Ń@õ@ĘņĻ-×Ć“ĄBčMAUIüLS¶9é8ūWĒ./PÉŌ6PÉ +PĢG¼÷ų½LGZśęWU@T+ē3ŅÖóļEź¶ņŗÅAĄĪ¹µżJSĘ÷ŃWU,ŗ±Ē*ņVńĒGS,J°ńŁĒKōŪŃķPēšä·½HūõTŠL +ÓPĢLĒø>D8ŽÕÜėøōģćĆ1łäÉ9żėŗ·ńéĀF·ń1ŲŃS¶ĘęēįBĖ92üīŃĘ+ZIū6āĻI¾üæ6FČą:*ąĘĢÜĢēŠÜķ +2ATģ=JĒPĒŃAOżå“.ĢXŲOĄ.ĆL··Ģ-KÉA¹Y@²ōõ4ŲŅ<Žó+*Z³Æ*ĆÕ<**źGŪ,įķū²ķĖŌUĢŻłß¹łŠĀĘĢ +éŁ?6G»ēXĻįųņēźŲõXõ7Į>äÜHø:Ń÷+īH1¶3ņāŃĘ+Æģŗ4C:Ž×Q;-Žłó5;Äųę?7ü·-¶ŚWģH»õģżšV“3 +54OęŠRēęććżēź¾Q5@čĻNĻ*ėąņŲų0ķŪ»ś·ŌĀ÷ŁÄ;ū·*Ā9Ić²Ė0GŪH3źėA0*Ą,,ZŲūRōĮYKÜ/OŁöZI +ö1¾żųĢĢ¼ŽĒöFÉ9µ9Ż»łīėĮQ9ŽÅ+¶õņæĶX¹µU¼ĢĶJ½įŌ3>ÅĶł=ęĶāܽ5µĢ²Čō1/0IG»6±ļ+FĖ:ĘÜŽ +ķå*“0QäYńõ;.EåČ²BśńC¾ņE-Ų³öūõJõĖĻĘUś“/2MøųĄ*żą*¶ńĖåš.T>RĆXķÖ±įŠ/E“ŹĘMõŲżŃ3+Ż +@WÖZįŠ7ÉäÕēŃSŅRĢUõĄč3Õ8Wć·õ/Böā6ū²óūż½¹ģæŽčŽ+č***DżĶŻŽä1ŻĢŚńŚŲ3åęė±õĖ¹óŁ¹ķųŪ +QåÜGŁōģŁRŅęܱŁÄ+öÖ»łŌOŲ“ķÜÖ÷²ķÜŪYŪµŹ»»ņJäæ-Źü°»ßXļōüø0>łćē“7įūFUB÷Żē°ßį7żó6R +ķ±ŃŪŠĘł÷±ŽčNŃĖŠCņõ°*6M»ņöķŚĮÜ2µĀĮ÷čNćė4³čŠNHčŌ³śöēįÅĶīŃÕ4:.¾*š=öX**Ž-@¶Z.+*H +VŅNĒLĆ4?7SŗČ½ųĶ-Óķ.ŅĮķ51ÜÕżż5ņ2*īN9H:/8Ī·śę+ĪŗÆ-Åą×SåęEõŪ¹ŌO¼śŌ÷WśöĻŁN8I¼Ģšč +Ł¹IMµķܾµŻ*Jļįå9ż½śUO2ĮĶĢHÄ·Ö:Ō@SM<Ģå±JļI@3ßŌŃå±Jėõ?2æŚŲåĻKė½?1æŽŻõ±*ĆüĄ¾°ńį +BZ6ēē±ŻQŁÕO9įĻÕ?BėÕV¾ŽöQĄ*ēļMęÓWśéŌZéņļ=öT*ŚÜZß“/ɳČ8õM1ĪNĄŅŹČć¼Ļį×õÄößŪY0°č +šżżĖ-*+*Ī¶ŗ¾;.*¾äHĶAåķūīŪ¹34NåóĆDĆXŲÆēęÆ»ųŌŌGŗCūG¶ąÆĻŻ½,±Y9GĘS6,čŹ/LWīõ±*7¼, +Æ×÷łC¾ņ*Kēźū»ģCøZ*³¼9üęEG=×DIü4śżIM»ņĢŁŽM>+QĶśļą»łĢėčķŌG¹@MĆ,?øĄ.µ/E@ėÖZ¼ēÖÆ +ēÄć4?78¹@VÖ³Ļ,ĢĪLĻ³åģGŁ?ģėŁõšč3VÆš>īöĻYŁOĢ¹śņGQĖF³LżżŻF±M*åč1ZīĢ3RóÓ>9ÖJŻ<¾į +µĶF·īĶAYōĆłģŪŗ¶ļßÉūėŌ±õ¼ÓĒņTåS>ēÓ>ĆXėTŻōGŪų³ņ¹5±ĮĶĢHĵSĪČæ÷EQAŹPķņĶĶ+D9“ĖPņå +ŁYėEŻĀĢRņ1Ū0üA1JųŃI÷éųŅYČ-ĪłÜÉ>ģ7ģ¹F*Ā;G»åĒļSČ¾Ķģ¹ōźŲW÷ĻńöęßÉ@ÉFŗņ2ÜZŁä,FIȱ +éĢWŁBłG7ó?ĄŲÓņīŅ¼+52ü±5@źĀ·ČēīĶĄæWŌ¶ėīäģAå¼øšą»CČŅę/4ŅĘ;Gæ:?øĒFłŌėłÖ·šÄŚ·ńäå +*Ź¹õņ߯.õ>Dī°ź9ĢHēM1°0A±āó;ņü¼0Ź»šē²P:-0H÷SWķ¼W;¾·IūčīUę¹?+2»ķSÓėŻĶ+ąR9Ū²ņšū +ēJÄļŚÖ<2šQYūĪ»?ĀXķ-3ZN+*ĪAŹČT-Öł@RĒłįźÕ9?UŗĆņ4źļ¹YŗõWĢ4ó·øįPēMQX>¶ŹĢHČUķģ/¼E +śJņņÆŹæ0ŌÓæåAļā.š¶0,ĒĢÜUDśü=Ī2ŽÖ=/-ūĻ0Jń½ćęUŚ<ŽÕ,ÖTī;KĄī:*ZņŪó°JIGTµV;¶śņ×¹µ +ĢFµģŠųņC*Y»ųķŲ³õDNē³š²RĻPĒ<·°ĖF·“¼Ś·óźIėźéM1°5ĖÅÆŅÅ8ż@ŲĒ¼½ĒÄRBĮÜ?:ōÅD¾ėŃA>6K +üöM,FS*HĮ+Ö²ē=Ų²3Żłµ:ŽĖ1EõĮŚ²Į»¶łäEµÜV>QĻĢ=NÄLSčāSŅÉ>·õģŲ±EĶōÉUÕXŪöņčÓĻµšę×¹ +Ķ5ō,26¼øųB“;šBÄņņ×ķLE÷ōXLŽł¼1ūŅ³@>OŁ¶?***:āą0°óēµæRĢłģŪWæŠŃAĖŻĶ@WŲ²ń-*ĒŠ¹,ŃLåBłĶĄöŠE,ĀĶHßÜäłø·ī±Ļø¶Ō +¼ó+öÓNĒ<łH“,ŲŁ?õQąÅ8ÜõčÓłŌėøŌńN×óTŠNĒŠNéę»Ė+ÉŚ»BJ¾ÖæM0Ģ³8Ž.JųµJBŽźČKWÜĘ4JłŽK +ĪUŚĆ¶,īY3<Ī»ģ°Š3»ā+4½<***N¹0.¾żČŠĻ±8ļā³*żFAóę3šC;O½µÖODŪQĪ“ģÜ09JņęļMÅęĻńī*P* +8*Žļ7āū+*QįįĒŅ²P½Éąå1K*õ4Ā±Ał÷W1JĄ0:HX+µL**¶YĆŌCK¼½ŁAĀŌņ2ģܹšD3:Tüżµ8Ł“»0*ż5=¾ĖēćąĒ»-¹1Ž½E,ęūģRå4ÅśöŌųLĖ4ć1*ĒÖ³UüNĮGĶ-;ģ»-Éę +0źī.5å*¶óŪ>ĒĢW6ŗPį“īĻĘĒ²żüĶė2Ę4E×°čÉüķæKH@T1**J0M93:WūŚī±²Ģ>:ÄÅE±µX²PXē³ģĮĆL +¶öķ7Č+**.IÜĖŁŻĮ±ßĘą4¾ū6SĻėÉŽė¹½»ŌPĆW·Q0/.Ć7KłõżźÄŽŽĘĒćüńĪŪ-/:ņ+.*¾ŪåL¾ŗX=±ąŚ +å/Žųą-æłö:Õ0*JŠżżS01+6CN+·QĢŗŁįRĄOĪäķH=õ>ł56*śU+*üłRZņÓĘĒ²żüĶė228J×Ä*I2+öćĖĶ +,:L06īAF*ėĶü²TéēŠ-źéŲ.ZÓŁXóū²XWķÆįŲÄ-öč*NÜüI42*2**-ZōHĢ-ćżIüēéĢóY4WL8Śłē.74G +ĖŚłņŌŽźMÆMęüūIŁ;:Gø°Ė÷ü8,īUĆ4?ųŲ÷WH2**ÓYĀ¾CQC3ŪŃĮģÓ8ZÓĒĄźśņ.ŹµĻĒ20ż¶šŻB4?TĮĘ +YøéÕS*Šė»Ģ1āEK¼ĖFMĒÅæńµŻ·ķ×6Ćō?ņļåå/ÄH6EĒōķż×7æß+>ÉūµK¹Å<ŽÆ***¾äżż/;**¶Ż2+é6 +PDŠŪ6HäłXøĆĶĢ*ŲõXę@Ć-+:S²2N51Zä+*¾ń¼9āóčŪVM·ĮæŲåĢĢHĄÜńŹ³±908KłõżźÄ¾ņęĒ?åį²ł= +ĘÓ@ĄĀG*/øüŃåģGż-*šöļįQ0Ē:Ęł3*MńōP;²ÜAÆĮĀČFEļõ·ńD/3VŚC¾VĢ9I2 +ēüż½É8+**Q+ĄIśS*Į-**ńß*“6**¹ĶŚ¾78OĘöäŅ +<60R@ņöń7-»ŹĻ6ĪĆK.-·>ßę¹A1**.JR8TZŃ/ŠåŻAĮ²;»īö÷Įķč:ŚŲÜ=X-±Y0ĢNłõżźÄ¾ī*ČÓCė¹K +ƶ¾ŁĄ8ÄS*=-**ņß*“6**³Y6ĢŪ@=ĖęģN8įė:¾³ĻūC.*Z½HŁäÜĪZ6å+Ā2*/*ī²<ŚZ³ć²ćŪEŲ7¹¾ŃĒ +ÜŠÖµģ³Zōߎ;²3Ä1śģÄ**Źß/Šż*ŃMQ@łI+ó8“¾éĮļIOĄżļ*ī*°³ūżżB**X¶ŲWŌSÅęōźYó¼?2BįŌPĻ +-“Ōąćī9Õ*“Ė“ē°õß*Ģ=QĢJ·šNµ6.5+29¶F*ĀśUšõYĪćQŽ×µÄS=ēÜŃĒå71äÜ6FĒōį÷ŁE.Į-R@łŁB; +d +1058 57[1 0 0 1 0 0]sl 8 false 0 114 di +/sl 60306 string uc +ī½ķ,¾±.Ņ·+ZJļT+ńĢ²ÖZ1<¾üżÅŻ9½ĮżXĄJOBšī=Ž+-øźUńõJZŌMļĮÕå1MķAčį÷µ +Ģ.**;J*>=*üūBK5TĀM-ąōĖAÜø7įÕAD:ķ7SłZī2<ĘķL,**ÜĒ*Žµ=Ē¶Ö+PÅĢVō½ĮńŪŠ*Æ8J*¾VPø4¾ +**Īš,/VķWR¶µę=ÆšUWRīÆźī.¶ŽŗµÜŹü³5Öä?J²Ž×Ó@öVķQŪńŹ³ė³ü++P9**NĮ+6S**ņćJ/ÄIŪÓĖłŅČ°²,åķłņįJ +Sąņ¼Ą>½+Bł°īNČFķēŌW×0żė×VįņĆ0Jøš°ė4ŌX¾ā7ɳ*Ž½OįÅĶŽVŻÄćū41E¾ī*Č?é>ōņ>2;*,**ÄĒ +*:=:*źŪ·+éłźŅ×?æū×FĢZBJ:ņā,Ń?:½»-*½,5ĪśĻQÉN;ų¾*M1*¾Óß*¾ņ×8ŪāXĻQļ.KŁ5*ńåX=G»É +×ķL:Ź»B¶.Oģõ8Ą*.ņöļ=Ę.K*,X²*H=.¾*:ó,š4Ę·V1Jæ8·,÷K*+Īāū41EŽņęĒć¼åĮXJB,JŻ.LZ+OģÕ¶Ö+01ĪVōE +HņŠ×+Q3ŽDB:BČŗTżżŻŗėŲÕQśäöėDćūDüÕ±ņŽ×ÉNĄYŌNś°×Sų8M½Ņ,*żĆ-:Į,»IŻ8åüÓ8¼³ėżķčÅ+ +2ÄĻN7**Jńż÷±FŪSQ²3ŹąĖŁHOȱÆī5S.±908KĒńŪĮ*J¾ĘVś.LK2¾V57ŽÉZ¾:*ą<øļ,ü42*Z°Ó4/1- +¾<4I.:Īāū41EŽņęĒćüµ¾Ü50ī>+ÖÕ1***øĢĖ*ąõ8PŅŃūŲFüņ;ÄĻŽR>39æ:·÷Æ8ÖZÉųŠ½Ģ0ģŁKJ,Ūż +żķŗöę2*DżķH*ĖūŲL5R-ź6Ń:ÄĆõŠ2äÜ6FĒ<å¹1ß*,1Ē.šĮĄ:2×@DJ+C*,Ą,I=+¼Č2*Ē.4ŅA5-īKĘJ +.<Īāū41EŽī*ČćüQJH¾żP,>Č²öżńQHüÉ.*Ęó0/VńSšŲģJ@*żē/īGXŪ±TéSŲĻö?.ĻĶõ***¾:ĆSR+ĻNÖ“YÕŠįõü?Qt¹5ĶÜĢOBł°īNČŚķG.2Jā¾æĮ94ÅŽāDæś<+ +æŽÅ3°,K@»ś.ųSP*ĘĒ³éŅŽĘ°N軯ŅåģJæ÷ĮµÜ׎“.A;Į +õBŽźĮæMRš¼õ:>23/Ä0»LF,:éö3>M-,ÕAČ6*ĻŌ3āåY++JĮ7CPVZ+°°ļĮ*,ČŚYļZ-Ź8GןµņćO¹/Ę< +***ZF-+ćĶ¹6LJ:ŗķĄ9ŌB1¼ŹFĆżÕOLÅŪŲõ8å=·ģŃ.Jį»Ó.Ć/ż01**äĮ+āB?Ė=EU¼0āW=G» +Ńõ>åäÜ6FĒ<õ¹1+,2æøNVĄß;2×P:I6<¾¶¼å.+:=5Iæ°ČĘZV1*¾+2įņ3ĀJNJSU*,ńģĆ=1J¶*ćŲŃń²ä +MZķW7*¾ĪSļM:Īäēė<ęFż?¶,ŻäĒ/4Bōļõ7ń¶śWĮĆų½BKBŲÕ·ł³STŗł½N<åēõå,ē-ĶÓ6ßæ*<įQéK-E +PKŅPó+W½*8ü°Ģ6Õ8ü6üĢ°åźĮæMRšŗõ:F2ßAČ4?4F,:éźWŃ*+,Ā,.0*Ją;ś?:*NJæą0:ĶĆ8I¼2JJß +1öćG5å@ŽņęĒć¼ł=ķėüŲß»1õ73ĄĄ¾**īņĖ4JõĆ+3V¹1ŃĪÜŁäĄJOÖōē8YG78ōäéT;35ń1½·éŁGJņ2ģ +Ėß7Fć²ĻĘŃżż½D<ö½ļö<>12:3ĒšB0ļĢłĢ*ŪY8ßļ¹õ»÷TĒ45-ņŁD/±Y0ĢNĒńŪĮ*+ĀŽZÅNJī2ŹVMµ5, ++2,*Ś+3øö3FO.*ó°ēŃVēņĆ;:.@Īāū41E¾ī*Č?XśC.¾Ņ0:*ŽUżåżIG**ŌÜS+¹UĶŹąÉB/OĘ20+Żųō9 +õ¶ĄÅĢDåW;6ø¾ū·8ĪŪWõH廎Ičķ6čKÖøÆ*Ž2XĄDżżA>S8ąJ»ÅÆķŻ×ŅīłčEĆŃõÉ>2EŲÄ*BłZī2ČŚķG +./LöļU0**Å*āŲƼ.¶¾ßĆ*ļ+æ*;ųöĻUJ*ĀP*¼³..+.>·Żąß0:Gø°óķįćÉ/8IŹŌōEį+*¾UKöż½»ĆĶ.¶6***īÆčżNąæŹŃV¼š*ŌB +HŠŚłå¼ōæ1ģŗUĄ>ŁųÄĢżąTßźÉæ=Sšŗõ:J*;Ę9G>:F,;é¼+ÆJ0FŻ0XBE8ŗŹąÅP3+īP=:,.:źĘ3+*Éš +ģĆ=1Z¶*ć°½Æėē»Ėīō+źčųŻąWßŅ,KFĆPĄļÄ/:½ĒŁL÷ܾī¹śöżČŚN/2:L1å049ķŹŃŹÜīżńNÅDļōHķE +RņĖßĘ¹4½śÖįĪ<źŌZćĀTK**īĢæ46µŠ»»µ:üź+8F¼ńĆåš.øōņöĶĆ/¾×1-ÅĪāūģK¶:ĄĶJøĒNZ.JŠĆ,K +ßĻ2+QÅĘ.3ĄęŪ,,*Śē.Ā**,3@L**ĢŚ¹ļā+Ź8GןYė¹ųģ=Ź»**ĪŹ-2>*,**ÄM@J““ÆŚ,S¶õšś.Ń,Ä: +øī“1ĆÅI0¾Ń÷żņSČ:L³Q612ÜąRŻŚÅ:56ų,-Jó÷·Ż¹2:¾2ā1īD=6æĘZĒ +**3?LŽó/Į+Ą6ŗŃŹ**µD+:6Ļ/+*>·õąĒ-BF¾°×ŻG÷²FS¶śģµC*+ąŅżčGM**ö·I;2-Äżżš-ńņ,“āļ= +Ż5PŅÆįLCD<4±ķJļļśŪ±Fėū½śÓ7Q:0ŪIż+ųŲÖõ>Bé/X<2śżżģ3ø2:JR*ZÓ¹ņMOėW*Ź½YĒņ²ŌP½½W“ +ōߎ;>·Ü=»ņņĆ-¶¼ōņ×įQĶH¹Ā9I¾AP:L*ņŪDĄ-+Ę¹AĢF*ēĻ=éüäŃńPßåP-**4æģ9=æś4I;+²Xń;ĆĖ +AĪ3ģŚR¶ŃņĻń±6楥ŗ5ļČÖķŗ²9ŪÕĢ.ķēU?ī±/4>ęĶJņżė½ČFåP;1¼ALK³æżż½Öé-**0¼Üżż1<å6*ā +6šUčDķKżĒų5ŚłēY³4ėøCū7“6ĒÆ=ē8.IÅ6*æłS.Ą-RŌ@Lź»±ĮŻō*,,*ÅŪ¹½-,Ę½ļ“Jŗå9Ą8IüŻįĀZ +ŅSĪßJQXŠMĶęK2J.Å/¹ņ½ģ»B*:F¶*ĪDüżYĪJĆSĀæŠķUĮŽßöM³3*.°ä->4***JJ<ĮUÉÆÅM4ų°*F¼üī +·=/ÅķZļüż0Eįź±;Mę¼F½¹ĮJøņāø>Ā9+,JėĪX*ī.ĮĪ»õĄ“ÆŚ¾Möč@WźLč»@C“³ŌÆDłčÅĻ¹ĄR>Ģ¼šŅ +8**>żżÕ/ĪųV*ĪĶĀ6īSEŁŚń9W*@É-:4+**RŹæ46łU@AµÓ*ćP.čŗė±ņąøŻOZBł°Ņö×½żå“.01ĪVōµS +IūO5ÜōŌZŁņūż9Ś+Ć+*¾=6°ÜĆH<öNĢ=“*YVĻ:æ±EĮFPÕ?ō=U±śW³I2½YĄøŲHŽYIł5¾YUżż½U+Āą÷ż +½üĒŅĪŁõ»WBæņżńõFUżżYÆÖW:F:Ī,>ø9“5ßįS¾öėõć7»U/RX»åŽÕšNŲó°ķéŅF±906FłõżźÄ¾ņęĒ?Ē +/“¶6öčé****ā²ööżżRZŽŚŽ:²żõÖZWŃå.µķFįZ°P-åWAēŃ·CĢÆ1ņŪĶ2*×A“ĄZńČ*Ń2**Čß*Ō@**ēČ +õć½üSŹ+ +*¾ŪåP*55.ÆCųźō*īI/+YĖ8ĘWÉ÷AŌRĆVGĖXDĖėćIÄHŪOŁ4,6K**R0-öŪ**6ł9ż½śāGøöSĘ+øķ4ÄŽÜ +ļ*ZųŚ3ī82¾öZśIOå?UI=ōōDĪN“1ė=Ź“ėšF:²OAZōß¾æ±żüĶė2Ź8FÉźYó½ųēAŗYJ2°7ģ¹BQ°*ÄÜM> +¾īßėLĘ»üŁóÓó³G7Ü·Ą¾ŅNģÉļ:6ŚŪ*N÷łÜ@ĒPņ++P;**>Į+RW*F1ū3żē,Yæęć.JģNÓę¹GÖį+*¾±Bū +żż9K92*ĻŚF-Y:ńĆUµA-ŪQ+XU>įÖĆ·ĮŻé±Jł°Ņö;¶ūüĶė268GÉźķ?¾Ų3B2S**PĄ-¹KŠE=Īģ5ÅDōAü +ŗVŪ4īŲ2F·ĢäėżķDĖ:ź6ŗY:N¹ŹÄ7åZ×@1,ųÓĶ*NéøŌ÷ä¼ĒęŌYŁĢūČŁę=AÅB¶ųÕóź<čüżēĮPܵé×,Ņ +V4óµ4śOÜCX»:+1äŅē2ŽĻłXV=OĖĖŪķŌDŹ»BZīźŻż±E,Š-åŃŽßY:*“?**ųĄ+Jóßģ=*ZÜÅ湶7ŃīĒĄS +śķŁR=Ń¹Ź¾Ī÷×:ĀšHQ/*,*VėżżĻŹ-B6żł“Ėć6ĪāZ¶ŚŁY²,L,5¾ßVöɶłCŌį8ņŠ-ZÆĒÕR“ÜZ³Bł¶Žę +×½żå“.0ÅĢOōAŚJŪŃ6¾½6*=/**.>żż1ō,LQP*E¹ņļÓ÷>żīĪ**üB*ķŹŠ6Ėģżń>QŠ;½°6WćJęŁHKņŚ1 +.*ķ+*ĢüZ:0**RG7ßöķüW6ÜŲ61YŃ:ĄĖėłō7ĢŌ@6HNæ5ĀI·ōĖOJµK¾UIŪÄīVńņķ4ęšö³2ŽźQĄ-åüūI +Ł;:G¶@W“æø3BFēZ***¾¾ĻNŃ1J>ĖAN*ļŃF“čņ.S.OČāRšóÉ=īźÕĢĄõõżż,O@Š»@įĘüģVįōÜ29IE7¾ +WšŌļµÅJZāGĶCöć4AļNüżżŲ4ŽA·éü981L6ODķ°ķĒč1Ś»ņĆĒšNדóOĮ-»ĄįåüūIŁ;:F“*OĘßGŹ.,1¾ +=**µķ2Żū<±?ų.ł*6ĀE¾ÓAQŽPń-Ļ¶VCė<Ā¹ĶßČążYYBøī9V*;,*ŽāÖē-**ēŗäŽßE1ÆWėżżé4*Õ--* +Ū4>ÖŚćÜ=Ó9ZņĖēēMēGüOĮĮׯ.1Ģūł½“MZøŅ¾+½ē3čDēZĆ+*Ę?6Žź-**ćĖ3BõÕŠĻŹš9¾¾ČÆ*īĢÄēŠ +RņL+ĘłÓLŁ7Ą:»Ļ:²ĢX3ZĒ**RG6Žó+*Zóżż.?ČćśŃ:-Ā½ó¾60J>CģW>*żĶĮCŌP¶4Nķļ*üÕF*ĶB°VŪB+Ęć±4 +ÜJ³»šźZ1šöėŻėųņźŲƵ¼ąäüŗĆßÄ÷äŻ+CāD=>ŃóņBCIŁó.¾<7?Č9*Ćń3-åĶŪäOŁŗįÅē+*Zöż½Ćø*JĆÉż1= +D+Į5łģE5¾öņŌ,Ēģ»źśųGźļķ7PĆ4?RĪ¶>XŽŽ+>ēŠY²VDøłŽ¼īõ°.Ģ¼JD7īųņOE“UŅOÅ:8ETĪ23;ÆJ +ćåżėĮµH1C¶łŚMOLčūTJ2JŲÜ»+Ć@ÖÜ»KęéæūļōĻńģTŠŚŲÕ¹¾IFøĖßPWČ.Ę9Å-16BZī*øā=ŪČ?VÖ +ÆįŌē³šāß±Åķ?ŽIŁ¾³O¾éMÅģŲ³éŠ7ńį“ėD?Ē:J°F8BTŅĘ;-Ę:Ū>č?æĆļÕĻFR¾XQ*Ń1*¾Ó/+.-*.QP +ŽDU>M²ÜĀ*ņĄĻæĶE7ZäēĆąÕ3K.ĖA“K;÷=6*ō,“Ą*ZŌżā5µĖć6öč³“HŁż9ß+@J*0**ĪÕ-÷į¶ÕG0ÆDę +×Z7¶õŃŁ4Ś/26:XĄ/+**¾:ß-Įæ-ęÓWēęŪįŗ:B4*.Ü?¼īA-ūQļ,AIŽó7ł³4:G¼ŗ¶6:**ZJ+066.ŹMŁ +>¼WŪAJŅ7ō+ćŽ*Ļ׳¶*V5ľZĖYŹ¾ŪäRNé>UāżĄ+-05üāõ<**ņF;+ņūŅ2ČŃEÆĀĪ»öß°W¹Æ+ŃM+*č/. +BÅ?ČĒIJļXJ,õüITJĖĆ,B*:BīNK/PÆ@öö¹R¼BŗšōöCNģś¶ī7+źżTF½łģĖ-½Ėł±Iū·ńįĮ/ČE@Äē³*Ļ ++-09LŹ¶BøņĀČ³ŹF¼÷æ½¼Mīųę/5OĘ9GZŚ2=DZĘī+Ā2õß.6I×ņןéEĶüŗųņčÕæ=ńīėYż¾øāFFµĢ2āN +įS>Q?4ūšZßŌHS+Nø78ģī=..·UģŗÖšFė»ÖZ2YĘ*šķTYŠßõČ×ń@*÷YG“ēš-żK2āĘE0¾¶**RŪ³OJ8Ę° +.±Æņ»:Ž1é-ZSÖZߎ:.F·Q:/ÄÆBńśŁQ»F¼ųĶ5Õ?SńźŌßBM½¶ėÓßö¼WŲ²ß“Ė4/ÅłĻM08J¾ +čŠRŃJŌ-*ZŁÄ7ÉŌ,ŽŌWMQģ.¾ŽŃÕśĶöēMõŽ-ĀŚ³Ń-üVÖZŁ¼ćČ³ČŚD?0*LGB.ż>Ē:C¶ņöĻįóĀø?UÖ²é +ŌÆ·ņ*T>Ų“ó“6źWS,Š.**Lń±śĪö×4ĪRĻęOĒÖJęī++*Žī?3JņšīV³H8*īµL5CĀøņÓX2:22@-:F**Źę +NńéÄŪŹOUQŗź>**@1-BĄ**ÖŹJ4źZŠÜēµĶ÷ZłÕ?ŠŃåŚūōĻĒ=6óģUŻćÕź?FR205,8Ö=,*J,07?*,ĆŹ: +üƼÉņčŌY=*@ŹF¼,M*2Ż³į8ŗ¾ŹŲ²¼ŽļĘĻ,/27BVJ2:NÖ*ļ.;Ś+3DŅŹD?7IŲŁŲłōēĶÄę1Ņ3*¹»:PĒĀ +ĄÆµPŽ¶ń-NZÄRĀÓK2Ęā»**3Ī*ćĻ²<¶Łā7?µM+Jš³Æīā»:*Ž9.×į:.ŹČź8Wé²BÖÅ@ĪU“ŚĶüBĄ**ꏎ +3.¼üQŽŁW6JŪ5ÓµWÆšCŲY9>æńåķÜ»ųņŪŃAģßĶøøōĻŁŠ>ĘĪJĄ¼ėÕNĄīĘ+MÄĮ+-Ć26@Zö*DL*2052X* +¾¹*KĄ/5H¾*ÆJā¾Æ±ÄŽėŹLÅĪJĀNŁŲÕ±UEIĆ2,2ĻZņėŁµŻ?éø±ĢJę/Ü8/ī>+ęŃMæJ¼į2**ŃģVĻÓ?Āķ +ģ63ßP-*:ēF*C*Žį33JńĖļęłŲ?·NėĶü>ĖPųO+*üņ27¹ųżö;CõĖ»<ŪõŚS4L×÷żżQ+åß*²RĘ*æÉłŹ¹6 +śē³Ć0C5šīłVV8>¾MåŪ·ļßĆ1ķÓWĒ4ćĒŠä2*@Ē;MĄ4?°6+Z434āõJ*2KĄ2å..MÄ?.Æ*ü²ķßĆ5Õ¼ėEļ +Pķ¼å»åąŁß·-Ö5Ż¹śJK¶2:Īśµż@*1Zź36Žå<Æ6ÄHŁT8Yßē**NšH8¾.*Īļā/ę²ĖHXźøE<¾øåöR@ńöQ +,Ś·**.¾¶ŹŻł+*čŗµóV27Ań=N>żŠ;ņģŪ/.H°ē/ßķ¼GKÉ6*ŁµĢŅÜZ¹Ėµ5ĒŗÅ5ĻÓ+Ņ»5Jó-5ZŃ**Rś. +Y;ł²Ē>LĶė9Ā.¼æ*°D¾**ŗūż½-*4Āį»Ļ¹æBĢųģČED½Q.½ĻU0ąä+*¾ńż½Š-G2J>æŃDQŠ0ęܹCßN8.* +EK/JYDŻZNÆܳŻSŲ-EéF0ńīPļĘIC;ŹHQ·Ą*Ā-**ĒÉŃ×/ŁÉ +Ć@XĖ»Å¶āŃ²2ķŚ÷ņżēĀ**ŽM³ÄżIøBV¼ŪKXąGVÖ=-0śń3.1ĀYÆ*śTżĶÄ×¾Æ***BFY6×KĶ?Ėāōś;åżI +śŪT*ÜćŻBŽēµō,,6ņżėU*>ß½?MR@³īÓćKēčEżżŁ.5B3ĆŁGÜTĪłōR8Å9H÷ē±ćSŚ5īĶ/²1ĘŗČ-ļō+*¾ +šżżŲ*,*¾ūĮį³FO:³HßK*Ąæ1JćJ*C+*Ž3YŲéDÓ:ź9łŪ7KBńUĻV¹ZCŌB**×4ĖĪäč**NČä6ZYæ°äĄķ² +ąæņõšMQ±Õ»ćÉÉČīŗµ9**.¾4¹ķU1µ-J1ĪP1PZ,JēU6OZ³ł38,=Y5**1ø@1JÉŻ»M.*żB*ĀĢ>6ÄĆ1,ų +ŃĒŹ·øćöÓ5׹**Ź4Żļō+2Æ-¾ąąŚ±÷;ņÜC:øWńįĆäö7å9ŗUĆ4õŌPÜOŻōÆCÄį9?č;**5żÕżõÄ°**ÄMA +¾ĮßOTź»“½ī»Ł“GŁóčRĻŅ:*üä“OżĘ/ŹJCĮĖµč<ßÓų.ÓäH8Ž5-*:éżżÓÕø**FĀčźFēč*»<°Īņó7MåŚ +ūņ¶+źåŠ2ŁĖ+QĶśšÜHS4OHŻÓŗżM2īO**ND1J2*Ž¶ŅŌ²V@·÷ļæÖ+:*Ž:śż½ųF*Ś¼į,ŅV@øėDĢ2ÜP;. +5Å-īą**ŹųżIßĪ8ÄšżżäŃB8¼Ģ/*żæ,Jõ×āFóüJ.*żŗ¶KŗķĖę1“Č-ęEå²ņ0Ż¾ČõŻ*Ē/æéń+**.J¾ī± +żĒłPüā.:õ×DJųĢY9Ā·ĒßćŽKÖB<Õ-**ģQR¾RP°ÕYåē÷=Yé.Kńżż¹łā»-/,...ģE·šżżŃŻŗøĘJ<±¾¹ +÷CSź¼°ŁJāO4ē.Ößņ5UN¶³±õ×Cćł9Ą-łó6ó¾:µW>**28ÄöķĪ*ŲI¹źÓ×CŌĻ5/ZŹ8+6C4³żSX=õ,ŹĻł +ąŃŌ;ėÖPĄīĒ-ČP±O¾=/*¾7øżż¹Õ6C<āQķź²ä*»µÓĮļķŚ°ßī¹żøźĻŗWF,ŚłćWĒčFŁ×ł@öÅżč8ģH**R +üżYé¾+**¾ūM·².ÆVóGMJ¶Š*Ö=>***?ÕNŽ¶5/+³Č¼ę±PJśLOT1ZU+BĪA6¾T-*:ä.ČćM0äŁÜO2±É/ß +¼·5ŹÄųPB6ßäöŁJI¼ź¹Bł**2C1ZR***ż0/NÖÆĮŽ÷3¾ÖĒ?2JŽ-B*¶:ęK*Ä8:»Žł?Ē*żK*=3ÅÜäµLŽÅ ++*ZõżżLŠĻżÜŪåą0Ä»ŌP49ÓTåJ*ĄQĒĢÜŪ»Ņ=Eļ-¾ŻĮY±łšZō+*¾°8¾Īö;×8F.*×A¼ĀXķ>żŽćJ×Äü½ +üRĆŚ²LQå;0ŹŻ»ĻšņŅōܽDĮUķćSń鹎ĶŹķłµ¾6 +ĖŽļ-ļ*ĀõéÜ·5;įģŚŌ»ÄR1įFWéMĒ*īÖµķżż=ģJ0I*+Ā9»ŲSšŲµČŅłäJś?ÄŻ>Žė-:KężNøŗU:8¾Rēō +“@ŚĄŚCȶųųæCāĮ*,Ü÷»UÄŁõģŗWĮņĘ<ŻµøV+Rõ“-¾ż8Ü;¶FĶ8NĘ-% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 171 di +/sl 60306 string uc +ī½é+¾>-Ī**ŠG÷*NX=ĀŌÕ=ó-JDō*FŚų*²üŌ/Ėäł8æŠE,³ĶZŠOģļÕ.J*DżżA>ī-ÉūåėŁõ²ä*U=ŅĮĻł +ā²KRäŲõāŽą6±;źŗķļöÅ×æQµG50**>ķĀ;ČV@é³ųĒų+-IU+**ĶčHļ*PY²ÖZĆėżYĘģÕ-īśżįåėX@Ėęå +,Ś»*TŠūYŹ9*/:ėżNHėQŃ0ęGõNé¶PÄ5Ā²DńQ6+öY?°ŚłäLC4ėÉŁłÓHįŻü/Ń1Ę:ķA>*ė=9@ŹäŗęŁ2Ž +ĖõūżIüÜæłĄÜ+Ė@42**ģ>B“0R2SīWśN¶?ŪYÅēÅ-¶ü¾ŗö2ŚÕPJęߊÜ,>ÄCOńOĀķÖƵųI1-ĘÜD=EĄ/ū +*ŃõŪöVČDżĢąŗż,Ö7KBń.9×ÉĖ3ŅN*ōż=1:*ŽAįżżIK**²Hæ+ŠAUŌ-22AęŁ+ŽīłĄĪÖö½ü;+ZVę13ųY +N>*<śżŻõč׏æøQęąčéżå6Ųæ*ĢśĮĄYĖXDÅģŁõäÜFēJńWĻĘŚFŁ=¶Ź*ŽīāŻ·<*ģ¾2»żż=ŅĪC¶½Ž,ęŗ° +=ų4õ°9Cš:+ĒUčļāEĶģŹŅ5.*F**D.ūÕõTÆJIĒCŽą4Ó:/ŻIųąė±å2ŠäćŚ×-üūĻ;×TæKÕ°3ŻÉTWYBĻĀÜ>+ŚÉ96ĶŚĶģ?æĒóõO3JĻéŚ60üš.źDGÕĘI6+āY°ŌMĆYNõ9R*VÉ9ĢŪä±¹,ŻE6¾UF·Š5ÄĮ× +¾Į=åR2ÄĶą*Ņūąü@÷āÜÉ»NēAĢ?VWłA.āŁ+2?VÕZTJJĢņę³0ż¾:ĶĶ+Dż51ŻŪSß,ßż9ūXķĀUAģT<åŲä +ķ=OMĖ¾+1JįŌÉ<¾Ćć+2Ōõć3Ö×*Ź:ŗS,Ę²B*49ŻF+įQ*Č×AO*C4,Z?õÉ=ZĶ:RSūŠ.ŹFBBčPÕįėV.P9 +:»Ü=Ź,Å****UżżA=6J*¶¶,ęĀŠ2ŠQĻÓ±GŽNXEćūæ.ĪM**ŹóR»**ĘNģ»Dō“śēć4@CVĘēÉÕTAP>ŪY¹E +īµ7·*@LŪ>+śU8¾Z³YĢ¾»/*ĮŽÉŌĘJ*J,Xķ/*Tŗ.*ēčAQŽ@KīKłOāC·Z²²ė¾æŲ7K2Ļ³M·; +ŗæɼ×Ķ;6Ś¹·**J:ņI»±3**ø1äŹ÷żõAV>łöEųĒ:¹ĻP28X3JĒ.æIéÆņ**JDĻ÷Rüäó3ąĘŗTÖJéL³ĢŠÜ +Šµ,IÉķŃÕÓßńå½ÉąÖāVBī*.2ø·T¶“<*¾2HG8,*æŚÉ<¾6ū*ĘĆńć3B¼*.2»S,¶Ė4*éōÜF+Ł.*ĄåA;1= +++æģņÉPęļ8īōĖ9=ēü½õÓĆ:·Qõ>31öŪ,/øĻK:IæNĪŽÜ1J*ēĶ8³Źę0>ęVŃ.AŁ5ĆŠłĒ*;Čņ>¾3ņMæŽK +MÆķŗ<÷æō効²µĖK@-ZUSų“½Ć²ßäIĶćĒŃRģS3N5÷*¹ķ6¾+GģļŻE+Nč0*BēXDæļ.JJŲÉįæ,Ļ**ĀļK3 +Rńņ*BJĒüß3ŽVJ*ĀŲųX.B½ńĄ62R6üß3JE@FŹčŚ-3³?-ńĆćĪł9ŻõÕBĀOńCL×¹ÅģŪłżßJ*:*°ŪC+ņĖŃ +4øģEŁČV±>šHI×*ņ8G,.RČ,2ųÜō94ō×/BķW;Ęŗ°0·>H;õĢżšŲ°PĄļŃYŹ²EČĮAÅ5U8/ōŪ?**°TŁ½µ, +ĘŃ0*DLG²,GW;*+ŁTIF+>1-*?ņ8Č¾X80**ĆÜ9Ģ*P,+¾6Ģ1æ55ł+JJéĮ7Äś³¶¾*QAHčDōŽUéÉÄņ½õ4 +ū*ĄAæ?Å0¶Č***īļżżą.*¾WQN*ßµ/æ°ńōä¶D¹ŽH +@Ņ2ŲÕH**¾RŗęURŠ+*0ĀF²,Ūø5**¼2HF+F0**+Ń8Č¾°ŗ.*¾SL9Ģ¾æÆ**ĄĮ·:ģ?0ģ3·³ķĖ*ąµ*¾*-1 +OĪåCŽ5ÉK×ö½Šé<æēCDŌĪ==“Ź;śT6GQĻ½ĶŹ¾³ÓAčļ@Rī»±-“PRäčé?+X.**FI¹ŻóU.*Ēńöø@śæōII +ÉOņų·āßMQT>åŲĆ·ķÜH@?3T=9**J2øµBŚCH+*.öRśĢŅ0OĒÅNöĶĒÜIÜā**-¾@Ł½=,ÄżżżŃLėĀɲåīŽ +ßAÄ,**+ē@IE+“XĘ/**īZŗQ,ŚÖ*+*:ZVŪ9A³-“īņŻąRÉFś²ßĪŚżśēE½āSSJ¾¹ķź-0B°Ž¾;*>Hč<āĶ +GŪZ6Õ½ŽŁA1éųTÖŪÓNH46EĆĻE1*¾ø*įß=ńģńæ=ā¶VBģņ±ø/Z¼µ½ąä³AäĘ;RõļąŪB*ĢII6**BīĻ÷×3 +ĘÓץü30ĢÜYĒ*×½OLN6궓GĢŻPI,*2¾Įń?ŗ·üöĻ¾***ĀNåŹŽĒé.ą**.ĻĘHÉ-¼ė-,**ZĘ¹“8°DćB1° +JXė¼āŅ7ÖæżÓ2ĵī¹OīŃÜżEYŹ8÷ŚŚŽé,*Īź½üżæ=**ņŚC+óĢ<ĪWģļµ¶AKõÓKīEūą,F,Ąä+*¾ńŻ6Ę? +ŃCBųUKĘŗµŚÅ;VĪ±ŗ“ŅŁĻ*ÄĶGß³ē¶ĮŻBTV¼=2õÆ*0*Jķ±Ę;éāĀ221ćXĆ4×õ7QCæJµżę.2śVJL.2Ę² +ęYĒ¾īōZČ*ŅFŁTTG5Ā:+ÆMģÓY2ņ»5=ŽŌU·JżŻäD<:ĒN“ćQO½0ĢšDøÜŁHLöÕŲYWķ<»Üńš,U5*¾³ß*O +įQęź4ŁĻÕō?øóń.RśņŃćDÄüæō¶.L<70Nå½FV,:õż÷ń“üČQYĢ+šPR2Kų÷Ö,ļŻĆķŲDķßµćĘķģäÕė-üŁ +DĆūU,³ŪāJāFKĪ¶÷Ó32K<Ą.Ę*>¹9Ć¾¹½4Ä***62:W·ID+KYāęI·¾ąś;3:ķUGĀPB+RŲ°½²,ŠÖĒ+0:ļ +0±Į*N±ĒM,ā¾ĪŌÕPU¶437Ē*ĄĶĢĒ· +63:.¾DIDæ8Å*ÅĢšŌ²Yć>į7Ē0Ń +LńńņųMźł8±ŃD¾Ž*¾Š÷5:¾>±ĖšĢ½ŁĶ3DķUĮąą-+8+.Eż±C*ŠŌ*õGüMĪE½NĻQ;Ž÷ż1XŃ6ČĖżYÕĒŚõÜ +Ó>Ģó8HüėTį“ĆK>BRWA2øŪ¶>śż½³¼:Č.2¾S@ķ>æ²Äī¾..,ĒµNJųČ¹J,.JŲŲµP*ĀHÆ,öłÄ.ĀåWANJö +W¼±,Īė+=:-2ŽŻ;+Ļ2ę+=2;ųXŅµÕµÕ1=QXŲV*é0±ßā³ōHÕ@¶õ6C“ūĶ:G·ę**1šPI:+.ĶŗŅ²6EüŹ>½ +ģÕĄĒ²I:Žēßļ¾;69šĘńżżS°ā2šš;æ>.JėõÓµĒRņ¹Ó3QĶŗķŲU³ģć»ÕęÖZZ2õÓYŹH32ÓųĒ3ĪćÉFFB+; +Mķ33Bń±/2¶īśLĶõ==:N=GR,,īĘä=¼šµÕ÷=øAÄę;¾¾śżÕ¼4ß+čE.ĄŌŲ½Ļ,š@Ę5¾ABPGæ,ŲV;Ą5ŃAįäHC++Sį+3:*D.H +;+S¹ėĆF:1ü4IC+30į/=2;UįE0*2üÓĒ8²ĄC¾Jņń=P:¶JŚ*0+Ą¹ÉōTÄPīÜåĄ:²ÉܲĻĘ6ģģ±é,E0*¾Ū +QQŽ¶ąÆµ·åŃS2¾ļ.µČ÷54*ĆQ8Ū÷żō+Öśż½±ēŁ÷3āH“*Ѷé;õĒņ*üQüņ@Š5K3³ĶEŻ³ųŹåL6»A-²µ@- +A*Ķ¾āLż¾.Ę·Ģ2ĢŹ*ßß7żæ32ČĒXĀ*+*MģĆF2ļš8B½2¾.ńūą.ĀHFQNJ.Æŗ:,ĀIõNQBæ*ZNüĄ3N@>QN +JP³»:,Āܵ:µŹJč°ķÓŻČĖ2ŗÆĮZāö×>SDß3»°ÖņöÕSæK<Ū³éāŲŃÕRŽčŠĮŃöĒ?õø-Žä>ļ:WūĄĪä7ūÖÕ +ō+*Lö½Æ;ČķOJÜBźC*ŁŹYŠŃQ4ųŲ+6łēéäXĮNFųÜNĆ“Ū¹ÖūUļęÉ3:NŪFæ·,KĆł-<:ĮóGĻ½<*Oé5=:ś +ĀFÆ,æKęĆFĪÕ×.FC+öņāUO>7QNJ¾¾¹:,.¼UFQņĶ.VéłLźĀ52QŹJĪĪŗJ,ĀśT6QN¾*ZŚŚU½?E;¶Šį*· +æ,Q<ĒAŅ?Ķ,Rų*N2ąėQ:Ēźąūä.Ź»ŗ×P³śBGR*OŻA8·W÷A/öą*ŗ*Æ,.Żē8**?żAéśćŗ@ķ³ŁŠ?“IÅ8B +üģO8ÜĮŶüōĖĶ@ĒĶKķĒöLłß¾ąÕ3,,.2LF;,.8JZ½ŽWĢ²B.2.B*Ž*4¾*+4Ą1ÖOöQ5Pī,**ZŽ*,/X*ü +-øJ6=·¶Z,0æ-J.Y²,łĻ*+,-**ĀF22×8Z,·Q2äæPJ-2:āą÷*=.½2=1V**.0:¾ĪöJ=V³ĀB>ĀF*+;1N +ßŲŲūTVC׊żżYĢ,1Dī6ÉO³YS¾×1JńńęŲ;¾=2ĆęE-KŹÕÅēĻ.BõÉÓ6¹:I²B¾ąĻ*¾ZŅ2JĒē6:ūč:ņóĖ +ČųµŽ;öŅHø»ĒYŽ8DS7F·Č¶ŽĘĻ,**ōJ/6C¶D¹Y=ī>µĒ+-/0*4Ņ1B°*Ē-ÜNŽ·į=M06;**Z3ZŽÖĒāEśD ++µA46CJV*:+æĘ+±ēŲ4+1ķł>EZāŽ***ĀF:Cé¶A²·ņš.Ē+įĮ-*4-4B°TÆŗ:SšDĢę.1+**;ZŽŽĻ2ŗA= +ĘäéąKF0/,ZJ;-äBJŲ³É?9ÅšąŃ:JĘĀķņ=īĶĘ+F»*X.+:¶ōźJŌ5ÅĆō11¹ō²ĄŗęŁøĄģćėČ0½ĒŠ½G“ēŃ +OT6Ź?Y-QĆ0ÄGŻRē8*ōą2¾Yµ?2²ėčęł“ąŚ»ķ½1żGUÅ/ÓKķéßĮēÕ“÷żÜEżGŚ?P*ę,Źß½5Ż¾UµŻųīŻĮ +SųæJėęEśõ°ų1ņŽ³IHśAT»*ZŽR½1AXūąY»ńßĮż4Ā-Ž<ĮÖõīå1šŽ÷ĶĢśõZĢŻ*R:š0żėŹ¹Ē¾ŽµIäß19 +ń,ŽBČĮ1-ķ1DßŪĶHŗ¶YŹ¾:R:<ż8ģźŗé@UHäÓ0óJ*MYėśŌŽ1ķŪ5Ę:JĄ8Mō¹»ę:ņ*éņżé>ļīśŚėßņź, +īS·ZT¹Y3īēŅ**JMŠżż3śÜ¹M*ź±ųĶĖŻöĻŁļ³»µWDśŽ6<öÖ-µĖ×ĻC÷č¹O4śč¹ōøęY°ĪģÕ+ļK9÷=7R× +ŠģUĮ:+0öĮVŠŃŃæ:M9ż¹<ĪäåT¾Æ4÷Į5Į±Å+ÖāļĖOŹARüÜ7ø¾Kī7ZFR*Ņ·3¾ZW9Č¾ŌHP+0Šź9ĢJYµ* +Ų1M,0ÉAµ>Ć0:5*:Ņ-ÖŲ**V¹°,PšCKī¹ÜŹXS¾W³Įģī-3ZN+*Īµ03*6*ŽUɱ;OIĆCBĻé.ļĢ/Įæöź7, +ĆéŚPÆķźķĪćāJėYĆ-æĒēAS*ćŽ*KŃõ¹;¾āÕŲ*+āć<Žč“F¾Æ÷īąZ³Ģ*Vŗøē.¶Ō6-ÉĀH>+FV:*9ņXD+T +ņ,*Ś×AQĪĶGŽļżQÜBĄM,Ū-łēÆæÓÓąJÖTDF0.¾×ā9Ź*ĄYśRŌß÷óK-9+Dķ5ĆŻé?+Ä>¾ÜäĮ+ā²*+*øŌµ +ł,å,½90¾½ŹæĶĆĮīķżżYÕ?üDIė+Ćżż½ČZŽåśSģ¼ÆĆś²Üä8¼ą³ę¶QÓģŃ+;¾XėĮ>¾>+C¾°ōŪųRüģ/ß¾ +¾ļßTSō<ĖR¾ŠųGSSłāĻÖ*Ąļ×ĶÆśżQŃĀŽĄŹĮ÷ąōõTĻJ1MśõDK1ń-JįAõŪFWÅM*55ĀLķīśŠŽå8-:ÉJĢ +J·ąŌżK0E“BŗÜ5·5ŗĆ:ų¼1ŃÉGÕź/OMĒIżĘ*YŗŹłżö·šNę+N<.JóčFY+Š·FŠŁY»W@Ćź÷łP³+³G¶¶S* +Nņ<ųÕU:Ē2ZšĖķXÓĒ²ķ¹ŁėŪ5¾DI+¾Įīß²čįēZ¶ŽÕöK=J»¼*N<ųXÜBūćĄZJ*øųŪó:G7IŻ±ĪėHņä»ķć·óźÜÜļY-;ųź8ÖłÕ°NŽRéłC3ZųŚAß¾ÆVó¹żßÜéĄ¾KćZņõųTõ¼ +ĄJOBłõŹ¾EÉ.KąŃŻōĶĒ*¹åS2*G,ēŻM1źÉ8.6éLš8/*įõĢ98*Ń²ÜÉĮķZ+ģF¾Ńś×5<ߥÉJöŻłW0N.¼L +ŅʱŠOņ÷½/FĄōYł:Õ“O¾M.*¾ćżźŪ=³*JÖ:ÆVĀŲčEōX²YB¾ĮQ9żżÜĻŌĖ³ņåäČņŗõ=źQäķÜ÷ßśā³»BŲ +ŪŹ+6ŻłSJ*ęéłģ.BŗĒZF*ēéūC3ZüģßĘ*æYó¹PĪÜOą*+.÷¹ŌŠņAUļ*ĆŽ¹·Žż5M²*Į:ń1Ė*ĆĄ+JĘßĮ2 +Ļ±ÕK¾Æ7÷Į=ö5DJĖ-ÖŲB¾ąÓÕ.**>µ<ÉTĮJõZźH*0JłQ/+š¼ŅY3,*Jń²*+*īø¾ō;ćŻņ,å¼VŠćīļMAą +±³ūYĶĢŪężIū+Į½ó±öĒĻĮĄżŹĢHĖÜ9?MµM*XP1*.¼1SYż5>-ŽŌÓ15NķĢ,*,Sų1Ė*±K+Jķī-ŪøļÆčūX +?0JŹ¼ę.čŪ:6Bę÷¼Q,čśW.2ĀJ½26Ų×:1Tņ×ķō÷ČR1śģYŁčŲAČX<1:3ćżżåä,JĄĖ»<-öAąR±Ėż0KĄś +Ē56ÄļJĘĶ@TŅÜQ**¾+LYDōß0¶üDĶOĄøD½¼DV>ł>Į;ŗ2Š±*ä¼ŲÓÓŃ<1µŻAQĒL5ń0ŻķĶOĻ=ÓąKÖą1Q* +åĄ-*2įĮ<Īä÷KÆJÆńĮ=ZJM+ÖņäW9>6DĶ/.ĄÓ1łOė±Ā-ąŠēõNJēEA-ĮĀą1ÉU»ÉĀ+ā<łūXY÷ī¾Ń.*ŽŲ +ēżóKļ**NøŹIQCZJé=BZęSÄ,CAć“ö9ęŲś@7ō8żN¾9ĮJ0Ź³żQIPÉ9Y¼¾Ņ0Ż»ķźU087¾Aä2ODĻŽłČŃX +8=óAčšįūA߶HVčMÓ»°Žč¹±Ļ,?@ģ÷NćöżõūNŽ=ĻŃłÅR*·Ń.*ĀÜÉR¾F/+ÖŌōK3Žäš*Jß÷Š3ĪGF*B@¼ +A,2żšN2ĪF/CŹOĀ-ĢŚ97N*¾ĄYV..>īYDßAī*N3***BĄ³Į+.*ńõ:Ä°ßļüččÜæ.JųĮæĪłŲöÉXīöēµ/22ßÓÉÓ6ø4+;źł@=.»łēV-ĶģT;ćŪŃŪå±.7ŗ-ŲD +M>*°Ū»Q,č»é°*ā:ŗę.ųÖF;*öW¼@,6IŗČĶ**ŲųX3BśćĻŗ*:ŲųC3Złę?Ąļ*ęń¹PŽź9Į;+LWńåN¾ēÕ“ +2-0JīEżŻ<Ē+ķD4**šŲXó81ĶĄ**Ą=>ZÜÕąKöģÕüæ/JłŚ±ćLų,ė»Æ»*.Ū4ĆIG°Æ?÷÷¼-Y²YōµŻŽĖÕ? +µ5ąŌß“×YįŠĒŹõCÖ÷ćQĆŠŚé¹õ15=IVļ?/JöGŃ9“¼Cčƶ6F¹PżģGõŻ,ū¾*Lņęé7ÓšąNQKæ¾YļU<īģQ +Õć1/.;ĆæłU=ĪÓõ4?.ĀűŽīśŪYPT:.6ZĀµöÕPŽIÕÓö2.Eśöµ¼æć+-.I,*/*;V.-*/QTŽŲ4LµöÕJ<ł +YšABAŚē.+*¾¹B¾ß-*Žēų*¾żHÓFłRøīņŪNĄLĻ±VŲć¾āåĢLõ³żżČńģT2.**“Ė»“,ŗōD20.¾Jųčņēżē +6ĮŻĘĄ±ĖĶCæéF/,*.:@E½ćśüÖHC***CÄūC=*¼+60+Ģįņ9Ć¾¹ŻFÉ4**āĘ¹Ō.8ŪH?8*¶Ę³CŽķż/¹Ķ,X +Ņ1*ųĘ=Ż.*-¾Æė½šāĒŻR+N4Ģ6õōI/<ļżżēµ³ĮķīČ*É.*¾čß*Ä8**Uķņ¾ļģŁ@ųÉZīźÕģ°ńē@GRāŅēQ +9ĆŚYüłÜBBųē.3šņĖ»łŁÉŁI¹Ē..8ś:.äĶļÅQĄLæ+Cē4¹½ĀóģŗW?::¾2ĆŠ¶åżR3FõODF*:NZļU·īŚĶ +ąĪŽ+Ā³NĶCæķĖN,,.ɲ“¼O,üBM68:¾:ÖA¶½³8ĢŠ:12NŗųČłŁÖYQ3**Ążż5WĆ**Ęó2źŹ÷²ßņGĄ×ż*+ +¶DŻQ<Ļ9.¶K**ŚüżIMJT+Ūää?D½æ-ŅóŲčµßŽĒXČ.Ę6?ɶ,ų1YŽīÕäSšÆY¶śņżĢOČ²éą*¼żńȱŻ0Ā- +CēF@¼³ĢĢ»±:B¾ĀŠö?¶½°,Æł¼ųD2<īĘ=ø÷ÓF¶ķW³Ž¾°4ćßO±A0Žå8-J.:øY·Ż<+åWĻ15¶T3ė6ŚŻD+ +Õ·3*?Ķę70üČ.ŗń²BZ*ČäŃõ1ĪZCÖ¾Ļ.*¾ąZ*U6¾æVµPŽŚYō<źCĮ»14¾ī»5ĒōöJ¾Q<*J³üö;*¾Hīńż +5JĆ¾üżŻŲR>ĮķÄæŁÕ220B¶.G-ä9Ų¾ļŻÉģÖŁUīśė=Õµ7Jģō½*XSńJFęĪÕųŪüęåD+ö:ŅõßGēŪP.ÖKåū +G=ęßś*B÷³õßEÆĒJ*N,ĒGż±<¾UĮ+*MķOĻēÆD*6MčūG3JĖ¾:ĘK÷³āÕĻ.**ßZ*T6JļR*¾żŽ.VÆCéŲü6 +P4,ZTč,¶.*õD÷ģNÉY×I22:Rę6üē1-šAKJļMA¶źÕéQĖĪéGJĖ5æ*.KńõN¾QŃ>JMŠéņķĖ*ģ/ą +*8ĆAÄųL+²Y¼*Ī6ńūī.:M:*Pļŗ26Ųó@*V>ų82BM¹ļ²¶60*éčÜāZ-ā¾ŹŃA8šī>¼+0ĀĀKIéŌW¹ó4¹WÆ +Ć“ŲĢPū6,¾Ė?4ŲČ¹F*÷PS°.¾2,Z*JP3»ōŚąŚūĮÆīā5ńŃ,+,ĢÓYLĄÖ1żī-öņŲL·XŪÉüA:SõÜ741ø.Ō +ĆüČ.BłčJŗ*ĒÄńĻČĢÓ°·:ŗŽK·Öł±ńŠ±æŽ*±źź¹ąą¹Ń+:šĒāčåČĆ·Įņ+-:¶¹Yīżæ,RĖÕ*¾×ĆšŪ×ļčē +ŗÖ2ĆķģÉIł5DŽĻ2+,**.LDĮż1ĀT÷ŁåBV<šIįĘ4+Vė²ćųHßM½»Ź*=LĀ2æJ@ļ¼.łŠöš¹R7,īōQĶ,,ŽŲ +Õż+żNRßė¹µü>M»»»ż-ךōéX°K*Ź²Į/øłÕRJMS+ßß>Äå,ķĻÕ/-Ą+F.5-öÕPŽ»Y,,,ĀÆNæŻD@YŪ?01 +4ņīź6ŚIöźT*½¹Ē.2<ęėć8īIø,ŻVĻ0.BīźW7ęŻD1ÕS2.1TęĖäü9²ź³3Bń**ę·Ņ³“÷ĻOÜ9×õÕ¾*²ķŚ +Ć³¹YV*K*ŽįB*Ē+*ŽĻP<ÄćXG°č50¾ņŁą*æXDŅ¾Ėæ4Ą×ö±Ö?IĖŠ3.2JĘŚśČ.ŚłŌē***KRń±æõĮó++* +æ2Éą9Ć¾įõĢš>**ŗF,ģYēÉįķEÉ1*:ņę.ī9Ē¾ńõXĪ8**Ś6-ģYćŌ¹Ż5Ę/*:°3»ä³ģEG=**Žæęū±2×¹6 +ZōŻĢGśä,źóZßJÜŠÓY;+4Ż»¼×°Ļ½JŹNJÖŁį*ä0**äķś*ŪģŚõļCJķLĖNŠ÷Ž686ŌßIŚC÷=GŽŽ5Éā÷²Æ +ķIßMåĆŚ¹ķWÅĒÆ*ĆJŽūČĢF÷ä,óÖZÆėéUTīģEĶ7//,AL+÷-ÉTZóÜõ<>,Į.ĄĆYCæMYŹń>6<Ņ>0ę9GŹļ +YŪÓÜ:+;Ęś?łøōµCÄĄ*ĮĢé¾ü;ƶöÓ³P±ß,9æŽüÓĖś6,¾ŽéąĻöõńZĻ-X²ĮÅGTś¾ńOŠŃńÓŌA46ĄX÷HŽ +9čNśŪĮ½ÕČęÜ÷VÕŠŪM.ą¶“ń-²ÓōUõOT;ē±×ū0“Ćēµ/QĮæV¹ķėĒ“Ō12*C.ķó3¾9łA*öŪM=ō¼÷BB**³ +»ÕXŹŚPI:*īVūź.Ņ÷ąŃ¾R*ņŽÜģ*Įš.,ĄĄŲāĶĖ*łÜų4.7øź½2?¹¼,ŌóDKŽĒ8Ē“ŃX3Ń?0*JŌ54*öķĪ +=ŪG7śßCĪēFĮŪēæ:*,YĘŽ-0ŌģYQŁÄ:ų±Dö3>*59ĮJ1ŻĮ<¾äĒ,*īéO=ŽÓ×*ĀŠ÷Ī.Īµ¼**Z61*śYĖ*T +ŗ.JŲÕõČJļł8/*¾ßAEßāææ¾ģ?ēĖŽēéR***żż×Oś44Īś59-²Öł1°M±*ęŗY?*ķHĪ*äXĆ5:*Ę<½D.ÄÜę +²ų8ľÖõü:3ęZķīĮKC·Ę.?ŌP8ūĶĘ?ÄĶHćV:6.¹Åóź¼šZR*F<÷Ź.F½õ,/æ*¾Æó±=Žģ5Qš+ŽŅŲõäO·ł +-M+¾×õīOæõ?į,ĀUõš¾ėA@2Į-2Éķ?6éäē0,;Ńźż3B¹Ö+4I»¼ßÄKżHø.öŻ¶Vā»V@ĄģÉ,>¶**Z.ńū@4 +ŹūÜ>·P²VļUģ¼óß³SšŚČHÖż=¼ćĖµäC44.²NÜąøµżųģ“T>öī1ųÜ°,QüAQGJ>ö÷ų¾Ž8Bæ½¼TĢ.2B²¼¼ +ć.Ģ¹WĆ¾NŽĪŃD¾½>Łģ¼ÆĖē>Ę:M=B½Ņ.ĢÜõŃųņJ*<3B½āZĢŻłß0YK¾Æ3ęśH0ŚŲćŹEKµõ1×Ļ59ĆÉ9Wš +7½Ė@+**å02K÷¹QS9ŪäÕBśCN¾AŌR.,.:NH¼O,ģGēV**¾:Ņ÷;ńłżżéČ-,*,;LŃõ±Éą.=+*Žį;õĆ°A6 +ÉÕĢš+*.GDYćæńõX86**Ś6-ģYCæ±½TÅ.*:°÷ŗó5ĢHõÆJ**¾-;¶ż@“Püż½ĀPŌōŲRµY3čżĘ*MC68š³Ģ +ĀHŗł·:æ;ķTīģUÕ,śķļ¾ŌOBUéčöŻĆ±·öFļJ*ŽäŲą¾üÓš¹ō¼²J*;4MłÕĢāæńņK¾*J45ŻŲ=1Ż×832J6 +Ł4ŽŻč.ŻŹ<,-4*3W7źIøAQżŅ8.4NĘUśV=śXø2*ö*ŅÕõUFńģ;ČÆJæČ“ÜæūYƱõB*»ÉņĮņóAĄMQ8*MŃ +²Į²óŅĘŽĪAäÕō»±ųÜWĒ“ŪT²éčŽūŻB?õ¼522ĒNō³üČ.F÷ßBŗ2ßŲń±PīɽS*L2ēąźYĒ*ń@č-0FųčÜU +Uėø4*?×ĘÜ=@ŪV:09Ē.Ü-0żĶļ;*¼Š;/8ęŻ<+IFŹ06ß>ō/żYīź×/ŽąšĪ¶ÅżÅņęĒ-Žą¾Š;ćIŃ;BųÜ?Ō +7KOöēŁ½3-TZŲVšŗXüDN/Ė5æ*.ß¹¼åżQŃ>JMŠéåśĢżżŻ5Ā+F49,Óōć1*ČÆÜ9ĖŽŽ6°.,Ž>ĢµLĪ“JŁģ +,¾ĆLķõšJļ9Į+ŽŲŲ19,S,°JäēĮ1÷CB*ŚINŅ1Ą+ÄܵBß°F¼ĄĢģīÉķNLåĢüGN/Ė-ļ*ĀąĮSąŃ³,īļʵö +õP*õ4;Ž1ßĮ·ŽĮ÷+:<7µüõP*O,+ZČęĮPĪUĮ+*įĪöĮ=¾įŗ*:ÕōK3JĖ¾:Ę×ų°ź<ķ*%% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 228 di +/sl 60306 string uc +ī½1**īĶ±4*üG6ĮÖQ½1øęņ¶ĪśßŃŁ?ŹłśĘćŹé±·öF¾½“8üżżņI=Ā>»ŻPģ»¼É=öżQų»Ė3/ģŪH<4ŽŽ¹4 +ū?TÆõÓĆŽĪ;5ćšŃ=-īÓėæ:+0ßÜęĶ/5Ż/,-ČVNQI±,¼U8*:¾2“HęøL*ĢŚN::īźD5śC=ZĻ×*J3Ć:÷Ę× +MŽĪš²Ę:Nģé<æ“żÕŻOGŌÓF?ŃĶC³<÷O<·ō70ZōĒ8PHäé4ķąÕų-+?µ½µ@:2Ņ6EUŹŃ½Ć,H586:@KVėü; +Ó¹ņ»ļę¾*1ŻŪ*ūÓܹķ×ĪŽī:ŹPģ*ż?3öÕRN¾:ĮšZōÕPŽ1åšKĄĆ³±»Żų/ķŹŠ21Bī²77ÖŻD+åC405PŌ? +“ü4BD²,XSęū.Nó-YÅ1VėSņ0QŲ/ģŪµKśäüżĶŻZIū6RI0/öäBæJ:*,įćÕNŽŪYĖ0**2Ą8-ųÕP¾5Ł=,* +,:DYGČńÅĢ°>**¾2<ģėŻD+¼¹É0*4ņĪ6·¶Lūģܹ¼ø**¾°6Z½+,į*8ūXG@*¾BOņÓY¶łŚó++**8ĄąGżŚ +PūūłJŠS¾ź9¹ĻÕ¶ŠXZōUłō¼T4ęŻTźĶ;+ģH÷ßéś2:L9ī¼N,8Żłč<+ß*Æ6F»ć.ģGõÜŻŅJ:L>ö¼N,ĢŻ÷ +ā6ÓÖ*Kķ×Å·õĖŁäŠßæĒß¾ū;3¶śāéTĀߥ2»åšŽŪAP÷5,5Z¶-õÅNŽėłÜŲĖ12øź+üGöR1ģąĻMµģŚ·šŃQ +ńäĖEAģ¹÷šR*RF2ŽDµXė¹Ś¾BõüĒBņŠHUē54P:¼ś×ÄKķŚõģŲ¹Ķ+āG4**½YD+X±-æÜćõLJėł@,**ųAĖĪ×FŽĖµäĒ;I·įÄ÷³śöļMÅĪįÓüņTĻ¹5ńXŪDĖXŪø>J.¾ńX +ūóSŠQÕšņ,ėēŃ1īÖVėĪFø.Ż“ĢŗAAŅÜĖĖ2Ę¼é²į8YĄ9Fųå±?čE0ķDŻÄ:Kæ,?šŗéõŌWłÕµõīĻ-õ+*8Ü +¹õęß¹µüÕŻW¹õWŠ4/4ņ.3ČÉÄXCø.ēźćā¹CŅF½õ6A·*Zźńą*¾żąčóķVD*:L÷Ä2Äķ×¹øø:*ŽęłGPē߶ +Z*ÖÓōŪšéčöĀJ*Zõ¾ēĆOöżż»ÕĆLæĀVĪų?·īżģ1Ń-K*ĀĮPäĶ³4æJā@łĖ@QģG÷äĮłōW¹Óī>D,ŽÆŠ.T> +SĻNŁČŲĆ-*ę±ķä-**÷K¾D*×ŗ·Ī¾1ÜČŅRŠN³°ņJ·šŅ·³éŲ³³ģŲ»AńōüćŚ½ŌT¾ŃĆčĻ·øš“īź»ÕüŁõėŪ +Yūź×WĶ<Ūø?SRE*Žł@äūDµģā߯Į,µĶFµģÕ·Y8F¼¾šQ°UģģQ<øéŗøĖRŹūčPÄćšĘÅŁ/īÕå +÷ć¹ĒčŁ@WCøĀÆYńÕśDŁ.¶õŚłŠŅÕVŲ³R9åÜÕļĶä³źå÷Ż.æJ¾Ž,įćQT=8Ž×ńŹ/¾*2Ē@-ųÅČŽļĶŪ0.*1 +L²,ķÅLŽ÷EQR2**J.G±¼µĶņ>6*J*°÷T8Ś»ōēŽ*:JODæĢżĻ3¶šNļÖ*ŽL@ĻÕ6ąēÕHP-**ø2,õėÓU¼ųT +ĄßŌW5PĢSŗČŁ÷*ż?*¾=ŻŚ?ć³į¼/Mė><»ĆRŻ3,:żćĻ*÷Ģ±ŅF½ģ±OčMµė>MÅ°ZõéęQåķ»ÆĻųPOMåņSĀ +Č¹ŃøĢŁÕS0**“īW¼ćõģܹCB*ÖBOńC=¶ķDO**ī2įēåš¾9Łļ*+Ā³±ĶCæµä2**2ĄŌš¼O,ģūTC0:ŽJÕ÷C +=öŲš**¾ļRÆńÕN¾Ū5.-¾-?ųĢĒJ±Fń·4¶śÜõ<ÓSŃRĻÜóéŲWéōėźŁBQ¼5+¾ÓE9HŁ³**IŪT=RŠĮæµTUō +.½Jśęūćē×ܶļܵ1Ķ0ģHŽŻÄø¶łāUAÜŁµļąÜōSĻDĒܳ8Hś½Ī3õ9×åPŽĻĶŠßą<ĢĮĻŻČ +;Q¼A06T2ĢūśŹ3śšR4¾ĀčųCųU<īŃ·ČīKÅąÜÜĶCæ=7/¾ŹZ±Y¹½O,ŚBI5DPSVśĢéFKG**ĻéčFśU<Ž¹µ +×*°Źäą7³āĢI½ņä»9äR98ŃRņZńŁ,*å»÷UŗšņĻF6·MõģHż¼4ĪHį3EÉY@ģ··ĢŁõģŚõä>÷Ē-49°õX@ŽY +ķÓ2ĶHĒĆ·=ńģßµPåKńJF*ĻöĢķ÷żēD+ö¶āłCŻ1ŽE;,ś:ŗB1øBI*RŠļ¼BJR<2*ŃĀH²,ŪD6*B0ĆIF+Fä +-*·³9Č*č..æŹÓ19Š·BŽŪ½@AU@»³E5ńāµA*ģŁä“Ż“SśčGŽķS.ø½ė»<;µųĀźń“IÓ?ZšĆ3ģ4PķļŪÓŃŽ +żIHāŪI@¶ź>Ą¹»ŻŪæŌ-*²X.,:.983ĶPS¾ÄNŚēŻE+Ś5Ā+F49HĪōć1*ČÆÜ9Ä1°.,Ž?Ģ1æ+Łģ,*ÄąÉQĄ +įZ*ÖŌōK3JČ¾.Ę·øµUŁ?O¶ņĖĒĄ>S±éŲ¶µščāĖķ9*ŗńą»1ń>Ē:Ną·AśP3õHĀ2ßķ¼HĀšņEĶOR<åŪÅėņ +čņėķķIF¼ųÕQYÜU>O=׳Æųūę·ĢÜJ?2ĪN=čś?3¶öāĖŽZļĒąßåŹŽU=.K,4¼³×Ż<+õÜÕ33L@3ėśŹ3śZ» +*ZŽĻÕ׳U<īä³4+ßćŅŃāĶC+=70¾ĘNś¾IŃCPĢ822¼Ä³»@1ŪĄĖ8FõäĮŁŠ>Ę;蟏Ł·šł4*ZüZ.öXĢ,/° +N׶Bį,ķųIĪĄŁµģEõć³·<²<ŁŌĽĻēīÓéŲ.ĒńźćÓ/µš1FµŃģÖŽ@÷ßßĪJ¾<5Sķ*ü?4ö²ø¾RĘĀķ°õ¾żĖ +2¶ō»/+æ*ÄŚÓ¾ś?=¶šįāŽīZČāį¾żĻł¶ķ¹ßŽ*K0ßŪ*śĻFöżĮĶ3æ+LBĻĪųÓ;÷ēDć¾¾+Ä깎śÓ·¶Š×¾¾ +:<źXģŪĮRĢIµĻņL7=׶¶ü·+6ńYÉ*öYĖŁŠēŁčŗńFRÅÄĀŁõĢŃLøųŌŌÜƵZķ³óĻMę<ł-üė½.īŃS4æ*+. +ĒÅYĒ¾¹ķD7**:.³0īYĖ¾9Ō1.*.J²ų¼/åģGćS**ö¶āū?č¹ś»±÷**Ą8ĖÕšŽ÷¹AR1**XÄG±,õ¼A:3*Jā +š÷źėŚµøP**¾-ĻłÄē;ÅżIö×./šV5A½XĖF÷ī¹J¼×+īÜUµģŗ×PĄ.+ēĢĻĪXź2Žż2RGŗĶ67ōGėŚ·*żÖźY ++óŌ<ŽėѵģśņŲ4C,ĒÕįĻßJŽėM½V99+BęæčåRĶ»éŗ.ĮĄVLĶCæ-ŻFŹ5+BRų¼Óåģ»±ĢTVRÖŌõ?=¶öŚé8 +ĮK+ÄÜŽśĖµ·śśĖ9ōę-.:°7õEÜīżļŃżV²B.4.ČåÕ2źėEżTĶ02ZŽóÉM·öŚłXć3=åÜš×÷¹*:źĻķ²*ÖõŪ +F³šŚŁöÓĒ³Ō.Īč³³Ü;Ü8ø*åŻøļŁNĆ“ćūČGõE+ģ»ó×NĆŌWś×īå½ńöÖ;EÆł¹+LÆ:čź±=ŽöU±F/¾4¼õø +Ćēµ7ä.*ŗ»ĶĖ*ŃYWĶ8+°ĪH=+µķŗéND*ĪFūź.BųäéĪJZŃ÷C3¶łÓŁĄÆßßTķåP¾ĒķŪ0Ą/CōŁćÄF¶Šß°ē +ŗźF÷5+*äYN*·Q9üH-ŽŪ±µģśńÓēī+Čė@ČPŅ8*HE.²=ĒøāSģ×ßę=Ē-¾½Ē-ĻģI9éÄ;ų“łŚ¹6¾ĆĶKĪĮķ +K3JńN+*öóŠ3īčV*DēŗR,ęEI**BÜś:Ņö2¾²YY»¾įż9ŻõP**6¼ņ+/*286.ņDŻĮ-³ĶE4**ŽõĒ4·ųĆó7 +-·B·īŽśW*V³HĀ»õæ66F÷ąU=Ļ=øÓéÖ9LZCP@×YŹŠŚĮĢéC6ŽģåčŁć°°ł»ī9źü²ŠŁę3óÜ1üÆ7¹ĪYŃ3* +:źš±;ŽśŪŃń,¾:Å1ĖJæP°*¾ąčåL¾÷¹9/.¾R³9LŹšŅL*¾ŌāķėĘ<Å--ĀSŃķC+Õ0Tæ+2ÉĻŻ5·źé1,:ĻD +æīģƾįµÜōTŠFĒÜOÓĒE½F**½,°Ö>P4öäåCFõąBG°Ņ·õńÓTå°.ßߎ:.Ü÷į»ĖYP<ŌB2źŗYĪD·“ėśģOµ +¼+āŗóŌÆ:Z¶E2ā,čŽßAHP/*F<ĖŻDXµ9ŪLJ<::VśģµöšNJ.*ččūCāšå6ĆĘ¾¾²ó±PŽÜŁP,ߥĢēõŹ¾15 +Āß+4ĻęĶCæ½ŌĒĘ+:ÉŹŁ½óŅźüč³B>2LøSåÜ/G¶üņĖµŌQĶā楌÷ł*ÖU½*õQŠæõTśĀķTöōµČLĢIŗōę×¹ +Å9ŻżåģGśżś÷Eõģēż1ķŪ·ōęŪ¹śżļ±åģGųB¾ĄFK9»Ź¾ĮåżŻ¹ōźß¹QģżŃY9īōżŁå8/0Gģ/»õį·āH»ōÆ +.ĀHżżæ.*»ā+JŻčŪāÅ.čHūą6Åßŗõ׳Ż*ģŹ²BŹĮēŁ9Gč³+īōU±Ņ+æ,»»ĶĒ¾±Y60.1L<Ń½³,KVę*,9, +·I¹OPĢ>61:Ź¶½óGĢIūך*¶*ĮZæš3ZRķ.Ū2¾;S²ńÓĘTĪŃ*RĘNļCPZłÕÅ3ÆŽ-Ū±·Öµ/FĮV1ķŚF·ś4ł +“8Ęå:*,żż²Äõź0@ZæąHśS0ZüJEīT½ßĘ¼ĢŻ;šĀ,½U8..4.;GėYł:Mķ½ō½+¶K4×É=-**6Xå°ņĖ+ę*L +Ā5*LĘ¹łż×ÉņÉJ*ī*ĀŚŪņŪ:6@ņē2óILĶ.,*5Z²ųģ5*.*öŌśņ*ī÷ż½ŹHÉ2+:¾ęEśŻżŻ.æ2:@**;ōDY +ņXŠE,ņWńDƵµZæUŃ*łÉšBĖÅFµ+IŚ²D¶üżļ¹õö3**NÄPłåVJłģ,+¾ĘWõNĶĻéĮ-*¾SLĶ?æŃ½U1,*T2 +°½P,źśB>**ĘŁłźŲF÷ŪB:2:Į>ś±PŽÅĶæ**¾āįåDæU5/K*.BĖĶCK9R*ÕćĒ:äŌĮYĶĻQJ*SŽæWP¶óó6² +¾ĪńŃCčśR*ĪłēėŌŠĶMRæłUźīĖ1Õ?TŃäčģA÷GüAG?ēāæĮńČUNY16JżIńņ*ę³GFźŠOĶF66ņ@µ0*+8å2 +ļ>64EĮÅē=K+=Ē8;Rī÷ŲÅZ¶:-åDō¼źÓRĶŚö:±ÖEQTĮ/?ÜO,š*ĀĄķ¹Ē5?ĒF:BR.@ü½Ē+ŌīēšŲÜ-+U6ćŲč·Ņ6¾½¶8øÓN¹YA±ĶL5ōš,ĢĻ:.ō>šĮL +Į1±IŹ67īõŲń2N“¼ÉģDæņŚ8+Ąč“0Žß:ĆīG·*śŹĻ4Äģ9·*ŪČĆXTƶ³5öÖP +æUQIMš“Š*0ĶŠ87¶Ā¼¼ÅMźG6:LźW0żź.ÖµĒL¾Ž·ÉśŪąšäT¾ĘĻåŁū±=ŽĆ?ĄĪŠSYōåN¾ē=ó,M<½īķCæ +ŌėP,Ź³µĮćĶŅČįżķŪJ>B°ēŗ°Xń,ĒÄéąĪJēÕ7ÄīXń3+J8č@*8/AI9*VV:¾Ńņ9Č*ß×NY4Å:śŪŻ.1ņŪø +D»DõRH>Č3*/*XʵĻ×*²ŌBČ-׳ż½īÉÕšńø62ŲŁC*C,ūżżW9+:ēE*;Õ÷OŪŲżD;<ŗé.>ĖąćAMŽĪé>RņēŲ÷óJ· +JHÉ*¶¼×ņśõŪ@8»üó½Ą×īÖ1¼ÖÅĀÖ1ż²F>öćN/<źŗĮņæĻ¾RBŚ¶.¾Õ“ķŁH*0=ēīį@EADS@Ģ³ū@+Ąč“0*āDżżŻR +>*Bų0ŽĆ6łĶŻ»ń:¾¶KĄZ¾Æ9Ż÷Ō8RBĻ³ćæ+ĪÓżż900KīĆčļ*ŗš@.ą<õöŪ»ņ*Āśų½3.@Āłżż8¾Źč/Ģį +öĻńM×ŌµłO+×ŲæŻORR<½Lļśš6żĶ0DČ8KŁŗ³õöēŁŹ0Å83Äš/:öćō:VćĖI»ŠX8ĢZųR4<÷Żļ80ĮįŃĻĖÄ +->½ķ¹·šįŪXŠ×0¾Ęł0żŅ.Ę·īāķD+÷0Ģüć2*ü+ÕµńMTM½ā.1PäęEõėūļĮ-õQ¹NŽE½ś:Éż*µÜ÷TÄF== +µH=*ö²Z6ĪYŹ;¾NXšżWTZ¾IVĶMæ,Ć1»L5įļB*¶¶O±±ŗ¾F06ZHÓōĢG*Ņ=46J.UµŚH?ēHĶH/LBQĶ²>. +ī@ÕÄįæ6łķ°>2ŃĻå*ŁŽūļ·īąF/<Ū6HżóÓÅ9-Mß²ķā5AōÓżŃÅēM>;@ŚPķŚć×¾Į;S9ć¾ĪŪĖ+¾2ć5AŌ1 +-2ĻŽĖ¶:Éų“Hŗ?,Cüé;6°üżI7ĪĮ*ĀļčZR*ŹóżżUļKĘ»Ģ¾F*ĻÜĮßĪŃ³±IŚ?*FņüY°+./D=/PL³,åSō +Ėé?,.ÓėFHŽQĖ,-ŽÖŪźÅ·::.WÉ/Ā¾OXŠ:6ZüżIVĘM:-ķB?**²óżżĮæK2ŗĪøF*ĶźSD ++KäDĶ/0¾SNżY,æ/ļüżŻ-:ŃDKņZYæĒŚŁ½¶G×/¾EŻ5-**ĄĪ¾ī*Ž²łY12BšŚ:3JŌ<ÕQĮ3JAXM2XXLī. +/æīüŻ»»TLųF2;,āūżĶ<:+°µąEæņ»ķLõ“Ą:ēENÖJŁĀ½Qļš÷Gß°W¾ėł@ĘĪęéā-įŃ4*ķDÉæÜ6µ++įÕū +1M/Gģ6/÷ŌÓAĮ+Æč½öĢ:*KU»įļKĘńXW;ĀOU×-¾0ĪźGĖūęĄń>NMXņäĒųīÉ0Q=..ĢŁÉŪ¾WLżŁQR*IRF +ĮŲ-õåé+Ē½ÜE7å6ąZ9?īīå8ńߥ1KLūńżżUł.*M<ÅżŻ·ĻRJR2äćB:ĄņZ*BĀ¶>8¾*šĖH¼šĢī<5:NBKµ +ÕE½.ĮŹżżĒĶóZZRĘ.Ä1*½ł4Y3+ßÆJĒ“ÕūżÉVZKÜżżÕŽNŅ@öFVĖĪŽ×ģ±åVåĖĶ/G4 +ĻÅŪä+R×ä¹ÖÆW×¹ĒT6īņGĒ/FŲŌ·Ļøņ7XLīĻā*LżI»FBF4ģÓÆ4*Īė½ū½ōB-NėåźźäūYæ.Õå·½Ź>Ś÷ß +UCXėéF÷ź,Ž5Q-¾ŃŚS¾ĪöOÜĆŽßęŚåZX:K·H.ŹųżŻE.JęVģDŗĀšÓU±.L.Oń*ŹĀNŽēĶEøś4ļö¶ÉGłßX +-:Ė×<+ĄKѵ/ZWėżż3D:ŁųPJ;ąHśłõĪāIń>Ē±ķ*/¾½±ųļÕåšŃQĶääźūżżęQŃMĘń½żĶ¼±Äųę;?ēēSŚ +ŚB*>É>øø.WŲÅżIśßĢ=Iøś2ōóėH×NĘÆ6æÕ@Ē9GÉ:3Šń½×żQ·ėÓį³GOŗÅżżYŹŅLQ1:ßŲÜżżåē÷ēĀüż +½¼.Ć“*åSł»ĆĄśųøų·īĖĮS¶ÓöżéUG*BéŚŃÉPĪL9O-*ĢܵĒ*LÅSNµčTģ=.8I:;ĄŽżõģĮéĘĀÜūÕJ.ĘŃ +żż½Ė0*ćBĶź,īčģB÷Ų<Ćō³Ķ½-śRčYĘāF=/ŹÓŪóäG+5ĮŹ“µó¾öź*Ž»é±¾XŌNē¾¶Cµæ2čč¼X·X20-** +ŹųżŻ±.īęUģ*ŹŹš³é<.į*Kō*ĀɲA“ßēĒĻĻAøłÜĻ¶Bų.ć40¾HĒ8*ąążżU.ī2ŹĖKį¾ł>šß²čMåśÜUāD +-:MZ°O¾ģ·õóģ¼ä×Zś÷įå½Ē°Āō×ŃĮµ³č<·+æNĘĄ³ß*ģE*AšīĮ6P2J2ŁńFĖé=L¾+2“āBō1Nßßö:Āü½ +õ*Ņ¾ÆßÄX2,·żżĶĖ0¾2źF·³ĪŌ4ćŪ2ĆåČ*śCćYHY=µHß*¶śDæ6=-ćWFģĮH3ŁÖńĮM¾-ĒÓŹ6/ŃüżÕ0+2 +DŁD¾5*Z.Ė-Ī°Š·ąĶņææ°8Ž/÷CäLŲÓ*H÷W*芿żIK+Lęź6ŗ5ŽåÆ9ųøėRüŗńŻņ5×ä*āīŁJß±Ė¾¾ŹąÅ +ńš/,CÜģTRYń?+Āż½śCķKŹżŚĖ=1*ä<»7W²JŗÖRĆWAł“HM*öżQ+O°ą.÷Rģā=>šö1ńLæéV¹Ó61Ńüżõ6+FųTŚ1: +øFPČ4ėēéÉG*ī<,×,ŽēĒ+LEśżżĮ·KĒ:īéź,ĢMąMļīKŃ2ŽĻ×C>3¶ĶĮHÖõēLKų=ģūĢÆā¶QtPņ,=ęQ=O +2:G·ćęKĄFķĢP¼±ŁĪÓÕŠFÉŚū7ņæµ80LŌäż»Yó*ŅNČO,8ĖĮŪJB*¶Š½5=OGéÆ@3H²@;¶BL°×ż9Ü.:¼Ē +»ų³ĖJęōŻüYŁ6*æĖ±Ż0ĆĶūXĢJUYKTÜCE*üõD¾ė¹åĘĪLńāń6*ÅŻ“@?<ŅśĶäĒRæ-ļżRJJöéāłTæZčżż +ģ:*GUUł+±Éńōū¶éŁÜĖ-:TŌŪ¶JÕ1ÄÅŁźżżQå=KæŽ:¹;RŅ2GĶM/6K+ĒčPĪ=ąHśVųK·5»ÓŌµśµ1±Į6Z +ę74ū¹ĆŲīÓĒ<ŹP26KŁFKĢµY=µģGū?æÄļõ¼@į-ĒĶI+ŗ³æ:*ÆN¼?G1*ÄĪN4LŪ“ģ7L+¾Ń·æņ²TņÜ3ų¾J +°3æÉæK2śżģߎī°Dµō2,*·żóżÕē*:źŗ+ģZW3“O¼FÕ-ōłßZ*ųPĒĢRń3,*:I.īż5M.*āŠżśŚ6*ÕĀüŻP +0,ÕAź;**PūżķC,ZLķVøP:¶;Z¾*BŌ@¹*ąOŲ5Žī.ļęę5ūżżF»>*Z¾ø½.-0Cä¹Ī¾S>JYļ.HLī¹ĶÅJVó +ÆUēõē»·š»³8-.Ā/J²6ßßĮLé;*MÜBŪēŁī-å¼Õ0**.++Ž+¶÷²Ź/?ųP·A**īN¼40;T9GK*8³WLīøŹ2L +żŻ»»ZŚ+=JZ*ZJģ½ū½“D*LēYæUęÖŲŚłŅńDB°>÷ŁųģĢPHöIć+¶śŚĶ,¾°ź½Č¼3¾“µżĶė/Į-ĻĮR<2ĒXń +õNūJBĆ°³»ĄŚ.2L<ūOģF*Ļ°ė+Z¶üĆEßJĒśżżü“D*ĒĘŠIPĀéĪ2śżżŃÕC:DUQ*<ąHś²ųææ=½ČĻŻżśõK +Hš>å@+*AS;I·į“WŲā=O<>ēŠÆéņ+ÄCŹRŠ?¾īÕŠŁ6ū@üŽė±åQĘ,3¶@·*Z“³?į-ĘMĢĘ:*2Q9Hä1,;,/ +6Ļ1øąćē+ąū½śÉŃ·>2ŽĘ+M¾ŚżųIåOLōąŻæµJöĆūß.½?ņĒ×5ŌZ?ūķ0ŹIśŲLCš“HW*öżäE5ŲÄšżŁ8Ļ: +ĻĶżńŌ·ÉQDÄ9įVņ¶įõĒ/P°ÕóżŪ.ūÓNįBøµķŪ:ĒPšĆÆBńēŗī5ŁņÓSP<ÖÖ»ĢįŗÖŲģżżżų5,ēÉŗ0<+ģ= +ų1FWŠŚčņõæHN;G»ū<*HW>KZZR¾*Ćß;>VļÓīA@PS:ŗüé2.µI@ŻNĄę;.Žß¾¹WķŅNņ2øĆĒĀų0½ÓÜNPę +AøŚ-ģL*ŚāR4ĘŁżķEP9@SŲ²¾FĘłķīU>=ē<ģ,ßÉāÖBśĖŅåEń-³ŻöD¼äĒÕŗ-š×/Ž>+Ž½8Ń÷:Į;Ā±TńŃ +ķī=õĮ9A0ę¼³·UEÅłQU*ĒA-ģO1LUł1O +¾.OČ@Ć@2śģ±,2.ÜDKĆLÕĻHāŪ·ōG<*Ü÷UČē¶5ĄRLżEŠĻĪīÜ0ļEń¹/ģŪ2ŲśZ»īśżżµŻÖRĄĆĒµńāń7*¾żęšYÄąē1F·*ŗ»?0Ę +ĖĶæ¾×18PŌF*TźĮĶTJĪõŌ4K¼ÜĒOµĆH¶öźQAXÉ>MĄĢ9Ū²ēŲÆņ·ÕŅō¼EŅJÉĒ<ŚÅÓĶɶķĻīRĒ.16?JšŪ +ÖŪŁ*żEź<Ļģ34Ąą/+<-/įążóĻSĻŽ1PģŽJ-śŹµĖN1ķŲÜF½ņäĻżżĶłUĻ·ųæ½ā¹*Ā>ēÕM2łæ2żśõWųÓ³ +¼ćūJ-Ź÷ŲĆæķÅ?ŌĆ2ęŪŽVÉ»1øå5*×IĮ+½>7å±9Zäł,ĻAÅ25üģ±¾ÅUQKUCŠŹüKøĆ6FłäDćĆ,ą,6ÉĆī +źē¹L/ąQššć:ĄYGP.*1Hņ»ĢN2Ś÷ŪHö·ŻŹĪ:1**ĘÆÜēżANĄ;Ś4?5Īē“<2ēX+æķŚµĘÉ;/.¾äź÷ŽŹę¶Ā +żČ:ąS0¹ćER*¾ĀHę*UVĘŚ×UČĆ?G,ŠÅQ±Yš+ķ39*Ž½?2å@:7/2łö9OßN*ßµķ¼?“ÜČKā3/Üø<5Ł +üżQ4Ę*ZC¹H::H.Ū·ĪĆ:¾ĮķUÅ5Šģ·,:ŗĀH¶?ŗĖŠĀ±Śµń4Ł<.»Ū@ßŲŃĖå7*U³3æķ»“.æÓĒŠźČŗį¶TŲ +UŽśE=O·ōķēłŲ>į»ŌŽßWRLĘū°æUQYCEśņM8“ūżĶŅ:+J±ōŗZPŗFą½K0ļīCŲļŃŲ»Ī*.:Ļ1H*ŪAÄ;ēč/ +ēąÄ4æļÕOQņÆńāń8*¾½ŃA;ōßåõ:Ę;įßōżķ2éų±;VUÄ,×Y÷D@õXN:MĶ¹EĆ6¼ēAéļ:Č·łL+Æ*8ßEµģŚ +łöVä9ܹńęĻŃ93ĪźĻ¹õĢGųZ*ÅģGøńęßĮåģĖMåĢIüżöē¹ņäĖMõ8Iżż.Ā1KA8æļŁÅ8Ū·ōīĮĄ*ų-WB½J +żė¹9R:īėÅS2ńģĮ<¾ū*+¾H<¾ŹMQIŚ*ŽÅD*õ?8;*ģśA?Ńüżå3¾ęĻ·Š+ī1>::°Ōėö¾Ü*Šā/ZÕ +ū6įČW*źóÓĻęõÉQó.½:õ,3ōįŻ2Ģī“67Āņ@*+*ÜÓJK*<7±ß¾éBĆEūżÉ3čÆ*>¶KB²éW2;>74Aż9·2“* +K+JāBĘ8Ā-2Å@Łż½Ų5.-**2Õ4ŃÅżĖę¶ĀżæMöčD93¾*Įąåōż½K:P5Ś3æŃżżÅ¼Ó8Å+*Ę³Įżżķź+āČPę +;ČżWųQ:*ĀČ/*8øÜ×,AßüżåWĘ×:āEü÷-õ7J<Äżż-/¾ßę+.¾ÉÆģį.ę@?@“ŗJ3@+čNøŌ·š×1J÷ä@1Jź +ĮĶķK:÷Õį4EķAĪČĆ0ŌøPųż½õLWŌŪU°Vń@źZĖ»ĻķADśYĄ<6ŽöŅBM1.*0?ZZ¾*ą?@üIŹ67T²¶3ZĘšüķ +,,68..?ż/C*KÄMæKæ2BLZŽīB2üū½É62XŚ06DīĖū9Ā1:ņą;2IKB,7Ś4ÄĖż-°*Ą4Å,Ā,Jųü¹ķéļ*Ļß +Ć,AŃĪ¹łĖ+Jæ,Į8æü×764ŅRĻ+¾ÉK2G06¶üżÅŃĆ*C0üöæEY5Ž<Čżżńņ*J*Ļ,Į.CFŪ÷żżēēæŽŁłū8RĄ +2µųĘš“H»*V?P.¼õ3HĀ?@R²ńPļŅĢī1āķŚŚ×ÓłÆZŹõß6»YBDFS?2źĘņĖŠ:1*ĮŹ³ĒŚ*öćĆŠ1ą-2¶ÉŽ¶ +*ųųüA+**9XNŅżÅ*J2?ÉY°J*ĀRĶī*īŃĀšÅM.Č³ŁF:*ĄNüµąį;Ž½4ß÷¹½.ĮÅÆÕī¶*ŁŌüA.¾+KüčA.B +.ūåĖŠ-Ī°ēD>B²FōźE-¾ŽĄÅY.»U*@Ņ¾±--*ÅMļ½BB²Ö1ķ@¹¾ĮYCĀµÄ3ŽAŠżż-ó*6J÷²323īBõżż?4 +°īõX»GORšżģ·ÉŁČŚ÷ņ-*īķßF<.*Æ9¼Ą1KÜUå4ßč0Įč?*ZōIĶčŚWH<¹C2ńåŌDBAščU5,;éżżN+·*ė +4żµX=*F8ķW-¾2üµüŅ-:N<½5*6ĪńUIżAN+2ķōŪ¹+<īśµ¼?*ÅįĶ¹¼4·ü1ķ5-:Mķ½:H.?L¶õ¹Ć¶KåHł +ē8¾7·ż+C*ÆŲĮż9..7ż»äš*R¼üĶń<.4»ńä**JĻÖ÷VQ1Ž>ŠżŁ8-ŽćRśY861ÅAEĻ*°3W¾ī±Å+īµ÷żżF +G*2,Ļ¹Ićæ:Iūż½ČÉ*ęśŗŃ-Č²4ņ²ĄJÕģWEū*Ęćű9*Dŗöķ>ÕŽäčNQXø+¾šŌODP°ĶŗTQöĮó?S°ķē=R +FżĢćø/“@SŠLAB*0>GıAVŻüĘĮWżż=?.¾É.żŻū×*ś.ń2/¾īKÅ6>*Ś8ĮżOÖ*ĮF¹µżż;2¼ĘżÅķ7*CóI +żÜ*ź¶ĀöåŠ>¶ėAJ8*6¾żĖę¶Āż:Ą=Qę-,9°ī¶ļ,ŅNłY2.GŽūżż¾²*÷9Ļ81*.6FPJJŽåEµ0ŽįBšōŁųR +*2LżĶ»U*8ÅążLIKĪĒŠļ-¾ÆńT2żD7*ĄÉżż1č+ŽĻĒIüY88ļåżżķ7ä*Ż:æ03*PŻAŚÓWĒXĖ¼*ęéŲ±=9* +äS;HÄLķģĖX*BSČņīĻMÅXT-2,¾ųę³±Ģ¼5.4ĖūųµČTRŠ²żĒŌWŗĒ7KUóÓĖZÕŌŠ.ŹYÖWšāĒé“»-ZéĄS² +½ĒKø>+ÜŲ:2×üżQ4Ę*ZCBŽå,¾=5G+:Ā*>+*øČäż@Z2DLīĄV2°üż½+PŗÓżż1-¾Į9·ARģÖU×@+:įņū1 +ąį*+įżĶŁH38¾6DÖOśü-TWīĄ¾ööĶĶ6*J*ĒJ+ÕĄļņä*ŅĪł9żµ?.*įö,ķ5¾6ĆżA1Į*ēēE0+ēÖĄ÷MI0¾ +Žäżżń.Ž÷2ūżż@1ĄP1³Ö²*ņ7Üģ*Oµ.źłź³³ŁX»øå+ŽōHK*ĀXķŪĒ.*źĖM0<·+į¼@ÜŪ1Ś³ł.:H¾ŅOä· +*0I*>ß@×;PŅHģĆńüŃņŻJ=æ*+å;Ké5*Ė>ČĶZģļYŠĘģJ¾Ķ¼āßŌżżĒ4<*²00öŲ*ņāŠ1*Ģ°X9G»ņNōūY +Ą2ײĀŽ9Ē¶Āśżż0Ā¾Vßō8¾Ź²UK*ĒĀĢµO4*6¼żĖö:ÅöĶ:RķņZ2ĒÄXŅæTźųŻ:5ÅĄK»<:Jü5>-ŽÕĒ.åµ +Õ÷ä*ā2ś9żµ».¾āę,I6*8Ćżż5²*Y/É/Lļ-/ļ±Ń+J¶÷żżJG*JLżż9Pß;üśżżąē.Ī¹Ļ-ĖĻ*T<ćäÜĒłÜ +²*¶ģQßę:ĄOÆīŽüŲ**ļĮåūčė@2>,LįĻ>ÓŽA·Cćū½üē.,ĢUŚ2āY²éÜśõOU3Ņ¾įµķüB?¶ĀęKšźēŁŚ*R +»śŌ75Ćś4ĆņČ,R³K:Lü7SJ*V00ö×*īĪłč*:æģŪ÷ąųFōūķĄ*ײĀ¾.×:>äY.B3Q2ŚõĄ@3J1Gŗš³C*Cų +ü-ļKR;ĄMY5Ā4øęĖłÄņBHū9R³æ5,Lį½ĢÕ*:ņ»ēAäSķXīĖ3*CśL9ŗE*ßųüżU9*;MÆĘĮPĄK.ŹYÓ9>ĀĶ +G0¾Żäżżńņ**3ūżż@1Ąź±üżĒÄH¾įŁ/ĪźĀJļ¹QRѵńł0*Yŗ74;øęēŚ¹±Å;ŁŲXõ>ĶĶ¾ö*š¾4±M*õįą; +ņżżåū5PķļńĀŽŲ5“:åķ¹ŲN6/÷;Āņļ=¶¹+¾ųÄ÷ēźü°3įQIšīRYōF/3*D--ŗW*¼.š@ßO.*:6ééŪżµ÷52-:Ź¶øĆżżżN+2=ģ,*:ųDż½ø +ŹĮZŽą;.J.ūõŽõČ+K¾ĀÆWūżżś6*;L-*Å;éšä>Y+*Ž;ÖK¶öżŌēŹ¶*4²ęŗĖŁ:28*¾²³ļ-¾<ŹżADŹ3-2 +J6ńWŪżÅę0*CZ6*2ɲģÄUR²Ė¹µ6Éģüē²éŹEĶ¹Ź×*NĢŽ²ūżX-¾ŻF°¾ņĒ=>:»Ó+īä³Tļ*JUæĄ.*Ėæ+Ü +@*G<żYPRÅ-Ī*/GšżżÉ:L@7;ĪM<8/0¶-L°ūż½B*C8ķ°B:6*3ŌĖ-*Įķ½=H8ļ½æęXń;-2Z꣜ŻūAāąĻ +Z½½ā-ĘżżĻYÓĮZ**°LęD-:MīBæéś.,¾Wē*Ž3ÖĻģE-**Äł5¼ZņE-B?*ąæ,*Nņżżß>īMĶżżĶ0ÅĆ/EX° +KĪÓµ?ōėF÷Ė1¾UŚĆ/Ż²ÕņŹLéĀDŻ7ß*ś16¾źÉ>µ¹IšQÉ3æ-Ę:?śŗėŻÕŅR+NÅ41MīšżĻ*¾:ā=æ+;ĒąŻ +åŅ¶7ø*ÆæJįĻżü²BJŁŠĖĄŃŹ9ÄĶ9*2¶HæļBā:NB5ĘÆćŗDKē²üõµU<Ķ.Ć/G¹ŪĶĆąGJSéE½*RP÷ß׌*+ +N16>¼Źęż.ļ*ī=ĒGŠŽNŠė¶Ćó?2Fūė?ę³3¶õ.ÅļMģŗń׶é;Céęć+ū¹¶+įÅ8:æÉO6µż÷TĖBYYüTZĢ86 +īÜS“GūIłį±;OĢN5Į9ZALé8;ĪÕJŻÄR·ČNķüżķFćŌÜBųŃFS<ėŁBŃŚŲö½ĻLŻH×:XūÓPÉL5M9WJFŻŲ9/ +:LĆÄWöéW·ą>ųAõżšIł¶ĮÖ0ÅÜ?Qt>ŁT.ŽóĘśżIśé*¾ĘSōYā?šåWÄ8øKļĄ°éŽ,ŻÉńUõżż5Ą-.ė=¼*% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 285 di +/sl 60306 string uc +ī½Ģ*¾öEIéźśģ+Žłč-Ą8I¼ŁīJBÕ0EåISŌāā-NŃFłÉąĀ=1īēŽJĪÅ:L>šFOÄL²ĀĄRX>1:ČäżÉ6RJå/Č +±Jć>Ä°F¶Jųā+Ą*Ā:łXĶ2F,OŠ=+ŹāĶĶ*/Ż2ČńDŗĀČ/Ä+ČNGēÆēRü1B7GÕõ2.*LżżõįWFĄüI¼*RL· +ĆTJ2ąÅYB/8ä׿2ć*BOĶSĀKæĆė¶ĆźŪ“µ+Čæ/ŹłZé>Ģć·ĢŁŹĀŲPĶJ¼¹5*,NĪū?Yä¼ĒßĻĶä·µ±šĆKĢJ +Töāż,ĪMī77BōįźŹøÄGĘIŗ?øīó2ŁęäW7ŌPģśģ-Ž¶ČײVBĪ@-,āŌäö9Nā+-NXśAś:.ŽäĄŗ6üģ¾āŽ¾: +įūF¾ń@.L<õöėMõ*ĀźųI4.KŚūżż:¾2,.Į4õ7H»ģH²2øUL+R*īLĢ×åQ¾ĪŁżū½I<.³=.¾6É/īSģļ1ģś +WĆĒJ*:ųŻżÕ-Į:ōż÷-ĪR.3<+2ųŽ8Õ,ĄĀż.ė*R6żżIćĀ¾ė¶Ćņćµµ+Čæ/Ź·JśŲŌćßÜ4?īCō0ō+Ž.ūI0 +*öć>.ņ4ŻąÅ“µė2ĘśJńM,ŻŹ:*2R.+ĻŽ+ęéłżéÆJ*ĘZUķę¾ĄŻ5ÅĖįō9Sæ+ŻTąŅBāü2*źÉŃęčäA9*ōÕ +L?ץ+×ÜZĶ.OLÖöÄ@į¶ķ?ēłģ¾āßÖ:ĮüżżĖ»łąÅJŗšüŻ»õ6*ČWżIĒ:>ĪßKB;ņ20Bņż¹śō»*;Ņµ>1¼Č +äŲ³é8HūŻńĮJRķżżóŽīĄ@Ż?×WŲVZś>ĒżO8łĀŅF1-¾ężżūÖ¶āĻżÅ9*ČMĄV2JģĮLC85BüM²-J22ć<,ß +°ŲĄŗĶO¶µ+Čæ/Ź·F4łŌćļéÉ/P,±5ŪĀŚķ/šįĘńīßĮżHI*ŽBæCĪ³šā/:³č*¶õÜQĒ;ß,0A¼č°ōżCPŠJ0 +ĪŗĆÓ2ūDK.ĖIš*F»ė;Ćģ¹ĀW²Uȵ»=µ¼ó+ĪäUŻĆ<ĒŚZJ1¶V>BÉ.8ō8īķÅŽęņ“Śõ0NXĘ0=ĆU½PÖÓĶĖ/ +-ĢŪĶAÕó*āĘłI4.KÕūżż:¾FČO.4½ėGłčD²FŲźµæBŌA·ģĖI¹ńś÷3:ņōżżģÅ.KÅ55?ĒĶ9Į*NÖżĻĪęīL +Ńæ+JņżżøF0Kźżį1*Ć:Ż/-ŹÕŃIYŌąæR÷Ķ:8*ĆżŁżŃŹ2ÖŹŌĪąW³ģW¾ģŃ¾ążĶŚ7ČĖ9²8-ķŁ7:Fßā7é- +¾µĻ9öGĒ*ļÖ³47.6ĶĢßæ,B»įHׯZäŚQ+ŽŻ4:7ŹäĖŌĢŃŚRĄ°Wį3ŪżżY²ŁOō4Į½O-ä¾ęŗEĆN*Źó-µ1Ž.P=āüČCŃAĢߏĶģ5üĻ +0:ō¹Ļõ3ūBĮųĘEŻż*:Ė×ŅŚĪJ?Aé;BŁBäö?¾½Hä<éXķ7ĘŚµĢĒJŽ:°VWõģGßOPNę:ąÅ»/ż½>2Ą¶łżżK +*3ćŪäS8.2C×Fę:ņŻĶäS029LĄļĀņļń=JRZÖł;KęÆĪĻ²ęīJ¾ĘéųIIJ*Ś5;D**źś½>Å++ŻÜś+JĒćŪUF +/Ā/3BĄ·ĻÕż=Ā-Jļżōż>=,@Ń?NÆUń¹5Ī.TŽŚI@ŃóĖäÆīŚé,<š2Ūłę½š*ĄšģįĀ*öU+*ć6ä¾Ä÷·ūTĖA +H+÷Fńō“JīŁ½PG,Iū-CøĪ*7æ*NĻHń7**¶ÖF?éņGĪJAA6:KÕĘšōHIÖĪŌĆĒŻĘ*-IDQł1/ćŹįUŹū“P.ē +-ķŹŪū=6ŽōQ錓7ÜŌæ8üAR¼éŲ+źčEüżĶėUģł¶ĀHŽ-:ö:Ā“Ē+ÖAßU¹ĒŻĪŚ-1ZÜļ*Æ<+óÕ,¶Źµżżķ +čĀāįQ*+»éĮŽEūČ*J·¼*śĆ;Ź+F4*>RģŅŚÜÕźCYāC:¶š>O<9ŽŁü+Žč¹ĻŚčōÄ-ŪܲĻ<ēßÕÓ÷EüĶŽ+ŗļ +ZĻŗüQ½BOÕÅ9ćąōAUĀM¶Ų½Ō-ÄüėŲƵ:KģH÷ą³Ć<²QĢŃŃå8IÄLéš0ęµī2Gß0Ąķé?QĮ³ŪÄXķÉĻĮ<½3× +²źGšIŁ*Q·õ½ģ.KźOģļIŌS:GŠÉżMõ<³:WŪ>+NäZ0ŌWŌŲÜÕźCYāCīšąB³Dķ,*ZÓ+Ć4ō6.óB>PÄóóüĢ +śķX¾Ķ/ĮXķżā/ąļ4VŽßęŪõŌ@.KōåøŽęõŪø¶š.šōėĮõ8ŪõŪøC¶ŻńŃ÷ōż×ē::õōėU..ĄĮSD;ūżNļĘ¹Ī³*ńūARæśęėTÉłČGńĆFĮKFO +ÄĆW³Ķź.ī½ū?8ŚN*UYįIPÄ-ĻQ242ŚŪłP,“»»µūņBMĢ·9ĘńŻU<ŪSRN2æ×ņĻVĖ»Mø8ĻåWī7IWó+ŹāNU +IIHG>NĀ1øś<*½Q·A¼æ×.æõģA-*,2G9Z²Lö¹ÅĒKų¾<ģ½æš=ÖėAĢŃÕźCYāC:LĄW5Eķ,*¾āQ1±=ėGQø +·ē°ß3OĶµł·ŗĆĶ@2ŽTģö?ŠńųōP=-ß³üī3ĄN>Ķ÷¾ßõĢŠęŹ-āßĖČ/Ę7>JF¶.4¾JÆ=SŅU>éŅVQ0*°Ū¶¾ +@B*BFµåCņĒĒ“ĢE7“*0ÄÆŠäÜ9Ķ8“ŪP0ĢÆõPõP,ŚäŁżÕ»<ā¼±Õ3JŲÓѵĆ<2ēõ?1ĄäŠ.J÷ü5ŅJ¼ņ“Ēŗ +ŗćĢ÷LĢK÷3ŚøC·ń»:*ÄĢĢRņXŲĪČ+M0ēąāŃö4ķU8¾ŪŽ÷ŃĀę/“7P0¾éżż=+¾2Hń+įķGOĢ×ō1ī9-īĻīĪ +ļLĀ122Ś*.<²*+M08Ą-1Ė5-B,¶°+ł½*ÖEµåCō½Ł¼ĢE7“*0L;ĪłŃŃFÅ3÷²ŹÜńīõ>õüŻ/R>öĮŪH*6Ļį +ÕS:O.½BG*8É<¾āéWB-Zė“SŗŗćĢ÷LĢ+Ē<ŪŲŌ÷P**ŁT94¶åN2.Ä,ÖÆĒNNøåüå@ŽĆä,Žś½śD9·2*FR* +Īųżż.+:ĀåŗŽęõ³R²š.šÉÕ4ŗ//0:׏HĖÓŪµNåCöÜūėŪš³2Ą¾ĻĶÕźCYāCņīą>·<µÜ1,*YY³°čŲÉBņųāOįMPŅÜāķ½ĆÕĖ0*µłōÓöļ×ōŲ=.*õżżĒ +*J.Ķ÷¾Ļ½¹ōīā3āēMQÜøńŻN3øŗß*3DĆĪęŲĄŚū@ņFįķHÜŌEŌ0TVYŠ.PÅĒIäBÉÅVš8ĮŻŃŚł½>Kųź9ōų +*ZĀüżģŃ»Ļūßņ*¶VÉ*<ķXßæ,ĪƾōPŽ¾Ż8/ĪµķBODķ/*ZĆ9WŲó,.æŻMDāÕņĘĻQ²ÆüÕŻą1É-ĪÖ¹ł8īö +³Ó÷+,Īųżż.æ:ÄåŗŽŽõŪŁ÷0<āėłÜÉŅāÆß“·øų:Šźųō°š¾¼-ĄĢęE¹įęJ³ēćČ,ó²Yʶ8ŪLõ@śŪ䣿µW +Dä¼±Õ3ŽŲŌŃēĒ=4ė9Ōå*äŲ.J÷ō48>¾7ŃŃŲł7¹äAÆ0ŃS;·ńĖ:*Ō×LGÅ4-+ŻT**öĄ0;ŪOJŅ.¹ĢĮÜÖH* +WYŁ1JÄ1ŁT20*ÕżżQ,<Ēŗä-ÄüėŪĖõĻKĢŗÆĄē2æĪ+17łŲø-øĢ:V¹įęJQó/ÉNó6O>Z“/ēG.½Q·Aż±Ą. +æõģ9-¾š3G9Z²LöĮēæZ×@+NܹĄM3Jßņ¾Żļäŗ=äJNĄĮ +SDēįż±±»TŁ@+ZÜ8ŽC*40Äū7µäEÆ0±R9·ńė:*PX>KųĢł:YŁ.å?¹³»Ió4YÉĆÜŪśłŠŅå?šŅĒ8÷čWO2Ą +äĶó*CAµõėBāó+¾żė2JņÕłD1Ż+GQęGĖDÓSYĻÜYV1Ų1Ģļ²śSŲ+W<Å°@ÓŃÉĆ8ŃLęÖĮĻń>õüY¹Ė.īōGü +ćD/ŲŁø÷½śĶŅ6øĮé<¾āéżE-JŁŽĪķ¾/éÕ³K4ēCķ1*ZLŃSKõÜ1CŻU°ŗ.ŁéüOÕÖćēij»MųŌCK>Dķó*CA +Õ·čJģ×XJ.å°ĶB6:½ø¾ųø½J+Äū°3NYRJ°/JøźĖĖÄó>õćJŚ2ąõģńÖ䣿ĒU.Ī÷ĻĶBĒ@¶ŌX2*>ķżĶ3āA +6ŹĶĪ·ūæ:N=Mš+ø¾ś0,ŽŹMÅĢ»,+Ęń::S:×Ō+ŗ9ĀVĪH9Dś²PżĢUŹ÷ŁJźŪSV¼T0ćäśżį½Ņ-ĄŗÕ-šś.2 +¾ä¼ėEķABś>M3øĮ+Äū×Æ4å+*żźī¾īRŗFĻéßčņZR3ōVńBćŪśżÕ?NģĖĶĪÕéĀŚĘ*:÷ā2XH:7-.üQīŚōB +2źČšZķäõ**ĀŪ¹łÕ+*öÅ+āS2ō“BJŻ8ąīĻ.ŽUĒ*/įöĢŗ;»ńé¶Ü¼Æ+ę¼ėWŃĻŻį6L¼źņĶŽ·²¶VĢ÷¼æŁż +?5ŪżśUÉņéŌĒEŹF»ÄĻRBłś=Ā-,Į;õĀXQ»Õ?2.ĢSÕDå³ÜūżIµĢ.żH3.TÉŠŽ53*B=ęIÉ@ėBśIæńŲįJś +WĢL·=µĢŚ·+ģś>JŲ-*äM+.øłSŚLĢžÉSČ×NY,Lņś.8łNN<»Ś+ś±RN±łŌ¼;+Ā¼ÆÄ1-Ēī-Yōā0Ü*-Īč +W?Pé/8Ż¹>ßRüõ»ÓéæK,0æĆßĶOĄ*+Ķ7ć?üżö÷M;CĻź·ī仵@M/6·JķĖć·Č¹żż1ĶFåäQ4æųōWśL,¾ā +Ńõ-śT+RŌ°<ńīćYõ,“øUĆŃÉłńA*ļĢ¼4ÕĘŃ::·ńZśźøTĀÕ@*ļ*+Š@ö.AįÖYŌõųUöFCŹYŲWīģQ-:A÷½ +ĒÉŚĆ²ŁāÖ:ĒŻµÉ.ćTõēŪæĻå7ÅćźčęŌKæ<ėĖā3ĖµŃę.J·¼*śŪĖI·NZ¼5÷čÄ*ŪYXŌC÷ŽÜ/6¾MµżżżåW +ēÓ0ö9»,ÓOĘņFJŌĆJš@Ē·ČĮÕ@¾Õć,NęBƜѣ6TÉ°ķDćśÖĪĮÅJčČÉ“5FöŪ*ā?üż>KAÕ@įO,Bšµķ@+K +ęŗøµ¾ā5ÉĻ3ŗ°×įĆSø+=ĒG¼1@åB<ūŗø,+*69NķĆ=ŚS*+ĀķE¾ŚPĒ2VGVÆšäÕ**ĢGūżżåČłÓ÷;QJų³± +ĒMH°AULJŠį¾ęŃõ:1YF*2F/īö°ßĻ=TYģīńČ6ĀĶ>IńōŻ,åĻÜųŚåøJÜŲR*śTżYÉĻŃMS9:+4żżµŃ²ęMH +ŪEBģN+ēŹĢōZńZZŽźøƹPĻā×W=2*Īöśü2IøĄ,NÜį*ęčßč¼“Ģś0,*ĪÅÄD¼°R,½04øT<ČņżżĶ“;6K9Ł +āŪQL<Ę9ŽT*ŗOĮ2¼Ńå@*żV:įö¹µĒķĻ0*śI¾ńR±ĖµńEü-/īĮ/,Ī÷żżÕšF.+,J*Žöż½4ć-ŪśųŽ0įEĆ² +°Õ-äM+ī@?:ŌēÉŽōĢFŽæļGG*MµT@ÉIŽīøÄM6śćś²ÄćW¾*F¹ųżéÄ<ĄFSA1/õå6ÓĘĀģÕĢ4ćŪąZĻĪÉżż +52±>*E¹ĖČW÷:<é=AĒ8FśHQ.CÄŻNŠ¹I-åCśęē?Ģ²5ś²Ģ*NŠżI66żŅŌę°0>üżķXēĪÅŗøµZ¹µĒźEßėē +ēLMS·>WHŪÉJ/VDFĆß½0Ģé8-ĻśĻń=ēE÷*ūņ1ŽĻŁÅĶÆ·3æĻ1Ł.Ę:Ą±×įĶ/°Ź»·W»½1ĶMųōDĶ=5ŗóÓ*2ųüI32I³ÖäS*BüżĶL+ßAIHŽŌĒĘX“Hßķļć8/ē;Ņ +ĖÜłÓ-*=3ILµ6ĖõĪ·7Š:PĻäčäŃ1*QģŚ·ōņż9EćµūI6:¾ŪNY·+ßčč->čż×¶6¾ēōżåE3äŁ1Jį<ßą5°Ā +J¾Nčź¼@ÄKö±óū8DåīéźŲ¶ĢVīÜŻ,:ÕöżMBś?Ģš¾*Īöż½E/ĀLńēŪæW?±õńF¹1ė+š:ŹŚŗ“µ0*.ÜĒ*īµ +4ŽŽėøāł¹īŚüJ2óŪÓFķäĒåI*8Ū»śĄ.ūų9ܵ/J=KCLP;ņĄ°3śŻBI*NOóż÷HĄ;åA3ÉŚ.Sóėą-į>Ģ·ŽÖ +WP·AöżŽ·.ŻR½·Xæń19ŽÕŃżĆüš*¾ŽC/*KņĖ7:BĆēQõ0N>6īļ“ŽŗX×UKĪāŁČ-+ęÖŚ¼õZ-źŠ²ä÷ż1Ģé +8-æģłXŃ7ŗZXō,īźēżÅ<3Ūżō»Y2,ÅßÖ>ĖHµń5ćżÉ÷+JT¶żĶÕ;>÷ŪŁŅVįA?Dø·į@+øO;µųõ3įźĶ?å¹ +¼ŚŪŹ;¶öF/蜯?M»Ž¼LPZŃēßVśņ.6·ø<õģóQęVĒÕ±ŠĒU“š¶ĄĻśŻøWĆ>GēŌ45¹QTĘ:Æ8½ø“:NŪNé¹Ś +÷éYĄņ.ѵ01.ä²ŗA8Ž*XłŅWĢĮ÷ėĢJAéUśD·ńē1¾Mµ9ŻĆO4żżõĆĻ6ÉN*GøéŚÄÆNżIä1*STūI÷æżź,ī +L36G>×āŪ+Læ4?čŃRĻLĒQĻFYFŽæ1¶Č6÷3Śō30GĪź÷ß: +»DEY×ÉŚ÷M@.ūķTĻHZI;=ÖŌ*śĪŗć+¼Ē23żż=QQ<źŪVåĻčŃPQ·/4½ķR¶Śń½æ+ÄŪ6»YńU-DŹ9ČĮ0ĀźĻ +¹åģūß+Ź.F4,VVŃ,CĻXśX“/ZņĪŅ/½ÕQų³¾æėŃQÅņ³ńā-š*÷Ź-ŽĻŁÅ9RŠ³żżłéųĀRĒŅöD68+Qõż5ń- +ZŌöüIńNīēQ»Ā<Å9ŻļKMÖö²ķõ+Ž1=?ĘĶܳŁ.CFéä,½CB2»ļ°NŪY×ÉŚ·K@*ū1Üøń=ÖĢæ+źT*ø²Ģ-ŚZ +,Ų.ĀĆŽ¹DŪŲ÷Ūü.šõ»WWķ×Cß+ä:ĢśTŻŽ¶Į9ŗÖ×3ōĀėóQ2ö±/ĀöėŁµ9żć*Ź2*õµÄ¾·A@RÅÉOņČż=E* +=µVŠßCóčéé+=SņZńē-¾±Qż½ŅMÕüżõāOč:šÉµ·*FĘŌżż9PåŽYßü½čĒZÉßNŲó¾1ĒLģņ=J4SŪ“ū*żāŪNĻĒ“2³ÜŁųĆOCłęSųĢī62SæŁ4ė*÷-¹-ģ +ń¾ĘMŻįā5IĮ-PBŗ?¶·7¾7ÉĆ9õ½õżĒ/+÷PČX¶ķ.ŽÖYŠ+ĪęėįÕQ2Ī¾±Ż5.6øöóC:>°ČĀ0-;¼FēK*īÓ* +“ŲJFø8ö:;ā7¼,N;ÓåH*NćQ24:įHIÉ+LŻÓĢĖ;¹4łōL/+*K.2/¾śżÆT*¶QÜö>1,**;×ĢŲYėÕĄæ+**F +ņ8¹łÅó3++¾ĘSXšżżQ+ęĻ3Ż-:šéżż¹+ĪļšģL****ĻÅG*C,ż¹Qø4*1JßÓÜņż9ĮĄ+*,,*Ā°WAŌ>0Ż¹¼ +.õēJ.XCæÕŲRĪM½ē-¾ż7AG¹HźŹ=0?É8.XT/:ĒņźŃYÜ9XJś³óWGGÜA³+¶ōņżAĢOĀ·łĢŻłēŃĒżQüBÕō +üż½ĄŁX/ÕQG3ČŗńRZĢēEHņ4·ŃżżIG¼ĆYF=°YUĘ“ł?+Šøė5¾āņŠćŠ<ĒHģ1TZ0¾N4N÷2-Ō3+X°>2Ą<+ +Ą*¾5ųI74*ģńPń@°S>CźŅ²źGSBRÄĒNöU@ÕĘ>H6Z.ū,įĆŽCÉ»U*¼Rųż*Īźųż½Y*X1Kų¾ŗ¾ÖŹŪY0ŽÄŽ +ż.=ĒīĄ.÷+;ÉäżįŚ4*ĶXć<*NŌĖCĆ߯Č;ćĘÖG0*ĖČäń×Ū/³Bõ±÷ēŗ/.ŅAO¶-°Ž»÷*:½Ī*Źśń×°Īęßõ +ąīŠS*Č?ŹŽWšćéĻ/¾ż¾+ņ²Ķ6ķšPHBAļ+¶ņōżMŻ0PWņÕHßī1EĆÆ2½šÉZé.OŠµ¾ę3š0ķüóNŁPŻĘ3ņ¼@ +6ŠÕU,NóÕQ.ē÷W-.P<Ć»»I2,²żCIĄÉY²ßP5Ž±SĻJIåå:ŗÉHNźšę³čįÅ>ßN +*Ō·ÄLēKæKĶõÓ8¾8Ėö+2÷óŻż½5*8ÄżA+ļ@üż½6¾6æżū=;@BČāQĄ4ŻōżB>*6šŅĒZ¾*²³QæĖÕšéźś<å +ĘLWŁłTģ*ųīIM¼ėBĒÕĄŽ4UNń·Ņ*¶II*äĶFæ=½WĢ:>ŃŌē¹Š¶CF*čM12ŻŲ¹ŚÉQ.5äÜFĪ½-ń°@*åĢüżż +>=ēUū4<Ź¹FĒŗ1ÕQ¼30²DAšÄ2×LģņĖéŅ-ģ7RÖēįŠ;W2:ÓéÜĄNÉ/ÅĆU1V*ż=QRZź7A3++KN²Ö5ĻJęż +²R*¶õ¹Õ+*,2GĪ»Ū¹üNć*¾šĒHŻø÷żÆT*:µķż¹ĶćßĻåW+¶I>ż-¾°éżżÜA*F6żY6NH³P9æ=ÓLĢłĻ/ +S»ō?Nø»ųŌææąP±ōųż½ķ±AĖ80×ķ*¶7H*ūżķF:5*ęīܹµķūWČō°+ļJ*KµķūSĆēF87Z.ÄĄ,¾µµĶŻłS8 +.Ė¼č+ęĻ3I*;Żüłż¹+Īßöż=:ģążżÅ+Ę;öI÷Z+QŅĆÉĻJężÆR*¾ÅżŪ5ĄßäšF1ß5>×CżŅē:O>2JņģćĖM +ż½×N*W5=ņJöūQR26õ¹GI6ĆłXłĀņ½ßķū×?Ōļēč°Ī2ĻQķÓįŪĖ*ŅA44ģīIŻżQµ.ŌÕNŹÉQšŠ?*äĢüżżō +Ė98Q½ŃŹĀśņéń*CMą*Šėū²ēÖJLÕĘEłÜHöóūķéX±,16FśXMõ½ĻāčŗNKTÕ»QĒĢY>K°ęĻźéܳÓR=ų?93 +5<ąĀµķĆĖ.ģł²R*¶Q¼BMĒ8=KB@ŹöHĖŅńŅSšB:DŠOńÅ*ZŗŲAOFNóÕAGēÉOĄ+ęDńųŌ+õ1óÆÉ6īéź*R¹@Q***Īß»õ¼BKK,ĒŌ?āæĘR +W²<·DŁøĒT²ā*ŚŁQHÉ-+*¾ÅOUēĶō>ƶ**¾9øĖŪūćņĪ¾Z*N@A÷żżŌ*śĪśķ³XšŽJŠO*ī +¶Äż÷÷+*¾ęć8¾7æżŪYG/¾-*ÆVÆöżÅSß*źżQY20CéŁ4įķ»@Š·öźDXŽĪõĖ/šß,ųż4/G華¹:HTYCTŽR +ŹóĒ9MĢŗY/ßųü1ģĶ=ĶšÜ.ĆĘ²ųXP82ŗ;+@6ĘŌÜÕµ@9č>F½ńWSšāėF÷V-¾Ź±õH-*NŁ2µ<Žß³ČĀĶJĄŚŻ +ø»*äLżY-8øüIųč±»ĮżY.æKŽśĀŅH¹23*HĮOÓA46»½ŪYĪĮ:ŗķÅ1M½·,ģHõ×D·üżŻ1Fųś½üīףżżI4+MķJÕŅXłŌ>;ŪVĻ4±ŻŚ-·łņ +ĻEåŪ¶UĖLŠõWĻĄ;YŚBųW·L“L¾ņ·PUĖüšVßDYEÄńGŁ·õżßKĻÅłżżüR<·2Hļ=QÄł;ĒG÷Ü4Ąó?żŪQŽŗČ +ņD=ĄĄĻ¹ø·V:I¾šPĒUFÜõĖ0¾šP×ĖRšäQ*¾÷R*ZÜÄYężżöY6ŽĄ/:äßĘõ»ōöšZß@Ķą÷õĖ;Ä*¶ņężń-ĀĢõżü½×QĄ +āĪį3ĘŁ*±C»ńµ?źLĮż¹Ķß÷²ń³P¾N°õZńą*VEF.łXĢUHŌRJłŽCTŅCšÕ/.Žå8->ŹLģĆCø*F»ż9PÅ5ĖX +ķ?GĆżżÅL0:Fą@ÓéÜ>OŚ@TDŠāAŌčģūīŃ>ŃWČ>ŹŃMĘšģßĖYŃķĒĢ±źĆąŗøµÓZĮó²GūXėŚłż4/ĖDūż½å +¶ĒĆN>éŲīŽ=·żĶŗY/å@Éżģ±ZHōŪBßVLXO¹Ö·TZŌOß4±ŻŁŌśUĪB0õ±³4źŁC·šäEJ*ŗÆĢRP3āū×R1Jļ +ļż>±+CćóĘ3ÓC¼õL*+ĖK=.½ó×ÅJ3öĆ,Ž³ŁLļF»ä°G,īöė-õŪøļŲNŪLæė“WČćĢP÷Ų÷¼TĻ=LÅėµŌĒš° +õōAÄ4ŌÕČŪÓĶÓĖLÅüŻįQ¶ąÉŚęU*¼GŪ6*0Ióż½ķUK²ńWö¹ĶMųņXĮĄīĀTōęńLņZŲRŃ²DY-ūS/JEŃŚĆ² +³ń5+*J¼3øīL<ēįĖ¼Æ*ōŗķżī·H,ŽĄ8ģźGśćJP³HC;ÓéHļN:Ö4¾ņĀŠµUÉōŚĒTŅ1Å1+ÅźBĄ¹ŪõTåKķY +Yūć¼ÆŠJ»ĻVXT¹ŃŗģūżÓįŹ>üżŻAĢ0*ŪąÜ+¾ļņüżżV;0Ćżć½õĒF9ŌģT.2EQHµ0Žņē×/šąĢšĆ³ĄŁõÜA +K·»·=9*Śü+¶øĻ.ļōüųŅ²Ģń½śīæńäĖFŁżķŁN?ųźĶŹĘčYķ¶<ń=ęģBC<łXĒ¼Ū¹ø¾Ā*æß,.ÅJ**.:Jö* +LŹ2ĒL**ČF°īś.Ģ²Ką³¶š*ZōĄFåT5>Ö“ūēČ0¾0ĮēśģźFøņż»Y?Z?øż9HųčźßįYWĘM9MJ×°W7ÕČŽö¾ +÷R*0ģŁäW<ņ.¹š·TķŌNņ0¹ŻŌŠŗ±ÉĪÜéČŅ·ōš¶Śó5¾µ70å³ųā?Ä,,ķĪŌĖH@ÉäĄ*SŪō/Qøæś*ŠE@6Śą +ŽśČąįß5ćAWÖQ¾ā¶68*ö¾,åŠÆ<ėŲō2*ÖŗIŁßśėµÓĒšZķÉšKį¼ÓŹä1åPŪżQ½Ö1ÄåŚąöµ?įÆŗ*¶ĶPųņ +°ČŻĪVDōŽńŅÜVēņćO¼SM÷č<æßłÜP:Qńā±6**Hå>āÕ.XĶČčģŪŻĆ±É¶ÅżLMźZC.1A.Ęč±9¶°6¾čEPęÖ +įōAŅG1Ģßļ¹YÉżW<īVĆé4ń8ŠŁŗ+öŌ,÷Ėüż½éÓ°Cż-3KīEŃ¹;ōŠøėšāB*F@ČĢÜģõ“HĢż½śņĒY +Ó¶7ÉÆ+M<·DõōčäSü»/ÄšĖüÄBßō<6³ųĆÓ7ų1+.ĆCłŚ³“»³.ŌøWŹōĮ+¾łŹŌåQQĮßM@»ś¼ĻN¶ŗ9AśŁ, +īÜż4O·óÆńāAI*Ü·éVĮżżåŪŲBÅżJL6¼ńĮ߯ZQųT:Ż2*ŌÅģŅZšĘų¼ß-5U*Åķ¼óįµĆ<>ęóē=Q8GĆDé, +ńWč·¶BQĒ»»Yę<ŹOŗšTŻ5EÅ8ūżżśŪĶXūč26øķĖ÷1-*õCWĶłPTĢ0FĒ°ėLųŗŠŻIåū±ŃīņE5N¶ō³šäķ* +ŽĘģ¼+Ž²-Ųéü»Ēöč³÷3āQĪH·“VŠ³Fõ·IģHõŲ4ÕOš+ÅŚŠõV»łĻ,Ņ»ōįZōO½I0µ,ÅĻŗūZź¾ł>Ņ*ŚŻōŪ +¹ĆņÜżĄ+,Ć:¶UŁŅ×DWT*% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 342 di +/sl 60306 string uc +īż“*ŽŪõėPÆWńäĖ-šE9*įW<ćŌ1=,öIZ*Y»@J¼śöOŠĮ*Ƶ½GŻĒč1:ųąN³ųĢ÷6*üÉ1ę@0ZUóĻį»GÅZß +°ÓóūXź.øņµģńŠ+¾łįŪ¹ņĒ=+źŚµŠĻ<;ó¼G@÷ū0ß÷MIW;KĄæU1ćėŁ*F÷ąšP3Oøóē2*ÖD4*ŌŌ.Ź-¶Ė< +÷ĒŁóRDČŅA½üłū9įĢFµłŪų·čśÆŠ<Ć¼Ü6GĻ²į²Ż+ü-+:źŪå**SS9ńHUĻHĻĆ»ķÕńHÖƶŠ0ļēVłÖµī+Ą +é8*Ķ0å@»śöļ=B,**Ō,FB*1ęĢŚZŻPēŚśõB8Ž½Ž*ģüÆĒ4åĢPģūPDKÄOŁĄé+ń1M¾»½XUMÄ>Õ8ĢŲŅüUA¶ķDÕĒ=ŲÕ¶ķĀF·ńÉ/*ACŹ2ŗś*šŃÅÕĆ÷+NøŲ»ß9½ä/NĄNōWKÉŗ+G·ńÜĒł@CŹ:;Ē¹Uļę9ų3µķEQ»ÉŠ;Ś5¾1ōOA* +SGß×9FŪ,åēąŅRĄīĘĻMŌÓ¼¹ŁłŪĢE¶“ZZ2,żŽŅßż²BG?²å6ļł?8ÄX>K»ņŌĢĢõŌV³BU·9ōåH*Ź¹W·×@ +CVŚ·:ņUĆĀĮåūöģ×USŠĀDÕĢōėś7**šÅ>ī“,Š×æĮ7¶B/¼BK;¶@Ołø@ĖWź»9²ÅµPøWŲ³6³öīĄęłģUķŲ +°ųŗWģżėYŌ·ŹFÕÅīDÄżĖõOęÅ7EÄZYńżQłÓųÄæѵB·üģESæåÄĻįęŁ¹µ½ŹJĻżĶ4ņį>č¹“7ęįżŪ»ķ>ßW +ŃQ:G:Õ±Då÷į½»øļß±ŁąÓWõ<ÅIODBģRä,Ķńņ8ćĢńģŹ=MĄ>ĒĢO³QEķC/ųŚFąĖAŌS9KÄ»5ŁäĶ»-9ŌAā +ĖIR*KģśĒ-VO+FöéĖEµPČY><>Ėāł*;ö÷YBøFøĻ×VĘ*LÅ-ÅK2łÜōŽ2;ąÓĪé4ż³ČĄJ¶:ZĪ¶*ņ½ÆĆX00 +3BĪ:FśŻńF0.2;T>“ōīķģ,MĮ1?ĄŗWķę+JĒōŻFÕ,¾8BMĮDłÄć+ö +÷Ū4ÓÄŠ“čĻńÜUµ¼ŗöVĄēŹFĒÓĻµ9+»Y=ļźU5.µ6ŻŲŃæ÷V*Z¶B½*¶šDēė4äZWõSī-ÖĶM0@ō,XŃ:-J¼/ +±ą¾é:²ųD.ņĀXóēZFą798*ŃŗŲ°**2ųYšB½ĀĢܳ*āęłķA×Zš>āSÕčøņŃŗR×ø?KKĢģXõ*ŚNłYĢĮ+īēB +»*F¾ļżO¼,:SB»0.*3Wā·ģśZĆŁÄĻŁźŗøUJŚćźĖDņŗYĒßH?ŃęŁū@šĶõó<8IŗķÓEÕOSĪč=Ē²WńāU*Lō +ń2**H4Z0:Ė;Ņ,¶ķĆŁ4NĘ:äŲ¶÷±ķęļåZ9²Aį³5P8Ķ/ĆĀÓÅEI/ąÆ2ŠB3*:RŽ¶62āVÓ,ĀĘēÜųšSD*ūõ +ŪÖ=ćēĪF*ZŠūõŁ¾JĘ³6FĢÓGø¶JVĀąÅ:M½źFø*RŠūAåÓ*¶Łø³CBĪöżß²BCŁųóY,ĆĆ@šFµķŪõēÅ1éāę +2čŚÉ¹PTJÜŁČ·<¼SÓY6¾ŻXVŅJ»ś:šĀ»EÕōė¼4ĪłįWŽėńÜČæšßĆ1õGŗ÷ņēąO2¾łHŪ÷ķܹ1ŻóÆ· +TŅĘ°Wķō±·1F¹Ė4üśÕ5FüĘ19äėŁµXĆł:ĀĢ8-¶8OšĶ<æU5Į/Eżū½õųĀ2ųĶłXÕ÷ŅRĆ*ŅNłķOģĄ=čŌZ. +åųęFµ+ų²ŹF¹Ü@µõ*ŚNłYĢĮ+¾śżżĄ¾2ļżAæ:įłżżśĀ93ūā·ģŗUČ4SXŪŁŲ¼Hū·³ķģŃīŗŁVŃĢŪ÷æĢńX +F¶ėŪUĆČŅ43Tā·Ö÷“ČĶõ³=žģEµORXŁ³ŁāKĄUYEQ¼5+īĆŁČŚ“čŅZš6ßŃęėQĻ8Aę4»ÉĮ¾Ūū>*¹W@JĄ +¾öB0ŽZZ2<åÅĄŪĻū;*X-0Cżż¹QåĀ-ųYī<:L1µō*Ņ6łżIųŁ,»?C*.;śżųXQöÉ6*V²L1ŽĻNłYĢ4*Üż¹ +śĄ²2÷½Ē,Cųųżż*ļÖęRÉ5ߚܣČNAėÖÆņĘäÜģĶCźÓ;ÖŲśLóĖēš*öS*YŪŌÜWÉ.»čOĀDķHņ÷óPą÷Įõ8G +¹ņźAłŲ>;4>¶ŹFæ“ōÆķäŃ/*Q¼·UČ*ćĻ=å÷ļŚÉE·ńźĻ5õīń<1ĀĶĪ¾²ĮÕ¹EĢĻ8545YŌ2Įé:²µB.ßĀģó +4ŃS.üÉ4.éFJ»@62ųķ1.µß8ŚS*ŅśTźóO=¶ÓRĆ*āöZśŽēUU>F98-Mõ*ŚNłYĢĮ+¾śżżĄ¾2ļż÷ßæZÉGĆ +B.,3»ć·.Ż»õļ·ģśņÜU34āÅ9<ā×ÖµõAĮŽ²P=·øHĻńŲ5Ķ?3ZÖ±éĀŽŪń.¾»°ĶłīæŽĻÕHŚ³éŃDSøČNÕX +ėČ>SŠ¼ōé×YõōŪśÖX¼ŚDµńāĶ/ŽŹ¾VHŗ¼Žä¼ēTņ*ĪNłYĢU+Žł½śĄ²2ļżé +ŠńK/ĪRJĀ,5ŁOÉ/·Ģ÷¼ŗōēĻCYł³ėÜ»ķģWF*Ž9ÕōĖŚµšä3ŠSLQI6GµĢŚõčÉ9AüčŁģŗÆŅ.ć/¹óĒÅČ0ģŲ³śJHÓUĶXGŚŲ÷¹*B=JQ³X5Iīé°3¾ń½ŚµėŲ³Õ<ņņāĻ=AźŅÆé6G·ĶLĖ/:ø6ÕÄ5ĪŪŽēÕ=Ź>āö4Å**3,*+äÖ/µä.O +6ŽĆ.öżśż½XŚ@½ś0GG=¾Ģņćæ+Ņ/Ś÷,÷żżĆC.3Ź.¶ģĆõøå9IIFŽą¼ē°UČ:śżI÷F1Ķ¹ķŪµõHÓēēć<>3 +:Jø<·K¾Ņ24óčŅU¼čŌSĒ“ū4ÄŠGėłÖł=ūCQųü+:óA¶ōā»łōŪųšÜ¼¶ķß³åŌėŁA7ģøōV¾õėŚŲµķą»Ł½P +üCU*É.+,ĘNĖēķ½³ŚR¹ÕX±õGĒ22?WDö×.Q?Pģ¼>BJNLPŽ>?ČVŁ¶³łūįéĢŻ¹šÜĒłäŪC9HŁóčÓNĻÄ+J +ĻQå¼G¹*ŽÕŌW¹µ±ģŽ×-łėń@ķżNśżż>Ė¾īKÄP*üJXµ¼9Ģī;/Nłō?,06É@7FåT4>ģū6Ä4:>;=öO¾*ēį +Ęņ±š2Ū¹ööļķś³Ü½µ²šźZŅĄ@ĖD/NļūķĮŃIE7**2.3/ŚŃO-1½÷B5***Š2Dåņ.ģłXø×ī*:<ĒĻ=ŲÖłūõ +äłQÅōG7QTęÖUĄī:źłR.*żGķĮŁµ¼·éŃWłų0īŃO-åĶ»ńŌWÕ8XG?ł¹łäQAģ¹?PĄXķÖWÉęÆNŲÖ:Q½åÕ÷ +üĻĻĒ±įĒ4,łÅNŽDAHŹżDNæ÷¹QXŚµķÜłį*śXĢņ°ēļ+å?ĶP;ēŪĆE½X7ĪŻøĢõI+ŻÅŹFēNW9÷ę +5ę<ßĖż73BE**BŻ0SJłµCŠŃAŻ**ŗ÷?ß°ÄŽ±3Ą-Ā-+BŽBJÉĘĢüüĒł9ļĒMĖ²µŲNLē7=šĪĶ0¶ō°źĘƱ? +K*,0ćWĘ,×ķÉļĄļIå>öVĒā*Ļ*+0C0R>ŠÓŻ½šüMŗšN=³ćA6KĖ.ŪĢäģżżķź;*Õ=3¾ćĮµKŽµ5ŠīśIÕOø +?ēŃNŁį*FPĄųÄėČ²ē<9?čŅVéņ;āR4-żüĒł9ļĒ=?N=MĄį@µó<ĀčüżIG**śčą*8żż9ĮÜ8ó7ķŗ?ŪQR*¾ +ĒćņWWĖ¼ÅČäłµŹPĘ1ÅżżXøōĢHĆßłųäęÅŽżżē·**ĢšÅ8ęÖżL,ŻÓQ.īŻDR¶š»/8É.,ŹOXĶćŪÕŪ¹µķÜī +Ž³ßöüIĻłćį-ŃŃõ“Æ6W»łć¾Ō?üŻŌF*HÉJĄDŽ2ņJYUÜÄ5OR9G»ņęSĀLķ“ōė¼U°äłżÉHæ2ķU9ŪćÕĻę +VėżåDO:üżŽ+6*B+8>2Śč¹*ś¹Ę-ŽIŻ*KéŽWŃ÷ū±īłIŻāĄŽ4ĮĻ÷öĆ. +**A**VÜß+ŽżūÅīłIŻ²“ŽTéMµōĒ/ZC**BÅżIżõķ¾ķżś°²,±Ģņ?ĒQ@ŽĒTā2ü½łšā+ĆķI,Żż÷ćÅ.2ü² +FśźIß*µ9ś½õŚ+*Z,ĘŻ3æõż*ķżśšß+±±é°üA>*Ć,ZĢ:¾+*ÖłūMīłIŻśĘ¾Äæį÷L,FGF*Ö=.*JDą2Žģ +õ9æķżśšM,MRĢHś1<Žė**ŽFK**²Šē*ō¼ż2śüåAIŽĒ,J·ČæWŻ“ĒYŃŻ½.¼żńÉZ3VUK>EIĘ*>4***2ܽ+¼żńÉ +Ó4ź°æéģHĘ*åĶŪ¹ōģżÉ-KVņūü9öūĶYøMĪśżŻÜT1ēE8īĮū3.-*¾ZüŹJņżļ½ż8śüåAA:BōĖÆĘŗĮ+Ģ:* +*2F¾īŽæ*ŽūūUīłIŻśĘ¾ĀīéśĀ,6K**²0-Ī×*X½ż2ś¹Ž.% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 399 di +/sl 60306 string uc +ī½Å-*ėŠQņ3/BĄ2¶š80:³+Ź,ŗż,öüåA×::æņźGĻ*K2ß>¾÷ō2Žóõ9æķżśšM,čDWŠ=4:.GŹ,BK**īüü +;öūĶY¼N¾äć+Æģü+/:Ą**/*B3**Śüü;öūĶYĄOZ,Ū>ōüJ,ęčO*,*6ĢRÉŹ½ż1śüåA=9*ś16ŽĀäŠĒłĆ2 +2ö*,*Žģõ½æķżśšĀ,EŹļŌćQ@*ŗ1*Jł»æ2ßõūMīłIŻśĘŽT=/ńō70öŪÕ;²üĄ,ā²*A**6¼ż2śüåAI<ÖŲ,Mļ80īDć¾;-/**¾²ńäō**Žöū +MīłIŻśĘŽEéĀŠCüżż3ø**īĀ,ī2įżö½ķW,>¼ż2śüåAI<¶ł³+ź³żż½Ą4**ĢŃ+źW<:,B¼ż2śüåAI<¶śŃ ++Sźłż½ł4Ū¹łŃ+ź7:ZDK*ZöūMīłIŻ.ÉŽ¶QŃÕóæ.öē*“3>RZ<*,*įõż¾ķżśR20Ū¹õ8üłŻŽõ½¼Ē/ææķ6å×=5īĄ***:ėżżöĄ+¾Ķõ½æķżś<5,ć9»ŠXÜļIÖ*ŻÉ=R3R“/Ńą***JåłŻŽ +õ½¼ē7æėD“Aż-3īŚQµ0,**BZ64*JĄ»ż3śüåAŪ;BĒņņæ½:+L2¾+*ćżI·*U÷ŻŽæ=1öü7īūĶķ»4ÖÕ,/æ +½:+X2**Nł8üżI×I6ūłÅŽõ½¼÷;+Ģ“R.¶ś?+ÄķŠRāå+ÖūŌäĻ½ėø÷łåŽõ½¼Ē/æ;/ZCł¹:ŽŽ+VW6*¾Wķ +ż,Żż÷O@.Ōµ<ÉĖĶV¾*,<*¾»*¾ÆĒµķ꯿/¼żńÉV2V±·śōHĘ*õĖ;0****ŲÄāW=÷ūMīłIŻśĘŽXéŹĆņæ. +Ę×*0*¾ßõĶ¾ķżśšĀ,MĶŁ/»åHŽę+*Zół½Žõ½¼3@+ŲõXĆŚäQBŽ1Y=10**ÉŌ3·ÜÉ-*żõ9Ąķżś°š+ų²ėč +=4ī<+*Z+.2:H.¾ćķI,Żż÷ćÅ.NĪH¼Q>ŽR,*ŽZB*ū/īöłÅŽõ½¼÷;+ėÓõę3/BĒ*Ōš80JĆ+Vüü;öūĶY¼ +N¾Õ/4Kķü+/:Č*öÅ,Öē8+Füü;öūĶYĄOZ4»VäüJ,ĘN*öM+ź>**@½ż1śüåAE;ZśżżµWĆšņ-6ī>+¾Ļ,Ä +42*.¾įķŻ,Żż÷OÖ.ŌÕܳĆ9N*ė70J*¾+4@Źöõż¾ķżś°š+Žż¾+V±ĒĄäH;+²****UŻż/¼żńÉ=4Fŗļ,NĄ +żĶÜ·ķŹżżżģē¹19ļQ0+ąÖõ9æķżśšM,å9Ū2æĖżE½UĢC96ī³/š=Ö5**Z.IłķŻ,Żż÷ć/.8żżżō=ēMųŻń +@3*õżżŪYń**R:+Ą+***ųõĶæķżśŠų+żCüżō?Č=ężżż4Žóżż7¶***VūżŻ6*,*łõŻæķżśŠų+ŻŗWéčż +å85ćżżY5*õżżĆøĻFĆųüż½ėÆ,*ųõŻæķżśŠų+ķ¹ēŲŲōUķŠ-ßńż4JóE?ZÓ3,ŽüūµīłIŻŅ¼ŽĖķ/LĶłżų +*ßäģé,ŽūP/Ź7**Ī½ż7śüåAÓ9¶łģµĒM÷Iå²RĻõÕ*XÅ1¶üźó*Š-Ę,*,*łõŻæķżś<5,QSģAõ±/0EĖč, +Ž»F*Ē-*ŽLŅ**īüūYīłIŻāĄŽ»ńĮÆ·WČZ4Ń×*X91Bš**ę·;-*żłõŽõ½¼C÷¾ńY¹ėŅŚÉ;:³NKŅOHFŽī= +õėśłµŽõ½¼SHæńÜčÄāŲLRøNRŲųüüūõūĶYčYīōĆVįö4KæĄāččķ½żÜż÷Oū/8żčS“B8ŁNĘFWūč.T+*żõ +ķæķżśŠų+½W9:»øŽ2Ų4T½żśłøÜ.%% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 456 di +/sl 60306 string uc +ī½ż-¾ŁŹ?É13¾ĘŌü*ųńūÕåé÷<åÜ@*:ZŅVWūūķķłIŻāįŽėń8O04=LU½żłłüåA¹CZō>++æK±ńõY½ķżś +°A-äĶ»<.,ęėūüóõūĶYü²¾Lµ+ĘĆņłÅŻõ½¼÷Ń+¼Ōę+;ĢķIüÜż÷WJ7VY7¼B¼żõłüåA÷B*śÆ*ŌŲH?,Żż +ū»żńŁ¾DBō³YĒśüķõūĶYšÆ*śį*Ź»õ,Ł¼żłłüåõūĶķłIŻõ½¼ķżśÜż÷»żńłüåµ3·*öń4,Ž½-EĘĪ0<łÅ +Ó»PMśāõņ.µĶ¹ńćÅAYÜVT¹żźÕõū1Y0@*öķÕ¾ńżżI¹ķÓ9YōĆ5õėł“ķÜ»»ėŲYéŌGU=+8-0:“źŽ+MÄÉĒ>čÕZśNō/ōIPÜ×ēņR@ÜOYÖÜų²ŌĢ-1V±Ć,JņŽC+*¼L-¾Ķ×¾ö¾·æB*1.29F¶īĘ+Ā=Ė4³6GĻܹIŹ +*üģC2é4Ą:Ī5Č/ŽżF/6M2Ķčä;0Ō6ĪVä9¼.ŠŻPQ<õõ;ļķĢ¾į1ŻųģŌNĆ4ó>ÅĖČā1:G¶>6C°īę+>G*¶F +¶ā¾+-Å8JXRŅÆ4³4OŲÓŹÓū°2EE-FżÓDSXäS·,ģ=+Ģ>NÄDÕ°QżŻÖŻFPŽ²X +³8ü/D¶łĮĒ°ā¶óZłÆåŌ÷UŹ6U5śżÓ>Ł¼?Č;QIŹłTŹ8·ÜPķżŃŅ×óTĖ<Ł,šæ.ģGµĖ>CąF¹Ųżżķ¹UıAŃ +ņ>Ć““żż½ņÉŪRĘ>Ē“Pżż½åSńčßNK茟ĖżķÉ8*=ßŲøłÓéŠāR<ćÜśųżżŃĶĄVGµč¶ķNC@üķ¼WĒXŃ·łāW +ÕōŪŁ³JJĪĵĢN1åä¹C8ōāļż/Čń²łāūTĒĆ÷YĖNćšÖĖµ +F*Ģż½śīYYG@ĒĮ+Į.2;Zā*,øŠ9*Å-04:LÖę;ĀQ÷Ś6ū½śżį²¹śĢśżżī/ß¾æĪ3A*,KĆņ.J*ąSųÄķGIJ +**:2RĘÄ.¶»ø**ĪKRÆń.Ż¹æÆ,0ĮĪŻä9ŚæŁõĢ88**īĪńęļżżĶÆ>2,F.?N»ńżY10A²45*JņG?āOJ*ŠÓ +õż4Ā¶JĄZ¾:ŅÖłżĢ½³ŠLFB*;ÉŃśż½WK0@Ʋ:JRĻC>ßE=>-¾/BD»łżżÓŽ:NĢ8+XĀŽążćS+*¾-ĒāŪäV +::2ī:Ä³ĆżŻPJ**ēŚÆįõČƾģ½ņß.¶;½×Õó+ÄHńĢ²@**:*ßÖÖżA+***ĀBšå±ē3Æ*-ŹąÆ:ŗ4.KēÄ3š¼ +ęŽĻéĀLęó×ß*¶øĮŪį=L.OōüŌ:+śī¹ŻćĮĪNÜįGÖæUį³Ą=āÄ4ARā2F¾Ģ**ö·=+3LŽÓA¶IZŚJļL0ĪSJ± +ÕĶĪŁAāĮĄMēP+J*ŗF4ōõ<äųP1-ĘUņś;2@Ā».OÜćŪß5ćß5ĄßĮęÜ<Ļ¶+ĒĀ@ÜPĀæ×8ŚąRī蚌OĮ5:čł@3RśWó0±ć +T5ū@7J>ŻŃ6±ćčųŽ6śYIB“ÓWŹĢ:QÕøńN<.3Gź·Č-:5åżø:¶7UķŌ/*,ŹÜÜ@K*¾BüÕJæéęŲÓN2Xęūõ> +A+ĪQņZĻ¶FÄMÜé4X,Īżģ³,:ĮĻ=ŻO.Ā³>Ē“óŃĄCāķłīŌā +ńP7õĖ=ĀNF?PĄß+ĪFE6ÄZā?ųÆÉč»=7ä-*åĒśB;TņÓŪč7,VźūB>PņßÕW?,źłŽ6VW2P2Ä,¶¼4YAōKMDųüI÷Ų*ņ2ś9ĘĮ¾į +śżż*ŗĀšżżõ/*1įż:Ś.FżżĶÄLąAżĶĢ6-ĮĖńQķ±ēĪK¹ėM,ö·õż3ą+ĪBµČ8Ą9ĄÜQĶ/ŽÅ5śY/0CōżŃõĄ +ZĘ÷żżT*¾ŚäżįźęÖP±YįĖ:KĻ³õ·9¾MY6,:½šL×ļ,.Čś.ĀÜżJŪJ»Śé<.,ß@żżIK-Ąč6¾ŪĖŪ¾ĖDCQŽęķ4Ā-Ć2>¶ś2G¾“B*V14=LīĘéŲ× +Mćß5ĄßćżżĆ/·.éüżQē1¾ŌģśTG@**¾*Ą-Ž3ü°·¾J2ī8ĀĪFėü°»Ą>.-4Ā¶ŚśŽ6RU*øąŠQ*VY*°>0-¶ +õ*Ņ6łY10K2ūżż*ĪĀĮłÜ*īRśYæĘRüż½ģĄJĀųIÉJ+ZÖŽ÷æ+Ī·WK0.ųżż?Č+īõ²øą+¾,*åĄģ½?B.׿ķ +Ū5ĄßäżżQ+¶GD÷łP·52/2**K.9Yæ³F0>Ń.7UPā@Ķ/ą<ÉżÕ0Ńß+*Z*ĄæVżżUĄ*.ĖóWR.å.*-Löż>2Ą +TŻ:-ż6Ž>ĪżżAŌå+V°1JŌ²*Ō÷S*ōDżżżčZ,JķäXŽÄė6åŪ?ß²Õ.÷æ-ļŽ+ā1Ī?,*ę3Üī¶8»ŲÄĶäēōł· +ŃŚ»KHŅśżŻÖ:õZėżżDó*ęčQ-Ą°3åŌšĀ¶*Fż¹YŪöX¼“42GNU,ĪŽVJņęŗZ0ÜŌ->0żżåüŲńPŻB0:šężõ +ä1-¾Įķ½J@ŽĻ2³1*ćÕżżł,¾įCü1+ĮĶżżQ¹A<“ūAQÄæäĢG½ųŁ¾ŽRŪ@G<¾ZFŪį+Īö³Ē:Śśš./<>Rńģż +TŚ1KōżĮAāĮ6żżżT*¾²üQķĒµģU>į²R*3»öµŽD¼38BJĄK6³ĒÉæZźźżÅŽ¾ĻDÕČŅ,ßŌĘ1“JRWšŻZJ3õī +./2Ą*õ5°īŃ+āą÷ż½ļ±Ū+>ĒZZN3¾8ĄMæõĪIéD6¾Ūū6¾ŪTC¾½6¾įõŪ÷°Ģņ.æÆTŽę+²Õ+ī¼Ž¶Ļ +,ę@Ė»WńåōļõļT·öĄZ¾÷ż½ÆK8ßŲżżSų+ĪÓ90+MŌŻÓ÷3ļ*¶ĄŻśõļéNūB:<²Ų½ŗ¶Æ»»óD9<>Ģæ÷żżķL +æ;ė±ģ¼ŌäŌäAį;;Čżć»@K*ĶNüõøZ2Ąüż½*Rąß»Y*¶>üõŽæ2Iū·U6RŌūµę/+:·µóŪĄZRFÕé4GRŽö+¾“3*L1Ī±Üīøī2;MRBńōłÖŅŚ»KHŅśżŻÖ:ÕZėżżDó*ęč5ĮZ+ÅÜÆ“*¶*F·Éļ+ÄHø¼3.;FĖĮļ+ +ĆčMA4.;69ŹæµH0ĄĘUņź¶īēDłŌ1-ÄMņś;2*ęWü9Ę/½āśżż*Īśųķżõ/*1įż:*¶.ŃX3,:5ēżÜöB*JPW +±-,-KåŗėM,2ÄŃ¹F*4Łõ8/2CÜAQŗ*XēśYĆł:āü½ųĄZĘļżżY/*ÄJģņŚ7/ÕŌĻEP,4ŹäüX·ÅŠ9øAC3** +.E=Āą+*ÖÉū½6:ŗRøé@+*ZUāĮE:ĮŅ÷Ü°¾6ń±SP-īéźżŃ2.KŚYĘæ÷@-ĘÜ·*Å+æĮÆĖ5É:¾Ī6MIDŽĀĢ° +ļµ9ńYåøÄį1Y±ŲNĄņ*L¶ņę3ÄŻč+*JQ<ŽóęļMåŠWśŚ-ü7ŚT8Ü:6XüżYVĘMÖōż½Č¼*²óÜ/4;Ką>FDF* +ÄÓå0ĮMĄ/õßćTģīå@ŃMOÖĪß/ēč1äŽŪÕ80Å-5¶Ä÷ēņėEÕA3058-4ĆĖßżÕ-Į4Ąüż½*Rąß»Y*¶>üÕŽĄ4 +Ķ..0F±æż1ķ5E>,Į/3É2øńöé4°Jīżż8Ļ6*½żįYÉĢÉ+LĀČZ»ŪżõTZ2°żŻ¹KBŚĖżżéLĻ*PBåėī<7BNĘ +ĘĀ?Ģüķ±2FūC81NÄNłĻ+ÄĶĖSŃE¶üŲō±Śūč¹·H-N08ī=ō8*?,źō*8FĮéļM0ĪOJŻX0īŁõÓF½ĆņöūżķŁLĶÄ +ņżżĮ38Łźēõ/BėBĒ4ē÷ƳĻłÜČīÖZXĢJķģ÷łģTļN¾īōü,7.ūŌ;-8ļTłśżżķUŠĮ508ŻćężżQ4ŁÆ6Iīį +ņļśż½óÆ.ūģż:²*ŠĘXDåĘEüżżĖYPåÕ7-åšīżżI¹ÄįčüżŻ@ßZĢłżżāé4FŌ6ߣčŗBŚŃŃĘż½ł4ć3ņżżĶ +Ōõē@¼ņ»ÆĖīÄ75PńZ³ż½ÆĆį4åģD8FēZī0ńIĖĪNŃA°ą1ÉŚ@6LÄųÄ·ĒężõćA3ŻŌü½ńŌņĢOŃÕ¶ā-Ļ.QÆ +>J/¾¹åēä;ģōżżõDóæUéF·Äē.ßÜßEĘśC>4»µķł³ģę¶ī>A+Ļ,Å8JQ-*öŚ7æó»¾ą+M-Ī³āĮ9½Ā*ĀGå? +*NæĘP×å,ŲõÜõīįׯKÄP¾ōĻķėŹśJ*Ž½V-ęE8A>ßIF+Ć½Õ::µŪYŠMüŅV¶õ±YKĻąÄ8JĖ**ź1:CZĀVŠö +ėĪ7æ:<č,ī*öķŃO*üF°*÷įF@3÷ĮWXMĘĀéÕ9-ÖÖäWŽėŻM:ńüÉŽŌūL>ķ-MŚ»ųÉļæĻ,0ĪÄ**Ī±2¾ėRæ°WUõōEijł2MęŌ²4¹XV:ūä,»Ł½ +µĶÄŪŽÜā¹8Æø×U*żł?Žę?4,MĆ9GKņūÆ*2Ä4B0E½Č±śü=ōZ-ŚūÓöL+ĄMÅ8,ā1Ī0+*ÄM1ĢQēBĄķŃŽ°ä +Q=ŪŲ/-*ō¼ōżĶč1F»ōĮÕĒ;M1ĪÄ3*ÓLÄĪąÜßõRÆCšĮOøź·+*6Ķ>*VėĻ-łĆ6MQé>,Ä9J»-*R18:ųģ¼ż +9ūEKÄ/Ōš-Å9G¾øQ*:8FÉęCż0³2ö½Įśč;0ßö»ķĖā04JG.*406KLQŁżüYų?ü5Ž½=+źŚR;Tā*ŠŃY,** +Gč*Ó-/Č³ā÷½żLū;70ŪIĖ.0Ģ;÷Ä;*īńč,Ī7CZäšüüµųI<Źµ@DĖ6B¶*ēēO+Žŗņ¾æ<7ćÜķ½QŻįæKÄĶĀ +ę+ŅÕ**ūJ;N8żõķŃYļŻBŗķĄźR+NQ¹/*ŲĻ¼Õń/-ÄżżżßU·š½°+Õ-Ž¶ń5ĆÆ“ĘZŃ·Ōóūā,2SĆ.9ß*ĄÕ·źZņIĮēÖµĻ׌ŽĻ-RŁ+*č+±3Kģ1Ģö9=¾ +ĻŃ<éņŅå¾NČE?79Öč׌ĪÉW;źźV,AIĘ*ĀķüŌVį8ß*ŠA2ÅŽčIĮ@ĻŽOD*ēēø+ZRīęńüÆBXłĖĆ³ćQ=öF¶ +:-ŃŁWL*ŽĶĆ>6¶ĖśŌPŠ-µP+C,:Ņóżø..ĶŁĮOLY3+ĢW0ąSų×*Mķé¾øķEĒ6G¾ų7*Ś9Jø*ēæ-PU¶ūWFX +µ2ĀõORĘ5CĒB<õżżéłNć²ĮĻFįēĖĶßĒāYDĶāäżżīõČŁāÓ¶ĒĀĢµųäŪīRšB,°=ēģ¶,ķ0NŅēżżńŚ0ļ¶3O +Īł5»õÓĒPĄ-P<ĒĆūżżÜõPĄMĆ8:AŽķ5I-±ĖRIZīöÆĖĒA@,+*AXīšē4Q·ĻLĘ9é6ŪūÆĄ*ļņĒŽŽßĒ=N?“ +ŗŠÓāUÅ,°ė½ż9=ŹÆśöS;āŁO“żż½ĘŁī¶?¾Äė/żż½FHSDÄĻŽNDÉFż½.Į*/׶Ōµöćś¾Rņüż½ņĢÆ>CXŠ³ +/żILķU¾ÜÜŻLó:ūįņīÖ3īģ¹+¾¶ŽNŁČūÕ¾ŚÜ*2µ9FGKLŅJæą=;»ū½B<*ö÷U²2Hć3/2C¼Ė462üį1¼Ėį +K,Ä4B°ĘŌōā+ö¾=78>ĻFKšŽŁ8=,Ć5AąåÖćīõ4°*āĀC¶ +7RNōėź/,*H3ćą>=SĻ,ÄUæL°±öĄZÖīé*+Ļ:TÖZZF06ßDµ@NKBżżII+RLė,įÆŽßåŪ¹ķĄī¶?Z¾¾×äśż +żģæ/?šņ*;KĪõū½54*@Ą³Wīī³/3<ųż½ķW»FČ“4*1Žį;ø“*Ę +RéQ5,;źżż,+¶*°“Ļ-Læ2šĢłS*Z°Ļ--ĄŁBūż½2*:6ńŻ.+ÖNńżąį6*C>»A¶¶Ļ<āóūżIēZ6¾ĪZųF*;> +:Į.GLõQŅ8ńõ¼78Fø*³Ž*JFøņ¶µżQRXY*ĀYĢPßŅŲōŪżó:¾6łI64*»ŪµłśÕ++ĻÖ»ÅĶĶ8.żĻYģäR¼WŪ +³BŚIO8*ĘWōėÕó¾:ęłżżB/ĄśļŪ<¾ę±ķ¼UČPęķ°.Q11ĄčęŪĶūć±ēŚä1¾×Óė¹šŃNJ÷ŗÕ:-ÜĖ·łō5-:M +ĪįŻO.*śż×Ńń.ĒDYŁ6*JLGŗKBŅBQŻA:ÅZ6·ś6ļ*BéE5Š:źżżś6¶BWėįŻä/*ķģłX·³»ŻŪ.--öśż½/B +ņ6L¶EüüįÕ2¾ĪŌó7ÜŲJ*:9żżIĖį*°Ö»ł,µOŠżżMQQāĻIĻøČGāO¶ņ*čĄ*Z¶źZFūQRXY*Ā9¼/KDķżļÕ +ōTZŹųI64*ā?śżżĮ×ī*źįżķ÷ń-MŽšŪĄ:*¼Æ-ÜC*Bśüķ<,¾ńż9Hå +NJJ*ļ<*ī;4Ü:6ZśżżĮ/K:õż½Qē1¾ÕĖ/S2äżżåå¾ĪŪżż¹·Z*ö0¼Ö8;8Ą/3:*ŹüżIS:Šē:,Ę3ÉZ6+2 +čøčßżAP1ŽVģżżńW4*³üżIż*1ś9ŪH:ĄŌĄK%%% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 513 di +/sl 60306 string uc +ī½Å,*Ģäē.8Jė2*NĀąčWøÆ»3Bü¶D.×ųüQ½×Į*<źżZR*ī9<īßV2ēśż½ĄøŚĮżD/+JÖ÷½QY;*JUĒ-Īąć +ūż½õ;-śüłżįF2čŚŌ.:VīżżŽAķćÜ6ßÅSļ¾E»śģ5łąŚóß+ī@·üģ»@6JTķ»NJ6æżI7¾7¼żµKļ*üżQ8, +ĘRÜGøŹ**÷²YĘ11ńżżąĄFVłķżįŅ-JéēĄJöZżż5>īęėżüIGB*ŗÕG/¾PUśIG;*LL,7īżżÉ+Ę3ÉZ672D +ŁÜ+=É-īÓõĘ2µ+āšżżIAÜUżżõĢĢ3KY,ó>åVZśę+Ņ5-¾āāŠöOVXY*ĀłŌ3,ČWN?0ń2*SśI64*ōĮ.FKG +4ęĻŗT00ÅīIćN*ĄÆŚčć+,J2Sš*Ś.äłÜĄ6ęłżżB/XęļćĻZ2<ĒŅēE³źļ°.ACĒ¾Æ6·Š5ćÅóćC5¾V,Ćøø +TZāą»Õ:-¾Yź¾µ0*0ŻżŁĀ*īäŪÕ0K,8Ė.»=*¾ĀĄA.-6żżżZK2āūżķSč+Ī³±K4Ą÷żż÷C:²õżżÜ76*ÜŻ +Ģ,:ȱāS1.*ĶżżQ4ĀĀDĀ*“ą<6M*¶Ā8ĻöżÜ3,ĪÖöżżXŠ:1š²<Ū?5IłżŻ÷³N@ū2ŃŃäóSļį1Ī“.*ŅįQ> +õö½Āä³Ć½¶ņÉß2;<6:?8-Ū׿ĮK+¾½Ę¾µW0,ĢSéJ2ī8üYŪ/,ĪVßF87*.WO8¾4?õżżŪÅ.ĀŃłÆ8.³ōW< +4ATRBFLLOXÄīĮŁ3+<ē>BJśŃŪĒ/D+šR@õ,.8ĒźķH°*.Ä7Ęē*,õūBA*ŲXģVĆ1A<ź=B*ņī²ķ/÷*ńżżą +Ą>ö³żżįŅ-JéēX2ĀZżż5>īĘŻūżIG?*ŗ9ōé:Ž3“É3.*ŗÆüżK*3óż½Ų6¾ŹÖ.>ē+4ÄŚåEŚ»Č8Šėūż½WÕ +1ĮĶOßĀÖCOżżµHÉŹW9Ąņ>¼ČÉĀę+Ņ;-ŽŅĘēčśĮęŚģ:2IE72*4¾ęUWżż9Ä.*ł+,8¼ÆK:Z2·*X,ń»YK0ņūż½Ź,A²C½ņ4=æ*ŽŹĀŲ6ūæLöä<ĆR¾*āŲÓüż½ģQĆR:*ĄŻL÷żżŪ¾6īEDī*Ž +īłYĻ:**ŗŲŲDó¶*:4:1*ĀNöĄZ¾÷ķż9@.æŚżżSøI*Ų“@NKÖżōII-ĄĄū»EB*ŗEŻ6..ÅJ27>*ĘųüĶ>23 +³2+āĀOJŠZ*2ŠÖÜæäĄC?ŌßēĶŌÅĀ,*¼ś÷äżżł@<4Mśż+ņ:½K¼ź*čĒ*ZøīŹŲśIZX¹6Ā±õ»·ļąĖ»ŁÅżż +Żģŗö¾¾ńüŁ³ķćŃ=+ļ8üįõÜøļąĆ?õåĆI±µIĀJüż99*ā-2²ļēņżń1H“Ž@é0ĶżæL¶ųäAQXŚCµõüż½łźA +AHŗ×Ņ“HŲJPA8Wīżė9Hėł½šĮGQĒ»ĒEIŪ¶ÆY»ųīćāżĶÜFTĶęĶXFRßĖ½XŹŪūēĆYįżżĒAXG*ß»µXźH½ö +Ė5łąŃQYX6GüżH÷ščżżĮYŚ·VĄ;IŹÆé-·ņĖ/ą:ęś½śō-5ŗļåāßżõ»@,ĻÕõ½öFNķE4C¼ąÖE°*Ņ8?±Ūż +ÅżI¾XUśįĆīŻT*YU<įēÜUé;,¾źéCĪ¹ķĖĖŃŻČÓJśīQńäÜ>*ĖŻĆÆźīUD¾Į/ųĪēłÜŽŠ/C+YµóK0ĪÜ.*Į +-.įäżæńŲåNFKF@>,Oę+2öŅĻŹĄņā<öT-ĘāNŃ¼ĀRąŌÉīę+ŅC-¾īNąõKŗXõĘĀ½@TÅ02:.SźSļP»·ĄŗĀ ++ęGS¼ĖŪµŅÕźÖCĪ¹Ē°ņ*čĢ*Z°ÄKåżIĀXńĀĀ¹żV=IŗņĀ@ģSļPāQ9¼Ā+ä¼ńÜźIĀQąåś¾*ŠŃG-ŽĪVčźü +½2ŪPĢ7¾ż¼/Šŗś.Ģ:æSŗ5>¾ß.÷ļ-R¹,¾ē+²3÷ŻŠ»ķIĘŌš1²Ē9Gæéū·*L1õõ9ÖYėÕ6½J°*óR*BGT¶ń +żü/łĻóČŲõ806G¾D²*Ź6:Ćė½żĮūęø/Ėµ³-0Ī8/*Į-ĆääķżZI×V+śÆ*ųCKC“ņ*čĪ+Z¶ŅĘEłūIōMé?V +F48F¾ø>*Ź9BĶR¼żĮūęøćģčĻ-RĢ+ZßźĻ>×G,**ĢÄ:H¾*Gū3ó-1ßżżļ½żÄūć²OōČ3ęĢŠ*6GĪĆAµÅ** +ŠS*ĆŽ;öū9ōMé?U5Ä8J·0¾ĘFŚKę,DQ;>,ÄŽ0+ČõŻ:WśD¶@2I¼*O3*ĄÆļ0K5ÖF/Ī2BśD.RÜżĄ²ļéŽŌ +Jī*Š=ć*2ĒĮE.WJń>ĄLĘęé:J÷ü5ŠĮW-ļÅ/ą1Ī6+Žņ*ąÖö³K¶üäŪŃżķŪ¹ņAF¾ę¹µüŌ2*śé=Ę..Ę¹õō +ķV*×õ8üó:¾ćC.Ć÷ÆĄ8żõŻÖķźŌź°ĄRņ.ūå¶*LšīéĄī½+ĘKĘ½>:8X¶¾S>.*üÉK¾öēS+īø“6SµĪö·5< +²Õū5;¾źõ°CÄūĻ7FC*ęFĆĪB>ßģšżXė÷Ż<ėæ,Öį4Ć*Č<*Ź9J65Ż@.Y6*8Ąż3HEóĄ-ĘąM*ģ4æĻ--¾õ> +M,ĻÉ.²Ź÷=6ĪśĖ5ŲNŅūŁM*ÉŃ:4ńå*¶Ē»ēÕģŚ¶± +ģģGüżōŪ½ģŚÖµōėūżż½+Ęļ2żÉ>õźżżōC÷äÉI=>.Ž/ŠłēÉUńHŪ¶³ņĢż½øT*FōūB³0ģü6æY¼Ś¶³ńśżŁ +õęEIÜŚCøöż÷½õÜEIHėł/D¶śŚAOąõI¼õöųšąÉ»ÅAÖVÅøæ½ÉüĪ·4KÆRÄ?äżYü<ē+ńčßÜKõÉćĪéųP-: +¶Ä¾Ō÷üAßō¶.ęV9¾ę:į;Ž +Ī-ŚūŻ*ā¶ĄżQ+ļīŻõŃV-.6ZĪÖ*ZX;.ŽćJīߥ-*:“·ŠżēS+*/6>8+Ź²ż1łŃ+Å5,NL<ÄżSPš@<»ŠLÄß +ż>»>6J²ö¾æššL-½V;*ÄĖżńŹćKźŲā10»µżįŁé.2Gū>XJń>ĄLĪīł9Æē·“É.QŠÆ»ĻłżżūĘĒ/żź°T1KĖ +ĄŲšXDī0+WŲźü½ĻņŚ:GĄĄėĆĢI½įAČ6õżļźš¾+UUĀ9JC,¾ł+ĀĻH:BPēņįīĘCŚAK*°ŗ@Ą¶ŗĘęéŗÆ1*1 +ßżĻŹRöķ1=.Žä>»¶ŗ**A²ßćBĪīēŠN*ī¶»żēæ+Ję@»8Ą1GżÕPĮKĶUŅ2+ŹÆżē+ļBņ;60/Jöߌ45įŠWĄ +ZVźL-Y¹7*ŠężéĮį+Z.Ē9ÄZLżMģ,7:Vś>WŽš>ĀLŽīł7Oēē=Ż/24>JŚ¶øŚżżµąĮ:üX=:*Ķ¼3ßSłź¾¹ +Ż0/JģōæI5JīPŹęłśż½Šß2Äčņ½MŲO<-Ō@6G¾š6*AC7*9śXYŌåYGOB4*ÅĖżłÓÉFĪEūA*C8ļżSJFśAD +Č-Žµö½¼¶*Ž½J5X0**ÕĖµ0JĄQųĶĢ4*L·żõ°V*÷Y²DKÕG¼ō:¾ĀųIŹ*AŠóņŁŃ2ų0įæRļ8»W2ø6@D¶ĢŃ +*ņūż½@>8ŽXķä/OM÷Iüå3¾ŚŪQē,DQ;>,ÄŽķRČ8śS2NXĻŃ-,¾2µńR²F°YŃ8*<Ź4KW·17¶śÉM÷¶čF÷Ó +8ĮĘSööĘ*Æ@żżŻÖH*WYżÄ²ÉN0ęXC¾śåę1-RéüżŌ +ß*ŚĪ/A-Į8õöēéļßßśQ10*ėöż¹KJŗśÅܶÖQX00*L@ūQęÄ5Z+ĀŹ²Æ÷MīD.óGV=61į47FMF*ųüżĶ::1 +J,2Bįō°ż9żń>J2ēŃ²+ÉŃ*ČĘĘå2*ģśX·L·ÕŪćĮ-Öłż½;H.ļåD++*ÖÖ¹łM-ĢIMGL¶ĮåüŌéŃSŗėÆD6V +7;JF+Ī5īłO-Jł4ĄįÅ8JC0¾ÉāSŠ8>@NRĻÕų8į,Žóę-A1æĪß½+ĘKĘżĶ,ąß½Ä²ä.WėZ*īIĪ/A-Į:õżß +5Ą+ßśQQ/¾ę,½N-Ęż»3ą+įļęÆ*NŅż»/øFJZ¾ĪĮģY*8ĄĘÆįLßZZUĻæCņįMļR3AR122:ā2VUżżå3*:Ķ +Qē,Dåī=.1:ÄŻĻ>GłŻēŌ3F0Å.*:ųäĻę:āÜPG*R,ō1²Žė5.-/øüę1ł³éö7Ń--¾ÓK,Ā>¾³ļ.Ż²*ZļS¶ +ī*Č<*FįŽę3äĄį¾+AKY>C*¶żżķ÷ŃĮīęķą*ī¶ĄżMCĻīIMG6ŽXüżå0-īĄ@Ś66Ąężµ9Ę11ńIGB*ŗżżķ/ ++ļļIIC*ŽöU6;B÷ņIśčXY<>2*ÖKå¾-ZŽ*,-2.6U²Z4/3čŗVą?P°ą¾*?ņüįżF+*<ų>WŽš.ŗąś¾3Ćšµ +>Oå¼÷<>0:R*ß+źūżŻ39ĄöEĖ¾*V»K-ĢIGBRüżõÜÖåĄ,æ+2:*F7MÄ8JC0¾MāR9ēē +öAYš8.O5/¾įäżūčO8ęĖü5¾6Åöż>:8ü57ć+īEśIIF*īIĪ/AM*.ģģ»-Īß±ūQQ/*ģżż¹KD¾ś9OæŹč8I +¹2J@GÄSĪķćģūÆ7D.æļ°<ūż9üļćÜ57FKF*ųüżĶGQæ2ĀVčŗ7 +8IOŃRĀ:Ś.ŁććBßÅĪÜU.õ@D:Ē¾*Āš¹¼Ńī²ėT<.:FźżżF+źDł>WŽš.ŗĄī*ēDŹļA0¾9¼/ß;ĒNI×**Lņ +Ś29Ąöé..Žč5DīśSų;ŠDśäŃŅ-G>Ēä<ÆV*ŪŃ.¾XīķĘ“Ēā¾Ų1Y06G¾°6*ASĶ56¼ĄO<ąĆ:ŚŹŅ0.Hī¶.: +Žö“żA*CĢīżSJFśåĖŅÉīJĮ62,*Ž½J5X0*.LĀęĪZČńśĶĢ4*ÕżżõŲ*÷½722Ńŗ@G2¾NųF60Å;čŌNB.S +ŅĘ=Ē*,Ć3/į8>ŠŁŚ.ŽXłż÷3š:Ä7É/+:æżĻ»Ė*ņó»Č@Ī÷ĻĒ*HÕ¼ąā*·źüµ>OŻŗ6.BįøS;Ž+ŗĆżI47Ą +źEĖø*:»K-8½WKJÜ°żFĖĮī°VBB2*:ćōżŻP2¾ąąŻ;ėŠ×+<ā0Ī>+ŽCŌŪFČ2***·¼ģļE½4Å.*8.Ś×żżA +0*Å=śIČ3/żżEåņ¾Ę¶N02*AZßćÖ:M22-ĄŲTõżēæ+Ž¹JĮ-ļļżX»:VŽļųZö*ńżÆČ°6.+@ŠĆ8ö×éJJ¾; +ė²łĆ0¼ÖH*D»żQ½ŌQ-**ĄML·żÅĆē+Ž×ųėæPŽ=āÅ4ģķ;>GłäZV*¶.Å51¾šóżPŗ2ąķ²,,ZóĒ7¶õßZRņ +7ü°IX,*K¼Ņ*JĘŁüķD6¾ćģŻįŲćL-²4:GS0¾Éźéūļß³é@ōŚėżµķśīܳ»ķåężżÅÜ“Æõū½ńܹļżµķUõė +Ł¶ļܳ÷I¾/Aį,ĪܽéX“X÷żYūµWöļųܵķżźUķ»ŁÖÕģ¹õīżģ»A“µéÜGGø¾Ī½ÜE+0ÜNÄÓĶŅ;ĄYWĪRöÅĆ*DŻģ3ńMOBČ +½Ń@+üįżżīA9Xūā,¼S*Jķ¹»Ę+>I*2ĒĆENVŽšéEÆø¾ZźŚKøšūANQłÄĄµ-Å9G¾°5*ĢÄīT +ŠQĒX7+:NĒĢ-NZ*ēźJLAB+Ęģ3*įéGÆøā6Äż11ŅīżżĻģ7šQµ36Jē+¾Ć4“ĖŚ3*ęŌ4:Ė+*·C/KTIĘR1J +ÖįDÆ“āęUūĶ<ńIäCĆ½õ@;ø*³Ā**JõD*öāū1P/ĢŲ꯿Ū¼·å°.ßöļ=ĘÜ3/¾ņ*ŲóūŻóŃÉ@VUĶ4GøĄ×łī +*ĘɳWŻżŁPó-¾żĶČ>ņ»Ó4F¾D;*Ī8ITNųüŃBöūMT°KĪÜļÖ;-ĘĢ@6*Ń-äŃ׿ÖI+ü¼ē2ŽŻYDŠM6LėAÜ/ +Å8JG2*ę18FÅ×½9ÓłüQ²ÅÖĪīOä,M1Ī06*.=SDųōŻ½Ųä70¾½8C*öI5HKķJÆę+Ņż*Žņ*×Ōų±÷ŪõIN5é +.äķŁRFZņ*čŗ+¾°Ī¶Ś6żł9éŻ1XČüņZ¾Ž*č¹*¾°īNTźśłżčõµXČļ?:¼ņ*č·+ZRņīńüŪšŠšµķHüżż5+ +8.*ßµķ¼G*ī9-:ō÷ŻÆæūG*Ž½ó5¾½NāŪ7*śåōļVL2¼üō1QÜ/+ŽßīĪÖ+Ņķ*ŽīJĒĆńåJŚ8°3Ķä/*ōżė- +Ī,ĄG++Į1ßĻŻQVĢ8/į¶ĶśZ±Cčńü0-źB?*ęIŠ+łUĶŹÓµB*ėń@æķšE7ZģēOČ“?÷*%%% +d +1058 57[1 0 0 1 0 0]sl 8 false 0 570 di +/sl 58190 string uc +ī½Ż,¾ńµüA:C“ņ*č²+¾°ź*LŲøįŚ»ųēĪ*ęéFŗQĢEµłä»±å +ē*8±ķ¼>ĄMżĶG÷čģŪ¹QJ@ļ¹ļ@8IēS¶čüÅķG:9éīÕףżķ»BCé*÷Õæ¹ę±>Bøņ.ĄõÕ+:CĄī»7½é7,ļöĻ +ÉŃ-,*/JŚÜčżżÕėP.*Å*ĮÜäżżYŃą+Bż9H*>įüłżÓó+++,KG.+Īß1Ž¾NDC@<Ž/×:NĘ2*¾Āøõżż±½čÅ +.*,ĄĪ¾¹õWĘ.?Dæāøéż½Ū0Ą+.LBųćżżł±JęėżüI:5ĄŹŁõ¼4M+*¾ČÆÅėI¶.ŻćæĪR¶*ęBäķ4Šß*2ČÓż +łēD/C*Ž¾:ĖÜöX?***2³Õ5š+MńS¶ę»šR*¶IŹ9*J.ū½ĆĄ*:VŹŻH;.ęĆŚ¶ķēFĪā7Ż¹Ą:SVĮÉJÕöżżFĮ +++ÕŻ0ķSķ³/×Æ-R1+*ę·Õ+0Å6J>ĮHżä*öč6ĻÖ×>5:E8.ŻÓĖ=S+šČ5°-.Ü׿ѷļ*Öżµ9¾ČZż»W»JĪö +×O00Ąīęķų,å79.ĖÓżż½ā7ÖĪį165Ń²ųżÜĶĄ;,å>FX/4ĢŚ»I,+Į5ĄĮX°Ģ×ĀŽļĒ<++=W±ś,Ū¾M³żżAN +ą;µÜ×L6¶°ĖĄĄĀ.ńū*HŗB+@@ćŠ-Ą0Ā±é=F8ą;FąN@Lć4ćL>åęł?6*ęĆóė9ēīJ2ĪR7G¾ö?“ż±öW¹6ĀA80 ++Ź³6/PńLÄSņ3ĮÅO@Gß°2*;ś½Ė41Žś½ö*Ī6łķ?4°Ī4IĖĄ²:.Ą0ÕAÖÜŁ:Ņ70õĄ.JĘäĢH.ZŗÖż»÷+*> +7QQ12ĄZ»»5<ßÆĀ6NĒńI¾ī,÷ę*ĶŻõZ@ŚĘ°43č-< +ÉNūżŲP*45½8ßĮD±3Ļ-R±+*KĒNķńÖŁōB2Iś4,FųDMåģēĒŅB7ĀQ¹ż¹łŚKö2óżDB8*łżō+R3ōŻķ,,L± +ųżõÜL28ūWYī<=Sī7üżżÅF,;ÜżżÅ,Į5żYń<*>Użõģ/Ų*°ÓõM:*æćĻõXĀ1>ģļ¹Ż5Ć5ĒÄÜJ+Åčüõż/× +¶āŗģS*ö<ĒÜåøŅÅś+ŗöZ2źVüżI25ģäżżÜ+¾KõŻÉNRŃ÷äF+RæēüŃ--JźĄIšßį°õżż9ńX*ĪżĶżšO*6* +1ѶÅWŽżīJęUŪ*æ“÷;=ņŽQ×¾LķZšśó·ĆUżżŻ5JÖŌĶMöŚłåDļJK-ĘĢ“4*ĆĮĆ2ÅĮĖ½8¹é8-ļź51=.02 +=Zŗ@ŠL=·BÄü½żķŚ86¾üYŃą+Bż9H*>įüŃN<ņ.ÕMłé:2DŚÜŽ2čĻŽ¼B*3°KQNŻ-,Lį½Ą¶*6ōōżįåĒÄ+ +ĢÜŪ:3ĒźįżżI,1<4üżöēĮļß3æõå¾ĪŁżż¹J@.Cõ»Õ:C4Ā2?ÉNLIÄīśżķš*Rńżż329¾Ą÷G*Bšü½ÓQ04 +A,/*?įüÉ--*.·:KH¾ŹOö±R*¶ż9Ż»ĄR1*K4÷Å1ŽżéJĪUŪ<ąM+ųņż½öĆŁ÷NT¾*æ<5äįųżżŪ*ÄWūGĀå +ėµ@¶46C¼*ó;*ZG¶ŽNŲ“×÷ķKµėÆ:ģHłÓ<óėČāÅĮ;Ęī>1ß¾żõöĖĮļKęż²XF*ōżė-Ī<ź½żāĮ6šżż¹AÅ +:FųŁÜ¾.,ŠZ6Ž9æ¾-śżżø²¶=ż?č+ĪŌłżĢIOB8Āų¼.LĪŪżż9,2Kś÷įõŽßöB/ÜJ+Åŗėżō+Ē¶āF»õĖ=ß +³ĄŽ¾C@ų½,ŽĮS¶Ņ÷żżLŗöMżżł0*0ŪżXÆ>=ÕÄ7-J/PłĶ86*Æ.ņĘ0;į.¼Ź4*ņżżÅX9L+JĆHõ-,08.»Ó +5:śŠMĻüżå9»WQ3īŌQøźVŁTżżŻ5JÖŌż-÷Ö·ĻLŅĘ+NQ5/¾čæ,ćDšöżÅŚ³ĒĮ¶ģ³÷+Ī=éB2¼ēXśōUĮĪÆ +ČżŻP/*ńżß-ĪRÄūż/ĀĘīøņ»6,KĮÉ4ĘJG8Ä+Ę:/JĄ/Ś26RüŃś.*OżśŻ¹æ:Ā:óĖ.ĒÖ²¶ŽĆŽĪ,G¼õ¹Ć¶ +=įęł±JęXśčŪ86ĄŹń9BĪīÕX,,ąņ-ńīŁF2čśż½:J¾Īę/1*ÄŚżøZ*CėĒł0JćÖżėŽ**Æ5PĘ1:żį6śB>* +ŚżÅ½·<.***KÜQ;ėD2ņTAJĪQĄPŪī51ÜAP*ZŌūżķō,Bōņ-+HĆĶż@FJ*ó:*J?THŪäżæŚW¹6ĀµäQ,ŹVś +Ōćš*<ļŁU-.>ųś>E*HĒśIĖ51¾ū½ł*Ņ.łķ³Ü,:7ćÜĒĀ:.ÅüŌAöŌ×:ņOüżżM8¾æOŁ°C*ĀĪż9Ė;*:Åņ+ +šD:ĪŁźł;2Ķä2FGE2B<ÆżżIų80Ąąé/2ĢĶ95żżEÕåĀ-ĒĆ8ŪÆ*īīŃżĻ5/ZWė +Ašŗŗ@ßZVīę+Ņ·,Jļ¶3ĆģTõYĀėøĮJŚ÷ŲĄā¾KM06ÅĮÆYķī*3;įÓ2+N°ńżéŅ¶*äżĮ1*ćÖż¼ŲOJ¾<ĖøO +.K:²¶»æĶX0+Qģżż5š2Z2Š-Ć2ø»ū½ń½±·æ,121MRYšÜ/ĒֲĎ1Łń-Ć6BD@·ĆÜżµŪ/+4GH605ĄŹAPR +0įK-14FśŹ9;¶üżµ·:Ņ÷żż..2KĻü8*6·żē+,ßÄ54/*ĘTż3,ą*“ĻFWĘ1:-/ü64*ģ·IĆ+ĆŻT»É4:U·ń +ŹAßB5»OĀé>.,¶łģéĢ4¹ļ°ŽĘßõōżć4/*ČK4IN*¶³Š¶īöĻ-Ré**čĻ-0ßܽ.ėŲįJF¼öüLKæ+-4QÕCū +ż»/ąļßæĘKÖųżżų*·6źżŁÅ+ĘŚżüIµĄ¶¾Ž:Q2+ĒÓ¶»æ¹A2ĄŽćżżIš:īĀš-MŌŃņūżżźåÄš,-4IOļżżÜ +±K3ŌĘ¾łčĮą-2FFĮéū²ĖRŗ¶KęÄ4ĶćFłäēīŽ¾ßīNEėü+ŗ¶·L°ĖüżI8CHéżżŽÆ2<öŻÕO1/ÄNéZRļŻü5 +7ä,ĀZ»S@C¶Ä3FGJBężć1į?åöIćVB28üķN4<ĄśæJöŲ×J6“üŻ¹Ē:UöĆį¾µAūż±ÕK:¹Ūż<čįę÷æĻ,1: +G;*¾Q?ŽŲ1**ć:-PĀÉNģ9¶ģŃP*I¹6ĘŃżż5°K.CŻMÆJõÅł“ōH+7BśĄ.ūø°+V;ČÆĆżżåżL->żżų.3š» +Å=ēĖÖŽśIµĖPĘ6Eįó.2:IÓ*±*1HZū.Ž“źĘĒÄšZłķāźŲķVFõŌB6šüżĘ.·Āś8Ćßź¹µģ»,µĀęQį¶“ó30 +ÖÜņŽV»ā.ČżńRø¾øŻįĒņQV¶õNÕĖ,N18GR1:H¾ņ²Ć*ZG¶*OųO²óÅ<“ėµ@ģGµB.żüŻN.W°Ģżš÷+»Õ?ą +Īõ=6¾ŚåL;4ū77Rö³AĆTėüšOŠńąKUń-M/6JŪ+*ęÄ4=ššü?Xé½²ļöÕHŪłŻÜRų8ļśŠ;ŽTYÓ·ųĻ0Bł4å +Ō¼Į+ÄüSĪ±µI<ūO8*ż;/¶łÜQŁ6/Ā4C,ĘĢ“2*ÅMĆ2KųDłōüĆ¾WQųĆMõģū½ķŚ»ń½óĢUĢ*±9GøųÕF¾Ę¹ +1żRæĆ¹ńI0Fū²Ēß@÷;-Å8*²**śŅ,V=+ģHĮłæį0ĢMß>ŌūZŲōūAS=ā-ī½Æ*6FćOJĪĘĻ=öüę*ÄšÕC*ż +æć¾Ļ+REõōYĖāłA¾Ćņ+Žżč¾ćē¹½WZĒJĒ²?ŗüķżFõłŲ·ā*ŽżŗBćļ¹A¶Č6A°śBG¾ś*D**øT+@Ē*X1:G¶Ī¾;>ĖBµż÷Vėł9ŃWU-Ž +Ķć0ŗÆĆÉŽJĻ,Å9Ī±*½Ė-īøŅJĻLRŲßīżü¼,üI÷ķFłä¹Õ°ŚP16.-Ä9G¾Š1¾Ń³¶ī+Žåß3¶CZŽ*4ÄYé׊ +Ö÷ÕśĆķŻķśżżŃÕĢŲ.28ĀM18Gø,öÕ“*ļ4źÅ6JĄĄĒVśūķšŃĢFAXšIōä·öŽAAüÉķ@KXÄ6?XāŽĻ-ę“,ŽļR+-08GĶņß×ĆAńōüūĶšõ@GYUŃH¶į¼Wø.*/ķMC¶īö+>¹*JG¾ņ +öæ=ōŽó*,²ēŅÆéņ;Ą¹×½żČść³åēńÜTX@GZĀB9LŚĘCģA¾öŅ,FF¾Ń=¶ō+Ģ18BR¾2+M1YDPņ°õż÷Żæ/¾ +żūµšłHGåĶIŗōäĒłōėU=Ś@É9J¶ŚR>XŽŽæ-Rø*ī°ź¶æL/6C¾ŗŅÆ.1łP8Ś·°ĢÜūõ9ĘYśó*üŃ:īśłĢćĒ +ĻLĆüśŲWĻ.OČN29š-ÅĢ2*M08D¶0,Ą**Ģ¶+Å-1:JĒ); +\end{lstlisting} + + +The primary variables used by this model are\-: +\begin{itemize} +\item The pressure of the phase with the lowest index +\item The two saturations of the phases with the lowest indices +\end{itemize} + diff --git a/doc/handbook/ModelDescriptions/flashmodel.tex b/doc/handbook/ModelDescriptions/flashmodel.tex new file mode 100644 index 00000000000..10f281aee0e --- /dev/null +++ b/doc/handbook/ModelDescriptions/flashmodel.tex @@ -0,0 +1,35 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This file has been autogenerated from the LaTeX part of the % +% doxygen documentation; DO NOT EDIT IT! Change the model's .hh % +% file instead!! % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +A generic compositional multi-\/phase model using primary-\/variable switching. + +This model assumes a flow of $M \geq 1$ fluid phases $\alpha$, each of which is assumed to be a mixture $N \geq M$ chemical species (denoted by the upper index $\kappa$). + +By default, the standard multi-\/phase Darcy approach is used to determine the velocity, i.\-e. \[ \mathbf{v}_\alpha = - \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} \left(\text{grad}\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right) \;, \] although the actual approach which is used can be specified via the {\ttfamily Velocity\-Module} property. For example, the velocity model can by changed to the Forchheimer approach by +\begin{lstlisting}[style=eWomsCode] + SET_TYPE_PROP(MyProblemTypeTag, VelocityModule, + Ewoms::BoxForchheimerVelocityModule); +\end{lstlisting} + + +The core of the model is the conservation mass of each component by means of the equation \[ \sum_\alpha \frac{\partial\;\phi c_\alpha^\kappa S_\alpha }{\partial t} - \sum_\alpha \text{div} \left\{ c_\alpha^\kappa \mathbf{v}_\alpha \right\} - q^\kappa = 0 \;. \] + +To determine the quanties that occur in the equations above, this model uses {\itshape flash calculations}. A flash solver starts with the total mass or molar mass per volume for each component and, calculates the compositions, saturations and pressures of all phases at a given temperature. For this the flash solver has to use some model assumptions internally. (Often these are the same primary variable switching or N\-C\-P assumptions as used by the other fully implicit compositional multi-\/phase models provided by e\-Woms.) + +Using flash calculations for the flow model has some disadvantages\-: +\begin{itemize} +\item The accuracy of the flash solver needs to be sufficient to calculate the parital derivatives using numerical differentiation which are required for the Newton scheme. +\item Flash calculations tend to be quite computationally expensive and are often numerically unstable. +\end{itemize} + +It is thus adviced to increase the target tolerance of the Newton scheme or a to use type for scalar values which exhibits higher precision than the standard {\ttfamily double} (e.\-g. {\ttfamily quad}) if this model ought to be used. + +The model uses the following primary variables\-: +\begin{itemize} +\item The total molar concentration of each component\-: $c^\kappa = \sum_\alpha S_\alpha x_\alpha^\kappa \rho_{mol, \alpha}$ +\item The absolute temperature \$\-T\$ in Kelvins if the energy equation enabled. +\end{itemize} + diff --git a/doc/handbook/ModelDescriptions/immisciblemodel.tex b/doc/handbook/ModelDescriptions/immisciblemodel.tex new file mode 100644 index 00000000000..d0080188523 --- /dev/null +++ b/doc/handbook/ModelDescriptions/immisciblemodel.tex @@ -0,0 +1,24 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This file has been autogenerated from the LaTeX part of the % +% doxygen documentation; DO NOT EDIT IT! Change the model's .hh % +% file instead!! % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +This model multi-\/phase flow of $M > 0$ immiscible fluids $\alpha$. By default, the standard multi-\/phase Darcy approach is used to determine the velocity, i.\-e. \[ \mathbf{v}_\alpha = - \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} \left(\text{grad}\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right) \;, \] although the actual approach which is used can be specified via the {\ttfamily Velocity\-Module} property. For example, the velocity model can by changed to the Forchheimer approach by +\begin{lstlisting}[style=eWomsCode] + SET_TYPE_PROP(MyProblemTypeTag, VelocityModule, + Ewoms::BoxForchheimerVelocityModule); +\end{lstlisting} + + +The core of the model is the conservation mass of each component by means of the equation \[ \frac{\partial\;\phi S_\alpha \rho_\alpha }{\partial t} - \text{div} \left\{ \rho_\alpha \mathbf{v}_\alpha \right\} - q_\alpha = 0 \;. \] + +These equations are discretized by a fully-\/coupled vertex centered finite volume (box) scheme as spatial and the implicit Euler method as time discretization. + +The model uses the following primary variables\-: +\begin{itemize} +\item The pressure $p_0$ in Pascal of the phase with the lowest index +\item The saturations $S_\alpha$ of the $M - 1$ phases that exhibit the lowest indices +\item The absolute temperature $T$ in Kelvin if energy is conserved via the energy equation +\end{itemize} + diff --git a/doc/handbook/ModelDescriptions/ncpmodel.tex b/doc/handbook/ModelDescriptions/ncpmodel.tex new file mode 100644 index 00000000000..537a9996efb --- /dev/null +++ b/doc/handbook/ModelDescriptions/ncpmodel.tex @@ -0,0 +1,37 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This file has been autogenerated from the LaTeX part of the % +% doxygen documentation; DO NOT EDIT IT! Change the model's .hh % +% file instead!! % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +This model implements a $M$-\/phase flow of a fluid mixture composed of $N$ chemical species. The phases are denoted by lower index $\alpha \in \{ 1, \dots, M \}$. All fluid phases are mixtures of $N \geq M - 1$ chemical species which are denoted by the upper index $\kappa \in \{ 1, \dots, N \} $. + +By default, the standard multi-\/phase Darcy approach is used to determine the velocity, i.\-e. \[ \mathbf{v}_\alpha = - \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} \left(\text{grad}\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right) \;, \] although the actual approach which is used can be specified via the {\ttfamily Velocity\-Module} property. For example, the velocity model can by changed to the Forchheimer approach by +\begin{lstlisting}[style=eWomsCode] + SET_TYPE_PROP(MyProblemTypeTag, VelocityModule, + Ewoms::BoxForchheimerVelocityModule); +\end{lstlisting} + + +The core of the model is the conservation mass of each component by means of the equation \[ \sum_\alpha \frac{\partial\;\phi c_\alpha^\kappa S_\alpha }{\partial t} - \sum_\alpha \text{div} \left\{ c_\alpha^\kappa \mathbf{v}_\alpha \right\} - q^\kappa = 0 \;. \] + +For the missing $M$ model assumptions, the model uses non-\/linear complementarity functions. These are based on the observation that if a fluid phase is not present, the sum of the mole fractions of this fluid phase is smaller than $1$, i.\-e. \[ \forall \alpha: S_\alpha = 0 \implies \sum_\kappa x_\alpha^\kappa \leq 1 \] + +Also, if a fluid phase may be present at a given spatial location its saturation must be non-\/negative\-: \[ \forall \alpha: \sum_\kappa x_\alpha^\kappa = 1 \implies S_\alpha \geq 0 \] + +Since at any given spatial location, a phase is always either present or not present, one of the strict equalities on the right hand side is always true, i.\-e. \[ \forall \alpha: S_\alpha \left( \sum_\kappa x_\alpha^\kappa - 1 \right) = 0 \] always holds. + +These three equations constitute a non-\/linear complementarity problem, which can be solved using so-\/called non-\/linear complementarity functions $\Phi(a, b)$. Such functions have the property \[\Phi(a,b) = 0 \iff a \geq0 \land b \geq0 \land a \cdot b = 0 \] + +Several non-\/linear complementarity functions have been suggested, e.\-g. the Fischer-\/\-Burmeister function \[ \Phi(a,b) = a + b - \sqrt{a^2 + b^2} \;. \] This model uses \[ \Phi(a,b) = \min \{a, b \}\;, \] because of its piecewise linearity. + +These equations are then discretized using a fully-\/implicit vertex centered finite volume scheme (often known as 'box'-\/scheme) for spatial discretization and the implicit Euler method as temporal discretization. + +The model assumes local thermodynamic equilibrium and uses the following primary variables\-: +\begin{itemize} +\item The pressure of the first phase $p_1$ +\item The component fugacities $f^1, \dots, f^{N}$ +\item The saturations of the first $M-1$ phases $S_1, \dots, S_{M-1}$ +\item Temperature $T$ if the energy equation is enabled +\end{itemize} + diff --git a/doc/handbook/ModelDescriptions/pvsmodel.tex b/doc/handbook/ModelDescriptions/pvsmodel.tex new file mode 100644 index 00000000000..0e819e14b5f --- /dev/null +++ b/doc/handbook/ModelDescriptions/pvsmodel.tex @@ -0,0 +1,51 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This file has been autogenerated from the LaTeX part of the % +% doxygen documentation; DO NOT EDIT IT! Change the model's .hh % +% file instead!! % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +This model assumes a flow of $M \geq 1$ fluid phases $\alpha$, each of which is assumed to be a mixture $N \geq M$ chemical species $\kappa$. + +By default, the standard multi-\/phase Darcy approach is used to determine the velocity, i.\-e. \[ \mathbf{v}_\alpha = - \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} \left(\text{grad}\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right) \;, \] although the actual approach which is used can be specified via the {\ttfamily Velocity\-Module} property. For example, the velocity model can by changed to the Forchheimer approach by +\begin{lstlisting}[style=eWomsCode] + SET_TYPE_PROP(MyProblemTypeTag, VelocityModule, + Ewoms::BoxForchheimerVelocityModule); +\end{lstlisting} + + +The core of the model is the conservation mass of each component by means of the equation \[ \sum_\alpha \frac{\partial\;\phi c_\alpha^\kappa S_\alpha }{\partial t} - \sum_\alpha \text{div} \left\{ c_\alpha^\kappa \mathbf{v}_\alpha \right\} - q^\kappa = 0 \;. \] + +To close the system mathematically, $M$ model equations are also required. This model uses the primary variable switching assumptions, which are given by\-: \[ 0 \stackrel{!}{=} f_\alpha = \left\{ \begin{array}{cl} S_\alpha & \quad \text{if phase }\alpha\text{ is not present} \\ 1 - \sum_\kappa x_\alpha^\kappa & \quad \text{else} \end{array} \right. \] + +To make this approach applicable, a pseudo primary variable {\itshape phase presence} has to be introduced. Its purpose is to specify for each phase whether it is present or not. It is a {\itshape pseudo} primary variable because it is not directly considered when linearizing the system in the Newton method, but after each Newton iteration, it gets updated like the \char`\"{}real\char`\"{} primary variables. The following rules are used for this update procedure\-: + + +\begin{itemize} +\item If phase $\alpha$ is present according to the pseudo primary variable, but $S_\alpha < 0$ after the Newton update, consider the phase $\alpha$ disappeared for the next iteration and use the set of primary variables which correspond to the new phase presence. + + +\item If phase $\alpha$ is not present according to the pseudo primary variable, but the sum of the component mole fractions in the phase is larger than 1, i.\-e. $\sum_\kappa x_\alpha^\kappa > 1$, consider the phase $\alpha$ present in the the next iteration and update the set of primary variables to make it consistent with the new phase presence. + + +\item In all other cases don't modify the phase presence for phase $\alpha$. + + +\end{itemize} + +The model always requires $N$ primary variables, but their interpretation is dependent on the phase presence\-: + + +\begin{itemize} +\item The first primary variable is always interpreted as the pressure of the phase with the lowest index $PV_0 = p_0$. + + +\item Then, $M - 1$ \char`\"{}switching primary variables\char`\"{} follow, which are interpreted depending in the presence of the first $M-1$ phases\-: If phase $\alpha$ is present, its saturation $S_\alpha = PV_i$ is used as primary variable; if it is not present, the mole fraction $PV_i = x_{\alpha^\star}^\alpha$ of the component with index $\alpha$ in the phase with the lowest index that is present $\alpha^\star$ is used instead. + + +\item Finally, the mole fractions of the $N-M$ components with the largest index in the phase with the lowest index that is present $x_{\alpha^\star}^\kappa$ are used as primary variables. + + +\end{itemize} + +This model is then discretized using a fully-\/coupled vertex centered finite volume (box) scheme as spatial and the implicit Euler method as temporal discretization. + diff --git a/doc/handbook/ModelDescriptions/richardsmodel.tex b/doc/handbook/ModelDescriptions/richardsmodel.tex new file mode 100644 index 00000000000..bee994cd12a --- /dev/null +++ b/doc/handbook/ModelDescriptions/richardsmodel.tex @@ -0,0 +1,12 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This file has been autogenerated from the LaTeX part of the % +% doxygen documentation; DO NOT EDIT IT! Change the model's .hh % +% file instead!! % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +In the unsaturated zone, Richards' equation is frequently used to approximate the water distribution above the groundwater level. It can be derived from the two-\/phase equations, i.\-e. \[ \frac{\partial\;\phi S_\alpha \rho_\alpha}{\partial t} - \text{div} \left\{ \rho_\alpha \frac{k_{r\alpha}}{\mu_\alpha}\; \mathbf{K}\; \textbf{grad}\left[ p_\alpha - g\rho_\alpha \right] \right\} = q_\alpha, \] where $\alpha \in \{w, n\}$ is the index of the fluid phase, $\rho_\alpha$ is the fluid density, $S_\alpha$ is the fluid saturation, $\phi$ is the porosity of the soil, $k_{r\alpha}$ is the relative permeability for the fluid, $\mu_\alpha$ is the fluid's dynamic viscosity, $\mathbf{K}$ is the intrinsic permeability tensor, $p_\alpha$ is the fluid phase pressure and $g$ is the potential of the gravity field. + +In contrast to the \char`\"{}full\char`\"{} two-\/phase model, the Richards model assumes that the non-\/wetting fluid is gas and that it thus exhibits a much lower viscosity than the (liquid) wetting phase. (This assumption is quite realistic in many applications\-: For example, at atmospheric pressure and at room temperature, the viscosity of air is only about $1\%$ of the viscosity of liquid water.) As a consequence, the $\frac{k_{r\alpha}}{\mu_\alpha}$ term typically is much larger for the gas phase than for the wetting phase. Using this reasoning, the Richards model assumes that $\frac{k_{rn}}{\mu_n}$ is infinitely large compared to the same term of the liquid phase. This implies that the pressure of the gas phase is equivalent to the static pressure distribution and that therefore, mass conservation only needs to be considered for the liquid phase. + +The model thus choses the absolute pressure of the wetting phase $p_w$ as its only primary variable. The wetting phase saturation is calculated using the inverse of the capillary pressure, i.\-e. \[ S_w = p_c^{-1}(p_n - p_w) \] holds, where $p_n$ is a reference pressure given by the problem's {\ttfamily reference\-Pressure()} method. Nota bene, that the last step assumes that the capillary pressure-\/saturation curve can be uniquely inverted, i.\-e. it is not possible to set the capillary pressure to zero if the Richards model ought to be used! + diff --git a/doc/handbook/ModelDescriptions/stokesmodel.tex b/doc/handbook/ModelDescriptions/stokesmodel.tex new file mode 100644 index 00000000000..fd841d06900 --- /dev/null +++ b/doc/handbook/ModelDescriptions/stokesmodel.tex @@ -0,0 +1,12 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This file has been autogenerated from the LaTeX part of the % +% doxygen documentation; DO NOT EDIT IT! Change the model's .hh % +% file instead!! % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +This model implements Navier-\/\-Stokes flow of a single fluid. By default, it solves the momentum balance of the time-\/dependent Stokes equations, i.\-e. \[ \frac{\partial \left(\varrho\,\mathbf{v}\right)}{\partial t} + \boldsymbol{\nabla} p - \nabla \cdot \left( \mu \left(\boldsymbol{\nabla} \mathbf{v} + \boldsymbol{\nabla} \mathbf{v}^T\right) \right) - \varrho\,\mathbf{g} = 0\;, \] and the mass balance equation \[ \frac{\partial \varrho}{\partial t} + \nabla \cdot\left(\varrho\,\mathbf{v}\right) - q = 0 \;. \] + +If the property {\ttfamily Enable\-Navier\-Stokes} is set to {\ttfamily true}, an additional convective momentum flux term (Navier term) gets included into the momentum conservation equations which allows to capture turbolent flow regimes. This additional term is given by \[ \varrho \left(\mathbf{v} \cdot \boldsymbol{\nabla} \right) \mathbf{v} \;. \] + +These equations are discretized by a fully-\/coupled vertex-\/centered finite volume (box) scheme in space and using the implicit Euler method in time. Be aware, that this discretization scheme is quite unstable for the Navier-\/\-Stokes equations and quickly leads to unphysical oscillations in the calculated solution. We intend to use a more appropriate discretization scheme in the future, though. + diff --git a/doc/handbook/SVG/box_disc.svg b/doc/handbook/SVG/box_disc.svg new file mode 100755 index 00000000000..66576620d46 --- /dev/null +++ b/doc/handbook/SVG/box_disc.svg @@ -0,0 +1,733 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + image/svg+xml + + + + + + scv i k + + + + + + + + + + + FE mesh secondary FV mesh + + + node i + E k + + + + B i + + E k + + + + + + + + + + + + B i + + + i + + i j + + x ij k + + e ij k + + + + a) b) c) + + n ij k + b i k + diff --git a/doc/handbook/SVG/car-hierarchy.svg b/doc/handbook/SVG/car-hierarchy.svg new file mode 100644 index 00000000000..8011090eac4 --- /dev/null +++ b/doc/handbook/SVG/car-hierarchy.svg @@ -0,0 +1,554 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + Sedan + + + + Pickup + + + + Hummer H1 + + + + Compact Car + + + + Truck + + + + Tank + + + + + + + + diff --git a/doc/handbook/SVG/car-propertynames.svg b/doc/handbook/SVG/car-propertynames.svg new file mode 100644 index 00000000000..02712e5ced7 --- /dev/null +++ b/doc/handbook/SVG/car-propertynames.svg @@ -0,0 +1,820 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + GasUsage + + + + TopSpeed + + + + NumSeats + + + + AutomaticTransmission + + + + CannonCalibre + + + + Payload + + + diff --git a/doc/handbook/SVG/ewoms_structure.svg b/doc/handbook/SVG/ewoms_structure.svg new file mode 100644 index 00000000000..2ebc2af7a12 --- /dev/null +++ b/doc/handbook/SVG/ewoms_structure.svg @@ -0,0 +1,1098 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + ewoms boxmodels common io material nonlinear + + + + + common specific models + + + + + + + binarycoefficients components fluidsystems + + + + + + + Physical models General infrastructure of the fully implicit cell-centered finite-volume method Common infrastructure (time management, start-up routines, propterty sytem, ...) In-/output infrastructure (writing/reading checkpoint files, writing VTK files) Binary thermodynamic coefficients (binary diffusion coefficients, Henry coefficients, ...) Material properties of a pure chemical substances or pseudo substances Express the thermodynamic relations between quantities Implementation of the Newton method linear + + parallel + + + Auxiliary infrastructure for useful fir parallelization + constraintsolvers + + + eos + fluidstates + + + + + + + + + Representations of the thermodynamic conļ¬guration of a system Constraint solvers (Solve systems of thermodynamic equations) Thermodynamic equations of state (auxiliary classes) eWoms specific linear solver infrastucture (backend, algebraic overlap code, etc) + + + + + + + + + + + + + + + + + + + + + vtk Modules for writing VTK (visualization) output modules Plug-ins for the physical models (energy, velocity, diffusion, ...) + + linear + + Slightly modified copy of ISTL solvers (for improved stability and performance + diff --git a/doc/handbook/XFIG/Ex2_Boundary.fig b/doc/handbook/XFIG/Ex2_Boundary.fig new file mode 100644 index 00000000000..0fabd55b11d --- /dev/null +++ b/doc/handbook/XFIG/Ex2_Boundary.fig @@ -0,0 +1,44 @@ +#FIG 3.2 Produced by xfig version 3.2.5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3015 3870 8775 3870 8775 6615 3015 6615 3015 3870 +2 2 0 1 0 31 50 -1 20 0.000 0 0 -1 0 0 5 + 4095 4770 7650 4770 7650 5760 4095 5760 4095 4770 +2 2 2 1 0 7 50 -1 41 3.000 0 0 -1 0 0 5 + 3015 6615 8775 6615 8775 6795 3015 6795 3015 6615 +2 2 2 1 0 7 50 -1 41 3.000 0 0 -1 0 0 5 + 3015 3690 8775 3690 8775 3870 3015 3870 3015 3690 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 105.00 120.00 + 9000 4410 9450 4410 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 105.00 120.00 + 9000 4815 9450 4815 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 105.00 120.00 + 9000 5220 9450 5220 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 105.00 120.00 + 9045 5670 9450 5670 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 105.00 120.00 + 9045 6075 9450 6075 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 105.00 120.00 + 9045 6480 9450 6480 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 105.00 120.00 + 9000 4005 9450 4005 +4 0 0 50 -1 0 12 0.0000 4 135 615 5535 7245 no flow\001 +4 0 0 50 -1 0 12 0.0000 4 135 615 5535 3420 no flow\001 +4 0 0 50 -1 0 12 0.0000 4 135 240 9720 4995 qw\001 +4 0 0 50 -1 0 12 0.0000 4 135 210 9765 5490 qo\001 +4 0 0 50 -1 0 12 0.0000 4 135 240 1170 4815 pw\001 +4 0 0 50 -1 0 12 0.0000 4 135 105 1170 5310 S\001 diff --git a/doc/handbook/XFIG/Ex2_Domain.fig b/doc/handbook/XFIG/Ex2_Domain.fig new file mode 100644 index 00000000000..b22a847134f --- /dev/null +++ b/doc/handbook/XFIG/Ex2_Domain.fig @@ -0,0 +1,77 @@ +#FIG 3.2 Produced by xfig version 3.2.5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3015 3870 8775 3870 8775 6615 3015 6615 3015 3870 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2610 3870 2610 4950 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2610 5535 2610 6615 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 6435 3690 8775 3690 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4860 5805 4860 6075 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4860 6390 4860 6615 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3870 4770 3870 5085 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3870 5445 3870 5760 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3915 4770 3780 4770 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3915 5760 3780 5760 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3015 3690 5355 3690 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4140 4590 5580 4590 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 6390 4590 7650 4590 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 8550 5220 8775 5220 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 7695 5220 7920 5220 +2 2 0 1 0 31 50 -1 20 0.000 0 0 -1 0 0 5 + 4095 4770 7650 4770 7650 5760 4095 5760 4095 4770 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 3 1 3.00 60.00 120.00 + 7965 6840 5985 5355 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 3 1 3.00 60.00 120.00 + 5380 6827 6167 6258 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2700 3870 2565 3870 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2700 6615 2565 6615 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3015 3780 3015 3600 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 8775 3780 8775 3600 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 7650 4680 7650 4500 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4095 4680 4095 4500 +2 2 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 5 + 6615 6840 9900 6840 9900 8055 6615 8055 6615 6840 +2 2 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 5 + 2835 6840 6075 6840 6075 8055 2835 8055 2835 6840 +4 0 0 50 -1 0 12 0.0000 4 135 330 7965 5265 L3x\001 +4 0 0 50 -1 0 12 0.0000 4 135 330 5670 4635 L2x\001 +4 0 0 50 -1 0 12 0.0000 4 135 330 5445 3780 L1x\001 +4 0 0 50 -1 0 12 0.0000 4 180 345 4455 6300 H2y\001 +4 0 0 50 -1 0 12 0.0000 4 180 345 3330 5310 H3y\001 +4 0 0 50 -1 0 12 0.0000 4 180 345 2115 5310 H1y\001 +4 0 0 50 -1 0 12 0.0000 4 135 285 2970 7695 Lin\001 +4 0 0 50 -1 0 12 0.0000 4 180 375 2970 7425 phi1\001 +4 0 0 50 -1 0 12 0.0000 4 135 240 2970 7155 K1\001 +4 0 0 50 -1 0 12 0.0000 4 135 240 6750 7155 K2\001 +4 0 0 50 -1 0 12 0.0000 4 180 375 6750 7380 phi2\001 +4 0 0 50 -1 0 12 0.0000 4 135 375 6750 7650 BC1\001 +4 0 0 50 -1 0 12 0.0000 4 135 375 6750 7920 BC2\001 +4 0 0 50 -1 0 12 0.0000 4 135 390 2970 7920 Lin2\001 diff --git a/doc/handbook/XFIG/exercise1_c.fig b/doc/handbook/XFIG/exercise1_c.fig new file mode 100644 index 00000000000..74ec7f550e9 --- /dev/null +++ b/doc/handbook/XFIG/exercise1_c.fig @@ -0,0 +1,37 @@ +#FIG 3.2 Produced by xfig version 3.2.5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3285 5310 3285 5535 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3285 5445 4905 5445 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5715 5445 7155 5445 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 7155 5310 7155 5535 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3060 4725 3060 5040 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2970 5130 3150 5130 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3060 4680 3060 5130 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3240 3870 7155 3870 7155 5130 3240 5130 3240 3870 +2 2 0 1 0 15 50 -1 20 0.000 0 0 -1 0 0 5 + 5265 3870 7155 3870 7155 5130 5265 5130 5265 3870 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3060 3870 3060 4320 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2970 3870 3150 3870 +4 0 0 50 -1 0 12 0.0000 4 135 390 3870 4365 K1 =\001 +4 0 0 50 -1 0 12 0.0000 4 180 525 3870 4590 phi1 =\001 +4 0 7 50 -1 0 12 0.0000 4 135 390 5760 4365 K2 =\001 +4 0 7 50 -1 0 12 0.0000 4 180 525 5760 4545 phi2 =\001 +4 0 0 50 -1 0 12 0.0000 4 135 510 2700 4590 300 m\001 +4 0 0 50 -1 0 12 0.0000 4 135 510 5085 5490 600 m\001 diff --git a/doc/handbook/XFIG/exercise_1a.fig b/doc/handbook/XFIG/exercise_1a.fig new file mode 100644 index 00000000000..58564300ff5 --- /dev/null +++ b/doc/handbook/XFIG/exercise_1a.fig @@ -0,0 +1,31 @@ +#FIG 3.2 Produced by xfig version 3.2.5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 1980 2205 3780 2205 3780 3915 1980 3915 1980 2205 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 1890 3915 1755 3915 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 1800 3915 1800 3240 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 1800 2880 1800 2205 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 1845 2205 1710 2205 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 1845 3915 1710 3915 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3195 4095 3780 4095 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3780 4005 3780 4140 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 1980 4005 1980 4140 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 1980 4095 2565 4095 +4 0 0 50 -1 0 12 0.0000 4 135 105 1575 3105 y\001 +4 0 0 50 -1 0 12 0.0000 4 90 105 2655 4140 x\001 diff --git a/doc/handbook/box.tex b/doc/handbook/box.tex new file mode 100644 index 00000000000..a1a24bf64c5 --- /dev/null +++ b/doc/handbook/box.tex @@ -0,0 +1,120 @@ +\section{Box method - A short introduction}\label{box} + +For the spatial discretization the so called BOX-method is used which unites the advantages of the finite-volume (FV) and finite-element (FE) methods. + +First, the model domain $G$ is discretized with a FE mesh consisting of nodes i and corresponding elements $E_k$. Then, a secondary FV mesh is constructed by connecting the midpoints and barycenters of the elements surrounding node i creating a box $B_i$ around node i (see Figure \ref{pc:box}a). + +\begin{figure} [h] +\includegraphics[width=0.8\linewidth,keepaspectratio]{EPS/box_disc} +\caption{\label{pc:box} Discretization of the BOX-method} +\end{figure} + +The FE mesh divides the box $B_i$ into subcontrolvolumes (scv's) $b^k_i$ (see Figure \ref{pc:box}b). Figure \ref{pc:box}c shows the finite element $E_k$ and the scv's $b^k_i$ inside $E_k$, which belong to four different boxes $B_i$. Also necessary for the discretization are the faces of the subcontrolvolumes (scvf's) $e^k_{ij}$ between the scv's $b^k_i$ and $b^k_j$, where $|e^k_{ij}|$ is the length of the scvf. The integration points $x^k_{ij}$ on $e^k_{ij}$ and the outer normal vector $\mathbf n^k_{ij}$ are also to be defined (see Figure \ref{pc:box}c). + +The advantage of the FE method is that unstructured grids can be used, while the FV method is mass conservative. The idea is to apply the FV method (balance of fluxes across the interfaces) to each FV box $B_i$ and to get the fluxes across the interfaces $e^k_{ij}$ at the integration points $x^k_{ij}$ from the FE approach. Consequently, at each scvf the following expression results: + +\begin{equation} + f(\tilde u(x^k_{ij})) \cdot \mathbf n^k_{ij} \: |e^k_{ij}| \qquad \textrm{with} \qquad \tilde u(x^k_{ij}) = \sum_i N_i(x^k_{ij}) \cdot \hat u_i . +\end{equation} + +In the following, the discretization of the balance equation is going to be derived. From the \textsc{Reynolds} transport theorem follows the general balance equation: + +\begin{equation} + \underbrace{\int_G \frac{\partial}{\partial t} \: u \: dG}_{1} + \underbrace{\int_{\partial G} (\mathbf{v} u + \mathbf w) \cdot \textbf n \: d\varGamma}_{2} = \underbrace{\int_G q \: dG}_{3} +\end{equation} + +\begin{equation} + f(u) = \int_G \frac{\partial u}{\partial t} \: dG + \int_{G} \nabla \cdot \underbrace{\left[ \mathbf{v} u + \mathbf w(u)\right] }_{F(u)} \: dG - \int_G q \: dG = 0 +\end{equation} +where term 1 describes the changes of entity $u$ within a control volume over time, term 2 the advective, diffusive and dispersive fluxes over the interfaces of the control volume and term 3 is the source and sink term. $G$ denotes the model domain and $F(u) = F(\mathbf v, p) = F(\mathbf v(x,t), p(x,t))$. + +Like the FE method, the BOX-method follows the principle of weighted residuals. In the function $f(u)$ the unknown $u$ is approximated by discrete values at the nodes of the FE mesh $\hat u_i$ and linear basis functions $N_i$ yielding an approximate function $f(\tilde u)$. For $u\in \lbrace \mathbf v, p, x^\kappa \rbrace$ this means + +\begin{minipage}[b]{0.47\textwidth} +\begin{equation} +\label{eq:p} + \tilde p = \sum_i N_i \hat{p_i} +\end{equation} +\begin{equation} +\label{eq:v} + \tilde{\mathbf v} = \sum_i N_i \hat{\mathbf v} +\end{equation} +\begin{equation} +\label{eq:x} + \tilde x^\kappa = \sum_i N_i \hat x^\kappa +\end{equation} +\end{minipage} +\hfill +\begin{minipage}[b]{0.47\textwidth} +\begin{equation} +\label{eq:dp} + \nabla \tilde p = \sum_i \nabla N_i \hat{p_i} +\end{equation} +\begin{equation} +\label{eq:dv} + \nabla \tilde{\mathbf v} = \sum_i \nabla N_i \hat{\mathbf v} +\end{equation} +\begin{equation} +\label{eq:dx} + \nabla \tilde x^\kappa = \sum_i \nabla N_i \hat x^\kappa . +\end{equation} +\end{minipage} + +Due to the approximation with node values and basis functions the differential equations are not exactly fulfilled anymore but a residual $\varepsilon$ is produced. + +\begin{equation} + f(u) = 0 \qquad \Rightarrow \qquad f(\tilde u) = \varepsilon +\end{equation} + +Application of the principle of weighted residuals, meaning the multiplication of the residual $\varepsilon$ with a weighting function $W_j$ and claiming that this product has to vanish within the whole domain, + +\begin{equation} + \int_G W_j \cdot \varepsilon \: \overset {!}{=} \: 0 \qquad \textrm{with} \qquad \sum_j W_j =1 +\end{equation} +yields the following equation: + +\begin{equation} + \int_G W_j \frac{\partial \tilde u}{\partial t} \: dG + \int_G W_j \cdot \left[ \nabla \cdot F(\tilde u) \right] \: dG - \int_G W_j \cdot q \: dG = \int_G W_j \cdot \varepsilon \: dG \: \overset {!}{=} \: 0 . +\end{equation} + +Then, the chain rule and the \textsc{Green-Gaussian} integral theorem are applied. + +\begin{equation} + \int_G W_j \frac{\partial \sum_i N_i \hat u_i}{\partial t} \: dG + \int_{\partial G} \left[ W_j \cdot F(\tilde u)\right] \cdot \mathbf n \: d\varGamma_G + \int_G \nabla W_j \cdot F(\tilde u) \: dG - \int_G W_j \cdot q \: dG = 0 +\end{equation} + +A mass lumping technique is applied by assuming that the storage capacity is reduced to the nodes. This means that the integrals $M_{i,j} = \int_G W_j \: N_i \: dG$ are replaced by the mass lumping term $M^{lump}_{i,j}$ which is defined as: + +\begin{equation} + M^{lump}_{i,j} =\begin{cases} \int_G W_j \: dG = \int_G N_i \: dG = V_i &i = j\\ + 0 &i \neq j\\ + \end{cases} +\end{equation} +where $V_i$ is the volume of the FV box $B_i$ associated with node i. The application of this assumption in combination with $\int_G W_j \:q \: dG = V_i \: q$ yields + +\begin{equation} + V_i \frac{\partial \hat u_i}{\partial t} + \int_{\partial G} \left[ W_j \cdot F(\tilde u)\right] \cdot \mathbf n \: d\varGamma_G + \int_G \nabla W_j \cdot F(\tilde u) \: dG- V_i \cdot q = 0 \, . +\end{equation} + +Defining the weighting function $W_j$ to be piecewisely constant over a control volume box $B_i$ + +\begin{equation} + W_j(x) = \begin{cases} + 1 &x \in B_i \\ + 0 &x \notin B_i\\ + \end{cases} +\end{equation} + +causes $\nabla W_j = 0$: + +\begin{equation} +\label{eq:disc1} + V_i \frac{\partial \hat u_i}{\partial t} + \int_{\partial B_i} \left[ W_j \cdot F(\tilde u)\right] \cdot \mathbf n \; d{\varGamma}_{B_i} - V_i \cdot q = 0 . +\end{equation} + +The consideration of the time discretization and inserting $W_j = 1$ finally lead to the discretized form which will be applied to the mathematical flow and transport equations: + +\begin{equation} +\label{eq:discfin} + V_i \frac{\hat u_i^{n+1} - \hat u_i^{n}}{\Delta t} + \int_{\partial B_i} F(\tilde u^{n+1}) \cdot \mathbf n \; d{\varGamma}_{B_i} - V_i \: q^{n+1} \: = 0 +\end{equation} diff --git a/doc/handbook/build-handbook.sh b/doc/handbook/build-handbook.sh new file mode 100755 index 00000000000..1689689be19 --- /dev/null +++ b/doc/handbook/build-handbook.sh @@ -0,0 +1,11 @@ +#! /bin/sh + +# this script builds the eWoms handbook from its LaTeX sources. The +# result file is called "ewoms-handbook.pdf" + +latex ewoms-handbook || exit $? +bibtex ewoms-handbook || exit $? +latex ewoms-handbook || exit $? +latex ewoms-handbook || exit $? +dvipdf ewoms-handbook || exit $? +rm ewoms-handbook.dvi diff --git a/doc/handbook/designpatterns.tex b/doc/handbook/designpatterns.tex new file mode 100644 index 00000000000..5b454e1befb --- /dev/null +++ b/doc/handbook/designpatterns.tex @@ -0,0 +1,475 @@ +\chapter{Design Patterns} +\label{chap:DesignPatterns} + +This chapter tries to give a high-level understanding of some of the +fundamental techniques which are used by \Dune and \eWoms and the +motivation behind these design decisions. It is assumed that the +reader already has basic experience in object oriented programming +with \Cplusplus. + +First, a quick motivation of \Cplusplus template programming is given. Then +follows an introduction to polymorphism and its opportunities as +opened by the template mechanism. After that, some drawbacks +associated with template programming are discussed, in particular the +blow-up of identifier names, the proliferation of template arguments +and some approaches on how to deal with these problems. + +\section{\Cplusplus Template Programming} + +One of the main features of modern versions of the \Cplusplus programming +language is robust support for templates. Templates are a mechanism +for code generation built directly into the compiler. For the +motivation behind templates, consider a linked list of \texttt{double} +values which could be implemented like this: +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +struct DoubleList { + DoubleList(const double &val, DoubleList *prevNode = 0) + { value = val; if (prevNode) prevNode->next = this; }; + double value; + DoubleList *next; +}; +int main() { + DoubleList *head, *tail; + head = tail = new DoubleList(1.23); + tail = new DoubleList(2.34, tail); + tail = new DoubleList(3.56, tail); +}; +\end{lstlisting} +But what should be done if a list of strings is also required? The +only ``clean'' way to achive this without templates would be to copy +\texttt{DoubleList}, then rename it and change the type of the +\texttt{value} attribute. It is obvious that this is a very +cumbersome, error-prone and unproductive process. For this reason, +recent standards of the \Cplusplus programming language specify the template +mechanism, which is a way of letting the compiler do the tedious work. Using +templates, a generic linked list can be implemented like this: +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +template +struct List { + List(const ValueType &val, List *prevNode = 0) + { value = val; if (prevNode) prevNode->next = this; }; + ValueType value; + List *next; +}; +int main() { + typedef List DoubleList; + DoubleList *head, *tail; + head = tail = new DoubleList(1.23); + tail = new DoubleList(2.34, tail); + tail = new DoubleList(3.56, tail); + + typedef List StringList; + StringList *head2, *tail2; + head2 = tail2 = new StringList("Hello"); + tail2 = new StringList(", ", tail2); + tail2 = new StringList("World!", tail2); +}; +\end{lstlisting} + +Compared to approaches which use external tools for code generation -- +which is the approach chosen for example by the +FEniCS~\cite{FENICS-HP} project -- or heavy (ab)use of the C +preprocessor -- as done for example by the UG framework~\cite{UG-HP} +-- templates have several advantages: +\begin{description} +\item[Well Programmable:] Programming errors are directly detected by + the \Cplusplus compiler. Thus, diagnostic messages from the compiler are + directly useful because the source code compiled is the + same as the one written by the developer. This is not the case + for code generators and C-preprocessor macros where the actual + statements processed by the compiler are obfuscated. +\item[Easily Debugable:] Programs which use the template mechanism can be + debugged almost as easily as \Cplusplus programs which do not use + templates. This is due to the fact that the debugger always knows + the ``real'' source file and line number. +\end{description} +For these reasons \Dune and \eWoms extensively use the template +mechanism. Both projects also try to avoid duplicating functionality +provided by the Standard Template Library (STL,~\cite{STL-REF-HP}) +which is part of the \Cplusplus-2003 standard and was further extended +in the \Cplusplus11 standard. + +\section{Polymorphism} + +In object oriented programming, some methods often make sense for all +classes in a hierarchy, but what actually needs to be \emph{done} +can differ for each concrete class. This observation motivates +\emph{polymorphism}. Fundamentally, polymorphism means all +techniques in which a method call results in the processor executing code +which is specific to the type of object for which the method is +called\footnote{This is the \emph{poly} of polymorphism: There are + multiple ways to achieve the same goal.}. + +In \Cplusplus, there are two common ways to achieve polymorphism: The +traditional dynamic polymorphism which does not require template +programming, and static polymorphism which is made possible by the +template mechanism. + +\subsection*{Dynamic Polymorphism} + +To utilize \emph{dynamic polymorphism} in \Cplusplus, the polymorphic +methods are marked with the \texttt{virtual} keyword in the base +class. Internally, the compiler realizes dynamic polymorphism by +storing a pointer to a so-called \texttt{vtable} within each object of +polymorphic classes. The \texttt{vtable} itself stores the entry point +of each method which is declared \texttt{virtual}. If such a method is +called on an object, the compiler generates code which retrieves the +method's memory address from the object's \texttt{vtable} and then +continues execution at this address. This explains why this mechanism +is called \textbf{dynamic} polymorphism: the code which is actually +executed is dynamically determined at run time. + +\begin{example} + \label{example:DynPoly} + A class called \texttt{Car} may feature the methods + \texttt{gasUsage} (on line \ref{designpatterns:virtual-usage}, which + by default roughly corresponds to the current $CO_2$ emission goal + of the European Union, but can be changed by classes representing + actual cars. Also, a method called \texttt{fuelTankSize} makes + sense for all cars, but since there is no useful default, its + \texttt{vtable} entry is set to $0$ in the base class on line + \ref{designpatterns:totally-virtual}. This tells the compiler that + it is mandatory for this method to be defined in derived + classes. Finally, the method \texttt{range} may calculate the + expected remaining kilometers the car can drive given a fill level + of the fuel tank. Since the \texttt{range} method can retrieve the + information it needs, it does not need to be polymorphic. +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +// The base class +class Car +{public: + virtual double gasUsage() + { return 4.5; };/*@\label{designpatterns:virtual-usage}@*/ + virtual double fuelTankSize() = 0;/*@\label{designpatterns:totally-virtual}@*/ + + double range(double fuelTankFillLevel) + { return 100*fuelTankFillLevel*fuelTankSize()/gasUsage(); } +}; +\end{lstlisting} + +\noindent +Actual car models can now be derived from the base class like this: +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +// A Mercedes S-class car +class S : public Car +{public: + virtual double gasUsage() { return 9.0; }; + virtual double fuelTankSize() { return 65.0; }; +}; + +// A VW Lupo +class Lupo : public Car +{public: + virtual double gasUsage() { return 2.99; }; + virtual double fuelTankSize() { return 30.0; }; +}; +\end{lstlisting} + +\noindent +Calling the \texttt{range} method yields the correct result for any +kind of car: +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +void printMaxRange(Car &car) +{ std::cout << "Maximum Range: " << car.range(1.00) << "\n"; } + +int main() +{ + Lupo lupo; + S s; + std::cout << "VW Lupo:"; + std::cout << "Median range: " << lupo.range(0.50) << "\n"; + printMaxRange(lupo); + std::cout << "Mercedes S-Class:"; + std::cout << "Median range: " << s.range(0.50) << "\n"; + printMaxRange(s); +} +\end{lstlisting} + +For both types of cars, \texttt{Lupo} and \texttt{S} the function +\texttt{printMaxRange} works as expected, it yields +$1003.3\;\mathrm{km}$ for the Lupo and $722.2\;\mathrm{km}$ for the +S-Class. +\end{example} + +\begin{exc} +What happens if \dots +\begin{itemize} +\item \dots the \texttt{gasUsage} method is removed from the \texttt{Lupo} class? +\item \dots the \texttt{virtual} qualifier is removed in front of the + \texttt{gasUsage} method in the base class? +\item \dots the \texttt{fuelTankSize} method is removed from the \texttt{Lupo} class? +\item \dots the \texttt{range} method in the \texttt{S} class is + overwritten? +\end{itemize} +\end{exc} + +\subsection*{Static Polymorphism} + +Dynamic polymorphism has a few disadvantages. The most relevant in the +context of \eWoms is that the compiler can not see ``inside'' the +called methods and thus cannot optimize properly. For example, modern +\Cplusplus compilers 'inline' short methods, i.e. they copy the method's body +to where it is called. First, inlining allows to save a few +instructions by avoiding to jump into and out of the method. Second, +and more importantly, inlining also allows further optimizations which +depend on specific properties of the function arguments (e.g. constant +value elimination) or the contents of the function body (e.g. loop +unrolling). Unfortunately, inlining and other cross-method +optimizations are made next to impossible by dynamic +polymorphism. This is because these optimizations are accomplished by the +compiler (i.e. at compile time) whilst the code which actually gets +executed is only determined at run time for \texttt{virtual} +methods. To overcome this issue, template programming can be used to +achive polymorphism at compile time. This works by supplying the type +of the derived class as an additional template parameter to the base +class. Whenever the base class needs to call back the derived class, the \texttt{this} pointer of the base class is reinterpreted as +being a pointer to an object of the derived class and the method is +then called on the reinterpreted pointer. This scheme gives the \Cplusplus +compiler complete transparency of the code executed and thus opens +much better optimization oportunities. Since this mechanism completely +happens at compile time, it is called ``static polymorphism'' because +the called method cannot be dynamically changed at runtime. +\begin{example} + Using static polymorphism, the base class of example \ref{example:DynPoly} + can be implemented like this: +\begin{lstlisting}[name=staticcars,basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +// The base class. The 'Imp' template parameter is the +// type of implementation, i.e. the derived class +template +class Car +{public: + double gasUsage() + { return 4.5; }; + double fuelTankSize() + { throw "The derived class needs to implement the fuelTankSize() method"; }; + + double range(double fuelTankFillLevel) + { return 100*fuelTankFillLevel*asImp_().fuelTankSize()/asImp_().gasUsage(); } + +protected: + // reinterpret 'this' as a pointer to an object of type 'Imp' + Imp &asImp_() { return *static_cast(this); } +}; +\end{lstlisting} +(Notice the \texttt{asImp\_()} calls in the \texttt{range} method.) The +derived classes may now be defined like this: +\begin{lstlisting}[name=staticcars,basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +// A Mercedes S-class car +class S : public Car +{public: + double gasUsage() { return 9.0; }; + double fuelTankSize() { return 65.0; }; +}; + +// A VW Lupo +class Lupo : public Car +{public: + double gasUsage() { return 2.99; }; + double fuelTankSize() { return 30.0; }; +}; +\end{lstlisting} +\end{example} + +\noindent +Analogous to example \ref{example:DynPoly}, the two kinds of cars can +be used generically within (template) functions: +\begin{lstlisting}[name=staticcars,basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +template +void printMaxRange(CarType &car) +{ std::cout << "Maximum Range: " << car.range(1.00) << "\n"; } + +int main() +{ + Lupo lupo; + S s; + std::cout << "VW Lupo:"; + std::cout << "Median range: " << lupo.range(0.50) << "\n"; + printMaxRange(lupo); + std::cout << "Mercedes S-Class:"; + std::cout << "Median range: " << s.range(0.50) << "\n"; + printMaxRange(s); + return 0; +} +\end{lstlisting} + +%\textbf{TODO: Exercise} + +\section{Common Template Programming Related Problems} + +Although \Cplusplus template programming opens a few intriguing +possibilities, it also has a few disadvantages. In this section, a few +of them are outlined and some hints on how they can be dealt with are +provided. + +\subsection*{Identifier-Name Blow-Up} + +One particular problem with the advanced use of \Cplusplus templates is that the +canonical identifier names for types and methods quickly become really +long and unintelligible. For example, a typical error message +generated using GCC 4.5 and \Dune-PDELab looks like this +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize, numbersep=5pt] +test_pdelab.cc:171:9: error: no matching function for call to Dune::\ +PDELab::GridOperatorSpace, (Dune::PartitionIteratorType)4u> >, Dune::\ +PDELab::Q1LocalFiniteElementMap, Dune::PDELab::\ +NoConstraints, Ewoms::PDELab::BoxISTLVectorBackend >, 2, Dune::PDELab::GridFunctionSpaceBlockwiseMapper>\ +, Dune::PDELab::PowerGridFunctionSpace, \ +(Dune::PartitionIteratorType)4u> >, Dune::PDELab::Q1LocalFiniteElementMap\ +, Dune::PDELab::NoConstraints, Ewoms::PDELab::\ +BoxISTLVectorBackend >, 2, Dune::\ +PDELab::GridFunctionSpaceBlockwiseMapper>, Ewoms::PDELab::BoxLocalOperator\ +, Dune::PDELab::\ +ConstraintsTransformation, Dune::PDELab::\ +ConstraintsTransformation, Dune::PDELab::\ +ISTLBCRSMatrixBackend<2, 2>, true>::GridOperatorSpace() +\end{lstlisting} +This seriously complicates diagnostics. Although there is no full +solution to this problem yet, an effective way of dealing with such +kinds of error messages is to ignore the type information and to just +look at the location given at the beginning of the line. If nested +templates are used, the lines printed by the compiler above the actual +error message specify how exactly the code was instantiated (the lines +starting with ``\texttt{instantiated from}''). In this case it is +advisable to look at the innermost source code location of ``recently +added'' source code. + +\subsection*{Proliferation of Template Parameters} + +Templates often need a large number of template parameters. For +example, the error message above was produced by the following +snipplet: +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +int main() +{ + enum {numEq = 2}; + enum {dim = 3}; + typedef Dune::UGGrid Grid; + typedef Grid::LeafGridView GridView; + typedef Dune::PDELab::Q1LocalFiniteElementMap FEM; + typedef TTAG(LensProblem) TypeTag; + typedef Dune::PDELab::NoConstraints Constraints; + typedef Dune::PDELab::GridFunctionSpace< + GridView, FEM, Constraints, Ewoms::PDELab::BoxISTLVectorBackend + > + doubleGridFunctionSpace; + typedef Dune::PDELab::PowerGridFunctionSpace< + doubleGridFunctionSpace, + numEq, + Dune::PDELab::GridFunctionSpaceBlockwiseMapper + > + GridFunctionSpace; + typedef typename GridFunctionSpace::ConstraintsContainer::Type + ConstraintsTrafo; + typedef Ewoms::PDELab::BoxLocalOperator LocalOperator; + typedef Dune::PDELab::GridOperatorSpace< + GridFunctionSpace, + GridFunctionSpace, + LocalOperator, + ConstraintsTrafo, + ConstraintsTrafo, + Dune::PDELab::ISTLBCRSMatrixBackend, + true + > + GOS; + GOS gos; // instantiate grid operator space +} +\end{lstlisting} + +Although the code above is not really intuitivly readable, this does +not pose a severe problem as long as the type (in this case the grid +operator space) needs to be specified exactly once in the whole +program. If, on the other hand, it needs to be consistend over +multiple locations in the source code, measures have to be taken in +order to keep the code maintainable. + +\section{Traits Classes} + +A classic approach to reducing the number of template parameters is to +gather all the arguments in a special class, a so-called traits +class. Instead of writing +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +template +class MyClass {}; +\end{lstlisting} +one can use +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +template +class MyClass {}; +\end{lstlisting} +where the \texttt{Traits} class contains public type definitions for +\texttt{A}, \texttt{B}, \texttt{C} and \texttt{D}, e.g. +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +struct MyTraits +{ + typedef float A; + typedef double B; + typedef short C; + typedef int D; +}; +\end{lstlisting} + +\noindent +As there is no free lunch, the traits approach comes with a few +disadvantages of its own: +\begin{enumerate} +\item Hierarchies of traits classes are problematic. This is due to + the fact that each level of the hierarchy must be self-contained. As + a result, it is impossible to define parameters in the base class + which depend on parameters which only later get specified by a + derived traits class. +\item Traits quickly lead to circular dependencies. In practice + this means that traits classes can not extract any information from + templates which get the traits class as an argument -- even if the + extracted information does not require the traits class. +\end{enumerate} + +\noindent +To see the point of the first issue, consider the following: +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +struct MyBaseTraits { + typedef int Scalar; + typedef std::vector Vector; + typedef std::list List; + typedef std::array Array; + typedef std::set Set; +}; + +struct MyDoubleTraits : public MyBaseTraits { + typedef double Scalar; +}; + +int main() { + MyDoubleTraits::Vector v{1.41421, 1.73205, 2}; + for (int i = 0; i < v.size(); ++i) + std::cout << v[i]*v[i] << std::endl; +} +\end{lstlisting} +Contrary to what is intended, \texttt{v} is a vector of integers. This +problem can not be solved using static polymorphism, either, since it +would lead to a cyclic dependency between \texttt{MyBaseTraits} and +\texttt{MyDoubleTraits}. + +The second issue is illuminated by the following example, where one +would expect the \texttt{MyTraits:: VectorType} to be \texttt{std::vector}: +\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt] +template +class MyClass { +public: typedef double ScalarType; +private: typedef typename Traits::VectorType VectorType; +}; + +struct MyTraits { + typedef MyClass::ScalarType ScalarType; + typedef std::vector VectorType +}; +\end{lstlisting} +Although this example seems to be quite pathetic, in practice it is +often useful to specify parameters in such a way. + +% TODO: section about separation of functions, parameters and +% independent variables. (e.g. the material laws: BrooksCorey +% (function), BrooksCoreyParams (function parameters), wetting +% saturation/fluid state (independent variables) diff --git a/doc/handbook/ewoms-handbook.bib b/doc/handbook/ewoms-handbook.bib new file mode 100644 index 00000000000..214b5dbc4dd --- /dev/null +++ b/doc/handbook/ewoms-handbook.bib @@ -0,0 +1,1015 @@ +% This file was created with JabRef 2.6. +% Encoding: UTF-8 + +@BOOK{DEBOOR1978, + title = {A Practical Guide to Splines}, + publisher = {Springer}, + year = {1978}, + author = {C. {de Boor}}, + edition = {1} +} + +@ARTICLE{VANGENUCHTEN1980, + author = {M. T. {van Genuchten}}, + title = {A Closed-form Equation for Predicting the Hydraulic Conductivity + of Unsaturated Soils}, + journal = {Journal of the Soil Science Society of America}, + year = {1980}, + pages = {892--898}, + number = {44}, + key = {Gen80} +} + +@PHDTHESIS{A3:oelmann:2006a, + author = {\"Olmann, Ulrich}, + title = {Behandlung anisotroper Mobilit\"aten als Resultat von Upscalingverfahren + mittels Mehrpunktflu{\ss}approximationen}, + school = {Institut f\"ur Wasserbau, Universit\"at Stuttgart}, + year = {to be published 2006} +} + +@MISC{A3:oelmann:2006b, + author = {\"Olmann, U. and Aavatsmark, I. and Helmig, R.}, + title = {{Buckley-Leverett heterogen --- Konstruktion der L\"osung mit der + Charakteristikenmethode}}, + howpublished = {Preprint-Reihe des SFB404}, + month = {March}, + year = {2006}, + note = {2006/05} +} + +@ARTICLE{A3:aavatsmark:2002, + author = {Aavatsmark, Ivar}, + title = {An introduction to multipoint flux approximations for quadrilateral + grids}, + journal = {Computational Geosciences}, + year = {2002}, + volume = {6}, + pages = {405--432} +} + +@ARTICLE{A3:aavatsmark:1996, + author = {Aavatsmark, I. and Barkve, T. and B{\o}e, {\O}. and Mannseth, T.}, + title = {Discretization on Non-Orthogonal, Quadrilateral Grids for Inhomogeneous + Anisotropic Media}, + journal = {Journal of Computational Physics}, + year = {1996}, + volume = {127}, + pages = {2--14} +} + +@INPROCEEDINGS{A3:aavatsmark:1994, + author = {Aavatsmark, I. and Barkve, T. and B{\o}e, {\O}. and Mannseth, T.}, + title = {Discretization on non-orthogonal, curvilinear grids for multiphase + flow}, + booktitle = {Proc.\ of the 4th European Conf.\ on the Mathematics of Oil Recovery}, + year = {1994}, + address = {Norway} +} + +@ARTICLE{A3:acosta:2006, + author = {Acosta, M. and Merten, C. and Eigenberger, G. and Class, H. and Helmig, + R. and Thoben, B. and M\"uller-Steinhagen, H.}, + title = {Modeling non-isothermal two-phase multicomponent flow in the cathode + of PEM fuel cells}, + journal = {Journal of Power Sources}, + year = {2006}, + pages = {in print} +} + +@ARTICLE{AGRAWAL2001, + author = {K. Agrawal and P. N. Loezos and M. Syamlal and S. Sundaresan}, + title = {The Role of Meso-Scale Structures in Rapid Gas-Solid Flows}, + journal = {Journal of Fluid Mechanics}, + year = {2001}, + pages = {151--185}, + number = {445} +} + +@INPROCEEDINGS{A3:allan:1998, + author = {Allan, J. and Ewing, J. and Helmig, R. and Braun, J.}, + title = {Scale effects in multiphase flow modeling}, + booktitle = {1. International conference on remediation of chlorinated and recalcitrant + compounds}, + year = {1998}, + editor = {Wickramanayake, G.B. and Hinchee, R.E.}, + address = {Monterey, California, USA}, + month = {18th--21st of may}, + publisher = {Battelle Press, Columbus, OH, USA} +} + +@BOOK{ANDERSON-CFD-95, + title = {Computational Fluid Mechanics}, + publisher = {McGraw-Hill}, + year = {1995}, + author = {J. D. Anderson}, + edition = {1}, + subtitle = {The Basics with Applications} +} + +@BOOK{ARFKEN2005, + title = {Mathematical Methods for Physicists}, + publisher = {Academic Press}, + year = {2005}, + author = {G. B. Arfken and H. J. Weber}, + edition = {6} +} + +@BOOK{A3:aziz:1979, + title = {Petroleum Reservoir Simulation}, + publisher = {Applied Science Publishers, London}, + year = {1979}, + author = {Aziz, K. and Settari, A.} +} + +@MISC{GSIMSKRIPT, + author = {P. Bastian}, + title = {Grundlagen der Modellbildung und Simulation}, + howpublished = {University of Stuttgart, Lecture Notes}, + year = {2007} +} + +@MISC{NUMPDESKRIPT, + author = {P. Bastian}, + title = {Numerische L\"{o}sung partieller Differentialgleichungen}, + howpublished = {University of Stutt\-gart, Lecture Notes}, + year = {2007} +} + +@BOOK{BASTIAN1999, + title = {Numerical Computation of Multiphase Flows in Porous Media}, + publisher = {Habilitationsschrift, University of Kiel}, + year = {1999}, + author = {P. Bastian}, + edition = {1} +} + +@INBOOK{A3:bastian:1997, + pages = {27--40}, + title = {UG: A flexible software toolbox for solving partial differential + equations.}, + publisher = {Springer Verlag}, + year = {1997}, + author = {Bastian, P. and Birken, K. and Lang, S. and Johannsen, K. and Neuss, + N. and Rentz-Reichert, H. and Wieners, C.}, + volume = {1}, + series = {Computing and Visualization in Science} +} + +@ARTICLE{BASTIAN2008, + author = {P. Bastian and M. Blatt and A. Dedner and C. Engwer and R. Kl\"{o}fkorn + and R. Kornhuber and M. Ohlberger and O. Sander}, + title = {A Generic Grid Interface For Parallel and Adaptive Scientific Computing. + Part II: implementation and tests in {DUNE}}, + journal = {Computing}, + year = {2008}, + volume = {82}, + pages = {121--138}, + number = {2} +} + +@INBOOK{A3:bastian:2000, + pages = {52--71}, + title = {Numerical Simulation of Multiphase Flow in Fractured Porous Media.}, + publisher = {Springer Verlag}, + year = {2000}, + author = {Bastian, P. and Chen, Z. and Ewing, R. E. and Helmig, R. and Jakobs + H. and Reichenberger V.}, + series = {Lecture Notes in Physics, Chen, Ewing and Shi (eds.)} +} + +@ARTICLE{A3:bastian:1999, + author = {Bastian, P. and Helmig, R.}, + title = {Efficient Fully-Coupled Solution Techniques for Two Phase Flow in + Porous Media. Parallel Multigrid Solution and Large Scale Computations}, + journal = {Advances in Water Resources}, + year = {1999} +} + +@ARTICLE{BASTIAN-HELMIG1999, + author = {P. Bastian and R. Helmig}, + title = {Efficient Fully-Coupled Solution Techniques for Two Phase Flow in + Porous Media. Parallel Multigrid Solution and Large Scale Computations}, + journal = {Advances in Water Resources}, + year = {1999}, + volume = {23}, + pages = {199--216}, + number = {3} +} + +@ARTICLE{BEAUCHEMIN1995, + author = {S. S. Beauchemin and J. L. Barron}, + title = {The computation of optical flow}, + journal = {ACM Computing Surveys}, + year = {1995}, + volume = {27}, + pages = {433--466}, + number = {3} +} + +@ARTICLE{BELIAEV2001, + author = {A. Y. Beliaev and S. M. Hassanizadeh}, + title = {A Theoretical Model of Hysteresis and Dynamic Effects in the Capillary + Relation for Two-phase Flow in Porous Media}, + journal = { Transport in Porous Media}, + year = {2001}, + volume = {43}, + pages = {487--510}, + number = {3} +} + +@PHDTHESIS{A3:bielinski:2006, + author = {Bielinski, A.}, + title = {Numerical Simulation of CO$_2$ Sequestration in Geological Formations}, + school = {Institut f\"ur Wasserbau, Universit\"at Stuttgart}, + year = {2006} +} + +@ARTICLE{A3:Braun:2002, + author = {Braun, C. and Helmig, R. and Manthey, S.}, + title = {Determination of constitutive relationships for two-phase flow processes + in heterogeneous porous media with emphasis on the relative permeability-saturation-relationship}, + journal = {Journal of Contaminant Hydrology}, + year = {2005} +} + +@BOOK{BRONSTEIN-93, + title = {Taschenbuch der Mathematik}, + publisher = {Verlag Harri Deutsch}, + year = {1993}, + author = {I.N. Bronstein and K.A. Semendjajew and G. Musiol and H. M\"{u}hlig}, + edition = {6} +} + +@TECHREPORT{BROOKS1964, + author = {R. H. Brooks and A. T. Corey.}, + title = {Hydraulic properties of porous media}, + institution = {Colorado State University, Fort Collins}, + year = {1964}, + note = {Hydrology Paper No. 3, 27 pp.} +} + +@ARTICLE{BURDINE1953, + author = {N.T. Burdine}, + title = {Relative permeability calculations from pore size distribution data}, + journal = {Transactions of the American Institute of Mining and Metallurgical + Engineers}, + year = {1953}, + volume = {198}, + pages = {71--78} +} + +@INPROCEEDINGS{BURRI2006, + author = {A. Burri and A. Dedner and R. Kl\"{o}fkorn and M. Ohlberger}, + title = {An efficient implementation of an adaptive and parallel grid in DUNE.}, + booktitle = {Computational Science and High Performance Computing II}, + year = {2006}, + volume = {91}, + pages = {67--82}, + publisher = {Springer} +} + +@ARTICLE{Chen2000, + author = {Z. Chen and Q. Guan and R.E. Ewing}, + title = {Analysis of a compositional model for fluid flow in porous media}, + journal = {SIAM Journal on Applied Mathematics}, + year = {2000}, + volume = {60}, + pages = {747-777}, + number = {3}, + owner = {faigle}, + review = {3phase model derivation finite element discretizattion Beschreibung + des sequenziellen numerischen L\"osungsschemas verwendete ELLAM methode + + + Trangenstein + pC + + + assumpt: no mass transfer between water and gas/oil (black oil) + + rho_w = const + + s(p, ...) und nicht s(p+pc, ...)}, + timestamp = {2011.11.29} +} + +@BOOK{CLASS2007, + title = {Models for Non-Isothermal Compositional Gas-Liquid Flow and Transport + in Porous Media}, + publisher = {University of Stuttgart}, + year = {2007}, + author = {H. Class}, + edition = {1} +} + +@BOOK{A3:class:2001, + title = {Theorie und numerische Modellierung nichtisothermer Mehrphasenprozesse + in NAPL-kontaminierten por\"osen Medien}, + publisher = {Institut f\"ur Wasserbau, Universit\"at Stuttgart}, + year = {2001}, + author = {Class, Holger}, + volume = {105}, + series = {Mitteilungsheft} +} + +@PHDTHESIS{CLASS2001, + author = {H. Class}, + title = {Theorie und numerische Modellierung nichtisothermer Mehrphasenprozesse + in NAPL-kontaminierten por\"{o}sen Medien.}, + school = {University of Stuttgart}, + year = {2001} +} + +@ARTICLE{A3:class:2002b, + author = {Class, H. and Helmig, R.}, + title = {Numerical Simulation of Nonisothermal Multiphase Multicomponent Processes + in Porous Media -- 2. Applications for the Injection of Steam and + Air}, + journal = {Advances in Water Resources}, + year = {2002}, + volume = {25}, + pages = {551--564} +} + +@ARTICLE{A3:class:2002a, + author = {Class, H. and Helmig, R. and Bastian, P.}, + title = {Numerical Simulation of Nonisothermal Multiphase Multicomponent Processes + in Porous Media -- 1. An Efficient Solution Technique}, + journal = {Advances in Water Resources}, + year = {2002}, + volume = {25}, + pages = {533--550} +} + +@ARTICLE{A3:coats:1974, + author = {Coats, K.H. and Chieh Chu, W.D.G. and Marcum, B.E.}, + title = {Three-dimensional simulation of steamflooding}, + journal = {Society of Petroleum Engineers Journal}, + year = {1974}, + month = {December} +} + +@MISC{DARCY1856, + author = {H. Darcy}, + title = {Les Fontaines publiques de la ville de Dijon}, + howpublished = {Paris}, + year = {1856}, + key = {Dar1856} +} + +@ARTICLE{A3:deneef:1997, + author = {De Neef, M. and Molenaar, J.}, + title = {Analysis of DNAPL infiltration in a Medium with a low-permeable lens}, + journal = {Computational Geosciences}, + year = {1997}, + volume = {1}, + pages = {191-214} +} + +@ARTICLE{DITTERICH1996, + author = {E. Ditterich}, + title = {Wirbel um den Bodenschutz}, + journal = {Umweltmagazin}, + year = {1996}, + pages = {46--48}, + number = {4} +} + +@INPROCEEDINGS{A3:edwards:1994, + author = {Edwards, M. G. and Rogers, C. F.}, + title = {A flux continuous scheme for the full tensor pressure equation}, + booktitle = {Proc.\ of the 4th European Conf.\ on the Mathematics of Oil Recovery}, + year = {1994}, + address = {Norway} +} + +@PHDTHESIS{A3:emmert:1997, + author = {Emmert, Martin}, + title = {Numerische Simulation von isothermen/nichtisothermen Mehrphasenprozessen + unter Ber\"ucksichtigung der Ver\"anderung der Fluideigenschaften}, + school = {Institut f\"ur Wasserbau, Universit\"at Stuttgart}, + year = {1997} +} + +@ARTICLE{A3:falta:1992, + author = {Falta, R.W. and Pruess, K. and Javandel, I. and Witherspoon, P.A.}, + title = {Numerical Modeling of Steam Injection for the Removal of Nonaqueous + Phase Liquids From the Subsurface. 1. Numerical Formulation}, + journal = {Water Resoures Research}, + year = {1992}, + volume = {28,2}, + pages = {433--449} +} + +@TECHREPORT{A3:forsyth:1993, + author = {Forsyth, P.A.}, + title = {Three dimensional modeling of steam flush for DNAPL site remediation}, + institution = {Dep. of Computer Science}, + year = {1993}, + address = {University of Waterloo}, + note = {CS-93-56} +} + +@PHDTHESIS{Fritz2010, + author = {Jochen Fritz}, + title = {A Decoupled Model for Compositional Non-Isothermal Multiphase Flow + in Porous Media and Multiphysics Approaches for Two-Phase Flow}, + school = {Universit\"at Stuttgart}, + year = {2010}, + owner = {rodrigog}, + timestamp = {2011.02.07} +} + +@ARTICLE{A3:gimse:1992, + author = {Gimse, Tore and Risebro, Nils Henrik}, + title = {Solution of the Cauchy Problem for a Conservation Law with a Discontinuous + Flux Function}, + journal = {SIAM J.\ Math.\ Anal.}, + year = {1992}, + volume = {23}, + pages = {635-648}, + number = {3} +} + +@INPROCEEDINGS{A3:gimse:1991, + author = {Gimse, Tore and Risebro, Nils Henrik}, + title = {Riemann Problems with a Discontinuous Flux Function}, + booktitle = {Proc.\ 3rd Internat.\ Conf.\ Hyperbolic Problems}, + year = {1991}, + pages = {488-502}, + address = {Uppsala} +} + +@TECHREPORT{subgrid, + author = {C. Gr\"aser and O. Sander}, + title = {The dune-subgrid Module and Some Applications}, + year = {2009}, + note = {Matheon 566} +} + +@MASTERSTHESIS{A3:grass:2005, + author = {Grass, Christoph}, + title = {Untersuchung von Randbedingungen bei der numerischen Simulation von + Zweiphasenstr\"omungen in por\"osen Medien}, + school = {Institut f\"ur Wasserbau, Universit\"at Stuttgart}, + year = {2005}, + month = {January} +} + +@BOOK{GRIEBEL2003, + title = {Numerische Simulation in der Molek\"{u}ldynamik}, + publisher = {Springer}, + year = {2003}, + author = {M. Griebel and S. Knapek and G. Zumbusch and A. Caglar}, + edition = {1} +} + +@INPROCEEDINGS{A3:freiboth:2004, + author = {Sandra H\"olzemann and Holger Class and Rainer Helmig}, + title = {A New Concept for the Numerical Simulation and Parameter Identification + of Multiphase Flow and Transport Processes in Cohesive Soils}, + booktitle = {{Unsaturated Soils: Numerical and Theoretical Approaches -- Proceedings + of the International Conference ``From Experimental Evidence towards + Numerical Modelling of Unsaturated Soils'' (18. - 19. September 2003, + Bauhaus-Universit\"at Weimar)}}, + year = {2004}, + editor = {Schanz, T.}, + publisher = {Springer-Verlag}, + note = {ISBN: 3-540-21122-5} +} + +@ARTICLE{HASSANIZADEH1987, + author = {S. M. Hassanizadeh and W. G. Gray}, + title = {High velocity flow in porous media}, + journal = {Transport in Porous Media}, + year = {1987} +} + +@BOOK{A3:helmig:1997, + title = {Multiphase Flow and Transport Processes in the Subsurface --- A Contribution + to the Modeling of Hydrosystems}, + publisher = {Springer Verlag}, + year = {1997}, + author = {Helmig, R.} +} + +@BOOK{HELMIG1997, + title = {Multiphase Flow and Transport Processes in the Subsurface: A Contribution + to the Modeling of Hydrosystems}, + publisher = {Springer}, + year = {1997}, + author = {R. Helmig}, + edition = {1} +} + +@BOOK{HYDROSKRIPT, + title = {Grundlagen der Hydromechanik}, + publisher = {Shaker}, + year = {2005}, + author = {R. Helmig and H. Class}, + edition = {1} +} + +@INBOOK{A3:sfb404:2003, + chapter = {A3}, + pages = {69--98}, + title = {Arbeits- und Ergebnisbericht 2003}, + publisher = {SFB 404}, + year = {2003}, + author = {Helmig, R. and Class, H. and Jakobs, H. and Bierlinski, A. and \"Olmann, + U.}, + month = {May} +} + +@INPROCEEDINGS{A3:HelmigEtAl:2006, + author = {Helmig, R. and Miller, C. T. and Jakobs, H. and Class, H. and Hilpert, + M. and Kees, C. E. and Niessner, J.}, + title = {{Multiphase Flow and Transport Modeling in Heterogeneous Porous Media}}, + booktitle = {Progress in Industrial Mathematics at ECMI 2004}, + year = {2006}, + editor = {Di Bucchianico, A. and Mattheij, R. M. M. and Peletier, M. A.}, + pages = {449--488}, + address = {Eindhoven University of Technology}, + month = {6}, + publisher = {Springer-Verlag}, + note = {3-540-28072-3}, + type = {Plenary lecture} +} + +@PHDTHESIS{HUBER2000, + author = {R. U. Huber}, + title = {Compositional Multiphase Flow and Transport in Heterogeneous Porous + Media}, + school = {University of Stuttgart}, + year = {2000} +} + +@ARTICLE{KALUARACHCHI1992, + author = {J. J. Kaluarachchi and J. C. Parker}, + title = {Multiphase Flow with a Simplified Model for Oil Entrapment}, + journal = {Transport in Porous Media}, + year = {1992}, + volume = {7}, + pages = {1--14}, + number = {1} +} + +@BOOK{KARNIADAKIS2005, + title = {Microflows and Nanoflows: Fundamentals and Simulation}, + publisher = {Springer}, + year = {2005}, + author = {G. Karniadakis and A. Be{\c c}k\"{o}k and N. R. Aluru}, + edition = {1} +} + +@ARTICLE{A3:King:1996, + author = {King, P. R.}, + title = {Upscaling Permeability: Error Analysis for Renormalisation}, + journal = {Transport in Porous Media}, + year = {1996}, + volume = {23}, + pages = {337--354} +} + +@ARTICLE{KOOL1987, + author = {J. Kool and J. C. Parker and M. T. van Genuchten}, + title = {Parameter Estimation for Unsaturated Flow and Transport Models - + A Review}, + journal = {Journal of Hydrology}, + year = {1987}, + volume = {91}, + pages = {255--293} +} + +@BOOK{A3:lancaster:1969, + title = {Theory of Matrices}, + publisher = {Academic Press, Inc.\ (London) Ltd.}, + year = {1969}, + author = {Lancaster, Peter} +} + +@ARTICLE{LAND1968, + author = {C. S. Land}, + title = {Calculation of Imbibition Relative Permeability for Two- and Three-Phase + Flow from Rock Properties}, + journal = {Journal of the Society of Petroleum Engineers}, + year = {1968}, + volume = {8}, + pages = {149--156}, + number = {2} +} + +@ARTICLE{LENHARD1987, + author = {R. J. Lenhard and J. C. Parker}, + title = {A Model for Hysteretic Constitutive Relations Governing Multiphase + Flow. 2. Permeability-Saturation Relations}, + journal = {Water Resources Research}, + year = {1987}, + volume = {23}, + pages = {2197--2206}, + number = {12} +} + +@ARTICLE{LHHW2011, + author = {A. Lauser and C. Hager and R. Helmig and B. Wohlmuth}, + title = {A new approach for phase transitions in miscible multi-phase flow in porous media}, + journal = {Advances in Water Resources}, + year = {2011}, + pages = {957--966}, + volume = {34}, + number = {8} +} + + +@BOOK{A3:leveque:1998, + title = {Numerical Methods for Conservation Laws}, + publisher = {Birkh\"auser Verlag, Basel Boston, Berlin}, + year = {1998}, + author = {LeVeque, Randall J.} +} + +@BOOK{LEVEQUE1992, + title = {Numerical Methods for Conservation Laws}, + publisher = {Birkh\"{a}user}, + year = {1992}, + author = {R. J. LeVeque}, + edition = {2} +} + +@BOOK{A3:looney:2000, + title = {Vadose Zone}, + publisher = {Batelle Press, Columbus OH}, + year = {2000}, + author = {Looney, B. B. and Falta, R. W.} +} + +@BOOK{MEISTER2008, + title = {Numerik linearer Gleichungssysteme: Eine Einf\"{u}hrung in moderne + Verfahren.}, + publisher = {Vieweg}, + year = {2008}, + author = {A. Meister}, + edition = {3} +} + +@BOOK{MEYBERG2000, + title = {H\"{o}here Mathematik 1}, + publisher = {Springer}, + year = {2001}, + author = {K. Meyberg and P. Vachenauer}, + edition = {6} +} + +@ARTICLE{MUALEM1976, + author = {Y. Mualem}, + title = {A new model for predicting the hydraulic conductivity of unsaturated + porous media}, + journal = {Water Resources Research}, + year = {1976}, + volume = {12}, + pages = {513--522}, + number = {3} +} + +@ARTICLE{MUCCINO1998, + author = {J. C. Muccino and W. G. G. Gray and L. A. Ferrand}, + title = {Toward an Improved Understanding of Multiphase Flow in Porous Media}, + journal = {Reviews of Geophysics}, + year = {1998}, + volume = {36}, + pages = {401--422}, + number = {3} +} + +@BOOK{MUSKAT1937, + title = {The Flow of Homogeneous Fluids Through Porous Media}, + publisher = {McGraw-Hill}, + year = {1937}, + author = {M. Muskat}, + edition = {1} +} + +@PHDTHESIS{DeNEFF2000, + author = {de Neef, M. J.}, + title = {Modeling Capillary Effects in Heterogeneous Porous Media}, + school = {Technical University Delft}, + year = {2000} +} + +@ARTICLE{NIESSNER2005, + author = {J. Niessner and R. Helmig and H. Jakobs and J. Roberts}, + title = {Interface Condition and Exact Linearization in the Newton Iterations + for Two-phase Flow in Heterogeneous Porous Media}, + journal = {Advances in Water Resources}, + year = {2005}, + volume = {28}, + pages = {671--687}, + number = {7} +} + +@ARTICLE{A3:nordbotten:2005a, + author = {Nordbotten, J.M. and Celia, M.A. and Bachu, S.}, + title = {Injection and Storage of {CO$_2$} in Deep Saline Aquifers: Analytical + Solution for {CO$_2$} Plume Evolution During Injection}, + journal = {Transport in Porous Media}, + year = {2005}, + volume = {58(3)}, + pages = {339--360} +} + +@ARTICLE{A3:nordbotten:2005b, + author = {Nordbotten, J.M. and Celia, M.A. and Bachu, S. and Dahle, H.}, + title = {Semi-Analytical Solution for {CO$_2$} Leakage through an Abandoned + Well}, + journal = {Environmental Science and Technology}, + year = {2005}, + volume = {39(2)}, + pages = {602--611} +} + +@UNPUBLISHED{A3:nordbotten:2005c, + author = {Nordbotten, J. M. and Aavatsmark, I. and Eigestad, G. T.}, + title = {Monotonicity of Control Volume Methods}, + note = {submitted to Numerische Mathematik}, + year = {2005} +} + +@BOOK{ORTEGA2000, + title = {Iterative Solution of Nonlinear Equations in Several Variables}, + publisher = {Society for Industrial and Applied Mathematics}, + year = {2000}, + author = {J. M. Ortega and W. C. Rheinboldt}, + address = {Philadelphia, PA, USA}, + edition = {1} +} + +@PHDTHESIS{PAPAFOTIOU2008, + author = {A. Papafotiou}, + title = {Numerical investigations of the role of hysteresis in heterogeneous + two-phase flow systems}, + school = {University of Stuttgart}, + year = {2008} +} + +@ARTICLE{PARKER1987, + author = {J. C. Parker and R. J. Lenhard and T. Kuppusamy}, + title = {A Parametric Model for Constitutive Properties Governing Multiphase + Flow in Porous Media}, + journal = {Water Resources Research}, + year = {1987}, + volume = {23}, + pages = {618--624}, + number = {4} +} + +@BOOK{PRANTL-02, + title = {F\"{u}hrer durch die Str\"{o}mungslehre}, + publisher = {Vieweg}, + year = {2002}, + author = {L. Prantl and H. Oertel}, + edition = {11} +} + +@MISC{A3:IAPWS:2003, + author = {IAPWS (The International Association for the Properties of Water + and Steam)}, + title = {Revised Release on the IAPS Formulation 1985 for the Viscosity of + Ordinary Water Substance}, + howpublished = {\url{http://www.iapws.org/}}, + year = {2003} +} + +@MISC{IAPWS1997, + author = {IAPWS (The International Association for the Properties of Water + and Steam)}, + title = {Revised Release on the IAPWS Industrial Formulation 1997 for the + Thermodynamic Properties of Water and Steam}, + howpublished = {\url{http://www.iapws.org/IF97-Rev.pdf}}, + year = {1997} +} + +@BOOK{reid1987, + title = {The Properties of Gases and Liquids}, + publisher = {McGraw-Hill Inc.}, + year = {1987}, + author = {Reid, R.C. and Prausnitz, J.M. and Poling, B.E.} +} + +@ARTICLE{Pardiso, + author = {Olaf Schenk and Klaus G\"artner}, + title = {Solving unsymmetric sparse systems of linear equations with {PARDISO}}, + journal = {Future Generation Computer Systems}, + year = {2004}, + volume = {20}, + pages = {475 - 487}, + number = {3}, + doi = {DOI: 10.1016/j.future.2003.07.011}, + issn = {0167-739X}, + keywords = {Computational sciences}, + url = {\url{http://www.sciencedirect.com/science/article/B6V06-49NXY7J-F/2/e8260e5d8f19639019cddea4776c024c}} +} + +@PHDTHESIS{SHETA1999, + author = {H. Sheta}, + title = {Simulation von Mehrphasenvorg\"{a}engen in por\"{o}sen Medien unter + Einbeziehung von Hystereseeffekten}, + school = {University of Stuttgart}, + year = {1999} +} + +@ARTICLE{SPAETH1969, + author = {H. Sp{\"{a}}th}, + title = {Exponential Spline Interpolation}, + journal = {Computing}, + year = {1969}, + volume = {4}, + pages = {225--233}, + number = {3}, + key = {Spa69} +} + +@ARTICLE{SPE5, + author = {J. E. Killough and C. A. Kossack}, + title = {Fifth Comparative Solution Project: Evaluation of Miscible Flood Simulators}, + journal = {Society of Petroleum Engineers}, + year = {1987}, + volume = {SPE 16000} +} + +@BOOK{A3:Stauffer:1984, + title = {Introduction to Percolation Theory}, + publisher = {Taylor \& Francis}, + year = {1994}, + author = {Stauffer, F. and Aharnony, A.} +} + +@BOOK{SAENDIG-05, + title = {Mathematische Methoden in der Kontinuumsmechanik}, + publisher = {Institut f\"{u}r Angewandte Analysis und Numerische Simulation}, + year = {2005}, + author = {A.-M. S{\"{a}}ndig}, + edition = {1}, + key = {Sae05} +} + +@ARTICLE{TENG2000, + author = {H. Teng and T.S. Zhao}, + title = {An extension of Darcy's law to non-Stokes flow in porous media}, + journal = {Chemical engineering science}, + year = {2000}, + volume = {55}, + pages = {2727--2735}, + number = {14} +} + +@ARTICLE{A3:Wen:1996, + author = {Wen, X. H. and G\'{o}mez-Hern\'{a}ndez, J. J.}, + title = {Upscaling hydraulic conductivities in heterogenous media: An overview}, + journal = {Journal of Hydrology}, + year = {1996}, + volume = {183}, + pages = {ix--xxxii} +} + +@BOOK{A3:whitaker:1999, + title = {The Method of Volume Averaging}, + publisher = {Kluwer Academic Publishers}, + year = {1999}, + author = {Whitaker, Stephen}, + volume = {13}, + series = {Theory and Applications of Transport in Porous Media}, + address = {Dordrecht} +} + +@ARTICLE{WHITAKER1985, + author = {S. Whitaker}, + title = {Flow in porous media I: A theoretical derivation of Darcy's law}, + journal = {Transport in Porous Media}, + year = {1985}, + volume = {1}, + pages = {3--25}, + number = {1} +} + +@ARTICLE{A3:Williams:1989, + author = {Williams, J. K.}, + title = {Simple Renormalisation Schemes for Calculating Effective Properties + of Heterogeneous Reservoirs}, + journal = {1st European Conference on the Mathematics of Oil Recovery, Cambridge, + UK, July 1989}, + year = {1989} +} + +@BOOK{GLADROW2000, + title = {Lattice-Gas Cellular Automata and Lattice Boltzmann Models: An Introduction}, + publisher = {Springer}, + year = {2000}, + author = {D. A. Wolf-Gladrow}, + edition = {1} +} + +@MISC{ALBERTA-HP, + title = {The {ALBERTA} Website: \url{http://www.alberta-fem.de/}}, + key = {ALBERTA} +} + +@MISC{ALUGRID-HP, + title = {The {ALUG}rid Website: \url{http://www.mathematik.uni-freiburg.de/IAM/Research/alugrid/}}, + key = {ALU} +} + +@MISC{GIT-HP, + title = {The Website of the Git SCM: \url{http://git-scm.com/}}, + key = {GIT} +} + +@MISC{APACHE-SUBVERSION-HP, + title = {The Apache Subversion Website: \url{http://subversion.apache.org/}}, + key = {APACHE-SUBVERSION} +} + +@MISC{DOXYGEN-HP, + title = {The Doxgen website: \url{http://www.stack.nl/~dimitri/doxygen/}}, + key = {DOXYGEN} +} + +@MISC{DUMUX-HP, + title = {The {D}umux website: \url{http://www.dumux.org/}}, + key = {DUMUX} +} + +@MISC{EWOMS-HP, + title = {The eWoms website: \url{http://opm-project.org/ewoms}}, + key = {EWOMS} +} + +@MISC{OPM-HP, + title = {The website of the Open Porous Media initiative: \url{http://opm-project.org/}}, + key = {OPM} +} + +@MISC{DUNE-BS, + title = {{DUNE} Build System Howto: \url{http://www.dune-project.org/doc/buildsystem/buildsystem.pdf}}, + key = {DUNE-BS} +} + +@MISC{DUNE-DOWNLOAD-SVN, + title = {Download of {DUNE} via SVN: \url{http://www.dune-project.org/downloadsvn.html}}, + key = {DUNE-DOWNLOAD-SVN} +} + +@MISC{DUNE-EXT-LIB, + title = {Use of external libraries in DUNE \url{http://www.dune-project.org/external_libraries/index.html}}, + key = {DUNE-EXTERNAL-LIBRARIES} +} + +@MISC{DUNE-EXT-MOD, + title = {Use of external modules in DUNE \url{http://www.dune-project.org/downloadext.html}}, + key = {DUNE-EXTERNAL-MODULES} +} + +@MISC{DUNE-HP, + title = {The {DUNE} Project: \url{http://www.dune-project.org/}}, + key = {DUNE} +} + +@MISC{DUNE-INST, + title = {Installation notes to {DUNE}: \url{http://www.dune-project.org/doc/installation-notes.html}}, + key = {DUNE-INST} +} + +@MISC{DUNE-MAIN-WIKI, + title = {{DUNE} Main Wiki: \url{http://users.dune-project.org/projects/main-wiki/wiki/}}, + key = {DUNE-MAIN-WIKI} +} + +@MISC{DUNE-USER-WIKI, + title = {{DUNE} User Wiki: \url{http://users.dune-project.org/}}, + key = {DUNE-USER-WIKI} +} + +@MISC{FENICS-HP, + title = {The {FEniCS} Project: \url{http://www.fenicsproject.org/}}, + key = {FENICS} +} + +@MISC{GNU-BS, + title = {wikipedia about GNU build system: \url{http://en.wikipedia.org/wiki/GNU_build_system}}, + key = {WIKIPED-GNU-BS} +} + +@MISC{STL-REF-HP, + title = {A {STL} Reference: \url{http://www.cplusplus.com/reference/stl/}}, + key = {STL} +} + +@MISC{UG-HP, + title = {The {UG} Homepage: \url{http://atlas.gcsc.uni-frankfurt.de/~ug/}}, + key = {UG} +} + +@MISC{WIKIPED-ALIASING, + title = {wikipedia about aliasing data location in memory: \url{http://en.wikipedia.org/wiki/Aliasing\_(computing)}}, + key = {WikipediaAliasing} +} + diff --git a/doc/handbook/ewoms-handbook.tex b/doc/handbook/ewoms-handbook.tex new file mode 100644 index 00000000000..cc61dd55812 --- /dev/null +++ b/doc/handbook/ewoms-handbook.tex @@ -0,0 +1,217 @@ +\documentclass[11pt,a4paper,headinclude,footinclude,DIV16]{scrreprt} +\usepackage[automark]{scrpage2} +\usepackage[ansinew]{inputenc} +\usepackage{amsmath} +\usepackage{amsfonts} +\usepackage{amssymb} +\usepackage{booktabs} +\usepackage{theorem} +\usepackage{color} +\usepackage{listings} + +\definecolor{bashgray}{gray}{0.92} +\definecolor{cppgray}{gray}{0.95} + +\lstset{language=C++, basicstyle=\ttfamily, + keywordstyle=\color{black}\bfseries, tabsize=4, stringstyle=\ttfamily, + extendedchars=true, escapeinside={/*@}{@*/}} + +% for listings of bash code in install.tex +\lstdefinestyle{Bash} + {language=Bash, + backgroundcolor=\color{bashgray}, + basicstyle=\ttfamily\small, + numbers=none, + captionpos=b, + tabsize=4, + breaklines=true, + frame=single, + rulecolor=\color{bashgray}, + framerule=1pt, + framesep=1pt, + rulesep=0pt, + aboveskip=\bigskipamount, + belowskip=\bigskipamount +} +% for listings of eWoms code +\lstdefinestyle{eWomsCode} + {language=C++, + basicstyle=\ttfamily\scriptsize, + backgroundcolor=\color{bashgray}, + rulecolor=\color{bashgray}, + numbers=left, + numberstyle=\tiny, + numbersep=5pt, + breaklines=true +} +\lstset{showstringspaces=false, + breaklines=true} + +\usepackage[ + pdfpagelabels=true, + %draft, + final, + bookmarks=true, + bookmarksnumbered=true, + bookmarksopen=true, + bookmarksopenlevel=1, + breaklinks=true, + colorlinks=true, + hyperindex=true, + linkcolor=black, + citecolor=black, + urlcolor=black, + pdfpagelayout=SinglePage, + %ps2pdf + %dvips + %dvipdf + %pdftex + ]{hyperref} +\usepackage{psfrag} +\usepackage{makeidx} +\usepackage{graphicx} +\usepackage{xspace} +\usepackage{relsize} +\usepackage[htt]{hyphenat} +\usepackage{lscape} +\usepackage{enumerate} +\usepackage{rotating} +\usepackage{subfig} +\usepackage{units} +\usepackage{url} +\usepackage{breakurl} + +\ifpdf +\usepackage{auto-pst-pdf} +\fi +\usepackage{pstricks} + +\usepackage[normalem]{ulem} +\usepackage{tabularx} +\newcommand{\snakeline}{% +% {\uwave{\makebox[\linewidth]{\mbox{}}}} +\uwave{\mbox{}} +} +\usepackage{layout} + +\usepackage[english]{babel} + +\DeclareGraphicsExtensions{.eps, .jpg} + +% Dune occuring in text +\newcommand{\Dune}{{DUNE}\xspace} +% ewoms occuring in text +\newcommand{\Dumux}{\texorpdfstring{Du\-Mu$^\text{x}$\xspace}{DuMuX}} +% eWoms occuring in text +\newcommand{\eWoms}{eWoms\xspace} +% beautify C++ +\DeclareRobustCommand\Cplusplus{\texorpdfstring{C\raisebox{2pt}{{\relsize{-3}++}}\xspace}{C++}} + +\newcommand{\porosity}{\phi} +\newcommand{\saturation}{S} + +\newcommand{\doxyref}[3]{\textnormal{#1}} +\newenvironment{CompactList} +{\begin{list}{}{ + \setlength{\leftmargin}{0.5cm} + \setlength{\itemsep}{0pt} + \setlength{\parsep}{0pt} + \setlength{\topsep}{0pt} + \renewcommand{\makelabel}{\hfill}}} +{\end{list}} +\newenvironment{CompactItemize} +{ + \begin{itemize} + \setlength{\itemsep}{-3pt} + \setlength{\parsep}{0pt} + \setlength{\topsep}{0pt} + \setlength{\partopsep}{0pt} +} +{\end{itemize}} + +\newcommand*\justifyNoHyphen{% + \fontdimen2\font=0.4em% interword space + \fontdimen3\font=0.2em% interword stretch + \fontdimen4\font=0.1em% interword shrink + \fontdimen7\font=0.1em% extra space + \hyphenchar\font=`\-% allowing hyphenation +} +% a new counter +% you can give a label to it and thus reference it +% syntax: \numberThis{printedTextToBeLabeled}{label} +% if you wanted a \newline after a numbered thing, you could just add a empty line after ``\label{#2}'' +\newcounter{thingCounter} +\renewcommand{\thethingCounter}{\arabic{thingCounter}} +\newcommand{\numberThis}[2]{% + \refstepcounter{thingCounter}% + \thethingCounter.\ #1 \label{#2} +} + +%The theorems +\theorembodyfont{\upshape} +\theoremheaderfont{\sffamily\bfseries} +\newtheorem{exc}{Exercise}[chapter] +\newtheorem{example}[exc]{Example} +\newtheorem{rem}[exc]{Remark} +\newtheorem{lst}{Listing} +\newtheorem{warn}[exc]{Warning} +\newtheorem{justCounting}{} + +\DeclareMathOperator{\grad}{grad} +\DeclareMathOperator{\curl}{curl} +\DeclareMathOperator{\Div}{div} + +\pagestyle{scrheadings} + +\title{ +\begin{center} +%\includegraphics[width=0.7\textwidth]{../logo/ewoms_logo_hires_whitebg.eps} +%\\[3cm] +{\Huge The \eWoms Handbook} +\end{center} +} + +\author{} + +\date{\today} + +\publishers{% +{\normalsize \texttt{\url{http://opm-project.org/ewoms}}}\\ +} + +\makeindex + +\begin{document} + +\maketitle + +\begin{abstract} + +\end{abstract} + +\tableofcontents + +\part{Getting Familiar} + +\input{intro} +\input{getting-started} +\input{tutorial} + +\part{Concepts and Software Architecture} + +\input{designpatterns} +\input{propertysystem} +\input{fluidframework} + +\part{Guides} + +\input{install} +\input{structure} +\input{guidelines} +\input{models} +\input{newton-in-a-nutshell} + +\bibliographystyle{plain} +\bibliography{ewoms-handbook} +\printindex +\end{document} diff --git a/doc/handbook/fluidframework.tex b/doc/handbook/fluidframework.tex new file mode 100644 index 00000000000..dac1d1a0bee --- /dev/null +++ b/doc/handbook/fluidframework.tex @@ -0,0 +1,436 @@ +\chapter{The \eWoms Fluid Framework} +\label{sec:fluidframework} + +This chapter discusses the \eWoms fluid framework. \eWoms users who +do not want to write new models and who do not need new fluid +configurations may skip this chapter. + +In the chapter, a high level overview over the the principle concepts +is provided first, then some implementation details follow. + +\section{Overview of the Fluid Framework} + +The \eWoms fluid framework currently features the following concepts +(listed roughly in their order of importance): + +\begin{description} +\item[Fluid state:] Fluid states are responsible for representing the + complete thermodynamic configuration of a system at a given spatial + and temporal position. A fluid state always provides access methods + to \textbf{all} thermodynamic quantities, but the concept of a fluid state does not + mandate what assumptions are made to store these thermodynamic + quantities. What fluid states also do \textbf{not} do is to make sure + that the thermodynamic state which they represent is physically + possible. +\item[Fluid system:] Fluid systems express the thermodynamic \textbf{ + relations}\footnote{Strictly speaking, these relations are + functions, mathematically.} between quantities. Since functions do + not exhibit any internal state, fluid systems are stateless classes, + i.e. all member functions are \texttt{static}. This is a conscious + decision since the thermodynamic state of the system is expressed by + a fluid state! +\item[Parameter cache:] Fluid systems sometimes require + computationally expensive parameters for multiple relations. Such + parameters can be cached using a so-called parameter + cache. Parameter cache objects are specific for each fluid system + but they must provide a common interface to update the internal + parameters depending on the quantities which changed since the last + update. +\item[Constraint solver:] Constraint solvers are auxiliary tools to + make sure that a fluid state is consistent with some thermodynamic + constraints. All constraint solvers specify a well defined set of + input variables and make sure that the resulting fluid state is + consistent with a given set of thermodynamic equations. See section + \ref{sec:constraint_solvers} for a detailed description of the + constraint solvers which are currently available in \eWoms. +\item[Equation of state:] Equations of state (EOS) are auxiliary + classes which provide relations between a fluid phase's temperature, + pressure, composition and density. Since these classes are only used + internally in fluid systems, their programming interface is + currently ad-hoc. +\item[Component:] Components are fluid systems which provide the + thermodynamic relations for the liquid and gas phase of a single + chemical species or a fixed mixture of species. Their main purpose + is to provide a convenient way to access these quantities from + full-fledged fluid systems. Components are not supposed to be used + by models directly. +\item[Binary coefficient:] Binary coefficients describe the relations + of a mixture of two components. Typical binary coefficients are + \textsc{Henry} coefficients or binary molecular diffusion + coefficients. So far, the programming interface for accessing binary + coefficients has not been standardized in \eWoms. +\end{description} + +\section{Fluid States} + +Fluid state objects express the complete thermodynamic state of a +system at a given spatial and temporal position. + +\subsection{Exported Constants} + +\textbf{All} fluid states \textbf{must} export the following constants: +\begin{description} +\item[numPhases:] The number of fluid phases considered. +\item[numComponents:] The number of considered chemical + species or pseudo-species. +\end{description} + +\subsection{Accessible Thermodynamic Quantities} + +Also, \textbf{all} fluid states \textbf{must} provide the following methods: +\begin{description} +\item[temperature():] The absolute temperature $T_\alpha$ of + a fluid phase $\alpha$. +\item[pressure():] The absolute pressure $p_\alpha$ of a + fluid phase $\alpha$. +\item[saturation():] The saturation $S_\alpha$ of a fluid phase + $\alpha$. The saturation is defined as the pore space occupied by + the fluid divided by the total pore space: + \[ + \saturation_\alpha := \frac{\porosity \mathcal{V}_\alpha}{\porosity \mathcal{V}} + \] +\item[moleFraction():] Returns the molar fraction $x^\kappa_\alpha$ of + the component $\kappa$ in fluid phase $\alpha$. The molar fraction + $x^\kappa_\alpha$ is defined as the ratio of the number of molecules + of component $\kappa$ and the total number of molecules of the phase + $\alpha$. +\item[massFraction():] Returns the mass fraction $X^\kappa_\alpha$ of + component $\kappa$ in fluid phase $\alpha$. The mass fraction + $X^\kappa_\alpha$ is defined as the weight of all molecules of a + component divided by the total mass of the fluid phase. It is + related with the component's mole fraction by means of the relation + \[ + X^\kappa_\alpha = x^\kappa_\alpha \frac{M^\kappa}{\overline M_\alpha}\;, + \] + where $M^\kappa$ is the molar mass of component $\kappa$ and + $\overline M_\alpha$ is the mean molar mass of a molecule of phase + $\alpha$. +\item[averageMolarMass():] Returns $\overline M_\alpha$, the mean + molar mass of a molecule of phase $\alpha$. For a mixture of $N > 0$ + components, $\overline M_\alpha$ is defined as + \[ + \overline M_\alpha = \sum_{\kappa=1}^{N} x^\kappa_\alpha M^\kappa + \] +\item[density():] Returns the density $\rho_\alpha$ of the fluid phase + $\alpha$. +\item[molarDensity():] Returns the molar density $\rho_{mol,\alpha}$ + of a fluid phase $\alpha$. The molar density is defined by the mass + density $\rho_\alpha$ and the mean molar mass $\overline M_\alpha$: + \[ + \rho_{mol,\alpha} = \frac{\rho_\alpha}{\overline M_\alpha} \;. + \] +\item[molarVolume():] Returns the molar volume $v_{mol,\alpha}$ of a + fluid phase $\alpha$. This quantity is the inverse of the molar + density. +\item[molarity():] Returns the molar concentration $c^\kappa_\alpha$ + of component $\kappa$ in fluid phase $\alpha$. +\item[fugacity():] Returns the fugacity $f^\kappa_\alpha$ of component + $\kappa$ in fluid phase $\alpha$. The fugacity is defined as + \[ + f_\alpha^\kappa := \Phi^\kappa_\alpha x^\kappa_\alpha p_\alpha \;, + \] + where $\Phi^\kappa_\alpha$ is the {\em fugacity + coefficient}~\cite{reid1987}. The physical meaning of fugacity + becomes clear from the equation + \[ + f_\alpha^\kappa = f_\alpha^{\kappa,0} \exp\left\{\frac{\zeta^\kappa_\alpha - \zeta^{\kappa,0}_\alpha}{R T_\alpha} \right\} \;, + \] + where $\zeta^\kappa_\alpha$ represents the $\kappa$'s chemical + potential in phase $\alpha$, $R$ stands for the ideal gas constant, + $\zeta^{\kappa,0}_\alpha$ is the chemical potential in a reference + state, $f_\alpha^{\kappa,0}$ is the fugacity in the reference state + and $T_\alpha$ for the absolute temperature of phase $\alpha$. The + fugacity in the reference state $f_\alpha^{\kappa,0}$ is in princle + arbitrary, but in the context of the \eWoms fluid framework, we + assume that it is the same for all fluid phases, i.e. + $f_\alpha^{\kappa,0} = f_\beta^{\kappa,0}$. + + Assuming thermal equilibrium, there is a one-to-one mapping between + a component's chemical potential $\zeta^\kappa_\alpha$ and its + fugacity $f^\kappa_\alpha$. With the above assumptions, chemical + equilibrium can thus be expressed by + \[ + f^\kappa := f^\kappa_\alpha = f^\kappa_\beta\quad\forall \alpha, \beta + \] +\item[fugacityCoefficient():] Returns the fugacity coefficient + $\Phi^\kappa_\alpha$ of component $\kappa$ in fluid phase $\alpha$. +\item[enthalpy():] Returns specific enthalpy $h_\alpha$ of a fluid + phase $\alpha$. +\item[internalEnergy():] Returns specific internal energy $u_\alpha$ + of a fluid phase $\alpha$. The specific internal energy is defined + by the relation + \[ + u_\alpha = h_\alpha - \frac{p_\alpha}{\rho_\alpha} + \] +\item[viscosity():] Returns the dynamic viscosity + $\mu_\alpha$ of fluid phase $\alpha$. +\end{description} + +\subsection{Available Fluid States} +Currently, the following fluid states are provided by \eWoms: +\begin{description} +\item[NonEquilibriumFluidState:] This is the most general fluid state + supplied. It does not assume thermodynamic equilibrium and thus + stores all phase compositions (using mole fractions), fugacity + coefficients, phase temperatures, phase pressures, saturations and + specific enthalpies. +\item[CompositionalFluidState:] This fluid state is very similar to + the \texttt{Non\-Equilibrium\-Fluid\-State} with the difference that + the \texttt{Compositional\-Fluid\-State} assumes thermodynamic + equilibrium. In the context of multi-phase flow in porous media, + this means that only a single temperature needs to be stored. +\item[ImmisicibleFluidState:] This fluid state assumes that the fluid + phases are immiscible, which implies that the phase compositions and + the fugacity coefficients do not need to be stored explicitly. +\item[PressureOverlayFluidState:] This is a so-called {\em overlay} + fluid state. It allows to set the pressure of all fluid phases but + forwards everything else to another fluid state. +\item[SaturationOverlayFluidState:] This fluid state is like the + \texttt{PressureOverlayFluidState}, except that the phase + saturations are settable instead of the phase pressures. +\item[TempeatureOverlayFluidState:] This fluid state is like the + \texttt{PressureOverlayFluidState}, except that the temperature is + settable instead of the phase pressures. Note that this overlay + state assumes thermal equilibrium regardless of underlying fluid + state. +\item[CompositionOverlayFluidState:] This fluid state is like the + \texttt{PressureOverlayFluidState}, except that the phase + composition is settable (in terms of mole fractions) instead of the + phase pressures. +\end{description} + +\section{Fluid Systems} + +Fluid systems express the thermodynamic relations between the +quantities of a fluid state. + +\subsection{Parameter Caches} + +All fluid systems must export a type for their \texttt{ParameterCache} +objects. Parameter caches can be used to cache parameter that are +expensive to compute and are required in multiple thermodynamic +relations. For fluid systems which do need to cache parameters, +\eWoms provides a \texttt{NullParameterCache} class. + +The actual quantities stored by parameter cache objects are specific +to the fluid system and no assumptions on what they provide should be +made outside of their fluid system. Parameter cache objects provide a +well-defined set of methods to make them coherent with a given fluid +state, though. These update are: +\begin{description} +\item[updateAll(fluidState, except):] Update all cached quantities for + all phases. The \texttt{except} argument contains a bit field of the + quantities which have not been modified since the last call to a + \texttt{update()} method. +\item[updateAllPresures(fluidState):] Update all cached quantities + which depend on the pressure of any fluid phase. +\item[updateAllTemperatures(fluidState):] Update all cached quantities + which depend on temperature of any fluid phase. +\item[updatePhase(fluidState, phaseIdx, except):] Update all cached + quantities for a given phase. The quantities specified by the + \texttt{except} bit field have not been modified since the last call + to an \texttt{update()} method. +\item[updateTemperature(fluidState, phaseIdx):] Update all cached + quantities which depend on the temperature of a given phase. +\item[updatePressure(fluidState, phaseIdx):] Update all cached + quantities which depend on the pressure of a given phase. +\item[updateComposition(fluidState, phaseIdx):] Update all cached + quantities which depend on the composition of a given phase. +\item[updateSingleMoleFraction(fluidState, phaseIdx, compIdx):] Update + all cached quantities which depend on the value of the mole fraction + of a component in a phase. +\end{description} +Note, that the parameter cache interface only guarantees that if a +more specialized \texttt{update()} method is called, it is not slower +than the next more-general method (e.g. calling +\texttt{updateSingleMoleFraction()} may be as expensive as +\texttt{updateAll()}). It is thus advisable to rather use a more +general \texttt{update()} method once than multiple calls to +specialized \texttt{update()} methods. + +To make usage of parameter caches easier for the case where all cached +quantities ought to be re-calculated if a quantity of a phase was +changed, it is possible to only define the \texttt{updatePhase()} +method and derive the parameter cache from +\texttt{Ewoms::ParameterCacheBase}. + +\subsection{Exported Constants and Capabilities} + +Besides providing the type of their \texttt{ParameterCache} objects, +fluid systems need to export the following constants and auxiliary +methods: +\begin{description} +\item[numPhases:] The number of considered fluid phases. +\item[numComponents:] The number of considered chemical (pseudo-) + species. +\item[init():] Initialize the fluid system. This is usually used to + tabulate some quantities +\item[phaseName():] Given the index of a fluid phase, return its name + as human-readable string. +\item[componentName():] Given the index of a component, return its + name as human-readable string. +\item[isLiquid():] Return whether the phase is a liquid, given the + index of a phase. +\item[isIdealMixture():] Return whether the phase is an ideal mixture, + given the phase index. In the context of the \eWoms fluid + framework a phase $\alpha$ is an ideal mixture if, and only if, all + its fugacity coefficients $\Phi^\kappa_\alpha$ do not depend on the + phase composition. (Although they might very well depend on + temperature and pressure.) +\item[isIdealGas():] Return whether a phase $\alpha$ is an ideal gas, + i.e. it adheres to the relation + \[ + p_\alpha v_{mol,\alpha} = R T_\alpha \;, + \] + with $R$ being the ideal gas constant. +\item[isCompressible():] Return whether a phase $\alpha$ is + compressible, i.e. its density depends on pressure $p_\alpha$. +\item[molarMass():] Given a component index, return the molar mass of + the corresponding component. +\end{description} + +\subsection{Thermodynamic Relations} + +Fluid systems have been explicitly designed to provide as few +thermodynamic relations as possible. A full-fledged fluid system thus +only needs to provide the following thermodynamic relations: +\begin{description} +\item[density():] Given a fluid state, an up-to-date parameter cache + and a phase index, return the mass density $\rho_\alpha$ of the + phase. +\item[fugacityCoefficient():] Given a fluid state, an up-to-date + parameter cache as well as a phase and a component index, return the + fugacity coefficient $\Phi^\kappa_\alpha$ of a the component for the + phase. +\item[viscosity():] Given a fluid state, an up-to-date parameter cache + and a phase index, return the dynamic viscosity $\mu_\alpha$ of the + phase. +\item[diffusionCoefficient():] Given a fluid state, an up-to-date + parameter cache, a phase and a component index, return the calculate + the molecular diffusion coefficient for the component in the fluid + phase. + + Molecular diffusion of a component $\kappa$ in phase $\alpha$ is + caused by a gradient of the chemical potential. Using some + simplifying assumptions~\cite{reid1987}, they can be also expressed + in terms of mole fraction gradients, i.e. the equation used for mass + fluxes due to molecular diffusion is + \[ + J^\kappa_\alpha = - \rho_{mol,\alpha} D^\kappa_\alpha\ \mathbf{grad} x^\kappa_\alpha\;, + \] + where $\rho_{mol,\alpha}$ is the molar density of phase $\alpha$, + $x^\kappa_\alpha$ is the mole fraction of component $\kappa$ in + phase $\alpha$, $D^\kappa_\alpha$ is the diffusion coefficient and + $J^\kappa_\alpha$ is the diffusive flux. +\item[enthalpy():] Given a fluid state, an up-to-date parameter cache + and a phase index, this method calulates the specific enthalpy + $h_\alpha$ of the phase. +\item[thermalConductivity:] Given a fluid state, an up-to-date + parameter cache and a phase index, this method returns the thermal + conductivity $\lambda_\alpha$ of the fluid phase. The thermal + conductivity is defined by means of the relation + \[ + \dot Q = \lambda_\alpha \mathbf{grad}\;T_\alpha \;, + \] + where $\dot Q$ is the heat flux caused by the temperature gradient + $\mathbf{grad}\;T_\alpha$. +\item[heatCapacity():] Given a fluid state, an up-to-date parameter + cache and a phase index, this method computes the isobaric heat + capacity $c_{p,\alpha}$ of the fluid phase. The isobaric heat + capacity is defined as the partial derivative of the specific + enthalpy $h_\alpha$ to the fluid pressure: + \[ + c_{p,\alpha} = \frac{\partial h_\alpha}{\partial p_\alpha} + \] + % TODO: remove the heatCapacity() method?? +\end{description} + +Fluid systems may chose not to implement some of these methods and +throw an exception of type \lstinline{Dune::NotImplemented} instead. Obviously, +such fluid systems cannot be used for models that depend on those +methods. + +\section{Constraint Solvers} +\label{sec:constraint_solvers} + +Constraint solvers connect the thermodynamic relations expressed by +fluid systems with the thermodynamic quantities stored by fluid +states. Using them is not mandatory for models, but given the fact +that some thermodynamic constraints can be quite complex to solve, +sharing this code between models makes sense. Currently, \eWoms +provides the following constraint solvers: +\begin{description} +\item[CompositionFromFugacities:] This constraint solver takes all + component fugacities, the temperature and pressure of a phase as + input and calculates the composition of the fluid phase. This means + that the thermodynamic constraints used by this solver are + \[ + f^\kappa = \Phi^\kappa_\alpha(\{x^\beta_\alpha \}, T_\alpha, p_\alpha) p_\alpha x^\kappa_\alpha\;, + \] + where ${f^\kappa}$, $T_\alpha$ and $p_\alpha$ are fixed values. +\item[ComputeFromReferencePhase:] This solver brings all + fluid phases into thermodynamic equilibrium with a reference phase + $\beta$, assuming that all phase temperatures and saturations have + already been set. The constraints used by this solver are thus + \begin{eqnarray*} + f^\kappa_\beta = f^\kappa_\alpha = \Phi^\kappa_\alpha(\{x^\beta_\alpha \}, T_\alpha, p_\alpha) p_\alpha x^\kappa_\alpha\;, \\ + p_\alpha = p_\beta + p_{c\beta\alpha} \;, + \end{eqnarray*} + where $p_{c\beta\alpha}$ is the capillary pressure between the + fluid phases $\beta$ and $\alpha$. +\item[NcpFlash:] This is a so-called flash solver. A flash solver + takes the total mass of all components per volume unit and the phase + temperatures as input and calculates all phase pressures, + saturations and compositions. This flash solver works for an + arbitrary number of phases $M > 0$ and components $N \geq M - 1$. In + this case, the unknown quantities are the following: + \begin{itemize} + \item $M$ pressures $p_\alpha$ + \item $M$ saturations $\saturation_\alpha$ + \item $M\cdot N$ mole fractions $x^\kappa_\alpha$ + \end{itemize} + This sums up to $M\cdot(N + 2)$. The equations side of things + provides: + \begin{itemize} + \item $(M - 1)\cdot N$ equations stemming from the fact that the + fugacity of any component is the same in all phases, i.e. + \[ + f^\kappa_\alpha = f^\kappa_\beta + \] + holds for all phases $\alpha, \beta$ and all components $\kappa$. + \item $1$ equation comes from the fact that the whole pore space is + filled by some fluid, i.e. + \[ + \sum_{\alpha=1}^M \saturation_\alpha = 1 + \] + \item $M - 1$ constraints are given by the capillary pressures: + \[ + p_\beta = p_\alpha + p_{c\beta\alpha} \;, + \] + for all phases $\alpha$, $\beta$ + \item $N$ constraints come the fact that the total mass of each + component is given: + \[ + c^\kappa_{tot} = \sum_{\alpha=1}^M x_\alpha^\kappa\;\rho_{mol,\alpha} = const + \] + \item And finally $M$ model assumptions are used. This solver uses + the NCP constraints proposed in~\cite{LHHW2011}: + \[ + 0 = \mathrm{min}\{\saturation_\alpha, 1 - \sum_{\kappa=1}^N x_\alpha^\kappa\} + \] +\end{itemize} +The number of equations also sums up to $M\cdot(N + 2)$. Thus, the +system of equations is closed. +\item[ImmiscibleFlash:] This is a flash solver assuming immiscibility + of the phases. It is similar to the \texttt{NcpFlash} solver but a + lot simpler. +\item[MiscibleMultiphaseComposition:] This solver calculates the + composition of all phases provided that each of the phases is + potentially present. Currently, this solver does not support + non-ideal mixtures. +\end{description} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/getting-started.tex b/doc/handbook/getting-started.tex new file mode 100644 index 00000000000..305d2dd1330 --- /dev/null +++ b/doc/handbook/getting-started.tex @@ -0,0 +1,21 @@ +\chapter{Set-up and basic workflows} + +This chapter is aimed at setting up a development environment for +\eWoms and to equip you with a rough understanding of the basic +workflows used for developing the software. We will first have a +brief look at the installation procedure; After that, we will run a +sample simulation and briefly discuss how to visualize its results. + +Be aware, that this is only a very streamlined version of the \eWoms +development workflow, so make yourself confident with the tools +introduced in this section before you delve into the \Cplusplus code +in the next chapter. + +\input{quick-install} +\input{quickstart-guide} +\input{parameters} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/guidelines.tex b/doc/handbook/guidelines.tex new file mode 100644 index 00000000000..91f5f037284 --- /dev/null +++ b/doc/handbook/guidelines.tex @@ -0,0 +1,90 @@ +\chapter{Coding Style Guidelines} +\label{guidelines} + +An important characteristic of source code is that it is written only +once but usually it is read many times (e.g. when debugging things, +adding features, etc.). For this reason, good programming frameworks +always aim to be as readable as possible, even if comes with higher +effort to write them in the first place. The remainder of this section +is very similar to the \Dune coding guidelines found at the \Dune +website~\cite{DUNE-HP}. These guidelines are also strongly recommended +for working with \eWoms. Some of these coding style rules may seem +like splitting hairs to you, but they do make it much easier for +everybody to work on code that has not been written by oneself. + +\begin{itemize} +\item Naming: +\begin{itemize} +\item Comments: They are helpful! Use comments extensively to explain + what your code does, but please refrain from adding trivial + comments. Trivial comments are, for example comments that only + parrot the source code (``this method sets a'' as the comment for a + method called \texttt{setA()}), or comments that explain language + features (``if b is true then a is c else d'' for the statement + \texttt{a = b?c:d}). +\item All comments, documentation and variable or type names are + exclusivly using the English language. +\item Variables: Names for variables should only feature letters and + digits. The first letter should be lower case. If your variable + names consists of several words, then the first letter of each new + word should be capitalized. +\item Variables and methods should be named as self-explanatory as + possible. Especially, this means abbreviations should be avoided + (for example, use 'saturation' in stead of 'S') +\item Method parameters which are not self-explanatory should always + have a meaningful comment a at calling sites. Example: +\begin{lstlisting}[style=eWomsCode] + localResidual.eval(/*includeBoundaries=*/true); +\end{lstlisting} +\item Private attributes: Names of private attributes should end with + an underscore and are supposed to be the only variables that contain + underscores. +\item Typenames: For typenames (classes, structures, typedefs, etc), + the same rules as for variables apply. The only difference is that + the first letter should be a capital one. +\item Macros: The use of preprocessor macros is strongly + discouraged. If you have to use them for whatever reason, please use + capital letters only. +\item Guardian macros: Every header file traditionally begins with the + definition of a preprocessor macro that is used to make sure that + the header file is only included once. If your header file is + called 'myheaderfile.hh', this constant should be named + \texttt{EWOMS\_MYHEADERFILE\_HH}. +\item Files: File names should exclusively consist of lower case + letters. Header files get the suffix .hh, implementation files the + suffix .cc +\end{itemize} + +\item Documentation: \eWoms, as any software project of similar + complexity, will stand and fall with the quality of its + documentation. Therefore, it is of paramount importance that you + document everything you do well! We use the doxygen system to + extract easily-readable documentation from the source code. Please + use its syntax everywhere. In particular, please comment + \textbf{all} +\begin{itemize} +\item Method parameters +\item Template parameters +\item Return values +\item Exceptions thrown by a method + \end{itemize} + Since we all know that writing documentation is not well-liked and is + frequently defered to some vague 'next week', we herewith proclaim + the Doc-Me Dogma . It goes like this: Whatever you do, and in + whatever hurry you happen to be, please document everything at least + with a {\verb /** $\backslash$todo Please doc me! */}. That way at + least the absence of documentation is documented, and it is easier to + get rid of it systematically. +\item Exceptions: The use of exceptions for error handling is + encouraged. Until further notice, all exceptions thrown are derived + from \texttt{Dune::Exception}. +\item Debugging code: Code which is only there to ease the debugging + process should always be switched off if the preprocessor macro + NDEBUG is defined. In particular, all assertations are automatically + removed. Use those assertations freely! +\end{itemize} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/install.tex b/doc/handbook/install.tex new file mode 100644 index 00000000000..ad8966f9525 --- /dev/null +++ b/doc/handbook/install.tex @@ -0,0 +1,379 @@ +\chapter{Detailed Installation Instructions} +\label{install} + +\section{Preliminary remarks} + +In this section about the installation of \eWoms it is assumed that +you work on a Unix or Linux compatible operating system and that you +are familiar with the use of a command line shell. Installation means +that you unpack \Dune together with \eWoms in a certain directory. +Then, you compile it in that directory tree in which you do the +further work, too. You also should know how to install new software +packages or you should have a person on hand who assist you with +that. In section \ref{sec:prerequisites} we list some prerequisites +for running \Dune and \eWoms. Please make sure to fulfill them before +you proceed. In addition, section \ref{sec:external-modules-libraries} +provides some details on optional libraries and modules. + +In a technical sense \eWoms is a module of \Dune. Thus, the +installation procedure of \eWoms is the same as that of \Dune (besides +using different locations to retieve the source code). Details +regarding the installation of \Dune are provided on the \Dune +website~\cite{DUNE-INST}. If you are interested in more details about +the build system that is used, they can be found in the {\Dune} +build system howto \cite{DUNE-BS}. + +All \Dune modules, including \eWoms, get extracted into a common +directory. In the following, we refer to that directory as {\Dune} +base directory or, in short, as {\Dune}-base. If it is used as +directory path of a shell command it is typed as +\texttt{dune-base}. For the actual location of the {\Dune} base +directory on your file system, you may chose any valid directory name for +which you have write permissions. + +Source code files for each \Dune module are contained in their own +subdirectory within {\Dune}-base. We name this directory of a certain +module \emph{module base directory} or \texttt{module-base} +if it is a directory path, e.\,g. for the module \texttt{ewoms} these +names are \emph{ewoms base directory} respective +\texttt{ewoms-base}. The real directory names for the +modules can be chosen arbitrarily. In this manual they are the same as +the module name or the module name extended by a version number +suffix. The name of each \Dune module is defined in the file +\texttt{dune.module}, which is in the base directory of the respective +module. This should not be changed by the user. It is allowed to have +own files and directories in \Dune-base, which are not related to +\Dune's needs. + +After installing source code for all relevant \Dune modules including +\eWoms, \Dune is being built by the shell-command \texttt{dunecontrol} +which is part of the \Dune build system. The \Dune build system is a +front-end of to the GNU build system adapted to the needs of \Dune. + +\section{Prerequisites} \label{sec:prerequisites} + +The GNU compiler collection with \Cplusplus support (\texttt{g++}) and +the tools of the GNU build system \cite{GNU-BS}, also known as GNU +autotools (\texttt{autoconf}, \texttt{automake}, \texttt{autogen}, +\texttt{libtool}), as well as the GNU variant of \texttt{make} called +gmake must be available in a recent enough version. For example Ubuntu +Linux provides these tools by means of the packages \texttt{autoconf}, +\texttt{automake}, \texttt{libtool} and the package +\texttt{build-essential} includes the \Cplusplus compiler +\texttt{g++} and \texttt{make}. + +At the time of writing this manual, the minumum version required for +\texttt{g++} is 4.4.0, \texttt{automake} the documentation by setting the switch +\texttt{--disable-documentation} in the \texttt{CONFIGURE\_FLAGS} of +the building options (see Chapter \ref{buildIt}). Additional parts of +documentation are contained within the source code files as specially +formatted comments. Extracting them can be done using the tool +\texttt{doxygen} (version $\geqslant$ 1.8.2 works). See for this +optional step section \ref{sec:build-doxy-doc}. + +Depending on whether you are going to use external libraries and +modules for additional \Dune features, additional software packages +may be required. Some hints on that are given in Section +\ref{sec:external-modules-libraries}. + +To access the git or subversion software repositories, git and +subversion clients are required. We recommend to use the git +command-line client \texttt{git} version 1.7.12 or newer with the +subversion integration plugin \texttt{git-svn} enabled~\cite{GIT-HP}. + +\begin{table} +\centering +\caption{Ubuntu package names for Ubuntu 12.04} +\begin{tabular}{llll} +\toprule +\textbf{purpose} & \textbf{package names} \\ +\midrule +general: & git & git-svn & libtool \\ +& automake & build-essential & libboost-all-dev \\ +& texlive-latex-base & doxygen & csh\\ +& gfortran & \\ +\midrule +for alberta: & freeglut3-dev & \\ +\midrule +for parallel use: & openmpi-common & mpi-default-bin & mpi-default-dev \\ +\midrule +for parallel UG: & flex & bison & \\ +\midrule +for parallel alberta: & libblas-dev &\\ +\midrule +for debugging: & valgrind &\\ +\bottomrule +\end{tabular} +\label{tbl:ubuntu-pkg} +\end{table} + +\begin{table} +\centering +\caption{Package names for openSUSE 12.2. For this distribution, the {\em science} package repository needs to be added. The science repository is available from \url{http://download.opensuse.org/repositories/science/openSUSE_12.2/}. } +\begin{tabular}{llll} +\toprule +\textbf{purpose} & \textbf{package names} \\ +\midrule +git & git-svn & dune-istl-devel & dune-grid-devel \\ +dune-localfunctions-devel & \\ +\bottomrule +\end{tabular} +\label{tbl:opensuse-pkg} +\end{table} + +\section{Obtaining source code for \Dune and \eWoms} + +As stated above, the \eWoms is based on the \Dune release 2.2, +comprising the core modules \texttt{dune-common}, +\texttt{dune-geometry}, \texttt{dune-grid}, \texttt{dune-istl} and +\texttt{dune-localfunctions}. For working with \eWoms, these modules +are required. + +Two possibilities exist to get the source code of \Dune and \eWoms. +Firstly, released versions \Dune and \eWoms can be downloaded as tar +files from the \Dune and \eWoms websites. They have to be extracted as +described in the next paragraph. Secondly, the most recent source +code can be obtained by directly downloading it from its respective +source-code repository. This method is described in the subsequent +section. + +\paragraph{Obtaining the software by installing tar files} + +The slightly old-fashionedly named tape-archive-file, shortly named +tar file or tarball, is a common file format for distributing +collections of files contained within these archives. The extraction +from the tar files is done as follows: Download the tarballs from the +respective \Dune~\cite{DUNE-HP} (version 2.2.0) and +\eWoms~\cite{EWOMS-HP} websites. Then, create the {\Dune} base +directory, named \texttt{~/src} in the example below. Then, extract the +content of the tar files, e.\,g. with the command-line program +\texttt{tar}. This can be achieved by the following shell +commands. Replace \texttt{path\_to\_tarball} with the directory name +where the downloaded files are actually located. After extraction, +the actual name of the \emph{ewoms base directory} is +\texttt{ewoms-2.2}. + +\begin{lstlisting}[style=Bash] +$ mkdir ~/src +$ cd ~/src +$ tar xzvf path_to_tarball_of/dune-common-2.2.0.tar.gz +$ tar xzvf path_to_tarball_of/dune-grid-2.2.0.tar.gz +$ tar xzvf path_to_tarball_of/dune-geometry-2.2.0.tar.gz +$ tar xzvf path_to_tarball_of/dune-istl-2.2.0.tar.gz +$ tar xzvf path_to_tarball_of/dune-localfunctions-2.2.0.tar.gz +$ tar xzvf path_to_tarball_of/ewoms-2.2.0.tar.gz +\end{lstlisting} + +\paragraph{Obtaining \Dune and \eWoms from software repositories} + +Direct access to a software repositories for downloading code can be +convenient to always follow the changes of done to the software +project. Usually, source code repositories are structured in +branches. One of these branches is commonly used to include the latest +features of the software, and there is normally one branch for each +release which only receives fixes for bug which have been discovered +after the software was released. The following text describes how to +retrieve the source code of \Dune and \eWoms from such release +branches. + +The \Dune project uses Apache Subversion~\cite{APACHE-SUBVERSION-HP} +to manage its software repositories, while \eWoms -- being part of the +open porous media project -- opted to use the git source code +management system~\cite{GIT-HP}. Fortunately, git ships with a +subversion plugin, so all modules can be managed using git. + +In the technical language of Apache Subversion \emph{cloning a certain + software repository} means nothing more then fetching a local copy +of the software repository and placing it on the local file system. +In addition to the software some more files for the use of the +software revision control system itself are created. They are kept in +a directory named \texttt{.git} at the base directory of the cloned +software repository. + +The installation procedure is structured as follows: Create a {\Dune} base +directory (named \texttt{~/src} in the lines below). Then, enter the +previously created directory and check out the desired modules. As +you see below, the check-out uses two different servers for getting +the sources, one for \Dune and one for \eWoms. The \Dune modules of +the stable 2.2 release branch are cloned similarly as described on the +\Dune website~\cite{DUNE-DOWNLOAD-SVN}: + +\begin{lstlisting}[style=Bash] +mkdir ~/src +cd ~/src +for DUNE_MODULE in common geometry grid istl localfunctions; do + git svn clone \ + https://svn.dune-project.org/svn/dune-$DUNE_MODULE/branches/release-2.2 \ + dune-$DUNE_MODULE +done +\end{lstlisting} + +The newest and maybe unstable developments are also provided in these +repositories in a folder called \emph{trunk}. Please check the \Dune +website \cite{DUNE-DOWNLOAD-SVN} for further information. However, the +current \eWoms release is based on the stable 2.2 \Dune release and it +might misbehave using the the newest version of \Dune. + +The \eWoms module is checked out as described below (see also the +\eWoms website \cite{EWOMS-HP}). Its source tree has to be created in +the \Dune-base directory, where the \Dune modules have also been +cloned into. Subsequently, the next command is executed there, +too. The directory which holds the \eWoms module is called +\texttt{ewoms} here. + +\begin{lstlisting}[style=Bash] +cd ~/src +git clone git://github.com/OPM/ewoms.git ewoms +\end{lstlisting} + +\section{Building the doxygen documentation} +\label{sec:build-doxy-doc} + +Doxygen documentation is done by especially formatted comments +integrated in the source code, which can get extracted by the program +\texttt{doxygen}. Beside extracting these comments, \texttt{doxygen} +builds up a web-browsable code structure documentation like class +hierarchy of code displayed as graphs, see \cite{DOXYGEN-HP}. + +Building the doxygen documentation of a module is done as follows, +provided the program \texttt{doxygen} is installed: Set in building +options the \texttt{--enable-doxygen} switch. This is either +accomplished by adding it in \texttt{dunecontrol} options-file to +\texttt{CONFIGURE\_FLAGS}, or by adding it to \texttt{dunecontrol}'s +command-line-argument \texttt{--configure-opts}. After running +\texttt{dunecontrol} enter in module's base directory the subdirectory +\texttt{doc/doxygen}. You then run the command \texttt{doxygen} +within that directory. Point your web browser to the file +\texttt{module-base-directory/doc/doxygen/html/index.html} to read the +generated documentation. For all \Dune-modules that described here, +the doxygen documentation can be generated analogously. + +\begin{lstlisting}[style=Bash] +cd ~/src/ewoms/doc/doxygen +doxygen +firefox html/index.html +\end{lstlisting} + +\section{Building the \eWoms handbook} + +If the \texttt{--enable-documentation} switch has been set in the configure flags of +\texttt{dunecontrol}, watch for a summary line of the build script that reads +\begin{lstlisting}[style=Bash] +Build eWoms handbook....: yes +\end{lstlisting} + +If this is the case, the handbook should be automatically build by +\texttt{dunecontrol} and can be found at +\texttt{\$EWOMS\_BASE/doc/handbook/ewoms-handbook.pdf}. + +If the summary line says that the handbook is not going to be build, +then you usually have to install additional \LaTeX packages. + +\section{External libraries and modules} +\label{sec:external-modules-libraries} + +The libraries described below provide additional functionality but are +not generally required to use \eWoms. If you are going to use an +external library, also check the information provided on the \Dune +website~\cite{DUNE-EXT-LIB} for additional hints. If you are going to +use an external \Dune module, the website listing external +modules~\cite{DUNE-EXT-MOD} can also be helpful. + +Some external libraries have additional dependencies which can also be +used by \Dune. Also, some libraries, such as BLAS or MPI might have +multiple versions installed on the system. To avoid problems, you +should make sure that all external libraries use the same dependencies +as \Dune. + +In the following list, you can find some external modules and external +libraries, and some more libraries and tools which are prerequisites +for their use. + +\begin{itemize} +\item \textbf{ALBERTA}: External library which can be used as an + additional grid manager. ALBERTA stands for ``\textbf{A}daptive multi \textbf{L}evel finite element toolbox + using \textbf{B}isectioning refinement and \textbf{E}rror control by \textbf{R}esidual + \textbf{T}echniques for scientific \textbf{A}pplications''. Building it requires a + Fortran compiler, for example \texttt{gfortran}. It can be downloaded at: + \texttt{\url{http://www.alberta-fem.de}}. + +\item \textbf{ALUGrid}: External library for use as grid. ALUGrid + requires by a \Cplusplus compiler like \texttt{g++} to be build. If + you want to build a parallel version, you will need an MPI library + like, for example \texttt{openmpi}, installed on your system. The + parallel version needs also a graph partitioner, such as + \texttt{METIS}. + + Download: + \texttt{\url{http://aam.mathematik.uni-freiburg.de/IAM/Research/alugrid}} + +\item \textbf{SuperLU}: External library for solving linear + equations. SuperLU is a general purpose library for the direct + solution of large, sparse, non-symmetric systems of linear + equations. + + Download: + \texttt{\url{http://crd.lbl.gov/~xiaoye/SuperLU}}. + +\item \textbf{UG}: External library which can be used as an additional + grid manager. UG is a toolbox for \textbf{U}nstructured + \textbf{G}rids: For \eWoms it has to be build by GNU buildsystem and + a \Cplusplus compiler. That's why \Dune specific patches need + applied before use. Building it makes use of the tools + \texttt{lex}/\texttt{yacc} or the GNU variants + \texttt{flex}/\texttt{bison}. + + Website: + \texttt{\url{http://atlas.gcsc.uni-frankfurt.de/~ug/}}\\ + Further information: + \texttt{\url{http://www.dune-project.org/external_libraries/install_ug.html}}\\ + +\end{itemize} + +The following are dependencies of some of the external libraries. You +will need them depending on which modules of \Dune and which external +libraries you use. + +\begin{itemize} +\item \textbf{MPI}: The parallel version of \Dune and also some of the + external dependencies need MPI when they are going to be built for + parallel computing. \texttt{Openmpi} version $\geqslant$ 1.4.2 and + \texttt{MPICH} in a recent version have been reported to work. + +\item \textbf{lex/yacc} or \textbf{flex/bison}: These are quite common + developing tools, code generators for lexical analyzers and + parsers. This is a prerequisite for compiling UG. + +\item \textbf{BLAS}: Alberta makes use of BLAS (acronym for + ``\textbf{B}asic \textbf{L}inear \textbf{A}lgebra + \textbf{S}ubprograms). Thus install GotoBLAS2, ATLAS, + non-optimized BLAS or a BLAS library provided by a computer + vendor. Take care that the installation scripts select the intended + version of BLAS. For further information, see + \texttt{\url{http://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms}}. + +\item \textbf{GotoBLAS2}: This is an optimized BLAS library. It does + not provide optimized routines for all modern processors, but quite + a broad range. Also, its license is now open. A Fortran compiler + like \texttt{gfortran} is needed to compile it. + + Available at + \texttt{\url{http://www.tacc.utexas.edu/tacc-projects/gotoblas2/}}. + +\item \textbf{METIS}: This is a dependency of ALUGrid, if you are + going to run it parallel. + + Available for non-commercial use at + \texttt{\url{http://glaros.dtc.umn.edu/gkhome/metis/metis/overview}} + +\item \textbf{Compilers}: Besides \texttt{g++} \Dune and \eWoms can + also be built with the \texttt{clang++} compiler originating from + the LLVM project. If you try other compilers, be aware that in addition to a + \Cplusplus compiler, C and Fortran compilers are needed to compile + some external libraries. +\end{itemize} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/intro.tex b/doc/handbook/intro.tex new file mode 100644 index 00000000000..dcdc4ca586d --- /dev/null +++ b/doc/handbook/intro.tex @@ -0,0 +1,93 @@ +\chapter{Introduction} + +\eWoms~\cite{EWOMS-HP} a generic simulation framework using continuum +mechanical approaches with a focus on multi-phase fluid flow and +transport processes in porous media. \eWoms is also an integral part +of the open porous media initiative~\cite{OPM-HP} for which it +implements the fully-implicit discretization schemes. \eWoms is based +on the source code of the \Dumux~\cite{DUMUX-HP} simulation framework +and aims to be a proper superset of \Dumux when it comes to features, +while at the same time it delivers better performance and a +higher-quality code base. To ease porting features from \Dumux to +\eWoms, the \eWoms source code uses very similar naming and style +conventions as the one of \Dumux. + +Besides being a generic simulation framework, \eWoms also aims to to +deliver top-notch computational performance, high flexibility, a sound +software architecture and the ability to run on anything from single +processor systems to highly parallel supercomputers with specialized +hardware architectures. The means to achieve these somewhat +contradictory goals are the thorough use of object oriented design in +conjunction with template programming. These requirements motivated +the decision to implement \eWoms using the \Cplusplus programming +language. + +One of the more complex issues when dealing with parallel continuum +models for partial differential equations, is the management of the +grids used for the spatial discretization. To date, no generic and +efficient approach exists for all possible cases, which lead to \eWoms +being build on top of \Dune, the \textbf{D}istributed and +\textbf{U}nified \textbf{N}umerics +\textbf{E}nvironment~\cite{DUNE-HP}. Instead of trying to implement a +grid for everything, \Dune defines a generic \Cplusplus interface to +grids, and provides adapters to several existing grid management +libraries such as UG~\cite{UG-HP}, ALBERTA~\cite{ALBERTA-HP} or +ALUGrid~\cite{ALUGRID-HP}. DUNE also extensively uses template +programming in order to achieve minimal overhead when accessing the +underlying grid libraries\footnote{In fact, the performance penalty + resulting from the use of \Dune's grid interface is usually + negligible~\cite{BURRI2006}.}. +\begin{figure}[hbt] + \centering + \includegraphics[width=.5\linewidth, keepaspectratio]{EPS/dunedesign} + \caption{ + \label{fig:dune-design} + A high-level overview of \Dune's design is available on the project's + web site~\cite{DUNE-HP}. + } +\end{figure} + +DUNE's grid interface is independent of the spatial dimension of the +underlying grid. For this purpose, it uses the concept of +co-dimensional entities. Roughly speaking, an entity of co-dimension +$0$ constitutes a cell, co-dimension $1$ entities are faces between +cells, co-dimension $1$ are edges, and so on until co-dimension $n$ +which are the cell's vertices. The \Dune grid interface generally +assumes that all entities are convex polytopes, which means that it +must be possible to express each entity as the convex hull of a set of +vertices. For the sake of efficiency, all entities are further expressed in terms +of so-called reference elements which are transformed to the actual +spatial incarnation within the grid by a so-called geometry +function. Here, a reference element for an +entity can be thought of as a prototype for the actual grid +entity. For example, if we used a grid which applied hexahedrons as cells, +the reference element for each cell would be the unit cube $[0, 1]^3$ +and the geometry function would scale and translate the cube so that +it matches the grid's cell. For a more thorough description of \Dune's +grid definition, see~\cite{BASTIAN2008}. + +In addition to the grid interface, \Dune also provides quite a few +additional modules; In the context of this handbook the +\texttt{dune-localfunctions} and \texttt{dune-istl} modules are +probably the most relevant. \texttt{dune-localfunctions} provides a +set of generic finite element shape functions, while +\texttt{dune-istl} is the \textbf{I}terative \textbf{S}olver +\textbf{T}emplate \textbf{L}ibrary and provides generic, highly +optimized linear algebra routines for solving linear systems of +equations. + +\eWoms comes in form of a module \Dune module '\texttt{ewoms}'. It +depends on the \Dune core modules \texttt{dune-common}, +\texttt{dune-grid}, \texttt{dune-istl}, and on +\texttt{dune-localfunctions}. The main intention of \eWoms is to +provide a framework for an easy and efficient implementation of new +physical models for porous media flow problems, ranging from problem +formulation and the selection of spatial and temporal discretization +schemes as well as nonlinear and linear solvers, to general concepts +for model coupling. Moreover, \eWoms includes ready-to-use numerical +models and a few example applications. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/models.tex b/doc/handbook/models.tex new file mode 100644 index 00000000000..36ddb2e567c --- /dev/null +++ b/doc/handbook/models.tex @@ -0,0 +1,179 @@ +\chapter[Models]{Physical and numerical models} + +\section{Physical and mathematical description} + +Characteristic of compositional multiphase models is that the phases +are not only matter of a single chemical substance. Instead, their +composition in general includes several species, and for the mass transfer, +the component behavior is quite different from the phase behavior. In the following, we +give some basic definitions and assumptions that are required for the +formulation of the model concept below. As an example, we take a +three-phase three-component system water-NAPL-gas +\cite{A3:class:2002a}. The modification for other multicomponent +systems is straightforward and can be found, e.\ g., in +\cite{A3:bielinski:2006,A3:acosta:2006}. + +\subsection{Basic Definitions and Assumptions for the Compositional + Model Concept} +\textbf{Components:} +The term \emph{component} stands for constituents of the phases which +can be associated with a unique chemical species, or, more generally, with +a group of species exploiting similar physical behavior. In this work, we +assume a water-gas-NAPL system composed of the phases water (subscript +$\text{w}$), gas ($\text{g}$), and NAPL ($\text{n}$). These phases are +composed of the components water (superscript $\text{w}$), air +($\text{a}$), and the organic contaminant ($\text{c}$) (see Fig.\ +\ref{A3:fig:mundwtrans}). +% +\begin{figure}[hbt] + \centering + \includegraphics[width=0.7\linewidth]{EPS/masstransfer} + \caption{Mass and energy transfer between the phases} + \label{A3:fig:mundwtrans} +\end{figure} + +\textbf{Equilibrium:} +For the nonisothermal multiphase processes in porous media under +consideration, we state that the assumption of local thermal +equilibrium is valid since flow velocities are small. We neglect +chemical reactions and biological decomposition and assume chemical +equilibrium. Mechanical equilibrium is not valid in a porous medium, +since discontinuities in pressure can occur across a fluid-fluid +interface due to capillary effects. + +\textbf{Notation:} The index $\alpha \in \{\text{w}, \text{n}, \text{g}\}$ refers +to the phase, while the superscript $\kappa \in \{\text{w}, \text{a}, \text{c}\}$ refers +to the component. \\ +\begin{tabular}{llll} +$p_\alpha$ & phase pressure & $\phi$ & porosity \\ +$T$ & temperature & $K$ & absolute permeability tensor \\ +$S_\alpha$ & phase saturation & $\tau$ & tortuosity \\ +$x_\alpha^\kappa$ & mole fraction of component $\kappa$ in phase $\alpha$ & $\boldsymbol{g}$ & gravitational acceleration \\ +$X_\alpha^\kappa$ & mass fraction of component $\kappa$ in phase $\alpha$ & $q^\kappa_\alpha$ & volume source term of $\kappa$ in $\alpha$ \\ +$\varrho_{\text{mol},\alpha}$ & molar density of phase $\alpha$ & $u_\alpha$ & specific internal energy \\ +$\varrho_{\alpha}$ & mass density of phase $\alpha$ & $h_\alpha$ & specific enthalpy \\ +$k_{\text{r}\alpha}$ & relative permeability & $c_\text{s}$ & specific heat enthalpy \\ +$\mu_\alpha$ & phase viscosity & $\lambda_\text{pm}$ & heat conductivity \\ +$D_\alpha^\kappa$ & diffusivity of component $\kappa$ in phase $\alpha$ & $q^h$ & heat source term \\ +$\boldsymbol{v}_\alpha$ & Darcy velocity & $\boldsymbol{v}_{a,\alpha}$ & advective velocity +\end{tabular} + + +\subsection{Balance Equations} +For the balance equations for multicomponent systems, it is in many +cases convenient to use a molar formulation of the continuity +equation. Considering the mass conservation for each component allows +us to drop source/sink terms for describing the mass transfer between +phases. Then, the +molar mass balance can be written as: +% +\begin{multline} + \label{A3:eqmass1} + \phi \frac{\partial (\sum_\alpha \varrho_{\text{mol}, \alpha} + x_\alpha^\kappa S_\alpha )}{\partial t} + - \sum\limits_\alpha \Div \left( \frac{k_{\text{r} + \alpha}}{\mu_\alpha} \varrho_{\text{mol}, \alpha} + x_\alpha^\kappa K (\grad p_\alpha - + \varrho_{\alpha} \boldsymbol{g}) \right) \\ + % + % + - \sum\limits_\alpha \Div \left( \tau \phi S_\alpha D_\alpha^\kappa \varrho_{\text{mol}, + \alpha} \grad x_\alpha^\kappa \right) + - q^\kappa = 0, \qquad \kappa \in \{\text{w,a,c}\}. +\end{multline} + +The component mass balance can also be written in terms of mass fractions +by replacing molar densities by mass densities and mole by mass fractions. +To obtain a single conserved quantity in the temporal derivative, the total +concentration, representing the mass of one component per unit volume, is defined as +\begin{displaymath} +C^\kappa = \sum_\alpha \phi S_\alpha \varrho_{\text{mass},\alpha} X_\alpha^\kappa \; . +\end{displaymath} +Using this definition, the component mass balance is written as: + +\begin{multline} + \label{A3:eqmass2} + \frac{\partial C^\kappa}{\partial t} = + \sum\limits_\alpha \Div \left( \frac{k_{\text{r} + \alpha}}{\mu_\alpha} \varrho_{\text{mass}, \alpha} + X_\alpha^\kappa K (\grad p_\alpha + + \varrho_{\text{mass}, \alpha} \boldsymbol{g}) \right) \\ + % + % + + \sum\limits_\alpha \Div \left( \tau \phi S_\alpha D_\alpha^\kappa \varrho_{\text{mass}, + \alpha} \grad X_\alpha^\kappa \right) + + q^\kappa = 0, \qquad \kappa \in \{\text{w,a,c}\}. +\end{multline} + + +In the case of non-isothermal systems, we further have to balance the +thermal energy. We assume fully reversible processes, such that entropy +is not needed as a model parameter. Furthermore, we neglect +dissipative effects and the heat transport due to molecular +diffusion. The energy balance can then be +formulated as: +% +\begin{multline} + \label{A3:eqenergmak1} + \phi \frac{\partial \left( \sum_\alpha \varrho_{\alpha} + u_\alpha S_\alpha \right)}{\partial t} + \left( 1 - + \phi \right) \frac{\partial \varrho_{\text{s}} c_{\text{s}} + T}{\partial t} + - \Div \left( \lambda_{\text{pm}} \grad T \right) + \\ + - \sum\limits_\alpha \Div \left( \frac{k_{\text{r} + \alpha}}{\mu_\alpha} \varrho_{\alpha} h_\alpha + K \left( \grad p_\alpha - \varrho_{\alpha} + \boldsymbol{g} \right) \right) + - q^h \; = \; 0. +\end{multline} + +In order to close the system, supplementary constraints for capillary pressure, saturations and mole +fractions are needed, \cite{A3:helmig:1997}. +According to the Gibbsian phase rule, the number of degrees of freedom +in a non-isothermal compositional multiphase system is equal to the +number of components plus one. This means we need as many independent +unknowns in the system description. The +available primary variables are, e.\ g., saturations, mole/mass +fractions, temperature, pressures, etc. + +\input{box} + + +\section{Available models} +The following description of the available models is automatically extracted +from the Doxygen documentation. +% \textbf{TODO}: Unify notation. + +\subsection{Fully-implicit models} + +The fully-implicit models described in this section are using the box +scheme as described in Section \ref{box} for spatial and the implicit Euler +method as temporal discretization. The models themselves are located in +subdirectories of \texttt{ewoms/boxmodels} of the \eWoms distribution. + +\subsubsection{The immiscible multi-phase model} +\input{ModelDescriptions/immisciblemodel} + +\subsubsection{The miscible multi-phase NCP model} +\input{ModelDescriptions/pvsmodel} + +\subsubsection{The miscible multi-phase PVS model} +\input{ModelDescriptions/pvsmodel} + +\subsubsection{The miscible multi-phase flash model} +\input{ModelDescriptions/flashmodel} + +\subsubsection{The Richards model} +\input{ModelDescriptions/richardsmodel} + +\subsubsection{The black-oil model} +\input{ModelDescriptions/blackoilmodel} + +\subsubsection{The (Navier-)Stokes model} +\input{ModelDescriptions/stokesmodel} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/newton-in-a-nutshell.tex b/doc/handbook/newton-in-a-nutshell.tex new file mode 100644 index 00000000000..10791395555 --- /dev/null +++ b/doc/handbook/newton-in-a-nutshell.tex @@ -0,0 +1,74 @@ +\chapter{The Newton-Raphson method} + +For the isothermal immiscible multi-phase model, the following mass +conservation equation needs to be solved: +\begin{align} +\underbrace{ + \frac{\partial \phi \varrho_\alpha S_\alpha}{\partial t} + - + \text{div} \left\{ + \varrho_\alpha \frac{k_{r\alpha}}{\mu_\alpha} \mbox{\textbf{K}} \left(\text{grad}\, p_\alpha - \varrho_{\alpha} \mbox{\textbf{g}} \right) + \right\} - q_\alpha} _ +{\textbf{f}(\textbf{u})} += 0 \; . +\end{align} + +Because of the nonlinear dependencies even solving this comparatively +simple equation is a quite challenging task. However, for +finding roots of non-linear systems equations the +\textsc{Newton}-\textsc{Raphson} method can be used. + +When using a fully-implicit numerical model, each time step essentially +consists of the application of the \textsc{Newton} algorithm to solve +the nonlinear system. + +The idea of this algorithm is to linearize the non-linear system of +equation at a given solution, and then solving the resulting linear +system of equations. The hope is, that the solution of this linear +system is closer to the root of the non-linear system of +equations. This process is repeated until either convergence is +reached (a pre-determined accuracy is reached), or divergence of the +algorithm is detected (either by trespassing the maximum number of +iterations or by failure to linearize). This method can be formalized +as follows: + +\begin{subequations} +\begin{align} +\label{NewtonGen} +\textbf{u}^{r+1} &= \textbf{u}^r + \Delta \textbf{u}^r \\ +\Delta \textbf{u}^r & = - \left\{\text{grad}\,\textbf{f} (\textbf{u}^r) \right\}^{-1} \textbf{f}(\textbf{u}^r) \\ +\end{align} +\end{subequations} + +\noindent with +\begin{itemize} +\item $\textbf{u}$: Vector of unknowns +\item $\textbf{f}(\textbf{u}^r)$: Residual (Function of the vector of unknowns which ought to be set to $0$) +\item $\phantom{a}^r$: last iteration, $\phantom{a}^{r+1}$: current iteration, +\item $\text{grad}\,\phantom{a}$: \textsc{Jacobian} matrix of + $\textbf{f}$, i.e. matrix of the derivatives of \textbf{f} regarding + all components of $\textbf{u}$ +\end{itemize} + +The value of $\textbf{u}$ for which $\textbf{f}$ becomes zero is +searched for. Bringing \eqref{NewtonGen} into the form used the linear +solvers +\begin{equation} +\label{GenSysEq} + \textbf{A}\textbf{x} - \textbf{b} = 0 +\end{equation} +leads to +\begin{itemize} +\item $\textbf{A} = \text{grad}\,\textbf{f} (\textbf{u}^r)$ +\item $\textbf{x} = \textbf{u}^{r} - \textbf{u}^{r+1}$ +\item $\textbf{b} = \textbf{f}(\textbf{u}^{r})$ +\end{itemize} + +Once $\textbf{u}^{r} - \textbf{u}^{r+1}$ has been calculated, \eWoms +updates the current solution in \texttt{NewtonController::update()} +and starts the next iteration if the scheme has not yet converged. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/parameters.tex b/doc/handbook/parameters.tex new file mode 100644 index 00000000000..4895c0ad3da --- /dev/null +++ b/doc/handbook/parameters.tex @@ -0,0 +1,41 @@ +\section{Specifying Parameters} +\label{sec:inputFiles} + +As already mentioned in the previous section, \eWoms allows to specify +a number of parameters at run time. A description of these parameters +can usually be obtained by passing the \texttt{--help} parameter to the +executable. The values used for these parameters are printed to the +terminal during the start-up of the simulation, e.g. the if the \texttt{lens\_immiscible} +simulation is run with the parameters \texttt{--end-time=30e3 --foo-param=123}, it will print an output similar to +\begin{lstlisting}[style=Bash] +########### +# Parameters specified at run-time +########### +EndTime="30e3" +########### +# Parameters with use compile-time fallback values +########### +AmgCoarsenTarget="5000" +... +VtkWriteSaturations="1" +VtkWriteTemperature="1" +VtkWriteViscosities="0" +########### +# Parameters unknown to the simulation +########### +FooParam="123" +\end{lstlisting} + +This can be directly copy-and-pasted into a file +(e.g. \texttt{myparams.ini}). The simulation can be instructed to use +these parameters by passing the \texttt{--parameter-file=myparams.ini} +parameter the next time it is run. Of course, this file can also be +adapted. + +An additional hint: Always watch out for parameters which are unknown +to the simulation, as this is \emph{very} useful for spotting typos. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/propertysystem.tex b/doc/handbook/propertysystem.tex new file mode 100644 index 00000000000..678bcf767de --- /dev/null +++ b/doc/handbook/propertysystem.tex @@ -0,0 +1,376 @@ +\chapter{The \eWoms property system} +\label{sec:propertysystem} + +This section is dedicated to the \eWoms property system. First, a high +level overview over its design and principle ideas is given, then +follows a short reference and a short self-contained example. + +\section{Concepts and features of the \eWoms property system} + +The \eWoms property system was designed as an attempt to mitigate the +problems of traits classes. In fact, it can be seen as a traits system +which allows easy inheritance and any acyclic dependency of parameter +definitions. Just like traits, the \eWoms property system is a compile +time mechanism, which means that there are no run-time performance +penalties associated with it. It is based on the following concepts: +\begin{description} +\item[Property:] In the context of the \eWoms property system, a + property is an arbitrary class body which may contain type + definitions, values and methods. Each property has a so-called + \textbf{property tag} which can be seen as a label with its name. +\item[Property Inheritance:] Just like normal classes, properties can + be arranged in hierarchies. In the context of the \eWoms property + system, nodes of the inheritance hierarchy are called \textbf{type + tags}. +\end{description} + +It also supports \textbf{property nesting} and +\textbf{introspection}. Property nesting means that the definition of +a property can depend on the value of other properties which may be +defined for arbitrary levels of the inheritance hierarchy. The term +introspection denotes the ability to generate diagnostic messages +which can be used to find out where a certain property was defined and +how it was inherited. + +\section{\eWoms property system reference} + +All source files which use the \eWoms property system should include +the header file \texttt{ewoms/ \hskip-1ex{}common/ + \hskip-1ex{}propertysystem.hh}. Declaration of type tags and +property tags as well as defining properties must be done inside the +namespace \texttt{Ewoms::Properties}. + +\subsection*{Defining type tags} + +New nodes in the type tag hierarchy can be defined using +\begin{lstlisting}[style=eWomsCode] +NEW_TYPE_TAG(NewTypeTagName, INHERITS_FROM(BaseTagName1, BaseTagName2, ...)); +\end{lstlisting} +where the \texttt{INHERITS\_FROM} part is optional. To avoid +inconsistencies in the hierarchy, each type tag may be defined only +once for a program. + +\vskip1ex\noindent +Example: +\begin{lstlisting}[style=eWomsCode] +namespace Ewoms { +namespace Properties { +NEW_TYPE_TAG(MyBaseTypeTag1); +NEW_TYPE_TAG(MyBaseTypeTag2); + +NEW_TYPE_TAG(MyDerivedTypeTag, INHERITS_FROM(MyBaseTypeTag1, MyBaseTypeTag2)); +}} +\end{lstlisting} + +\subsection*{Declaring property tags} + +New property tags -- i.e. labels for properties -- are declared +using +\begin{lstlisting}[style=eWomsCode] +NEW_PROP_TAG(NewPropTagName); +\end{lstlisting} +A property tag can be declared arbitrarily often, in fact it is +recommended that all properties are declared in each file where they +are used. + +\vskip1ex\noindent +Example: +\begin{lstlisting}[style=eWomsCode] +namespace Ewoms { +namespace Properties { +NEW_PROP_TAG(MyPropertyTag); +}} +\end{lstlisting} + +\subsection*{Defining properties} + +The value of a property on a given node of the type tag hierarchy is +defined using +\begin{lstlisting}[style=eWomsCode] +SET_PROP(TypeTagName, PropertyTagName) +{ + // arbitrary body of a struct +}; +\end{lstlisting} +For each program, a property itself can be declared at most once, +although properties may be overwritten for derived type tags. + +Also, the following convenience macros are available to define simple +properties: +\begin{lstlisting}[style=eWomsCode] +SET_TYPE_PROP(TypeTagName, PropertyTagName, type); +SET_BOOL_PROP(TypeTagName, PropertyTagName, booleanValue); +SET_INT_PROP(TypeTagName, PropertyTagName, integerValue); +SET_SCALAR_PROP(TypeTagName, PropertyTagName, floatingPointValue); +\end{lstlisting} + +\vskip1ex\noindent +Example: +\begin{lstlisting}[style=eWomsCode] +namespace Ewoms { +namespace Properties { +NEW_TYPE_TAG(MyTypeTag); + +NEW_PROP_TAG(MyCustomProperty); +NEW_PROP_TAG(MyType); + +NEW_PROP_TAG(MyBoolValue); +NEW_PROP_TAG(MyIntValue); +NEW_PROP_TAG(MyScalarValue); + +SET_PROP(MyTypeTag, MyCustomProperty) +{ + static void print() { std::cout << "Hello, World!\n"; } +}; +SET_TYPE_PROP(MyTypeTag, MyType, unsigned int); + +SET_BOOL_PROP(MyTypeTag, MyBoolValue, true); +SET_INT_PROP(MyTypeTag, MyIntValue, 12345); +SET_SCALAR_PROP(MyTypeTag, MyScalarValue, 12345.67890); +}} +\end{lstlisting} + +\subsection*{Un-setting properties} + +Sometimes some inherited properties do not make sense for a certain +node in the type tag hierarchy. These properties can be explicitly +un-set using +\begin{lstlisting}[style=eWomsCode] +UNSET_PROP(TypeTagName, PropertyTagName); +\end{lstlisting} +The un-set property can not be set for the same type tag, but of +course derived type tags may set it again. + +\vskip1ex\noindent +Example: +\begin{lstlisting}[style=eWomsCode] +namespace Ewoms { +namespace Properties { +NEW_TYPE_TAG(BaseTypeTag); +NEW_TYPE_TAG(DerivedTypeTag, INHERITS_FROM(BaseTypeTag)); + +NEW_PROP_TAG(TestProp); + +SET_TYPE_PROP(BaseTypeTag, TestProp, int); +UNSET_PROP(DerivedTypeTag, TestProp); +// trying to access the 'TestProp' property for 'DerivedTypeTag' +// will trigger a compiler error! +}} +\end{lstlisting} + +\subsection*{Converting type tag names to \Cplusplus types} + +For the \Cplusplus compiler, type tags are like ordinary types. Both +can thus be used as template arguments. To convert a type tag name +into the corresponding type, the macro \texttt{TTAG(TypeTagName)} +ought to be used. + +\subsection*{Retrieving property values} + +The value of a property can be retrieved using +\begin{lstlisting}[style=eWomsCode] +GET_PROP(TypeTag, PropertyTag) +\end{lstlisting} +or using the convenience macros +\begin{lstlisting}[style=eWomsCode] +GET_PROP_TYPE(TypeTag, PropertyTag) +GET_PROP_VALUE(TypeTag, PropertyTag) +\end{lstlisting} + +\vskip1ex +\noindent +The first convenience macro retrieves the type defined using +\texttt{SET\_TYPE\_PROP} and is equivalent to +\begin{lstlisting}[style=eWomsCode] +GET_PROP(TypeTag, PropertyTag)::type +\end{lstlisting} +while the second convenience macro retrieves the value of any property +defined using one of the macros \texttt{SET\_}$\{$\texttt{INT,BOOL,SCALAR}$\}$\texttt{\_PROP} and is +equivalent to +\begin{lstlisting}[style=eWomsCode] +GET_PROP(TypeTag, PropertyTag)::value +\end{lstlisting} + +\vskip1ex\noindent +Example:\nolinebreak +\begin{lstlisting}[style=eWomsCode] +template +class MyClass { + // retrieve the ::value attribute of the 'NumEq' property + enum { numEq = GET_PROP(TypeTag, NumEq)::value }; + // retrieve the ::value attribute of the 'NumPhases' property using the convenience macro + enum { numPhases = GET_PROP_VALUE(TypeTag, NumPhases) }; + + // retrieve the ::type attribute of the 'Scalar' property + typedef typename GET_PROP(TypeTag, Scalar)::type Scalar; + // retrieve the ::type attribute of the 'Vector' property using the convenience macro + typedef typename GET_PROP_TYPE(TypeTag, Vector) Vector; +}; +\end{lstlisting} + +\subsection*{Nesting property definitions} + +Inside property definitions there is access to all other properties +which are defined somewhere on the type tag hierarchy. The node for +which the current property is requested is available via the keyword +\texttt{TypeTag}. Inside property class bodies this can be used to +retrieve other properties using the \texttt{GET\_PROP} macros. + +\vskip1ex\noindent +Example: +\begin{lstlisting}[style=eWomsCode] +SET_PROP(MyModelTypeTag, Vector) +{ +private: typedef typename GET_PROP_TYPE(TypeTag, Scalar) Scalar; +public: typedef std::vector type; +}; +\end{lstlisting} + + +\section{A self-contained example} + +As a concrete example, let us consider some kinds of cars: Compact +cars, sedans, trucks, pickups, military tanks and the Hummer-H1 sports +utility vehicle. Since all these cars share some characteristics, it +makes sense to inherit those from the closest matching car type and +only specify the properties which are different. Thus, an inheritance +diagram for the car types above might look like outlined in Figure +\ref{fig:car-hierarchy}. + +\begin{figure}[t] + \centering + \subfloat[]{ + \includegraphics[width=.6\textwidth]{EPS/car-hierarchy.eps} + \label{fig:car-hierarchy} + } + \subfloat[]{ + \includegraphics[width=.35\linewidth, keepaspectratio]{EPS/car-propertynames.eps} + \label{fig:car-propertynames} + } + \caption{\textbf{(a)}~A possible property inheritance graph for + various kinds of cars. The lower nodes inherit from higher ones; + Inherited properties from nodes on the right take precedence over the + properties defined on the left. \textbf{(b)}~Property names + which make sense for at least one of the car types of (a).} +\end{figure} + +Using the \eWoms property system, this inheritance hierarchy is +defined by: +\begin{lstlisting}[name=propsyscars,style=eWomsCode] +#include +#include + +namespace Ewoms { +namespace Properties { +NEW_TYPE_TAG(CompactCar); +NEW_TYPE_TAG(Truck); +NEW_TYPE_TAG(Tank); +NEW_TYPE_TAG(Sedan, INHERITS_FROM(CompactCar)); +NEW_TYPE_TAG(Pickup, INHERITS_FROM(Sedan, Truck)); +NEW_TYPE_TAG(HummerH1, INHERITS_FROM(Pickup, Tank)); +\end{lstlisting} + +Figure \ref{fig:car-propertynames} lists a few property names which +make sense for at least one of the nodes of Figure +\ref{fig:car-hierarchy}. These property names can be declared as +follows: +\begin{lstlisting}[name=propsyscars,style=eWomsCode] +NEW_PROP_TAG(TopSpeed); // [km/h] +NEW_PROP_TAG(NumSeats); // [] +NEW_PROP_TAG(CanonCaliber); // [mm] +NEW_PROP_TAG(GasUsage); // [l/100km] +NEW_PROP_TAG(AutomaticTransmission); // true/false +NEW_PROP_TAG(Payload); // [t] +\end{lstlisting} + +\noindent +So far, the inheritance hierarchy and the property names are completely +separate. What is missing is setting some values for the property +names on specific nodes of the inheritance hierarchy. Let us assume +the following: +\begin{itemize} +\item For a compact car, the top speed is the gas usage in $\unitfrac{l}{100km}$ + times $30$, the number of seats is $5$ and the gas usage is + $\unitfrac[4]{l}{100km}$. +\item A truck is by law limited to $\unitfrac[100]{km}{h}$ top speed, the number + of seats is $2$, it uses $\unitfrac[18]{l}{100km}$ and has a cargo payload of + $\unit[35]{t}$. +\item A tank exhibits a top speed of $\unitfrac[60]{km}{h}$, uses $\unitfrac[65]{l}{100km}$ + and features a $\unit[120]{mm}$ diameter canon +\item A sedan has a gas usage of $\unitfrac[7]{l}{100km}$, as well as an automatic + transmission, in every other aspect it is like a compact car. +\item A pick-up truck has a top speed of $\unitfrac[120]{km}{h}$ and a payload of + $\unit[5]{t}$. In every other aspect it is like a sedan or a truck but if in + doubt, it is more like a truck. +\item The Hummer-H1 SUV exhibits the same top speed as a pick-up + truck. In all other aspects it is similar to a pickup and a tank, + but, if in doubt, more like a tank. +\end{itemize} + +\noindent +Using the \eWoms property system, these assumptions are formulated +using +\begin{lstlisting}[name=propsyscars,style=eWomsCode] +SET_INT_PROP(CompactCar, TopSpeed, GET_PROP_VALUE(TypeTag, GasUsage) * 30); +SET_INT_PROP(CompactCar, NumSeats, 5); +SET_INT_PROP(CompactCar, GasUsage, 4); + +SET_INT_PROP(Truck, TopSpeed, 100); +SET_INT_PROP(Truck, NumSeats, 2); +SET_INT_PROP(Truck, GasUsage, 18); +SET_INT_PROP(Truck, Payload, 35); + +SET_INT_PROP(Tank, TopSpeed, 60); +SET_INT_PROP(Tank, GasUsage, 65); +SET_INT_PROP(Tank, CanonCaliber, 120); + +SET_INT_PROP(Sedan, GasUsage, 7); +SET_BOOL_PROP(Sedan, AutomaticTransmission, true); + +SET_INT_PROP(Pickup, TopSpeed, 120); +SET_INT_PROP(Pickup, Payload, 5); + +SET_INT_PROP(HummerH1, TopSpeed, GET_PROP_VALUE(TTAG(Pickup), TopSpeed)); +\end{lstlisting} + +\noindent +At this point, the Hummer-H1 has a $\unit[120]{mm}$ canon which it inherited +from its military ancestor. It can be removed by +\begin{lstlisting}[name=propsyscars,style=eWomsCode] +UNSET_PROP(HummerH1, CanonCaliber); + +}} // close namespaces +\end{lstlisting} + +\noindent +Now property values can be retrieved and some diagnostic messages can +be generated. For example +\begin{lstlisting}[name=propsyscars,style=eWomsCode] +int main() +{ + std::cout << "top speed of sedan: " << GET_PROP_VALUE(TTAG(Sedan), TopSpeed) << "\n"; + std::cout << "top speed of truck: " << GET_PROP_VALUE(TTAG(Truck), TopSpeed) << "\n"; + + std::cout << PROP_DIAGNOSTIC(TTAG(Sedan), TopSpeed); + std::cout << PROP_DIAGNOSTIC(TTAG(HummerH1), CanonCaliber); + + Ewoms::Properties::print(); +} +\end{lstlisting} +will yield the following output: +\begin{lstlisting}[style=eWomsCode] +top speed of sedan: 210 +top speed of truck: 100 +Properties for Sedan: + bool AutomaticTransmission = 'true' defined at test_propertysystem.cc:68 + int GasUsage = '7' defined at test_propertysystem.cc:67 + Inherited from CompactCar: + int NumSeats = '5' defined at test_propertysystem.cc:55 + int TopSpeed = '::Ewoms::Properties::GetProperty::p::value * 30' defined at test_propertysystem.cc:54 +\end{lstlisting} + + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/quick-install.tex b/doc/handbook/quick-install.tex new file mode 100644 index 00000000000..cb80995fad8 --- /dev/null +++ b/doc/handbook/quick-install.tex @@ -0,0 +1,73 @@ +\section{Installation of \eWoms} \label{quick-install} + +This section describes a way of installing \eWoms that works in most +cases, but depending on your operating system of choice, \Cplusplus +compiler and features which you need, some tweaks are possibly +required. As a pre-requisite it is assumed, that you are using a +recent Linux distribution that has the appropriate development +packages (\Cplusplus compiler, autoconf, automake, libtool and +pkg-config amongst possibly others) installed, but that you did not +install \Dune via distribution provided packages. If you need more +information, or if you have \Dune already installed, please have a +look at the detailed installation instructions in Section +\ref{install}. + +\subsection{Retrieving the code} + + +You can retrieve all required \Dune modules by either downloading and +unpacking the tarballs for the \Dune-2.2 release followed by +downloading and unpacking the tarball for the \eWoms 2.2 release, or +by retrieving the code directly from their respective source-code +repositories. If you decide to use the first method, make sure to +unpack all tarballs into the same directory; if you prefer the second +method, make sure that you have the \texttt{git} version control +system with the SVN plug-in installed on your computer and enter the +following code snippet into a terminal: +\begin{lstlisting}[style=Bash] +cd $YOUR_DUNE_ROOT_DIRECTORY +for DUNE_MODULE in common geometry grid istl localfunctions; do \ + git svn clone https://svn.dune-project.org/svn/dune-$DUNE_MODULE/branches/release-2.2 $DUNE_MODULE \ +done +git clone --branch "release-2.2" git://github.com/OPM/ewoms.git +\end{lstlisting} +%$ + +\subsection{Building \Dune and \eWoms} +\label{buildIt} + +\eWoms is a \Dune module and is recommended to build it using the \Dune +build system~\cite{DUNE-BS}. To simplify things, \eWoms ships with a +few option files for \Dune's build script, \texttt{dunecontrol}. If +you are using \eWoms the first time, we recommend to use the options +optimized for the debugging experience, \texttt{debug.opts}: + +\begin{lstlisting}[style=Bash] +cd $YOUR_DUNE_ROOT_DIRECTORY +./dune-common/bin/dunecontrol --opts=ewoms/debug.opts all +\end{lstlisting} +%$ + +Once you have finished developing and testing your own code on +small-scale problems, re-compile everything with compiler +optimizations enabled before a production run in order to speed things +up by a factor of approximately 10: + +\begin{lstlisting}[style=Bash] +cd $YOUR_DUNE_ROOT_DIRECTORY +./dune-common/bin/dunecontrol --opts=ewoms/optim.opts all +\end{lstlisting} +%$ + +Sometimes it is necessary to have additional options which might be +specific to the operating system of your choice, or if you have +special requirements. For this reason, the option files mentioned +above should be rather understood as a starting point for your own +option files than as something fixed; feel free to copy and modify +them. To avoid confusion, it is helpful to rename your +customized option files, though. + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/quickstart-guide.tex b/doc/handbook/quickstart-guide.tex new file mode 100644 index 00000000000..4f9ee66080b --- /dev/null +++ b/doc/handbook/quickstart-guide.tex @@ -0,0 +1,87 @@ +\section[Quick start guide]{Compiling and running a sample application} +\label{quick-start-guide} + +The previous section showed how to install and compile \eWoms. This +chapter gives you a very brief introduction how to run a first test +application and how to visualize the output files it produces. Only the +rough steps will be described here; More detailed explanations can be +found in the tutorials in the following chapter. + +\begin{enumerate} +\item Go to the directory \texttt{test}. There, various test + application folders can be found. Let us consider as example + the one for the fully-implicit models, \texttt{boxmodels}: +\item Enter the folder \texttt{boxmodels}. +\item By default, the \texttt{dunecontrol} command only compiles the + parts of \Dune modules that are necessary to build modules depending + on the given module. For \eWoms, \texttt{dunecontrol} does not build + anything by default, because \eWoms only provides \Cplusplus + template classes but no libraries that need compilation. To compile + the test simulation for the "lens" problem which uses the fully-implicit + model that assumes immisciblility, enter +\begin{lstlisting}[style=Bash] +make lens_immiscible +\end{lstlisting} + +You may also compile all available tests which use the box scheme by entering +\begin{lstlisting}[style=Bash] +make check +\end{lstlisting} + +This takes quite some time, but if you have a multi-core processor +with \$N cores, you can considerably speed up compilation by using all +of available cores by using +\begin{lstlisting}[style=Bash] +make -j $N check +\end{lstlisting} +%$ + +\item If everything was compiled correctly, there should be an + executable called '\texttt{lens{\_}immiscible}'. To run the simulation, + simply execute it, i.e. enter +\begin{lstlisting}[style=Bash] +./lens_immiscible +\end{lstlisting} + +You may also want to change some parameters from the command line. For +example, if you want to change the time up to which the problem is +simulated to $30.000$ seconds, use +\begin{lstlisting}[style=Bash] +./lens_immiscible --end-time=30e3 +\end{lstlisting} + +You can also get a list of parameters recognized by the +simulation together with a brief description, by running +\begin{lstlisting}[style=Bash] +./lens_immiscible --help +\end{lstlisting} + +\item After this, the simulation should start and produce some output + on the terminal. It is possible to interrupt it at any time by + pressing \texttt{+}. + +\item The actual output files produced by the simulation are a series + of \texttt{.vtu} files and a \texttt{.pvd} file. Each \texttt{.vtu} + file contains "visualization ready" data for a single time step, + while the \texttt{.pvd} file "stitches" these files together into a + coherent data set. For example, the \texttt{.pvd} holds the + simulation time at which a given time step was produced, that can + later be used for visualization. +\item You can now display the result of the simulation using the + visualization tool ParaView (or, if you prefer, VisIt). Just type + \texttt{paraview} in the console and open the \texttt{.pvd} file. On + the left hand side, you should now be able to click the green + ``Apply'' button. Once you have done this, the visualization of the + simulation result appears on the screen and you can click the + ``play'' button in the toolbar to view display its evolution over + time. Also note that you can choose the visualized quantity + in the toolbar. For the lens problem, the most interesting quantities + are probably the saturations. +\item Play a bit around to make your self familiar with the + visualization tool of your choice as you will be using it a lot. +\end{enumerate} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/structure.tex b/doc/handbook/structure.tex new file mode 100644 index 00000000000..8b40e030018 --- /dev/null +++ b/doc/handbook/structure.tex @@ -0,0 +1,124 @@ +\chapter{Directory structure} + +We briefly describe the directory structure of \eWoms in terms +of subdirectories, source files, and tests. For more details, +the Doxygen documentation should be considered. +\eWoms comes in form of a DUNE module \texttt{ewoms}. +It has a similar structure as other DUNE modules like \texttt{dune-grid}. +The following subdirectories are within the module's root directory, +from now on assumed to be \texttt{/}: +\begin{itemize} +\item \texttt{CMake}: the configuration options +for building \eWoms using CMake. See the file \texttt{INSTALL.cmake} in +the root directory of the \eWoms distribution for details. Of course, +it is also possible to use the DUNE buildsystem just like for the other +DUNE modules. +\item \texttt{doc}: contains the Doxygen documentation in \texttt{doxygen}, +this handbook in \texttt{handbook}, and the \eWoms logo in various formats in +\texttt{logo}. The html documentation produced by Doxygen can be accessed as usual, +namely, by opening \texttt{doc/doxygen/html/index.html} with a web browser. +\item \texttt{ewoms}: the \eWoms source files. See Section \ref{sec:ewoms} for details. +\item \texttt{test}: tests for each numerical model and the property system. +See Section \ref{sec:test} for details. +\item \texttt{tutorial}: contains the tutorials described in Chapter \ref{chp:tutorial}. +\end{itemize} + + +\subsection{The \texttt{ewoms} directory}\label{sec:ewoms} + +The directory \texttt{ewoms} contains the \eWoms source files. It consists of the following subdirectories (see Figure \ref{fig:ewoms-structure}): + +\begin{itemize} + +\item \texttt{boxmodels}: The fully implicit vertex-centered finite + volume (box) method. This directory features the following + sub-directories + \begin{itemize} + \item \texttt{common}: The infrastructural code shared by all box + models; For example it contains the file + \texttt{boxfvelementgeometry.hh} which is used by the box scheme to + construct the dual grid out of the primal one. + \item \texttt{modules}: This directory features parts which can be + re-used by multiple models, like a generic plug-in for the energy + equation, models for determining the filter velocity, or plugins + describing molecular diffusion. + \item Each of the other subdirectories contains + a physical numerical model which uses the box discretization. + \end{itemize} + +\item \texttt{common}: General stuff like the \eWoms property system + and classes for managing time which are shared by the fully-implicit + and the semi-implicit models. For example it features the file + \texttt{start.hh} which provides the default way for starting a + simulation. + +\item \texttt{io}: Additional classes that provide in-/output routines + like writing and reading restart files (often called 'checkpoint' + files) and for writing a series of VTK files. + +\item \texttt{material}: Everything related to material parameters and + constitutive equations. For example, the properties of a pure + chemical substance (e.g. water) or pseudo substance (e.g. air) can + be found in the subdirectory \texttt{components} with the base class + \texttt{components/component.hh}. Fluid systems are located in the + sub-folder \texttt{fluidsystems}, while capillary pressure/relative + permeability laws are located in the sub-folder + \texttt{fluidmatrixinteractions}. Furthermore, the classes computing + binary coefficients like the Henry coefficient or binary diffusion + coefficients are held by the sub-folder \texttt{binarycoefficients}. + +\item \texttt{nonlinear}: This folder contains an implementation of + the Newton-Raphson method for solving non-linear systems of + equations. + +\item \texttt{linear}: This folder features code which is required to + parallelize the linear solvers, as well as back-ends for these + solvers. For example it contains classes to calculate an overlap + given the distributed matrix of the linear system, and provides + classes which use this overlap to provide overlapping matrices, + vectors, linear operators and scalar products. + +\item \texttt{istl}: This folder contains a copy of slightly modified + linear solvers from the ISTL \Dune module. The reason why they where + copied is to provide linear solvers with better performance and + stability without being held back by the \Dune developers. + +\item \texttt{parallel}: Contains some helper classes useful for + programming MPI parallel code. For example this folder contains a + simple MPI buffer class, and various commonly used \Dune + data-handles. +\end{itemize} + +\subsection{The directory \texttt{test}}\label{sec:test} + +The directory \texttt{test} contains at least one test/example for +each numerical model and for each important aspect of the common +\eWoms infrastructure. The directory \texttt{common} contains a test +for the property system. The sub-directory \texttt{implicit} +contains test applications for the fully-implicit models (where each +test is named according to the scheme \texttt{PROBLEMNAME\_MODDEL}). +Each subdirectory contains one or more program +files \texttt{*.cc} which contain the main function for the +test. Moreover, the problem definitions can be found in the +\texttt{*problem.hh} files. Simply executing the tests should either +run the full test or give a list of required command line +arguments. After test execution, VTK output files are generated by +most tests. For more detailed descriptions of the tests, the problem +definitions and their corresponding Doxygen documentation should be +considered. + +\begin{landscape} +\begin{figure}[hbt] + \centering + \includegraphics[width=\linewidth, keepaspectratio]{EPS/ewoms_structure.eps} + \caption{ + \label{fig:ewoms-structure} + Structure \texttt{ewoms} sub-directory of the \eWoms source tree. + } +\end{figure} +\end{landscape} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/tutorial-newmodel.tex b/doc/handbook/tutorial-newmodel.tex new file mode 100644 index 00000000000..0a9ab94d72b --- /dev/null +++ b/doc/handbook/tutorial-newmodel.tex @@ -0,0 +1,3 @@ +\section[New model]{How to implement a new model} + +TODO: describe how to impelment a new model \ No newline at end of file diff --git a/doc/handbook/tutorial.tex b/doc/handbook/tutorial.tex new file mode 100644 index 00000000000..2d32a938a7e --- /dev/null +++ b/doc/handbook/tutorial.tex @@ -0,0 +1,30 @@ +\chapter[Tutorial]{Tutorial}\label{chp:tutorial} + +\eWoms provides two sorts of models: Models which use a fully-implicit +discretization in space and time and models that use a +semi-implicit space and an explicit time discretization. + +Fully-implicit models as implemented by \eWoms describe the +conservation quantities of a flow system as a system of strongly +coupled partial differential equations which is directly using a +non-linear solver. Physically, these conservation quantities are mass, +momentum and energy; Although the momentum is usually not explicitly +conserved in the context of flow models for porous media. + +In section \ref{box} a short introduction to the vertex centered +finite volume scheme (VCFV or box method) used by \eWoms as the +primary spatial discretization is given. + +The purpose of this chapter is to introduce how flow problems can be +solved in \eWoms. Being a simple but representative case, an +isothermal two-phase problem (i.e. two fluid phases, one solid phase) +will be considered. The source code of these tutorials is shipped with +the \eWoms source package and can be found in the \texttt{tutorial} +directory. + +\input{tutorial1} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/handbook/tutorial1.tex b/doc/handbook/tutorial1.tex new file mode 100644 index 00000000000..d9f0369f955 --- /dev/null +++ b/doc/handbook/tutorial1.tex @@ -0,0 +1,548 @@ +\section{Upfront considerations} +\label{tutorial1} + +The process of setting up a problem using \eWoms can be roughly +divided into three parts: +\begin{enumerate} +\item A suitable model has to be chosen. +\item The geometry of the problem has to be defined and the suitable + grid has to be created. +\item Boundary and initial conditions and material properties have to + be specified. +\end{enumerate} + +The physical set-up of the flow problem being solved in this tutorial +is illustrated in Figure \ref{tutorial1:problemfigure}: It +consists of a rectangular domain with no-flow boundary conditions on +the top and on the bottom of the domain, which is initially fully +saturated by a light oil. Water infiltrates from the left side and +replaces the oil. The effect of gravity is neglected. + +\begin{figure}[ht] +\psfrag{x}{x} +\psfrag{y}{y} +\psfrag{no flow}{no flow} +\psfrag{water}{\textbf{water}} +\psfrag{oil}{\textcolor{white}{\textbf{oil}}} +\psfrag{p_w = 2 x 10^5 [Pa]}{$p_w = 2 \times 10^5$ [Pa]} +\psfrag{p_w_initial = 2 x 10^5 [Pa]}{\textcolor{white}{\textbf{$\mathbf{p_{w_{initial}} = 2 \times 10^5}$ [Pa]}}} +\psfrag{S_n = 0}{$S_n = 0$} +\psfrag{S_n_initial = 0}{\textcolor{white}{$\mathbf{S_{n_{initial}} = 1}$}} +\psfrag{q_w = 0 [kg/m^2s]}{$q_w = 0$ $\left[\frac{\textnormal{kg}}{\textnormal{m}^2 \textnormal{s}}\right]$} +\psfrag{q_n = -3 x 10^-4 [kg/m^2s]}{$q_n = 3 \times 10^{-2}$ $\left[\frac{\textnormal{kg}}{\textnormal{m}^2 \textnormal{s}}\right]$} +\centering +\includegraphics[width=0.9\linewidth,keepaspectratio]{EPS/tutorial-problemconfiguration} +\caption{Geometry of the tutorial problem with initial and boundary conditions.}\label{tutorial1:problemfigure} +\end{figure} + +The solved equations are the mass balances of water and oil: +\begin{align} + \label{massbalancewater} + \frac {\partial (\phi \, S_{w}\, \varrho_{w})}{\partial t} + - + \nabla \cdot \left( \varrho_{w} \, \frac{k_{rw}}{\mu_{w}} \, \mathbf{K}\;\nabla p_w \right) + - + q_w + & = + 0 \\ + \label{massbalanceoil} + \frac {\partial (\phi \, S_{o}\, \varrho_{o})}{\partial t} + - + \nabla \cdot \left( \varrho_{o} \, \frac{k_{ro}}{\mu_{o}} \, \mathbf{K}\;\nabla p_o \right) + - + q_o + & = + 0 +\end{align} + +\subsection{The main source file} + +Listing \ref{tutorial1:mainfile} shows the main file +\texttt{tutorial/tutorial1.cc} for the tutorial problem using +a fully-implicit model that assumes immiscibility. This file has to be +compiled and executed in order to solve the problem described above. + +\begin{lst}[File tutorial/tutorial1.cc]\label{tutorial1:mainfile} \mbox{} + \lstinputlisting[style=eWomsCode, numbersep=5pt, firstline=27, firstnumber=27]{../../tutorial/tutorial1.cc} +\end{lst} + +In \eWoms, this file is usually quite short, as most of the work for +setting up the simulation is done by the generic startup routine +\texttt{Ewoms::start()}. The tasks left for the main source file are: +\begin{itemize} +\item Inclusion of the necessary header files from line + \ref{tutorial1:include-begin} to line + \ref{tutorial1:include-end}. +\item Specifying the type tag of the problem which is going to be + simulated at line \ref{tutorial1:set-type-tag}. +\item Starting the simulation using default \eWoms startup routine + \texttt{Ewoms::start()} on line \ref{tutorial1:call-start}. +\end{itemize} + +The default \eWoms startup routine parses the command line arguments, +optionally reads a file which may specify further parameters, creates +the grid which represents the spatial domain, and then starts the +actual simulation. Parameters for the simulation, can be either +specified using command line arguments of the form +(\texttt{--parameter-name=value}), or in the file specified by the +\texttt{--parameter-file="file\_name"} argument. The list of all +parameters used by a simulation can be obtained by passing +\texttt{--help} to the executable on the command line. + +\subsection{The problem file} + +\begin{lst}[File tutorial/tutorial1problem.hh] \label{tutorial1:problemfile}\mbox{} +\lstinputlisting[style=eWomsCode, numbersep=5pt, firstline=28, firstnumber=28]{../../tutorial/tutorial1problem.hh} +\end{lst} + +For using \eWoms, the central file is the \emph{problem file} as +shown in listing~\ref{tutorial1:problemfile}. This file is responsible +for specifying the physical setup of the problem which is to be +simulated. In this context, all problems first need to set up the +relevant properties for the \eWoms property system: +\begin{itemize} +\item First, a new type tag is created for the problem on line + \ref{tutorial1:create-type-tag}. In this case, the new type + tag inherits all properties from the \texttt{ImmiscibleTwoPhaseModel} + type tag, which means that for this problem the immiscible model + for two fluid phases is chosen as discretization scheme. +\item Next, the spatial discretization which ought to be used is + selected on line~\ref{tutorial1:include-discretization}. In this + case, the vertex-centered finite volume (VCFV) method is chosen. +\item On line \ref{tutorial1:set-problem}, the problem class is + specified for the new type tag, while the kind of grid that is to be + used is defined in line \ref{tutorial1:set-grid} -- in this case + it is \texttt{Dune::YaspGrid}. +\item Since \Dune does not provide a uniform mechanism to create or + load grids, \eWoms features the concept of grid managers. In this + case, the generic \texttt{CubeGridManager} is used. This grid + manager constructs a structured rectangular grid with a specified + size and resolution. For this grid manager, the physical domain of + the grid is specified via the run-time parameters + \texttt{DomainSizeX} and \texttt{DomainSizeY} and its resolution by + \texttt{CellsX} and \texttt{CellsY}. These parameters can be + specified via the command-line or in a parameter file, but the + problem file must define default values for them. Defining these + defaults is done on lines \ref{tutorial1:grid-default-params-begin} + to \ref{tutorial1:default-params-end}. +\end{itemize} + +Next, an appropriate fluid system -- which specifies the thermodynamic +relations of the fluid phases -- has to be chosen. By default, the +immiscible model for two fluid phases uses +\texttt{\justifyNoHyphen{}Ewoms::Fluid\-Systems::TwoPImmiscible}. This +fluid system simplifies things considerably by assuming immiscibility +of the phases, but it requires to explicitly specify the fluids of the +wetting and non-wetting phases. In this case, liquid water based on +simple relations is selected as the wetting phase on line +\ref{tutorial1:wettingPhase} and a light liquid oil is chosen as the +non-wetting phase on line \ref{tutorial1:nonwettingPhase}. The next +property, which is set on line \ref{tutorial1:gravity}, tells the +model not to use gravity. This is then followed by the specification +default values for parameters specifying the temporal and spatial +simulation domain from line \ref{tutorial1:default-params-begin} to +line \ref{tutorial1:default-params-end}. The values of those can be +overwritten at run-time if desired. + +Following the property definitions, is the problem class which +specifies the variable parameters of the physical set-up to be +simulated. Such parameters are, for example, boundary and initial +conditions, source terms and spatially dependent quantities like +temperature, porosity, intrinsic permeability and the parameters +required by the capillary pressure/relative permeability law within +the domain. Since there are quite a few methods necessary to fully +specify a problem, and it is often possible to specify meaningful +defaults, it is strongly recomended to derive the problem from the +class specified by the \texttt{BaseProblem} property as done on line +\ref{tutorial1:def-problem}\footnote{Technically, deriving the problem + from the \texttt{BaseProblem} is not required if it implements all + necessary methods. Do not complain if things explode in this case, + though.}. + +For the isothermal multi-phase porous media model which assumes +immiscibility, the problem class provided by the user must implement +at least the following methods: +\begin{itemize} +\item \texttt{initial()} for specifying the initial condition within + the domain, +\item \texttt{source()} which defines the source term for each conservation quantity, +\item \texttt{boundary()} for the conditions at the spatial domain's + boundary, +\item \texttt{temperature()} which provides the temperature within the + spatial domain. Note, that if energy is conserved, this method is + not necessary. Instead, energy fluxes must be specified by the + \texttt{boundary()} and \texttt{source()} methods and an initial + temperature needs to be defined by the \texttt{initial()} method in + this case. +\item \texttt{intrinsicPermeability()} for returning the intrinsic + permeability matrix $\mathbf{K}$ in $[m^2]$ at a given location. +\item \texttt{porosity()} which specifies the ratio of pore space to + total volume of the medium at a given location. +\item \texttt{materialLawParams()} which defines the parameters for + the capillary pressure and relative permeability relations at a + given location. +\end{itemize} + +All of these methods take a single template argument, +\texttt{Context}, and the three function arguments \texttt{context}, +\texttt{spaceIdx} and \texttt{timeIdx}. Together, these form the +so-called \emph{execution context}. The execution context can be +thought of as a collection of all available information for a given +method. Thus, execution contexts a way to abstract away the +differences of discretization schemes. The following methods are +provided by all \texttt{context} objects: +\begin{itemize} +\item \texttt{pos(spaceIdx, timeIdx)}: This method returns the + relevant position of the execution context within the spatial + domain. +\item \texttt{globalSpaceIndex(spaceIdx, timeIdx)}: Returns a global + index for the spatial degree of freedom which is associated to the + execution context. This index can be used to store spatially + dependent information in an array. +\item \texttt{element()}: Returns the \Dune grid element to which the + execution context applies. +\item \texttt{gridView()}: Returns the \Dune grid view of which the + element is part of. +\item \texttt{problem()}: Returns the \eWoms object which describes + the physical problem to be solved. +\item \texttt{model()}: Returns the \eWoms model which is used to simulate + the physical problem. +\end{itemize} + +The execution contexts for the \texttt{source} and \texttt{boundary} +methods always provide the following methods in addition: +\begin{itemize} +\item \texttt{volVars(spaceIdx, timeIdx)}: The secondary variables + used by the model relevant for the execution context. These are + useful to specify solution dependent source and boundary terms. +\item \texttt{primaryVars(spaceIdx, timeIdx)}: The vector of primary + variables to which the model solves for. +\end{itemize} + +Finally, the execution context for the \texttt{boundary} method +provides the methods \texttt{boundarySegmentArea(spaceIdx, timeIdx)}, +and \texttt{normal(spaceIdx, timeIdx)}, which return the area of the +boundary segment and outer unit normal of the boundary segment. + +When specifying the boundary conditions in the problem class, a small +safeguard value \texttt{eps\_} should usually be added when comparing +spatial coordinates. For the problem considered here, the left +boundary might not be detected if checking whether the first +coordinate of the global position is equal to zero, but it is detected +reliably by testing whether it is smaller than an +$\epsilon$-value. Also, the \texttt{bboxMax()} (``\textbf{max}imum +coordinate of the grid's \textbf{b}ounding \textbf{box}'') method is +used here to determine the extend of the physical domain. It returns a +vector with the maximum values on each axis of the grid. This method +and the analogous \texttt{bboxMin()} method are provided by the +problem's base class. + +In addition to these methods, there might be some additional +model-specific methods. + +\subsection{Defining fluid properties} +\label{tutorial1:description-fluid-class} + +The \eWoms distribution includes representations of some common +substances which can be used out of the box. The properties of pure +substances (such as nitrogen, water, or the pseudo-component air) are +provided by header files located in the folder +\texttt{ewoms/material/components}. + +When two or more substances are considered, interactions between these +become relevant and the properties of these mixtures of substances -- +e.g. composition, density or enthalpy -- are required. In \eWoms, such +interactions are defined by {\em fluid systems}, which are located in +\texttt{ewoms/material/fluidsystems}. A more thorough overview of the +\eWoms fluid framework can be found in +chapter~\ref{sec:fluidframework}. + +\subsection{Exercises} +\label{tutorial1:exercises} + +The following exercises will give you the opportunity to learn how you +can change soil parameters, boundary conditions, run-time parameters +and fluid properties in \eWoms. + +\subsubsection{Exercise 1} + +\renewcommand{\labelenumi}{\alph{enumi})} + +For Exercise 1 you have to modify the tutorial files only sightly, or +even not at all. + +\begin{enumerate} + +\item \textbf{Running the Program} \\ + To get an impression what the results should look like, you can + first run the original version of the tutorial problem by entering + the directory into which you installed \eWoms, followed typing + \texttt{make tutorial1 \&\& ./bin/tutorial1}. Note, that the + time-step size is automatically adapted during the simulation. For + visualizing the results using the program \texttt{paraview}, please + refer to section \ref{quick-start-guide}. + +\item \textbf{Changing the Model Domain} \\ + Change the size of the model domain so that you get a rectangle with + edge lengths of $\text{x} = \unit[400]{m}$ and $\text{y} = + \unit[500]{m}$ and with discretization lengths of $\Delta \text{x} = + \unit[20]{m}$ and $\Delta \text{y} = \unit[20]{m}$. For this, you can + either change the properties defined between lines + \ref{tutorial1:default-params-begin} and + \ref{tutorial1:default-params-end}, or you may pass them as + command line parameters to the simulation when you run it. + + If you chose to change the problem file, re-compile the simulation + by typing \texttt{make tutorial1} in the toplevel directory of your + \eWoms build directory. Note, that you do not have to recompile the + program if you use command line parameters. In both cases, don't + forget run the simulation before re-inspecting the results using + \texttt{paraview}. + +\item \textbf{Boundary Conditions} \\ + Change the boundary conditions in the problem file + \texttt{tutorial1problem.hh} so that water enters from the + bottom and oil is extracted from the top boundary. The right and the + left boundary should be closed for both, water and oil. + + Note that in \eWoms, flux boundary conditions are specified as + fluxes of the conserved quantities into the direction of the outer + normal per area. This means a mass flux into the domain is negative, + a flux out of the domain is always positive. Re-compile the + simulation by typing \texttt{make tutorial1} and re-run it + as explained above. + +\item \textbf{Changing the Spatial Discretization} \\ + Change the spatial discretization used for this problem from the + vertex-centered finite element method (VCFV) to the element-centered + one (ECFV). For this, you need to change the file included on + line~\ref{tutorial1:include-discretization} and the definition of + the splice for the spatial discretization on + line~\ref{tutorial1:set-spatial-discretization}. + +\item \textbf{Changing the Shape of the Discrete Elements} \\ + In order to complete this exercise you need a \Dune grid manager + that is capable of handling simplex grids. By default, \Dune does + not include such a grid manager in its core modules, but the freely + available \texttt{ALUGrid}~\cite{ALUGRID-HP} can be used. If you do + not want to go through the trouble of installing this grid manager, + please skip this exercise. + + Change the grid manager used by the problem to + \texttt{SimplexGridManager} and the type of the grid to + \texttt{Dune::ALUSimplexGrid<2, 2>}. The grid manager is specified + on line \ref{tutorial1:set-grid-manager}, while the type of the + \Dune grid manager is set on line + \ref{tutorial1:set-grid}. You also need to change the include + statement of the grid manager from \texttt{cubegridmanager.hh} to + \texttt{simplexgridmanager.hh} on line + \ref{tutorial1:include-grid-manager} and the one for the grid + manager from \texttt{dune/grid/yaspgrid.hh} to + \texttt{dune/grid/alugrid.hh} on line \ref{tutorial1:include-grid-manager}. + + The resulting grid can be examined by re-compiling and starting the + simulation, loading the result into \texttt{paraview}, and selecting + \texttt{Surface with Edges} instead of the default visualization + mode \texttt{Surface}. + +\item \textbf{Changing Fluids} \\ + In this exercise you will change the fluids used: Use DNAPL instead + of LNAPL and Brine instead of Water. This can be done by the + following steps: +\begin{enumerate} +\item Brine: Brine is thermodynamically very similar to pure water but + also includes a fixed amount of salt in the liquid phase. Hence, + the class \texttt{Ewoms::Brine} calls back to a class which + represents pure water. (This can be, for example + \texttt{Ewoms::H2O}, or \texttt{Ewoms::SimpleH2O}.) The class which + represents pure water is passed to \texttt{Ewoms::Brine} as the + second template argument after the data type for scalar values, + i.e. the full definition of the brine component is + \texttt{Ewoms::Brine >}. The file which + defines the brine component is located in the folder + \texttt{ewoms/material/components/}. Include this file and select + use the brine component as the wetting phase. +\item DNAPL: Now let us use a component that represents an oil that + is heavier than water (in environmental engineering this class of + substances is often called \textbf{d}ense + \textbf{n}on-\textbf{a}queous \textbf{p}hase \textbf{l}iquid + (DNAPL)) which is also located in the folder + \texttt{ewoms/material/components/}. Include the correct file and + use the DNAPL component as the non-wetting phase. +\end{enumerate} +If you want to take a closer look on how the fluid classes are defined +and which substances are available in the \eWoms distribution, feel +free to browse through the files in the directory +\texttt{ewoms/material/components} and read +chapter~\ref{sec:fluidframework}. + +\item \textbf{Use a Full-Fledged Fluid System} \\ + In \eWoms, the canonical way to describe fluid mixtures is via + \emph{fluid systems}\footnote{For a thorough introduction into + fluid systems and the concepts related to it, see chapter + \ref{sec:fluidframework}}. In order to include a fluid system, + you first have to comment out lines + \ref{tutorial1:2p-system-start} + to \ref{tutorial1:2p-system-end} in the problem file.\\ + Now include the file + \texttt{ewoms/material/fluidsystems/h2oairfluidsystem.hh}, and + instruct the model to use this fluid system by setting + the \texttt{FluidSystem} property via:\\ +\begin{lstlisting}[style=eWomsCode] + SET_TYPE_PROP(Tutorial1Problem, + FluidSystem, + Ewoms::FluidSystems::H2OAir) +\end{lstlisting} +However, this is a rather complicated fluid system which considers all +fluid phases as a mixture of the components and also uses tabulated +components that need to be filled with values (i.e. some components +use tables to speed up expensive computations). The initialization of +the fluid system is normally done in the constructor of the problem by +calling \texttt{FluidSystem::init();}. + +As water flow replacing a gas is much faster, simulate the problem +only until $2.000$ seconds is reached and start with a time step of +$1$ second. + +Now, revert the changes made in this part of the exercise, as we will +continue to use immiscible phases from here on and hence do not need a +complex fluid system. + +\item \textbf{Changing Constitutive Relations} \\ + Instead of using a regularized Brooks-Corey law for the relative + permeability and for the capillary pressure saturation relationship, + use an linear material law which does not consider residual + saturation. The new material law should exhibit an entry pressure of + $p_e = 0.0\;\text{Pa}$ and maximal capillary pressure of + $p_{c_{max}} = 2000.0\;\text{Pa}$. To do that, you have to change + the material law property on line + \ref{tutorial1:eff2abs} as follows: +\begin{itemize} +\item First, switch to the linear material law. This can be achieved + by replacing \texttt{RegularizedBrooksCorey} in the private section + of the property definition by \texttt{LinearMaterial} and rename + \texttt{RawMaterialLaw} to \texttt{TwoPMaterialLaw}. Also remove the + line which contains the \texttt{EffToAbsLaw} typedef. +\item Then, adapt the necessary parameters of the linear law and the + respective \texttt{set}-functions can be found in the file + \texttt{ewoms/material/fluidmatrixinteractions/2p/linearmaterialparams.hh}.\\ + Call the \texttt{set}-functions from the constructor of the problem. +\item Finally, re-compile and re-run the simulation. In order to + successfully compile the simulation, you also have to include the + header file which contains the linear material law. This header is + called \texttt{linearmaterial.hh} and is located in the same + directory as the header for the regularized Brooks-Corey law, + \texttt{regularizedbrookscorey.hh}. +\end{itemize} + +\item \textbf{Heterogeneities} \\ + Set up a model domain with the soil properties given in Figure + \ref{tutorial1:exercise1_d}. Adjust the boundary conditions + so that water is flowing from the left to the right of the domain again. +\begin{figure}[ht] +\psfrag{K1 =}{$\mathbf{K} = 10^{-8}\;\text{m}^2$} +\psfrag{phi1 =}{$\phi = 0.15$} +\psfrag{K2 =}{\textcolor{white}{$\mathbf{K} = 10^{-9}\;\text{m}^2$}} +\psfrag{phi2 =}{\textcolor{white}{$\phi = 0.3$}} +\psfrag{600 m}{$600 \;\text{m}$} +\psfrag{300 m}{$300 \;\text{m}$} +\centering +\includegraphics[width=0.5\linewidth,keepaspectratio]{EPS/exercise1_c.eps} +\caption{Exercise 1f: Set-up of a model domain with a heterogeneity. $\Delta x = 20 \;\text{m}$ $\Delta y = 20\;\text{m}$.}\label{tutorial1:exercise1_d} +\end{figure} +You can use the fluids of exercise 1b). + +\textbf{Hint:} The relevant position in the domain can be obtained using +\texttt{const auto \&pos=context.pos(spaceIdx, timeIdx);} + +When does the front cross the material border? In paraview, the +animation view (\emph{View} $\rightarrow$ \textit{Animation View}) +is a convenient way to get a rough feeling of the time-step sizes. +\end{enumerate} + +\subsubsection{Exercise 2} + +For this exercise, first create a new problem file analogous to the +file \texttt{tutorial1problem.hh} (e.g. with the name +\texttt{ex2\_tutorial1problem.hh}. The new problem +file needs to be included in the file \texttt{tutorial1.cc}. + +The new file should contain definitions of new classes with names that +relate to the file name, such as +\texttt{Ex2TutorialProblemCoupled}. Make sure that you also adjust the +guardian macros in lines \ref{tutorial1:guardian1} and +\ref{tutorial1:guardian1} in the header files (e.g. change +\mbox{\texttt{EWOMS\_TUTORIAL1PROBLEM\_HH}} to +\mbox{\texttt{EWOMS\_EX2\_TUTORIAL1PROBLEM\_HH}}). Include the +new problem file in \texttt{tutorial1.cc}. Besides adjusting +the guardian macros, the new problem file should define and use a new +type tag for the problem as well as a new problem class +e.g. \mbox{\texttt{Ex2TutorialProblemCoupled}}. The type tag for the +problem should be adjusted, too. For this, modify line +\ref{tutorial1:set-type-tag} in the problem file and the adapt +the \texttt{main} function in the file \texttt{tutorial1.cc}. + +After this, change the domain parameters so that they match the domain +described by figure \ref{tutorial1:ex2_Domain}. Adapt the +problem class so that the boundary conditions are consistent with +figure \ref{tutorial1:ex2_BC}. Initially, the domain is fully +saturated with water and the pressure is $p_w = 5\cdot +10^5\;\text{Pa}$. Oil infiltrates from the left side. Create a grid +with $20$ cells in $x$-direction and $10$ cells in $y$-direction. The +simulation time should end at $10^6\;\text{s}$ with an initial time +step size of $100\;\text{s}$. + +\begin{figure}[ht] +\psfrag{K1}{K $= 10^{-7}\;\text{m}^2$} +\psfrag{phi1}{$\phi = 0.2$} +\psfrag{Lin}{\textsc{Brooks}-\textsc{Corey} Law} +\psfrag{Lin2}{$\lambda = 1.8$, $p_e = 1000\;\text{Pa}$} +\psfrag{K2}{K $= 10^{-9}\;\text{m}^2$} +\psfrag{phi2}{$\phi = 0.15$} +\psfrag{BC1}{\textsc{Brooks}-\textsc{Corey} Law} +\psfrag{BC2}{$\lambda = 2$, $p_e = 1500\;\text{Pa}$} +\psfrag{H1y}{$50\;\text{m}$} +\psfrag{H2y}{$15\;\text{m}$} +\psfrag{H3y}{$20\;\text{m}$} +\psfrag{L1x}{$100\;\text{m}$} +\psfrag{L2x}{$50\;\text{m}$} +\psfrag{L3x}{$25\;\text{m}$} +\centering +\includegraphics[width=0.8\linewidth,keepaspectratio]{EPS/Ex2_Domain.eps} +\caption{Set-up of the model domain and the soil parameters}\label{tutorial1:ex2_Domain} +\end{figure} + +\begin{figure}[ht] +\psfrag{pw}{$p_w = 5 \times 10^5\;\text{Pa}$} +\psfrag{S}{$S_n = 1.0$} +\psfrag{qw}{$q_w = 2 \times 10^{-4} \;\text{kg}/\text{m}^2\text{s}$} +\psfrag{qo}{$q_n = 0.0 \;\text{kg}/\text{m}^2\text{s}$} +\psfrag{no flow}{no flow} +\centering +\includegraphics[width=0.8\linewidth,keepaspectratio]{EPS/Ex2_Boundary.eps} +\caption{Boundary Conditions}\label{tutorial1:ex2_BC} +\end{figure} + +\begin{itemize} +\item Increase the simulation time to e.g. $4\times 10^7 + \;\text{s}$. Investigate the saturation of the wetting phas: Is the + value range reasonable? +\item What happens if you use a grid with more cells in each direction? +\end{itemize} + +\subsubsection{Exercise 3: Create a New Component} + +Create a new file for the benzene component called \texttt{benzene.hh} +and implement a new component. (\textbf{Hint:} The easiest way to do +this is to copy and modify one of the existing components located in +the directory \texttt{ewoms/material/components}.) Use benzene as a +new non-wetting fluid and run the problem of Exercise 2 with water and +benzene. Benzene has a density of $889.51 \, \text{kg} / \text{m}^3$ +and a viscosity of $0.00112 \, \text{Pa} \; \text{s}$. + +\clearpage \newpage + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "ewoms-handbook" +%%% End: diff --git a/doc/logo/dumux_logo_hires_whitebg.eps b/doc/logo/dumux_logo_hires_whitebg.eps new file mode 100644 index 00000000000..82d195f1409 --- /dev/null +++ b/doc/logo/dumux_logo_hires_whitebg.eps @@ -0,0 +1,3755 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: cairo 1.8.10 (http://cairographics.org) +%%CreationDate: Wed Nov 3 14:45:28 2010 +%%Pages: 1 +%%BoundingBox: 0 0 1600 412 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%EndComments +%%BeginProlog +/cairo_eps_state save def +/dict_count countdictstack def +/op_count count 1 sub def +userdict begin +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/B { fill stroke } bind def +/B* { eofill stroke } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/pdfmark where { pop globaldict /?pdfmark /exec load put } + { globaldict begin /?pdfmark /pop load def /pdfmark + /cleartomark load def end } ifelse +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +%%EndProlog +%%Page: 1 1 +%%BeginPageSetup +%%PageBoundingBox: 0 0 1600 412 +%%EndPageSetup +q +Q q +0 411.2 1600 -411.199 re W n +q 0 0 1600 412 rectclip +[ 0.8 0 0 0.8 0 0.000012207 ] concat +/DeviceRGB setcolorspace +8 dict dup begin + /ImageType 1 def + /Width 2000 def + /Height 514 def + /BitsPerComponent 8 def + /Decode [ 0 1 0 1 0 1 ] def + /DataSource currentfile /ASCII85Decode filter /LZWDecode filter def + /ImageMatrix [ 1 0 0 -1 0 514 ] def +end +image +J3Vsg3$]7K#D>EP:q1$o*=mro@So+\<\5,H7Uo<*jE<[.O@Wn[3@'nb-^757;Rp>H>q_R=Al + C^cenm@9:1mM9jS"!dTMT<$3[GQ$8#0$s<4ZX!SPQ1`C/mioWjnAY&^gM+`4=1jRLW!YA=M/6)*KS9PE`kN%="Tc + _Aoh+fk'&t\ctIN)4XQLi@Xi-8>.nOW?*DmsG$9eXi0S^6MAH;r.U=0:*@G'b5nkBFVsn9B=e5:;fBkOa(K?GC-^h=(,dIU>=;f1"EkIXb_>k + -s^W^^pnX!PjdJ%0OEX9GI`IODGpB_@VYP$,Ve)9)c^;EVg3_OR,+@`"`Y"/@)9.f?D&^M-b]LA5Nm'p6a:\o8+ilD(@3*H&9U-pGP&-[:aqBZ0.E[gTXXh + R-bd?S)cFeue_oLiW1!Gh4bqmTX*5NdkX^&=oc4c/u3R3him5rW-bk4-FPO/64cjJ"NfM-A + pdmMLs9bg+A1b:RDcnp_.8^`j*/PFP,br&5EPjLV0e.i3UV:)/KAQt + c&9S`c]hAtC?S<-FYI.FME/7kmmcSHX88I3.R2@rU9Z/bt[nR;Q;sjr'1j9V,CdDk!(2c>H + kL-,t/#kP<5@U$.3]NMDhd8].C['t#-1YI.MaWTo3RXC:E5ac!9gqWX3IR9KucZ'j>qTsL1 + L8\qgYd2o!LmMh2J\YjZE*GFA2AdN83\5De2n/MrB_=3rim!"t4OhP3Dk'8q/QfGp-=,Nn! + 5T$":TJ=ZBr9$17F1hfX_H1g*&5$ISSqfcp)T3*ft!4&IG98mcZS([+JT``bs:ImL_;;DQ3W:G2.fn6r"qJ(qNWZ"^X"2e52%GgMSLI8Kk4>GbW+p3@B:FZg + l=H*pAdVW@V/b6,Id/'77msX]..QgSj3el#LI\WiM#P$%;hrlG=/8(IU.,Xg*'Ihc/PK!V[i2+R% + -5?]OMFah%/E"FV#]\C:n2_?3DA?SFFKKQY#Z]*PaEE,ZYscV2`Op8p#<'`*lH?M%e8I97H + j%SJe2u1n>R779Z`8-/i(YmPo1QTRrAcbD^u&jO3bbHRo=3B:"d3=k5m:o8WAA>ODR#=e:e + #=XD1MXb<+f2O<1SL8Xbgk+;!1WT2VU]#c[aU7YoZh6[jVZ+jVBgHn1"l^3`Tn*CK%A`g,e + :nh;B#/K[s$*qJRTcQVU-#Dh=Uj/caHaM.ZW\0%.2Y%LYcRnN!R(E>bT&e@jp31Lu<>7EGG + _%hqtC'?o%<=Ya&>)[+W)Id+FR!FX*SIIhHr.a0>WXZPA43&oYKkjiYQ=EnI`de8p)6X(D0P\3;@YW1s%4<:cNY!bmC?0fIT0JlKJ;,sS/QsCSqoPf.Sko`)Y+OD-]/5$ImA/ + =Es-)NRqQWZphASH,I(;Z.-e!:X2k-AQra'QG_>>8i!7B*[qXIiISq,6<.lF'"@g/^a4!,] + N=[mP2m8AWY8D?N:+R)8=OqlQ&4j-uPmfhY + up$j3Wj^Ua=I("IS'#P5Wj>mLdm2=o4bm%%b-UD=0^K+gehqRmT7tB;0&?ac4A$Ym!l>fP9(LWgP,fjHM0><'TBt4U?[H0bp)"= + lWA-NQ,9bXjpm650=[pnchD$6tm^>[8SWX"+Ip\8mL6#ju[J%dhO+'>mBi=-pb!8b?+9ehs + @mHLF-,iK=Z>98$?Q\bgf^Q[?R4uLR[miER%_53\7,#R%+>q*]+b*efYE[hLJ2E='06DMmY + [uXi)^oW+P8?$`!=r^UX?[;e]KK.kZ@\C;UC_%IH<8EB=@uGcNmj>to&(/?htZ:0FgA#=bn + -O<0%iFUmB5/;Hd1%g.nkKD..`o);tJ=a+=k)_Dj/$pYl%Q/]O%d@->,Cmu3(;sdkm[J%Vn + gIU?3iHZP-2Zl\m+hr&3(Sa3J:tj`))BWJTLL#e"Wc/eo(N*l3!dDGBha4)e$P!$^7GZId6 + X4YN>.!g5?DQ>'08;lC;aiQM86\bL-,hjEHUinP)2"krLb[%%69sKBTPJ$'4cRSZ2_!gjH3 + B9:'=,&Sp;!H_37GD"R4qdsiOnWNF5D>kq>oBZc2n^7iNpqJ=%V8on-AU@DD/BdhUE1/^Zm + iu;t-`5lWl+""s1E+HNohc/S9NAU"@uj#l3c;H_C)hgrt"hL>k)jM0MVX)CFXmdtG1fOO,+drB4,X.c*L]M6p + >DNs53bSn,WMC"=FYi>-4%E!SD6t^r@MJpZH]1YTW/gZ5h4>h5@F70!6Ut<.0:""0`cF!>u + "l.MT^+"K%2pToSgiWmJg^p;-CK:^71YA537]aNipJca(Y_f"GrRYg&CR+j:jueel4;f8Y1 + V:ltX;P5T?efI4VfJ\_769_&5LnkU[Y0CYFn[f;G%$a@;PnA,MdqWMPjcV&Yf^eYCn*/";+ + dil=4iR88b;O%RRm=Kr,T^`OH*-XZU&"uQ*m_l[G2P:PSl=&ho%M]CL'bOt]qP8S]j^/6>' + !Om[afp/K$f2fe8.gEq']=ZOYa#nW5nn:(R=WQt^s7_^U`OKS$IA)I$kW!uoL'8$&WpoGnS + o@"FM%!2Z\W*2F#JX31_/@1l.I#B;7!W]\s#j;fDN*FY$B!Ql,WM\M:B'X@NWnbBE+ef+X + 2'h2s;9pY?%R`t;.Zk)`j&,+_>bp%0W,-idEkVU/R%fl"5!C5$WnFh&Kj!8Rha,(e0:#/AIAHZYG'ul(6&6/@?%_Ii:,8,DPj.@=pKb=r9>Abj./hDmfo&22VjB++W*t + ZYYNX$JQ8HI'6-EDA1MgZ(QWt(kE_P$3Knj3E4_:BS,T/S-Q'lW=[PC"&X*:j:r/[O\/oKbl%77:YP[F-sh%aRHgedSog0d+S,lunrf%N + `tO%`27(8``";*BHIm8$ZfSVMN/gEg.)L]5MU6<(TYjf%.o6.>9Ro4+gZX[lkF_4X%@Et::0C`o^FhVmN7DY\-m] + 2J4EEmG**;i'QX2^bk2Hsq&EjG_P$!fo+DB&,r)_I'ltQ7Tn?9"-l^Om*@dIS;L;1s79!`P + 5BAtP&_B6:&CpEOUU*XM!TkE=Z52EC(*L-J:.\+oF\D"+*!%_r@C-b7\f62i#kZ);P@bg9R + M6I]Ojqfk[+=A=0eF&i+q\NKF\,dp[9Ida>GCdNBNC@Y:ACM\!*.9+1W9$,'h'^>&JMZ"UJ + r(qj]oS)!PgX#iB:O(brl`ia"uBMZ,$M4;cGWf@($IgoU>X*5#l1#1e.Ko1oiILj;ifl*l/ + o?i,)Ti1mrp1'&Z"o5uJ(6VAFe9)1e(k#/Y1"O;O[q'YpVVFcfa<3.IT!*F[/2a[h32odkq + C/CMp5F+ZiTB41FiBXWn6p5g_&9OQ,175,GA^r%lLYA.qZo]\oIFH9g]2Dn3n"LbuEe`auF + rrdD9'rH;q8_P)B`CR*C"n-GF2W8Nt$Xkb29ba:0ZhI(3h__UtfgpW6iOg!ncP6K"62"`!! + Pf>`4<%^*$DF=5@3ukcPSh_i\q,uan5.&'j;;=*'`bpdp16O@aqheE!>]`CZ4@:UJN&jn:( + l.dG)'s:ATi93G.293FSAS)ij[LeSR=_abA[G3F;#1Nq5]-%FV[>R6GfkG%J(4Z5a*pr4b(N4[Y\Sd'b.$(W57LrLbpL5U:Dom5EiR/`TQkN;UF;A?I2\H4 + d[M7Yo)M)uC(F0j$G@`]bEpQ2rmCWI8n%`g"t+9sc]i + 's0Ghh0`lrn?2.9Ro94QT_dR-^1cFrR;u3(egt#oR)55Cn@,r:n1'C]*Id*".TJda?9F + 8P8(EjV5_3>dtPCRgVEsB6t=#Oh'2JS/Gl1g2RNW9r*;@ZgPOJb>`5t2*1ef@'(K,'t!:oS + *RmhR)@6]8QOGbL+[P3D`BUZ9j36PrW)?L#j]uQ6/#Rm-k\ + sfl8_hQ'Z=o"cUPof%in!/2^%f:`r8Q$ZHVZPjAEDCo2N\4X#05mJppTHIF%1'HjH.!=QpLBZg7"]$iTaD?qMW + FjGje[9*Y";3]qI#PV9r<5MLY?UBqCp6!Ff/[-KfXX[qLPS+Iqo4pD_fgM)(]#p8#0VLcW* + $E\h"lNFHjjIGif=2g9I\>O/)EU5O9mrABh/Y`,@"'Sk)c*8eD(48ta>2`<]bMZ-@JHb.%c + f)'PjZg@6mO64oQAPY[iX;8nuu(rDs!Yd4rJHP')`VA314(5AciP5fm+CK).AWLGIUH*\oB + m8Z0XI6#&!Pc\lJp);o<(]aCP5r]$/+Y'-=)kf_,_5nF2$5qfc(_%B$\agV`p),mfJEF=l- + F9@RJWHmNTa$^S7WQs80#ju-(*Qp$iA:cY=.P6rV'48+lP/+j:*(*$+]Y=u'7DGeAV;sqPa + a>=kU+5H'>8bFG?*ka!"o>PjbOiE"2IXFn2^`00Etp/^S$:qi/tetTtkiK$K/\7i6B(\gVI + q@%^*/LNV!iC?5327Y15Rn;BMl"bEEml.O.n%gd'ZFkno;O^'g;/J/cH[RK;=m`MkfuR",9 + $AR=:R^:/NXf";-lX!PP_a0"RREK15gMo]Pa/K3NPUZUJ@c3E'UdV\6hTNkn!)_ua@)dBTq5_-rDu;ar7CM0*Hg\95^&2$R8@8_V(gLX,>=on + tD57LUpnM + -Ke^I__pgP;9$cBlh9+82YS$Q_!VR-TY#=YiM+!),V8e]3&0Hm=oA*e1iR7i:_P#BRo+L`S + Ha-YOHFk57[18=c]mLnjql\]:p?Uou5]<$n@J*g.]h=l(j;n?S)F7'KJ:6\VmMiu+#6CnAT + `Lb$KBjl)l[HDf%#>Q<#f_KO@15\*%N*'<0ObFrP5ZlDM_?:_\C0Y%\;r6&$X"+j8@"F1AI + A62\lc&#.]*Q@P+H@):7*"M5eQ6'3hB.1e/@W`JOH2/BbYkn)OX0odco9E&pRgLF1C%kQZ[ + 0(?$TN.Q5?Z_m:CZJh_phOY1WW\5;!`gmEXqp]3XkAQi6@JID9.Y(`X;%$&-9*nJIcs0,]b + iA+CKWQ;8rpb@U8_SCVU=S^1s@)BBTS8YsmMobsB%Y?A!ZC\A\e(#J0`@=U9US_!7T^#*c' + *$,n,M8eLpq&Gu-06.B^%seIePd+KUlWN*\Qd)trE4T=n2gq$RalW,>r>qS4Y>'5NRm%hJ[Ge(:nk7=*cT$(\@bWp3tF^K"@ + EbUn3QoZ/>oe^G<]^X5o0MUKH/j"EM>R#)puLP*CC(%!k:?G$8>dU!",j + ;jk%%R9(8KJX'A1S,)$hCGK3)Y?&7V>&,A`-;AeH4oU4rFp?Pg.]Kd\1"e813^kn.Zn( + 5XbVPSP]F?<5hFEEJSC8Wcpu'XpA#;A:Ig//H_m"liD8GD5:,?E,Hq?B:03eoosA_;9FpET + J,R;!u6.mSC&$n:&Zd%-<=TGc!!L@edr!NFR4q4-*n=)r.r*dPaa^AGJ\nPrI,6;3q[dKTo + .LH);4^/T]V@4CU;TH_4sI"N%`C@pY(YkjP2AV+QKT-uhT'C=t6'1QFkP"kptYXZ:S#>%_X + ?OgQ\Z\.&rXY1fc%[;Bh/XR/NEl+ab9qkfAkmcL,4VdJV('r`g(%L.01r$IF_nFo\iC"NWV + )K9]mK#>@^qaT8^Era+oN,F + <*h:*c[CQIIH-$hBh^NWQ+0h+I].;&8%>"-!Y%_#$kbNh:jhI:$(c[=j=0.%2WD=b + Z75NMEHK5[/[1\S(_BG3c,HQ%-crF57!IqGBKI@OHg*`d"[."$fs#!=&mr",!7a%&>G3MWj + Ookj_(qFUZNTfkX5sjjQ4Z+R#L5,D;/E'1-q@$=Qma4QYBNOWf8+iKQ-QOO]Vl5\git,Wcp + Kob8F$(;j&8*/0Lj,=HTLM+_5kM3-F=)phe'?BEitNK+=RA:EWL609e\;CBHTGUVSb^2]o> + a,UB!U6=2OMfH0a-<0P@%`@sJ` + 8`2)!Ml(&9/#\9`gtM_LXKOSIPZU#tD_4%0J_$MW^f.*jaL"`!u+7V0K1Mo"Q>dKOLr>N3T + (.aB;<4oaA1#KE]LVIH&ppaORS[KnA,rYM`E`;,KMG2#DcsOoL:^6U4'Z^(8lIf=/^#=l;YjfqBp$"2 + o[H+SF7M-Gq.GL2H,(5ro@_Z<"Qg>qBQUM0@>#7j3HA3X47#(W#$L$%P + <.BOqOZ73i9N_+#%AR(3gt9OpjDB(d:qf:+R1mjn^[2jP]K(bigH^1EP7enH"MCIRrTH.r1 + 'R4+Y&fU%d7Aka(8MP)/I(CCN)-\Vhq&jpV1JfAKB09rlXK^a$kd71lTirO*m'X5E)mfo.b + *G8-9;V>juJoF`u_3d.d*^]'p.WEr'(h5SD,)Xf[GMJG9dG$TND"i`F + K]k$/6^Vgi1#2ujWYg?LPs`^jHCh)5VTsOTF)HER\p%'c_BlAlj6bgLOb&E>HTN+o2fI7X.@2Q,(9P36T'T_^#ne=?__F_b(947:b+f0[#eY!%22kNr6$F:Vjf^G%!faHmg^<#'RP + 6*rc]6QC^E44,#@Q5gM:;5u+)SXMJ%Yn>m"*@KTgIrXn)enj0;lZ_l;Jae+#]*Nm$D55$GG + .6lahu<*-69YTWGV?sFi[=%T.3pi3k8q_6K,caVO8+1!-5-?DBIcQip<^i%q>[kMQZ%4Sb1 + \B[?e(p%5Oc7'm7V-N/,oa:!%\\H&qO8g1;f5QLao1o5[4LYC^34Q#RiekOoqSmF/UC#W?$ + ns#J36VZ`C2g7u)&$"n6R:GUkos'Km#$#1ufT88*_89fn28XrC/m4&C`+]K=;7]@gXTllS= + \5;:YNp'6,B"^IN?Hl%0I,8[nK:)U4@@=U6ejjBZeiCmn\bi%2hA%2F=`PO74ZH-( + hoR/lphn^k_I@G`#/c2$qh>h*ep_^TUJNY,6nUMVa%rt`1[CM-qgI:oFR5Z#FkETHrgaJ-3 + qX6.#OpSKNf?S&4O]P!6eaf3&/DL0SsgnNO(C1HS^Qf1o/%O`I)8eChH4bBHl4VJZ/q1f"U#09bqi+E6"<[OU\*on7m41hXg]YWZW45:p + ul>XTJ^_TRE+%sHu8I-\R`/,.4#tU=/WVdPMrIldPKaV?P]?EUrnq=5]"J?T%6nudRL4r/E + W/GIMdYe@*DMPP!*Fq2[o@bVrBZZ4$8Xu5@W4kXoMFAe]8_XT&jIZRFTHKamWSs@=FAGK8U + ^o"I`13D/?is#HCT;-IOKthjdP`e)&u!rE8ne(?1fP*OoW,=d9!a@H9X>-8Od%YbLH7s+[' + pr5$-S1#JRl>;;0&IJSJ>WEZ'1hA'XQ!\^e>0Q9GlF%R(UG?CE5fZ-$%AJe#Gn'6,r]'LN% + /9+EE0D-clKG1iRAd^_2C.9*Sd5L!a'oR"EAXn%m3g#]+q6CtDC/*l7c)66(QS46Ug + VQK3e;Jc%WfINS+[aK-p`]-'Nr<KT> + _G:U1:E8pV[%%ZT1XR&`eI.q&=p1dNXJ",(e+.TpYTJQml:&.rFU$Rs3E*=,M*'N%R>6e + oQe`5FIMCU;bZ7aqm_j$=A_>DJUGLNmhDLiW`e0@",P(Q`Pc%RWFWF=%W.Db&YY'bdQP_(cKND6of:!.^DEpj0B.0Ve@Kc9Qd94S1cqLm7a"LU8pBM&< + =+jOpf(T;t7kB85tGRcTMD1X\0Ql!`[6"<8"O; + qrEZ/7RgZT<-8$t2+rrc.KN+K#,P?4@Bu+R)_bs:(*->69a(^F##KC/eJr2?N*2:kVh/bo5 + +jQ0/7=0B@>J<)elnuMM&oJ.;,u^lm.G!dX/RA_bmR9f#RH3;PboMA:)r,J5VY[468(]+C;oAKE\VrdjS_ANq(^&;*)qHFIHG+TX+Q5O-nN56;CKZXb=e1u + jPtG&YS6iY/8V!%A79'^QfrW!>/Y*A$nuDPj1+C=/m1V!1&q^Xm=ubMA]j/_op!qlm + 6d2om]X=>m$FEl9D&:dZD?o!91\A^>*?3DIs + +cDR#9\*lpcaHE97Zu$='/(FfPH7,/;@hFKgaB:_W+`4*QR8i>hNU]FOf"P)i-i?h&Xp*Z> + Q9AE??T9]GOrjKnS)\PrJa*/BGa"X8kkI=@rVE`Rsi,"K2`a5]$]Fl-?5PYOH + il"I6lpm_c*f6bU';Xm9<,lWLp$?JY#Z80qU=1=(KU^/@7uAaE!bUL*0AS"M9saefQPF%bg + V/?laIqS*#IoaUl#9*A6u`)^'lo=f>R$QTQZ(eS0tGfk75"BG_'i0>(/>f=l!$J_Ys\_<3WuZb"J@ndQ8^IaEjJTkm, + !*BA*'&J-=^aRP\EdcjW<.l"(*p7l + ?SguU.eb8dSI_gn^oI?Zc!Z'"?,i!Cl(&SfJZ[Ipm4Z0<28)'ZUY/7)?AG-bsJZrXVG5chq + /c4M(G&j!1/'P"4Q5lZV$Fp==6#8g;=`f&fI)MXne"0QWQQ.$KXJ`m5ck?c/@A09uSn*kM-sjp*tlLA7ug>=@u&qC3qi.FJo&r"R\(!`SLb08 + V?0?mFcb2p_8KKEX(Yf2V<;s9pIl$kij9&:Bn2oDWbn`a6@(A"/b>d*p[^R6#Ob/mb4._pc + iZk5&Uf%&:1S1.OE$+U49Hia + sMX@lm&sNctYcHpql;diF7K;IN&[s@nLTU]3Nuog19Rn6QEZ1d7U30k$*h%1=.ikNuNu[_? + -ut8:>IA%>_aG#JhGnYcu4TI&I+n0K8=&m-suJk.?:6k@G;oNF?K88%QLYIWA#Q%[SA&RFSpLEVE&"l + @fPP'Iu4L^Bf)Y:V[k7GS*\Lk[l\Jc@LuBVgJ,>D%$\$H[h?0R5frN'i6M6^EkD(6Y@%.J<,"qaQ1O;(-1,#OMOrB.;.r#)6FFg+rOi[!PD#q^lBZC7M)T8VUAO(!b86@Mfk]JdCm]QH=$2$$* + %L75$c]"PnuhYYYIi56h0\!6:c1D;kd[C^(*((_l9@SEs3F?!1Is#Mk_Arf_VOd!ZGP(oLj + tTLSl?FI*nk(:bA"JS5[L,COdhI@>;,YM0YugjRBlQC@.fDr2p^C;O+hrYe8(Js8S$7p7&EEoQ7(e:M1W`X]p^_=c'cLqboCstT(.Tk=o/o-;/"I*74+9mVGh3YqLq + D8;lWi4eU#2H[9Ke8CXr:cL;dZ.=_uZ97AeUNW5#Icqh<&ODkUHScca;io0As4YLi[Kk61R + s@;"'P[O(6tH=Ls@4G1g&mrC8?bP7j3H,k&Yh'reE.je)L%&>WrApHH"^FR/R$e,K.^p1;)(J> + ;OdRp+sD=p4(&To4\)+?Zr()o[Nki>-Ii)!5MDMqYn6*\YT(j08Z:p]gcMUANWFL[c+cNoI + sRg7Dq/h_o7j*=%#J^8=1+q/5T248T?ph7E!\`O:pWUBO_Za$XhOU9mh.)a-$:J+NLc-JCP + W.0Sjil7nMA\S^Mh/aW=,s(4*55ZO4Mj/->aB02'1KI]d`1feUe#4OH"1;dA!6c-q(!36kH + mkrR,=]^\*49DO'$V,R-D6I5+FCn=;Q/4;@\, + maOtkI-eHt,HP<6:0dX&VM48[=MmS`(0s:]_/ESEe_+YCd3XKt&J`8rLdY@^[?)$S5Rj,Ig + "(SFB\H$K&SdR(\^>5kP.LY*Xn0fRE84M8aL!RU['V[GdsQ`>M`I#7M+LnY8BQiOdXtGW:q"B!)kLoicT]fQ#M5'4f5*;Z*8lWj.Y&b#h0<\%2W@f8 + je?m+IkIduJb,fq6adiXpc2<]JT!t#e^drfOhi.PN_m.&'PC[5R+\`d$jnT88ND(5>fr&r?k1`^^ocCq + 3hk-.eH&Fln%8KorW7=f;53P=P-S5h[O + B*J*[^ShY[j!81f/:hoG\?*LJHu/s?^,<"q=QN$=F\+s!a"rsu^So*%$m1,^0 + "#GALur=[?f7t>S(Q=e"=0nC/%:r2.j(#bUJ3""t?_d$>G6\E941,jmALpGbijM^AO\JHe + *A*,"J54Q-?m_#!1_QPt8d%_n2JM7)Vpt,&$(ssQ#8l-AUTJqj'-tOpX7%9/`@[q70[u9p] + 4Z0HXAt^sf1]V4@^@rbNkq_G` + u(KS[0%+Ag4;0eYc8+a,[cOBpo0NEeo:5dGN5+Hi7pDNSFbRV39.`+kNL'/BCP]cepS`1V`%i_U(nRX^4%ArG'VFE.acdiIkh?)^Ch`hcbp@1q&\C6_W + -9=u.@od6Nbg7M>ZSOlrL/bN8rg*87\q.+!G4FO(g>\fmQY/d:Pf@p74C`Rth[O/u(q]5?n + _Kcr,lM>o&?DGPeoX8Od3jD&8a,U&*#9^XP5mP-O)q12dSa)%C>FZ`%R%s`TG3rhC%[5&LSl:cAq6#n@*\5Sj>#>THko;`+co`U.DL]Z:^3l(n:85$TC + 4Ap)cAjjmm8V?Gpamdjh/?\IT=D0+D'ZWbk+4opF=B5\`DQ'S9(9EcM?WdX*?[3@0-7YLZT + :)(g@CZM3!DFrZ5l#b3+fT`\[oKXpUUJXD$293!?0")T&:(irNkQeNE'8e#_cnY18PPiRCj + `f-"TM5EQI-r]N7bb>9GD6!W'k5KEdQAP*r\@/a9OH$aCQOhYH)B8s2T8&k?H':7a]R2^*N + dSYYfK!SS&c(c-7`"ca`d."G?.gl3eB94P`:n`/dH05L=1$nND\e9QqXQ]YkmetR&!s>'/M + oq(UipG(Cj3jh9HD.7/WaD+(Mk>D>V=abbeb\(u<[[<;TC`c*\W2]f`RM&!>. + $@0@p%tY-`,e4OOp@6'tO2noY,S=?L0h9bD8\S";F!$\iBq+LsKZH!.+`8QiJeJXMA0E"8] + /([P93&^i-CY$F&E)qZ4Q-$;`p_Tgo.a?KH_I"N/T=mQ`N1[,.b"^l..1c<(QQB$Y.p!K]> + fc8q)gd#nddj?eV(0S7+BKG#ts#6I<*-<`_";S=7:"c)UOd"Mg'V[ANd$IGs^:4OrlK]m4t + IY.F5WJ51U%`=-fl-4TJ6/Wc7bT-_3%n=o_:O)Ufju"88)W=L#JW^iNAF,0<3im5d0%UY#I + dO$EmHf:A@s[QgX9WWk;2R;\ZnP-C\M^#Fl*jMtC'[]VhAn + ^`C-LiT=K8<(B_5ol)3'G;u4A/.'V-6o2DKkA>A`nlG@8-kS?D\l,VIX3M_'fp>WY0h\%Zb + rSnY/Rj8-f?a*=K$L+ui1_W%i51dtd!WKY7-*6G_$R*7<(Q&^qatekH[\(\!G1m0&5u-A(Y + t1/">`OLZH1pE,Oo0(+Q(d(UT?cKP`mCQIWWa4=TjY5aCgnVB/H0E``?[T08^i]q?*&`AXV + JKot7YA9^WbQA1afd)@I")-40:d,4U=q20KjcC/]UUi]"Xs\qu\kVP)_Lt3M=99+]V<1tdd + PN4GF;ZMJ$*2Z'ZBC0@hQe7C.FNkL!+dWJ,3D>"k(3$adZl=\[k!WR/(2.,:p_i/\EJs30J + LHgcBakPbh'"hfcei_Tfa;\3X'N")A(se6*q=KL)FEG'[Z^Cd/IuOSIfiM&;-^tYn!AE9Q@ + '(aCE6XXGie>nIcEZaeS:mm%SS,8p&--'1''ZOZaBJ6ESX+?Oa0llO^;D#rRoN2*oC=lPQn + sO_3*P'!_4Z%WZ;R9+&H,j'#d(6pBu!=_ + Bcl)#-O>RPTrSr@\S*pkoR(t(k^lL4ldqM"_nXV-3[@!4sh$SPVUF=*kC;=,Y7Y:1YkGeh' + D<=-pXb#"AGXIK&*Ki!3&RVNtT%iDW0p6A,tX[%Tj$ZG4fTK.DQlP'B]Uam7+HQ8BZ<2_Lr + 6&QU9\&%htVMV=I4G)CR@28G')Y'XLG3[e_fT+4A7R,5D+TgqsZ^rb7\:!TG,n\em`S'`a4 + SF/l/>]G"[X)<#BDdf:4t!**O&0\$)@cJ1J$'K\)h44>7CP9S*,R1\l[&gQP\cMYfnV)-F( + '4JEbX"*Wq8/sEZ*-5%9F',Y4cEHn5YLQ+HWsm_RB!'HaNjE[P#=:XRGXmThVc0Qi23SM91'E0T*'pbi6/!]IN%ta04:7]"lCo@t`%OoXIl+]@'u + O9Va'=;Z4qoY@:-U/rIVB:BQ`5K[#-oq9Y9>aPB$0hPN4Q92`'s,2P#H/S'lD&N3C/`J$hSJg:V + Xj3Ub7e_+C)Ah?@+V)AT)W=F-N3?H#u*tU);Q@ijOLM3!2@l1km#,`SC`r6B'r_Aq7WL,G9 + U248^(?!Us6TJ9b[n]:W2F'MG@9a:nMATcNDm]5/,Eb(Zs)!r03WkJadhbic'f!Oc&VhYfF + Vm9#Q5U8/&r`D#Nrq2>X&?dC;^&hQCF9fcBX*gf*4&hO$MLYQlAk.jElZ@DKTt2+!3fopYk + )C;Nu6'*?lLt65:9u^U`uT-b"o9c6\g,8.ScfpK$S907FR]O5r94*'H:I/Rb##PFKNLuPI_\EaX7DLJ++ok]')#T9Fk$`*@m9QilDiCT59BJ'cP1a0lOa$Odl89P0(?,*nFmM/(DSd9))UXnYi9eG-*j + [Gl#Pb6]'HMHq%JlbT9/$ag,CitCA-AU4]Q\mZAU2bnR=^QA@*T'(BK4L8k)lAZPt'@.Z.H'k"XR+[M92n+Fl5q'*q + #M;ZT$"lK%I=,Js91""\_K`ZOFRb,]G!j&Fi5(pa%>Db($i/(in&!pX.gPd!R2-lp;Diu`l + ;lr4"8VBHO'[j&lq(:QRMjnI/)U`f#3CBQTZ<#:W/6.W+.6g\RCG[VQqa.IN+;q1U=m[85l + m`O0:bDd8sAe>H:+K38#25&LWgot'ZGl.jI!7&R5E<+CdK&VBH1GK#8q0?@C-^KJ/LXp'J+ + $XQIbG*_5Nf\N%WXW0ap,'K^L-VN8r#WGh$_c'b\%7FHn>M'eZfd&f_]'**4)?eNt8/!Z`- + _Y%j#[)&-F]ek,7sXkM[7J_cH(Y1d#&>:lq]9-87Y0S:`J5QhWaPC6KpF]Tn0oV[:cAI`t4 + L5D%N(udg?6#I>GZ4?,X4)-V#*Po`66-9r>.C/h#8)_)o5Zeo + ?BG"F!D*!?j>)gL)NlP=eN0+aF2%OTR+;m*,me6;A37h$dTi*[&N`rZ>pCFOPcoE&gDMO^$SI*f8JYh$4nWPOmmD5n2g+5.M + +m?6/G)A?>gM#@'%da5!@A',4jTTM*2/*!B^YYNe9u>Fdj#Yk1WPjRb8bk.Wn\?;*@'!<-7 + c]>,?nl8GJ@ZD`Zi:k[pebuU3M^cY(MB!;=Hr,J1nFl:]m7l68'1SiuMY(qpL\]\knI!J?F + ,C?9ALLi/RX94bC&:']EY@U\Z7:-9r/kQK=KA#;LhUW$XJ%Jc\$mAc$7O]kb2'ge.>W"N_< + fJ1Ud5@ZYV=L.^6KoP$qR[G@D.h;-p^S/?_O;a<+aLh=-54E,Fks?7W#;u\ZY`WY9ms`_7Rp(7R+@Y4cR;qX% + 5SkL)/"sJhL#*q+u?@*E[5lVSOer5o(B4-Q1]$m+r)g!AFXBcl=4.>!?eiFfVm#mEmZ)d;; + -`(TH74p[=^N_#5i+*AO9KU:o6Xe&)#1`[ZnHp@3o/Ei>45]iHE:]CL_D^XT0PRK'pKr>bc + S#71G>"VOh1cp9pBoZgPM^Jb:A1M.2j6lhFB_X[k[B'"R3p<$9t^IBQq%K]q<^0S"s+#um/ + KF`QiOY@U_?]t)d&-@?MOZu_TW%KGqC*Q\B^qYU#+1(iOZ/.sDhNu?\F9`4nbMQ4QI::\Ei + C4eQ49aRJ3aaW[_+Y3PN#0V%Z$"QBU0\02dQP!gtfs + +S*?(i-L`nF;@S:.F]CIm1ME2-2#Y`dFAUdM=&g + 9m8Tn"f[+BFaT^SjB.ZYLU?0,4k4R89aV1-6hYup?-g?iXY<-VB5o[dq3fgg0]h%TN>mE%5 + )\*oL2j(AB;CI_Q*G'l`F$Y2O?N!sau+M#TGEf1pC[kOf+.C4monp`opd3`slD),$dTLg50 + ks3^O-l:NP[Q?X\f5\1RD>/CJk5TkGhXL:[:)=_(j&h[$s6438]V1R,4,:9ph'4;N"e*+0tfC-lZtoG$[Z9**iYK?t"CfR)#P,L&-p0>#R+2$q_%:3$)\<1/52A&Sk' + c&M-A,nMfI2@J.F@oo;-M:WH9N6<9>[',:[r,EYotPHN2\jAR>dlT:FBph(fcaY1F3/Z50+ + I(544gb2mQ6Rg`A(5f]TFI#B<'+W/IJcF+H&3kM!PJZJ*# + g3G8C@\C4`9;:gTY?$mVPFE6hSnE&RiJkojQ@X8&(AtQ3=dAp4=jC-g7>Ojo70u-*'7DSie + .TGl-e9)%R&,Z533QtPG.j5Zg1"\TVHN8$EnIFnb5@BO9N![YH_53kiptpR("_Yq5*r24HG + 6klm!CqJ\*eUI4@\_l(2(Z)`b^oBPhLn_HL^m^PhtrC2mgN-.N7uVUDfP^>Q0#Bnob@Z"N! + #_i)h(/rCjE+"[KR7\6jdb:+=Hf^[SQ5>f.[>,Xc]M2,Z5Tc9.^'pd`Y??NM%O-8juadK,t!np#,6+3'8CI)$Fm&@ + N1dRS!FdB"J;4q?"'7)$SQHpm'+purB8mM#2+G+/)e3u.NXMuP7l/B\*b+bJD^BcaOn=d&g + "0TSq1,"o#X$`nnK%e>\Vq+'3(0tjG4J)WaZ"1W7dufL6TA3SAj"-P$'&SkrKW<"HRWM_Vj + +KI:pT[4bZO0fArPAp1R<,MLk-0_KNO0FU,GN]S,$b$NPr3DS4^nJh.e8+WY_P:HAYiY-9:0ULWiJ4)7>N4+-PjoY^71$ + 7ZApJa!m8Uk7*VR\G=%J6K'SBX\6I,U-qsD+5r(m1?:GB<;\6#Jgu#%)S+9DYs673Q>]otS!Q#2rfKM/dDg)5aS + u4"hoE*ga2/VJUU8 + ZY!X5jrPaXE_25bhPshi]=E67boUuMfE:jDUK-10N,2Vb@,:)B^)]X[]\,Ea + odAO;Ee%>RO5m/2D7:1I@qb$7r,h"T2(QJn3=:CIfn-ODqihIkbOTa`tiS_%=T-hj#Y#4H> + QQGc,#<&#f.pf8[n][i[bG]_S1@p4(F-"^=qeMe-_2k-G?MV5(VkB64a_W[E>b0B0@kbL9( + @5U0!:BDrNcnV149aKF//_&7?DVpu%3[@NQD>BJkH$X1Rt$V"Z8(6_mS>.k]'4a>3to2dCJ + +a[s=Es(5&\j_.=AORt'/+%@8Bh_D2m6-Ps/A(,P=u+Ak(Ybs5p>8-USZBTB*EN;SkFj^H< + B5n0crkdp.)kX@8Ub&Rh1]eoKd/nOra + + E+Xtg66Hj?\0%`"4T"S'esP1LSo?SH/ZRSZ=9nu``.,U3#QUBp+W48H[&'Z!``_.4=tF+QA + C+ZN\\Q`Gn:j0KQeE\=DNY/mXO:iut]:koTVJP45"j9&"]."0&7>\%Q'ie[3IE)0IW-0+@Cl;`/LP%Li,81<^EB>*U4%kAtF@Wio*&sBQ%]g]Z:oFbA_$V's$-IOMH?tDt?R + NXGN)7MAi_ko5/"mPM[o+Y'9.Md5dO9q$"NX.;3,Skl&&L]Y`S;]6TQ,[1/WOM@Nd.Q6MJt + ^Ej"m+a*`LZH]0&B,BZ?9N`h[@hZ0]0N?be,]Pu=Eb;f&01lol*N#OC@S_rD).j9[4ia(0% + NYTQGgGDl"a2'@?660ggnGIUi<]n>*1auRMtB76Cj-Iqn@ofk-1JSUTXk6TLqh"[ + 8>2QS#bI4cl`:_hO!1MeKBF!-"!WG8,D=<$CW38# + "bnE8)/R.T^s^DXoKH8Q9[_[O\W2YK=.qNRb=k_aeh?2XqVF;Oi#l\U>8#FDT9>kb2f_JoW + QF=DTUQTa:&sli]T\cm(6JZ#I8HVRR9'MG_]sPL59l(?ms"\]Y)m6K]jB[3*ZUi.1n'(6F= + V+1b8h./L>KkRB)iF#\/fj)__Yl0H;o:;k_*=U;Lqd-7E`8E0nJ;VM>JY\r!RWe#Ge$%D'F + f6k^+ml*`$<\$L@j6rJ%[A4FL`k=sI^`.C4?j@TZa9+%B.$9WX#<3ZS$(Md33k[1n5?!J\dN3;%bug/J:oCJOd:37jIJ!VuS?X/]_%&T?4.R(j6J+:=d.ol$#-Rspp_6aT;$96,%AZQ + @snS\;3&k[clGa.[7f#g9Yl+]d01:a6$pdI5=*FW_;)Mq3-Y[IFpC9A"A!Kb]Nmtf=O09h# + 67AHi+N:%3/.]GL$JBKUR*`)@#TcUbX%U_i`HI#0H>Y6b_V"tR6NWWTWg8s#,+:C1sQD(Q3 + h=46`=t$CC8Lo. + GuU;&\#M5eCL>'h/B;%rMK#W,J6lbOtNJLD^p-pZ*,=AV+i`t_7h"I5"%E(PVNo[T4h:H3= + t!#d:/EN(pA.7e_I4@HGI'sj/%WJL#_E]Y_P9j-#;g#\gYo$>Z70+g?$7fu6&PSZ04IM39p + $^%G5PNH$\(4tYIPOs.%fcqPT'UN%F!PPXeoris)YdZS=+:'EJbDL1[7 + /lD%pN]YJL/TmL!PB/d'B3$hfh + )dlMlJRB'tFnFQ^%UbA/i2-_['4h2%Z%P:MR@pbs2>pfY#,$?e""LHXYRjpJ)%AUW$<"E;n\g^p)i8u+Q="Wb8d"p21)im;-25T95>CMU?i7YWWupKL& + c4I+f31Ws&U35) + a743>RVi4B-RU`W'OeqXmLl>5f,9r/",rM0;14[W.jjD%td>]R'NBg8BLt2^%%-#f?js2/V + -sqW@2%"0p0c7":S(1e#3Z^pU;e5l:*c'oXD!';cQ(=T%($R:*"i5!5Nq%T+\K)HNZ.b$M> + BgIBV$r+tZ%or"\+t(U3n/fZr82&R$NY_eiT6_%HF(ui]OZ3g5(i%`QW0@A?elVn+p_Iq=' + ^Ei[@"s>SIGpNX,D$M^Wl,08G5",@L'[1]4>D`0BL=X6@>,Yc%oBkMD-S5Xo_.<\lfn\**mjMH"fNTeY66b_auIU:0utHX]c\`,ca-4 + Nc?'AOK;p\`&](Y&b-FXa%qat3lXmTkG.4VBS\^g7@Ra):o\a9R-of:jLELXAb:j(%@9FYf + js\l:pe(VfB*E6;9H5MA)H5ZSaR#3L)*To2W*lgK2QtqJLe4/6\7O6B`n$9Hbfsq77;W7k5 + 6hBX0W%,iY]EN-Em7f!jBZo*o\$3TR+I'af9/=!V&%1ei1H%@6+/&h)X=q^iMUMOME.jJ_5 + l3BD4F2:Ltd-cn7+Q]P*51s]HPNN@F%&0mN!b*SOqA&?sSGU]PJp'mpV*,\.8g0D[(=XAn@ + =TV#*VnC/_L0l\>E-pAaUdroWpB`p6%>9egE_B]gLdWh?@1nA]2s"Qro&lBjptHhWCZpu1A + 49,)CKpCG;f&fV[q(_h;[C>p@_a%()!EFH,1@=^B)eE@@lkI&m^2:p24WIj8iL`o1nfVf/@ + X.%9W^3(SXOqJ/^'1"OGUIkcCpF!`#n:H5;-jWrkK2;l1\MJ*pn0/oFL7;@,kRTY^:b`u2! + 3Sr5-=I9Fh?\R;A9SZ"W!_+*5F2j$ORnh3aT4`2!j=f#9#``W$jaFSOjL4ErL4&MmVlia$a + HO5p_Y2d/hcfm[J-gs]<^DARKU21#i,:$t,9fB\CW%=j4-:'Vb7#4,.j + ]MSsVTB1$$=EIN$W66S+=%-fJfWp5[EekLJE)9U_7\U-JiIM>eXFl;!n0W3me'OH[F*Ca)cHncRfL52#<@!%k"0*lE4G7'UCKd,r!7U@^-9*-+IOEF\X#S.@C`jVb^F)X:M$&7Ar,B+#lp)7'K>(7e887;3e_NraFH:Pg?^o-]bdV,FeX[TtPP^Eul>S+^\XS5*\bl/_ + Pd5G1eb;lQS8>q;2_,dJiYehn1u4<]aK6a@!_FB2YS[hX&#?Z&tM2E%t.V.XFE=a%5:[>/[ + -gfJ\W^"$h@=C,!o[8$g2k7):o4=_k$1r-Kd]]8HLds1f0S"TcE#/. + D]t"M>FR_1kW'D*eL?db?'^]CQEJ%siPiV%'`5Cd\9)70H#@/56en+;@dU`J0%NYFFgWRgQ + %>bJ+L`6gSq08!3-B/4^./Up->D4H-578TZO'8WQPaIPE+TI4<42>cf/?LGZBk,P"`KT#n7 + KGakN39[,%]&t$9gY2u@62k%(0aZ6@LqECjE'?nog^k!3t\!Ucu"Kn7eS@Y$1'+?Rg-H+^\ + 5#Mpl;+[#%WF&<$A_P^HRrmr$F<[[[RLN5\ND0Jj7(a@A_8].kD*h'n<@-$foUaZ6ouI,Gf + 9$*=67Sq`B],eRXc2\qL[%nD=d@M[@9NlKUi>%IF)7'gFtJaE-`3`Q"'cU`")n7Y7EnGh`H + B^2R+H2fC0HB#@2[X:qh5M*mB^&:?\DGXEBjL6"AE3gI#N-TM%b>egN[TVLZ0/2-&LWKXt> + Rl(sY!uj-]6tO18=!r88q0.JT8Vl9\IH,`*.&;'UnGCl5ZVJ_kR@-932g'*/J=dqBWuK[]. + R$SlI]@Y',:Q3HuP?Kq^VV5`a%cE4.r3i9<`CPD2H?U(Cl`*hqtJV??`jroYjh52oh\(.k= + RCS`tXa"Kt`e9%3)KUb8ZC9>7`ba,$gk]lg&V^"!?7-g83Lku[bCe:5P-3CdXb*G'.P7AEM + ^&MMR\mPSE_>/a*DTR^AI3NSP@TO$7G7Bl#"b",q-$OiUcF"lRA,RrVQ.L859Y:A#58L4?0 + 73=\s]sDeme""q/u"hhT9?rh*lZ6p5H$qEkN*(LCrL%o-WP.8O_:#O+K2sD5dgY'm52C0"F/UkK*Fldd#u=]&L(#.:F)SW?:s9Np-lR;Xb['/@6F\?1RlUUDi;9o + P#UU*Du:RG!=@Dp[*2-mR>7.="`W?gRNC0+Spc>qGt!*GDaS5E + l4Br!WKAdb[GN8rmeU7#%lqLJ?W1hW:W,Rq>Ai]Y\fsWG?XsQVn3^HYE=,j2.#WH8S.ps9S + MLJ?%#LanTC5Qh$"6ZH]3h0,% + `ea5!Z9UtPHu=MGpY1Z];pUH2OD@b2=KK8/V5OJV0hEpo+d`$@@b;u=oH@G"W.:'/7CEU=q + =Na'q8:J,>,/Z&*ZRF>+.\.e%D>-"DJAWE@p%q;#7LRU+E77;5+@3@NtYT("DZW_-#3&O,9 + :U.dbPI7Dmp%ABsV3J>n2;2BcWK=oYc'k)[8oS!IANNh4ppANeSP[o179W\7#ZRc.;]K8rY + YIN`Uh;/BlDX#i2.1Vgb-jE?4^W;!FZU+G>]-)lpoLX#f%^L;>:XT^-jN7;PX:;,8(OGZ`B + E.A#DPPP]*E@K&Juc^I,Ca=5GrA+ZQI^Q>t)Dj(2@)Uj,E1O$i$RD6@5"HiAUP\MshE*820 + @FA'MEb2s3Y$9**Ge2b$rQ[cBF%)e]nu\&S8'O>rW_(M,k=hbiZE:TAPDXk!9g=Z_s>l/iZ + h.'b__aVSW9_Z$p:ZKV'-WdH&bhRn^^7"UMR&i8U]s*R`_2C3U/\#!K!Nf.d).sS:G@d^XG + 7!FLTi<.)?#H$CKJPhoIBoZUVo7_O;"2cGA3ap>=7#DmBR6o-ajJ6G'QQrIbiBA@>P:5"/sc\hK#:f0=G#Sk+Abg$ + S6];kMcmnkF*uqQhTA=>*PSiSIoAi\U9\LUt)6Eo'WUCjP=U#qF&Z.jIJ_Gk,eZs5h1,5Q\ + :4$=N<.@;aBl0GOV-)$-r^!Uq4?c33S`@\C_=e4L?tJaRa,:*V>i*[cU]7j8^6^?6&$k@`u + u!@e2FQkc:hf;_`FD1`%3BPRrOCPKR#!ogJeQn@IKH>'%DL>je!KW!u#P6?PEk5;0sHr]>: + RjXA=C7H8\a=D#$*=A_c7kgt%g2h#9*Xd!0:#mNbkihW$ + mpaMi@=3DU7.f#,d3pV28gbplNDJ<&68"S?0:^F8#1!9/8Kr?Qj:&88%KF4./Sjq'G3%=aT + ,:&rRgm5T\8WZ/0m+:PGo*`6(\) + t?K&ZQ9#l(K]Ks&S1OaoVkQ60R".W;j7KLcQ7W5i>LL@>\i"tn7Tq!5_\)s\d@TrRZ4N5`' + /nF#op5oUh^h?WT+9r.hqlNB+A-Ore_!f5RIdA-aB]cX=4(l7JKiSl,emO=m1_#T54+Tim' + p/Ai6ScSDt$1'%6iin3@(m'!"k1V;3:.c0,^s^o.P.YB + 'Pj=_BY,]lQi&Hn,<,!'Vi,, + 25c+PeP]G0oMt7Kok;llMYDm29rh/Fq1?1=[ssfOD)Ee)t&r$L/]bnuh6peB/gRLO^F<,T[ + :@)'R!65]ILb;)#".)dtW/@>5Q66>EC$#*U2c0PmT*]\*j>VI>)^Tn]H+g8?29VeUNq`HZ; + Jp/"SO>#:YS&>"u&U8auf:lq[sW,CDLdW4WS*JZJP+l7:?\I:tq%$8\o!UhY@b7.46XX_j1 + kgL"dfQNG9i=/7*P4Je9A1F=VYQDLeiV8!WTe3Vl61cZTBc[?5*_.4@;9^ + YTWp4F&rIGh,Z/"D><\3><2G;jrP);ql.#lWN+/$:hncBf7#\='*.P=uK+EL1o*_o,d8HM6 + Jam(5=,g%5]9^/:Wc%&fq(`_eM,a>AU1.hY3:IB&\a\S(0nX@T2L?+MHDIeSNYNm'(E;ot6 + ,O\=nlSK;e1EiI#pX3/q@X>?$^'.Lg4cW;N/FY6-oLhmQ*pigqK:[oD>>!lt@-)b?PuNNH[ + oKOIEQoL&e3R,XBi_WYc:Rp:+$$\6H3uB%WN],?8G + uh!k*jG%gt)#2\"Eqd(qZF`j^)DXDlVPCjs@#=,k1FNP4j,e)%Zi + PGf8bVLQk`8Qs7ZdH4Di=VlFIUJE.2.jGBbpl([9W>#ERQ0gc]m@3u=;qcYn)>!k7h5ro2. + dN"ss'nP0UdsQ]Ukiib8JX.$:7Mb7N79;CLCA+oJ2RR=7mm?YB,n,\+cGoYgR;7_R-Gj$#+ + r)'*^&SRT"[;mWgI`;CH!Gi2_!:U5@g);^]"sO;2BS,)D--n*E%Yg1jr3;56+I94cOuiB+%TP#p/('2G)DR?k[m + *&eWIF>toj,-N*jP'mVLk=V*!o!]VZF"L4U?IHAC95QoA/$Gj"Q7sC57fCaW;q1#mPJ)dck + WX/aRmk/YH@l9)r68&H(KH5Wj*5QS'epi9e@N48W"6YL*,\7/#fV&q!m[`rb,ERp)cc/Bc+m/6"EIN;8CdYso5`Zk^C3".()@Di(8,eBcF + ;e?%CW.,PF,$"S0p!BJBAZ1C@bQ$k-'@VYJL]HnH"8<"-K_RDh[u`YDM^pG.&'ei%)L'G*9JNn,]jR"haW-[Dh3U\`uQ4cGB*"& + clj'i2[sa3[rGB+/%!)5oYj^0,4bAs(mc][pF[Ac=*%.E-;g6MiYW(A\B5rAS+P>aBSI^oa + -Y,n+2Y2Z9M5@O+e>U0Qk[.PFMDh&_j^u?-lGOLf6&""K+L0[l<)0_-h`Y + e#>E[/tgH3^X"76>g[5%A/e%/MZ9i5MI*U;mRk.HFba.Gc9C!aCou6_Z2?Abhmj+GX>jV*120pjZbZJ + eI.JP2f7E%nB$LrlX:Ws37sc2R%ZfjV.(-bm1q'NL/1mtnLfDaiG^poL1_4gWL5X!)3p4ZRpTDQ$@)?_.&?L@KC/nHu8 + .':KAX.c6SU#Q6$."Q\fjrTrkmfE>_(S$0:?-0JiP#3E=l%E('p*'X&-*egb0IS8^`Ze!s/ + 0Lk7.!M/2^o5Ks,U,BP>H,kpg!.1cc/i_BH@8l!0=%pZ0(:rR9i<0uqDLXh)IZql5r8`!H- + irsVqr8gjj;b1[rVrt@8[DoZ,>.m + \%s_kN[,K7og#?Bq#5^625ZP0[6;N6]TdZ&76(,Wo*0h@f08rG7o=-+mm1Y._j:20du8+IX + #@eI3n>J.u7A)-ht<:@$l9*[d[M"[;F2?l/9Z42aQ@Hm7ifFHNO"qUe=?%^1A?^ChAsGd*J)KPi;PJ31#:T/)o4-0To'TD + ):A4CZ6+)\4XI-l.P>s;X1^9`'c]q6'i)`s;3\_1.32*XOR9uZIDV3Va^jsV7'9NAPfr0P$$= + 1$1Rh(9'3WQ9FZOS!e*ad\[YXCI4D>Ma(+ZX*0sALQ-3b1 + )4]7J^K,b9oDf=9O%H1sC%WC83F`b)l!&=g!EjTcmSK+P5SdKEo#1gj"7:IsSXP$CRuR?&8Tk;8qRW_qPJXhfP + cZQ[M/6X&*M-;15iSM%LTh++OJ^9!l(A@XW!"a&a0c=U>HDGKe3e9]GY_>?&blfuEk.>O_V + /eh8NF[\><;7B%k1aMr`/kYp=92NkmbtSnZjH'?aCCKg$ra_b6srOZtuMW + 1qCZcr#oq*C*OS9n9N,B"0c(PCXh*9R@D@;s + i+oN!/A:1`,d?5L,fI>p&er!Hi]7NOm*jCU;BA7Y97l=HBoAU%m"n2[:ZAoRRH-_!-6V`UB + &mWrg7p$oj$S4=oa)p0NaWcUpUc%)N/Vfng0?3c-QH"u`[#sqZ/`-56EEjGO + ]sV(QhV2mO$Z=btb1?g:X)(D)d\g+T^92`rRYfj'*KqAe_e>qpLS((3fs/S0[3;;O;3Gf`@ + H3`MMHdatS"<_\`CE9j'XU.o:\<`*;G?ao[p"fTKo%'/?eqR[gL'p"CY\XJdDSc`B1^0^n9 + b_F^GbrJSuAZ(Y0)1[#NjV(mRnV[l[&QNZgE*gkI^20rs-I3I!1]7X(.C50eX[@"N9%D<0t + `&06:$r=BGR,oCW&n:]$95;69gq\l1e.&I5Cinn4(mIXc$5o)tC>W/k?84i<6R,H)$3+.8= + D-!3ILp_nY/#X1L_acA4nL4[R"X*ViXWZaXV#Z;9Q$j^ZQ+jl+j?l;L'Y=u3k&ua]UR+)+U + ()\*B)\Q8ZO_?=0FVU.dpX9dC&'8Wf#N2`AGQ!jMtfp#L3nI`g*`p$T_N)@K10onm/ZO7F(!:j.pK. + /KB`>GSdWu:TZS#;J\]TNh?%k#I[<:?=*!kjM"$CR\&8pp6'>0iqm=0\LQmop + U#@XcMG>@iMP1YAD@Z40B0nFYk#T1?ec<;2C;2g];!fW?G0jY:b&EI5h6\$Ca`sm[!,@_.c + BIs]$\;$+og1"l#%Lo>D2;m#R_7(k)1$#an^=W\.GTSN!I5Y]`1L?!fD,Vmm4unm&*eSVX9 + :2\;U$TB`;Bn@74^]TpOTensY<$A]+gUX3(XiKdA(0CmWW?rCA([i_O_@[N9#)ij&Jl+VA5 + *Afcm$cCbm]CpdC&\g"N^KcRXVR2JjQ(VW"s5MS42Csf0XQhXTo9E.F>>0_9qo + Wm:"%dn3lr@e)!rp_#.7e;l_[DcN_[Y@Td,H&i.!S^f + g%d-J_81dtd<85>Q4N>^7^T!i<@-DR/4_H%?[L::?s+!T6mPsHBSOn$T_=W1p+n"8TEm5Oq + )K4:2iXWQR7Gb(HL]]O@)Gh_j6Ce`$*lhM4,dLh&Qrk9@+7`D(3F)'O-21@<>D``N*p<-`g + *c"e_skq=GfgbYZ8Ktr$Ns&[H&hD0rdi(E&L#%92bg(-V;-PcIu5#/$[P7ja=>!En4IRbsuAnn9p1Ucmk7H`0i(B]If!da7*CT`"3")pp8Ob>7gUC<^i(IKm + uk/>`=pL>ajcqh)8aI+8"AEkn77J:!1&3UGP/DZgeeosOtos1=Z)%aqKPg`5>*[m$Q^JZ69 + enHmdCTf.5d]3Z+WHBmF<6CY94N(B6rAi)]e>uR5Hp0>goh^YD^blo4Sm##NED6>%jI."2B + L'mT9UoW\M>"oU9K"&'`.j0)TL;4*RWRP::2]f,-;A9Z'EUfhNU\D$F4GKlS*thg_(#XZ37 + \m_322XXP=\9>??pkXcVk7Pg@H(figa).EIKX^Gh3npL"abbN(e6\Eac$W/oj6>0G + r)I'WeDX&92(P$b#/*:d?Gl4rW_fps_64Nfjs2VeSl1GhMIVRfAj<.sE3]QiO;[,gXD>@Vq + 8.IlF.La-YK<(GI7f0:rEl^/$lsX@WiRHJCBk8%N> + 7nbSX)r*&X>so;img=en+RO?gP'%n@&fq&54qSNIt>&^$\MP_b9:neOn*s&bP@bhkBujhYY + -9'D%slu'hr[E-E-QB`?D6\;M[@<=I^?T=:T9Jqk#CCe._p3?&N,qToL^,r_pEs&9]9]Lb/ + =ZZgEeg&f`"iJIRKg,m-hqJqX_DRZ_uPRF+*k$,s$/Tq"Cf[6X#@)NL)FVmUK=)P^ClOEr? + ;L,HQ-.:ZYkiTIW??a)?N)gYu%+gn<(A"1*s.JdbJffd(5Gd_j@.QH.Y)8n+i=/9SdS:OQX + R-:IL;&mD4SC,V:aB+o5d1kTF"UQE,n^N6HQ5sX30sFr:Qmb0e0gAVm5=h:L&5e#:FU(HYS + ft&(16I?\PFDor\9P)+6#S`PkX4,h6gR$)r'3XXk;`=_]TCQ(99QCj`l&GcUTmoFr'LoY%7 + HXRNg)rEA$D1!gb3053T+&5P2IIgiAqXf/6e&b^d(=]UcMBe3b%;6E-JpA;P(Jc0#U;lbH7 + J]Y*$Mq3s4NF^tY!`hLLLg+t*JegMOKh=Y^)CQEii/>\H,'.RoiQKgHEMgMNHbRQSd5Q7Ke + /J.Ta08:Uh20S29hBt,Cm_7Ys@4K)hdq[Z0WB"6%0#MH#fq*_JZ7_KX]4Y0%,E.8im#KRef + J[C%TdjQ3pN["q]mTK%Cojb$.khceUpb()CYA,aF2"N7S+S_8M$&s4HrE.r + '$>[L[5lp'C1Fs1qqHZMGlIJnnf;$U'G(I"mA+e*&C1eR"kZ6JC%j25WQ2$X@c`p5GEB>>= + ?6RMjVA0#TA/[$@fFUA/Rs"UAJf-JXEh(LE(@\W0*[Z&YTb.$CWt'W6j8+:j'S,WBfa1MF$ + :4;-58-F?"(M)^H+`8nQ1](;Y%46%QEjFI5@iR6e'(^>'_n!-XY/V(,%1)@A0uBcW)+ugZOLZW.o8e?E,,TG4@:Zn9r-N20QMmJ6T?cCO+@nZ!E^lj + Z(?#CQNG+)[$=B[j\B(C:2K)gGg6fho>E2"a"%G6<-cc>`CBWLO+^r^(t"@W!4P">&p^8b* + +)!B;\A@jPe3JSoM@RG;*#$c`LDk5BKsI=!BO=jTgW!OPM8QJWJBf>To<3Yi=7p,C8'Cc^, + 5Kj;EG[l^k3@/3=/C^1VK3o^7)XlcjB9#n\d2%!\e0T"H='VRE[,\R[c]B\qKqg7.*PN&C. + 56P5>L0]M(k[$B">Sa'6I%BO.D)%[5Cf;:PK$%3Ce!Hj(3Va4a$H+u2;95EEl7;Q9qOFY$7 + &*&5i02Y)U-nYjH;j,VdiV-']/)5S5'/(7Wp't==S4"1Z?.Q`=N;lr,[]f]6<4Z\d7a_[q^7U*-_j+d.P@+Ks,U+ZR + 2I?[oE(sG+=`$IL7AF0X!GK"DC'Z9Q(q64,)i$J<2ehW^sdpkHk/lW31ACj+I`BMN0$fVA + rk3%KL:iPA[FBq-lB8a'QkBk9`WJs&R1eE%<;)=]oYb]a3ihH;=gj:!eSIH+pY2k8#7I<_[ + F&fcoi*ka6b;0eL?C`4eanpgoK"h9@oR,Q-fM$j@?3Q- + \Wl"dB^$J_?bs/^CR#=E;li,&3!iPPuW!!-U91t[`YI)]F)4H;YI'SuY#T<_N5at[W.ARk] + $ruM799=?lnIa=c=Ne[;LU=b2+g=]ZE_H@C9GM3Gm:dnYC'WKLe.AN6CH)QFUC%UMS2m=\& + Q;">a!)DBUA78F;"Z$sLr+-'6A)hgWiuXP.`/hL\Ra9rY[W9Vjk'In@I,b)(fc0>^(locX8 + /ft%:]`fL;UDk)K5%ECH+XI%_n8TUK#1K8]g/S5YAI;Ug?-bqE>E1WIdJJe + 4']baFNG:-%'RsDH0\Wb5jHpXl%#qQE)\isPXcb%=F.3PK\Qbk#imX)h1j%L1IG=@h>QR`, + 'eqglU0K[+PKbE%CDG9L86H@XM[?B`hIIaB*G/JL0B@fr"GRV4LC-FP>8>57o$!AMVj\SR! + &ea0_C>Y0Ne\m@j[=pOIP0eYF_>$(B50`gc3[e1B?X<@_7=]pA`/?4Ckm\b#>f8l(i]]B(? + +rL6RZ]&kTL,N=p8L(BPrStD=,JrK?B@)8uQ,SLgcqPJ5nkuW%8&$D-)J*=H/eHPqer>+SR + ;&[[]+4LJq4lKXoY;KM/h#i^6o-1PXP""_)$%fRfPmIQTCb#aXjBeudg:KC./@IZn8UOU@p + "oZN5_L7k#QW%2d0`6X/(d"kMPUY>]+<7?Z-N!Rdic"N/k0TcYg*c#)6lY)mp:eo9t!*DmJ + 5oq>t,Wgn?TAkTLC[SrQrkN=Wj'oufj`Kj#L;=,n#`<\G@i3SpNlX5;Q6Q]D3NdFh24A$4# + [+j2O*.#HNLmYkg]QE]A*W5GFe[4ed$Ch%7iKt]*^rn0Y?Qb3g8ZsSkbDX?$j(>'&nr`?Ai + nB7I\6M!J6(\hBhoR@h[us]Y719]7.(f:&_jE,3@&ZThI8+??:V[N]V&Qc%3aiVj@?1(jns + O=%EE'$a:7Y:`Nf'T)jXCS=.#b$&q)G=EHNKYg9%ulV-p,GEDlt[!>NCCJ2tqSLiaU0i9X5 + DE,Pu#Ig@bDBAM((ac)YNZ+aNUH"YgGRWroYJ5caZ9E>?LE@@<2GYnOh>6Us.YOWWPGn&tV + K[eDo8rZWhRXDJeJ\V9r^_%I,iK]gJl5\Sio0Q,F>E8Rm?5cn<%XLLW?s3%+9i"Y*Bc + 6VYo"DQ@HlbpSQn`acmnZ5e!$Jds5W-hnICRb9>[YZnCq%1!6`Y5s0RAlR=j`D*&b-nP:okdFXf8f;[=AE/oX68HSo*CNd-Fn4eI!F9`Y"Eg/Ok*'%CYA6BRU>f(NX'h)Po=A#@R^[a8asMClV+p+Fg(qkiE + ^VrSj7mac28U)2Nfa_cLZct;!T''gM,RaDcQ,"Mp4=]/a#G2!MF8-#iiTJF%`+a7+n2_$GS + qCo0`dI)XlOCm`pV7Z0'*+J"1#Onh_jhL6Wn3"@.ga;Bt_E0%bo0!Hr8*3M>.*[Y_)upe4+ + Drmg\5jTt*#XU+h!J)Jp64%2pIeqj^pr+6c2P+e2k$FbBr6X'Bj0OC&-&"WUHE + k[L@Hcief!'#>(^X$!KY57k/MHWe.2ZM>rhV'bjQI+YFQ\l7HG))3R0%X%Ke#BM!cAV"B/f + -STQETJMeSO17H"X9E)*hnF4*B!q`NH*?+Rs58>:;YiP"B2_#st + Xk[?`Nnbt3PJAF>mLk0M/f@P&Fe5:4]r,mI(2U/>UOoXuPl94ZLU<;0*V2#=4=+0fS"B!kj + G6mE5S"1H0=Z<:julU($hbdKG^cDuTKj]$Y.X1*Gpk4O)87-`>.gRA@Y_I6s>/4/Y:b/Q@0 + f*>7'-$6b+EmZ;Qj"r72@Ar5b!foc"<@mdcrBiMDNbuS&%ipC=nNG%Vp=jfh@]aa_4_B`=P + E&nd$#t%91T)P(bB!2[r:dZj!EPb/3o,8*B=6L^GC/0G#D^:Ynp,W>In-Eelh5[RhZ&AOjn + #WE52Uk0lE0r4cY'<]!4je=1%O!FmYU;eZ:CWGGsTK(+Qo<_q:Q+FV==D%P(:2&%RY4%5nC + n1oLTV4YoHIP`=Z?N(;m$>/.i][@2N0;dCaH=a%9]S>=ZZFJ6YW;=`VIt92Tk=a[Pih?qJ? + HTOGu4/?!($`^I&N),"D*B1p6t^hHJ6JZ*?Xc<7BLRn[>'2Oj;`_`4/bRE7Lo7kr=PW[CF^ + FjJ5Cg>BbDJ`,0%0QH056#Gs"T;V(@#m*@aU]F*&e@YNMT[;6r8u1!6@%CC!,A('ti7FkAsKfO85i\d&OoT#`9 + "3sgIb$i8mB78,X*1NTJIgd6+c,,[SgfQVfY0)!j%eo`W\jtWO-$T3jG9\$1K=(IWi/BGQJp@4k`#a + kSjf*HmO0A&3+f==>#eq?5aW8fi+*:Jcoq_$(aR! + 91&544m8Bo\p41YM"Z&[hkc2Q<5nl)X3U-Tf%^FMD7C[5@n@a(a(#]SQGhNfR^g.<;4O[a" + 4_^k]Q6Y@)O9N3^(C:Zu,_VC8=Sg'Pis=VR`2k]H0[#?nW+N+CShsp:;/spXCUu@PnOE.^I + a+PDX$lAkPM5^sUTo1doa%7U6AdNknH9A6r+q/+!'j@o^a`8s"kY@@5;5fg87E;;i5oAZOE + c.>";4t$C(EW:;$s519(Ue(D]@iD1cX^/1fEhg91'Us,pA#`dR6K;YEW,ra^ckIUR`+%?,I + !-2_N\.R1dsG8^%ck,f53gL60Pf9UV"ujuP(66@^!bU!PeEH,!15+[q_C&>TReP93[+9HV@ + jlD-i0SC='O:79Wnk@EtNe%U&Jp,YC)qpBRU.G?P$CcY]rFa$d(J.UDrK#1LkT$8GW!#]2Y + g5D%Qn.#Ml2RN2]%0nH//0qUV(pDY^&>BX9J=D;#*GHf8/?OFE2BcVa)`)/J,MgkbB&GtEO + D1N1H+Sf#nk5Ko4uNlH6Beqq65bU_3`PgF;u'm!;99)66ld;'Uj,bTg28>W]`t8E309:=imhh!H06*6\I+jT>e))#:5 + TFj#/OCW-1#E2]!bZH[5CDVa]R!O-?5$.-)=(c8t>[WaNofDMG08BK`I[cc\,"E_(1bgW0. + af>Y0No]ltf8>Kb]BFq-no]q^DP?UPNCO'),uYi[!7PE-==*9E^^VKL2;h`PP>eNE#sMCd& + u\N'lQ.r-JqMS!/_ + @Wk`:9(:$$#^,DN%3g5pMnMB6>J)j6u,p`Xe;"kP&15)kM6"o[)dq<$MV,'%=T+3'#9O-cb + &in&[:uTH4h/6/tT2+1+#da*iJ!.G[nMp>Wlg?6uK9k/-$es4PAl^dfU=&J3aH4=u@>GiWI + LQ9a]8S+G6Sr\*kp1gqT4t*bERkU#j3;qF4oG2'_/CbT?L<\KJ9lF@Uns\q?ZD$G`F'o&ia + 3Lid6d&@O.oR'EJn?*R^_d#F1K6AUaA+s`*)P6LXN8h!IaV0'"Q,/sIR.?'WEeBo,VF\n%] + c8;'Dej-!FIZ=/h3L^"kl/*H>h8 + (WI#=.#\o2M/QHmV-5[u$0PFb#O]#Fj@)PPa6en9uIuq#;`'4IP]gE#mJ6G@lK1[GUR1*Qg + O`=7uM\&46DgaVL]CJE9,5hh1.A$7asc3&Jc9C/$sgc(NX+,j"0q%WEWj9iu4BTV\IHVdK@j&Pt%Kb#M;(6!2&D.\A8%J:!s47HM7GI + #RfQ`K36KXm&H&b/&qd8U20tZRfgMmh/*<$YMe7igPf2T&hY@+(kNRa;Sh]H;FgmBr`cbk.C_7$AaCV:M9n4OH + BGr)\LI>&eQ?$;'n3i.U\2r<5g?Y./8rmHBOu_:rFWC!SE*7\f>-R#g\\L:QS"r^_3:s(nj + \7Y-Ab&Y(sr5l`8TmX4e@B`*"_6=Q[\i8>Jl!Wf#h9Mb-Br`d#9FG/.OtPKRQT#G+]W'Ik= + Mn*D#@L%=_/VB0R%qP!??YHAs#(m#9:OH1U:OXMjK9+p\GOi"'9;*'kr82^VW2s'Ci7.PWh + AuZ/&3GCm>Lp3-.:l4V]8BG*nd!uku[]AbZ'S/Uq8TN5An.Q5/Qm!dn_'TF"A5mcO<$7YWGanX5edjJoY#s + n>E6V/Zb8.SUi^'LrE.\n[lgt*,S%&0H0(cZjnk;!bT=OVTRHY0\. + Tt/;KPoL?C"i5Rakc"H@m6_=J16;UiR^2l5jsJ5k>RIM0`+Gd,I7LYWt\g92Vb1(DY@P:$" + >;F]lA[RB-Td.%)cKHJ'W-h*Q-d[C.KK!(8n#h"Z^lLI8`tWb<48"4U*DL`CT/hi]pB'`JF*(_?f@A?odaH*?i9e]Op0oP`umbSjTr_2@m&(q:chVg=%4,$-^7i>/j6DT.s8Inr^:(K(%* + ro4<+&]HW;RgG&%kHp+*XY+BDH^g@iDn=SV)$X(k0kpI@+ls`/o#5PTZpQ$nCPp909a=)0B + PSn.,?#6=U:m`"_fMa1j_P$"g*t"TGGY,RRi739a'nE'qJHIP;Fta.C2_(ZAU[(>r@8,28N + OBCB4g&oX<;mjf8lk'6"XUW3O^o2mk_)6d@0sWF*\r#ke=cc*lVBSDp9u2:]0:l-A=`^aJ4 + PFJDqD(++#$s'iprfu%4KSEs'BGQ56&SXO>Bbr1P]n(/i"Hl>ObSW/d(mHZ?A@=(PP)H/cMs&O(6 + QB/V4RV1I)N5;);6DM`Z;<3L7E#cVbgQ"QBh$NV7hT5N'`T1S7,3V5k9KoD#Wa%c<:cgF*t + LV'UEFZ^85Z-=4cu4-&o[_,W;PHDcpQf? + t)BC=P[dtD#P-ZZX$WOFE(eP_J@I5Pe,q0tGJIU$5QkmdZJF&+c.V/L(')R.,57(ce1 + fHtj0W.k"E8DeT26]4OP4OX,>Q4r_,[t/!-7Li90;jkVcR-O$- + '*hAlX((dZ(RJ5p4U9-`Do'7bfQJR0'LKoXH[lP>Clk_P&t'YXt92%bN/!K)bSN#?<6C;HY + _@B*$;[MsD$^nJY6e58Y6=Mb4L7I2HC0unH8?QA=J*B*5A&`#U41It2<84aC`C7NOHqA+X03>,)LLfbbu/*-5&V9&n#:1\YjQ$8 + /uEN1(;>P;HZeY>t6=J5BR!b)9888bFI82\9EF"=>s+-pRM@:SnPrc,!OC^e;2)"FCSU$Gb + or9E2\Af<)lJa2,/EL5:eK)/9@W5%;ZKb)gld>I*aZXFj5C#BeGQuIFmCNfB%IM'mJ;./-C + 3Xp&YNedLVdAon-D>645h5eMmUE69l9_:"*'/8H_H&U"@[E)Ei!5/18;N"T!b'<>d2e3M"M + UKAlQX0COR8@c&uZ.elg^UB!0==Z99);j1pM6548C7eMU;Lt=1ul$FWu+0<9tXT91WGW + />YarMK?F*1S6VYKUp0CIUaX_lXl"=^6^Va&j$.,gBXi"$5'kn&acmH"fQ6t/2cHtHaQqGq + ^8\=/$dg2A;Ds22G]SR;a*$u*bASC3F>?gr#Gg>(EpX/+@hlfgq.!f'>U+R6"1T&JG76\)B[#[Tq"_bol_+GoC=/M^M7>=GG?$fspbkGO8Hc9kU[CjW#G2"[g>Xu`&9>na4SQ^a\ + pRA+lgLH^(Sos4f?ib\sM-C(JFLh.DN(HtL#KPO$e%,o<'q:q?=e_V]118e/l;rA1cRmjn< + mp$-)>kSRj.c%8UEWkffAd"CkG>d9X])/S71"_Yd^B-fEJGR42q,tQaAd12h&n`'8SiJC>" + qNh6-DjB6G!Cg25hWn`EZP&q=]@hnkbV#?$`&I\tJV0%S^-.i0Eac>-pB")BWi\3TXprBX! + ?SN(3Ml+d*\.$<2gPQG2M*WY#k0ePs#bJLNrC;$?Tm#0agFQ^Aq,Q5a^EUj,GP<0X3.:@5q + O*+-Y_MqHjHfakd&4^:5A1fYDk`H]+[G#j9A>9<++*QtBW_9NsmDB'fR"?[p:aCH0^5RRh> + (&`40=RERm[qONCO+N]7_0J@UJSGsr)&n`3Zo/g?(E&pQ)=aFr,D0eUb`.4KNPm?ZY6/WC? + YtMnqTL`8ah^J@5=_58P`ID7>M7^GjI/8-rUM[k + +TS1,0Z0'5;f7`OqRGTA3b&_F<<$5s,>pe/[GdFeg.L%gjAW[dd5<]S6U-mi[4qPNq#.lQ3 + Cc*)g*'M9pCeL-/"uAFING!";':LeI/p#>0+9^BYaW/t@;#I"@rW[@m9)TfQH(c#'&bQIXc + ]I/MY)U92SgFXQ+nnJX5&g,\Q;n7t?I:?T=mD60^R[Zmec.Zh=Lrq&9bj#_d1h&s&r9"qAP + S?-JJ6G43hI/4ob;>&`HT0C"I4c!Sg$pksH2AJ1D:2$lmdF&TLdWdk%X,Q)VU-_99Gc&\qh + HUl\QhfO1A)i;:7\Ws39@uQ!.bo3<>q@Ab#W9SAC*^4%hjtXtlhWscCr_J1`#pH&$gV0WrZ + C2"8`/31&\Vj:g&!qd>6"i'[+V;:H_HX>S'g[jG*q8$Ir + =BF?$&VtmFnX.Ab59Z,j)W[PbN2)Ic%ZS:91_U'ZE-)g#+\*\t4#noTMW) + &sqg-#]//)pTT58RZaYjRF>80L\<*'i(Z#j=i7PU-?)Fh!=D]D8%e9;s(Z1NMg82:m7,"T! + rO!I:PlJh6'R@Ke=<;13&/l*[76L0'\iQnaVn@^K5lZCr:S14 + +Bn4[a#fl,,iq=JQ31Fj)c9LlhP0MU#C`NJh$*k(n^cCK2m-N!gPm4!6@ns2)fgUJ*d!1o, + MGlpX*\0[lrPD\!@.U]KXK++8I$2?J-+RkeP$iimd0i0.5H_#4SVA)A+4URls(_WKTF,c<6 + oFl!1HH"EoTuXTsLWU_nmtpTGf@Ke-1e3B7-7)'iR1j%O:Po`R.7H("Th!S:A?fYF0EqY8= + <_3,XMu_Z1hO%KgD^,IVCaQ"Y8^L5[U%#B5-/FRXHQ:X:s't>W^Rg=<-j[3$<9`d"_+-7,! + T,*$oCMbp8Jc\5#hiIhm8SS?Xt]WOX:B2L7&siUf_;3b5sYneV[b!!hD)_e9i5kFt]ZVEs% + TE8?"D>Rn`.'D.r`KLIlbAduZ;O&7AEDq2#2BitWGj>qiKkodS9Tpb?CE'[8XcY+jl49BB2 + c`YK')U-f;Bk727*[N`ZP6Pha6DD=\*B#oolm[S#A%5O&fb/R@AA`_ckaGEf(c+=p$9P+4' + fD[_SR,+GM-A9$P[a%h)aZ+-*_T@)UMl)%&C?=g@]`e&S>%,mU'SYTtg)aF!PnG0<;Mro6= + \EIO0;]H'R8Z.\7:02aLi9fW^BY6dbNndR/hji+1Q(65O_Jir=A1`2?W1BU:PAn$8cb'-.> + ,_dR"E$"P#=u"e9hj0a/0*f7OECdMmIT)L'1:dOO9WiX(S>9V),[r5u*9_?4$"s0pF4n$Zj + cpd,]JApic@hnFq:,%1eh:6P*K:6VnKR4&]?3CTNd]E)4-`e(53qR+]<)l<9qm`58r(:ujY + b9qApOYg6Mkh4DaI8BTWL]#D7AX!bP[MMl_,`?:ka!d`f3]81pS^m;M4:?;mM-4qD4:oW&1 + l!4uA3/&bI*CHWE4TgTP$_;r(.!0i"kJ1qD'BQN#[,a1gp1eg&]q[\SE0Td4>Mk:@/M3a0Uj"A+Klqu<3(%mRGD?DjE`[7@ZH + D@K*+,(+sfM3Q!ghqJgs4Hb>R(HbWNG(oCRK+.B/Q\IJU^FqZ&gJ^cposAkMZZS'H&X3_Rb + 6`$V'3/e$['kF3r4N4?4#$+e?D!jsb;%,RKmMZ-q97l`V!kT"]uE6\3-2Y%Xq?V2qfbON>, + Bscp6%3ettH]a'J=Q#[t#)M<@LJ^(&"2^W@tm;;;MS'W25H[9T:h?F7e#((u\N-+&n)[YJ0rfhBD/!(!J5qb5)N&?B&` + *o.[GgHJK8P5tFGdM\Sp$7fGeh^AKE@Kpg?Q9h$V"!rCA,:"iR#CLR&74P/KWfX4hH + //$FED%(?Z,t[-K#4J-nA(X0=?]L\DdI$C?W4>"^bom,tu_An*bD6qA(@=gu%D'P8%KZe?m + N)`S0?-PP\_i5L3U:2Y'mnQU\p6R)YoH/CF6o-A0Q^uds[t(?I$"5DoiekOR- + tu_a3T`9Yjdpfu/,4=[UT?F'H7UIk/34i^>/_S9LE@GU21&WF$8OmaIlOsX_)]Cmg7U`I@g + A.o2GqP_a_W@BE@<::QEd$Pq4f&mg`<%rSV`2rH3f?>FG43TKe-#mdLHEV%g:l'"Nod.Z4V + b_%`\_2F6tF;:AZ:*2]g[?psp7aY>$]Fep4$ + !T+3uI%M4%_:J)u.1:HRH]sVpqIS)-WO=5:gWNb,f#:9=)d)rOBeH9(]2M`,*U2Z-+Dub;? + Q/CPt(:Y/*0<38=Ikb0W%+iX:,O$To=nYY0NE=[e!HLlcM-YS%,\<?_AGb5jD" + LPOoG_&Tg+FL=CcY/Cmg4PM4Kf8?ApriKjVtMFd3,ZIt)gH4NR>XiU8JO + SQUoi<8`2"PVlP4!Oqh8V-NJ:GfgZlDKCS*W3\2fdie`sRnt)>:b7\a@*+?n50p@hb]mGfLgWWMgJ"Q?X(e6-G\K+(CT + EkhX/Z-"]6so5k)NYq*/WoI6\,W.mu@3s3nbZF"0W,2a2>8Ie3W_+<3Aig?b"VC2@t5-W-V + F5\dgF%T@gH9K9M2IP_uGa'Uf*6F*8[&Su+<4$$loNV'i,RScp(*73eKL&eTf<8H^_b"Ji` + W78DK8KSY_2#&mJ#8N+gm?CoBm\4em%%;JiCo2`Pbf_(>NPnogi&e2N$d,@JWKQkf@GeBj\4r^l6(bo2o';U<52PLd"^f9>.'dO?!H\A/oEjh#aT>;[j"Kdp8jJD-\7'rXW,K=?C(\]"8@^HE]sWM + ^u1fen2^;S^(GeLjG21qs3Z;/h"`(qJ*EM)1\Z8gVA:)D`Kf3!Bq@8nC%^[4k`hc.qF+h"B + "L"dRZ[aMBRb*rSUF>R1S9\NtX92&o"W]hHr3KN.W!:JFXd5[&kD9c/jg9KXN93*Do6iH?$rL)@_^P0aZ%ak + U[jP;(+?`JaE)ctFJ0!t>s+VpPWgaF-0Ri541=`mF+H@P+?HK3BuH0OglU0iQH>rO(>+]KJ + SV7eeiN"W=Sq]s*20O_Y8`#sEg927#%pJ.3mJ=KL;)6K3r[@G$#0IQY5e/`Yst'RBr(fWE6 + Y]ZC"e>P:.V[+V^=,N\(gh`[$>j-+j*s!_+>2SI4B2>d5,!lLl`3Ft?OnUZ@f` + 'tu=jL+i^10j<]p9$%i&(ql,Kbl]c2K+>N-J`2p5F(0kKO[-u76aqRe_3b@#/[O=NGd-J`[ + aMlX$jK-$AtNUWTLQA`>Wp#\PPDk/1T!h<#@$n4j0^*i^qSU#jW(0R?;g&0ea6u"m<7+G[T + <_$dbg=-kOn]$Pduf,>Js'L"u("Ld/n#7iO4P["L&MmYZ1h7@pSB$k"I"3?/^X"[KjIGdq;\B)VZ@mnD(f>(Fm\m\#7%\WY0P2oUGi(4%g + DM4YmkoUT,+j>^$BE_[tk>6DAJ9gUKMpIu_:TjUp%fLD!*V&;6j^t+(rYo)peS3`qMsqC#7 + @8Xu-'We+g4Rd9-9@:t$%sr]]]BBM0!&8P`TneO8;Jni#-^VZ'lnaS]cF*ClPWm7Zp0SYCL + i-ZDRX+66`!WH#2J$ + PJI>c]#<6YUJ562q'PS]q9E5ZY\5`[/k$@XZaUs\SW"tO+ZrH'h?lt^QNF]5 + [*JsCcn!"^LH4NT1c%j#nD>3D=K:mcM=9o?g=E@[op-kAk'c-Hq:>RNLRQSFK$g\KZ-GV;X + \kDApo)JWB&FM9:68g"Scf@'XMj6*>HI4p)WHh5<&-.1KTIj1-]mjhNSr_0LIcFih@X0CGP + @#Mn[&\o`YNd5RJEbLq6W9T"DDfjPk$/lE\P5IkR-"#?ic90/c#O2d4`MCMg7ZkGhKVWA4I\m";d#Qak0MN9$&-%04N + [kQuUb[tBaUZF_ZEq^m]j>79:SOfbk[QV)9-_9(:Pok$dS7o$eP@"AB-V[a9gqL"qb6CW+;IQ[gq*Ke_>E,[Hq5IVftUP9gna=DA)2]3:X.t?WMMt\' + rK[I9e5ND6+F_Ru`K2fT&5(\LrL>)jDacLp[QV:ts3J>gl24RD(;eb=THTRiqS8/c7Y)Mb + D46]NZA'UV:W'..VHDBK$q5cc\:V,Y1q-SU'CfVqVQHa'J^[5(6A[^E5\6NoQ4/LNqGJ%7# + =1dSfrI;UjE*,4L(Cc/<@W>M_AiC`kaCK_PsSYKt:l.f4/H5dWHID=]ICt8:A;=4VKCUm]aB/BTLkX61_L'pYUip9R6r,*gMK5 + Ir..nF"p43csnh%Wf6j3+:cmtpW3l[AfEB?qT^@+ + 9[,PZ`&_b;W__[mC2%ACb@$t>[Q$-a'R;(MRM57CJFYIMm*#8b94Mc:tAL,<`YR`?t$GXmg + qmXHo%R8G-I;CbEG8Hffma#\4keN(.k;LQ$Wd`@orfi]a&X]d$2[>@?,BL.c?"2[;ut + hCL@=%^R[feAF:s"XNG?cN[YHDGQ$er-H^'@J_u[N%2cKan)=G,nreu*+`"Ph'G9+8qH4m: + peJ):",?o4\sQB%7O.DPq>^/eJ1eY)?JfEF3^O(G3kq4b]Ce>?us%h+&8MjlB + cTL'?r<(f:$hIE\W%QtKe2NWf+qB!)RkRhIpZDB%5B_2#j>-7(+3(p,3]*Hj0nNQ6g-X'no + VrHdK`c`@"L.s8,k=tCDS?!(\MKj0u-O4IfBjk4)<>NHUJX^9WQ$qlW@B6B;Z;MRH$*?;:d + ^Sg%&cD?.3YYG12:I\X`A!hT.7UP"@jkO)VRk;m''g"u][F'[,geM_icK*UjS#JMA&-hYQ% + GJG]V8$3h%fsN8l.O9m'O!\YNn@(W7&d2d]!IX_H"L=rB)/Vfc1@Io4jWa@%n4nQ0_mE-P)32_8k\Kr5*:W%HmK4^jG>%2oZ + .!*,hRR+862V-N=H<�@U%AhJ5qlO^WdC/;^8sZ=:WFZs@pD)+`tFqs'n'Bc*MS$c<8P%B8YZ?%u#7-RL)em9+[pNAra0P39D2i]Ic*5"Q + U2m\aO+?"Ko5(WMlh0*oc2UcTY>YfFCDALpCnG5c]h*/BWsmD_@n + obPtaAZ]OA\N)IlAZlr8'U7ZOa6du6\ll&_hJ7c[@Dpgi`bPrViXjjfT-/Fem8.`mp.j$L, + 4EOIg,jT'1nT^Q",@5Of=,WR/m,)T&:C/.o&1M9$cM+ci%Y6EQ1QV6uFW+/hSXTeZt/:ssSJK[;(e,D^,.Js@sDPeA->Q,sQ&:d0d.QC0Nl+ + ACK`8a/YmV,XU`;VGDLbS.!lnM(HhpL=Vq<&BDg%[]E3.)IF-1's3Ti@ei$nXiqXIB;),\u + 2,CWX6g/!9rd?oAY/?;HJ-8AtG>k!#@'g;)Q[bo:8g_G"P]TK^-.GT@`"TlOhBjYM=apPXp + Xg_jE1K+FUhbfjmXh:2MT00_W7J+D_Rh:W!C/IfL/Sj:8<2s2=q39')e`OAU!tW3M,68W`(JmK; + DL`Lp!,rA9U$=;NHe$Kk]J01+%c$RJP%)X.4iuH8hiq<%G=l7V1Lao8YqA*VD`Ubr4=E>6- + #%$3A?NcPGAVe1pY^dFR3Sd`C:X-bnY!9Y/'kF6f'e`8EY4BWCU/c(T,:aaT]@h>?IaI!fW*G!]VPunj8P6L]\L1X]4RHSVM'u=%3;.8= + HA`6L,"5T/M.>i*l7>mt/Y#9592c)g:3mgX!!@XDYg + 5rgeh9\^"+]uIVN/uiHJdp-(?J%nW'S^3 + 1AJ$d`N%1K9fta+Au`U)9*q=I^.U#G@npbFnUrbhXM?$4>Eb%q*t*Q,fgVXt'qR"aMOhO3> + AJ.p/]rU#B'LZG!0Rk&HqFF2=6KBt/G)@*F,ZeOKnjdfERO_Ek?:"0):rKHIAbeG?Z#3/a1 + :q6:74/g$+rh2A@1dMSS7eVma-rq>sA2:L&[[SFh.q/FfTX)#F\\nI^96NDs0BEEG'I7.t@ + Y%nnUC'FT2$_f/m0^.c#SZ4nrjm^[QjA-/t8["&5YD]49U)MF#fprrB + ^U?7&X9`aVo$a:nJV:'U%()dGsJ`Ql`09.dSQlcd3"qR($%HPp?MUDab/>B`5%;%L%i+jqX + \;h@+9`VANnDK<8iWV;/pNPQE*`s;C[;%:W0Bm9ZKUd1kt0%^JZ + e7W(5,GY3siW1L+H-Eg?U(F7uT3u/3*<]4CjE7UT-NEM2on6dd6H@#gE(3:m-f3?n4u5*p% + ]Gafu1e[OB8/6t``o-5oSIpp3dLkiBlRj/ANF*1c,cSk8'sM=-udA=oPRTA_KIM?Xk_-43S + [))/`ADakB_6ldGd)D'[=$8h*#m)4cL/8icI;7RXN]c6LX*%*fATmm2T9a%LnV^kaOOi0#t + pn':b2$`-k#Hk*.hQ'A81U$QbnbC[C@it(nqsALZm+eKAeaRF2SIk + N)H"j:.16Y0ZO>&+^Fo,d2oJBV(* + %,0P'HkR-bN@sQm4aXR0YqNZ"([29(`252aQSj^VQst+/msF4Plkk6jr2)=jH_-W9f#$TNj + PPP7`/.t&Y\MRME:*&=W8\p',#Xf.T[:HbaM9c'G-^mc9.Q3lL&8>2jF^jK[Q/X^D2=4S7J + @/M+DCFTUfTJn:^2b4"\:/]2+@IZssXu8*792^T(&s!2_\P+T,&_ + ?&;V'Sh)B5C54YDG%bV&Hj!Ypn%FD3cPgG6r5t%<2%Q0(ekVd61:UPrA<''M^kEJa!lBipN + \':s!AjTs>NO_@o1/?PC;M_3PbMW@"qQ^6L;d%Wk(E&8ZJO+].5C'gtOA85E5ZJTXdsjJ+L + 3\A@?e_BI(=@rT;]O]JR?l]lHHcW_#RM*:H7'%+1.22[@?3bNK\/OH:160=Xk:*"%^8o/l` + m!q"@pgB19?dWI.iY!DD)&E>u,"^o3EHk7&Wlj\7(f?)7JUA)h + 3:]L,MtAB9$J?WED,:J.s=t*1Sd3+L-Ec= + E%F@Wc+h*`T,_*66h+cG]a>K!$SP#?!p5X*b=7^gAB=P7NZKl'j8DZs+JV0_'N(QS:IhI4l + -'Y;7uprQS4DO1BbP_F`M%?UAtOZ=-C2\teGO`Agu=`C!RQS!7g97.nq)F;4R^gVa.ll%$* + #bbE:JQ6hM/IbP0;f<+DV"%CFnAsb#?_R[T6.:B.im&_3=,]FromS!JK7]MbU,j + TpH78S+2iZD;O&hs,&,:MpZTiC5!&#Cak`l[8AO^]'6>]'9_R5*6&CteA"N_d2G\K\#D.V@ + #\VL4lqK[k"d7!\FLbuaq6n;neiJ>;*M%,N"=E,_B<^MDB2bokCg8MB/'<:)(!q7sJ#U+"D + qLXLFd3+dWBK2.\5[Xjd!]OUc%D`k + &pn,\/TL)0`_'BLP8"[EpJ76Y=5nRJ3DB;r?TiM;`f;#&(Q+FUO_E9-]BYgG3R=i(0K(&Mm + @#mNs^&f,60a>+GU!IHh?8G\fL$_D6Hs*=Xm/m!o*Q<@L/"V7kS]:MRqDs8,)Of)VLBMpcW + DY[3;EK_th*@:_:^2u^@$]1_n&at<)bnRj;(b1KV5,[U&NmIMCG)qBb1aF[1=/2hI*&N`Db + GEBOL2YN](FW,\$*H?@_=KH>$)k8iNS=Q:%Fp/r$ft)rQkECcm&3BH)?t1@h_!X:h#i^6.f + Z`_An-B*'mC-HiJV*C)\;$QN6:N5OJeDIPd_4bX+i4l-3ZTBR(toC/'m5)^T + tu[^_8smR^'W`3%H-8p_U!_L*7L[I6"JnT5@@.DEbir.&^7,j*ljFb!lPUuN6rb:kq= + +57.JD`/foI^He-<_J7m*Ts+B&G6U`?+/br + K$Brq[&;A=gN6S+lPAUrL8P2S'L^HNh#RUXD6n(SABqqo9`h0&8^EqS]9;R=Lk"mjs1Hi'& + b)J@B^Ap8atWb)1fdAJ:8MIdN&)mTc=dcq3ch5?f6EXs$-2VJ+qns#TsFu''9JAK/TD5QrD + 9IqM.:E&,;Aa.2@b0it7[.DE/_u8)t:#'TPa2N5)2"a]$bm#+#<:',d%[Bf2ASk7a!5m9;_ + Z:YBh*h$qn'H;WYIW.Xe<9p3X@e@J7!WB%"ZlP!+^D.?jYf@guV"c_>LHWfH+b(Vn0D7/O-<=6op*]p%Jc9lD2Xs#kM4c-Q + B*7``T(J'p[';foQ0?8gDmrQC]W9>pC-HqPfi%rq<5RAkh+W95`.$kAPVTj + r%'3ue_\m['l7e1E>7&C\bDo2VGTGTu8Yab2RG:7,P;KbX2mh$>d + N@6k]=0Vs@+U61BOOMCM/:D[Sf!^]75!C&)j^6g>PtU7G"p)$r_$6rOghEF`8i7Ic%@)+JV + ACE#b5`,cqhETDHg8+He8+\6?oTd*,!4,5+6.nd=HW@WtGHnblk)h7[92^HUAOEFV7eRC?]3k( + 1j>fBBG=raZ_S0BE<8i[0)"e"qAJpaVT2QXc6)dj&eRT/+`%Br\lS+)+a]gSL1_OmuDS + b!YYfD%L@7r8C>2W#9Unl)Rk]=3"ab`u]iQ2k3X!1a@`NsDJ2QVmP.<^:<-R$or`@=L&46L + #n2?^a;bV+rq>9&D-lWD(P`Ud(JPsM-VhsY9=JQ%:8I=jnoR;GHZpgI6Z3Q49UM%hqC4c9t + u_(go^f\n3='dSmo4O9E:90M@h-;Tn<3G)-T:.Ha\5M*4-[Wko1@t=P]0,9gc\@2p]Lg./R + \"dX57['Z_H=iYEC&3RZWG6aVWjfj#eMRd$BoCF4]rueC"ekCnXg:LCdJ2N8g?.+V$6+^:= + -oDQraqY3`5:n$Y3^\h.3r7&[l.XO$V&T?UTJ`)NnbY]`pH\K-$"AP0"qS6nRTEP/.n^^=; + %ACP\RjgkfJfHX3AeIpe*YWZf\r@/@;u17i8ar/,:fW*Tp>5$f2!nA7$\%a`Ya-$eO8d+>9 + 0XH1W7_!LFW8NE-t<0=.-n$!/,_m4smH)L61s]Rlg?8f!0JMIt,?^OR-\nh1+.lfn1EF`js + g?)deCUSb94?^Z4-@YkSp,XR=7/*qcT2?uMB+Tu2;'7ud:9`c;O(<:.6fP9Z_S7eJXdfn[FBR;V;BH>C-` + m!XZs".aBTR*r8+_3O%8u+f$TR.B!pGlB3@m%eAeg\EHAL,$ArMA$lSjC+Heu9]PEBT24=I + (KL'1:8Ao(YLfSD#R;7I>"N`QPo?mfNLcP&gE9$;B?,fV6r#Z0M!bP?Q?@EcNSC#DU7U!bh + g!q'/&\C%Be=WHJ,8WO05&'-,8+=>UmS!/#6:&2UbB,'Bqm:!+^1M6HDA*?)eW;8d$aYqmI + "e>lH1J`-///U(TD.A$E33phQgZh"Aj>]WUH0SC[`=&:fC+VE@^>f\RhOZ!\dZ9KP7VG@RE + pdM`\^s#RbY-c]/P:r4H8](3Ke%;CGgI?R86lZ^,t6.1nLh:08kGie!u-.!02]TQ:ZU"bTj + jcqU1Olb:o:4UjsRWO^c*B58:?_"("6U\%;OEa.eBXD5*P((hrF*=cb-T79$p]HTR!Z#:+F + #C'j.H)rh?6sX@/N,9qX$B6j4-sDQQ]m#b_1nEZbh; + 3,Egh5?1>@X-:,^-`5KN[QBorSJUf/$PX/-Ap*)n6]&mHq90dJT-cHf]Vpu+rM\0[K`R@$f + isEAWEL#n1JeXSTTk.ib3-e4!lH[U?atb9kU+GO"*gZbcWn')s:$DBXi=1tm<`Tl:E`i`Id + Te?/7&&B=Coo)Shl8=@C>X4rg=6m;(jGXI7+i_9/o,,YMM>S.!)rX0C,1ecj$3$j6T', + (F<6k,h<#]2]C_j6c%iO_]AKb-6bA"[j9aP*ka.7^s:hWbW@Y;JsFd]rOr)e488NcEW3a-c + R;:1CtEC?YhJkIRl^\d+:q0do+lh^;qYZMd/NR?UAPj!BPEJ#@0VFKiCWOP&P-sEm6jRk%mLE@]]`<"AJ+Qm*dc)^qSf+I + `j`(!85FTC]"9=cp_s*e^#NR-Es9k!)XF_f_U"\@a>8NHiL'CmHXZ`)1.:V6@D]/j>3-E6"c]fc8knQDgMU*PQ+D + k(nPZB&I]B.5#HB8nTLi81EX,3F?-LhNY.Rdu,Dc&^='$4pqlmFMrH#ujGF(/p_mi9;L*(c + +F'`kQQ,mS(0[%(P%[D#[C'+,XDS^al7dPJ-Es/+O+\B51jhFoM&ep:$:pFFk`WY$7',X2D + MU<[epV%^8SWC*4Z-"]:[EBT7g,JRd9?G6*1:pZ:Ci]OFhL!k$n"'3f1jjg,p/s6'GoCkW* + K-s[qEB**m(3D8##2=HR8NgH:.\H9pBstX8")D.q>B:V]bY0ee+S8Ag*Cl)<]eRHLa.6Djd + dCBG&u?pGNGGUT,N\:coqt,BP1`>WJLB&aO.Ak7=ZPq<^QK<)="n[bchW=\]@$+lZGW)LJM + ZfGfpK>?.<"X!Oq+[+9TKh);L(V_#>o5n,r[fj;e2c_,LpW11Z@^Z.d*`flcar/*RWrpj,/ + '`P=#,5Wf079Ed[d"t6cn0+uAc)0-uoF#h-[ME*5ui-2a&cNd(h#,ki5#YT^NZb%`8-2;0W + cl`L"!)#,kc]6"# + usB8S]ZY*klaZ"?(jIO;\=_X(5j9N=Md<^ga14:C_KlQR]\__nLs7<'&!E"'_@=G_P87Jfq + .1"S(gC83NDNtX[>$V*CU0S"gh4X,=adeURDkEZ9#NZ>M[#7M4jtGj_L0,aAruWb.\$O;KN + q*=RRsTM8\CQbb^1TL#SLX93OB-VWq8Y-FLg(5('HIS_FY;0Eea0aK]j4?S>dhPCkT;6"`R + HF'%R4p6Q;7-Mt5Q;YZsK2-pc3g,+:!U]H=usfFN?RL@c^G)iVX3jGibkY%*Nt<%,]SQ9q* + <]9=)#"-l3s/P/@$9K8'CM/U9?$+DIY_]a.<&XuB`Fs2*OQ^XR#R=#gp@Me>.&GlAZn?9%s + $SWGeYU]g[8I_n+:U"M-4IZV\Qq@M.8H^GZ"J?8tS.eWPh&/It"S`ji=YdqF-jQ-jbOR^5' + Iu19CD'R3bXCYiN9O*-l9ai>7I5rF4HiO=\.)P9WL[,Q+k-57L_M-+ + HRRO9SLK\KZETNT=h1;cUiL*id.r![M\(`,I.-gHEK2)GAp-6can>"rL$XZpaQ#.2tB_i"p + >Z:qKN2!DsSO>U$4/*!f!BlU+,dbVRIRS:l_0'_)4:Maa)H;'0=3U.Bb.0R3h1. + -@]LdRpP;kt61K,nsV\nI_'8^*nRI\gPS3jYnKe#kb%Ns1&*!t@*3jYbU\3eo+fK57npG^0 + gj8MP>%Y.Q;ghSgBX9'Dp:^W7HU6*($EmQhB:eUB+?oZQd@n2/>dJW["WiR)EW\P,% + =SaJS18"_LOtmR]=Zes=Os\)G&>.=(8"GE+MtRHS.8a@dO&I7;6c;l&)9CaBJ8Lr8Q')5LM + G`Thd#F!hl)YS9+tMb"[Y9%9STJ&+W`lO*>.q!CodSD_`ZZAD32(o2V2,b(5P;+mp?+ + !^1P^pPT"X$(ZP1II"=!u-8Ne11`#nGG2YLDNd;uF&Q'8[.r6AFShQ`H&t4M!N.7L*d#'&# + :]OuZL14#s-1`A6jbbeqroiS*ZqdaS[K;YF<@0LK*B6YO=iR]+,\_+nia,SeR1c0\in + mU#:#ft";oU\*/$m$g_%!ZcH@JH1?$IYI$u87:>3@l;Ys5fZ-c]ULoGGQZi?pWpYAhj")l, + nu-Ro@QJ4`tWPMdk+'Pp;^+g[TGOtNM=dRN(H#L1iV$0+P;4MXpqT8YWu*UZ.MM?$8N/I@6 + ',XH-M]@L^._Fg6IY**Lf0NEo.ma?t8ng3!\Es3R84^YI?YHXOK()pi]83F?0\;K[&Ra5[f?H1Y\.s3!HmX@'5` + B]TBP7S#&9,!\'_C^`q#gL6Y'-'uMEl6sN_\9;m!OeS.#PjBeA<`?UY?m7h'&=%lUD<;W-. + CR`H[SB@BDp'W;75EsD="LQ4i/SSM3#^k^`^\RfX=VJTr%DJ&4L?N(j?Fn&Vjnm + 'P; + g`W`Xnpi.sC&"sI-&HBFuBS^=[_>."FoNZP:T1)!tTdHJn)A"A9_54iB!@2u;e)8%"/k9_V + 1m?rafr][OlZ]HqVc6,:d+jX15Z;Y:oKr3IeW&Bm"B(i$8:8M]3VE'9Ialt.U,uD'3bQj^n + 2f&u8#\lX29*"Bhk!>8eBMJVH2^04YKmgReSiVS8ou#4+g[JZ^i.Fu1/X9&HL;#r5[DYBt> + uY,ue7ca1>ePogjAN^,k.OINcDJBoc6O`1;RS3:78cCpUX_]VeQ+Gb$;/ifpRQq8%\bL8:s + PTb-b=8$mQT + ]L:?<`*E+sK87BRS^#"/fr'Z-dn"IM`/ + n2,"`W_a[B"eIOdYb1(ZQm$W1firj=Ia.MQXcOgK)=V/XpfFu:,0jduda0n?;b*WRi_&C-l]Sp + %/L)T#Ef*[XcS-e[Ziq*1*h:R+uD*aJ(^Y9NRKk/k)=b471.bcl,C,r10>-1tmuP@/6\YS. + 1b-KGk\!8sHW0V&(g&(-\3p1-uW3\>%s*LGU5722X%Fgj,CVqD`@'3bFu$3C`" + LcGb'5^A2ZeC5tuk0$*,8/\ojG',,T6\Q_*9;[OH?0=cP$Yu)RAJn9IYSO1 + 9n$D&P6lp;8H\I#KV/d"pWZW=EVY2Obt0#.j"bR\Z@0R=L[W[,0+U,?%I6!*,$YjDiFd9SH + l6KZaB`$n,:>u+U%p>#o&6[DUA:T9#YPtRuQC"3!57\s%`(IQCq + /U[q9/2Y"/Nj"0tPSh__8CBmPK:8:BXCng%%'ac0n#TeDp78*4_t2@k?rg4*"*jLZ]ZJH2h + kSWt9V!n/DePY\H6"^b;bRo`hT2HLA>\:n/sA_U,lh#XRroqZX0b9)cXO/2RQX^[,J(a?K' + nk&a&5WQb&UB*;mc`l?MICWAo7G\BUeA:X"a#`4FHaMmM->ff`>N*-nDH[&PTnsN'!PVTd4 + /hb6 + '1(d%72bf)+N$'I3D/0<1PI1daYrb9[cts34L:B3Ial'2'#7dP4^r%>a[HSE,?UQI/Kb=2! + nu`Y,Qt:>h$1;Gae\uq)_uoKnPOo]7tCRDbfJ*rkMQVPs1U.ANbBTe:g>qk8/.>R.D8XS,Z + V9FfcLlYH0F1FP>>M'H^W/ClDbj=..$R^@<]L]%i,GDN1l1:'#NG8[.B0\PBDh*?+C!p;*U + Be1#j=X;q1>JG?h`N8muAT8qY06"?#+-2F0rff'!JE?D]ECl[<;.!(d9H("n8P4_OMAC\)5 + do`JJIJ/`WbN)8+B\B67/Eo]NC1:nsY+YME(j-I@BJ?igI;,->#+^uMl#`Pc8dVrM0pu(G[ + -_((fs^;&Ic7g)>Ft)jmb>=Z77%\T2\.!h$=s<-Cb/3mW3c+qa\O:,QHVe<6elYujOE9N:# + )p,bI'eG68ltAMh"jMj`Q>*\HSamjqef`MWkrfTnag_:=5D<]>feSBtR4bZYru";8X87c\? + ]?^9>3D5/5:]bdJOhQ)$NjE^12Qurntj?8:FrZFTGKb2?V= + _)AhRPSPpWtE63B>l$6`(.8_a5B+K + 1)jZhU:0MPO3*^s5&3Q$Bolo'f2riHgM1mXWp53+$JnJ$9d^]#1>'@-:[H0Z+m\*kPH>jM8 + PR'C$6AjGT5GBL^,YU2lDA:`?:T6'$:!-'5KW0VI]58bK+sP#FARC;BhWL=I"N-mY/-aIIr + eK^%30`0AM;N"Mqf*=AiMJO5f5.Rq)')\Zau`>U`IfpOb\jP?$1`R#3c9+O%nVj191(Pfu]Yb;RSpH'E:JMh,M:!2R2* + IkVWcUC5r.kpsKUq/Z!7)ndi^\;G12&OM'?P&bM43KOX.Y][_LtJcTthrSbli]brdi]/rBQ,LRKTCaE=H!0A;l+h0on/ + <_Z^C!C!.$bEjD=jRl+gEjdia/IaC!_N2WV2]p=PU-7\8!4QY+e;^B5\NIV+Im4.E12Vq;p + 7#6)b5k-PVX\(I>-#H5j?lMn4_PC")Z[Qjg'bQA + +19ug]_#Qpc(o'Jf+Z:g[1MH_1,&Zt7*f@RrP0[J7t5g + sQT%3+hbg+Rr]HPhCgOD^'*rN^YYB@GWmR.,O'h4>\qUcB\.o,I/Jnk*QaZ/=U@3fK+T5Vn + M+i&h + EkPbG,rb[Y)kCO-*`cT%oHJYNM!"8,,j8s=/f + *N!4N@$7Xn$JdD<:XW8\#Xu;/3oK'DW`Wr!\atIn.\bJB%Sj#ON)4>,<8^GQ"0`RPOS]R8rt#bE&43J(iV`''(<;HbW6]G'mrU9nMFA^Ad\5 + AYDh9c@L)_@S%WB`.MZ=TF4q):/F9@&8B1b/DON]?tJ1AKD0-l%N.a)+K5ON>P%NdX7)+mA])alB`,Yk7Wg?*@de`A(7NQ + K!m/7X(o.W.__Z-`,#k!XQ+u9-*Y+aN317\Y!FhGJLn`J8.n2R'<_9UOnNhs0E`QK*!)_Y. + iLXUiZ>@ZohD#Siq3kfQQd%e,#Td4NO8a_P\.&5[1EbS:q[#F.O@P0&(3rm!YC_aWmu(:3f + :5.%N^m3W`%ubR'(\bXlBoZZO\[t$k#CZK.uJK'Y>dmiNQrA&X`jBr4H_Y!-!,eVABL'h$> + VZ$K:noiUAT8B-a@9#ElN`jb?m=f&/.bhU848Ycoge[KhnZ$]kjTnFkj\`'A";#Hd\+;X5V + pLCJ!o:N38Eo#S/WZ4$7+%Yh_.cqPlC59pmU0iJlCP8c6rm1?N4pk(g'9W4jo1ClKU_?"@Tcm>'d7QC':3?VO<@,$fKT):q&8*j'6u#3PE7 + N=.d#o/`*l6W?!3PWD]_0-_+],I6%cZoTh70ELfE.j>B6<*FAi;FjGi]rVG + GaNXhHA8G"cK!K3/L3Y=Jgpn-A^&CQ["ZBXO8QmRP!j+5M9_=GM**99Fs + 5NCl1<4bS21_3R&eRk1l#BGnR(?TQr7&A'X_=9)ieK[a*6uWf6A]VSNM,+TS#k[jDo(!c`p + ?D.+"Yu)b,%V2S#=jmG8+c+7eJhB9+&Wgu;:?`0;%T:RUGA_T&`MrF@H,@bmX>U#)PMPN^c + t)K#-\-<'#7$QiD:2O>"S!#bJ@e5,FUX5=up/n3n&+?'6[\r9OUkK7aZ0]YD/lNGk=;V6"K + cD1`\T@,s+L0(sh\i<@(hc]c!RcpYjd]mtUV8kWI$tq*jHAK=(2^?tU1&n"F5oEAXP!Z]%4&WbZEne#$n;aOpc>#4R@c)"V; + 04PbR@oPTZ'](/lk)kG!bC.8Ea%-/teHd@#JpF,chV\4d?$WeHE5H.8B5,lH-)(a)@CjR349ego<8]GH+\!"P')E,f,K"P>B/^c3u91] + =",&s/Mh.41X>@/0tJ>Z^)LYuX"$b_&WT'3Dn?BjJ$/Jf,b(%98'nuX@H*pAC-t-dMnmF+KnCkJr2e`'k/p9)!Z6h\"A6(++b=Rl83B#8<5%'7:]=/1#hIf!#L:=[N + Ii[KMGWklG605lASI]0=In`eI!Bc`92[nL0!&!V-d)D%^j1=0F_B&NY[]lPA,(8;g+^'c;A + =5U66(98s"Trad,g7-r7k(!>q_f"a154Y%-O]A7tQ0cno;hG#@SeD/csZ/q5+u_+rM6kl@C + 6@"jTkn=q@O7TT>$b;eL3/WUVY0XhE;EQ]^pb"FG]Y`H('L)rN5R*I*`OW)ULB7-QYm]kZ=-7J>L/7Q(OdR+* + ]"QL3@c't[N9Bi&l@(0+jlL8R/^'HSPn"K;\%D@YoreK`mXJ"4cMB_iO(flV`eRC4f4`lbt + UAG*e:T\eV'fNJO_p.K0Mc:9E`3K_'\W;qjnZ_P9Ip[^kNkn4]ukGGdJVS[O%;!.@>1QR(< + NqRL>e37=pmY6o`A,f[#W-VIDgL%I*RS.M%]0r$ + *`&^4E=JXB=Bt=QDVtmk:,GVl6Q&0U6TpYaY@a8/`#AR + d7-1muVgU>?R/9OV\pU*A\S(OjPNh\%.ZZVABj;SiJQ"_5\.kC_^AX'bt+uP?o"p:DG.?Pl + ZB"9[KPiC-/3'c,7O`UWo*Weie[pQNDr/6/eG<$o(irGr@#nX?P#-nU];e139p/b^^bB4*) + .X4g3qfs2DSp_H5dDmp\G^0Bd7K"#7"a^am#Wtn4.Etfr)[,&6mNGp@X.`DJld:QEnLfA14 + UB$B.GS*%_UuZLOS,$_*3,9XP_cgIPnqCcrB8ANL;d+/I>':DPH\ld:M69*ACZI5SVl8To" + [$D3K7r.WCCAjG`O+C"]CEa[kJGr\NB4R'_AWfApFR<*XMMrZ'a(I6Gin/%XjH@uno(Zj28 + 6`0b?VO"OTDX]CKkP31:`k%60,#3[9U>$)$)n53TN),]Fpl&('.)ApJ%u;1((W$A9]*4N\9IgH*.(XRflBjtO0uhXQ\"COqpn$U-aoN"C:iHJ[hBX])m?>jdP2q + $0sRsf[r[2SWe8Z7Qc)!&aW-"88=.[a#/P_$\tSlR>8Q3j(ZZ6HCOW;M'LBAAi]hGqJJFrnXeB48"k1/kgKh:]S*p?%0l?gb,(f)Di&f`HW`S*aK"@>:[e7b,O + `/EMW$CTjJIUJV'\;]m::f[:HsCW(Ek+4I + ^f"%p)H@f#Eq7ig%4Z`kF[1#6Cfq%HYls$%JV#tpf'3,c?\1`*SaO8@R.X&ShfIoGK-=_hs + KPpe>l4'3=)(,0`W0iNV3p5^3)hsGFeGs3n0[3,chQB-lb>2h@&L2bYl)F!^'2r+0CC=/,M + #Z:E\?jSh_G*!!n-'TSgfGiXj.a2;sQtF2f?tM*E2Fkd17C)3%,GOkB;T5p;@ZT@$0i8ans + 0!%!f]FM;0.%[G2m"X3 + N#M:[^uDU\m#QKMRRp2\>4%X1FhTY-IGDiO>/f@g=A`DV=4UCM[t/[0hO%IclCW%7@e#i@G + !@.4a^jt1>dduoRE]@8RG@Lc6(u\>oU9&DT2DTHQ/"Uh[cD`%JipB'_Ep6`1nfl(%8 + VIWW,u@G;JILO5&,7BRNo(cWnLIZudM4*%m?aJ3G%[P@>*f#TT\LE]9p1ldVenaq(([dh7Y + W40r,ADt&$4-FhKB66i\6q98Z0H;k\^G8/1\ZlL1VMs)=9rn)SKc';NL0r6&h!SCm-)C85Q + 3]3I<6upC#/-6hr=2S",g@*oP2rG\8WlJmU.L(->MVs$05WR?;fq[o:fEVmaE!WBgeB@n&S + ^=c`WrSV)Br%aL>59hjHu[`9oP3@=1.^N?s#<%LLOAo2329N2H?(dWU)b4?`uAQVO>i2a`@ + ZaVUS/rYj>)PE&-^*nlK"]29r4`=&f<$cXif(GYMe/3cILmD-Yk#p0-Of`^)3VFC6PZmt*e + \M6hjr-UkV@=#'rre>.pU(Q)U&Z=k=i.cFpA(bhDsECEUud>-KdP+\b53E@f!-3M7o>@oQl + N.r8AoiBoulV3.k,)J[I@d#giLM>clnP>rVYUOm1l*/jPkI>^/*.M(Yl;!0A)V0b=g<%cfT + -t$^)LgZOekY'7]2lIa"abDO".0Xi`,Nn&)Cq!;bq;cNJD9eNQ;Fe*h[Jd$2T`XP4BE?=EP + -K;aDsD0-d$JB[hW1,'uOf-7h*n$hF^NFK3(%?>d5kKc@-IKFg1\]E0Z$_7o7\5Vf1B>28?d&G`\*qFfGs%SAao)9<,"DN4*&Uc:d8K[cA$+:FSqc[F4:F$Bg-7Y(hnTf]sV1AX'O1M%n'fYtm<;W&2XScHF,D*bK6 + =S"eJSeJbfu.&J"d5`fV5AYaP]P9o.ZnS4T6SBk^PBc.kH3A8K)4sE8sD+o)Nf9X<^>A]1A + [5Q1e>_qEg.%"!D76l(dLJmQ684:JLH[JV?+at7L4s.nHP7VPrL_@W#ZX?Qq4k(ru + ^2*YFWPLGno#9Y6?4T@g4[GD,Q]r,0AG)jt6,(Dh,$m[!-7"WE<;Hipl8UL^.(SO#Xsf + 8?X;-Ibd2FA3>l7JG"JH\o;M+/Y;9V&?%>>f\5uS!LG@:SaCKJ'<1sh')W0.Eq=>o28QFtG + >/!8bsJPIW[E1jK)=B09-P+CV07I:=qE;GKa9Sf6^"F=F,Fqo=SI6= + jnG75\rU=6@^d-8h-7L:Df,0^=RhW(7X=^7k472A,Gn/8'UXjXRi"u'8rZB + &.%I\>4^R92RG"59LR%7EcK4(ed&uWllAK[n4;:n];ZrerY+d'9G(k7u@-5]hkAr6.: + L:gu]lkT,OtokgE5U/2n=L$GHoHiN*[epE.1XU%u7a1fQUhd]YDN;J)SJ.X,#BoC]b.g + bW%7P*=oMd`r=oET[WV&l"=7Oh1=Eu:p4s1VN*gr7;k'.O@a"soMaDM7pafQ)5p9o9Tm06SLeB;?9n\/NYYe/s3V[DNOH\< + A:i=7gK`.O0Rj@Q?A$Q=h9JP]AN5Z#.-<]t\0*\fgL5lnZu";"E7 + $/>3Tq$E6V1Q]de(STH@R>!gQk-Wcc/RY6RF60N^aM*1g5*>Z[I!N#U`f6b6X#S"t8;TdJ> + _eMI`Sbn[=Qp*3\s"@1/'CGER_Pp1kqk`DSm'$mqS\H;`L9-6"WGGc!m(T1aop/9)cFn6J4 + ThO@R,p-r0l%cBkLEU/6XtBYpm!G-rOJ2l!0=P^5p^T#pVsao^Hd8SGuj6ns>1`F4 + s_4Jg!sX+I^VYk\+*W5JntH]=(cbe-()/!K^`gpt + (!8>=76F&O:jj't'[6029^iad*)VhC-FuP!M'/#_jOMFY;$Kn7`@RfW%,&n$gcR?,&.+DKPCg@[r\4@7C%Z3Tm!+Z6)P>6/F#67?rcrpSq75NURE[r^[jKBG1TIDGLR[7($m'd;!O,180n8fTn9]L=Q5-Ugub$A^N: + PdQCL%Y20)QX`7rq)6(q_2^(eb;j/g1J'WcYQ>J)]"'R.1os3\30M4?"o<)@SUaQr"4@<'#Y37\0p1*c\`S&URGe:"1_s#aEI5*6TR)Dn + %>d_AU@hq,]k\.(Kli=4N#?jDc3U,8(LV^5EC5c:&VWmdD96N+,]/\)WR1/"sPbJ_ + (oD*cA@Aj_+,P0c_S:aTFT,$]2DJ`s3:]f6%Hb-$O9bV0`9dQN.8&_ujf>m)]Q=:P>edX]< + cUTb++V:C*Uf%X:2h1,gfO[?%Z&;f+-*GRrtS@HcG6F + k-RG]*IK;Agp8L;X+>]bg9"R7>glmtUgE`_hZ`N,&iiu(U;t0mfMh6oX36MZ1CGLA:iLKVT + Q#YdE.o\a=dAoG+D:o!+;>)\7Vl_)g1E/R4.9Gs1e<<`aC`-aY7];i"cPoEe`W_#E5(+Umf#4g"HI2dKAam-8FO + -1?1-F>#ZHh>u8?kOqUR9!?J1tW#&s>N)Y'cSoYr1o'fZ(6<+`ENZ3e*e#7pP6JCXX,V@gW + `B1"iPCcP"gTc_km5dlT]W`-b(+m:"gTb']4kpac;Zs4Z0]!OIKb2B7)i>.T\e1M?FtV'Aj + b?n!Q%A',-.Aj/X=9k;(Jba%)B%H4R>bYiH)6l:@^qu+*c3#dbs(Ss_6%C')k1^ + YZ3j-VMT-=pT+cO`Ua[iK'jXM[IZ;0F"!5DCC1@pQ+lBOPQEJtlYTF5IPr0NaW@de_n$T;EkB1'VsPZ^#pA3^ckL9'm6r= + +Aa[ENDHI<<;EsFDj(GU`c[R<9UAhLT"1VsTXHNRn>_I*E1YaO$,!BL + UFLb[sI6GAqaXHA4HmMW](R=:/'bO0uI[Ya<[g%!q\_Pf/O&LArD[RiKV8kXTGA7MbgJX"p + dT;G.CD$-P-50_TmHk%RfS"Z:Oq$*7V6o71iPF:TVQW]o"NqKkcR-Ecj69uZtR[F0iFDgF, + #3(H)j+8oEhBl`N'I-Pu/2+dE3f#)*P$EfVLPUrSp(AsF3'$]q3Te[c9ce3?s$lT-Eq_M6p + I@,M1u9XX'WXo3nSS#$DOOUNT;`\^L=g2F%Opt'-kC!(K[:o+@/8CS`UP=c.0=4OA?eu!^!rJ0&(Q$3GE8KE*[qpjs)>XDG*5-ggormf)bRGto&/p3)5)L.B)Ym18/Ju> + RAnFD=t8q@7L"Y`Ya)F'fAXsRP,/`9:#,5)G\KJ=WDG[i3Cc%n2#9fSnS/D?%A,>IsnMEKs + ]3TNr',Ha`-PWRu&).UrsoU%dR:O/_pD#'SbVc1([p%^hF5A4"W)u>OaUf(Rl\n*dj:T.6- + te-BEAYp0dNZ4QVVA; + nc)qP<.cbQSp`ejDa0aff%\eZ'T4f=uj$7Fr\Y7BNSFqrfq]*JM#HA[>)(lY>jjfgGKbs5I + XC*;`hA#odRXIE**Dj6\Qp)j)R-WSnB^Ib33nm4T:[KWRpD.HJPaEDnX22X,>3K,J21hoK9 + fh)3`YJp:]=#o?3osK5dR>Xk(8Mp\b',&.A^C^lU)V"2POB'a;F8C`Nbt&<%nA_N:iMXXIo + <8md)[loblt&I_)?`/uTEhNI!E<+Nt(_6h1c.GBOb`fMC)TpZM6CE:@@m!Tts&e'(*aGm/e + <(RO5@+1H^@?QNem!B*6Y&DG\)Ses+J@r*p7(ja;'+@2.b_62DBh]"m_Sa'mWe@.j&;4qp' + iV3Q43nQ?I6Elpl%;4F#*M)g0WWoY3)1t,k'jJn9cZ@E&d\;)QIMs%@l!2`7!#R_]WFT'FB + Wk$YV.`dF]AZ8<5tHK/R6?I#m&(Les3@S>#9@dF[=TDEQZ8MbR4)'+1+I&74?U_ZP/(KEsme9N\=3fRuBau]M7?C)Do4@mO:FO.7 + ih,&5Ip;-WN@$iPUKAdYB'ntV6DB4Ml(lfo!ig?aUr@Q@4J@RkEZ>dI[DgMDuY\uhd;`ohH + s%V+p)8KVlDYpu4k#nasS*#_kH;G)'M,I@;"`Aupk_5%UeWu1hUg@bsfnZKRno:(sU.0__B + q!btZ`Tuli](l$9RU$^Yq.sD_;/o(g@qdd]B5Y:GhRWE9%_+BMf0Q[otBgP-LC\@>Ee?`Vb + =`hRQ[_Mf"nSl4o]WSQ(c%9S%Ph.-lSW$X/VMMEJr/hjp?I`hc-Rl5*f@q?_A-S5u:I&"V5 + ->G(eah9QpWKFPjRgM&:"@@6SOupmrReS9Y#dOQW.r;)a`hff%9GVGDR"S>U^cICX6ZIfI% + K^CSXeoDKb,eb/lu]srJXGJE5M=ts8cN*=8n"nocJC5+P&!*at="p."K%'tK>#np,d + fns3D"3&1jF5n"9\M:m/9D\$'mt()1,'73lE+sjgKGD@&5.VJ,lRsd!/&/rs;;0[U!jKr^_ + c0*s_=OUI.Kr,:;J?LUZAVS[4rYpS-6E[g$lJ8@9rZQ[Vq^3h/nO.,'*_!8ikY(pd;CQR=M + 5O2"C[5N%/(>Yo!'=u6!5K2]S(Oc&QFDU>&W45P!ibSOdk>YjimJ,Gi50$+9?Zf@Ea^"#-! + cZ4c_;,W$S0e(aOcaP!B%qO@AqU=8N#Z$YDk"ff^_E"JM&XN`&JP9e#0hs*O#^Tsl3?i7RR + E-Hq.WT$mbPfeY$=N]gAmk9MmFQL^n=aVl@6+?JTK.ZJ\SJ`=58$P@#/h(q]< + VNfJe-mjsD6KT/)!,&5D1,W%RrA%l.2(M*t\l\dU4QiO5P31>&:Ph#W"6mYEj7+VNI&Wc<% + Y?DB!'d(=rKY@I=L`DI/P2294]:A416ts3R;!^Xm<0>SOfY3g,iXYK-t3a=JC=\]dMB3mC< + \c1.HuAXVFd.O$/04t`70DP[-UB5d7>O8W8J;=@Ui*"O&9X\8#]!$9S,"K3(BBL-"OBLP6T + Z6B@-H1>4QN]erm-7MA-X=!>`CMgAHd + m9n;)isB#K!4fj+JLK\W<)HNH+ATaY-*Nk$6Z1EZ99"5 + 3j!9\(M`c,A_;,M#+U$8+QZ>e8)Z!fYW5Ec$%M(+h^H-I]f&;9qhc]]&BC[?AM)t=4n'jZ= + K>/$LN?4"p)MA%g$-D%>!/s#GEt*T$%oY9ojXX-4VSm37:W4>_.n\",(S%OhrLI]h,0TNtD + i[.L1ZHV@sC6eheORP+>#l(:EBR3\c$GBd?<@7W`8\Z(iI?JhF(RiQ,fLG:,-,W2 + `YodfL8C[kWM7_?.&BJSgeXE%3,Z[W.!LFL8IN?['Ccj#WHq2M<3aFmJoTilDaa5._<3u-K + ifE57Xj"pku[H4WRY1UZ0uh/hGR!(PYoBTNF8fqs[lh2]2fRMT->ZY9'eo!IZXe>OQS%u7jg[KR + MA`&L2s;%9rE[c/U$GahsYe\6EWpBA_-%Dhg0$/m]jn8*hL3_s45\ttDHLKg + AXMtLJB[di1uWIK@as(lA\uA@kt\@VjuC'giO9RZ(5C4?3NG"fk^s8s9qE'0T-=@pWjR5+7 + S)2Q,KfgV[R*ZeRls^lD5`lf/P(;)m\c]u"2]:d%@--CEH##;m+a_A?M[D20V(%DbJDQdPG=#Hh#r*VKL7"/.SR5"dhg)%'0ZF + _c-0ZJRpj8pT1q8rGIQb+u6?PeQqHm%`I:',LhaXn=S@oJR>3!Ts"H/pH@/M(t<<.X'&H,s + V[X`k_*P[?NRI6iURIm/S'fRSEQ@EZ+,4]9[V#W1$6f:*-W^q`_0I@&^+-63W@Jkj3d/[aJ + h-jiLCK0-U=GQ3IKrsM-,oF']M#2`$"E!qC76(2m]`X&BnISh,VnWCRr$K]5#nR=3qr'HpW8F65r&[uO;-rNROP,&WNI"NZ:.A^6YX + ,H,Zo,EaD1!&L*jKIk/&AJ`[%9R,q>c5-`.rE5ft_Y50U%r8QQ:^5nkR@$c`>4Nu7E4Ng>7 + _QGQmn&CR;PG7BEa%E^LPME#Bd4JW5_S4UMlMG0iR<2G>.V8VYr!D]LX7])&Z'<6#L8pI8- + 5S??S)rW1I0]`e,Yo;b>'&Yqo6,"5:U`Jhbq!%A!C%g&[T$#GF'N*O(Z/$Gji;/MB$*\6Xn + >MJ0KR9I(Fhk+$Xh"OpR]jLC+Dpch#(2l8R#+D[9h&$iNT?")-3ec0YLM7M!jVdlpb/I;fH=H+TF34;=Ij%&^#AIlE#QP:N%gp0.rn0`n=CLjpBj4M^X@0SZHXr + kd4I-(+pn>6CP*$h`se[,U-[k7J_[!=;H73FE^?iG!Q(h@[17,")rW7KTjSS..dNd/RUhZo + lt"+g:<(DeS)eU[8][F>17]X>SS?eYC#2iYru<\L*`%g""+hr%n1\8i96*T%5Tk34(d*U0-PEOO`Y:#-aof$$7ZtDBV=`nCq + (-OS8Q=qL[-^W\nP:@"+"$4\%iZ5hW1j=XEC87#'65f/9Z&$tc,%17_8$bXM*#i*R,NQ/i6 + a8Q>]iP5Bb&-*U(#'D(*TCla,GDe!\P]+L2q*f*3>/@:PbTnjG^:(e2,\rc^>d]VJ`RB:K; + Z&E*3m\hXRZgklPEmJ`T_K*A9'!05iUdLFaYQTPEVhm;G<.WOh_R#^"Gmo`fqhbl[R!>kS\ + =G@Q(7@3cI/Se>FB;KKBSZaLjiJe6I=*X1.r;b]E6V_p3Y@hj>t[JaJ+!Bu_=!`*E38\g'2 + gc\IM5ZSb2[N/fM+^qMf@q,RTWM0sT0gO[8#IZWceC`S"-Y+A6A8RMQk)D^&6;T,^Z(bA,_ + =pMM8@mgS0X`YHe7]%EE-%=I*]-hD86IG7QK*Pma+.<+[Q[qO9\laN7rU=UZrnbNlN@[bqg + e]][#3c5O[%Cu:/nG(Y=jk&9-34HmF_c1)E-DV-[g]@%?D1.2$o;N)CrjiLDXqo[c!j6/,!]_UE";QoF(;Em@5%o + to+,><&$EbbOTH=K;!d>;1aJUYDV?:s(2Jri)s%:lJjq#Nk<06E4]>+uF57i\M$94VG\q^X + XM&Uoi>#3l*cYFqnR2l8eIA4p:hhm58ig`BF;aUn7koB7%a,\4C8IjMV;i/-B>/)^0\N-IDP.W#i4d\hd1s[<-<r5G4ZrWj,J1r=^+CH!TMaglPR=a(^B?a. + a"=-iS((WCC!8,DKp5qhk.qqt*8!pqt1kA1uWG99RJoYQ.p9Q+@)Fi;iC[]pt[`;-Ub*VjN + sFe(aDs=1+'poY3[0Qr$@IX4h(]gGRJa0i,AEeS7t];0YkmM>:q,tMV'u\QREk5/2@lf)pI + >%6)r4oMA\sLcP)HXC@sT<@)^-5J[(<`N=@q$I6,1i(-s2R\*Ka`FEo%J'EDkg@EoifnIhS + u0D4d7e(XWJEs4A[S=3$F\kT!]07iTEZsZ+pYR@;c4\\1&ZuSP:-tWOR-)Vk-VfD^?+;63^ + $\u8KW,bIajA=2Vk"he@M'UJ[AQ=S\m>:,MO?rV/,KSqr^M*D`!7j!%_[8Q;*tsl!Uf>Q2, + *F?d^($O\!F"0TL$`S2E<>]0Bp)P3dT^HCK*3f6C7tTAoT-"U:iFjf$$utp'Vsaa5q6DUQbDnYWGF`]1o[)ohUj(Q#TT%GEl:3-DG + Ke0(EBk<>9I>Zl1+)'9@@34+3H(KG2/C;\$MVlslF(,0$NN;uqfF%!FG.I%9db)emmEP<:S + ->;T>g1q.a;&"BY(6[1J4[2[2rmh)sK@^oL&E>r:,O!MTg.p5Y%*,d];iO!Y21a5qu7>N