From 2f3846fa640f6e96e3c8ea6b280fac5f1aebf811 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Wed, 20 Sep 2023 08:59:27 +0200 Subject: [PATCH 01/24] added cookies problem benchmark --- benchmarks/cookies-problem/Dockerfile | 61 +++++++++++++++++++ benchmarks/cookies-problem/umbridge-client.py | 33 ++++++++++ benchmarks/cookies-problem/umbridge-server.py | 44 +++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 benchmarks/cookies-problem/Dockerfile create mode 100644 benchmarks/cookies-problem/umbridge-client.py create mode 100644 benchmarks/cookies-problem/umbridge-server.py diff --git a/benchmarks/cookies-problem/Dockerfile b/benchmarks/cookies-problem/Dockerfile new file mode 100644 index 00000000..def3b1cc --- /dev/null +++ b/benchmarks/cookies-problem/Dockerfile @@ -0,0 +1,61 @@ +FROM ubuntu:mantic + +RUN apt update +RUN DEBIAN_FRONTEND=noninteractive apt install -y bash python3-pip pbzip2 wget clang-15 git cmake curl zip pkg-config gfortran lld +RUN pip3 install --break-system-packages umbridge + + +RUN wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \ | gpg --dearmor | tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null +RUN echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | tee /etc/apt/sources.list.d/oneAPI.list + +RUN apt update +RUN apt install -y intel-basekit + + +RUN git clone https://gitlab.com/max.martinelli/igatools.git && \ + cd igatools && \ + git submodule init && \ + git submodule update && \ + git checkout a335daa3bf480cb6e57e2ea8522e3817e841a66d && \ + cd vcpkg && ./bootstrap-vcpkg.sh + + +WORKDIR /build_igatools +RUN cd /build_igatools && \ + cmake /igatools \ + -G"Unix Makefiles" \ + -DIGATOOLS_COMPONENT_DOCUMENTATION=OFF \ + -DIGATOOLS_WITH_GMP=ON \ + -DIGATOOLS_WITH_MKL=ON \ + -DMKL_DIR=/opt/intel/oneapi/mkl/latest \ + -DIGATOOLS_WITH_QUADRUPLE_PRECISION=OFF \ + -DIGATOOLS_WITH_SERIALIZATION=OFF \ + -DIGATOOLS_WITH_SUITESPARSE=OFF \ + -DIGATOOLS_WITH_SUPERLU=OFF \ + -DIGATOOLS_WITH_TRILINOS=OFF \ + -DTBB_DIR=/opt/intel/oneapi/tbb/latest \ + -DTBB_LIBRARY_DIR=/opt/intel/oneapi/tbb/latest/lib/intel64/gcc4.8/ \ + -DTRILINOS_DIR=/opt/trilinos/current \ + -DIGATOOLS_WITH_THREADS=ON \ + -Wno-dev \ + -DCMAKE_INSTALL_PREFIX=igatools \ + -DCMAKE_BUILD_TYPE=Release \ + -DIGATOOLS_STATIC_EXECUTABLE=OFF \ + -DCMAKE_C_COMPILER=clang-15 \ + -DCMAKE_CXX_COMPILER=clang++-15 \ + -DBUILD_SHARED_LIBS=ON + +RUN make -j8 && \ + make setup_tests && \ + cd tests/ && \ + make poisson_lorenzo.release + +WORKDIR / + + +COPY umbridge-server.py / + +CMD python3 umbridge-server.py + + + diff --git a/benchmarks/cookies-problem/umbridge-client.py b/benchmarks/cookies-problem/umbridge-client.py new file mode 100644 index 00000000..cd8b48a9 --- /dev/null +++ b/benchmarks/cookies-problem/umbridge-client.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +# run this script as python3 umbridge-client.py http://localhost:4242 + + +import argparse +import umbridge + +parser = argparse.ArgumentParser(description='Minimal HTTP model demo.') +parser.add_argument('url', metavar='url', type=str, + help='the ULR on which the model is running, for example http://localhost:4242') +args = parser.parse_args() +print(f"Connecting to host URL {args.url}") + +# Set up a model by connecting to URL +#model = umbridge.HTTPModel(args.url) +model = umbridge.HTTPModel(args.url, "forward") +#model = umbridge.HTTPModel(args.url, "posterior") + + +print(model.get_input_sizes()) +print(model.get_output_sizes()) + +#param = [[-0.2,-0.2,-0.3,-0.4,-0.5,-0.6,-0.7,-0.8]] +param = [[-2.3939786473373e-01 , -8.6610045659126e-01, -2.1086275315687e-01 , -9.2604304103162e-01 , -6.0002531612112e-01 , -5.5677423053456e-01 , + -7.7546408441658e-01 , -7.6957620518706e-01]] +print(param) + + +# Simple model evaluation +#print(model(param)) +print(model(param,{"NumThreads": 10, "BasisDegree": 4, "Fidelity": 2})) + diff --git a/benchmarks/cookies-problem/umbridge-server.py b/benchmarks/cookies-problem/umbridge-server.py new file mode 100644 index 00000000..22e919ed --- /dev/null +++ b/benchmarks/cookies-problem/umbridge-server.py @@ -0,0 +1,44 @@ +import umbridge +import os + +class TestModel(umbridge.Model): + + def __init__(self): + super().__init__("forward") + + def get_input_sizes(self, config): + return [8] + + def get_output_sizes(self, config): + return [1] + + def __call__(self, parameters, config): +# def __call__(self, parameters, config): + arguments = " ".join([str(x) for x in parameters[0]]); + + num_threads = str(config.get("NumThreads")); + basis_degree = str(config.get("BasisDegree")); + fidelity = str(config.get("Fidelity")); + + arguments = arguments + " " + basis_degree + " " + fidelity + + # System call, cd into working directory and call model binary + os.system('export IGATOOLS_NUM_THREADS=' + num_threads + ' && /build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo.release ' + arguments) +# os.system('export IGATOOLS_NUM_THREADS=10 && LD_LIBRARY_PATH=./ ./poisson_lorenzo.release ' + arguments) +# os.system('cd ./test && export IGATOOLS_NUM_THREADS=10 && LD_LIBRARY_PATH=./ ./poisson_lorenzo.release ' + arguments) + + # Read second line of output file + with open('/poisson_lorenzo_results.dat', 'r') as f: +# with open('/build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo_results.dat', 'r') as f: +# f.readline() # Skip first line + line = f.readline() # Read first line + + return [[float(line)]] + + + def supports_evaluate(self): + return True + +testmodel = TestModel() + +umbridge.serve_models([testmodel], 4242) From 90d74f4522d785b200e9819868f6e29f470b466e Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Mon, 16 Oct 2023 08:46:17 +0200 Subject: [PATCH 02/24] added readme --- benchmarks/cookies-problem/README.md | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 benchmarks/cookies-problem/README.md diff --git a/benchmarks/cookies-problem/README.md b/benchmarks/cookies-problem/README.md new file mode 100644 index 00000000..c82fcab1 --- /dev/null +++ b/benchmarks/cookies-problem/README.md @@ -0,0 +1,103 @@ +# The cookies model + +## Overview +**This benchmark should probably rather be a model but I will prepare the text as a benchmark for the time being** + +This benchmark implements the so-called 'cookies problem' or 'cookies in the oven problem' [1,2,3], i.e., a simplified thermal equation in which the conductivity coefficient is uncertain in 8 circular subdomains ('the cookies'), whereas it is known (and constant) in the remaining of the domain ('the oven'). The PDE is solved by an isogeometric solver with maximum continuity splines, whose degree can be set by the user. See below for full description **ask max** + + +## Authors +- [Massimiliano Martinelli](mailto:martinelli@imati.cnr.it) +- [Lorenzo Tamellini](mailto:tamellini@imati.cnr.it) + +## Run +``` +docker run -it -p 4242:4242 linusseelinger/ +``` + +## Properties + +Model | Description +--- | --- +forward | forward evaluation of the cookies model + +### forward + +**check with max whether check on input values are made** + +Mapping | Dimensions | Description +--- |--- |--- +input | [8] | The values of the conductivity coefficient in the 8 cookies, in the range [-0.99 -0.2] +output | [1] | The integral of the solution over the central subdomain (see definition of $\Psi$ below) + +Feature | Supported +--- |--- +Evaluate | True +Gradient | False +ApplyJacobian | False +ApplyHessian | False + +Config | Type | Default | Description +--- |--- |--- |--- +NumThreads | integer | 10 | **ask max** +BasisDegree | integer | 4 | Default degree of spline basis +Fidelity | integer | 2 | Controls the number of mesh elements **ask max** + + +## Mount directories +Mount directory | Purpose +--- |--- +None | + +## Source code + +[Model sources here **fixme**.](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem) + +## Description + +![cookies-problem](https://raw.githubusercontent.com/UM-Bridge/benchmarks/main/models/l2-sea/l2sea_example.png "geometry of the cookies problem") + +The model implements the version of the cookies problem in [1], see also e.g. [2,3] for slightly different versions. With reference to the computational domain $$D=[0,1]^2$$ in the figure above, the cookies model consists in the thermal diffusion problem below, where $$\mathbf{y}$$ are the uncertain parameters discussed in the following and $$\mathrm{x}$$ are physical coordinates + +$$-\mathrm{div}\Big[ a(\mathbf{x},\mathbf{y}) \nabla u(\mathbf{x},\mathbf{y}) \Big] = f(\mathrm{x}), \quad \mathbf{x}\in D$$ + +with homogeneous Dirichlet boundary conditions and forcing term defined as + +$$f(\mathrm{x}) = \begin{cases} +100 &\text{if } \, \mathrm{x} \in F \\ +0 &\text{otherwise} +\end{cases}$$ + +where $$F$$ is the square $$[0.4, 0.6]^2$$. The 8 subdomains with uncertain diffusion coefficient (the cookies) are circles with radius 0.13 and the following center coordinates **check order with max** + +cookie | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +-- | -- | -- | -- | -- | -- | -- | -- | -- | +x | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | +y | 0.2 | 0.5 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | + +The uncertain diffusion coefficient is defined as +$$a = 1 + \sum_{i=1}^8 y_n \chi_n(\mathrm{x})$$ +where $$y_n \in [-0.99, -0.2]$$ and $$\chi_n(\mathrm{x}) = \begin{cases} 1 &\text{inside the n-th cookie} \\ 0 &\text{otherwise} \end{cases}$$ + + +The output of the simulation is the integral of the solution over $$F$$, i.e. +$$\Psi = \int_F u(\mathrm{x}) d \mathrm{x}$$ + + +The PDE is solved with an IGA solver (see e.g. [4]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$) of maximal regularity, i.e. of continuity $$p-1$$. +The mesh is **fix with max** + + +## Bibliography +1 Joakim Bäck, Fabio Nobile, Lorenzo Tamellini, Raul Tempone, **Stochastic spectral Galerkin and collocation methods for PDEs with random coefficients: a numerical comparison**. In *Spectral and High Order Methods for Partial Differential Equations*, Vol. 76 of Lecture Notes in Computational Science and Engineering, Springer, 2011 +2 Jonas Ballani, Lars Grasedyck, **Hierarchical Tensor Approximation of Output Quantities of Parameter-Dependent PDEs**. *SIAM/ASA Journal of Uncertainty Quantification*, 2015 +3 Daniel Kressner, Christine Tobler, **Krylov subspace methods for linear systems with tensor product structure**. *SIAM journal on matrix analysis and applications*, 2010 +4 Lourenco Beirao Da Veiga, Annalisa Buffa, Giancarlo Sangalli, Rafael Vazquez, **Mathematical analysis of variational isogeometric methods}**. *Acta Numerica*, 2014 + + + + + + + + From 60c4b8bdfa4e5b973c1fc020d7d6681c02aa7b1a Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Mon, 16 Oct 2023 08:53:08 +0200 Subject: [PATCH 03/24] fixed ref --- benchmarks/cookies-problem/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/cookies-problem/README.md b/benchmarks/cookies-problem/README.md index c82fcab1..c338a3d1 100644 --- a/benchmarks/cookies-problem/README.md +++ b/benchmarks/cookies-problem/README.md @@ -91,7 +91,7 @@ The mesh is **fix with max** ## Bibliography 1 Joakim Bäck, Fabio Nobile, Lorenzo Tamellini, Raul Tempone, **Stochastic spectral Galerkin and collocation methods for PDEs with random coefficients: a numerical comparison**. In *Spectral and High Order Methods for Partial Differential Equations*, Vol. 76 of Lecture Notes in Computational Science and Engineering, Springer, 2011 2 Jonas Ballani, Lars Grasedyck, **Hierarchical Tensor Approximation of Output Quantities of Parameter-Dependent PDEs**. *SIAM/ASA Journal of Uncertainty Quantification*, 2015 -3 Daniel Kressner, Christine Tobler, **Krylov subspace methods for linear systems with tensor product structure**. *SIAM journal on matrix analysis and applications*, 2010 +3 Daniel Kressner, Christine Tobler, **Low-rank tensor Krylov subspace methods for parametrized linear systems**. *SIAM journal on matrix analysis and applications*, 2011 4 Lourenco Beirao Da Veiga, Annalisa Buffa, Giancarlo Sangalli, Rafael Vazquez, **Mathematical analysis of variational isogeometric methods}**. *Acta Numerica*, 2014 From 1dc1e9ac1dfb1b155bfe5b3f31d7179ed92d9c8e Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Mon, 16 Oct 2023 15:09:42 +0200 Subject: [PATCH 04/24] fixing readme with IGA information --- benchmarks/cookies-problem/README.md | 31 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/benchmarks/cookies-problem/README.md b/benchmarks/cookies-problem/README.md index c338a3d1..31aecba7 100644 --- a/benchmarks/cookies-problem/README.md +++ b/benchmarks/cookies-problem/README.md @@ -3,7 +3,7 @@ ## Overview **This benchmark should probably rather be a model but I will prepare the text as a benchmark for the time being** -This benchmark implements the so-called 'cookies problem' or 'cookies in the oven problem' [1,2,3], i.e., a simplified thermal equation in which the conductivity coefficient is uncertain in 8 circular subdomains ('the cookies'), whereas it is known (and constant) in the remaining of the domain ('the oven'). The PDE is solved by an isogeometric solver with maximum continuity splines, whose degree can be set by the user. See below for full description **ask max** +This benchmark implements the so-called 'cookies problem' or 'cookies in the oven problem' [1,2,3], i.e., a simplified thermal equation in which the conductivity coefficient is uncertain in 8 circular subdomains ('the cookies'), whereas it is known (and constant) in the remaining of the domain ('the oven'). The PDE is solved by an isogeometric solver with maximum continuity splines, whose degree can be set by the user. See below for full description. ## Authors @@ -21,14 +21,14 @@ Model | Description --- | --- forward | forward evaluation of the cookies model -### forward +### Forward **check with max whether check on input values are made** Mapping | Dimensions | Description --- |--- |--- -input | [8] | The values of the conductivity coefficient in the 8 cookies, in the range [-0.99 -0.2] -output | [1] | The integral of the solution over the central subdomain (see definition of $\Psi$ below) +input | [8] | The values of the conductivity coefficient in the 8 cookies, in the range [-0.99 -0.2] (software does not check that inputs are within the bound) +output | [1] | The integral of the solution over the central subdomain (see definition of $$\Psi$$ below) Feature | Supported --- |--- @@ -39,9 +39,9 @@ ApplyHessian | False Config | Type | Default | Description --- |--- |--- |--- -NumThreads | integer | 10 | **ask max** -BasisDegree | integer | 4 | Default degree of spline basis -Fidelity | integer | 2 | Controls the number of mesh elements **ask max** +NumThreads | integer | 10 | number of physical cores to be used by the solver +BasisDegree | integer | 4 | Default degree of spline basis (must be a positive integer) +Fidelity | integer | 2 | Controls the number of mesh elements (must be a positive integer, see below for details) ## Mount directories @@ -68,12 +68,12 @@ $$f(\mathrm{x}) = \begin{cases} 0 &\text{otherwise} \end{cases}$$ -where $$F$$ is the square $$[0.4, 0.6]^2$$. The 8 subdomains with uncertain diffusion coefficient (the cookies) are circles with radius 0.13 and the following center coordinates **check order with max** +where $$F$$ is the square $$[0.4, 0.6]^2$$. The 8 subdomains with uncertain diffusion coefficient (the cookies) are circles with radius 0.13 and the following center coordinates: cookie | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -x | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | -y | 0.2 | 0.5 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | +x | 0.2 | 0.5 | 0.8 | 0.2 | 0.8 | 0.2 | 0.5 | 0.8 | +y | 0.2 | 0.5 | 0.2 | 0.5 | 0.5 | 0.8 | 0.8 | 0.8 | The uncertain diffusion coefficient is defined as $$a = 1 + \sum_{i=1}^8 y_n \chi_n(\mathrm{x})$$ @@ -84,8 +84,11 @@ The output of the simulation is the integral of the solution over $$F$$, i.e. $$\Psi = \int_F u(\mathrm{x}) d \mathrm{x}$$ -The PDE is solved with an IGA solver (see e.g. [4]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$) of maximal regularity, i.e. of continuity $$p-1$$. -The mesh is **fix with max** +The PDE is solved with an IGA solver (see e.g. [4]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$) of maximal regularity, i.e. of continuity $$p-1$$. The computational mesh is an $$N\times N$$ quadrilateral mesh (cartesian product of knot lines) with square elements, with $$N=100 \times \mathrm{Fidelity}$$. The implementation is done using the C++ library IGATools [5], available at [gitlab.com/max.martinelli/igatools](gitlab.com/max.martinelli/igatools). + + + + ## Bibliography @@ -93,9 +96,7 @@ The mesh is **fix with max** 2 Jonas Ballani, Lars Grasedyck, **Hierarchical Tensor Approximation of Output Quantities of Parameter-Dependent PDEs**. *SIAM/ASA Journal of Uncertainty Quantification*, 2015 3 Daniel Kressner, Christine Tobler, **Low-rank tensor Krylov subspace methods for parametrized linear systems**. *SIAM journal on matrix analysis and applications*, 2011 4 Lourenco Beirao Da Veiga, Annalisa Buffa, Giancarlo Sangalli, Rafael Vazquez, **Mathematical analysis of variational isogeometric methods}**. *Acta Numerica*, 2014 - - - +5 Miguel Sebastian Pauletti, Massimiliano Martinelli, Nicola Cavallini, Pablo Antolín, **IGATools: An isogeometric analysis library**. *SIAM journal on scientific computing*, 2015 From f98e7bc3c4813390487044bb06c359707c8b916f Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Mon, 16 Oct 2023 15:14:46 +0200 Subject: [PATCH 05/24] fixed typo in centers --- benchmarks/cookies-problem/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/cookies-problem/README.md b/benchmarks/cookies-problem/README.md index 31aecba7..844b3d70 100644 --- a/benchmarks/cookies-problem/README.md +++ b/benchmarks/cookies-problem/README.md @@ -73,7 +73,7 @@ where $$F$$ is the square $$[0.4, 0.6]^2$$. The 8 subdomains with uncertain diff cookie | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -- | -- | -- | -- | -- | -- | -- | -- | -- | x | 0.2 | 0.5 | 0.8 | 0.2 | 0.8 | 0.2 | 0.5 | 0.8 | -y | 0.2 | 0.5 | 0.2 | 0.5 | 0.5 | 0.8 | 0.8 | 0.8 | +y | 0.2 | 0.2 | 0.2 | 0.5 | 0.5 | 0.8 | 0.8 | 0.8 | The uncertain diffusion coefficient is defined as $$a = 1 + \sum_{i=1}^8 y_n \chi_n(\mathrm{x})$$ From a0709ec56f29e5dc118f26b110c10e6ff2ba9a0d Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Mon, 16 Oct 2023 15:30:52 +0200 Subject: [PATCH 06/24] figure of cookies domain --- benchmarks/cookies-problem/cookies_domain.png | Bin 0 -> 33346 bytes benchmarks/cookies-problem/draw_geometry.m | 28 ++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 benchmarks/cookies-problem/cookies_domain.png create mode 100644 benchmarks/cookies-problem/draw_geometry.m diff --git a/benchmarks/cookies-problem/cookies_domain.png b/benchmarks/cookies-problem/cookies_domain.png new file mode 100644 index 0000000000000000000000000000000000000000..cec9d4efb28e3642a1694413ee60bab275ec8a75 GIT binary patch literal 33346 zcmdqJby(DY*Di{J!T=IOD=iIDA|NRxh@>DPB@&7tol=SeQVt*>(j_9&BGRCQl$3OL zNq56p)_vdWUK6CDrg#beGCmd-)}=d2^7pW? zu&uGM&WGcjhgZ-$E?MvkVXvg`goQX`+WfbcbSGv{l9{DRjR1#XB5UKisR)$8Ra#lm95x+5>6?Vhka>glDu zlX$j~kJ6rfWLmt$tVh&DkAGi(My;Z)-sRc_7mMD{xx!>6#33=Y&pDsuT3*Z!E7!@k zR>*Bp$=UgWm+^okkd=%oKaiOM=l2b=(U06u^g|e9P{*hHdGl@(*JFRp8di@x8Fxr~ zsi0$&5SI#&dAU zWh4u{rYz@&gN1d=N+}%v;JAc<@x#ja|J8?7$5XDnz@im%Uf8QSJ84f8_b16rbbZj- z27>^@bGtP?OzJRx{MfSf-L=HT#LcE}&xZY;!8mjh6%BN49xmnE4Sii4Fbhi*FuUFP z3_evZmQzt7z99TvdidM3D>og@U0jMu9&Iml`3b-ZTcbNwyfYMo!T zJMoHmD9mfkb3Brmw`}Gur`AWDu1}vHUh?tZzn~Ln@(_NVcV%gMGFWJ)$QD`1P*|OC z)9K^c3Ot1vGd%mR*z0$K;Ucm*ul~QY3@|f_g;Exdk3MU&ELvGv*?r|2U`og!Whg}= zTmMMlEH1FcT>Ae0F+Nj{7SF+;Qq&vd#b9|qoEG+@=$}7tGs&|>Hk%7t)I>#JfBN(( zVO@&cJ$d2yHgiEZSFFnIkHMS9jY-!<8~&uAUVfqL8y%%{zLN49=E!dN+qDH9oDO)jH z));hoEMhw3+O=yz^0jBlYCY@cSZPZG-Iyma2d3#Cf9b!I+0CkI(!zQ7=#R+K%N?=Z zSsm6YM+y4;>})fmL<7NCm8>6KVm0V9ZG!uCo*;D=^Xw;qqM*Z%ruUrDnTPx{l1 zd66$S>#ju=JWcBhLeV3T4Z%tqRAf9kN|Rj)Q52#x9}Fhn)lY8(1YGPNCV4byBOI&p zv3v3SxS0Zem`{nIIeDb8IXZN+=W!N?sfEG^vY>F=6w!}X7e{gIii?YPhL%{6L34AK z$p*b6BeY?S1fd~K0r1AbAV1v=5fLhS7MRN7nGzUp(p7^TaR1G>2-RB>aoa7=H-p=4-yC9VAoMBVbyUPYc2cdgNcc@98Z^ zT>etc-zgyO8Igv8fx+Z>v>%;o{!KZ5{traTdQ_$>^2{NaWApb3LhokL@X+GVgp#<) zHguotU3I#x?whvZZg?WCnXg&<9AUzabYM|3I@t*GRmtoY8L_WhDH*T!IXQfrbhO#Z zH|}$^f_X7bA>@I9L5P>m`{0`IrBSYHLl?wS^>{w|u>XlTPgmqh_CxZGu0IS_5VBA6 zy%?!;B;$>p9xe4ZbmDzqW3NAMul7EA?mhLI!N>jdU_L(CWvfkuYD-Q|>sYc%-@1Qe zV5bM6z;C`CbC(pZu*wqqBpYQy^#wGgt z?VGW4m%>M@G56^=RZ@EpPOy=a2hc)Z=XV`L`LEq6hS;O zx`ha88jk0EPcyCJ&i2F31_lSM=thdI`%4|B)g(6RA;5lG@93@CkB^Vva;8f4-m9WNFwlS^GTOv}WYoZEPGxhe=aEMrx+RTgeyM;1UVy8rXmU2ttK6cgDJWm$WrL*h59jR7#h#k^p&K#oNtxQW*GPr?11pWh)a zMB#5^8sWg@_JIoru0t6YQ($a#QJHdENKE;<@2OX}Pz;a$lxX$i4*G5(n1=fE9iv5$ z!$$03f$TZExVX5vtqqr`S3evpjwX73&9r&NthS0PPq&OoZtoginSg*-yk&5V+h9Up z4TuOq)V~Ik{%5@AOmc6;sf9Ds zYsp|dPQqhzbGEfZY>9`dUZ9b7T0i^bU9-94W8tfUE4^fcM?;<|Hp1~LY&Yn#S`r6U zSdg0U%U17b`%nGEX-c&NOF+P1CbRsuk6?F7(p7G|_X=hZ!h-8MF~g z?90P#c@s7{rma~C0aG!w-Lh!NUrD)?J(bfGC5M3{ziEAsH)I17#offvtycw2C|#Ee z8{6YWI(%mOYfb@7jFdX$6$^@b93?5`#&M~jwVMfAo3DPsManL`{l59Lk%L(ECd6Ys z9!vE%g7Safe&?91mDQ-lyY!Rs#&z$*?a!Y-yDassj8^pf7_NrJin%<^;wyDq8;j=F z)wzGaE$&8ZvCc)7lC_}5>1|1(0Mnk{-oZ?93*wnTi>HqDjd-i^oB;{zE-G$){r$N8 zX^%8(8#>ETq+Bw5_43VmRl5(mh1M#!)RvldSB4uyX|DSm?~hiv8Q@a!8B6${9$&g5 zJdo@;@%`LU%+c;j#qyU{m2MTb$f{Gr1}d3(qQWOtZ@%MtxC!8nev+lVqj_=>m)mXH ze~Ll|5u;pz)Ttzs$x;eGTwGWPH~5@iv0C1ntt-9VDYtpIQPqOCYWp56Jw6j#Y4^4p z{a$+&<5efS!>|_Ls<4?AzPE~-Pxd_>tMc?bDdVE@wS7zz(%@$sbaD#ixefNi2P@d6Ds9WiSnp| z50319g5ivsJQg9z)tSgLT_w$DfA~ePNg3`=4J77H7_ze(B6jW$q}j0hZDNkQ5h7jU zMp&+J*zBnonQ~?4?B;XD8?QUbGdKAuSPgGBz@e7xeqRcyu3T<47ev#2)U5vKJUEjQ zbrL9cBt{pX508KH$erWu^rPuNHfw{7LB==h{x4P&GL0--==M)(lA0?g`$T#!0hzFSQcOSJ5;FId#=c4oEtH!2v3Cx#6LeIo;k=ujn?*M%d^|2|3)Dpb|w&3Wo+X^D*GO=clcr+v+#AM+qnA zAIl<+7zcK(GqOM})df+tGl0j_>>V)(m;;I-7C1Rsi z1l~X8$#5hL$hgPZm?%6!*=YF94)F@PtIEh4s>CECd!=lHx%3+<*_3*BOftUhFr5GL zl`AWk9(p8XJ}P|gecbUHC4)HP3`gGn08e_!v-06i6|t8Zbv;2cO#o(Eycgx*qTX|7 zuW{t@5ybAF4Q^tPe@q zf5*=;d23&%Hjoj?A~c~lXP5l^RsV9$v$b6VQJq^zKCfwYI6L-?I^wDyJkkqEV>~83 zsabZ#TKfb7UD1GQ>KEIw#PC2w;l!+=Pncz7v3CvjzWk_tj6R_>t|w?LrP_f1lbY}$ zFS61yGAL7b$@`)HEPIDFEv`&pAFYjq92tn(%1*C_r0bUHCcEzKa87>rFscn?L$YW; z_ALr&oHl$kAu!9ZM3OU ziSaw64$6%+v%_RvFs;3xh~7twbuWjAgsAhKTt*_4LhATk8bGKh|_QVkHV zIBi_qg(6fD*>xev+ffmWsk|3IMNRFWR0tASuGPJ-B}!N#8;mNrSIJ>2PlG`IaU|5Z z&`)yA;7P1~fE8dCc433rK2P0TtM;=)35pcJVg>%N@D5HT{Dk z%eFP&vmgPHX@Vi8rRS!6MH_QFj*q((hH89$@86FmnJR3gFCwaj zX#C}eb${N<4h1Ezp`MY^sMfPQy~5hoxtQj$abN<<#w}y9uytZCFT`!OugMWhY99}; z{dk3Ya@bFvT~;>uG|7}Yx>CFJuKVKejO4T9c~7yTbj7f-N{^zwGe5^tz1ob;GP0Nb zJ%{@QdOZv&4~n1O*VT<7nX)!HigVFGS7__%?uk9#YqRrlYJSVQxzQAHm0dka;^Frf z?d>1|xaH{=%}xAxWn5e#xI3UbNgX{E!q_Ta1DW&Y@p>IWrOo5VnO=i{-iNS0*t>*s z)w5PU?fvZ3ep69Om~gsstNmy*riKTlSK+$yJLB%&@?g=k;cr7jL#pw@be_wFNvC8j z6ORyAr=mR1&a>>eElQmoZ^`1-@h@u`!N5?+_Vo0SC)FvlMG8ut?wFkIU=vH&1JoEk zTT7LD{f^g2%w~XJeaMJLv6kb=g|@<(!VpJ)>>xKE^N(-+i|?MB@7`Rz9X?a_R^nRe zpkWMkfGKqVH}{OJVQq|&Ypj?3j|)`Ce{^fUR2Svtt@at%<>ux-d-iM;bO6Cv!>>;w zUZ1`AQhLAkMKHk)tkh#Hn7oV_V`MU26^U$)ei7SVN?l*BfnD{1C6oNw=?MULx(!Xd zu1xjht#eE+B#Z|=1W2(6^%WF>fb0pVHu$^5#i z-d*>)fJ>Xn!uAv2wYg^T;^bZvdBbX(on}vRuU%oQ_@0Zr2zx!k>{7KKwl+vG`IVc& zB#(ZE()9g+@$}yAQj`tM_u0w5kmL!RsI{!fpMYwpf6e-b1FvBjqf%45-MJ7dzIVi! zvH0{&wPmX1f(eWRkonCYR-pCNlPb>m)QtneZ=H>X83QROyrL~Hf$E>orm(C26Mt7S z?KOqAB=|VdMQ7|B;m%-xbMe=&QEqxwzU!IFlT#012jgaX^4u$!8LKxJI5U z#hX9w>l)2Y)?}WKj^Ssde$}I^sHmu=rDbislp^!$QQ<&+5Lt-tDtCV67og6VfOK;z z@T%I)*(d<9XWw(rAH~NpSBNrrt^YVV+G}lu8xOx?HQyF50tC~;eyJ}vNM07N(0)?3 zTPT+uO?&hAsThW{J)^7HY~coOXuRaRGFEj|b9Ph{!)x?>_*uy{gKsJ!tZ(z`2`J`z zM#O3)F%)u!ZT!!lKOub81HIoWemRH%Vy9u}sAuItl>*p1U4JcIx$*PL_}S^6h|Pd~ z`_1{#DG zN~*sj93k188 zvFA%A)jPSTCr7QXUf~jdt0}$5RkQH*4lZEakWRmIQ$uVpNrlPljQot=FB>H1{U{klf^cW6zb>(#NJXJ}0YX z^GR?m>xN(o;5^2#MXr^M{9ba(FPn>l+@toMe=;jG={eBwB;V76n@)4EL43<#*u}-f zyqXq0gk~|tHFipSx<_l5FQquNwRwe7(Dn!~6hYRU^RolOf+(unacKXjC zDjnvZ9p*PjvJ)`5t_)>tS`twCFCXvL5BWABb=;-MfC&|jrrC86 zwZ}c?>B>GJ@dS_`d~7RLgqh?8VEgK@(Oh+ z?nd6Pw2xVbHSRbS#6IS|H_8Av1dMJ+k>0}`Mz=#^>ldBdh=Ap<_kDeR4963=TA6Ca zb|aejR1~kdfreVy*^Pg->W0(=>D-ErmjB`R*vCnnD7`XAisI_ zsd33@<1qH07vVG7?bIXx<;DAXoqTPmcqyl-{SfqI6iJxm?!f2!*}T>JQ=m&FIsBy2 zHSyjtu&ZL#)w3NaklDD9f6IU1PI@eCPJ-cC7M@5pRprd?e{>WGPCF~Zd(&akQts;y zK^b@P0tz_QO`CPTSBfn2UKW*!SJ&ZYnD24=NWsDWbeO@F>|9}K2tF`zAG{Fg!WH0f(*67ig}U#Jfn)7D=AB*f~ldA z89fISw#ic%z#wRC?AXjoW$d0heYU&U%^t06AsFihOJwkIviGDvRm)M&Wag&u-E;qM z>Wr?TL&K=sc-6xne(|>DRxs|biRcb@@_k{^sfW>t?=G2~mAkd&$xqlli&gnqPV#lM zs6#5=v-80!8%lKUy_sW0@-$jpO==&|!brDzRMCKJAb_-}p$99d<>R?S*hZXGH9sE} zGK<*awvR?4zi8R`w+|LQo^fT*zuW_! zQ16Or&84nw)ONY>Ek9kY^F3EU;{g8}VFh@lI42<2`_SkR+(Lu>6i7 z(n^3Pg()hn?&-A_d>t;yu4w7Vf8~v}>6CnwaJNnVfJ?do&`z2cyVqhy%w}Ic$rOa# zNaB{^N`}j5-dDG11S3w}KV0=7&@Dp`tF0E$2;1nroX>ku{5d^+8q!N~{we)U#~EUu zb!?`30E-hoPNOM3pS=dVsRIO@7c`bqmahfZ+)Fw3Ct#XuPmHfvpP;JQ_c!c#{!5@S z0dQ-c(6G^JQp9?q$z2R^IabS;6iS#npA&z6#G#SS@k*MUnb~wJA7I)~pFfMilNnik ztB4Z2+DV1DcfFCpx02)$ObdE?Gw@f8WJH<#YFS~tkkwlf+4ka8{<6R^spm{mD?YTi z1n&jR0t{cB?GD?iLlW!#eBTkT3}{RFQr^+nUNw8oN~ZKl7G=7QMNZZ|SDJU+^ZlC; z1bqL3yht^%=JE>>u2GWuvsuUF{r&x6G}mwAZ9p1OO%&U~Ah_90XQp}xMK|154VU6n zb&WkTBs00V%`>U1O(_ko!|Kof{P{tN?I;L9Dym$ayP8MP8qIY}JzV$<7nMS^kkJkhB`??$zJQHHKr4PN^f@~_ceodi z2^oa?BGP+l2`f5eUiWQ`Ia1aT_SdSACfOwKB&1nPGCEr)K3iX{SX%`q1u1-Mt}Qz+ zPYMuc0z*vo#v8li8Fp!hBa;atmtdj?W`y^ypB(PQ->}w!S@Hf|1~rkatSsNN)6Md! zdtA{E3LgGg%CE7diI0AgkZK=XzVS5b7y81?sDU?}d(6J>5>#A~>oT^Zys-RcB(C2q46>Q*?8n$!riJn>&z8s{L zLsX(n%?R`&z;uA@_T9U8?VtU4TuQ7QpvuUA zK){T~K`7kRnPQJ@o*42wo1OxCnU>BO7v|q~v3L=T)CmUm#sTV& z5n7*L*Z`;1fs{m7N8^T&kX{&kAYlnx`#%DWvRSAy-R^jefp^&S=Ww*X_I~q5tDAKM zEo%-DpE9-PZ9mlS0h?63#^V_8(z}r%ezjM~eSk#L8w~8|AiTvYW&c*&NRA zH`PT?00NYXX`Ks*JLr`Jt^#$3p$?^4T1lF^@Ry(R^GkdeS6z%bOex%TYXf;e^qL=N zZ>A17qvf6%8LhRVzRes_P;E{qNV02Eu@r;&@E^@9(Tp;*jqW~JBxHCsX>bGS!DNb!i7lC5!!?bQ(pPz7c62e=x;;<-Rp(d{jBEsmVPAeiHiz}sP zA&QkgY%aE#P*$-eEPmj>OZA}Uw-sB{=!j-FADNMB9+@mIoi9r>mL=S7;OK&(XDtT z7AUQ#Io3Uz*y<;WviB$FGY$EYh}TjFQvZCZAf8hqYjfg=wz+f5TXB^Ji=QDe&E~X` z@%U&p&Dv;<8WgF{YbX=qj$VvPhkuazii=$UW*6$&Ow|WfNzJ|@mKrE^FjGHsTj)$R z`x(l)zWt94BRApOb3V6CZu+ny!cV&B>PCLT#uv$AZ<{}Oe^CCZ-1|2C>6gLfK+>zr zijcMAba6kRTGKTV1jXW{QCAmaG@_<~(OwpJ&9TA=r+^D6e010%1w3~nlaD{FcT*(4 zX!0I6&nqezAvpJ0xNI?8ynnE-kJ354C)$9tMa@<@%0BBWw`l{ME*NU!J4^kL`y8g~ zp;E;lYM%mVH!Xx$P<89)6+52edW_tmpJX=p#R8r|K-CFSn16Df&qg;kg?dGGW<^Cs zZtfcreU_D}ba@1odLvS&hD_!Tl)!ERuvG0fEPUkeV!pRJ+Wk@Wx@uAA*jGRTLTyFu zV%hy`J!$(+nN?F8?wxP$sjbfBA9{yHd0vny%N1R4&0=+PX< zKXj>hdLDsT2lZ$Pr#Y3S34jmn5?d-v^*{{ndjDWg_PU4X0Ln+{Koiz%ULsJt5&pLJ zLVIP7vf?!MvQ~!KJObVgv*uB;^!`443xu$P$YJh8@KP|?*br(LXh|Lm2qnRhc67SY0F5^yYYOQQf~M{ zOr$t|Rqn%dUJI1RrO;^jS}MlA9$dcZXx4svF02HC9C|;N#4ujQ0_J9Y2Uiv{$T=LGnV1 zpJ&EhA&4@BHcrR=R189X>;8(m@kdYtokH~}O=xSr1C)7CpV8BvyUPNqTlHk>Jf|7L zHUkgiEMc2RjOR}?KT#xRQFv=;#joQggwZm3YGq=?gyD|=*CJu1{2GuGF- zbm&ZpL5LF!Zp)F^2V|bDhFh{gs#_zuXC{;sr5!I;Xcw_-8&}n~a0x(=xA>M3kqgs&klD zq4LRsM3F4P6fIMH>qL2*@$SqK90lV#AvQDdNo>Wrv{?_^7j`=C{Se0WNA@Xkh(`eS zO4DQFJvA0*QuuitzvOy7o%$?o$#0$OkDIo#eZ(o`5!OugiAq8djG8%}RKEFkt1>%s zRl+>mbYTY1+ae0}FciQm*z*$FZ&{C4Sy6;?<<}qgHwWArZ!n*OfX&)4aclcDI%q+N43% zWs3wcjqf;y&ER)uXSiEG-ewvqnj%7t1&X*FIv;{fT-U!~a~vlM8aL%N?Q7FW^TxM* z`}!4+>G_IdPV$vRbR$8-3}Lfa?k0H*spw2tctqicEdyQM#!o2;7l@+H1%VM~vkN*n!a7R8NMmQBKExA}kI1|hRxo_TbCfC}VnI_b-RUrAFg3L>rhfx`c6f{BP zU}8LxXGaRMfbDdOFTiZaz|+%nRZ!IWvi1J@WFMDL&E-I4{a87?uCXy*Dal1ww3gx6 z93hS$a+fGksR`pt2xh?25Qa-Fk~xDT6)T12|D<*Jqh04EELoTgF*G`6DlG zGRvIjZL%yHwE32i(eRvy!aaByMOYV34F1hfb&qjltYjGkcE-PqxwnWh#lmmT3_5c) z{Cmg2sind;J;iO1sG+K=3aMfv`kWqz=~-70itz8~`jdd2F(ZRDQH_c|!9K#2x==4C zFOQcpn?DJpmhZw%K7pE-QV638@Y6Tstm@cI#0XbnF2tom0YzS7{Xf@94_uwt- zxkJQN+Dq1?_Nl6>>V2_(fayP;OI;r!lFLyAacSK_@;I1J$Zk+wf5bGtZM5u&FLWEP zYhXaHl>6;U7Pv%lIc_1cdGHgJz*wM}V-FjTPQ4)gg5yIuR|St6Cb2{kSU5S=_HN9i z^5CkheE_aX`rePR=GLB8`PTPm|{N7&6RwJ_A9Vd`gnU1K=`TH*uOEI(3epJI5 z9UWy+I>|Aq853%QN=zO^;qNc8;TEZkoFlB_M8@3B1k}$~-D)70=s!-}`E|Vmyn40( z)IhBV;Z#3goltQ@L_}nsE;g{?5nMo~B#&9rMK-_`aw+bp*mt_9g6i6?pzpGNJsqIw z!FhryxO1*nxUD4#KZ~=9tEzq}wL4(;(aP9;(jSUi96R#?Df@LNt306f%S6A1>#IEL1;D|?OXJ^XdGY^7s6P&8wd-;jT zcg}pKDAi6<%8at5yb6_Wnwkq|%)r<^BBSyc9@c@a!(V_xUQQ);fblHKfKjAJ567-`O^SlU$~ee z^w$ZBMgs_orOW>O8DGM!DV%BZ#~YEcCv?mG`QyF1Cf6`Epo|LDOFFjWkxDGg1A9u2 z`q5^h?1^e%_he3?JXe*1TH!F|2oz9iOR%p+$Oh7hyNaS)>4c&D&b>ZTbGlSRDNE}y z6M-J?P)U-|$J+pzvV6D)Yj#%>Sfy!sv7Eex< zy7ZE;4sUkXK#ybc{9k{fW#RwbpIDidbq7Wdmyt z;%hcis(hTbB^^&lyu%VJKO{(^n(v};`k5!r(ovoY2s!X}wQVo;+rb(D{{XSX`U{tW z|B_N4CwY8y;g43nrdKer({ECO-d^mUKi=w~lI3~uwdpi&GsChyVbNnr^4;*>hkKld zNrqo^zwOK!0y9&=EL2 z*h|j!l~jRgpf!fi7mCfx9hg59L#a1fp_Bk-ag#E~+3Ot=PToQ zLFS}2;ei0>1GlxHDl)oVF@6H^>?l!UAB1RP0xDjKKcCVopOwT2#)g`uV_i3?dMg?3 zEfZ~+%BQ=8?UROTy@j}{$D(xY&E*@)mW}6^yGL9bX`MpNC=J}o+$7T1INus-SggJL zz38l}dZ|WoKM2Wtg*6mQAmmcjEH6{$8fTEC1U^P(>X)Ejnl+pMuHKfS3@OC=B!ThuiH}+o>A#z)!;LC1 z;4s|}10$k^Ak9&uZP_P@(zylIvFmMx`Q3#Al=f7t%goBur=b4nqMP|vopqTM&#vie zT^OK8(X%e|DpO0rTX)eJQ#px_3S(eM{D#YP8{zp98ITNj6*m3b0p8G*pf7QoQ9=D6 zh$?V{>p^qWiKtHC^@Ca7#hGjBSLrNUpG?e!(L>(@e^6CZXWd??2wXp8fEyf$olf<#iLMSWhqIHGl@?E# zdJKeGY;;Jh!1GD=M(T@H@8oDe3e>D!$3q}57j#teKcBo_$6s1N3s=JpLeg0LOm&}^AfMh3mkt{t8$1(TbUyuX3nW0f@PXz|NN2hl= zK?gn>Jcj}KvWPk(xXIR2(%1t&ih&9S__#4>%BiTyv})*!TZr(^K*w#F{P$Kn;MQO` zaT{@X?d1i|fwn~P{?U)+we;r!#tFw;Xd^D5ic!8BAttc#s@J(q92zJvmaO?lT1N0p z3K;o(<3R66Nf~ru{7Sf4l%2f<#fhTwU@Yr|O- zC2*GJV60~y85ubSwSe4bL%+z9^F1S{X=$Lo4M$6%A6IF>_7f}M_W?MI+2!nIO&X2E(l^9lr! zA7aqJ!if?E{{om}a$dzil@GdmHmm2rtM$cm590_o^gY>05*oTvsP`)_#m34?a|Y+a z666Bna>So|GkoUIWL$fqm5=KgH>;9y51dvBBvX^G8CHj5 zlzWCTh2{?LRVSGeStd;mQ>J<_Qx$`=MEZD=7>ocklFu=Q90_tItZkOnrj1ifEH7$E^9w>f(0#Sf8a$z8pY;RPav@?=~Fug*W(M6{yo ziECbpcmj4~l`!_ruMA3TRaua-fnchO`)DQUFw+!KmcI*C+Qm5QJW^S&v!n46V;QR5 zbD;nMIy*_8IHi<#>AYCw^P1!#uV&3m(75C z20+yA+IWrY%7Ze3`8JX91TmLCTOHEApg)zLb=bdRevDIoRp!rpali4=B(bl1kEY_N zW9v1*4B#4s4w99j;*t@^Rwyurc=!hd1b_o;IP8<_8cxGrRE|N38lC_cG_%)8lhuYP zoM_6RKzs5IaC# z_Ubos^B7WFpAJ0S8 zchT{vmLpoZYJV!EFUI$Hnqk&rhTiws!DC0%)RW5Rc%u>Y$~y<{6W?FDjd^6AWB>{* za$da0R1a3G)a5?bC{qwhC7*qghDYqFRB&_#|8Rroe1elLEY6KZ!#=Yb#kh^Dr^vs>?jZ+=C{(kfCwywQHd_ZUzt@3DZtp-|Fzy>k9n3-(*^yy&( zpO1jYra9kb!?N$=Lu@MV3q1F1ms(-cR>y06VRK`8{)kQw`)l5fAHcy~VxnNVN;2Mp zQVYjA85o23x4UX}oJ5{g9n zbR!H1q3mB{=a-{0u#7|B96@Cd7`6FIC;XC5?GzPXCuoVUmQK$k_LcrF^E^p)T6f5;vA-> zeozEh?9SA^e;=@k?b=us_)&_`t)3KWiDJc}{ZU617&~-DZxcReh5IMS-58trzjC+B zl1|yRkM9XcU%&k_PavbYt6rOn1l!*PP@Jo4Jkk9Wx`wG;7fhlhO*CjDZQ~Y!%EI0h z6BlQ2pE$Sl5atCO!eB3-b!o`Z<|+oCUB8L%sMsdN`*~xdsh7C)17JLX7?5Ryb#H!p z6I_Xv#xfZfT-x!zrnEDJaD zC*i!~;DCSjYz4OH9;9oxzr5|cPvmTjiOZANn zO9aa>2bBhNj)PU2>%FhK)E5t#lG?@4>+K7xtFE-iF}?>el_jFwbiwq*&=6~XAzcEdoRG~{5RaKG;Y!la)AEQ=r ztI|+^Lu}3~R50`$?TA8uRUgqzT{!YNG`#?+022(BvKcW>*Pt{G>LP#@__hLy!SSB` zQbLQB;S#{p8o4(35Gn>_vVH!432z1n5bWDRoZzJzDE^ zO)SC^HS-#WvV2?v~jT$y?m(>4S(v_7-dHkfRwyUM@?P;dk7WK$B>Pc~P?={>`T@QrF>y zPgB{BQ=fz8aUv2{!Y~+`b4To!l+#DQ1RmAnb49)U&+Cnkj#b#|Zpe6Mm zdoB$%Ns46Io+$lHdpGMCdU@>5-okyqK@!1zuffS)j|dRHyXVp7aHZT`!nWocg9Z|M z%cA%tU{r)@&8T@YHZP}BVTkv2W$Gjtlq(no7_rm}&IjrMH(ELQP~#NGsDfk;Q+Mbf zOcdosE$yQ&QQdr(m2odC<$)i6D#{xYzBPsw0|7Z=T-uO?e$AD1s%=So0@h_>Anh=R z*U`{7w%X_40#d+{wiyrQG(Mv}DeLkLp!=A&%gm@%P-P*#d+X%?^`bO-XFpaYAc((2 zl&xlJL$svRe~H6%O`~=lzRu(UFo~iyZgZ_nZqPrfOx!{#&H+QO0*0;*UY4LBem+s3 z*V~fom_|iqY9DwsUBL9GR`K+oWFa-|Ub++{@7K)d9*lIQVkJz5IW8=+z5j(Dz)u={7u9RC#xE z@nvvC$(rd3xz}w(%p2tG7l#)|v7*eNRZ3*kSqqGW$D2QJ86?2Oxdyh&I%4U{eJUUh zZoK6SX+TKvnBW^u=I+RyAj2Amhlg`LXa}+3OBl9fDqq+LFD-Z?gK+lpZZUF9LYWwk z(k%SYW2Ly?XTOdC(}WGT;Q^3aCr1xZ#tbxzjR9m_|}ekvTsvkN(Ef9&iV`fdy0=@=`<1u&^-L zc}u%aedx}^iN4@I;^onWz`No}Yu$x)rdg|y_E(Ox92y?RbcaB~0SfT|7nhLQelGSg zK9f$Kq5iLs%Dpj9>7&7pcCn=#O#6`W4Iw;3;%p{b0%moL> z#@dh3cnaf@ZI-cvgaXQ?1td}YvacYi_Vwv7)dLlwYkW;e0|aRLjmUFve@tz?fy(4~ zQQcPzjQk+sJ@0CI*i8Sau&}F|%R~z8P8_AeMz=)iyzJ>XzeXY$Wv^bV360$#oNht- zB4C0Zq*YPgqn>}9-)^fT9?N_1ojvMjEw5 z@O<)qJt~+cxkmt$J^nVD0a#a*KI{VC>jRK9X0Kb(SI2{IiWodV2I38wb-J4I*liVb zO!Q&aJs^!{L2Wkt*)hH{RCzj8IpF&Zx=Q{U9-w#H684!t2~>d4(}LpAVou?{YUi^B zt@lp7_jNHXakD4e&yw$th3s>d;J#gJ=qh{mKrr^#ii1`bxrk)QM|X+5{+;ujrnz_Z zDtNJ3Z~pTQJ1IC>+JD%k51L{5&QmLT%|`2Dn!Jvo5qkwA#3v;kMdy2EWM{XY)jPe-wi-lb-$qyAvIyE-Zk#-KVPaT|bEf6-M(yoeo$I6?AL zwFG?2i)9!aFHaQs3(CQe;7@{Cv?vnj47p|>b9q!+$`WGXs#-{2@}kp@GkcgkGieK& zvPK5w*Oll`Q+|d@EGsmD?ls-q)VFYv(V8(K^8;ww6fj&vu>k`-+wJ*5*&;=Zd|AUEP zHi$jR-m3#J=F_-2Xuct+RKsaxye9d^oH`)2>)~y z=$YowArG#xXJ?VBVlf3zY*yBW=`&A(Dm3jQgj*vvq-h;}D9%GGc$!`p=P8JW&E_*l zJ}>RAOR-ik>&`X(6%wC;KVW25?^2*rBX$%DsiWomf3^4BK~Zh%o}eI-b5N3m1{+Bd zlpr~SAd+(i1(hrrl+a|!Nf8>6C`eYaAVH!am_U+5ktCqxG~f2Vd*7>?_h$Z?sd`g2 zU3HGe2Y}ojFt^tN055LOG;Yu`4HrBhK%+S!-%) zB5(L-SsXAf=Lay#;npY6$TR%ljxO*Ye1&*(v=h!6FRZc=b%Bb znz*Xi77e*KHxE(oZ`7lz+G!syU!1o37ZJW%dh^jtVOcaM_rRPHzB$=Gx=_4MRa?_x06nCcL z&DcSP^7}Zdcl**Y_mZf(k-+q_VlMyU*%)`ERWu|f%(@LvxC_d@ zM)-RREMq}@JpQ}wzbgb6hcJAN0vTyC&6rsidHI6p;-H`qI=c%FB>N`|KqSXJ zD24+8bYkH(YX1)vUvp5)n@%jmC#@mULm^NFd{}iio_tokPAav~%P1rdhX%QZ3gPlt z)CB=J!LpWP$n63Eapj+3NZ$8xXKG_jGX6u_z7@h0hio6HRddw{V zu;l{BIL51GpXl#8IZ1&25i#7|y<)m6xwXHWAt^}x>KQs>{HNeV?S(lqO(pAv25w)K z7d6Kis9PasrdfjW&x;$r_0)3zA*5*vq9Fd-jq~4pM?C&RO7wtPxB$>wR-=tyuR!lU zT-eGSp&;Px3R1Gad4K23p7$D>n!-wqXu=gIHTD$4n2O!Qx;-VNY&s$zn&?rgiy3yS zGqB12&)SCKH-Liug&299v#8t;Sj6Ac>-m3T^Us0(GINhZHB>r{EElgzU%7f!#mS+X z8`%UgK&;6t;zTIn<#oY+1h=~22G&Af{}mW{oBemb#a|T_C1fkm$})L(r*~c#hy&N> z!MJ^6J986G6q@xM`ro!>eP?Y`A_|yhHtIO;{-lL_{g0d{I2J^dT?Gmg>RtjCpH_(x zD61e=qUFH}pl=@~GSu_wc#YPfTr*WU{!=I;?p{bSL;hg59rO!4VSp40UvqB!;eL?q zb4_b%)(Y7-m^=Sl{*#J0r$T^d0`;4?p9A~*``=CUcwvhKDsOqzA|?azgGDq&OaskU z3qf-cEPkp%dvH@nS~M-HZtZQ(e@UG0O9y+b*V=eJa2f|mC=DpeCoh%N*#MjlY9NpI zqW=~JrRDC3L_vW>dC5iBl~ZYpk*Re25q-2F9?FboRo!Sq3ooBt&Iz#vY_E)d5oJaK z;UeQF_NeB7{6X}|zpG(@+1lD(meHG^8C>P%xZhbG{^N2~yizY|qLal5hKfI)AH`cd z4(|p8ZJX)NtdY0nwfdaiN#9F#R$3O1N+XKc3fv)ga1`{lT@Plzrvd&SmK2x=83ul z^|_J>o%l1UwnHSqFa}1qp$tHI&Hh(1x7e&j zu3Y)X*t#ipqtoW{!7dgFM%vc>XVMdd3N87w?miWaq?Z;Zc@8$W{o%jqPW>kkD@B9A zZjtoYgZ5f~TqaRzf&?gXCdSljnouL3Fogg0Or73fr--wB<$b^xNiVm-XRX~9y#6wb zQV!6h*2?}V>8&&6qYuU=S8H8MA97mkzQe+=M9__;QpSTjjqBe;Azu;Ddm}DvT8zHT zl_c;o&QD?1|Cq~kW~Fw`J`^*^bimgXNgwAXFa!V1@y3Fa!!qaO^-I-&NmHbHYSS%^aK~|I1AqSQrZQ{RAK<%+JZBWy{?qpOa*Lr zD;x5HC$vmmHUV9}#bJSBc?AXUUZyJ*Gqoo;H}j%O&;Ej&0w5&3F@)Cr?WPS~EUo*J zD{rdDomg-I5>SsK0uXiEV-4nQT=`n6GrA*7>$ll6sSG0iX%HmQMt5!;c-zzUPexqE zFpGWVu0mVq1Fsb+1Y(kTh8!ZqfVj<4w24d7#X>xz^xX{GT@a=}>kI)tfHl+m^Xkox z3>Z(Bh+71sS6aonsxGlU2Xy`K8xZX6f=t&BTTy}{Af-M+^d-YP5Uain1rIHaJfw#> zgTMfTJw_OHAheAENBYZ_!d@J49!TATxK_3saX0ndEg;E==iu-SD8hi5bph|e`b><# z+C*dR4XiXEMaM|E2~6(4k-z<@G7-N9NF`4*EZaTv;tFPMz~2M{Lh_}|JoCN76q5Pr zzeB_Zyk#U5V4XIVP`U1ESnemutO}$yqGF4d&qUikDZt7MxGf9IJn&2e#@te09~I^U zVT7fjE-go|@2-PR#NTfQ)(icD$8NHEV8@G`^Ox>+xsffvdSj~3Fir- zoT{j*bzJAKFT|#m-YlkB>8u(~I`c}v;;FvMCFh0f#wv7EMBTyGn?9|}e8%a`3i9!M6^{%Z#d(pVssA<#@3>jFY!aG>?{^?ejN zyilCcX5~?IAz1ftD!&nkTXuJeteO0>q$Dc#C)a%4<1eKS4GmRRRzh6}Q4QZA1LID7W2mje&VSfHRf(yHTf9Oz2FbD%T zOH1FY+gJ=!T~+1Wy!9p1<&E*?gC>h#!~#tfHl0N5eh}9JJ{v|x#uEKMZ*?wVaJA)5 z6^dbV*mEIqA}L7RUrrIS7JaRrcOcpXBn%?@QkfMign7h2=`51&NpzQMAwQwkduu)g ziVs)7ysWLQgHLD}8BNX1t~vBGU+$smjqCT;q{tKpEn^l^%N!F8H zmF*dTxeV!^bJeI?S8(ekv~p}%zGI4$`qW9r^ZMeKOlG}>P6P4OmUy4>=;&yjik8s_$}`TU1-qj=ps1Lc zcW9X!w1{OA*SUSy#f3Zf_3JAU?QA08LwIRitz0g+mD9_^?V|Ct!F(z$jc$8psifS1 zO7O?Z)3%i~G<$SRj4oA$*QakqI9DQwsC+u6*O4r6qJiem^ zOMGEn-N>pJk`jVOJ3_i#^I-?w{)U}R)=z-ABZ{Hoq@sKnXbx5+#)EnTx9Ww~|pCM6{)gJ4cY#Rr;?{Y4kp ze3V&L3E%EQ8iI|DjT!sTpFfcu)NwT^oDxz}=7`pjB-N{ydV}p&;xc#qK#9P6rFM)@ z1aw1bk@5-(fzsGg?*mX^jWj-8{G0CYi{GrS0_42-`1mD; z{?Adb1T1cDsE(a~|@ zuLAA~-ge}pSyL0m zl!JA|vj65>510)q3b9B`E38nlL^qd|0xJCmxzLBh8UjdEieg8$ny!vcm38}>_^xW; zyEivCk?5JjLmk@oXEQlOED!3QF#@#!tQ#dIC0Jdl&$qX=K~$2QVjI_y7U_8rRWoU! zsHiA#k~YKVAwA~R*%7}m+qtmNqsXE+Z?ZoV09RIRofv#xujLI0tY$wI>q1DAB>Vov zub^7DS>>&e&qNWT*l4|`j?0%X!=c~>iC5VmrCR!20zqi~el8#ylqw^}%{aesL305R z9z~kni!pU|bs-@kK>XFaEbbhP7Sn;4&=4g*?{knTCL`05n=8M&4sx8ke4DV*u`TRM zytse>7jSJ??)&)cg0c4gqt9pM#vR{&Mt%9S51CwkOP`9T0)CTHYJ%hdv^Uz40q^nM zD(f2F8NRG89DgIB>YdG_vjg>~rqKFD(_74VA_Fy#W5SnoJF zMJ_^;+aZMc?V5cg$y-yZTt}_xnVF4)tn3=gAi2yTct5&UHFt1`o`r}!hBZg~*5En= zxYsBFHtO}OMJNj6s<24E`pcIjBuoyc!JBO#D4rbs=n<_}Fjp?@$SoT@9bs7p5R3La z+!5PtnFvxwE|Ktvh~;yI&Lje7KjjZ?Nd@kEs;d(nJV$~G5v|I6`s~ij%YSZ6XJjw{ zx1z`rAJI;vQ%jRZa?VOrd5VN=lh>%Cb>1Bb2TR(9z@U1wG7x3{IerN~)2sg5chmc} zfR5N%I9c|hP*6}fT`Q9d-klZMGzb(Ve6-@K$1S5FF8c^ykwj zz3GqD)zzYB1y!BQr@$IM?)g!3r8+4o35I2kW|N6^7rPMH)ZJw!oY72+FvW=aw}X?o zWHbDQZbsYNw~o?uws9z0iVA|4RV~h)BNocrnUz=h%^O5OZwWgffJ7uOUDBAiz_$W= z5AuLC{>Ji;-{RX}6Y7__01*NrUSnfpHAO^y2(d}uAg1iz4F0I6>NE@&I3J3e@HoM$ zmfFIGgVmOMLgF8GsqvO}?1Z(jU3bD{lNvB2etv%T_VXZqtAs`TbEqD@VdLhg>a3>( zG8c(JFip&gCskg9VTaAv^wjv3H;=-?!eF-^hQT<}l^eK5HCDn1bJi+@7>frWPaIvY zsS(Mab_y-IbZP+;D9+WMEz7Wb5c8^Hzseu3>bZ00V6v>8@M0V!i25BhQIF1U&cQ~o zs@YxzY3k@X_t>w?_5wgjATR{sszZIMWTce-_*$M`P#~h*^D+^oBxSi~6z~j1MMVV& zD0M)o-}JT(zRDhI5)yP^hJ$$n4C|oD0i{*Ac-8=@Ug2rHJN9}N8E%6^EF!lP(9RuP zedabFNO@F~)l^l_5)wj=*5KanAIxMCA1utwt}C{EDJk0xcoQ2N3t81*i~0%b&yQEW zSQT#?S@40@@xk7Fx?)OF61xawErs2)TRD>JK%xX8bkhIsk8K0z5-sgw_dpn9d`-t7 z9%|gfT9kjKp|zvn#kRqPz=AXvxo4jr)B%s@8^nJ_Mvb7rAx7!yB{wl9C_-IaTn794 zsLxM0d%L;0!A4-De&%{mP>_Ma7bvYUbS+aSP&rj^xW{u}v$@{bonVNw$|N8mfHz)}&8I*!t`GS{h$h4lJw(pUj__L^p2R8>`#mh!T(SwqfvsXZ)>mH`|1l)lZA{r%T_#vB|REI{7e z5J*4aA(7R_@NN*>2ibjwT>`(XIddV(4$0z4+>!?2@BQxw8IhX=UpDr%MfJ;mL1d^t zX)nWzYs6zO(ETj(&z;QtW)B}HThSF=c+V{)wE41j63H}z_l>&JQc_lW{v3w8!j4q7d*9 z1|IT>=$$kNxnjZxbic2s67p;`ZdBhOJB7wNoS9q~zC=$(O84n|BLsP%h^ zV{e%y+CAOWh^1LL=M;GMJdf)4L>~>wUgF6};*gwQQIzFb(^MWxsi?bof`fW$);LHB zDE+GjM>#-AZ&VQz=x%n?KAA};0n@dSh4lDnaE?2a_5 zdZRS@$@$X6+yeKIqA@1~PitC>SUO4r7ZvaLc8&l`L`2lV%2R(`)mpkZwG9W^DkSM^ z#mYX_b_-EI{0X~Fy-kB0x(z&iC#M%lwem#232TQQnjm$%tKEeNy{9?s~>F>e=;A8|@jdTwY0=M9rh6&4v-rb0!LX|11PANR5YN z_Ehjm;TjoF&mn>HSJ9_S)OxDclUCH6MQ3R_*EOoBSv~aL@m##99Y{b+S5?nG`RGM2 z9<5HsC6heCk#lgZ)9q+Uu9`AvxSx&Wkt|B%HD+&m$CNs)hs}{E@=%JQIU}inPiWT8 zL@U#ARQ_dzL!4HpDw@bI$@7g#=3GMC;Rkli+W|P|uddbj+?>mnGxZ86L6uJO&*>HC z@&vg>)U7Lo;f{%p5A+bOerf68fa&xc56Sb}p7HoP8PrB?XFkJkGCA#~#S)n=)@i6H z-lH1ssHy<7jLPA&h96BAFupnFM4|VPcd0r5=wJ#z=9HZK)QkcDo5QVJ#H==C!xS^l zhDRB)c@@(ln${J3@O_d!JPgjSKVW^jZ)j@j2Oen%sbDlpg;TRsW80*N8kW+4kcf3zU+F2!!kKleAdf6mP#3)}&bB0kKpK#$J zQ9Fg-fQ2d#39sjp?BM1p^xd=9WkbBQ64~OiL)dqysOCO@pX)DWjw^w zgoA&S#XJ+v>LVs95_F9c%|B64PiT|)D1&)x03Usf952ifrW2KuTRCcH#MPosFHsf7 zJ0nN98V>iD2nGXnk61*+cgMWS%9{bbwb}4Zww8jL`H6FLb{i25sr0JWfef(8WrYXd zPTP7>R!BJS#|m6q8X0P(szhQvw&F3!@804SfNvHQ#JrV;Z|=d9#v|uQXy8;b_40a+ zoc$0Pan_4u&*HkgkGMKUcfw&L;=z~0suRS}I4{CJ`eeaVVhh{sDv_1s$U~FJqpqbz z_akOto+Fgz3mG|@nmwOD2$5E8Np3@)v8Yvx^*At zPh8);kVKf}?c28#4&D0)2d_$8q`t(*N9zqub3`O^$jqt@Qinwn4Rn<2(kc2RIKrFk!2Yz=IbOW<{eU4BnM>dl0<7vIG(Z+m02 z@$f%>_rjHL%8afvYn*;+m#3z#E+mor_iW9kR#xsl4;4yowHaF9x|r2EmIJrS`DezkWcJLlby}U9cuMbH?6f(Q_GBb@Ya^w z!s}Vy!|)eM?)v@Z0Hu+J$8sD(>M?Qq?p-G7fVw&q8XYy(o}btzCum3%ihs_$E#xjN zJ?`~pN=i!dur2HZwUIf$ArzWBSpT_h%JQ#$7N(A5NtcIWW2R3HCN4ZsFMsxo&6TUF z3+wAo)Q|7H^Dc@F<3AXS?mxM3stQiq4oVtmHp9Wr4uuZX#~94}jLV^suU)1lj|x?w zL=6vjV$5|rMNZH&Fz_z?g=|kxPm)TLYu#1;gpWT-#s#@Y@y;mf85q!s9o&v?c2k|U z5OO36?S$>ZW7xS<4nYPV=PFbkilBbEC42pvnxWObLbWd8WBQ4<<>Z%NJB}<<`^Fv%b5#X;?p)W*T0|bD6p%zRrc|m zQ1awX=KW{QF2z+z2EHa15d@1@GXVY;z@MG5>j;-N^xoPdUgY(*@?)>fBmsM z3Ti2^l-f@hIWi%t^ylsP5n4gnovoy-r4?uA6g{!;G+7z38uf378+^>OUClSjkPer2 zcr#IsDCV&UMpML2RxtKWD#4sDv{^Fe{|ar?xEx&^9o_29V3)FbWNQ4&VnsGMqCfHd zz~ei&Z=YqdE%hX>&Wq$ckg6&8C^RG_^^oSZ4pF8XMX)n+=al1^)D(e`hV59ydx$9_ ziHx#c8T3?3ys52CQO97Xmc{RnLZR+U@ML}hDvZ;A;qI$kW-(DE#r*vI@$7?(^g1J( zXp#jC(Xd@O8MC?~Y)DgTs_6_YLyQXhveGQc?%ch*QuDfdVBpGDuS4NTws_COUV|Dw zGrNj!xgS-wT;Eio>2-|ylh7m%_J24pc6$Kv26lXYE-r-jp4mo%*5}9e;}v~YW>EK#Nu=Dl>}Lzq^7gYD;* z8UI(<#bVNc4}?RnL9P|J-YCxlNDcK|2m&*d>{OcKk3umDCb8WM1VXC?xw2W#kE;~! zu`SlrROuuR{k*D5_icAXDzBp0*op%8vR=Z%?1s;XFbO@s*)CwS&3g-(EA7yR!18bk z%=OFe^*|I;yUjwODY|p3G=xUF6foK2WfQKwMjoN-QpTS@H>CtR5z86cR*WupA08fR z)xN1TcT-j=yp&g=X6!(Ldxka-T|cn~a{sSe)c`W8_IAbb^K|FtvK!KKo(LzTCbeWf zj)h6jgH(QkHOOqQ5b2C4@`^_y_0b|9G53S`Pzvtf=O(%wr-N0* zd!E z+fJ(%G5Zss`^Edn*}Z-v%Hp*LrMMZ8aSPEX=b14J(2oQRdq&#YAKuTVW@&MkT;aNr zT{`shJ#Q@R4%P-c95{0AS!8p`y$q`7(8E+S%O#!dGVrOs-d>oi8cx(~6M2oR@;){y z=jf_hJCE8?RFdl@d@y~j9(1L(8aLsfO!Q3xN}(|n5LJ%-TFQx z@3KUqTvxh6MR3!#Lg1~svhmj)fa2b@FNcKEEF`=l0sHkrJmh13CZV}twxo4lO1ZocCi+Ey(5Ad~7 zo+Hr&u+TE%hepB-8a64vN#Jz&cylQ7{q>5(GbntSj?ZU$EURd!)s<6d4rPg!3IFxR zop8GyQ*gC>zanOR?h+4=`bcNF;lrF1Z4U~>&wLax)XAAHR+lNXv&L(UbkUxF@dwe{dkr{`fb#{Y>p1yHn4K}&lDAXfXnaRaSILFrO z+H&6pAS8}Xz+xPP)Y1WE;vYb8m(%bpa}mYAP8jM-#*MuIMH&{C$)7FzN5^Y2M*#CIbs=|? zD@^|`Bal)k_V_Li$6K##RaGN#FT-Q;knmYZO(JCWQr-0MsLsp!ivm%cw)y+rfaG() z(vQjogj4;SF>#3i@*w$z!S4$=i@dx%C@MUT4}IPI-P|ryP5{t#rW0u%%M}XmU?S@R z(hBSWzc)9-lxxS#0TzUI6<%dx@DAB{SAo~fh`|x?U@*#qyG~0>OB(D-ph*dowlpv> zFfz&*8bZ!c{176n+ZP#R4UzDf5Pag131Os^K$<3i>v3$P2oBh)_|M06gy27ajO*wb z{T4QSfc60J1Zp_gA-#)db@5ajCv1ozW0uf^tGc^x&3<0W&(p?+U2=EAS#R$gXkV?n zW~U%v&558}62m+)GExT$HCV$S>iQbk46a=9g1E`^m|kuaa~;r70?cq5p|6J{)x~l+839_& z5^0i#(-5023RodjsF9524NEV;naIV(V~fdGA3Wb_3tif>Fa+6 zEgut^inH_D+fag-=;)c*Sq*h{7*`ISx0}v3#KggX8y|ff+0;}L45le5G|z2uU){&? zyay~fAk{@hst}Ln2->vVzIh~tl%GEiZ$b&Ot7UvQ^YYx_Sp4aCpb3&R_D0NY@x0za zBRRR?zIniHp;F^d*GQK2&Cfdx{*O8L`n8smpz^OJ_fnXM8%{%OJ(cu6T54*K{XdRw z2T%b)Z4RGR#?9=5p%RAWXDlduWlcua0ILN&H;j~&m%&By>@qKJtkj7sbSrp=c}I*g z7Xg0;V!s>C$jBI}b(w-Fl?qaYw8Gv&p!d7rQrrjA$`tU7CiuOVFwDEav{DZN$Ifv6 z{0qmY<2u8^DsfC~xiIMgk7hYOu&oV=+_$MJM-^36#bsrCB>)NBE8Q(nK3oR`}@NCOjxxLw^8P&aVm9*b=Z=jasgb|)6+xD(C`n^ z?G!qDLLbq5FuA;!Ux-N}BOxUX1la(;MN>v<>K3rUl!;TnrmE~UfbhM39cg;;%=iHH39h2-9b(U-6d@9 z#9hhDI}P$pgy#u@O#;%r1dvQUN6PKrfY0 zv+h8%E}+A|?;KeA{X}hX2os{1eU?6<+1(o_K=o_f=*4&E8u+|fp>s?_Lqk(j6BOI> zt3LJ5*SXhW=YzpuoNN7Ij^R&V25eXYh642In4O(XPEP(zPXCbGs56|uvgO(7!91%L zeFb@W*gXh4-5-KDQn(Y4?14C=ZrR%xGja~5czy=~Cv2sWK&s%=1Wxp9KTXO zp{d5r+n!=f5D&yXHqKL+cUr)#&K25S=*HK0F^~S>k+u7vjggTH1L!BPB$@ztah6V;W-W^F0eY?!dXYmQ=P zw*d+patmzu+7dfU?SaKin_?=?I}H{rSw$q2e{Mr~V`D?{58RRxMjkAjIxsU*EQ2X% zU1YF6a|2QciRgkSB`ZC>%a5w{#oysDyLBo))tYbRYWaKgFW|&q16mJj=a_Tjdjo_&Vy%5bIhn79yr5NBev0k zh2MK4nyO&ZVb6t*ueYg0Zosbta{w|s1I3UnabNvV3=6%o!XtwY#-^O;L{;Dokl>-D z2tyaPa;7OGEvJkawqX$d7whWS}aXS;I=PgJhfW^Z&Qq>0KZ{{(w}X|6OYK|ED(`Cj==2bk$zkULTPws-~o+SSD{3@;?AJ CQznG~ literal 0 HcmV?d00001 diff --git a/benchmarks/cookies-problem/draw_geometry.m b/benchmarks/cookies-problem/draw_geometry.m new file mode 100644 index 00000000..fdae0c90 --- /dev/null +++ b/benchmarks/cookies-problem/draw_geometry.m @@ -0,0 +1,28 @@ +clear + +% basic domain + +% circles +xc = [0.2 0.5 0.8 0.2 0.8 0.2 0.5 0.8]; +yc = [0.2 0.2 0.2 0.5 0.5 0.8 0.8 0.8]; +N = length(xc); + +radius = 0.13; +theta = linspace(0,2*pi,200); + +for n=1:N + plot(xc(n) + radius*cos(theta), yc(n) + radius*sin(theta),'-k','LineWidth',2); + hold on + text(xc(n)-0.05,yc(n)+0.01,strcat('# ',num2str(n)),'FontSize',15) +end + +% central square +xsquare = [0.4 0.6 0.6 0.4 0.4]; +ysquare = [0.4 0.4 0.6 0.6 0.4]; +plot(xsquare,ysquare,'-k','LineWidth',2); +text(0.485,0.5,'F','FontSize',15) + +axis square + +saveas(gcf,'cookies_domain','png') + From e1bf36f5097e70cae9d50d2bd9ed4c61c68af854 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Mon, 16 Oct 2023 16:17:45 +0200 Subject: [PATCH 07/24] an easy version of forward UQ benchmark --- .../run_forward_benchmark_in_matlab.m | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m diff --git a/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m b/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m new file mode 100644 index 00000000..7f179806 --- /dev/null +++ b/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m @@ -0,0 +1,81 @@ +%% ----------------------- forward UQ benchmark -------------------------- +% +% ------------------------------------------------------------------------- + + +%% preliminar operations: + +% 1) add sparse grids matlab kit and um-bridge matlab client to path +addpath(genpath('~/GIT_projects/Github/sparse-grids-matlab-kit/')) % this is version 23.5 Robert +addpath(genpath('~/GIT_projects/Github/umbridge/matlab/')) + +% 2) run docker container with a command like +% sudo docker run -it -p 4242:4242 + +clear + +%% setup model + +% specify model port +uri = 'http://0.0.0.0:4242'; +model = HTTPModel(uri,'forward'); + +% config cookie solver +config = struct('NumThreads',4,'BasisDegree',3,'Fidelity',1); + + +% wrap model in an @-function too +Psi_fun = @(y) model.evaluate(y',config); + + +%% create sparse grid + +% setup sparse grid ingredients. We will build increasingly large grids using a for loop + +N = 8; +knots = @(n) knots_CC(n,-0.99,-0.2); +lev2knots = @lev2knots_doubling; +idxset_rule = @(i) sum(i-1); +idxset_level_max = 2; % <--- controls max size of sparse grid + + +% With the choice above, the sparse grids will be nested. To recycle evaluations from one grid +% to the next, we need some containers + +S_old = []; % the previous sparse grid in extended format +Sr_old = []; % the previous sparse grid in reduced format +Psi_evals_old = []; % the evaluations of the model output on the previous sparse grid + +% more containers, to save results at each iteration + +nb_pts = []; % nb of points of each sparse grid +Psi_EV = []; % the expected value of Psi computed on each grid + +% here we go with the loop +for w = 0:idxset_level_max + + % create sparse grid in extended format. Recycle some work from previous sparse grid + S = create_sparse_grid(N,w,knots,lev2knots,idxset_rule,S_old); + + % reduce sparse grid + Sr = reduce_sparse_grid(S); + + % eval Psi on each point of sparse grid. Recycle available evaluations whenever possible + Psi_evals = evaluate_on_sparse_grid(Psi_fun,S,Sr,Psi_evals_old,S_old,Sr_old); + + % compute expected value and append the new value to previous container. Same for nb_pts + Psi_EV(end+1) = quadrature_on_sparse_grid(Psi_evals,Sr); %#ok + nb_pts(end+1) = Sr.size; %#ok + + % update containers + S_old = S; + Sr_old = Sr; + Psi_evals_old = Psi_evals; + +end + +%% plot convergence + +figure +semilogx(nb_pts,Psi_EV,'-ok','LineWidth',2,'MarkerFaceColor','k') + From 2db8e34513f095058f0e161e23e0175d8503af90 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 24 Oct 2023 15:13:58 +0200 Subject: [PATCH 08/24] moving stuff from benchmark to model --- {benchmarks => models}/cookies-problem/Dockerfile | 0 {benchmarks => models}/cookies-problem/README.md | 0 .../cookies-problem/cookies_domain.png | Bin .../cookies-problem/draw_geometry.m | 0 .../cookies-problem/umbridge-client.py | 0 .../cookies-problem/umbridge-server.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {benchmarks => models}/cookies-problem/Dockerfile (100%) rename {benchmarks => models}/cookies-problem/README.md (100%) rename {benchmarks => models}/cookies-problem/cookies_domain.png (100%) rename {benchmarks => models}/cookies-problem/draw_geometry.m (100%) rename {benchmarks => models}/cookies-problem/umbridge-client.py (100%) rename {benchmarks => models}/cookies-problem/umbridge-server.py (100%) diff --git a/benchmarks/cookies-problem/Dockerfile b/models/cookies-problem/Dockerfile similarity index 100% rename from benchmarks/cookies-problem/Dockerfile rename to models/cookies-problem/Dockerfile diff --git a/benchmarks/cookies-problem/README.md b/models/cookies-problem/README.md similarity index 100% rename from benchmarks/cookies-problem/README.md rename to models/cookies-problem/README.md diff --git a/benchmarks/cookies-problem/cookies_domain.png b/models/cookies-problem/cookies_domain.png similarity index 100% rename from benchmarks/cookies-problem/cookies_domain.png rename to models/cookies-problem/cookies_domain.png diff --git a/benchmarks/cookies-problem/draw_geometry.m b/models/cookies-problem/draw_geometry.m similarity index 100% rename from benchmarks/cookies-problem/draw_geometry.m rename to models/cookies-problem/draw_geometry.m diff --git a/benchmarks/cookies-problem/umbridge-client.py b/models/cookies-problem/umbridge-client.py similarity index 100% rename from benchmarks/cookies-problem/umbridge-client.py rename to models/cookies-problem/umbridge-client.py diff --git a/benchmarks/cookies-problem/umbridge-server.py b/models/cookies-problem/umbridge-server.py similarity index 100% rename from benchmarks/cookies-problem/umbridge-server.py rename to models/cookies-problem/umbridge-server.py From 1ee39c7cc906f389e98974197e971db294e6a261 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 24 Oct 2023 15:56:02 +0200 Subject: [PATCH 09/24] add test output --- .../{umbridge-client.py => test_output.py} | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) rename models/cookies-problem/{umbridge-client.py => test_output.py} (79%) diff --git a/models/cookies-problem/umbridge-client.py b/models/cookies-problem/test_output.py similarity index 79% rename from models/cookies-problem/umbridge-client.py rename to models/cookies-problem/test_output.py index cd8b48a9..279f307e 100644 --- a/models/cookies-problem/umbridge-client.py +++ b/models/cookies-problem/test_output.py @@ -2,9 +2,9 @@ # run this script as python3 umbridge-client.py http://localhost:4242 - import argparse import umbridge +import pytest parser = argparse.ArgumentParser(description='Minimal HTTP model demo.') parser.add_argument('url', metavar='url', type=str, @@ -29,5 +29,9 @@ # Simple model evaluation #print(model(param)) -print(model(param,{"NumThreads": 10, "BasisDegree": 4, "Fidelity": 2})) +#print(model(param,{"NumThreads": 10, "BasisDegree": 4, "Fidelity": 2})) +output = model(param,{"NumThreads": 10, "BasisDegree": 4, "Fidelity": 2}) + +print(output) +assert pytest.approx(output[0][0]) == 0.06932827462480169, "Output not as expected" From a4e3492599dfe2f814a6ae728939276d155a217b Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 24 Oct 2023 15:56:54 +0200 Subject: [PATCH 10/24] adding a benchmark model to the server --- models/cookies-problem/umbridge-server.py | 49 ++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/models/cookies-problem/umbridge-server.py b/models/cookies-problem/umbridge-server.py index 22e919ed..9b6363c8 100644 --- a/models/cookies-problem/umbridge-server.py +++ b/models/cookies-problem/umbridge-server.py @@ -39,6 +39,53 @@ def __call__(self, parameters, config): def supports_evaluate(self): return True + + + +class TestBenchmark(umbridge.Model): + + def __init__(self): + super().__init__("benchmark") + + def get_input_sizes(self, config): + return [8] + + def get_output_sizes(self, config): + return [1] + + def __call__(self, parameters, config): +# def __call__(self, parameters, config): + arguments = " ".join([str(x) for x in parameters[0]]); + + num_threads = str(config.get("NumThreads")); + basis_degree = str(4); + fidelity = str(2); + + arguments = arguments + " " + basis_degree + " " + fidelity + + # System call, cd into working directory and call model binary + os.system('export IGATOOLS_NUM_THREADS=' + num_threads + ' && /build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo.release ' + arguments) +# os.system('export IGATOOLS_NUM_THREADS=10 && LD_LIBRARY_PATH=./ ./poisson_lorenzo.release ' + arguments) +# os.system('cd ./test && export IGATOOLS_NUM_THREADS=10 && LD_LIBRARY_PATH=./ ./poisson_lorenzo.release ' + arguments) + + # Read second line of output file + with open('/poisson_lorenzo_results.dat', 'r') as f: +# with open('/build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo_results.dat', 'r') as f: +# f.readline() # Skip first line + line = f.readline() # Read first line + + return [[float(line)]] + + + def supports_evaluate(self): + return True + + + + + testmodel = TestModel() +testbenchmark = TestBenchmark() + -umbridge.serve_models([testmodel], 4242) +umbridge.serve_models([testmodel,testbenchmark], 4242) From e238b43ad34c0cea33c27d2997af8492b8d90f15 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 28 Nov 2023 17:10:19 +0100 Subject: [PATCH 11/24] fixed readme of model --- models/cookies-problem/README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/models/cookies-problem/README.md b/models/cookies-problem/README.md index 844b3d70..503a6c61 100644 --- a/models/cookies-problem/README.md +++ b/models/cookies-problem/README.md @@ -1,9 +1,7 @@ # The cookies model ## Overview -**This benchmark should probably rather be a model but I will prepare the text as a benchmark for the time being** - -This benchmark implements the so-called 'cookies problem' or 'cookies in the oven problem' [1,2,3], i.e., a simplified thermal equation in which the conductivity coefficient is uncertain in 8 circular subdomains ('the cookies'), whereas it is known (and constant) in the remaining of the domain ('the oven'). The PDE is solved by an isogeometric solver with maximum continuity splines, whose degree can be set by the user. See below for full description. +This model implements the so-called 'cookies problem' or 'cookies in the oven problem' \[1,2,3\], i.e., a simplified thermal equation in which the conductivity coefficient is uncertain in 8 circular subdomains ('the cookies'), whereas it is known (and constant) in the remaining of the domain ('the oven'). The PDE is solved by an isogeometric solver with maximum continuity splines, whose degree can be set by the user. See below for full description. ## Authors @@ -20,14 +18,15 @@ docker run -it -p 4242:4242 linusseelinger/ Model | Description --- | --- forward | forward evaluation of the cookies model +benchmark | model setting for the forward UQ model [**ok link to benchmark? in case, fixme**](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/l2-sea-propagation) ### Forward -**check with max whether check on input values are made** +**check with linus whether config values must be given a default (See table below, I think we are confusing default with benchmark value) and if there should be a check on y(i)>-1, see also table below.** Mapping | Dimensions | Description --- |--- |--- -input | [8] | The values of the conductivity coefficient in the 8 cookies, in the range [-0.99 -0.2] (software does not check that inputs are within the bound) +input | [8] | The values of the conductivity coefficient in the 8 cookies, in the range \[-0.99 -0.2\] (software does not check that inputs are within the bound) **do I just say here that it must be y(i)>-1?** output | [1] | The integral of the solution over the central subdomain (see definition of $$\Psi$$ below) Feature | Supported @@ -57,7 +56,7 @@ None | ![cookies-problem](https://raw.githubusercontent.com/UM-Bridge/benchmarks/main/models/l2-sea/l2sea_example.png "geometry of the cookies problem") -The model implements the version of the cookies problem in [1], see also e.g. [2,3] for slightly different versions. With reference to the computational domain $$D=[0,1]^2$$ in the figure above, the cookies model consists in the thermal diffusion problem below, where $$\mathbf{y}$$ are the uncertain parameters discussed in the following and $$\mathrm{x}$$ are physical coordinates +The model implements the version of the cookies problem in \[1\], see also e.g. \[2,3\] for slightly different versions. With reference to the computational domain $$D=[0,1]^2$$ in the figure above, the cookies model consists in the thermal diffusion problem below, where $$\mathbf{y}$$ are the uncertain parameters discussed in the following and $$\mathrm{x}$$ are physical coordinates $$-\mathrm{div}\Big[ a(\mathbf{x},\mathbf{y}) \nabla u(\mathbf{x},\mathbf{y}) \Big] = f(\mathrm{x}), \quad \mathbf{x}\in D$$ @@ -84,7 +83,7 @@ The output of the simulation is the integral of the solution over $$F$$, i.e. $$\Psi = \int_F u(\mathrm{x}) d \mathrm{x}$$ -The PDE is solved with an IGA solver (see e.g. [4]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$) of maximal regularity, i.e. of continuity $$p-1$$. The computational mesh is an $$N\times N$$ quadrilateral mesh (cartesian product of knot lines) with square elements, with $$N=100 \times \mathrm{Fidelity}$$. The implementation is done using the C++ library IGATools [5], available at [gitlab.com/max.martinelli/igatools](gitlab.com/max.martinelli/igatools). +The PDE is solved with an IGA solver (see e.g. \[4\]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$) of maximal regularity, i.e. of continuity $$p-1$$. The computational mesh is an $$N\times N$$ quadrilateral mesh (cartesian product of knot lines) with square elements, with $$N=100 \times \mathrm{Fidelity}$$. The implementation is done using the C++ library IGATools \[5\], available at [gitlab.com/max.martinelli/igatools](gitlab.com/max.martinelli/igatools). From 4e81de8c516ac24d73f5f3202d50138b34faa32b Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 28 Nov 2023 17:39:24 +0100 Subject: [PATCH 12/24] fixing links in model readme and adding some annotations --- models/cookies-problem/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/models/cookies-problem/README.md b/models/cookies-problem/README.md index 503a6c61..6e348455 100644 --- a/models/cookies-problem/README.md +++ b/models/cookies-problem/README.md @@ -15,10 +15,10 @@ docker run -it -p 4242:4242 linusseelinger/ ## Properties -Model | Description ---- | --- -forward | forward evaluation of the cookies model -benchmark | model setting for the forward UQ model [**ok link to benchmark? in case, fixme**](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/l2-sea-propagation) +Model | Description +--- | --- +forward | forward evaluation of the cookies model +benchmark | model setting for the forward UQ model [**ok this row and link to benchmark?**](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem) ### Forward @@ -36,7 +36,7 @@ Gradient | False ApplyJacobian | False ApplyHessian | False -Config | Type | Default | Description +Config | Type | Default **ok this column?** | Description --- |--- |--- |--- NumThreads | integer | 10 | number of physical cores to be used by the solver BasisDegree | integer | 4 | Default degree of spline basis (must be a positive integer) @@ -50,11 +50,11 @@ None | ## Source code -[Model sources here **fixme**.](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem) +[Model sources here **fixme**.](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem) ## Description -![cookies-problem](https://raw.githubusercontent.com/UM-Bridge/benchmarks/main/models/l2-sea/l2sea_example.png "geometry of the cookies problem") +![cookies-problem](https://raw.githubusercontent.com/UM-Bridge/benchmarks/main/models/cookies-problem/cookies_domain.png "geometry of the cookies problem") The model implements the version of the cookies problem in \[1\], see also e.g. \[2,3\] for slightly different versions. With reference to the computational domain $$D=[0,1]^2$$ in the figure above, the cookies model consists in the thermal diffusion problem below, where $$\mathbf{y}$$ are the uncertain parameters discussed in the following and $$\mathrm{x}$$ are physical coordinates From 41ab4064c2fecc6d2ace66a54877b210fea660c5 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 28 Nov 2023 17:45:54 +0100 Subject: [PATCH 13/24] do I specify intervals of parameters in model? --- models/cookies-problem/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/models/cookies-problem/README.md b/models/cookies-problem/README.md index 6e348455..8f5428df 100644 --- a/models/cookies-problem/README.md +++ b/models/cookies-problem/README.md @@ -18,7 +18,7 @@ docker run -it -p 4242:4242 linusseelinger/ Model | Description --- | --- forward | forward evaluation of the cookies model -benchmark | model setting for the forward UQ model [**ok this row and link to benchmark?**](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem) +benchmark | model setting for the forward UQ model [**ok this row and link to benchmark?**](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/README.md) ### Forward @@ -75,12 +75,13 @@ x | 0.2 | 0.5 | 0.8 | 0.2 | 0.8 | 0.2 | 0.5 | 0.8 | y | 0.2 | 0.2 | 0.2 | 0.5 | 0.5 | 0.8 | 0.8 | 0.8 | The uncertain diffusion coefficient is defined as + $$a = 1 + \sum_{i=1}^8 y_n \chi_n(\mathrm{x})$$ -where $$y_n \in [-0.99, -0.2]$$ and $$\chi_n(\mathrm{x}) = \begin{cases} 1 &\text{inside the n-th cookie} \\ 0 &\text{otherwise} \end{cases}$$ +where $$y_n \in [-0.99, -0.2]$$ **do I specify this here or do I just say yn>-1?** +and $$\chi_n(\mathrm{x}) = \begin{cases} 1 &\text{inside the n-th cookie} \\ 0 &\text{otherwise} \end{cases}$$ -The output of the simulation is the integral of the solution over $$F$$, i.e. -$$\Psi = \int_F u(\mathrm{x}) d \mathrm{x}$$ +The output of the simulation is the integral of the solution over $$F$$, i.e. $$\Psi = \int_F u(\mathrm{x}) d \mathrm{x}$$ The PDE is solved with an IGA solver (see e.g. \[4\]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$) of maximal regularity, i.e. of continuity $$p-1$$. The computational mesh is an $$N\times N$$ quadrilateral mesh (cartesian product of knot lines) with square elements, with $$N=100 \times \mathrm{Fidelity}$$. The implementation is done using the C++ library IGATools \[5\], available at [gitlab.com/max.martinelli/igatools](gitlab.com/max.martinelli/igatools). From b6798eccfd074f1d17a0e422bb9fc5df1189c76e Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 28 Nov 2023 17:51:30 +0100 Subject: [PATCH 14/24] one further annotation to readme --- models/cookies-problem/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/cookies-problem/README.md b/models/cookies-problem/README.md index 8f5428df..5f0f72c0 100644 --- a/models/cookies-problem/README.md +++ b/models/cookies-problem/README.md @@ -84,7 +84,7 @@ and $$\chi_n(\mathrm{x}) = \begin{cases} 1 &\text{inside the n-th cookie} \\ 0 & The output of the simulation is the integral of the solution over $$F$$, i.e. $$\Psi = \int_F u(\mathrm{x}) d \mathrm{x}$$ -The PDE is solved with an IGA solver (see e.g. \[4\]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$) of maximal regularity, i.e. of continuity $$p-1$$. The computational mesh is an $$N\times N$$ quadrilateral mesh (cartesian product of knot lines) with square elements, with $$N=100 \times \mathrm{Fidelity}$$. The implementation is done using the C++ library IGATools \[5\], available at [gitlab.com/max.martinelli/igatools](gitlab.com/max.martinelli/igatools). +The PDE is solved with an IGA solver (see e.g. \[4\]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$ **not default**) of maximal regularity, i.e. of continuity $$p-1$$. The computational mesh is an $$N\times N$$ quadrilateral mesh (cartesian product of knot lines) with square elements, with $$N=100 \times \mathrm{Fidelity}$$. The implementation is done using the C++ library IGATools \[5\], available at [gitlab.com/max.martinelli/igatools](gitlab.com/max.martinelli/igatools). From ebbf0962a8ba5ef943490920237f0b09cff8b074 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 28 Nov 2023 19:02:46 +0100 Subject: [PATCH 15/24] done with readme for benchmark --- benchmarks/cookies-problem/README.md | 71 ++++++++++++++++++++++++++++ models/cookies-problem/README.md | 2 +- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 benchmarks/cookies-problem/README.md diff --git a/benchmarks/cookies-problem/README.md b/benchmarks/cookies-problem/README.md new file mode 100644 index 00000000..e91145c0 --- /dev/null +++ b/benchmarks/cookies-problem/README.md @@ -0,0 +1,71 @@ +# The cookies problem forward UQ benchmark + +## Overview + +This benchmark runs a forward uncertainty quantification problem for the [cookies model](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem/README.md) using the sparse grids matlab kit interface to UM-Bridge. See below for full description. + +## Authors +- [Massimiliano Martinelli](mailto:martinelli@imati.cnr.it) +- [Lorenzo Tamellini](mailto:tamellini@imati.cnr.it) + +## Run +``` +docker run -it -p 4242:4242 linusseelinger/ +``` + +## Properties + +Model | Description +--- | --- +benchmark | model setting for the forward UQ model + +### Benchmark configuration + +Mapping | Dimensions | Description +--- |--- |--- +input | [8] | The values of the conductivity coefficient in the 8 cookies. They are i.i.d. uniform random variables in the range [-0.99 -0.2] (software does not check that inputs are within the bound) +output | \[1\] | The integral of the solution over the central subdomain (see definition of $$\Psi$$ at [cookies model](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem/README.md) for info) + +Feature | Supported +--- |--- +Evaluate | True +Gradient | False +ApplyJacobian | False +ApplyHessian | False + +Config | Type | Value | Description +--- |--- |--- |--- +NumThreads | integer | 10 | number of physical cores to be used by the solver **this is actually not set by benchmark** +BasisDegree | integer | 4 | Default degree of spline basis (must be a positive integer) +Fidelity | integer | 2 | Controls the number of mesh elements (must be a positive integer, see below for details) + + +## Mount directories +Mount directory | Purpose +--- |--- +None | + +## Source code + +[Benchmark sources available at this folder.](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem) + +## Description + +![cookies-problem](https://raw.githubusercontent.com/UM-Bridge/benchmarks/main/models/cookies-problem/cookies_domain.png "geometry of the cookies problem") + +The benchmark implements a forward uncertainty quantification problem for the [cookies model](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem/README.md). More specifically, we assume that the uncertain parameters $$y_n$$ appearing in the definition of the diffusion coefficient are uniform i.i.d. random variables on the range $$[-0.99, -0.2]$$ and we aim at computing the expected value of the quantity of interest (i.e., output of the model) $$\Psi$$, which is defined as the integral of the solution over $$F$$. + +The PDE is solved with an IGA solver that uses as basis splines of degree $$p=4$$ and maximal regularity, i.e. of continuity $$3$$. This benchmark is identical to the one discussed in \[1\]; however, raw numbers are different since in \[1\] the PDE solver employed was different (standard FEM with piecewise linear basis). + +The computation of the expected value is performed with a standard Smolyak sparse grid, based on Clenshaw--Curtis points, for increasing level $$w=0,1,\ldots,5$$, see e.g. \[2\]. The resulting sparse grids are saved in files named `sparse_grid_w=.txt`. The file `cookies-benchmark-output.txt` reports the number of points in each sparse grid and the corresponding approximations of the expected value of the quantity of interest. The script available [here](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m) generates all the results, using the Sparse Grids Matlab Kit \[2\] for generating sparse grids. The Grids Matlab Kit is available on Github [here](https://github.com/lorenzo-tamellini/sparse-grids-matlab-kit) and a dedicated website with full resources including user manual is available [here](https://sites.google.com/view/sparse-grids-kit). See also [here]() for a generic Matlab UM-Bridge client and [here](https://github.com/UM-Bridge/umbridge/tree/main/clients) for a minimal example of interfacing UM-Bridge with the Sparse Grids Matlab Kit. + + + + +## Bibliography +1 Joakim Bäck, Fabio Nobile, Lorenzo Tamellini, Raul Tempone, **Stochastic spectral Galerkin and collocation methods for PDEs with random coefficients: a numerical comparison**. In *Spectral and High Order Methods for Partial Differential Equations*, Vol. 76 of Lecture Notes in Computational Science and Engineering, Springer, 2011 +2 Chiara Piazzola, Lorenzo Tamellini, **The Sparse Grids Matlab Kit - a Matlab implementation of sparse grids for high-dimensional function approximation and uncertainty quantification**. ACM Transactions on Mathematical Software, 2023. + + + + diff --git a/models/cookies-problem/README.md b/models/cookies-problem/README.md index 5f0f72c0..f6a09432 100644 --- a/models/cookies-problem/README.md +++ b/models/cookies-problem/README.md @@ -50,7 +50,7 @@ None | ## Source code -[Model sources here **fixme**.](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem) +[Model sources here.](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem) ## Description From 8189fb6d0fe6eb8af3b75f6a0d4cdd5a8d37f16c Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 28 Nov 2023 19:15:15 +0100 Subject: [PATCH 16/24] add one note to benchmark readme --- benchmarks/cookies-problem/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/cookies-problem/README.md b/benchmarks/cookies-problem/README.md index e91145c0..25b28a9a 100644 --- a/benchmarks/cookies-problem/README.md +++ b/benchmarks/cookies-problem/README.md @@ -57,7 +57,7 @@ The benchmark implements a forward uncertainty quantification problem for the [c The PDE is solved with an IGA solver that uses as basis splines of degree $$p=4$$ and maximal regularity, i.e. of continuity $$3$$. This benchmark is identical to the one discussed in \[1\]; however, raw numbers are different since in \[1\] the PDE solver employed was different (standard FEM with piecewise linear basis). -The computation of the expected value is performed with a standard Smolyak sparse grid, based on Clenshaw--Curtis points, for increasing level $$w=0,1,\ldots,5$$, see e.g. \[2\]. The resulting sparse grids are saved in files named `sparse_grid_w=.txt`. The file `cookies-benchmark-output.txt` reports the number of points in each sparse grid and the corresponding approximations of the expected value of the quantity of interest. The script available [here](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m) generates all the results, using the Sparse Grids Matlab Kit \[2\] for generating sparse grids. The Grids Matlab Kit is available on Github [here](https://github.com/lorenzo-tamellini/sparse-grids-matlab-kit) and a dedicated website with full resources including user manual is available [here](https://sites.google.com/view/sparse-grids-kit). See also [here]() for a generic Matlab UM-Bridge client and [here](https://github.com/UM-Bridge/umbridge/tree/main/clients) for a minimal example of interfacing UM-Bridge with the Sparse Grids Matlab Kit. +The computation of the expected value is performed with a standard Smolyak sparse grid, based on Clenshaw--Curtis points, for increasing level $$w=0,1,\ldots,5$$, see e.g. \[2\]. **add picture of convergence** The resulting sparse grids are saved in files named `sparse_grid_w=.txt`. The file `cookies-benchmark-output.txt` reports the number of points in each sparse grid and the corresponding approximations of the expected value of the quantity of interest. The script available [here](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m) generates all the results, using the Sparse Grids Matlab Kit \[2\] for generating sparse grids. The Grids Matlab Kit is available on Github [here](https://github.com/lorenzo-tamellini/sparse-grids-matlab-kit) and a dedicated website with full resources including user manual is available [here](https://sites.google.com/view/sparse-grids-kit). See also [here]() for a generic Matlab UM-Bridge client and [here](https://github.com/UM-Bridge/umbridge/tree/main/clients) for a minimal example of interfacing UM-Bridge with the Sparse Grids Matlab Kit. From 8879f2acc8d587f4a47b8a8da56e681f9e743337 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Tue, 28 Nov 2023 19:15:38 +0100 Subject: [PATCH 17/24] ready to run test output and forward benchmark --- .../run_forward_benchmark_in_matlab.m | 21 ++++++++++--- models/cookies-problem/test_output.py | 31 ++++++++++++------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m b/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m index 7f179806..d6cca0cf 100644 --- a/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m +++ b/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m @@ -18,10 +18,10 @@ % specify model port uri = 'http://0.0.0.0:4242'; -model = HTTPModel(uri,'forward'); +model = HTTPModel(uri,'benchmark'); -% config cookie solver -config = struct('NumThreads',4,'BasisDegree',3,'Fidelity',1); +% config cookie solver with num threades +config = struct('NumThreads',4); % wrap model in an @-function too @@ -36,7 +36,7 @@ knots = @(n) knots_CC(n,-0.99,-0.2); lev2knots = @lev2knots_doubling; idxset_rule = @(i) sum(i-1); -idxset_level_max = 2; % <--- controls max size of sparse grid +idxset_level_max = 5; % <--- controls max size of sparse grid % With the choice above, the sparse grids will be nested. To recycle evaluations from one grid @@ -60,6 +60,10 @@ % reduce sparse grid Sr = reduce_sparse_grid(S); + % save it to file + % grid_filename = strcat('sparse_grid_w=',num2str(w),'.txt'); + % export_sparse_grid_to_file(Sr,grid_filename,'with_weights'); + % eval Psi on each point of sparse grid. Recycle available evaluations whenever possible Psi_evals = evaluate_on_sparse_grid(Psi_fun,S,Sr,Psi_evals_old,S_old,Sr_old); @@ -74,8 +78,17 @@ end + +% save values to file +% save('cookies-benchmark-output.txt','nb_pts','Psi_EV','-ascii', '-double') + %% plot convergence figure semilogx(nb_pts,Psi_EV,'-ok','LineWidth',2,'MarkerFaceColor','k') + + + + + diff --git a/models/cookies-problem/test_output.py b/models/cookies-problem/test_output.py index 279f307e..710f418a 100644 --- a/models/cookies-problem/test_output.py +++ b/models/cookies-problem/test_output.py @@ -13,25 +13,32 @@ print(f"Connecting to host URL {args.url}") # Set up a model by connecting to URL -#model = umbridge.HTTPModel(args.url) model = umbridge.HTTPModel(args.url, "forward") -#model = umbridge.HTTPModel(args.url, "posterior") +#test get methods +output = model.get_input_sizes() +print(output) +assert pytest.approx(output[0][0]) == 8, "get_input_sizes() returns wrong value" + + +output = model.get_output_sizes() +print(output) +assert pytest.approx(output[0][0]) == 1, "get input sizes returns wrong value" -print(model.get_input_sizes()) -print(model.get_output_sizes()) -#param = [[-0.2,-0.2,-0.3,-0.4,-0.5,-0.6,-0.7,-0.8]] -param = [[-2.3939786473373e-01 , -8.6610045659126e-01, -2.1086275315687e-01 , -9.2604304103162e-01 , -6.0002531612112e-01 , -5.5677423053456e-01 , - -7.7546408441658e-01 , -7.6957620518706e-01]] -print(param) +#test output +param = [[-2.3939786473373e-01, -8.6610045659126e-01, -2.1086275315687e-01, -9.2604304103162e-01, -6.0002531612112e-01, -5.5677423053456e-01, -7.7546408441658e-01, -7.6957620518706e-01]] +output = model(param,{"NumThreads": 10, "BasisDegree": 3, "Fidelity": 3}) + +print(output) +assert pytest.approx(output[0][0]) == 0.06932827462480169, "Output not as expected" -# Simple model evaluation -#print(model(param)) -#print(model(param,{"NumThreads": 10, "BasisDegree": 4, "Fidelity": 2})) -output = model(param,{"NumThreads": 10, "BasisDegree": 4, "Fidelity": 2}) +#another test, this time for the benchmark version +model = umbridge.HTTPModel(args.url, "benchmark") +param = [[-0.2,-0.2,-0.3,-0.4,-0.5,-0.6,-0.7,-0.8]] +output = model(param,{"NumThreads": 10}) print(output) assert pytest.approx(output[0][0]) == 0.06932827462480169, "Output not as expected" From eb62dee30043a76f6ae4e8264bc24e3213263f64 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Wed, 29 Nov 2023 15:17:14 +0100 Subject: [PATCH 18/24] fixing Dockerfile: it must use clang 17, openmp 17, intel one api 2024, a different IGATools commit, and setting properly environment variables --- models/cookies-problem/Dockerfile | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/models/cookies-problem/Dockerfile b/models/cookies-problem/Dockerfile index def3b1cc..49e29eaa 100644 --- a/models/cookies-problem/Dockerfile +++ b/models/cookies-problem/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:mantic RUN apt update -RUN DEBIAN_FRONTEND=noninteractive apt install -y bash python3-pip pbzip2 wget clang-15 git cmake curl zip pkg-config gfortran lld +RUN DEBIAN_FRONTEND=noninteractive apt install -y bash python3-pip pbzip2 wget clang-17 git cmake curl zip pkg-config gfortran lld libomp5-17 libomp-17-dev RUN pip3 install --break-system-packages umbridge @@ -10,18 +10,20 @@ RUN echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https:/ RUN apt update RUN apt install -y intel-basekit - +# RUN . /opt/intel/oneapi/setvars.sh RUN git clone https://gitlab.com/max.martinelli/igatools.git && \ cd igatools && \ git submodule init && \ git submodule update && \ - git checkout a335daa3bf480cb6e57e2ea8522e3817e841a66d && \ + git checkout 576316cfcec2be3212cba7a95f2b2df60a3f64c2 && \ cd vcpkg && ./bootstrap-vcpkg.sh + +# git checkout a335daa3bf480cb6e57e2ea8522e3817e841a66d && \ WORKDIR /build_igatools -RUN cd /build_igatools && \ +RUN . /opt/intel/oneapi/setvars.sh && cd /build_igatools && \ cmake /igatools \ -G"Unix Makefiles" \ -DIGATOOLS_COMPONENT_DOCUMENTATION=OFF \ @@ -41,8 +43,8 @@ RUN cd /build_igatools && \ -DCMAKE_INSTALL_PREFIX=igatools \ -DCMAKE_BUILD_TYPE=Release \ -DIGATOOLS_STATIC_EXECUTABLE=OFF \ - -DCMAKE_C_COMPILER=clang-15 \ - -DCMAKE_CXX_COMPILER=clang++-15 \ + -DCMAKE_C_COMPILER=clang-17 \ + -DCMAKE_CXX_COMPILER=clang++-17 \ -DBUILD_SHARED_LIBS=ON RUN make -j8 && \ From 8176feba56c941de1fa41f70c7ced2870e3e01d6 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Wed, 29 Nov 2023 16:42:03 +0100 Subject: [PATCH 19/24] umbridge server needs oneapi setvars to be run every time the solver is invoked --- models/cookies-problem/Dockerfile | 1 - models/cookies-problem/umbridge-server.py | 18 ++++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/models/cookies-problem/Dockerfile b/models/cookies-problem/Dockerfile index 49e29eaa..54d20a91 100644 --- a/models/cookies-problem/Dockerfile +++ b/models/cookies-problem/Dockerfile @@ -54,7 +54,6 @@ RUN make -j8 && \ WORKDIR / - COPY umbridge-server.py / CMD python3 umbridge-server.py diff --git a/models/cookies-problem/umbridge-server.py b/models/cookies-problem/umbridge-server.py index 9b6363c8..04b5ef39 100644 --- a/models/cookies-problem/umbridge-server.py +++ b/models/cookies-problem/umbridge-server.py @@ -13,7 +13,6 @@ def get_output_sizes(self, config): return [1] def __call__(self, parameters, config): -# def __call__(self, parameters, config): arguments = " ".join([str(x) for x in parameters[0]]); num_threads = str(config.get("NumThreads")); @@ -23,17 +22,13 @@ def __call__(self, parameters, config): arguments = arguments + " " + basis_degree + " " + fidelity # System call, cd into working directory and call model binary - os.system('export IGATOOLS_NUM_THREADS=' + num_threads + ' && /build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo.release ' + arguments) -# os.system('export IGATOOLS_NUM_THREADS=10 && LD_LIBRARY_PATH=./ ./poisson_lorenzo.release ' + arguments) -# os.system('cd ./test && export IGATOOLS_NUM_THREADS=10 && LD_LIBRARY_PATH=./ ./poisson_lorenzo.release ' + arguments) + os.system('. /opt/intel/oneapi/setvars.sh && export IGATOOLS_NUM_THREADS=' + num_threads + ' && /build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo.release ' + arguments) # Read second line of output file with open('/poisson_lorenzo_results.dat', 'r') as f: -# with open('/build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo_results.dat', 'r') as f: -# f.readline() # Skip first line line = f.readline() # Read first line - return [[float(line)]] + return [[float(line)]] def supports_evaluate(self): @@ -54,7 +49,6 @@ def get_output_sizes(self, config): return [1] def __call__(self, parameters, config): -# def __call__(self, parameters, config): arguments = " ".join([str(x) for x in parameters[0]]); num_threads = str(config.get("NumThreads")); @@ -64,17 +58,13 @@ def __call__(self, parameters, config): arguments = arguments + " " + basis_degree + " " + fidelity # System call, cd into working directory and call model binary - os.system('export IGATOOLS_NUM_THREADS=' + num_threads + ' && /build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo.release ' + arguments) -# os.system('export IGATOOLS_NUM_THREADS=10 && LD_LIBRARY_PATH=./ ./poisson_lorenzo.release ' + arguments) -# os.system('cd ./test && export IGATOOLS_NUM_THREADS=10 && LD_LIBRARY_PATH=./ ./poisson_lorenzo.release ' + arguments) + os.system('. /opt/intel/oneapi/setvars.sh && export IGATOOLS_NUM_THREADS=' + num_threads + ' && /build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo.release ' + arguments) # Read second line of output file with open('/poisson_lorenzo_results.dat', 'r') as f: -# with open('/build_igatools/tests/models/poisson_lorenzo/poisson_lorenzo_results.dat', 'r') as f: -# f.readline() # Skip first line line = f.readline() # Read first line - return [[float(line)]] + return [[float(line)]] def supports_evaluate(self): From aa6b149f14b717984866a14ac8596a5de32be83a Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Wed, 29 Nov 2023 16:44:20 +0100 Subject: [PATCH 20/24] clean up of test_output.py --- models/cookies-problem/test_output.py | 31 +++++++++++++++------------ 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/models/cookies-problem/test_output.py b/models/cookies-problem/test_output.py index 710f418a..5efa17fd 100644 --- a/models/cookies-problem/test_output.py +++ b/models/cookies-problem/test_output.py @@ -1,14 +1,17 @@ #!/usr/bin/env python3 -# run this script as python3 umbridge-client.py http://localhost:4242 +# first run the container as +# +# docker run -it -p 4242:4242 +# +# then run this script as python3 test_output.py http://localhost:4242 import argparse import umbridge import pytest parser = argparse.ArgumentParser(description='Minimal HTTP model demo.') -parser.add_argument('url', metavar='url', type=str, - help='the ULR on which the model is running, for example http://localhost:4242') +parser.add_argument('url', metavar='url', type=str, help='the ULR on which the model is running, for example http://localhost:4242') args = parser.parse_args() print(f"Connecting to host URL {args.url}") @@ -17,28 +20,28 @@ #test get methods output = model.get_input_sizes() -print(output) -assert pytest.approx(output[0][0]) == 8, "get_input_sizes() returns wrong value" +print("get_input_sizes() returns "+str(output[0])) +assert pytest.approx(output[0]) == 8, "get_input_sizes() returns wrong value" output = model.get_output_sizes() -print(output) -assert pytest.approx(output[0][0]) == 1, "get input sizes returns wrong value" +print("get_output_sizes() returns "+str(output[0])) +assert pytest.approx(output[0]) == 1, "get input sizes returns wrong value" -#test output +#test output for default config param = [[-2.3939786473373e-01, -8.6610045659126e-01, -2.1086275315687e-01, -9.2604304103162e-01, -6.0002531612112e-01, -5.5677423053456e-01, -7.7546408441658e-01, -7.6957620518706e-01]] output = model(param,{"NumThreads": 10, "BasisDegree": 3, "Fidelity": 3}) +print("model output (quantity of interest) ="+str(output[0][0])) +assert pytest.approx(output[0][0]) == 0.06934748547844366, "Output not as expected" -print(output) -assert pytest.approx(output[0][0]) == 0.06932827462480169, "Output not as expected" #another test, this time for the benchmark version -model = umbridge.HTTPModel(args.url, "benchmark") +model_B = umbridge.HTTPModel(args.url, "benchmark") param = [[-0.2,-0.2,-0.3,-0.4,-0.5,-0.6,-0.7,-0.8]] -output = model(param,{"NumThreads": 10}) -print(output) -assert pytest.approx(output[0][0]) == 0.06932827462480169, "Output not as expected" +output = model_B(param,{"NumThreads": 10}) +print("model output (quantity of interest) in benchmark configuration="+str(output[0][0])) +assert pytest.approx(output[0][0]) == 0.05725269745090122, "Output not as expected" From 9b5c7b262d1671d1cb5a2902ce65d02f4567435e Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Wed, 29 Nov 2023 18:12:06 +0100 Subject: [PATCH 21/24] separating benchmark run and save from plot. Save results on txt and mat --- .../run_forward_benchmark_in_matlab.m | 64 +++++++++++-------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m b/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m index d6cca0cf..7fd5fd91 100644 --- a/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m +++ b/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m @@ -5,15 +5,21 @@ %% preliminar operations: -% 1) add sparse grids matlab kit and um-bridge matlab client to path -addpath(genpath('~/GIT_projects/Github/sparse-grids-matlab-kit/')) % this is version 23.5 Robert -addpath(genpath('~/GIT_projects/Github/umbridge/matlab/')) +clear -% 2) run docker container with a command like +% 1) run docker container with a command like % sudo docker run -it -p 4242:4242 -clear - + +% 2) add sparse grids matlab kit and um-bridge matlab client to path +% addpath(genpath('~/GIT_projects/Github/sparse-grids-matlab-kit/')) % this is version 23.5 Robert +% addpath(genpath('~/GIT_projects/Github/umbridge/matlab/')) + + +% 3) to regenerate the files with the results (sparse grids in .txt and .mat, results in .txt and .mat), set the flag below to true +saving_stuff = true; + + %% setup model % specify model port @@ -27,6 +33,9 @@ % wrap model in an @-function too Psi_fun = @(y) model.evaluate(y',config); +% a simple call to test that things are working fine +% y_test = [-0.5; -0.5; -0.5; -0.5; -0.5; -0.5; -0.5; -0.5;]; +% Psi_fun(y_test) %% create sparse grid @@ -36,7 +45,7 @@ knots = @(n) knots_CC(n,-0.99,-0.2); lev2knots = @lev2knots_doubling; idxset_rule = @(i) sum(i-1); -idxset_level_max = 5; % <--- controls max size of sparse grid +idxset_level_max = 1; % <--- controls max size of sparse grid % With the choice above, the sparse grids will be nested. To recycle evaluations from one grid @@ -54,19 +63,30 @@ % here we go with the loop for w = 0:idxset_level_max + disp('========================================') + disp(strcat('w=',num2str(w))) + disp('========================================') + % create sparse grid in extended format. Recycle some work from previous sparse grid S = create_sparse_grid(N,w,knots,lev2knots,idxset_rule,S_old); % reduce sparse grid Sr = reduce_sparse_grid(S); - - % save it to file - % grid_filename = strcat('sparse_grid_w=',num2str(w),'.txt'); - % export_sparse_grid_to_file(Sr,grid_filename,'with_weights'); - + % eval Psi on each point of sparse grid. Recycle available evaluations whenever possible Psi_evals = evaluate_on_sparse_grid(Psi_fun,S,Sr,Psi_evals_old,S_old,Sr_old); + if saving_stuff + % save it to file. Type help export_sparse_grid_to_file for info on the saving format + grid_filename = strcat('sparse_grid_w=',num2str(w),'.txt'); + export_sparse_grid_to_file(Sr,grid_filename,'with_weights'); + % save also some minimal information in .mat format + grid_filename_mat = grid_filename(1:end-4); % i.e., remove .txt + save(grid_filename_mat,'S','Sr','Psi_evals') + end + + + % compute expected value and append the new value to previous container. Same for nb_pts Psi_EV(end+1) = quadrature_on_sparse_grid(Psi_evals,Sr); %#ok nb_pts(end+1) = Sr.size; %#ok @@ -79,16 +99,10 @@ end -% save values to file -% save('cookies-benchmark-output.txt','nb_pts','Psi_EV','-ascii', '-double') - -%% plot convergence - -figure -semilogx(nb_pts,Psi_EV,'-ok','LineWidth',2,'MarkerFaceColor','k') - - - - - - +if saving_stuff + % save values to be plotted on file, in txt format. The data saved consists of the two vectors nb_pts and Psi_EV, + % they will be stored as rows of the txt file + save('cookies-benchmark-output.txt','nb_pts','Psi_EV','-ascii', '-double') + % save also on .mat + save('cookies-benchmark-output','nb_pts','Psi_EV') +end From 79be74f7f2a83b418bc9ce5ad585d7a0a7253631 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Mon, 11 Dec 2023 00:12:32 +0100 Subject: [PATCH 22/24] added config default values to model and benchmark. Fixing readme of model and test-output.py accordingly --- models/cookies-problem/Dockerfile | 3 --- models/cookies-problem/README.md | 20 +++++++++----------- models/cookies-problem/test_output.py | 14 +++++++++----- models/cookies-problem/umbridge-server.py | 8 ++++---- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/models/cookies-problem/Dockerfile b/models/cookies-problem/Dockerfile index 54d20a91..43f1d4a0 100644 --- a/models/cookies-problem/Dockerfile +++ b/models/cookies-problem/Dockerfile @@ -57,6 +57,3 @@ WORKDIR / COPY umbridge-server.py / CMD python3 umbridge-server.py - - - diff --git a/models/cookies-problem/README.md b/models/cookies-problem/README.md index f6a09432..a9342850 100644 --- a/models/cookies-problem/README.md +++ b/models/cookies-problem/README.md @@ -10,7 +10,7 @@ This model implements the so-called 'cookies problem' or 'cookies in the oven pr ## Run ``` -docker run -it -p 4242:4242 linusseelinger/ +docker run -it -p 4242:4242 linusseelinger/cookies-problem ``` ## Properties @@ -18,15 +18,13 @@ docker run -it -p 4242:4242 linusseelinger/ Model | Description --- | --- forward | forward evaluation of the cookies model -benchmark | model setting for the forward UQ model [**ok this row and link to benchmark?**](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/README.md) +benchmark | model setting for the forward UQ model [(see benchmark page)](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/README.md) ### Forward -**check with linus whether config values must be given a default (See table below, I think we are confusing default with benchmark value) and if there should be a check on y(i)>-1, see also table below.** - Mapping | Dimensions | Description --- |--- |--- -input | [8] | The values of the conductivity coefficient in the 8 cookies, in the range \[-0.99 -0.2\] (software does not check that inputs are within the bound) **do I just say here that it must be y(i)>-1?** +input | [8] | These values modify the conductivity coefficient in the 8 cookies, each of them must be greater than -1 (software does not check that input values are valid) output | [1] | The integral of the solution over the central subdomain (see definition of $$\Psi$$ below) Feature | Supported @@ -36,9 +34,9 @@ Gradient | False ApplyJacobian | False ApplyHessian | False -Config | Type | Default **ok this column?** | Description +Config | Type | Default | Description --- |--- |--- |--- -NumThreads | integer | 10 | number of physical cores to be used by the solver +NumThreads | integer | 1 | number of physical cores to be used by the solver BasisDegree | integer | 4 | Default degree of spline basis (must be a positive integer) Fidelity | integer | 2 | Controls the number of mesh elements (must be a positive integer, see below for details) @@ -76,15 +74,15 @@ y | 0.2 | 0.2 | 0.2 | 0.5 | 0.5 | 0.8 | 0.8 | 0.8 | The uncertain diffusion coefficient is defined as -$$a = 1 + \sum_{i=1}^8 y_n \chi_n(\mathrm{x})$$ -where $$y_n \in [-0.99, -0.2]$$ **do I specify this here or do I just say yn>-1?** -and $$\chi_n(\mathrm{x}) = \begin{cases} 1 &\text{inside the n-th cookie} \\ 0 &\text{otherwise} \end{cases}$$ +$$a = 1 + \sum_{i=1}^8 y_n \chi_n(\mathrm{x})$$ + +where $$y_n>-1$$ and $$\chi_n(\mathrm{x}) = \begin{cases} 1 &\text{inside the n-th cookie} \\ 0 &\text{otherwise} \end{cases}$$ The output of the simulation is the integral of the solution over $$F$$, i.e. $$\Psi = \int_F u(\mathrm{x}) d \mathrm{x}$$ -The PDE is solved with an IGA solver (see e.g. \[4\]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$ **not default**) of maximal regularity, i.e. of continuity $$p-1$$. The computational mesh is an $$N\times N$$ quadrilateral mesh (cartesian product of knot lines) with square elements, with $$N=100 \times \mathrm{Fidelity}$$. The implementation is done using the C++ library IGATools \[5\], available at [gitlab.com/max.martinelli/igatools](gitlab.com/max.martinelli/igatools). +The PDE is solved with an IGA solver (see e.g. \[4\]) that uses as basis splines of degree $$p$$ (tunable by the user, default $$p=4$$) of maximal regularity, i.e. of continuity $$p-1$$. The computational mesh is an $$N\times N$$ quadrilateral mesh (cartesian product of knot lines) with square elements, with $$N=100 \times \mathrm{Fidelity}$$. The implementation is done using the C++ library IGATools \[5\], available at [gitlab.com/max.martinelli/igatools](gitlab.com/max.martinelli/igatools). diff --git a/models/cookies-problem/test_output.py b/models/cookies-problem/test_output.py index 5efa17fd..8fe6d9fa 100644 --- a/models/cookies-problem/test_output.py +++ b/models/cookies-problem/test_output.py @@ -29,19 +29,23 @@ assert pytest.approx(output[0]) == 1, "get input sizes returns wrong value" -#test output for default config +#test output for default config (thread=1, p=4, fid=2) param = [[-2.3939786473373e-01, -8.6610045659126e-01, -2.1086275315687e-01, -9.2604304103162e-01, -6.0002531612112e-01, -5.5677423053456e-01, -7.7546408441658e-01, -7.6957620518706e-01]] +output = model(param) +print("model output (quantity of interest) for default config values = "+str(output[0][0])) +assert pytest.approx(output[0][0]) == 0.0693282746248043, "Output not as expected" + +#test output for another config output = model(param,{"NumThreads": 10, "BasisDegree": 3, "Fidelity": 3}) -print("model output (quantity of interest) ="+str(output[0][0])) +print("model output (quantity of interest) = "+str(output[0][0])) assert pytest.approx(output[0][0]) == 0.06934748547844366, "Output not as expected" - -#another test, this time for the benchmark version +#another test, this time for the benchmark version (i.e. p=4, fid=2 again, same as model default) model_B = umbridge.HTTPModel(args.url, "benchmark") param = [[-0.2,-0.2,-0.3,-0.4,-0.5,-0.6,-0.7,-0.8]] output = model_B(param,{"NumThreads": 10}) -print("model output (quantity of interest) in benchmark configuration="+str(output[0][0])) +print("model output (quantity of interest) in benchmark configuration = "+str(output[0][0])) assert pytest.approx(output[0][0]) == 0.05725269745090122, "Output not as expected" diff --git a/models/cookies-problem/umbridge-server.py b/models/cookies-problem/umbridge-server.py index 04b5ef39..acba06f2 100644 --- a/models/cookies-problem/umbridge-server.py +++ b/models/cookies-problem/umbridge-server.py @@ -15,9 +15,9 @@ def get_output_sizes(self, config): def __call__(self, parameters, config): arguments = " ".join([str(x) for x in parameters[0]]); - num_threads = str(config.get("NumThreads")); - basis_degree = str(config.get("BasisDegree")); - fidelity = str(config.get("Fidelity")); + num_threads = str(config.get("NumThreads",1)); + basis_degree = str(config.get("BasisDegree",4)); + fidelity = str(config.get("Fidelity",2)); arguments = arguments + " " + basis_degree + " " + fidelity @@ -51,7 +51,7 @@ def get_output_sizes(self, config): def __call__(self, parameters, config): arguments = " ".join([str(x) for x in parameters[0]]); - num_threads = str(config.get("NumThreads")); + num_threads = str(config.get("NumThreads",1)); basis_degree = str(4); fidelity = str(2); From 2e7da23e446ffd4b273d69600e60657edbea81d2 Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Mon, 11 Dec 2023 17:51:48 +0100 Subject: [PATCH 23/24] fixing the readme for the benchmark, and further adjusting the one for the model for consistency --- benchmarks/cookies-problem/README.md | 24 +++++++++++++----------- models/cookies-problem/README.md | 4 ++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/benchmarks/cookies-problem/README.md b/benchmarks/cookies-problem/README.md index 25b28a9a..29f4e3b6 100644 --- a/benchmarks/cookies-problem/README.md +++ b/benchmarks/cookies-problem/README.md @@ -2,7 +2,7 @@ ## Overview -This benchmark runs a forward uncertainty quantification problem for the [cookies model](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem/README.md) using the sparse grids matlab kit interface to UM-Bridge. See below for full description. +This benchmark runs a forward uncertainty quantification problem for the [cookies model](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem/README.md) using the [Sparse Grids Matlab Kit]((https://github.com/lorenzo-tamellini/sparse-grids-matlab-kit)) interface to UM-Bridge. See below for full description. ## Authors - [Massimiliano Martinelli](mailto:martinelli@imati.cnr.it) @@ -10,20 +10,20 @@ This benchmark runs a forward uncertainty quantification problem for the [cookie ## Run ``` -docker run -it -p 4242:4242 linusseelinger/ +docker run -it -p 4242:4242 linusseelinger/cookies-problem ``` ## Properties Model | Description --- | --- -benchmark | model setting for the forward UQ model +benchmark | sets the config options for the forward UQ benchmark (see below) ### Benchmark configuration Mapping | Dimensions | Description --- |--- |--- -input | [8] | The values of the conductivity coefficient in the 8 cookies. They are i.i.d. uniform random variables in the range [-0.99 -0.2] (software does not check that inputs are within the bound) +input | [8] | These values modify the conductivity coefficient in the 8 cookies. They are i.i.d. uniform random variables in the range [-0.99 -0.2] (software does not check that inputs are within the bound) output | \[1\] | The integral of the solution over the central subdomain (see definition of $$\Psi$$ at [cookies model](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem/README.md) for info) Feature | Supported @@ -33,11 +33,11 @@ Gradient | False ApplyJacobian | False ApplyHessian | False -Config | Type | Value | Description ---- |--- |--- |--- -NumThreads | integer | 10 | number of physical cores to be used by the solver **this is actually not set by benchmark** -BasisDegree | integer | 4 | Default degree of spline basis (must be a positive integer) -Fidelity | integer | 2 | Controls the number of mesh elements (must be a positive integer, see below for details) +Config | Type | Default value | Can be changed in benchmark model | Description +--- |--- |--- |--- | --- +NumThreads | integer | 1 | yes | number of physical cores to be used by the solver +BasisDegree | integer | 4 | no | Default degree of spline basis (must be a positive integer) +Fidelity | integer | 2 | no | Controls the number of mesh elements (must be a positive integer, see below for details) ## Mount directories @@ -55,12 +55,14 @@ None | The benchmark implements a forward uncertainty quantification problem for the [cookies model](https://github.com/UM-Bridge/benchmarks/tree/main/models/cookies-problem/README.md). More specifically, we assume that the uncertain parameters $$y_n$$ appearing in the definition of the diffusion coefficient are uniform i.i.d. random variables on the range $$[-0.99, -0.2]$$ and we aim at computing the expected value of the quantity of interest (i.e., output of the model) $$\Psi$$, which is defined as the integral of the solution over $$F$$. -The PDE is solved with an IGA solver that uses as basis splines of degree $$p=4$$ and maximal regularity, i.e. of continuity $$3$$. This benchmark is identical to the one discussed in \[1\]; however, raw numbers are different since in \[1\] the PDE solver employed was different (standard FEM with piecewise linear basis). +The PDE is solved with an IGA solver that uses as basis splines of degree $$p=4$$ and maximal regularity, i.e. of continuity $$3$$, and the mesh has $$200 \times 200$$ elements (i.e., the fidelity config parameter is set to $$2$$). The structure of this benchmark is identical to the one discussed in \[1\]; however, raw numbers are different since in \[1\] the PDE solver employed was different (standard FEM with piecewise linear basis) and the mesh was also different. -The computation of the expected value is performed with a standard Smolyak sparse grid, based on Clenshaw--Curtis points, for increasing level $$w=0,1,\ldots,5$$, see e.g. \[2\]. **add picture of convergence** The resulting sparse grids are saved in files named `sparse_grid_w=.txt`. The file `cookies-benchmark-output.txt` reports the number of points in each sparse grid and the corresponding approximations of the expected value of the quantity of interest. The script available [here](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m) generates all the results, using the Sparse Grids Matlab Kit \[2\] for generating sparse grids. The Grids Matlab Kit is available on Github [here](https://github.com/lorenzo-tamellini/sparse-grids-matlab-kit) and a dedicated website with full resources including user manual is available [here](https://sites.google.com/view/sparse-grids-kit). See also [here]() for a generic Matlab UM-Bridge client and [here](https://github.com/UM-Bridge/umbridge/tree/main/clients) for a minimal example of interfacing UM-Bridge with the Sparse Grids Matlab Kit. +As a reference value, we provide the approximation of the expected value computed with a standard Smolyak sparse grid, based on Clenshaw--Curtis points, for level $$w=5$$, see e.g. \[2\]. The resulting sparse grid has 15713 points, and the corresponding approximation of the expected value is $$0.064196096847169$$. +The script available [here](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m) generates the results, using the Sparse Grids Matlab Kit \[2\] for generating sparse grids. The Grids Matlab Kit is available on Github [here](https://github.com/lorenzo-tamellini/sparse-grids-matlab-kit) and a dedicated website with full resources including user manual is available [here](https://sites.google.com/view/sparse-grids-kit). + ## Bibliography 1 Joakim Bäck, Fabio Nobile, Lorenzo Tamellini, Raul Tempone, **Stochastic spectral Galerkin and collocation methods for PDEs with random coefficients: a numerical comparison**. In *Spectral and High Order Methods for Partial Differential Equations*, Vol. 76 of Lecture Notes in Computational Science and Engineering, Springer, 2011 diff --git a/models/cookies-problem/README.md b/models/cookies-problem/README.md index a9342850..258a9e5c 100644 --- a/models/cookies-problem/README.md +++ b/models/cookies-problem/README.md @@ -17,8 +17,8 @@ docker run -it -p 4242:4242 linusseelinger/cookies-problem Model | Description --- | --- -forward | forward evaluation of the cookies model -benchmark | model setting for the forward UQ model [(see benchmark page)](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/README.md) +forward | forward evaluation of the cookies model, all config options can be modified by the user (see below) +benchmark | sets the config options for the forward UQ benchmark [(see benchmark page)](https://github.com/UM-Bridge/benchmarks/tree/main/benchmarks/cookies-problem/README.md) ### Forward From 4b58a66675d1a850ad8d4d887d03c11cb913eeec Mon Sep 17 00:00:00 2001 From: Lorenzo Tamellini Date: Mon, 11 Dec 2023 18:02:02 +0100 Subject: [PATCH 24/24] cleaning up test benchmark as discussed with Linus --- .../run_forward_benchmark_in_matlab.m | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m b/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m index 7fd5fd91..498351d6 100644 --- a/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m +++ b/benchmarks/cookies-problem/run_forward_benchmark_in_matlab.m @@ -16,10 +16,14 @@ % addpath(genpath('~/GIT_projects/Github/umbridge/matlab/')) -% 3) to regenerate the files with the results (sparse grids in .txt and .mat, results in .txt and .mat), set the flag below to true +% 3) to save results on file (sparse grids in .txt / .mat, results in .txt / .mat), set the flag below to true saving_stuff = true; +% 4) to plot results, set the flag below to true +plotting = true; + + %% setup model % specify model port @@ -39,7 +43,8 @@ %% create sparse grid -% setup sparse grid ingredients. We will build increasingly large grids using a for loop +% setup sparse grid ingredients. Instead of building the final grid in one go, we build it gradaully using a for +% loop that creates a sequence of nested grids N = 8; knots = @(n) knots_CC(n,-0.99,-0.2); @@ -48,8 +53,7 @@ idxset_level_max = 1; % <--- controls max size of sparse grid -% With the choice above, the sparse grids will be nested. To recycle evaluations from one grid -% to the next, we need some containers +% To recycle evaluations from one grid to the next, we need some containers S_old = []; % the previous sparse grid in extended format Sr_old = []; % the previous sparse grid in reduced format @@ -84,8 +88,6 @@ grid_filename_mat = grid_filename(1:end-4); % i.e., remove .txt save(grid_filename_mat,'S','Sr','Psi_evals') end - - % compute expected value and append the new value to previous container. Same for nb_pts Psi_EV(end+1) = quadrature_on_sparse_grid(Psi_evals,Sr); %#ok @@ -106,3 +108,16 @@ % save also on .mat save('cookies-benchmark-output','nb_pts','Psi_EV') end + + +if plotting + % in principle, only nb-pts and Psi_EV are needed, so if you have them on file already you can just load results + % load('cookies-benchmark-output') + figure + semilogx(nb_pts,Psi_EV,'-ok','LineWidth',2,'MarkerFaceColor','k','DisplayName','sparse grid approx. of $\mathbf{E}[\Psi]$') + grid on + xlabel('sparse grids points') + ylabel('$\mathbf{E}[\Psi]$','interpreter','latex','rotation',0) + legend show + set(legend,'interpreter','latex','location','northwest','fontsize',14) +end \ No newline at end of file