From df428b2b881d3ef3b04532b0236acfb356e16848 Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Mon, 23 Jun 2025 12:53:12 -0700 Subject: [PATCH 01/13] first commit, streamwise stability test case set up, goal is to implement it --- src/loMach.cpp | 2 +- src/reactingFlow.cpp | 4 +- src/split_flow_base.cpp | 30 ++++++++-- src/split_flow_base.hpp | 15 ++++- test/inputs/input.supgBox.ini | 109 ++++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 test/inputs/input.supgBox.ini diff --git a/src/loMach.cpp b/src/loMach.cpp index 9f95926f7..4d55564ea 100644 --- a/src/loMach.cpp +++ b/src/loMach.cpp @@ -182,7 +182,7 @@ void LoMachSolver::initialize() { // Instantiate flow solver if (loMach_opts_.flow_solver == "zero-flow") { // No flow---set u = 0. Primarily useful for testing thermochem models in isolation - flow_ = new ZeroFlow(pmesh_, 1); + flow_ = new ZeroFlow(pmesh_, 1, tpsP_); } else if (loMach_opts_.flow_solver == "tomboulides") { // Tomboulides flow solver flow_ = new Tomboulides(pmesh_, loMach_opts_.order, loMach_opts_.order, temporal_coeff_, tpsP_); diff --git a/src/reactingFlow.cpp b/src/reactingFlow.cpp index 78819464f..7933b1def 100644 --- a/src/reactingFlow.cpp +++ b/src/reactingFlow.cpp @@ -956,7 +956,7 @@ void ReactingFlow::initializeSelf() { } AddTempDirichletBC(temperature_value, inlet_attr); - // AddSpecDirichletBC(0.0, inlet_attr); + AddSpecDirichletBC(0.0, inlet_attr); } else if (type == "interpolate") { Array inlet_attr(pmesh_->bdr_attributes.Max()); @@ -1032,7 +1032,7 @@ void ReactingFlow::initializeSelf() { Qt_bc_coeff->constant = 0.0; AddQtDirichletBC(Qt_bc_coeff, attr_wall); - // AddSpecDirichletBC(0.0, attr_wall); + AddSpecDirichletBC(0.0, attr_wall); } } if (rank0_) std::cout << "Temp wall bc completed: " << numWalls << endl; diff --git a/src/split_flow_base.cpp b/src/split_flow_base.cpp index 0094f9227..3f72f4119 100644 --- a/src/split_flow_base.cpp +++ b/src/split_flow_base.cpp @@ -31,13 +31,21 @@ // -----------------------------------------------------------------------------------el- #include "split_flow_base.hpp" +#include "tps.hpp" using namespace mfem; -ZeroFlow::ZeroFlow(mfem::ParMesh *pmesh, int vorder) : pmesh_(pmesh), vorder_(vorder), dim_(pmesh->Dimension()) {} +ZeroFlow::ZeroFlow(mfem::ParMesh *pmesh, int vorder, TPS::Tps *tps) + : pmesh_(pmesh), vorder_(vorder), dim_(pmesh->Dimension()), tpsP_(tps) { + + // nonzero flow option + tpsP_->getInput("loMach/zero-flow/nonzero-flow", nonzero_flow_, false); + +} ZeroFlow::~ZeroFlow() { delete velocity_; + delete zero_; delete fes_; delete fec_; } @@ -46,13 +54,25 @@ void ZeroFlow::initializeSelf() { fec_ = new H1_FECollection(vorder_, dim_); fes_ = new ParFiniteElementSpace(pmesh_, fec_, dim_); velocity_ = new ParGridFunction(fes_); + zero_ = new ParGridFunction(fes_); *velocity_ = 0.0; + *zero_ = 0.0; + + // set background velocity if nonzero + if (nonzero_flow_) { + Vector zero(dim_); + Vector velocity_value(dim_); + zero = 0.0; + tpsP_->getVec("loMach/zero-flow/nonzero-vel", velocity_value, dim_, zero); + VectorConstantCoefficient ub_coeff(velocity_value); + velocity_->ProjectCoefficient(ub_coeff); + } toThermoChem_interface_.velocity = velocity_; toThermoChem_interface_.swirl_supported = false; - // no need to create additional zero vectors - toTurbModel_interface_.gradU = velocity_; - toTurbModel_interface_.gradV = velocity_; - toTurbModel_interface_.gradW = velocity_; + // need to create additional zero vectors, in case of nonzero vel + toTurbModel_interface_.gradU = zero_; + toTurbModel_interface_.gradV = zero_; + toTurbModel_interface_.gradW = zero_; } diff --git a/src/split_flow_base.hpp b/src/split_flow_base.hpp index 9b57c4d99..4c9c13c32 100644 --- a/src/split_flow_base.hpp +++ b/src/split_flow_base.hpp @@ -35,6 +35,11 @@ * @brief Contains base class and simplest possible variant of flow model */ +// forward-declaration for Tps support class (tps.hpp) +namespace TPS { +class Tps; +} + #include "tps_mfem_wrap.hpp" class IODataOrganizer; @@ -143,6 +148,13 @@ class FlowBase { class ZeroFlow final : public FlowBase { protected: + + // Options-related structures + TPS::Tps *tpsP_ = nullptr; + + // Options + bool nonzero_flow_ = false; + mfem::ParMesh *pmesh_; const int vorder_; const int dim_; @@ -150,10 +162,11 @@ class ZeroFlow final : public FlowBase { mfem::FiniteElementCollection *fec_ = nullptr; mfem::ParFiniteElementSpace *fes_ = nullptr; mfem::ParGridFunction *velocity_ = nullptr; + mfem::ParGridFunction *zero_ = nullptr; public: /// Constructor - ZeroFlow(mfem::ParMesh *pmesh, int vorder); + ZeroFlow(mfem::ParMesh *pmesh, int vorder, TPS::Tps *tps = nullptr); /// Destructor ~ZeroFlow() final; diff --git a/test/inputs/input.supgBox.ini b/test/inputs/input.supgBox.ini new file mode 100644 index 000000000..05af0a347 --- /dev/null +++ b/test/inputs/input.supgBox.ini @@ -0,0 +1,109 @@ +[solver] +type = loMach + +[loMach] +mesh = meshes/boxStab.msh +order = 1 +nFilter = 0 +filterWeight = 1.0 +maxIters = 100 +outputFreq = 100 +fluid = dry_air +refLength = 1.0 +equation_system = navier-stokes +enablePressureForcing = True +# pressureGrad = '0.00005372 0.0 0.0' +enableGravity = False +gravity = '0.0 0.0 0.0' +openSystem = False +sgsModel = none +# flow-solver = tomboulides +flow-solver = zero-flow +thermo-solver = calorically-perfect + +[loMach/calperfect] +viscosity-model = sutherland +sutherland/mu0 = 1.68e-5 +sutherland/T0 = 273.0 +sutherland/S0 = 110.4 +numerical-integ = false +Prandtl = 0.72 +#ic = channel +#hsolve-atol = 1.0e-12 +#msolve-atol = 1.0e-12 +#hsolve-rtol = 1.0e-10 +#msolve-rtol = 1.0e-10 +#hsolve-maxIters = 2000 +#msolve-maxIters = 2000 +#streamwise-stabilization = true +#Reh_offset = 1.0 +#Reh_factor = 0.01 + +[loMach/zeroflow] +nonzero-flow = True +nonzero-vel = '1.0 0.0 0.0' + +[io] +outdirBase = output +#enableRestart = True +#restartMode = variableP + +[time] +enableConstantTimestep = True +integrator = curlcurl +#cfl = 0.4 +dt_initial = 1.0e-4 +#dtFactor = 0.01 +dt_fixed = 1.0e-4 +bdfOrder = 1 + +[spongeMultiplier] +uniform = true +uniformMult = 1.0 + +[initialConditions] +rho = 1.0 +rhoU = 1.0 +rhoV = 0.0 +rhoW = 0.0 +temperature = 300.0 +pressure = 101325.0 + +[boundaryConditions] +numWalls = 0 +numInlets = 2 +numOutlets = 0 + +#[boundaryConditions/wall1] +#patch = 1 +#type = viscous_isothermal +#temperature = 300 +#velocity = '0.0 0.0 0.0' +# +#[boundaryConditions/wall2] +#patch = 3 +#type = viscous_isothermal +#temperature = 300 +#velocity = '0.0 0.0 0.0' + +[boundaryConditions/inlet1] +patch = 4 +type = uniform +#swirl = 40.0 +#velocity = '1.0 0.0 0.0' +temperature = 300.0 + +[boundaryConditions/inlet2] +patch = 2 +type = uniform +temperature = 270 + +[periodicity] +enablePeriodic = True +#periodicX = True +periodicY = True + +# background velocity set by new ZeroFlow option + +# how to set up the specific BCs in config? inlets walls and outlets are hard coded +# mainly need neumann walls, or is that periodicity? \ No newline at end of file From dd70420312bdf52f03073640b3ed7ac4b695f6f0 Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Tue, 24 Jun 2025 19:24:40 -0700 Subject: [PATCH 02/13] attempting stability in calorically-perfect --- src/calorically_perfect.cpp | 56 +++++++++++- src/calorically_perfect.hpp | 18 +++- src/loMach.cpp | 2 +- src/utils.cpp | 157 ++++++++++++++++++++++++++++++---- src/utils.hpp | 52 +++++++++++ test/inputs/input.supgBox.ini | 2 +- 6 files changed, 265 insertions(+), 22 deletions(-) diff --git a/src/calorically_perfect.cpp b/src/calorically_perfect.cpp index c182c14e6..d8a13953a 100644 --- a/src/calorically_perfect.cpp +++ b/src/calorically_perfect.cpp @@ -58,10 +58,11 @@ MFEM_HOST_DEVICE double Sutherland(const double T, const double mu_star, const d } CaloricallyPerfectThermoChem::CaloricallyPerfectThermoChem(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, - temporalSchemeCoefficients &time_coeff, TPS::Tps *tps) + temporalSchemeCoefficients &time_coeff, ParGridFunction *gridScale, TPS::Tps *tps) : tpsP_(tps), pmesh_(pmesh), time_coeff_(time_coeff) { rank0_ = (pmesh_->GetMyRank() == 0); order_ = loMach_opts->order; + gridScale_gf_ = gridScale; std::string visc_model; tpsP_->getInput("loMach/calperfect/viscosity-model", visc_model, std::string("sutherland")); @@ -132,6 +133,9 @@ CaloricallyPerfectThermoChem::CaloricallyPerfectThermoChem(mfem::ParMesh *pmesh, tpsP_->getInput("loMach/calperfect/msolve-atol", mass_inverse_atol_, default_atol_); tpsP_->getInput("loMach/calperfect/msolve-max-iter", mass_inverse_max_iter_, max_iter_); tpsP_->getInput("loMach/calperfect/msolve-verbosity", mass_inverse_pl_, pl_solve_); + + // artificial diffusion (SUPG) + tpsP_->getInput("loMach/calperfect/streamwise-stabilization", sw_stab_, false); } CaloricallyPerfectThermoChem::~CaloricallyPerfectThermoChem() { @@ -164,6 +168,20 @@ CaloricallyPerfectThermoChem::~CaloricallyPerfectThermoChem() { delete rho_over_dt_coeff_; delete rho_coeff_; + delete umag_coeff_ + delete gscale_coeff_ + delete visc_coeff_ + delete visc_inv_coeff_ + delete reh1_coeff_ + delete reh2_coeff_ + delete Reh_coeff_ + delete csupg_coeff_ + delete uw1_coeff_ + delete uw2_coeff_ + delete upwind_coeff_ + delete swdiff_coeff_ + delete supg_coeff_ + // allocated in initializeSelf delete sfes_; delete sfec_; @@ -448,6 +466,33 @@ void CaloricallyPerfectThermoChem::initializeOperators() { rhon_next_coeff_ = new GridFunctionCoefficient(&rn_gf_); rhou_coeff_ = new ScalarVectorProductCoefficient(*rhon_next_coeff_, *un_next_coeff_); + // artifical diffusion coefficients + if(sw_stab_) { + umag_coeff_ = new VectorMagnitudeCoefficient(un_next_coeff_) + gscale_coeff_ = new GridFunctionCoefficient(gridScale) + visc_coeff_ = new GridFunctionCoefficient(visc_gf_) + visc_inv_coeff_ = new PowerCoefficient(visc_coeff_, -1) + + // compute Reh + reh1_coeff_ = new ProductCoefficient(rho_coeff_, visc_inv_coeff_) + reh2_coeff_ = new ProductCoefficient(reh1_coeff_, gscale_coeff_) + Reh_coeff_ = new ProductCoefficient(reh1_coeff_, umag_coeff_) + + // Csupg + csupg_coeff_ = new TransformCoefficient(Reh_coeff_, csupgFactor); + + // compute upwind magnitude + uw1_coeff_ = new ProductCoefficient(rho_coeff_, csupg_coeff_) + uw2_coeff_ = new ProductCoefficient(uw1_coeff_, gscale_coeff_) + upwind_coeff_ = new ProductCoefficient(uw2_coeff_, umag_coeff_) + + // streamwise diffusion direction + swdiff_coeff_ = new TransformedMatrixVectorCoefficient(un_next_coeff_, streamwiseTensor); + + supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff) + } + + At_form_ = new ParBilinearForm(sfes_); auto *at_blfi = new ConvectionIntegrator(*rhou_coeff_); if (numerical_integ_) { @@ -483,6 +528,7 @@ void CaloricallyPerfectThermoChem::initializeOperators() { MsRho_form_->FormSystemMatrix(empty, MsRho_); if (rank0_) std::cout << "CaloricallyPerfectThermoChem MsRho operator set" << endl; + // Helmholtz Ht_form_ = new ParBilinearForm(sfes_); auto *hmt_blfi = new MassIntegrator(*rho_over_dt_coeff_); auto *hdt_blfi = new DiffusionIntegrator(*thermal_diff_total_coeff_); @@ -491,6 +537,14 @@ void CaloricallyPerfectThermoChem::initializeOperators() { hmt_blfi->SetIntRule(&ir_di); hdt_blfi->SetIntRule(&ir_di); } + // SUPG + if (sw_stab_) + // SUPG diffusion + auto *Sdt_blfi = new DiffusionIntegrator(*supg_coeff_); + if (numerical_integ_) { + Sdt_blfi->SetIntRule(&ir_di); + } + Ht_form_->AddDomainIntegrator(Sdt_blfi); Ht_form_->AddDomainIntegrator(hmt_blfi); Ht_form_->AddDomainIntegrator(hdt_blfi); Ht_form_->Assemble(); diff --git a/src/calorically_perfect.hpp b/src/calorically_perfect.hpp index 118cd3dd5..ccbdba656 100644 --- a/src/calorically_perfect.hpp +++ b/src/calorically_perfect.hpp @@ -155,6 +155,8 @@ class CaloricallyPerfectThermoChem : public ThermoChemModelBase { ParGridFunction R0PM0_gf_; ParGridFunction Qt_gf_; + ParGridFunction *gridScale_gf_ = nullptr; + // ParGridFunction *buffer_tInlet_ = nullptr; GridFunctionCoefficient *temperature_bc_field_ = nullptr; @@ -171,6 +173,20 @@ class CaloricallyPerfectThermoChem : public ThermoChemModelBase { GridFunctionCoefficient *rho_over_dt_coeff_ = nullptr; GridFunctionCoefficient *rho_coeff_ = nullptr; + VectorMagnitudeCoefficient *umag_coeff_ = nullptr; + GridFunctionCoefficient *gscale_coeff_ = nullptr; + GridFunctionCoefficient *visc_coeff_ = nullptr; + PowerCoefficient *visc_inv_coeff_ = nullptr; + ProductCoefficient *reh1_coeff_ = nullptr; + ProductCoefficient *reh2_coeff_ = nullptr; + ProductCoefficient *Reh_coeff_ = nullptr; + TransformCoefficient *csupg_coeff_ = nullptr; + ProductCoefficient *uw1_coeff_ = nullptr; + ProductCoefficient *uw2_coeff_ = nullptr; + ProductCoefficient *upwind_coeff_ = nullptr; + TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; + ScalarMatrixProductCoefficient *supg_coeff_ = nullptr; + // operators and solvers ParBilinearForm *At_form_ = nullptr; ParBilinearForm *Ms_form_ = nullptr; @@ -233,7 +249,7 @@ class CaloricallyPerfectThermoChem : public ThermoChemModelBase { public: CaloricallyPerfectThermoChem(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, temporalSchemeCoefficients &timeCoeff, - TPS::Tps *tps); + ParGridFunction *gridScale, TPS::Tps *tps); virtual ~CaloricallyPerfectThermoChem(); // Functions overriden from base class diff --git a/src/loMach.cpp b/src/loMach.cpp index 4d55564ea..8ebd6392d 100644 --- a/src/loMach.cpp +++ b/src/loMach.cpp @@ -166,7 +166,7 @@ void LoMachSolver::initialize() { if (loMach_opts_.thermo_solver == "constant-property") { thermo_ = new ConstantPropertyThermoChem(pmesh_, loMach_opts_.order, tpsP_); } else if (loMach_opts_.thermo_solver == "calorically-perfect") { - thermo_ = new CaloricallyPerfectThermoChem(pmesh_, &loMach_opts_, temporal_coeff_, tpsP_); + thermo_ = new CaloricallyPerfectThermoChem(pmesh_, &loMach_opts_, temporal_coeff_, (meshData_->getGridScale()), tpsP_); } else if (loMach_opts_.thermo_solver == "lte-thermo-chem") { thermo_ = new LteThermoChem(pmesh_, &loMach_opts_, temporal_coeff_, tpsP_); } else if (loMach_opts_.thermo_solver == "reacting-flow") { diff --git a/src/utils.cpp b/src/utils.cpp index 553181e64..89bfa0d7c 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1133,23 +1133,108 @@ void scalarGrad3DV(FiniteElementSpace *fes, FiniteElementSpace *vfes, Vector u, R1_gf.GetTrueDofs(*gu); } -/* -void vectorGrad3DV(FiniteElementSpace *fes, Vector u, Vector *gu, Vector *gv, Vector *gw) { - ParGridFunction R1_gf; - R1_gf.SetSpace(fes); - - ParGridFunction R1a_gf, R1b_gf, R1c_gf; - R1a_gf.SetSpace(fes); - R1b_gf.SetSpace(fes); - R1c_gf.SetSpace(fes); - - R1_gf.SetFromTrueDofs(u); - vectorGrad3D(R1_gf, R1a_gf, R1b_gf, R1c_gf); - R1a_gf.GetTrueDofs(*gu); - R1b_gf.GetTrueDofs(*gv); - R1c_gf.GetTrueDofs(*gw); + + +void streamwiseTensor(Vector vel, DenseMatrix swMgbl) { + + int dim = u.size() + + // streamwise coordinate system + Vector unitNorm; + Vector unitT1; + Vector unitT2; + unitNorm.SetSize(dim); + unitT1.SetSize(dim); + unitT2.SetSize(dim); + + // streamwise direction + for (int i = 0; i < dim; i++) unitNorm[i] = vel[i]; + double mod = 0.0; + for (int i = 0; i < dim; i++) mod += unitNorm[i] * unitNorm[i]; + mod = std::max(mod, 1.0e-18); + double Umag = std::sqrt(mod); + unitNorm /= Umag; + + // for zero-flow + if (Umag < 1.0e-8) { + // for (int i = 0; i < dim; i++) { + // for (int j = 0; j < dim; j++) { + // swMgbl(i, j) = 0.0; + // } + // } + swMgbl = 0.0; + return; + } + + // tangent direction (not unique) + int maxInd, minusInd, plusInd; + if (unitNorm[0] * unitNorm[0] > unitNorm[1] * unitNorm[1]) { + maxInd = 0; + } else { + maxInd = 1; + } + if (dim == 3 && unitNorm[maxInd] * unitNorm[maxInd] < unitNorm[2] * unitNorm[2]) { + maxInd = 2; + } + minusInd = ((maxInd - 1) % dim + dim) % dim; + plusInd = (maxInd + 1) % dim; + + unitT1[minusInd] = -unitNorm[maxInd]; + unitT1[maxInd] = unitNorm[minusInd]; + unitT1[plusInd] = 0.0; + mod = 0.0; + for (int i = 0; i < dim; i++) mod += unitT1[i] * unitT1[i]; + unitT1 /= std::sqrt(mod); + + // t2 is then orthogonal to both normal & t1 + if (dim == 3) { + unitT2[0] = +(unitNorm[1] * unitT1[2] - unitNorm[2] * unitT1[1]); + unitT2[1] = -(unitNorm[0] * unitT1[2] - unitNorm[2] * unitT1[0]); + unitT2[2] = +(unitNorm[0] * unitT1[1] - unitNorm[1] * unitT1[0]); + } + + // transform from streamwise coords to global + DenseMatrix M(dim, dim); + for (int d = 0; d < dim; d++) { + M(d, 0) = unitNorm[d]; + M(d, 1) = unitT1[d]; + if (dim == 3) M(d, 2) = unitT2[d]; + } + + // streamwise coeff + DenseMatrix swM(dim, dim); + swM = 0.0; + swM(0, 0) = 1.0; + + /* + std::cout << " " << endl; + for (int i = 0; i < dim_; i++) { + for (int j = 0; j < dim_; j++) { + std::cout << M(i,j) << " " ; + } + std::cout << endl; + } + */ + + // M_{im} swM_{mn} M_{jn} or M*"mu"*M^T (with n,t1,t2 in columns of M) + // DenseMatrix swMgbl(dim, dim); + swMgbl = 0.0; + for (int i = 0; i < dim; i++) { + for (int j = 0; j < dim; j++) { + for (int m = 0; m < dim; m++) { + for (int n = 0; n < dim; n++) { + swMgbl(i, j) += M(i, m) * M(j, n) * swM(m, n); + } + } + } + }` } -*/ + +void csupgFactor(double Reh) { + // return 0.5 * (tanh(re_factor * Re - re_offset) + 1.0); + return 0.5 * (tanh(Reh) + 1.0); +} + void EliminateRHS(Operator &A, ConstrainedOperator &constrainedA, const Array &ess_tdof_list, Vector &x, Vector &b, Vector &X, Vector &B, int copy_interior) { @@ -1270,7 +1355,7 @@ void readTable(MPI_Comm TPSCommWorld, std::string filename, bool xLogScale, bool assert(dims[1] == 2); // all not 0 ranks have not had matrix size set as in h5ReadTable - if (!rank0) tableHost.back().SetSize(dims[0], dims[1]); + iGradientVectorf (!rank0) tableHost.back().SetSize(dims[0], dims[1]); double *d_table = tableHost.back().HostReadWrite(); MPI_Bcast(d_table, dims[0] * dims[1], MPI_DOUBLE, 0, TPSCommWorld); @@ -1282,7 +1367,7 @@ void readTable(MPI_Comm TPSCommWorld, std::string filename, bool xLogScale, bool } namespace mfem { -GradientVectorGridFunctionCoefficient::GradientVectorGridFunctionCoefficient(const GridFunction *gf) +GridFunctionCoefficient::GradientVectorGridFunctionCoefficient(const GridFunction *gf) : MatrixCoefficient((gf) ? gf->VectorDim() : 0) { GridFunc = gf; } @@ -1324,4 +1409,40 @@ void GradientVectorGridFunctionCoefficient::Eval(DenseMatrix &G, ElementTransfor } } + +VectorMagnitudeCoefficient::VectorMagnitudeCoefficient(VectorCoefficient &A) + : a(&A), va(A.GetVDim()) { } + +void VectorMagnitudeCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + this->Coefficient::SetTime(t); +} + +double VectorMagnitudeCoefficient::Eval(ElementTransformation &T, + const IntegrationPoint &ip) +{ + a->Eval(va, T, ip); + // double res = 0; + // for (int i = 0; i < va.size(); i++) { res += va[i] * va[i]} + // res = std::sqrt(res) + // return res; + double mod = std::max(std::sqrt(va * va), 1.0e-18); + return mod +} + +void TransformedMatrixVectorCoefficient::SetTime(double t) +{ + Q1->SetTime(t); + this->Coefficient::SetTime(t); +} + +void TransformedMatrixVectorCoefficient::Eval(DenseMatrix &G, ElementTransformation &T, const IntegrationPoint &ip) { + + Vector buf; + buf.SetSize(Q1->GetVDim()); + Q1->Eval(buf, T, ip) + Function(buf, G); +} + } // namespace mfem diff --git a/src/utils.hpp b/src/utils.hpp index 4f23c5d91..e73a3b9d9 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -188,6 +188,11 @@ void makeContinuous(ParGridFunction &u); bool copyFile(const char *SRC, const char *DEST); +/// upwind diffusion +// double upwindDiffMag(Vector *state, double gridspace); +void streamwiseTensor(DenseMatrix swMgbl, Vector vel); +double csupgFactor(double Reh); + /// Eliminate essential BCs in an Operator and apply to RHS. /// rename this to something sensible "ApplyEssentialBC" or something void EliminateRHS(Operator &A, ConstrainedOperator &constrainedA, const Array &ess_tdof_list, Vector &x, Vector &b, @@ -224,6 +229,53 @@ class GradientVectorGridFunctionCoefficient : public MatrixCoefficient { virtual ~GradientVectorGridFunctionCoefficient() {} }; + +/// Scalar coefficient defined as the magnitude of a vector coefficient +class VectorMagnitudeCoefficient : public Coefficient +{ +private: + VectorCoefficient * a; + + mutable Vector va; +public: + /// Construct with the vector coefficient. Result is \sqrt{(\f$ A \cdot a \f$}. + VectorMagnitudeCoefficient(VectorCoefficient &A); + + /// Set the time for internally stored coefficients + void SetTime(double t); + + /// Reset the vector + void SetACoef(VectorCoefficient &A) { a = &A; } + /// Return the vector coefficient + VectorCoefficient * GetACoef() const { return a; } + + /// Evaluate the coefficient at @a ip. + virtual double Eval(ElementTransformation &T, + const IntegrationPoint &ip); +}; + +/// Matrix coefficient computed from a function F(v(x)) of a dim-sized vector coefficient, v(x) +class TransformedMatrixVectorCoefficient : public MatrixCoefficient { + private: + Coefficient * Q1; + std::function Function; + + public: + /** @brief Construct the coefficient with a scalar grid function @a gf. The + grid function is not owned by the coefficient. */ + TransformedMatrixVectorCoefficient(const VectorCoefficient *vc, + std::function F) + : Q1(vc), Function(std::move(F)) { } + + /// Set the time for internally stored coefficients + void SetTime(double t); + + /// Evaluate the gradient vector coefficient at @a ip. + using MatrixCoefficient::Eval; + virtual void Eval(DenseMatrix &G, ElementTransformation &T, const IntegrationPoint &ip); + + virtual ~TransformedMatrixVectorCoefficient() {} +}; } // namespace mfem #endif // UTILS_HPP_ diff --git a/test/inputs/input.supgBox.ini b/test/inputs/input.supgBox.ini index 05af0a347..00b51906c 100644 --- a/test/inputs/input.supgBox.ini +++ b/test/inputs/input.supgBox.ini @@ -35,7 +35,7 @@ Prandtl = 0.72 #msolve-rtol = 1.0e-10 #hsolve-maxIters = 2000 #msolve-maxIters = 2000 -#streamwise-stabilization = true +streamwise-stabilization = true #Reh_offset = 1.0 #Reh_factor = 0.01 From 1334c2bac56d3bb4d84f4294785a2967ef6f25de Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Wed, 25 Jun 2025 13:49:14 -0700 Subject: [PATCH 03/13] supg implemented for calorically-perfect helmholtz solve --- src/calorically_perfect.cpp | 62 +++++++++++++++++----------------- src/calorically_perfect.hpp | 6 +++- src/split_flow_base.cpp | 5 ++- src/split_flow_base.hpp | 2 +- src/utils.cpp | 63 ++++++++++++++++++++++++----------- src/utils.hpp | 18 +++++----- test/inputs/input.supgBox.ini | 10 +++--- test/meshes/boxStab.msh | 3 ++ 8 files changed, 99 insertions(+), 70 deletions(-) create mode 100644 test/meshes/boxStab.msh diff --git a/src/calorically_perfect.cpp b/src/calorically_perfect.cpp index d8a13953a..33531dd91 100644 --- a/src/calorically_perfect.cpp +++ b/src/calorically_perfect.cpp @@ -168,19 +168,19 @@ CaloricallyPerfectThermoChem::~CaloricallyPerfectThermoChem() { delete rho_over_dt_coeff_; delete rho_coeff_; - delete umag_coeff_ - delete gscale_coeff_ - delete visc_coeff_ - delete visc_inv_coeff_ - delete reh1_coeff_ - delete reh2_coeff_ - delete Reh_coeff_ - delete csupg_coeff_ - delete uw1_coeff_ - delete uw2_coeff_ - delete upwind_coeff_ - delete swdiff_coeff_ - delete supg_coeff_ + delete umag_coeff_; + delete gscale_coeff_; + delete visc_coeff_; + delete visc_inv_coeff_; + delete reh1_coeff_; + delete reh2_coeff_; + delete Reh_coeff_; + delete csupg_coeff_; + delete uw1_coeff_; + delete uw2_coeff_; + delete upwind_coeff_; + delete swdiff_coeff_; + delete supg_coeff_; // allocated in initializeSelf delete sfes_; @@ -468,28 +468,28 @@ void CaloricallyPerfectThermoChem::initializeOperators() { // artifical diffusion coefficients if(sw_stab_) { - umag_coeff_ = new VectorMagnitudeCoefficient(un_next_coeff_) - gscale_coeff_ = new GridFunctionCoefficient(gridScale) - visc_coeff_ = new GridFunctionCoefficient(visc_gf_) - visc_inv_coeff_ = new PowerCoefficient(visc_coeff_, -1) + umag_coeff_ = new VectorMagnitudeCoefficient(*un_next_coeff_); + gscale_coeff_ = new GridFunctionCoefficient(gridScale_gf_); + visc_coeff_ = new GridFunctionCoefficient(&visc_gf_); + visc_inv_coeff_ = new PowerCoefficient(*visc_coeff_, -1); // compute Reh - reh1_coeff_ = new ProductCoefficient(rho_coeff_, visc_inv_coeff_) - reh2_coeff_ = new ProductCoefficient(reh1_coeff_, gscale_coeff_) - Reh_coeff_ = new ProductCoefficient(reh1_coeff_, umag_coeff_) + reh1_coeff_ = new ProductCoefficient(*rho_coeff_, *visc_inv_coeff_); + reh2_coeff_ = new ProductCoefficient(*reh1_coeff_, *gscale_coeff_); + Reh_coeff_ = new ProductCoefficient(*reh1_coeff_, *umag_coeff_); // Csupg - csupg_coeff_ = new TransformCoefficient(Reh_coeff_, csupgFactor); + csupg_coeff_ = new TransformedCoefficient(Reh_coeff_, csupgFactor); // compute upwind magnitude - uw1_coeff_ = new ProductCoefficient(rho_coeff_, csupg_coeff_) - uw2_coeff_ = new ProductCoefficient(uw1_coeff_, gscale_coeff_) - upwind_coeff_ = new ProductCoefficient(uw2_coeff_, umag_coeff_) + uw1_coeff_ = new ProductCoefficient(*rho_coeff_, *csupg_coeff_); + uw2_coeff_ = new ProductCoefficient(*uw1_coeff_, *gscale_coeff_); + upwind_coeff_ = new ProductCoefficient(*uw2_coeff_, *umag_coeff_); // streamwise diffusion direction - swdiff_coeff_ = new TransformedMatrixVectorCoefficient(un_next_coeff_, streamwiseTensor); + swdiff_coeff_ = new TransformedMatrixVectorCoefficient(un_next_coeff_, &streamwiseTensor); - supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff) + supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); } @@ -538,13 +538,13 @@ void CaloricallyPerfectThermoChem::initializeOperators() { hdt_blfi->SetIntRule(&ir_di); } // SUPG + auto *sdt_blfi = new DiffusionIntegrator(*supg_coeff_); if (sw_stab_) // SUPG diffusion - auto *Sdt_blfi = new DiffusionIntegrator(*supg_coeff_); - if (numerical_integ_) { - Sdt_blfi->SetIntRule(&ir_di); - } - Ht_form_->AddDomainIntegrator(Sdt_blfi); + // if (numerical_integ_) { + // sdt_blfi->SetIntRule(&ir_di); + // } + Ht_form_->AddDomainIntegrator(sdt_blfi); Ht_form_->AddDomainIntegrator(hmt_blfi); Ht_form_->AddDomainIntegrator(hdt_blfi); Ht_form_->Assemble(); diff --git a/src/calorically_perfect.hpp b/src/calorically_perfect.hpp index ccbdba656..5ecfcd808 100644 --- a/src/calorically_perfect.hpp +++ b/src/calorically_perfect.hpp @@ -48,6 +48,7 @@ class Tps; #include "io.hpp" #include "thermo_chem_base.hpp" #include "tps_mfem_wrap.hpp" +#include "utils.hpp" using VecFuncT = void(const Vector &x, double t, Vector &u); using ScalarFuncT = double(const Vector &x, double t); @@ -136,6 +137,9 @@ class CaloricallyPerfectThermoChem : public ThermoChemModelBase { // Initial temperature value (if constant IC) double T_ic_; + // streamwise-stabilization + bool sw_stab_; + // FEM related fields and objects // Scalar \f$H^1\f$ finite element collection. @@ -180,7 +184,7 @@ class CaloricallyPerfectThermoChem : public ThermoChemModelBase { ProductCoefficient *reh1_coeff_ = nullptr; ProductCoefficient *reh2_coeff_ = nullptr; ProductCoefficient *Reh_coeff_ = nullptr; - TransformCoefficient *csupg_coeff_ = nullptr; + TransformedCoefficient *csupg_coeff_ = nullptr; ProductCoefficient *uw1_coeff_ = nullptr; ProductCoefficient *uw2_coeff_ = nullptr; ProductCoefficient *upwind_coeff_ = nullptr; diff --git a/src/split_flow_base.cpp b/src/split_flow_base.cpp index 3f72f4119..776ac7f36 100644 --- a/src/split_flow_base.cpp +++ b/src/split_flow_base.cpp @@ -39,7 +39,7 @@ ZeroFlow::ZeroFlow(mfem::ParMesh *pmesh, int vorder, TPS::Tps *tps) : pmesh_(pmesh), vorder_(vorder), dim_(pmesh->Dimension()), tpsP_(tps) { // nonzero flow option - tpsP_->getInput("loMach/zero-flow/nonzero-flow", nonzero_flow_, false); + tpsP_->getInput("loMach/zeroflow/nonzero-flow", nonzero_flow_, false); } @@ -57,13 +57,12 @@ void ZeroFlow::initializeSelf() { zero_ = new ParGridFunction(fes_); *velocity_ = 0.0; *zero_ = 0.0; - // set background velocity if nonzero if (nonzero_flow_) { Vector zero(dim_); Vector velocity_value(dim_); zero = 0.0; - tpsP_->getVec("loMach/zero-flow/nonzero-vel", velocity_value, dim_, zero); + tpsP_->getVec("loMach/zeroflow/nonzero-vel", velocity_value, dim_, zero); VectorConstantCoefficient ub_coeff(velocity_value); velocity_->ProjectCoefficient(ub_coeff); } diff --git a/src/split_flow_base.hpp b/src/split_flow_base.hpp index 4c9c13c32..6467e0421 100644 --- a/src/split_flow_base.hpp +++ b/src/split_flow_base.hpp @@ -153,7 +153,7 @@ class ZeroFlow final : public FlowBase { TPS::Tps *tpsP_ = nullptr; // Options - bool nonzero_flow_ = false; + bool nonzero_flow_; mfem::ParMesh *pmesh_; const int vorder_; diff --git a/src/utils.cpp b/src/utils.cpp index 89bfa0d7c..0cbd3b78e 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1135,9 +1135,9 @@ void scalarGrad3DV(FiniteElementSpace *fes, FiniteElementSpace *vfes, Vector u, -void streamwiseTensor(Vector vel, DenseMatrix swMgbl) { +void streamwiseTensor(const Vector &vel, DenseMatrix &swMgbl) { - int dim = u.size() + int dim = vel.Size(); // streamwise coordinate system Vector unitNorm; @@ -1154,6 +1154,8 @@ void streamwiseTensor(Vector vel, DenseMatrix swMgbl) { mod = std::max(mod, 1.0e-18); double Umag = std::sqrt(mod); unitNorm /= Umag; + + // std::cout << Umag << " " << endl ; // for zero-flow if (Umag < 1.0e-8) { @@ -1181,9 +1183,12 @@ void streamwiseTensor(Vector vel, DenseMatrix swMgbl) { unitT1[minusInd] = -unitNorm[maxInd]; unitT1[maxInd] = unitNorm[minusInd]; - unitT1[plusInd] = 0.0; + if (dim == 3) { // DOUBLE CHECK THIS WHEN TESTING 3D + unitT1[plusInd] = 0.0; + } mod = 0.0; for (int i = 0; i < dim; i++) mod += unitT1[i] * unitT1[i]; + mod = std::max(mod, 1.0e-18); unitT1 /= std::sqrt(mod); // t2 is then orthogonal to both normal & t1 @@ -1206,15 +1211,15 @@ void streamwiseTensor(Vector vel, DenseMatrix swMgbl) { swM = 0.0; swM(0, 0) = 1.0; - /* - std::cout << " " << endl; - for (int i = 0; i < dim_; i++) { - for (int j = 0; j < dim_; j++) { - std::cout << M(i,j) << " " ; - } - std::cout << endl; - } - */ + + // std::cout << " " << endl; + // for (int i = 0; i < dim; i++) { + // for (int j = 0; j < dim; j++) { + // std::cout << M(i,j) << " " ; + // } + // std::cout << endl; + // } + // M_{im} swM_{mn} M_{jn} or M*"mu"*M^T (with n,t1,t2 in columns of M) // DenseMatrix swMgbl(dim, dim); @@ -1227,11 +1232,22 @@ void streamwiseTensor(Vector vel, DenseMatrix swMgbl) { } } } - }` + } + + // std::cout << " " << endl; + // for (int i = 0; i < dim; i++) { + // for (int j = 0; j < dim; j++) { + // std::cout << swMgbl(i,j) << " " ; + // } + // std::cout << endl; + // } + } -void csupgFactor(double Reh) { +double csupgFactor(double Reh) { // return 0.5 * (tanh(re_factor * Re - re_offset) + 1.0); + // printf("%f\n", Reh); + // printf("%f\n", 0.5 * (tanh(Reh) + 1.0)); return 0.5 * (tanh(Reh) + 1.0); } @@ -1355,7 +1371,7 @@ void readTable(MPI_Comm TPSCommWorld, std::string filename, bool xLogScale, bool assert(dims[1] == 2); // all not 0 ranks have not had matrix size set as in h5ReadTable - iGradientVectorf (!rank0) tableHost.back().SetSize(dims[0], dims[1]); + if (!rank0) tableHost.back().SetSize(dims[0], dims[1]); double *d_table = tableHost.back().HostReadWrite(); MPI_Bcast(d_table, dims[0] * dims[1], MPI_DOUBLE, 0, TPSCommWorld); @@ -1367,7 +1383,7 @@ void readTable(MPI_Comm TPSCommWorld, std::string filename, bool xLogScale, bool } namespace mfem { -GridFunctionCoefficient::GradientVectorGridFunctionCoefficient(const GridFunction *gf) +GradientVectorGridFunctionCoefficient::GradientVectorGridFunctionCoefficient(const GridFunction *gf) : MatrixCoefficient((gf) ? gf->VectorDim() : 0) { GridFunc = gf; } @@ -1428,21 +1444,30 @@ double VectorMagnitudeCoefficient::Eval(ElementTransformation &T, // res = std::sqrt(res) // return res; double mod = std::max(std::sqrt(va * va), 1.0e-18); - return mod + return mod; } void TransformedMatrixVectorCoefficient::SetTime(double t) { Q1->SetTime(t); - this->Coefficient::SetTime(t); + this->MatrixCoefficient::SetTime(t); } void TransformedMatrixVectorCoefficient::Eval(DenseMatrix &G, ElementTransformation &T, const IntegrationPoint &ip) { Vector buf; buf.SetSize(Q1->GetVDim()); - Q1->Eval(buf, T, ip) + Q1->Eval(buf, T, ip); Function(buf, G); + + // int dim = Q1->GetVDim(); + // std::cout << " " << endl; + // for (int i = 0; i < dim; i++) { + // for (int j = 0; j < dim; j++) { + // std::cout << G(i,j) << " " ; + // } + // std::cout << endl; + // } } } // namespace mfem diff --git a/src/utils.hpp b/src/utils.hpp index e73a3b9d9..7317517e4 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -190,7 +190,7 @@ bool copyFile(const char *SRC, const char *DEST); /// upwind diffusion // double upwindDiffMag(Vector *state, double gridspace); -void streamwiseTensor(DenseMatrix swMgbl, Vector vel); +void streamwiseTensor(const Vector &vel, DenseMatrix &swMgbl); double csupgFactor(double Reh); /// Eliminate essential BCs in an Operator and apply to RHS. @@ -256,22 +256,20 @@ class VectorMagnitudeCoefficient : public Coefficient /// Matrix coefficient computed from a function F(v(x)) of a dim-sized vector coefficient, v(x) class TransformedMatrixVectorCoefficient : public MatrixCoefficient { - private: - Coefficient * Q1; + protected: + VectorCoefficient *Q1; std::function Function; public: - /** @brief Construct the coefficient with a scalar grid function @a gf. The - grid function is not owned by the coefficient. */ - TransformedMatrixVectorCoefficient(const VectorCoefficient *vc, - std::function F) - : Q1(vc), Function(std::move(F)) { } + TransformedMatrixVectorCoefficient(VectorCoefficient *vc, + std::function F) : + MatrixCoefficient(vc->GetVDim() ), + Q1(vc), Function(std::move(F)) { } /// Set the time for internally stored coefficients void SetTime(double t); - /// Evaluate the gradient vector coefficient at @a ip. - using MatrixCoefficient::Eval; + // using MatrixCoefficient::Eval; virtual void Eval(DenseMatrix &G, ElementTransformation &T, const IntegrationPoint &ip); virtual ~TransformedMatrixVectorCoefficient() {} diff --git a/test/inputs/input.supgBox.ini b/test/inputs/input.supgBox.ini index 00b51906c..d6f7bbbaf 100644 --- a/test/inputs/input.supgBox.ini +++ b/test/inputs/input.supgBox.ini @@ -6,8 +6,8 @@ mesh = meshes/boxStab.msh order = 1 nFilter = 0 filterWeight = 1.0 -maxIters = 100 -outputFreq = 100 +maxIters = 1000 +outputFreq = 1000 fluid = dry_air refLength = 1.0 equation_system = navier-stokes @@ -35,16 +35,16 @@ Prandtl = 0.72 #msolve-rtol = 1.0e-10 #hsolve-maxIters = 2000 #msolve-maxIters = 2000 -streamwise-stabilization = true +streamwise-stabilization = false #Reh_offset = 1.0 #Reh_factor = 0.01 [loMach/zeroflow] -nonzero-flow = True +nonzero-flow = true nonzero-vel = '1.0 0.0 0.0' [io] -outdirBase = output +outdirBase = output_nostab1L #enableRestart = True #restartMode = variableP diff --git a/test/meshes/boxStab.msh b/test/meshes/boxStab.msh new file mode 100644 index 000000000..74d166c19 --- /dev/null +++ b/test/meshes/boxStab.msh @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf889720de9350d00f33208efc95855b676a2a69d82ed0be121ee69454d53a26 +size 5004 From 09ba113438e748218f1e767d83cd4453d6d7605c Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Fri, 27 Jun 2025 18:59:44 -0700 Subject: [PATCH 04/13] implemented supg for tomboulides and reactingflow, concern about viscosity --- src/calorically_perfect.cpp | 16 ++++- src/loMach.cpp | 4 +- src/reactingFlow.cpp | 103 ++++++++++++++++++++++++++- src/reactingFlow.hpp | 22 +++++- src/tomboulides.cpp | 127 +++++++++++++++++++++++++++++++++- src/tomboulides.hpp | 30 +++++++- test/inputs/input.supgBox.ini | 8 +-- 7 files changed, 297 insertions(+), 13 deletions(-) diff --git a/src/calorically_perfect.cpp b/src/calorically_perfect.cpp index 33531dd91..52aef1c90 100644 --- a/src/calorically_perfect.cpp +++ b/src/calorically_perfect.cpp @@ -471,7 +471,7 @@ void CaloricallyPerfectThermoChem::initializeOperators() { umag_coeff_ = new VectorMagnitudeCoefficient(*un_next_coeff_); gscale_coeff_ = new GridFunctionCoefficient(gridScale_gf_); visc_coeff_ = new GridFunctionCoefficient(&visc_gf_); - visc_inv_coeff_ = new PowerCoefficient(*visc_coeff_, -1); + visc_inv_coeff_ = new PowerCoefficient(*visc_coeff_, -1.0); // compute Reh reh1_coeff_ = new ProductCoefficient(*rho_coeff_, *visc_inv_coeff_); @@ -538,13 +538,14 @@ void CaloricallyPerfectThermoChem::initializeOperators() { hdt_blfi->SetIntRule(&ir_di); } // SUPG - auto *sdt_blfi = new DiffusionIntegrator(*supg_coeff_); - if (sw_stab_) + if (sw_stab_) { + auto *sdt_blfi = new DiffusionIntegrator(*supg_coeff_); // SUPG diffusion // if (numerical_integ_) { // sdt_blfi->SetIntRule(&ir_di); // } Ht_form_->AddDomainIntegrator(sdt_blfi); + } Ht_form_->AddDomainIntegrator(hmt_blfi); Ht_form_->AddDomainIntegrator(hdt_blfi); Ht_form_->Assemble(); @@ -628,6 +629,15 @@ void CaloricallyPerfectThermoChem::initializeOperators() { lqd_blfi->SetIntRule(&ir_di); } LQ_form_->AddDomainIntegrator(lqd_blfi); + + // auto *slqd_blfi = new DiffusionIntegrator(*supg_coeff_); + // if (sw_stab_) + // // SUPG diffusion + // // if (numerical_integ_) { + // // slqd_blfi->SetIntRule(&ir_di); + // // } + // LQ_form_->AddDomainIntegrator(slqd_blfi); + if (partial_assembly_) { LQ_form_->SetAssemblyLevel(AssemblyLevel::PARTIAL); } diff --git a/src/loMach.cpp b/src/loMach.cpp index 8ebd6392d..a3a25a3c8 100644 --- a/src/loMach.cpp +++ b/src/loMach.cpp @@ -170,7 +170,7 @@ void LoMachSolver::initialize() { } else if (loMach_opts_.thermo_solver == "lte-thermo-chem") { thermo_ = new LteThermoChem(pmesh_, &loMach_opts_, temporal_coeff_, tpsP_); } else if (loMach_opts_.thermo_solver == "reacting-flow") { - thermo_ = new ReactingFlow(pmesh_, &loMach_opts_, temporal_coeff_, tpsP_); + thermo_ = new ReactingFlow(pmesh_, &loMach_opts_, temporal_coeff_, (meshData_->getGridScale()), tpsP_); } else { // Unknown choice... die if (rank0_) { @@ -185,7 +185,7 @@ void LoMachSolver::initialize() { flow_ = new ZeroFlow(pmesh_, 1, tpsP_); } else if (loMach_opts_.flow_solver == "tomboulides") { // Tomboulides flow solver - flow_ = new Tomboulides(pmesh_, loMach_opts_.order, loMach_opts_.order, temporal_coeff_, tpsP_); + flow_ = new Tomboulides(pmesh_, loMach_opts_.order, loMach_opts_.order, temporal_coeff_, (meshData_->getGridScale()), tpsP_); } else { // Unknown choice... die if (rank0_) { diff --git a/src/reactingFlow.cpp b/src/reactingFlow.cpp index 7933b1def..477eb13bd 100644 --- a/src/reactingFlow.cpp +++ b/src/reactingFlow.cpp @@ -55,10 +55,11 @@ static double radius(const Vector &pos) { return pos[0]; } static FunctionCoefficient radius_coeff(radius); ReactingFlow::ReactingFlow(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, temporalSchemeCoefficients &time_coeff, - TPS::Tps *tps) + ParGridFunction *gridScale, TPS::Tps *tps) : tpsP_(tps), pmesh_(pmesh), dim_(pmesh->Dimension()), time_coeff_(time_coeff) { rank0_ = (pmesh_->GetMyRank() == 0); order_ = loMach_opts->order; + gridScale_gf_ = gridScale; /// Basic input information tpsP_->getInput("loMach/ambientPressure", ambient_pressure_, 101325.0); @@ -538,6 +539,9 @@ ReactingFlow::ReactingFlow(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, tem operator_split_ = true; assert(nSub_ == 1); } + + // artificial diffusion (SUPG) + tpsP_->getInput("loMach/reactingFlow/streamwise-stabilization", sw_stab_, false); } ReactingFlow::~ReactingFlow() { @@ -618,6 +622,20 @@ ReactingFlow::~ReactingFlow() { delete transport_; delete chemistry_; + delete umag_coeff_; + delete gscale_coeff_; + delete visc_coeff_; + delete visc_inv_coeff_; + delete reh1_coeff_; + delete reh2_coeff_; + delete Reh_coeff_; + delete csupg_coeff_; + delete uw1_coeff_; + delete uw2_coeff_; + delete upwind_coeff_; + delete swdiff_coeff_; + delete supg_coeff_; + // allocated in initializeSelf delete vfes_; delete vfec_; @@ -1112,6 +1130,49 @@ void ReactingFlow::initializeOperators() { rad_kap_gradT_coeff_ = new ScalarVectorProductCoefficient(radius_coeff, *kap_gradT_coeff_); } + // artifical diffusion coefficients + if(sw_stab_) { + // run once to avoid division by zero + // updateMixture(); + // updateDensity(1.0); + updateDiffusivity(); + umag_coeff_ = new VectorMagnitudeCoefficient(*un_next_coeff_); + gscale_coeff_ = new GridFunctionCoefficient(gridScale_gf_); + visc_coeff_ = new GridFunctionCoefficient(&visc_gf_); + visc_inv_coeff_ = new PowerCoefficient(*visc_coeff_, -1.); + + // compute Reh + reh1_coeff_ = new ProductCoefficient(*rhon_next_coeff_, *visc_inv_coeff_); + reh2_coeff_ = new ProductCoefficient(*reh1_coeff_, *gscale_coeff_); + Reh_coeff_ = new ProductCoefficient(*reh2_coeff_, *umag_coeff_); + + // Csupg + csupg_coeff_ = new TransformedCoefficient(Reh_coeff_, csupgFactor); + + if (axisym_) { + // compute upwind magnitude + uw1_coeff_ = new ProductCoefficient(*rad_rho_coeff_, *csupg_coeff_); + uw2_coeff_ = new ProductCoefficient(*uw1_coeff_, *gscale_coeff_); + upwind_coeff_ = new ProductCoefficient(*uw2_coeff_, *umag_coeff_); + + // streamwise diffusion direction + swdiff_coeff_ = new TransformedMatrixVectorCoefficient(un_next_coeff_, &streamwiseTensor); + + supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); + } + else { + // compute upwind magnitude + uw1_coeff_ = new ProductCoefficient(*rhon_next_coeff_, *csupg_coeff_); + uw2_coeff_ = new ProductCoefficient(*uw1_coeff_, *gscale_coeff_); + upwind_coeff_ = new ProductCoefficient(*uw2_coeff_, *umag_coeff_); + + // streamwise diffusion direction + swdiff_coeff_ = new TransformedMatrixVectorCoefficient(un_next_coeff_, &streamwiseTensor); + + supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); + } + } + At_form_ = new ParBilinearForm(sfes_); ConvectionIntegrator *at_blfi; if (axisym_) { @@ -1218,6 +1279,17 @@ void ReactingFlow::initializeOperators() { hmt_blfi->SetIntRule(&ir_di); hdt_blfi->SetIntRule(&ir_di); } + // SUPG + DiffusionIntegrator *sdt_blfi; + if (sw_stab_) { + // auto *sdt_blfi = new DiffusionIntegrator(*supg_coeff_); + sdt_blfi = new DiffusionIntegrator(*supg_coeff_); + // SUPG diffusion + // if (numerical_integ_) { + // sdt_blfi->SetIntRule(&ir_di); + // } + Ht_form_->AddDomainIntegrator(sdt_blfi); + } Ht_form_->AddDomainIntegrator(hmt_blfi); Ht_form_->AddDomainIntegrator(hdt_blfi); if (partial_assembly_) { @@ -1244,6 +1316,17 @@ void ReactingFlow::initializeOperators() { hmy_blfi->SetIntRule(&ir_di); hdy_blfi->SetIntRule(&ir_di); } + // SUPG + DiffusionIntegrator *syt_blfi; + if (sw_stab_) { + // auto *syt_blfi = new DiffusionIntegrator(*supg_coeff_); + syt_blfi = new DiffusionIntegrator(*supg_coeff_); + // SUPG diffusion + // if (numerical_integ_) { + // sdt_blfi->SetIntRule(&ir_di); + // } + Hy_form_->AddDomainIntegrator(syt_blfi); + } Hy_form_->AddDomainIntegrator(hmy_blfi); Hy_form_->AddDomainIntegrator(hdy_blfi); if (partial_assembly_) { @@ -1401,6 +1484,15 @@ void ReactingFlow::initializeOperators() { lqd_blfi->SetIntRule(&ir_di); } LQ_form_->AddDomainIntegrator(lqd_blfi); + + // auto *slqd_blfi = new DiffusionIntegrator(*supg_coeff_); + // if (sw_stab_) + // // SUPG diffusion + // // if (numerical_integ_) { + // // slqd_blfi->SetIntRule(&ir_di); + // // } + // LQ_form_->AddDomainIntegrator(slqd_blfi); + if (partial_assembly_) { LQ_form_->SetAssemblyLevel(AssemblyLevel::PARTIAL); } @@ -1427,6 +1519,15 @@ void ReactingFlow::initializeOperators() { lyd_blfi->SetIntRule(&ir_di); } LY_form_->AddDomainIntegrator(lyd_blfi); + + // auto *slyd_blfi = new DiffusionIntegrator(*supg_coeff_); + // if (sw_stab_) + // // SUPG diffusion + // // if (numerical_integ_) { + // // slyd_blfi->SetIntRule(&ir_di); + // // } + // LY_form_->AddDomainIntegrator(slqd_blfi); + if (partial_assembly_) { LY_form_->SetAssemblyLevel(AssemblyLevel::PARTIAL); } diff --git a/src/reactingFlow.hpp b/src/reactingFlow.hpp index 3ea4fa4c7..4813f2147 100644 --- a/src/reactingFlow.hpp +++ b/src/reactingFlow.hpp @@ -153,6 +153,9 @@ class ReactingFlow : public ThermoChemModelBase { // Initial temperature value (if constant IC) double T_ic_; + // streamwise-stabilization + bool sw_stab_; + // FEM related fields and objects // Scalar \f$H^1\f$ finite element collection. @@ -173,6 +176,8 @@ class ReactingFlow : public ThermoChemModelBase { // Vector \f$H^1\f$ finite element space. ParFiniteElementSpace *vfes_ = nullptr; + ParGridFunction *gridScale_gf_ = nullptr; + // Fields ParGridFunction Tnm1_gf_, Tnm2_gf_; ParGridFunction Tn_gf_, Tn_next_gf_, Text_gf_, resT_gf_; @@ -245,6 +250,20 @@ class ReactingFlow : public ThermoChemModelBase { ProductCoefficient *rad_radiation_sink_coeff_ = nullptr; ScalarVectorProductCoefficient *rad_kap_gradT_coeff_ = nullptr; + VectorMagnitudeCoefficient *umag_coeff_ = nullptr; + GridFunctionCoefficient *gscale_coeff_ = nullptr; + GridFunctionCoefficient *visc_coeff_ = nullptr; + PowerCoefficient *visc_inv_coeff_ = nullptr; + ProductCoefficient *reh1_coeff_ = nullptr; + ProductCoefficient *reh2_coeff_ = nullptr; + ProductCoefficient *Reh_coeff_ = nullptr; + TransformedCoefficient *csupg_coeff_ = nullptr; + ProductCoefficient *uw1_coeff_ = nullptr; + ProductCoefficient *uw2_coeff_ = nullptr; + ProductCoefficient *upwind_coeff_ = nullptr; + TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; + ScalarMatrixProductCoefficient *supg_coeff_ = nullptr; + // operators and solvers ParBilinearForm *At_form_ = nullptr; ParBilinearForm *Ay_form_ = nullptr; @@ -356,7 +375,8 @@ class ReactingFlow : public ThermoChemModelBase { std::vector vizSpecNames_; public: - ReactingFlow(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, temporalSchemeCoefficients &timeCoeff, TPS::Tps *tps); + ReactingFlow(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, temporalSchemeCoefficients &timeCoeff, + ParGridFunction *gridScale, TPS::Tps *tps); virtual ~ReactingFlow(); // Functions overriden from base class diff --git a/src/tomboulides.cpp b/src/tomboulides.cpp index 184df27b6..3902da648 100644 --- a/src/tomboulides.cpp +++ b/src/tomboulides.cpp @@ -67,7 +67,8 @@ void Orthogonalize(Vector &v, const ParFiniteElementSpace *pfes) { v -= global_sum / static_cast(global_size); } -Tomboulides::Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalSchemeCoefficients &coeff, TPS::Tps *tps) +Tomboulides::Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalSchemeCoefficients &coeff, + mfem::ParGridFunction *gridScale, TPS::Tps *tps) : gll_rules(0, Quadrature1D::GaussLobatto), tpsP_(tps), pmesh_(pmesh), @@ -80,6 +81,7 @@ Tomboulides::Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalS rank0_ = (pmesh_->GetMyRank() == 0); axisym_ = false; nvel_ = dim_; + gridScale_gf_ = gridScale; // make sure there is room for BC attributes if (!(pmesh_->bdr_attributes.Size() == 0)) { @@ -128,6 +130,9 @@ Tomboulides::Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalS tps->getInput("loMach/tomboulides/psolve-maxIters", pressure_solve_max_iter_, default_max_iter_); tps->getInput("loMach/tomboulides/hsolve-maxIters", hsolve_max_iter_, default_max_iter_); tps->getInput("loMach/tomboulides/msolve-maxIters", mass_inverse_max_iter_, default_max_iter_); + + // artificial diffusion (SUPG) + tpsP_->getInput("loMach/tomboulides/streamwise-stabilization", sw_stab_, false); } } @@ -169,6 +174,8 @@ Tomboulides::~Tomboulides() { delete L_iorho_lor_; delete L_iorho_form_; + delete Mv_stab_form_; + delete swirl_var_viscosity_coeff_; delete utheta_vec_coeff_; delete rho_ur_ut_coeff_; @@ -204,6 +211,19 @@ Tomboulides::~Tomboulides() { delete iorho_coeff_; delete rho_coeff_; + delete umag_coeff_; + delete gscale_coeff_; + delete visc_inv_coeff_; + delete reh1_coeff_; + delete reh2_coeff_; + delete Reh_coeff_; + delete csupg_coeff_; + delete uw1_coeff_; + delete uw2_coeff_; + delete upwind_coeff_; + delete swdiff_coeff_; + delete supg_coeff_; + // objects allocated by initalizeSelf if (axisym_) delete gravity_vec_; delete utheta_next_gf_; @@ -343,6 +363,8 @@ void Tomboulides::initializeSelf() { tmpR0_.SetSize(sfes_truevsize); tmpR1_.SetSize(vfes_truevsize); + swDiff_vec_.SetSize(vfes_truevsize); + tmpR0b_.SetSize(pfes_truevsize); gradU_.SetSize(vfes_truevsize); gradV_.SetSize(vfes_truevsize); @@ -661,6 +683,45 @@ void Tomboulides::initializeOperators() { swirl_var_viscosity_coeff_ = new InnerProductCoefficient(*grad_mu_coeff_, *utheta_vec_coeff_); } + // artifical diffusion coefficients + if(sw_stab_) { + umag_coeff_ = new VectorMagnitudeCoefficient(*u_next_coeff_); + gscale_coeff_ = new GridFunctionCoefficient(gridScale_gf_); + visc_inv_coeff_ = new PowerCoefficient(*mu_coeff_, -1.0); + + // compute Reh + reh1_coeff_ = new ProductCoefficient(*rho_coeff_, *visc_inv_coeff_); + reh2_coeff_ = new ProductCoefficient(*reh1_coeff_, *gscale_coeff_); + Reh_coeff_ = new ProductCoefficient(*reh2_coeff_, *umag_coeff_); + + // Csupg + csupg_coeff_ = new TransformedCoefficient(Reh_coeff_, csupgFactor); + + if (axisym_) { + // compute upwind magnitude + uw1_coeff_ = new ProductCoefficient(*rad_rho_coeff_, *csupg_coeff_); + uw2_coeff_ = new ProductCoefficient(*uw1_coeff_, *gscale_coeff_); + upwind_coeff_ = new ProductCoefficient(*uw2_coeff_, *umag_coeff_); + + // streamwise diffusion direction + swdiff_coeff_ = new TransformedMatrixVectorCoefficient(u_next_coeff_, &streamwiseTensor); + + supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); + } + else { + // compute upwind magnitude + // dividing by rho anyway + // uw1_coeff_ = new ProductCoefficient(*rho_coeff_, *csupg_coeff_); + uw2_coeff_ = new ProductCoefficient(*uw1_coeff_, *gscale_coeff_); + upwind_coeff_ = new ProductCoefficient(*uw2_coeff_, *umag_coeff_); + + // streamwise diffusion direction + swdiff_coeff_ = new TransformedMatrixVectorCoefficient(u_next_coeff_, &streamwiseTensor); + + supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); + } + } + // Integration rules (only used if numerical_integ_ is true). When // this is the case, the quadrature degree set such that the // Gauss-Lobatto quad pts correspond to the Gauss-Lobatto nodes. @@ -688,6 +749,18 @@ void Tomboulides::initializeOperators() { L_iorho_blfi->SetIntRule(&ir_ni_p); } L_iorho_form_->AddDomainIntegrator(L_iorho_blfi); + + // DiffusionIntegrator *slio_blfi; + // if (sw_stab_) { + // // auto *slio_blfi = new DiffusionIntegrator(*supg_coeff_); + // slio_blfi = new DiffusionIntegrator(*supg_coeff_); + // // SUPG diffusion + // // if (numerical_integ_) { + // // slio_blfi->SetIntRule(&ir_di); + // // } + // L_iorho_form_->AddDomainIntegrator(slio_blfi); + // } + L_iorho_form_->Assemble(); L_iorho_form_->FormSystemMatrix(pres_ess_tdof_, L_iorho_op_); @@ -809,6 +882,19 @@ void Tomboulides::initializeOperators() { Mv_rho_form_->Assemble(); Mv_rho_form_->FormSystemMatrix(empty, Mv_rho_op_); + // Vector mass matrix for streamwise stability of the velocity gradients + if (sw_stab_) { + Mv_stab_form_ = new ParBilinearForm(vfes_); + VectorMassIntegrator *mvs_blfi; + mvs_blfi = new VectorMassIntegrator(*supg_coeff_); + if (numerical_integ_) { + mvs_blfi->SetIntRule(&ir_ni_v); + } + Mv_stab_form_->AddDomainIntegrator(mvs_blfi); + Mv_stab_form_->Assemble(); + Mv_stab_form_->FormSystemMatrix(empty, Mv_stab_op_); + } + // Inverse (unweighted) mass operator (velocity space) if (partial_assembly_) { Vector diag_pa(vfes_->GetTrueVSize()); @@ -886,6 +972,7 @@ void Tomboulides::initializeOperators() { Hv_form_ = new ParBilinearForm(vfes_); VectorMassIntegrator *hmv_blfi; VectorDiffusionIntegrator *hdv_blfi; + if (axisym_) { hmv_blfi = new VectorMassIntegrator(*rad_rho_over_dt_coeff_); hdv_blfi = new VectorDiffusionIntegrator(*rad_mu_coeff_); @@ -899,6 +986,17 @@ void Tomboulides::initializeOperators() { } Hv_form_->AddDomainIntegrator(hmv_blfi); Hv_form_->AddDomainIntegrator(hdv_blfi); + + VectorDiffusionIntegrator *shdv_blfi; + if (sw_stab_) { + // auto *shdv_blfi = new VectorDiffusionIntegrator(*supg_coeff_); + shdv_blfi = new VectorDiffusionIntegrator(*supg_coeff_); + // if (numerical_integ_) { + // shdv_blfi->SetIntRule(&ir_di); + // } + Hv_form_->AddDomainIntegrator(shdv_blfi); + } + if (axisym_) { auto *hfv_blfi = new VectorMassIntegrator(*visc_forcing_coeff_); Hv_form_->AddDomainIntegrator(hfv_blfi); @@ -995,6 +1093,13 @@ void Tomboulides::initializeOperators() { auto *hms_blfi = new MassIntegrator(*rad_rho_over_dt_coeff_); auto *hds_blfi = new DiffusionIntegrator(*rad_mu_coeff_); auto *hfs_blfi = new MassIntegrator(*mu_over_rad_coeff_); + + DiffusionIntegrator *shds_blfi; + if (sw_stab_) { + // auto *shds_blfi = new DiffusionIntegrator(*supg_coeff_); + shds_blfi = new DiffusionIntegrator(*supg_coeff_); + Hs_form_->AddDomainIntegrator(shds_blfi); + } Hs_form_->AddDomainIntegrator(hms_blfi); Hs_form_->AddDomainIntegrator(hds_blfi); @@ -1434,6 +1539,26 @@ void Tomboulides::step() { }); } + if (sw_stab_) { + /* + for (int i = 0; i < dim_; i++) { + setScalarFromVector(u_vec_, i, &tmpR0a_); + streamwiseDiffusion(tmpR0a_, tmpR0b_); + setVectorFromScalar(tmpR0b_, i, &swDiff_vec_); + } + */ + Mv_stab_op_->Mult(gradU_, tmpR0b_); + setVectorFromScalar(tmpR0b_, 0, &swDiff_vec_); + Mv_stab_op_->Mult(gradV_, tmpR0b_); + setVectorFromScalar(tmpR0b_, 1, &swDiff_vec_); + if (dim_ == 3) { + Mv_stab_op_->Mult(gradW_, tmpR0b_); + setVectorFromScalar(tmpR0b_, 2, &swDiff_vec_); + } + Mv_rho_inv_->Mult(swDiff_vec_, tmpR1_); + pp_div_vec_ += tmpR1_; + } + // Add ustar/dt contribution pp_div_vec_ += ustar_vec_; diff --git a/src/tomboulides.hpp b/src/tomboulides.hpp index 1463b426e..4a1703ff3 100644 --- a/src/tomboulides.hpp +++ b/src/tomboulides.hpp @@ -181,6 +181,9 @@ class Tomboulides final : public FlowBase { // reference here. const temporalSchemeCoefficients &coeff_; + // streamwise-stabilization + bool sw_stab_; + StopWatch sw_press_, sw_helm_; std::string ic_string_; @@ -234,6 +237,8 @@ class Tomboulides final : public FlowBase { mfem::ParGridFunction *resp_gf_ = nullptr; mfem::ParGridFunction *pp_div_rad_comp_gf_ = nullptr; + mfem::ParGridFunction *gridScale_gf_ = nullptr; + /// Swirl mfem::ParGridFunction *utheta_gf_ = nullptr; mfem::ParGridFunction *utheta_next_gf_ = nullptr; @@ -286,6 +291,19 @@ class Tomboulides final : public FlowBase { mfem::VectorArrayCoefficient *utheta_vec_coeff_ = nullptr; mfem::InnerProductCoefficient *swirl_var_viscosity_coeff_ = nullptr; + mfem::VectorMagnitudeCoefficient *umag_coeff_ = nullptr; + mfem::GridFunctionCoefficient *gscale_coeff_ = nullptr; + mfem::PowerCoefficient *visc_inv_coeff_ = nullptr; + mfem::ProductCoefficient *reh1_coeff_ = nullptr; + mfem::ProductCoefficient *reh2_coeff_ = nullptr; + mfem::ProductCoefficient *Reh_coeff_ = nullptr; + mfem::TransformedCoefficient *csupg_coeff_ = nullptr; + mfem::ProductCoefficient *uw1_coeff_ = nullptr; + mfem::ProductCoefficient *uw2_coeff_ = nullptr; + mfem::ProductCoefficient *upwind_coeff_ = nullptr; + mfem::TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; + mfem::ScalarMatrixProductCoefficient *supg_coeff_ = nullptr; + // mfem "form" objects used to create operators mfem::ParBilinearForm *L_iorho_form_ = nullptr; // \int (1/\rho) \nabla \phi_i \cdot \nabla \phi_j mfem::ParLinearForm *forcing_form_ = nullptr; // \int \phi_i f @@ -309,6 +327,9 @@ class Tomboulides final : public FlowBase { mfem::ParLinearForm *rho_ur_ut_form_ = nullptr; mfem::ParLinearForm *swirl_var_viscosity_form_ = nullptr; + // streamwise stability + mfem::ParBilinearForm *Mv_stab_form_ = nullptr; + // mfem operator objects mfem::OperatorHandle L_iorho_op_; mfem::OperatorHandle Ms_op_; @@ -321,6 +342,9 @@ class Tomboulides final : public FlowBase { mfem::OperatorHandle Hs_op_; mfem::OperatorHandle As_op_; + // streamwise stability + mfem::OperatorHandle Mv_stab_op_; + // solver objects mfem::ParLORDiscretization *L_iorho_lor_ = nullptr; mfem::HypreBoomerAMG *L_iorho_inv_pc_ = nullptr; @@ -374,6 +398,9 @@ class Tomboulides final : public FlowBase { mfem::Vector utheta_m2_vec_; mfem::Vector utheta_next_vec_; + mfem::Vector tmpR0b_; + mfem::Vector swDiff_vec_; + // miscellaneous double volume_; mfem::ParLinearForm *mass_lform_ = nullptr; @@ -392,7 +419,8 @@ class Tomboulides final : public FlowBase { public: /// Constructor - Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalSchemeCoefficients &coeff, TPS::Tps *tps = nullptr); + Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalSchemeCoefficients &coeff, + mfem::ParGridFunction *gridScale, TPS::Tps *tps = nullptr); /// Destructor ~Tomboulides() final; diff --git a/test/inputs/input.supgBox.ini b/test/inputs/input.supgBox.ini index d6f7bbbaf..3548e8f18 100644 --- a/test/inputs/input.supgBox.ini +++ b/test/inputs/input.supgBox.ini @@ -6,8 +6,8 @@ mesh = meshes/boxStab.msh order = 1 nFilter = 0 filterWeight = 1.0 -maxIters = 1000 -outputFreq = 1000 +maxIters = 10000 +outputFreq = 500 fluid = dry_air refLength = 1.0 equation_system = navier-stokes @@ -35,7 +35,7 @@ Prandtl = 0.72 #msolve-rtol = 1.0e-10 #hsolve-maxIters = 2000 #msolve-maxIters = 2000 -streamwise-stabilization = false +streamwise-stabilization = true #Reh_offset = 1.0 #Reh_factor = 0.01 @@ -44,7 +44,7 @@ nonzero-flow = true nonzero-vel = '1.0 0.0 0.0' [io] -outdirBase = output_nostab1L +outdirBase = output_wstab1T #enableRestart = True #restartMode = variableP From 5a97641548481ed942be8e814ca6c788ffb4a33d Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Mon, 30 Jun 2025 16:06:13 -0700 Subject: [PATCH 05/13] fixed tomboulides bug, added diffusion to Qt forms, still no help to torch --- src/calorically_perfect.cpp | 16 +++++++++------- src/reactingFlow.cpp | 15 ++++++++------- src/tomboulides.cpp | 15 ++++++++++++--- src/tomboulides.hpp | 1 + 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/calorically_perfect.cpp b/src/calorically_perfect.cpp index 52aef1c90..41fc60e9e 100644 --- a/src/calorically_perfect.cpp +++ b/src/calorically_perfect.cpp @@ -630,13 +630,15 @@ void CaloricallyPerfectThermoChem::initializeOperators() { } LQ_form_->AddDomainIntegrator(lqd_blfi); - // auto *slqd_blfi = new DiffusionIntegrator(*supg_coeff_); - // if (sw_stab_) - // // SUPG diffusion - // // if (numerical_integ_) { - // // slqd_blfi->SetIntRule(&ir_di); - // // } - // LQ_form_->AddDomainIntegrator(slqd_blfi); + DiffusionIntegrator *slqd_blfi; + if (sw_stab_) + slqd_blfi = new DiffusionIntegrator(*supg_coeff_); + // SUPG diffusion + // if (numerical_integ_) { + // slqd_blfi->SetIntRule(&ir_di); + // } + LQ_form_->AddDomainIntegrator(slqd_blfi); + if (partial_assembly_) { LQ_form_->SetAssemblyLevel(AssemblyLevel::PARTIAL); diff --git a/src/reactingFlow.cpp b/src/reactingFlow.cpp index 477eb13bd..96e761143 100644 --- a/src/reactingFlow.cpp +++ b/src/reactingFlow.cpp @@ -1485,13 +1485,14 @@ void ReactingFlow::initializeOperators() { } LQ_form_->AddDomainIntegrator(lqd_blfi); - // auto *slqd_blfi = new DiffusionIntegrator(*supg_coeff_); - // if (sw_stab_) - // // SUPG diffusion - // // if (numerical_integ_) { - // // slqd_blfi->SetIntRule(&ir_di); - // // } - // LQ_form_->AddDomainIntegrator(slqd_blfi); + DiffusionIntegrator *slqd_blfi; + if (sw_stab_) + slqd_blfi = new DiffusionIntegrator(*supg_coeff_); + // SUPG diffusion + // if (numerical_integ_) { + // slqd_blfi->SetIntRule(&ir_di); + // } + LQ_form_->AddDomainIntegrator(slqd_blfi); if (partial_assembly_) { LQ_form_->SetAssemblyLevel(AssemblyLevel::PARTIAL); diff --git a/src/tomboulides.cpp b/src/tomboulides.cpp index 3902da648..edab3710a 100644 --- a/src/tomboulides.cpp +++ b/src/tomboulides.cpp @@ -210,7 +210,7 @@ Tomboulides::~Tomboulides() { delete rho_over_dt_coeff_; delete iorho_coeff_; delete rho_coeff_; - + delete umag_coeff_; delete gscale_coeff_; delete visc_inv_coeff_; @@ -223,6 +223,7 @@ Tomboulides::~Tomboulides() { delete upwind_coeff_; delete swdiff_coeff_; delete supg_coeff_; + delete visc_coeff_; // objects allocated by initalizeSelf if (axisym_) delete gravity_vec_; @@ -685,9 +686,11 @@ void Tomboulides::initializeOperators() { // artifical diffusion coefficients if(sw_stab_) { + visc_coeff_ = new GridFunctionCoefficient(thermo_interface_->viscosity); umag_coeff_ = new VectorMagnitudeCoefficient(*u_next_coeff_); gscale_coeff_ = new GridFunctionCoefficient(gridScale_gf_); - visc_inv_coeff_ = new PowerCoefficient(*mu_coeff_, -1.0); + visc_inv_coeff_ = new PowerCoefficient(*visc_coeff_, -1.0); + // visc_inv_coeff_ = new PowerCoefficient(*mu_coeff_, -1.0); // compute Reh reh1_coeff_ = new ProductCoefficient(*rho_coeff_, *visc_inv_coeff_); @@ -1540,6 +1543,12 @@ void Tomboulides::step() { } if (sw_stab_) { + + // Update matrix + Array empty; + Mv_stab_form_->Update(); + Mv_stab_form_->Assemble(); + Mv_stab_form_->FormSystemMatrix(empty, Mv_stab_op_); /* for (int i = 0; i < dim_; i++) { setScalarFromVector(u_vec_, i, &tmpR0a_); @@ -1558,7 +1567,7 @@ void Tomboulides::step() { Mv_rho_inv_->Mult(swDiff_vec_, tmpR1_); pp_div_vec_ += tmpR1_; } - + // printf("%f\n", tmpR1_.Norml2()); // Add ustar/dt contribution pp_div_vec_ += ustar_vec_; diff --git a/src/tomboulides.hpp b/src/tomboulides.hpp index 4a1703ff3..2a4016097 100644 --- a/src/tomboulides.hpp +++ b/src/tomboulides.hpp @@ -303,6 +303,7 @@ class Tomboulides final : public FlowBase { mfem::ProductCoefficient *upwind_coeff_ = nullptr; mfem::TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; mfem::ScalarMatrixProductCoefficient *supg_coeff_ = nullptr; + mfem::GridFunctionCoefficient *visc_coeff_ = nullptr; // mfem "form" objects used to create operators mfem::ParBilinearForm *L_iorho_form_ = nullptr; // \int (1/\rho) \nabla \phi_i \cdot \nabla \phi_j From 119bcccb1a69b623c3467eca8bc38c80202a12b0 Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Tue, 15 Jul 2025 14:56:38 -0700 Subject: [PATCH 06/13] fixes, now starting chemistry ramping --- src/reactingFlow.cpp | 91 ++++++++++++++++++++++++++++++++++++++++---- src/reactingFlow.hpp | 13 +++++++ 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/reactingFlow.cpp b/src/reactingFlow.cpp index 96e761143..29bebd7ad 100644 --- a/src/reactingFlow.cpp +++ b/src/reactingFlow.cpp @@ -542,6 +542,11 @@ ReactingFlow::ReactingFlow(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, tem // artificial diffusion (SUPG) tpsP_->getInput("loMach/reactingFlow/streamwise-stabilization", sw_stab_, false); + + // NOTE: clipping Qt for debugging + tpsP_->getInput("loMach/reactingFlow/clip-qt", clip_qt_, false); + + } ReactingFlow::~ReactingFlow() { @@ -635,6 +640,7 @@ ReactingFlow::~ReactingFlow() { delete upwind_coeff_; delete swdiff_coeff_; delete supg_coeff_; + delete supg_cp_coeff_; // allocated in initializeSelf delete vfes_; @@ -844,6 +850,22 @@ void ReactingFlow::initializeSelf() { Mmix_gf_.SetSpace(sfes_); Rmix_gf_.SetSpace(sfes_); + // qt rhs visualization + rhsqt_bd_.SetSpace(sfes_); + rhsqt_fo_.SetSpace(sfes_); + rhsqt_jh_.SetSpace(sfes_); + rhsqt_hf_.SetSpace(sfes_); + rhsqt_sd_.SetSpace(sfes_); + rhsqt_total_.SetSpace(sfes_); + Xqt_gf_.SetSpace(sfes_); + rhsqt_bd_ = 0.0; + rhsqt_fo_ = 0.0; + rhsqt_jh_ = 0.0; + rhsqt_hf_ = 0.0; + rhsqt_sd_ = 0.0; + rhsqt_total_ = 0.0; + Xqt_gf_ = 0.0; + // exports toFlow_interface_.density = &rn_gf_; toFlow_interface_.viscosity = &visc_gf_; @@ -1159,6 +1181,7 @@ void ReactingFlow::initializeOperators() { swdiff_coeff_ = new TransformedMatrixVectorCoefficient(un_next_coeff_, &streamwiseTensor); supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); + supg_cp_coeff_ = new ScalarMatrixProductCoefficient(*cpMix_coeff_, *supg_coeff_); } else { // compute upwind magnitude @@ -1170,6 +1193,7 @@ void ReactingFlow::initializeOperators() { swdiff_coeff_ = new TransformedMatrixVectorCoefficient(un_next_coeff_, &streamwiseTensor); supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); + supg_cp_coeff_ = new ScalarMatrixProductCoefficient(*cpMix_coeff_, *supg_coeff_); } } @@ -1283,7 +1307,7 @@ void ReactingFlow::initializeOperators() { DiffusionIntegrator *sdt_blfi; if (sw_stab_) { // auto *sdt_blfi = new DiffusionIntegrator(*supg_coeff_); - sdt_blfi = new DiffusionIntegrator(*supg_coeff_); + sdt_blfi = new DiffusionIntegrator(*supg_cp_coeff_); // SUPG diffusion // if (numerical_integ_) { // sdt_blfi->SetIntRule(&ir_di); @@ -2371,7 +2395,16 @@ void ReactingFlow::initializeViz(ParaViewDataCollection &pvdc) { pvdc.RegisterField("epsilon_rad", &radiation_sink_gf_); pvdc.RegisterField("weff", &weff_gf_); pvdc.RegisterField("emission", &emission_gf_); - + + // diagnose Qt issues, rhs contributions + pvdc.RegisterField("rhsqt_bd", &rhsqt_bd_); //boundary terms + pvdc.RegisterField("rhsqt_fo", &rhsqt_fo_); //bilinear form + pvdc.RegisterField("rhsqt_jh", &rhsqt_jh_); //joule heating + pvdc.RegisterField("rhsqt_hf", &rhsqt_hf_); //heat of formation + pvdc.RegisterField("rhsqt_sd", &rhsqt_sd_); ///species-temp diff + pvdc.RegisterField("rhsqt_total", &rhsqt_total_); + pvdc.RegisterField("Xqt", &Xqt_gf_); + vizSpecFields_.clear(); vizSpecNames_.clear(); for (int sp = 0; sp < nSpecies_; sp++) { @@ -2819,43 +2852,85 @@ void ReactingFlow::AddQtDirichletBC(ScalarFuncT *f, Array &attr) { void ReactingFlow::computeQtTO() { tmpR0_ = 0.0; + rhsqt_bd_ = 0.0; + rhsqt_fo_ = 0.0; + rhsqt_jh_ = 0.0; + rhsqt_hf_ = 0.0; + rhsqt_sd_ = 0.0; + rhsqt_total_ = 0.0; + Xqt_gf_ = 0.0; LQ_bdry_->Update(); LQ_bdry_->Assemble(); - LQ_bdry_->ParallelAssemble(tmpR0_); - tmpR0_.Neg(); + // LQ_bdry_->ParallelAssemble(tmpR0_); + // tmpR0_.Neg(); + LQ_bdry_->ParallelAssemble(rhsqt_bd_); + rhsqt_bd_.Neg(); + tmpR0_ += rhsqt_bd_; + // printf("%f\n", tmpR0_.Norml2()); Array empty; LQ_form_->Update(); LQ_form_->Assemble(); LQ_form_->FormSystemMatrix(empty, LQ_); - LQ_->AddMult(Tn_next_, tmpR0_); // tmpR0_ += LQ{Tn_next} + // LQ_->AddMult(Tn_next_, tmpR0_); // tmpR0_ += LQ{Tn_next} + LQ_->Mult(Tn_next_, rhsqt_fo_); // tmpR0_ += LQ{Tn_next} + tmpR0_ += rhsqt_fo_; + // printf("%f\n", tmpR0_.Norml2()); // Joule heating (and radiation sink) jh_form_->Update(); jh_form_->Assemble(); jh_form_->ParallelAssemble(jh_); tmpR0_ -= jh_; - + rhsqt_jh_.SetFromTrueDofs(jh_); + rhsqt_jh_.Neg(); + // printf("%f\n", tmpR0_.Norml2()); + // heat of formation - Ms_->AddMult(hw_, tmpR0_, -1.0); + // Ms_->AddMult(hw_, tmpR0_, -1.0); + Ms_->Mult(hw_, rhsqt_hf_); + rhsqt_hf_.Neg(); + tmpR0_ += rhsqt_hf_; + // printf("%f\n", tmpR0_.Norml2()); // species-temp diffusion term, already in int-weak form tmpR0_.Add(-1.0, crossDiff_); + rhsqt_sd_.SetFromTrueDofs(crossDiff_); + rhsqt_sd_.Neg(); + // printf("%f\n", tmpR0_.Norml2()); sfes_->GetRestrictionMatrix()->MultTranspose(tmpR0_, resT_gf_); + rhsqt_total_.SetFromTrueDofs(resT_gf_); Qt_ = 0.0; Qt_gf_.SetFromTrueDofs(Qt_); Vector Xqt, Bqt; Mq_form_->FormLinearSystem(Qt_ess_tdof_, Qt_gf_, resT_gf_, Mq_, Xqt, Bqt, 1); - + // std::ofstream myfile("mq_mat"); + // Mq_->PrintMatlab(myfile); MqInv_->Mult(Bqt, Xqt); + // std::ofstream myfile("mqi_mat"); + // MqInv_->PrintMatlab(myfile); Mq_form_->RecoverFEMSolution(Xqt, resT_gf_, Qt_gf_); + Xqt_gf_.SetFromTrueDofs(Xqt); Qt_gf_ *= Rmix_gf_; Qt_gf_ /= CpMix_gf_; Qt_gf_ /= thermo_pressure_; + + // NOTE: clipping for diagnosis, goes both directions for now + double clip_val; + if (clip_qt_) { + tpsP_->getInput("loMach/reactingFlow/clip-qt-value", clip_val, 50000.); + + for (int n = 0; n < sfes_->GetNDofs(); n++) { + // Qt_gf_[n] = std::clamp(Qt_gf_[n], -clip_val, clip_val) + Qt_gf_[n] = std::min(clip_val, std::max(-clip_val, Qt_gf_[n])); + } + + } + Qt_gf_.Neg(); } diff --git a/src/reactingFlow.hpp b/src/reactingFlow.hpp index 4813f2147..d1d1ea514 100644 --- a/src/reactingFlow.hpp +++ b/src/reactingFlow.hpp @@ -155,6 +155,9 @@ class ReactingFlow : public ThermoChemModelBase { // streamwise-stabilization bool sw_stab_; + + // clip qt for diagnosis + bool clip_qt_; // FEM related fields and objects @@ -208,6 +211,15 @@ class ReactingFlow : public ThermoChemModelBase { ParGridFunction sigma_gf_; ParGridFunction jh_gf_; + // viz for qt rhs + ParGridFunction rhsqt_bd_; + ParGridFunction rhsqt_fo_; + ParGridFunction rhsqt_jh_; + ParGridFunction rhsqt_hf_; + ParGridFunction rhsqt_sd_; + ParGridFunction rhsqt_total_; + ParGridFunction Xqt_gf_; + // ParGridFunction *buffer_tInlet_ = nullptr; GridFunctionCoefficient *temperature_bc_field_ = nullptr; @@ -263,6 +275,7 @@ class ReactingFlow : public ThermoChemModelBase { ProductCoefficient *upwind_coeff_ = nullptr; TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; ScalarMatrixProductCoefficient *supg_coeff_ = nullptr; + ScalarMatrixProductCoefficient *supg_cp_coeff_ = nullptr; // operators and solvers ParBilinearForm *At_form_ = nullptr; From cd202032cb24dcf8874d3a8d265fb240f33fb06f Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Mon, 21 Jul 2025 16:34:46 -0700 Subject: [PATCH 07/13] streamwise stabilization implemented for calorically_perfect, reactingFlow, and tomboulides --- src/calorically_perfect.cpp | 9 +- src/reactingFlow.cpp | 114 +++++++++--------- src/reactingFlow.hpp | 16 +-- src/tomboulides.cpp | 4 +- src/utils.cpp | 2 +- test/Makefile.am | 8 +- test/inputs/input.stabChan.ini | 77 ++++++++++++ test/lomach-chan-stab.test | 30 +++++ test/meshes/channel182p4_25x9p4_THIN.msh | 3 + test/ref_solns/stabChan/restart_output.sol.h5 | Bin 0 -> 150168 bytes 10 files changed, 187 insertions(+), 76 deletions(-) create mode 100644 test/inputs/input.stabChan.ini create mode 100755 test/lomach-chan-stab.test create mode 100644 test/meshes/channel182p4_25x9p4_THIN.msh create mode 100644 test/ref_solns/stabChan/restart_output.sol.h5 diff --git a/src/calorically_perfect.cpp b/src/calorically_perfect.cpp index 41fc60e9e..cb81e3452 100644 --- a/src/calorically_perfect.cpp +++ b/src/calorically_perfect.cpp @@ -630,15 +630,16 @@ void CaloricallyPerfectThermoChem::initializeOperators() { } LQ_form_->AddDomainIntegrator(lqd_blfi); - DiffusionIntegrator *slqd_blfi; - if (sw_stab_) - slqd_blfi = new DiffusionIntegrator(*supg_coeff_); + // DiffusionIntegrator *slqd_blfi; + if (sw_stab_) { + // slqd_blfi = new DiffusionIntegrator(*supg_coeff_); + auto *slqd_blfi = new DiffusionIntegrator(*supg_coeff_); // SUPG diffusion // if (numerical_integ_) { // slqd_blfi->SetIntRule(&ir_di); // } LQ_form_->AddDomainIntegrator(slqd_blfi); - + } if (partial_assembly_) { LQ_form_->SetAssemblyLevel(AssemblyLevel::PARTIAL); diff --git a/src/reactingFlow.cpp b/src/reactingFlow.cpp index 29bebd7ad..5e626c694 100644 --- a/src/reactingFlow.cpp +++ b/src/reactingFlow.cpp @@ -544,7 +544,7 @@ ReactingFlow::ReactingFlow(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, tem tpsP_->getInput("loMach/reactingFlow/streamwise-stabilization", sw_stab_, false); // NOTE: clipping Qt for debugging - tpsP_->getInput("loMach/reactingFlow/clip-qt", clip_qt_, false); + // tpsP_->getInput("loMach/reactingFlow/clip-qt", clip_qt_, false); } @@ -851,20 +851,20 @@ void ReactingFlow::initializeSelf() { Rmix_gf_.SetSpace(sfes_); // qt rhs visualization - rhsqt_bd_.SetSpace(sfes_); - rhsqt_fo_.SetSpace(sfes_); - rhsqt_jh_.SetSpace(sfes_); - rhsqt_hf_.SetSpace(sfes_); - rhsqt_sd_.SetSpace(sfes_); - rhsqt_total_.SetSpace(sfes_); - Xqt_gf_.SetSpace(sfes_); - rhsqt_bd_ = 0.0; - rhsqt_fo_ = 0.0; - rhsqt_jh_ = 0.0; - rhsqt_hf_ = 0.0; - rhsqt_sd_ = 0.0; - rhsqt_total_ = 0.0; - Xqt_gf_ = 0.0; + // rhsqt_bd_.SetSpace(sfes_); + // rhsqt_fo_.SetSpace(sfes_); + // rhsqt_jh_.SetSpace(sfes_); + // rhsqt_hf_.SetSpace(sfes_); + // rhsqt_sd_.SetSpace(sfes_); + // rhsqt_total_.SetSpace(sfes_); + // Xqt_gf_.SetSpace(sfes_); + // rhsqt_bd_ = 0.0; + // rhsqt_fo_ = 0.0; + // rhsqt_jh_ = 0.0; + // rhsqt_hf_ = 0.0; + // rhsqt_sd_ = 0.0; + // rhsqt_total_ = 0.0; + // Xqt_gf_ = 0.0; // exports toFlow_interface_.density = &rn_gf_; @@ -2397,13 +2397,13 @@ void ReactingFlow::initializeViz(ParaViewDataCollection &pvdc) { pvdc.RegisterField("emission", &emission_gf_); // diagnose Qt issues, rhs contributions - pvdc.RegisterField("rhsqt_bd", &rhsqt_bd_); //boundary terms - pvdc.RegisterField("rhsqt_fo", &rhsqt_fo_); //bilinear form - pvdc.RegisterField("rhsqt_jh", &rhsqt_jh_); //joule heating - pvdc.RegisterField("rhsqt_hf", &rhsqt_hf_); //heat of formation - pvdc.RegisterField("rhsqt_sd", &rhsqt_sd_); ///species-temp diff - pvdc.RegisterField("rhsqt_total", &rhsqt_total_); - pvdc.RegisterField("Xqt", &Xqt_gf_); + // pvdc.RegisterField("rhsqt_bd", &rhsqt_bd_); //boundary terms + // pvdc.RegisterField("rhsqt_fo", &rhsqt_fo_); //bilinear form + // pvdc.RegisterField("rhsqt_jh", &rhsqt_jh_); //joule heating + // pvdc.RegisterField("rhsqt_hf", &rhsqt_hf_); //heat of formation + // pvdc.RegisterField("rhsqt_sd", &rhsqt_sd_); ///species-temp diff + // pvdc.RegisterField("rhsqt_total", &rhsqt_total_); + // pvdc.RegisterField("Xqt", &Xqt_gf_); vizSpecFields_.clear(); vizSpecNames_.clear(); @@ -2852,29 +2852,29 @@ void ReactingFlow::AddQtDirichletBC(ScalarFuncT *f, Array &attr) { void ReactingFlow::computeQtTO() { tmpR0_ = 0.0; - rhsqt_bd_ = 0.0; - rhsqt_fo_ = 0.0; - rhsqt_jh_ = 0.0; - rhsqt_hf_ = 0.0; - rhsqt_sd_ = 0.0; - rhsqt_total_ = 0.0; - Xqt_gf_ = 0.0; + // rhsqt_bd_ = 0.0; + // rhsqt_fo_ = 0.0; + // rhsqt_jh_ = 0.0; + // rhsqt_hf_ = 0.0; + // rhsqt_sd_ = 0.0; + // rhsqt_total_ = 0.0; + // Xqt_gf_ = 0.0; LQ_bdry_->Update(); LQ_bdry_->Assemble(); - // LQ_bdry_->ParallelAssemble(tmpR0_); - // tmpR0_.Neg(); - LQ_bdry_->ParallelAssemble(rhsqt_bd_); - rhsqt_bd_.Neg(); - tmpR0_ += rhsqt_bd_; + LQ_bdry_->ParallelAssemble(tmpR0_); + tmpR0_.Neg(); + // LQ_bdry_->ParallelAssemble(rhsqt_bd_); + // rhsqt_bd_.Neg(); + // tmpR0_ += rhsqt_bd_; // printf("%f\n", tmpR0_.Norml2()); Array empty; LQ_form_->Update(); LQ_form_->Assemble(); LQ_form_->FormSystemMatrix(empty, LQ_); - // LQ_->AddMult(Tn_next_, tmpR0_); // tmpR0_ += LQ{Tn_next} - LQ_->Mult(Tn_next_, rhsqt_fo_); // tmpR0_ += LQ{Tn_next} - tmpR0_ += rhsqt_fo_; + LQ_->AddMult(Tn_next_, tmpR0_); // tmpR0_ += LQ{Tn_next} + // LQ_->Mult(Tn_next_, rhsqt_fo_); // tmpR0_ += LQ{Tn_next} + // tmpR0_ += rhsqt_fo_; // printf("%f\n", tmpR0_.Norml2()); // Joule heating (and radiation sink) @@ -2882,54 +2882,50 @@ void ReactingFlow::computeQtTO() { jh_form_->Assemble(); jh_form_->ParallelAssemble(jh_); tmpR0_ -= jh_; - rhsqt_jh_.SetFromTrueDofs(jh_); - rhsqt_jh_.Neg(); + // rhsqt_jh_.SetFromTrueDofs(jh_); + // rhsqt_jh_.Neg(); // printf("%f\n", tmpR0_.Norml2()); // heat of formation - // Ms_->AddMult(hw_, tmpR0_, -1.0); - Ms_->Mult(hw_, rhsqt_hf_); - rhsqt_hf_.Neg(); - tmpR0_ += rhsqt_hf_; + Ms_->AddMult(hw_, tmpR0_, -1.0); + // Ms_->Mult(hw_, rhsqt_hf_); + // rhsqt_hf_.Neg(); + // tmpR0_ += rhsqt_hf_; // printf("%f\n", tmpR0_.Norml2()); // species-temp diffusion term, already in int-weak form tmpR0_.Add(-1.0, crossDiff_); - rhsqt_sd_.SetFromTrueDofs(crossDiff_); - rhsqt_sd_.Neg(); + // rhsqt_sd_.SetFromTrueDofs(crossDiff_); + // rhsqt_sd_.Neg(); // printf("%f\n", tmpR0_.Norml2()); sfes_->GetRestrictionMatrix()->MultTranspose(tmpR0_, resT_gf_); - rhsqt_total_.SetFromTrueDofs(resT_gf_); + // rhsqt_total_.SetFromTrueDofs(resT_gf_); Qt_ = 0.0; Qt_gf_.SetFromTrueDofs(Qt_); Vector Xqt, Bqt; Mq_form_->FormLinearSystem(Qt_ess_tdof_, Qt_gf_, resT_gf_, Mq_, Xqt, Bqt, 1); - // std::ofstream myfile("mq_mat"); - // Mq_->PrintMatlab(myfile); MqInv_->Mult(Bqt, Xqt); - // std::ofstream myfile("mqi_mat"); - // MqInv_->PrintMatlab(myfile); Mq_form_->RecoverFEMSolution(Xqt, resT_gf_, Qt_gf_); - Xqt_gf_.SetFromTrueDofs(Xqt); + // Xqt_gf_.SetFromTrueDofs(Xqt); Qt_gf_ *= Rmix_gf_; Qt_gf_ /= CpMix_gf_; Qt_gf_ /= thermo_pressure_; // NOTE: clipping for diagnosis, goes both directions for now - double clip_val; - if (clip_qt_) { - tpsP_->getInput("loMach/reactingFlow/clip-qt-value", clip_val, 50000.); + // double clip_val; + // if (clip_qt_) { + // tpsP_->getInput("loMach/reactingFlow/clip-qt-value", clip_val, 50000.); - for (int n = 0; n < sfes_->GetNDofs(); n++) { - // Qt_gf_[n] = std::clamp(Qt_gf_[n], -clip_val, clip_val) - Qt_gf_[n] = std::min(clip_val, std::max(-clip_val, Qt_gf_[n])); - } + // for (int n = 0; n < sfes_->GetNDofs(); n++) { + // // Qt_gf_[n] = std::clamp(Qt_gf_[n], -clip_val, clip_val) + // Qt_gf_[n] = std::min(clip_val, std::max(-clip_val, Qt_gf_[n])); + // } - } + // } Qt_gf_.Neg(); } diff --git a/src/reactingFlow.hpp b/src/reactingFlow.hpp index d1d1ea514..de96897b0 100644 --- a/src/reactingFlow.hpp +++ b/src/reactingFlow.hpp @@ -157,7 +157,7 @@ class ReactingFlow : public ThermoChemModelBase { bool sw_stab_; // clip qt for diagnosis - bool clip_qt_; + // bool clip_qt_; // FEM related fields and objects @@ -212,13 +212,13 @@ class ReactingFlow : public ThermoChemModelBase { ParGridFunction jh_gf_; // viz for qt rhs - ParGridFunction rhsqt_bd_; - ParGridFunction rhsqt_fo_; - ParGridFunction rhsqt_jh_; - ParGridFunction rhsqt_hf_; - ParGridFunction rhsqt_sd_; - ParGridFunction rhsqt_total_; - ParGridFunction Xqt_gf_; + // ParGridFunction rhsqt_bd_; + // ParGridFunction rhsqt_fo_; + // ParGridFunction rhsqt_jh_; + // ParGridFunction rhsqt_hf_; + // ParGridFunction rhsqt_sd_; + // ParGridFunction rhsqt_total_; + // ParGridFunction Xqt_gf_; // ParGridFunction *buffer_tInlet_ = nullptr; GridFunctionCoefficient *temperature_bc_field_ = nullptr; diff --git a/src/tomboulides.cpp b/src/tomboulides.cpp index edab3710a..090ea11a4 100644 --- a/src/tomboulides.cpp +++ b/src/tomboulides.cpp @@ -649,6 +649,8 @@ void Tomboulides::initializeOperators() { S_poisson_coeff_ = new VectorSumCoefficient(*twoS_gradmu_coeff_, *gradmu_Qt_coeff_, 1.0, -2. / 3); S_mom_coeff_ = new VectorSumCoefficient(*graduT_gradmu_coeff_, *gradmu_Qt_coeff_, 1.0, -1.0); + u_next_coeff_ = new VectorGridFunctionCoefficient(u_next_gf_); + // Coefficients for axisymmetric if (axisym_) { rad_rho_coeff_ = new ProductCoefficient(radius_coeff, *rho_coeff_); @@ -669,7 +671,6 @@ void Tomboulides::initializeOperators() { ur_conv_forcing_coeff_ = new VectorArrayCoefficient(2); ur_conv_forcing_coeff_->Set(0, utheta2_coeff_); - u_next_coeff_ = new VectorGridFunctionCoefficient(u_next_gf_); rad_rhou_coeff_ = new ScalarVectorProductCoefficient(*rad_rho_coeff_, *u_next_coeff_); u_next_rad_coeff_ = new GridFunctionCoefficient(u_next_rad_comp_gf_); @@ -689,6 +690,7 @@ void Tomboulides::initializeOperators() { visc_coeff_ = new GridFunctionCoefficient(thermo_interface_->viscosity); umag_coeff_ = new VectorMagnitudeCoefficient(*u_next_coeff_); gscale_coeff_ = new GridFunctionCoefficient(gridScale_gf_); + visc_inv_coeff_ = new PowerCoefficient(*visc_coeff_, -1.0); // visc_inv_coeff_ = new PowerCoefficient(*mu_coeff_, -1.0); diff --git a/src/utils.cpp b/src/utils.cpp index 0cbd3b78e..69d53643e 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1427,7 +1427,7 @@ void GradientVectorGridFunctionCoefficient::Eval(DenseMatrix &G, ElementTransfor VectorMagnitudeCoefficient::VectorMagnitudeCoefficient(VectorCoefficient &A) - : a(&A), va(A.GetVDim()) { } + : a(&A), va(A.GetVDim()) {} void VectorMagnitudeCoefficient::SetTime(double t) { diff --git a/test/Makefile.am b/test/Makefile.am index 56e794a18..73ccebf53 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -50,8 +50,9 @@ EXTRA_DIST = tap-driver.sh test_tps_splitcomm.py soln_differ inputs meshes lte- tabulated.test lte_mixture.test distance_fcn.test \ sgsSmag.test sgsSigma.test heatEq.test sponge.test plate.test pipe.mix.test lte2noneq-restart.test \ coupled-3d.interface.test plasma.axisym.test plasma.axisym.lte1d.test \ - lomach-flow.test lomach-lequere.test interpInlet.test sgsLoMach.test autoPeriodic.test aveLoMach.test \ - reactFlow-binDiff.test reactFlow-singleRx.test reactFlow-table.test radiativeDecay.test + lomach-flow.test lomach-lequere.test interpInlet.test sgsLoMach.test autoPeriodic.test aveLoMach.test \ + reactFlow-binDiff.test reactFlow-singleRx.test reactFlow-table.test radiativeDecay.test \ + lomach-chan-stab.test TESTS = vpath.sh @@ -125,7 +126,8 @@ TESTS += cyl3d.test \ reactFlow-binDiff.test \ reactFlow-singleRx.test \ reactFlow-table.test \ - radiativeDecay.test + radiativeDecay.test \ + lomach-chan-stab.test if PYTHON_ENABLED TESTS += cyl3d.python.test \ diff --git a/test/inputs/input.stabChan.ini b/test/inputs/input.stabChan.ini new file mode 100644 index 000000000..cdfab8b28 --- /dev/null +++ b/test/inputs/input.stabChan.ini @@ -0,0 +1,77 @@ +[solver] +type = loMach + +[loMach] +mesh = meshes/channel182p4_25x9p4_THIN.msh +order = 1 +nFilter = 0 +filterWeight = 1.0 +maxIters = 100 +outputFreq = 100 +fluid = dry_air +refLength = 1.0 +equation_system = navier-stokes +enablePressureForcing = True +pressureGrad = '0.00005372 0.0 0.0' +enableGravity = False +gravity = '0.0 0.0 0.0' +openSystem = False +sgsModel = none +flow-solver = tomboulides +thermo-solver = calorically-perfect + +[loMach/calperfect] +viscosity-model = sutherland +sutherland/mu0 = 1.68e-5 +sutherland/T0 = 273.0 +sutherland/S0 = 110.4 +numerical-integ = false +Prandtl = 0.72 +#ic = channel +streamwise-stabilization = true + +[loMach/tomboulides] +ic = channel +numerical-integ = false +streamwise-stabilization = true + +[io] +outdirBase = output +#enableRestart = True +#restartMode = variableP + +[time] +integrator = curlcurl +cfl = 0.4 +dt_initial = 1.0e-4 +dtFactor = 0.01 +#dt_fixed = 1.0e-4 +bdfOrder = 2 + +[spongeMultiplier] +uniform = true +uniformMult = 1.0 + +[initialConditions] +rho = 1.0 +rhoU = 1.0 +rhoV = 0.0 +rhoW = 0.0 +temperature = 300.0 +pressure = 101325.0 + +[boundaryConditions/wall1] +patch = 4 +type = viscous_isothermal +temperature = 300 +velocity = '0.0 0.0 0.0' + +[boundaryConditions] +numWalls = 1 +numInlets = 0 +numOutlets = 0 + +[periodicity] +enablePeriodic = True +periodicX = True +periodicZ = True \ No newline at end of file diff --git a/test/lomach-chan-stab.test b/test/lomach-chan-stab.test new file mode 100755 index 000000000..6ee8992cf --- /dev/null +++ b/test/lomach-chan-stab.test @@ -0,0 +1,30 @@ +#!./bats +# -*- mode: sh -*- + +TEST="stabChan" +RUNFILE="inputs/input.stabChan.ini" +EXE="../src/tps" +RESTART="ref_solns/stabChan/restart_output.sol.h5" + +setup() { + SOLN_FILE=restart_output.sol.h5 + REF_FILE=ref_solns/stabChan/restart_output.sol.h5 + OUT_FILE=output_solns/restart_output_stabChan.sol.h5 +} + +@test "[$TEST] check for input file $RUNFILE" { + test -s $RUNFILE +} + +@test "[$TEST] run tps with input -> $RUNFILE" { + rm -rf output/* + rm -f $SOLN_FILE + $EXE --runFile $RUNFILE + test -s $SOLN_FILE +} + +@test "[$TEST] verify tps output with input -> $RUNFILE" { + test -s $SOLN_FILE + test -s $REF_FILE + h5diff -r --delta=1e-10 $SOLN_FILE $REF_FILE /velocity +} \ No newline at end of file diff --git a/test/meshes/channel182p4_25x9p4_THIN.msh b/test/meshes/channel182p4_25x9p4_THIN.msh new file mode 100644 index 000000000..ec72d1aa7 --- /dev/null +++ b/test/meshes/channel182p4_25x9p4_THIN.msh @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8bba9f4101291f8cfce812f02d669bdbe7178b74674a08eb745bb96531cd9bb +size 673400 diff --git a/test/ref_solns/stabChan/restart_output.sol.h5 b/test/ref_solns/stabChan/restart_output.sol.h5 new file mode 100644 index 0000000000000000000000000000000000000000..8255c8900d899aa7a500fe04735fc7fdb589d22c GIT binary patch literal 150168 zcmeFYcT`nNvoA`JoKcW0L6D>fC`qsY35rTm5JW)9iUbu5s0e~20ZD=&IU_-Gk|c{D zAYlQbu#mJ+SilMxP*CCa-dpcEyMF| zbI#x6i_-q{fbVy|)8_j6RH)~l@&A;+U!(fY@Xs&CKM($0{$C}5qxw3ge=%(Jn``Oc z<*VQN^SerQ-TAVY<3*oaf9gKY9^TGZFZlRgb^iO&A3fdwr^CM}xqj>Gf7NSNDz)F^ zF;lTpF;ZRh`D>PoUY@_l<3x(O?eE{Bzx(~u;Pg*{-ud%B9n}u>zRyLEzX{|L1yuX% zPn+rh3)O+G-}~eb-QpDuid??2@E`Bf{{6m_&p!kItM`9lOENbj718_uNPwD({%`ud zt~xniMML~|bpM(EKj!_1Zzn{z=e2*$_ir}-6W{;PZR+*^jPCwlTU&!o-?dqhT>ko_ zreZ>J;-k9ieEs5onx1MqYWqum)YSh_{D!2=Fzj{m_2>)-r4dAVG(cfIW8 zaN+V_!~eH(sOS2-8O{IKfB#|E{^-ehx_{aOe%q^mW!FsqvAqA=KK}FezyJNeMFRiT zu8IC;JoC5y{E@@Wy^dZU-c(e#ewTi~%RhSkk2U$z`(Kx*j7{{Ysc8PU-raoq&pUs{ z%lut$OZ(^fKQ@VqO6|9P|LON%_5EMZ{Qs{Wf7#E!W${}e)W0SA$Nv1W7k^4x)c#l3 z|ISX+A?x*5I}HuZ-y{BI~FBVtceAY>&&Zcol*urhF< z$h?jR%#qYp0oKRxSa4U#?ITZsIcjd+YiBBO-4NGad65C8fgje+r({9O(?j`rcXME- zLo~o$#eY}zh4etUC_N`~b9U-pr4DLLDmeB7rAM$`FS+!7_ zk_*EYdCsC%*?{|X{AKf88gTCqq@8?~1UlLt4lj-;0F#=>qcz?HIQLXZ_t(8dC|$VM z^jIzhYR=Z`y#1U8^|Pn35ic^KC-zl}Lr^w6IW^r}{xBE3jY@Vk^yP!2in8J7fkIg5 zoAsO=D*|3ie#Hp&BA_#tpIhk82OSlCW5LaAc>Bs-`)GX{xNFvA^AQuF&x|5?{(Bts zhxgG;kz--6-^)SEnKRk@4enOfLo)Ffv1pc+P1J&WhQ zR6>lGBI^~EN?@Yj(e^c+}}}-87&Y6i4DiC$g)-{TGv zZ*p&7vl8yzj~RIPq7uA38O-P#D&fIukhrdG6&S6ikM5|ehEGRg59FM$1(O}MlH{T~ zcr3un)AOwk)=t%N#v*yhXx<51y;1{r6$Hx$imJfQ6*oMX#gL+E5nDL)j`X6} zU!!*=#5&)K64_G;lF2mIocAih@DU5Sm{JKk9hZb1xT|2q`vZr;`$`z*)z&Y1hSp2= z8#e1xC6Hdd+@t-i3Yz+juj*uVz<#!?YPUH%LD`CbOVX|r3^TVnbR0Y3lx>6K6nd_v znX-e!f(HsKmw zP;yVbUXQU0E;kkI6TgnyV|k;4vO6F__`DUR0|V8GSIoNkFu>IQqe~3a4)h~&RmrGK zZc@LL@}nK@#;OI~)5icz<8$eo%E#+n9-(7G@R&w z=V4aLM-O*G7S{c3*%oL6$mnw9@aLRoCm%UvuZp(C)wYL+ryXC%naCO2*eY^^j zVh3-> z?Ak;S7FbQaNw(*(;9K{}u166IIb0;B6(uZiwh3zAyp9Ebu8#+Km#|>f^!R(6^)QUK z=Jz)}9EL(Uo+2~HVK@-rt@nk51q*5hO20i8_*(Jzv7{krf8{l}+ByWoKR>IdVTXVc zko(~&H5MeY>dDt6u~2$MpQk|z3koWC?HD<+5c#S9p&~67m>GX^R*7Pvzq5RR)*A~u z4lDnNAH%|ry^cYAyN6+YsiXeHzG0~3q4s-&=yC~at&T=?%2vfPWYc3ovF+{Ug4aW! z(cv4XGdTnn&+@#z(Rd$HBRL1%uyDy76P_1_1t!jYBhlBeP|Geod_oTkQO!-7ZaF?VMcES&J>G+{h~1(~(@)C*V%A+m&BE6M(lq92$z7fWEec92Z3#d=|HB z^eI5|S{bI_WSoQ$cFTMBWF}#Ft1963FC5I5T#VbBj03^iYeVMJIPh`XGu-`d0@~+T z4(oSJfclz?W#)$okcer0wWjtE3h8P#m0BeQtO!1_x8o zD=E%dI4D}T6x`K-gCvIh4U=vhNCs6|??d%;z8}Uc5jb%AmB)1c7!J;?aah}LNAg%| z_IyHv1HaD)vc<$uKdOD7jO-Ep9H(7AC*k0yen&Gg7YBwKcS~k(;NXeM&d&{!h@RXf zHoM6QP|ee`HbDIAHI0b-ZB4-Nt$jrW?l`bqQuM;U$HBuOFXK${N$Aw&)QA$A1d(m~ z+1CbfaByFks2ZB*g6O6k-^K(yBC%|5>zaUB$4inqgA*XNK{>St(Y42Zvg;aJkD4}$ zfiD`joxO65`aTZo0yDZ39^$~|*-N)nM5p>xT}M3`53Q+F&+Rwy;D6mit@sNbE=lS% zx_9G&R+N=L^Z_2!elB0zWsZk9;~aWlQ#@2$_)sqwg$H}K>$01xcqrw2Sr~R=9(0~P zlhbpZha=*gEi%&cuxDAK$vqnnhnLk@cscO!rP!gAt$YqHam+Jzmd=5NEqxS*1`nEa z`SLyic(825R+kX)K#c6(G08m-BTgSRkFMdtH!v>tVC#p=ym+*kE zkL(SJ#l!iK9)tW5ME~*YM~<-0!+8@+;{>sJ_%NEy{u7Pkal-Si?^o2%Kb$-|hx+ke zh}2oYgX+2^B!9=ly6X0kp9J>|XEg0z4SEjt_*m;-RF8(=qfI9{6bO zS7K0|+lqefw&{D9@o;Feq(ayd4`Q#r zGCq1U2iI6?J_csb!RK3&&Jlxi;8c_A(NIdcx4;#E9H>rgX|FIcH zgTnLh%f9i8)wX#c?@-GMMRE=1Sd1`4{PnrS&YqB3g2;8#N!xJ(SY{aFvLguKNK9Fp zG$MdmIE#qRJ_69kmZwi2A%I752;H4X0u;UE9)&dm+!p5|8`&*E%Xr;k`Q#-~KS^`T zGH3~M>AmT`ZeN1EKO*jTITOJDM;^n0vPCEi66X@#vk2J@8VO(47T_aqp##78BHReF zZqA8ZgwWXU_sy6IaPr|ZV$)RuXm1hd-##aRed?#Vwsry}YH#Q9Y$X8cT|+`)8v(W- z|Mi6#(fu;?(1njoOWe3S3C0~Foo4-(z*3o?g)3zVb{Z|^1w35>`=>Xo6aAN< z>660iD@T_=S;Un8J=GFKv+Rs8uOPs+t;mRUR|0hE)EI845MZL>Mya_H0csOx^lmv2 zK+`ssn-k4{RUzF(dhZg9eHgw+7rX>rree$0i2n}?>yYrCBWz9f< zdCy1Affp7bMHG)=qFn?7hO%l+nnj32^5!;Q1lOM*qL=y>p-=3N6+e>mohIA2vPlFG zdVb<5JeF@U7dZp825S>cpcU4@Lpxu^@{4SmZI=PG~9-1T|D$m=^d?tcm zi16ZvJ|a{t7@eExCqm+xP@OkliIA+;T>eFg1P*4OTEaa@AU}G%`fC&kB)`Y;ao#7v ze!b?1Astk|pd2_dOoTE;t>Z<8L@*NKh&tA|0*A&lz7OiHfY$=&(KA1m!JP2%6JcQ) zs+>M?dNHklf3aZ_hvf>WIVP-{x30kT0d|I>gG8uRu>WwZm)xoOs|$0_zvHx!gP?NPWy!{bhm(Ru=YKk=;aS%Dg$c_#TyAF-p?g zNg(#4m{!=F1T9-b`z#T^XXg`ztfEOU`fE7rrW*+cFPVAja*zPaIkc$phzMsUk^&>P zR^T$<<=uj}S0ElA+l41i9xU31YTAn4Z2sf%y@y>~}{g@E|as zYYiyiaq@Wv*GUS}*UO#jP868sZv3S24`ObPd(z%cC& z8E;n#sK%5ISRACl%l1eun_4n(D$xeaeIP-0kc5XV(s!PrK)zfm5^U_(e2JGQ!Kp*? zPPEA+*y`zcoBfRhRcdSZFx$v56hAn~TtWi6ECP5?@F%%7hOuh z@aa`pIPO9mJG2TPk7T*4PEbJkw%Cn3NDoA>VndOOgyE;leK|uU@cRL(b%-u)y_Iw> z#GiUNWzf8i1TeC9A7>Q_?x*$Ytv?{aMclMc0=OE;WH$7D>!zJtB3^ocdksS zYLH=cpI?&D02!P<6>#uzP~eQ4%BgZ13MB0)njt(PLjj$r`b`lstg2r2evjn*qwgMr ztq>UsJFoVBtt3N#5>E|Q8LjKh;HH}E6d>2FE;&R|;LR<$Q7&AJBa00T zL~V+5`IAfo7f|&9H`yCIYIpG zB@&nn=FYjFB!Qg!S*B0EBv@a|8tqsi!MAD4U14ElP(1Cno0pmbcz-eTdx(GAvZZ*2 zF*10?8DA2!BEuA`x#IX&WKZd%;z~Y|0RQ0}$EqC}gao^@&oEHnlVWqJ*jWnfoE5fd zLhF0gd(T>HI0fjGEo|0&Dd6{TAL|pO=gtFTTO~0R$Wc${JWsU>jqAQozdWVDA@$vL z?fWPobw|fpc9aaF<~9W*7&7pko;uO84UMnykfT+G0yRuUMyYZX*s-pVrTU8u4`0>E zRa_zihpVJ7%>oIE*W<|J&q=^?%$h(yhV0^=78^xXGQ=I_=FUqZLnHCPTkBpjC}0j6 zbG;@*{cZdFf;KWd{^A`n@P-UuUSF=`Li{I+Z;v~Bm;ybsi)}Sl6kuHaRiKCVB?NcI z$r8vA=(}@~MT`uS`EOhLk-cqOl(`dv)?r0JQR}iY8G=l|Mam+2ujO2uOKL)T(DT5; zlz$bnhWd~16j_BM=i}Gk4pZRQx9r_y2P7~4Ze7)}(77xRnG-UqoUZ>(Tnw@p8PS zAwyb9)tI0=;&Y3+n!cV4yX2Y++Xu;zm|?)T(m@83lXD(FQE9Z87oz!v4A1Mi90d1J z;HGg}iMc5Syl8jRs3E)bg7n~xwNk}?}Xf$i26)}r~y51cy*I`K&F7=zk_94V0hUA_P00c3xfJia~?q(HUUXk9;& zpZHpbrRy#Vn9)kd?cPp-udKhGznDaJfGwwDIRM$WZ~NqhkiWW+;5kaZhwLkx>gXg3 z1=~XOLrVdP~n;Ga!!{FesZ?lMc>IF`E20sLnPmv8ulk+?8uLB#k{IQ z`srPA<#t&H8EPI@-0MSnd&#unO_c-%wo3gkUq=4G^JK4^dpZRcT9uB^WKp1Dt|UU` zF4`xDcSdhFLj2FvUJKie{6^oKg&jzb!kp&h){uPFce=khFNExH*+~HhS_;H<4qiw_ za$ftPVZm=thUu6(4C`4k$e&ieGL7~JiMOzchySjZCpvc&XAoqQo)h8AO4p4KMkEOI*QA3|K03;kt9 zgg2On(bdsJXc?t8bwKBs`D3@nl3dBKDJIW_RYU7^)RXR4ERwg)8Fij23YgyuZ_p^A zKw#EePt(1Q-{t4C&U9^VkwYrFCox~_Ln?&-S?3q4(=kTPBl$aBe|G5#vfp7MDQ_^|6yPPxJd=$?_CGALvK+0q z61F?H67eT4Y5k5Zjtn2b`5m1jZ0}XR_94Qx#+Td`=ZSF3NZ0@PED>Dcnne;^ z)%C*<&Xb_SuCj}b9r+Qd9lLfT{$DV`(Qo4d6nTDKapJ=3(?g1?m_|g z*~M45Y_v{+2evsEAU)iADJ$hq0XBv`yiLB= zbdUT*F9r1C>TlQoK2WK{im*~4f zTrVQr37w~w?Nm>jqI1$d5C0S!biUL&U2KK+@z^FI>Af`xZnP04xllXvIk&?OXOUku zh&XyJ5S`z>zuu?RlE71CS0NkH6OxAT7zKyyM}AFUfzV`9nChe!`rU35i}{wLR2d>ctb=OHbB5JCQTb6}c(y2TWKSKoYlH$e&4-T0 zB7I*8*S;)(*4Hin&4p4fGPEvQu&srXz+zFKzv2`L=-hVszc@{TOBM&uemqYCcNKP@ zSahDg#uHOggWBg7bQ(*{kRCK^Tqs57jRL-FZ2?;7J8eZ&;AA`zt{4*d+Qy0C+wiUB zBsG%HrJu)Nqf#UNjhYGio>7bw7B5TPq-NadH+Ly8`*`PS>ltR)9;u zpD}`)2yX|u=oHZT`C9U3R73;f`_O)W;ZNxMRrgF*3_bcz^kYy+LEp9POcPf(+KBM8 zwdZG8AQ4naUwjlyh%k7B&hZ%Hzdv=Aqsxs5MJGNdNVF57r`utDN}B|`+O`cg51{W| z*Oqu)b28{E6-gIYlffp#Kj6hj*T!E+BjJ*>JnZ3K8ZL4E=n9i6FZC+A44_5#$SBrJq|^ffx3POt;Ec;D+T!%VPKn zeDuD;`S9in*lF?yFx*Gq#rB7I<_cFp*g`Yy+|mkgo=VZi$`GLO9P?nrbpo(ywn^x9 z5P)*-lM)B*5?BQdIRuL?!R<+Hhj!K_zyvfYDx!9qEyJ&4Rs{iv5f5rRPI%`3n@6s_L9`p(4QhT?&U2P``<| z(zgXDj&u?j%O(g*uxnjomUMg>EFxd9-8Nqad*$8sH|fxKyt9wKMfeivT`RsZ%(et` zMw6#G>(M&gKcul0M*x$1Y>|7a=~~^hX(rmu1D2?fKZU2oC)h)>jCNz)zjA9()TAbcy9gFeLPU#TyIkL#=}A4v7Hhq{<-^R)n+`3OP{^`vFyc5JouR% zyD3gP55%uMH_}numvQ%9=V|JBI2U$ISmhZW)E{OCv>m}i(B2wObx20xb4MtpT!-4phHhX;L<=Sw2u+g-opr4$7ZtK!edk8APJ zug@5giR9Fhnxou?;>j+Wz1GY}QC!@Wcl!=B-ifYR<+OWvFr7Ui;TwvFf-JZCQ@l8+ zmrzf&qs9T&@3g-^$`{=BGf^-S!9ieibmQ%_ICxvLYyZ?!9K>_}au9xr12ap%x36k& zF#ajrL@y8rlrQn$gpc6hF>a=k4dpA`iZ-IdlyETmT;PS%H5_zw&F#p<;DEn`!N+h7 z2aL!1o~sgYaB|m7WBC&t*dDbf^hw|#O3YcDu5|*6em>j~Pnm$sw;?yJQT`#}8)bS! z9tTqI4D5!|aKK_%)_Q#j2RdJ8d&Ga@p!R%C`J-_hlz;j}cO)GL0qcoo^cQj9wS!vB z+6V`6L1t;xC|@z>9`Mrm01o!W-%OSl$H7;rN9?)GI2e)A=OClJkw+8+1fY7l>5@UO zIu6Rlj;QC}!GZa0g}k>_I7s$&`hh|GJF6HQ@@L?nL0ZFucpe9#e5-F`5Pjx1A$bms zn-TCf(%%>dTW8qgZBlWdU;A^LIUWZ;cG_$8e@FEFTsg1s3guaZV|TZpe2YTxRPpEK z3HWYr#uJ}C0iBP=#Pahe;Ophbq#a);AjUVZ-@z3JJ86phdV6tDaAoHD#|@M>3L__d zBcpuF%=pT36Ap~%2d!<F8FtB-_*oXMp*37l<2BLFDuO;Hr zAuQN5DsFL33_(CgU1ImMA=v*VIkmKR2)1@a%!(;sq1i}J@njLoBOc~`%}g~6Ef3{( z#c&QoW?`q%`57!+*my%UOT@zLH|>mv7qB2+t8=Q&77O2h2@dd}eC*;edC`SuSYUfK zO?#ve3r$~Ek99a>A+=i9%l_*S9P-4U_6r*VTcJ_yZx%x!=2mYWaAycYCx(kXC_@mj zC2N-vfQ5Gd9^>j&M6Y3+^M=SUOhuJ)MY0UT;ys1qffHEJ*8Bz3wOAOPz92JIjq<1F zEnCX{Shyx=nR;*%3sr8ed;D6lfb}_lN5=~bnaqk=CVHVi?*@XuR*@NQFsdy9Rr|V@O`Mp5pz+K}(l&_U9 znGzGV#==#jZNK)R_hNJB*Xxq8kTs*&#a)2~r8vwoHX6~lle?^DKgxfv=o(`ihrq`& zwq4wL2(mgT{vEbM&|SJUtonQi)MWfNXVkFJ6Bu5m)P{w}0Xpfg;A@r*Zr3~?zm-a~5@f{ZYI^Aq@N3men&mj9I6$?^7 z&sx?{b-?B4RJlkYlU zmg+z}(Xtb6Y?}1;gm;2K_4~ef&raCkq-d-n+zD3%eT~EFJAmi#Ex&x54)AR3-7}2v z4*OGVtB$r};BnZ47FS0M>?D@2O0Ty=_oGsl$tUd~Hyx*za=9JiZc*>v`LrD}Maz|w z_hP_#P&>bZwgYUUtR95TbN~l?^w`a?PB~dI0ct?EMl3Rpx!V) zc+aX6Di!3~ruh(^&$}zWOd-4lX)pHjlMduR!^ZEcbwEXM2u}{eV~mz`yv=gLfRSrO zPq`WfGHf_Szg)n8^fc|5!4w9vX^9&eAsyfri@#O0s}o)aXUx_4b;3@1r!hisCnz3l z-4kNe37yg%t;)+CAQ+u9AQ#pFmNG}CzbkdXU7DxW%n0{z5=p-#9^qCBA}7TZFyNkd z)YP`K9av}@%Uy4^gK=JV==$|`kfvJdUVhgO7Z)Xk3Q&JS^N|im-wsgtG#^fd@F4{c z7?|HQb%KP;We$tEPOvq98Rgp63GHE}7?nGn2=~IWT}!nST6aZWQu)~dyN@>qU1{ln zb^J(OrF#cp-6`SCD%CKX7&6k6TM30ZgLeb^DAhsfGa2ZgKHr)zJL9DbkCt3M#BBCuVah;Fy%|hVcDzSU7CCXyQ>z9JgzqLVPS!%J+}jN4xH>Qnl(8uEse}FMoz@ONYJoJ} zWOTcz7M)wvTdz0O!aJwD#~ciGKwJzZ^m5jL?hEJigv+(C^W*Hfk8CwSt)%*l{X!K? z(AZ!eh&!8rU5yIcz4H246-)`vdnq0fDAU3rVTR@KB>gDb4pW3|#5C zfH6;oG^E)A?}zS?uw^o;;|V>SLZeoD1}} z=`{uB+3+h+NwC*56W)0~lyEql2HND^4%!aMAZDO(X@U7M=!IFf?W~9aOPw8j4Z9zL zk)FfcJCg|T8smOWIu!#r-O#TOFp2QKS2#d&pbbp1Y(Md@&W^K;B$B?@&P6a_8g2Zqx}#LB3X{JG?%wJnkptIhr_W9A&V!}xh4Q`rd7y5Pf7I-I4%B{@pEcsh0{@i!2P_Ysf-RSw zLbX&Pv{FTxhMC7h*y;jxLSQ_kWWS(X^h|_pMcf1J4&nl#sL!SV+2UL>@bMbI)&pP)#iv-|~UOsB# zo(PWzGnKX#rU27S(2t0@YPi%nCFjak3kHXJW#7N81#W!){oUVc!E(~7g4?EY+qn3u%K#L#=*64=b>wP%~mZG7IXE`w$wuJ!~o?1wgys~!&qKNRl_@f;vhG|--};okqwBc z1g)Fg#Iweg!0Z2N0GCt=3I+!qUYk|Hlgi|zErg@j-7myq<5L5Ij>$S**|pFRkrnl` zqYkS5gtq@IuZL5;>djip4WQ7ao7EwX%16`NT)gVRJw$6Le_tKss}dP`^J;)a^45LB z1J%GU8_U2ehw$$snjr%jl@O;UI96~C;pgMTTiUKvg1y_cf`L~h?0!QCZ*r=H+sk`i zaNI!WN1nzVGgFlys98|8R#yer?WUx!>7ehdYQpx^{tjq#N=UIh&P2`256R`xWVkVWpYXmacT&0Dt~=%Vi`>Z*z?-2e%> z^mOe6I^P5g^Y>0@BvYT9HSdBw1aE$fdlyJ03cRV&MSNF0yv01z2^UR&=Ifhx!U+Ej z*N}=1@S+-w^cU)Yr;iyzmC^jN?;TpMqj_GH<#ioW#=zDtwcB$17)bHGO^#y3z%HNE z&H-u+0C z3)yyIVLHJfG3zD5ds`F)vSeZ5EpeERa1jgL_Mh)s@4>=>WtMYT!VqXtHX?Mph9H(= zRy#0&@boRr8C;C0-^JFd$0$CNXY5I=AH+g>*#==jZWw+jEbd-L_;}YapY&>vVGzuE z7Wc>m#b1haFJ2TFhJ?=-+H0q<5I9vpGlB5qE&W-(k~guiVv=>&=M)y)ctpD=4`87U zW2buw;l@ww*>$CQV+eeQl-hOhLvWS*mG`Z&A;_oc`+WD^5OnUA{u)L(q#Fz2L6>*sqxg@6t3R$T z6yfP*Q#oaiVS%beF|?W<_3u0;=Q}b4c>9xPW*tM|qMM7wd_eOC3I0k|#)6T0F}^JY z;pitHT$i9826GiVd#{tj@Kv^s5aT%vAK%YcmN^WAU;4YE&QmKI^0m?l#ltoV=-Ltf{cy>Qpqvyc)7gH!L~>U5^j!T)90H{((-((v zLm=ere$f5R5S*)XBT~%_!N(`34qo{=1mq1RnmDv>W^3$1DtmCiys)oZ3zeU4nG$kP zJTFAM+DX_A#aC;KGpP|C+#Dd*HrgjXM+{ng_!38v;JI`o4kVJ*+K z?7^W)FkEjB=R)INQeJMLL-9pj^~L@eggcK|dl)aFhT_KyiZZQNa4@UEDJY+h@bf(W zt?uP0P8uc7w?72o#xL6s3~Az^Ek*ok{rUua>e>8ZKRy9O*H3O2XC`3uWl71Y%?T)6 zAoQ^d;^5S0x+FHlZ}r(yGkqx(Cr)(cpxcXsyig``)kz$r?Rouc9~$?W`KLwJW>oj> zE4Ytv?3tPdqK)W&0Q>CNz8F0mOe8AzCyz{knak&h#;6IHT-z7SlQ{u(yHlD!v*CbO z&9-9}-A{Pop(UGicoH0z8p-+(CL#J-2i<{-lR%tM*~>#c35S(Mo2t=#f>2BNfa1u< z@A|98Bl-gFiznBcdo9**Mq{hS};kD}+%Lr0wD5Ki2#O;jcdg0okw|#6S+1h-=HZT{UV?p z564nEv!_sAhyL?*(s?aBJg?n5aQH03!!NZK+gIZe-0t(WEa7=D4n2@iU^@?`Ws55= z#`Dk<;ddqK6Usv*WpISvK>3U(>O0S&{7$27`S&hXl%E-Nlv+2!L)Oh`p}ZJv3&KTkHS1iL@6mTB7A(z8NRcI^@tw3Bj1M;5nl&o;)W9t{{NPK zP-g}nPSHxIwWZ_1CZI!6_W>T1wPS>K+2O&5dHOqp0qV~{FSZNigPL}}`H_L%TjsY` zp8tS{@M@K?B=LFhJpIX81lhXp}a|n8qvo2I3D30rlwtV5uV*Z zeEu8CFKOL-U);Ea@>?8FQ;7QWkk->^K6ic|422^@nbA0l*LkwOA^CV{`P^|t?@g*E zd6Sv&Aouf({BsGEXStHbCvg|$UvN8ryjs9R>;;T8r_wyQG#g+}!aTHo?|RD0gVuvk zZjn5ShhL+HF6udWXl+RmDUQPfMX{yAECmljv3mV<*?6#Xc>HV)@zpEwV)3>R0Z!Lc za9~hAHQN<><-aY0i&|(AiqJ3q4=Ak#rYg!(VYJ0Z`bCr#@QTGVI zy1UrlVfPZOh)mR|K3al(i89YQ3YVa?FS&?tcM0K0l-}s6qWb`N2j3Qi@AoYwiF=^= zc}YK%i?Rv8WzodMit@JaW2H(xGzbv9xyur2iTH`p;u2&=IR3BaFAbl(Mu66C@`F9- zzQ70LsWbOe3Bc5KTDI;h0giPKZIr1jf$5mv8%KG>f56E$hi=5Tlfd*HSpvu>+zww( zT?Cp;C!DzBA|%(Iex;kdi2U;ipSm;wl5A$`4v!H)^}KD_UB@NhHxNnf$y)-K;Okn7 z2)BPZPQ)(B2I1l_wdhapSc1cnktX5q(S3t5#?qJ?0w}pgTuO-|KyFEAhX-Xkib_Z8rOe_1S2yQ%Fnupz~^Sc z{lyyLHQwIT)8!$8H)~3j5)~@Pgj4!ii4f|hHw*_ImdSegb?B2ui)eR5S{KK zYdkwX5+PM{=ZA4r4!yW&pNeo-Js9(H286>HKDXq28R0-=_M9=heUStzREMm1l)jPO7F?Fz4H5YENv`_Mon5o9HB3c-p9j~_5otVc(L{bpbNN@}ZvU zz)a`Yuw$MKbg{fWVPu4NVclHVzetAhTJNOURx+Gwk=wn3@GpWhDpKE1k>Ri=_iHmt zGH85xqN4Pe4D>49Yqba$w(+Rgd{YMDHp^}>@gn@)VD%wcPCg2Rb}}T#W+GhfMxOWQ zbrM9hK0lQnjPTaR>A!r@dK@EPk()!ft?dz^p*>n;Sf_h<;TXcBm~A>{coNB=6gHrT z!;zuR_WJC_bTW!l)@50`lVQp7ok#LzGF*Mz#F0>daH;HBFM~G8AmDb9`?WX)VzZ)Y zLJ*!QQCy+ntQZB@6CK9g7%9LyVwXLNaJcp1Pgn@kWDt9>HW$1^2LB@+tXYVEhYSXP zj(6z(g~Fv6scbS3?AkK+B3xngpo+vjB-fwJI=bVYWbo8bB-?h9A8Vh_#BBf8HZ<>E=3$9MG~TJOe5U)y$>98?V_3X~1XDYo=cuClCSx~3FULkA+*a1( zvl$yCkUq6dP}(2eYY?~md~E{Vb0`gW-dG_+r>)OPJG2hL(yxB%A$%#z*K@SHoXBwf zcxE$SFv3~(jR+VHBfQRY)?lOU2!B44X235`fu~Uu61NeajcHu`iwdI8px(YvZ3E$A z3$2!AXUUMxf4A#&4Z`QT4aK_MMmSD4yYLZBgjdeje=mdZo&_sut93gOt_>N>J}C+e zJdPW3L+fiDFTAzRgm8i>eT9TrGQ5u#RWRB_xN)wbA|Zsw6L|8=FsTr&Z>CHfCoh^W zb2H2m;f>$9Q!yVz>rg0FxwlJ)0{Xno{Rferc>cf_zjMpad?C9h0O=m$28D7Huy`h zKg%FP{`LhQ1$3X}OYZeV-C~5_-j;LL49%;nN0;7??pf3xxn0t@AIX)K>Lw2JzWoVzrQp@Pg?wXX4QNHr6j% zKHo+AL3W!S-y*sXk)!Orzmo*t!_S3LZ6SMlZaQPjh71ziB1d}j(7lGk%c)Pgk=;=v zT8|?<-T2ExHZ)%lK0HcyJ2jG@heYX}a$^dJsE|KCx<&zq%SukdSJ6G4(?_4{Y9qcc zGF!fSPlmwX;X;23h?5Z>_RfMt$A%iJXS^IYeu4JEzW9PHQH1B_Z4PKXjP3#4 zmk7n7^;<4*VwFJqVY|21hKn7#hd|y_Ie^BIlnJE!SdQ+kyv#hCu7U8m-;&N%rc>ZT z@y+&qi0`7KS0+bZB0Q|F{Eoy+6lnb>;gE>zf@l0o*&W;Z`Sva81vkGXN9 z{eNGb!NLdOsZ%UYrXYRlbYf}YMdLN;w<%d6oad4dqlXVOx(9?U+b}}=QYH6YZP5{g zQ$EK{5U^h zTjsMjd=2U8jWMO?cG2h_MOnhRflP#x7YdSA2tqjGov(`yo}z&F)5kKm_9Fc`U?ZcB z_UrMF6}MQBo}SEFSDoC3p7#lp+Fv3Z`s;_vy$)osP4lRz5+=jWCPLQj-DDWqeW$cD z1U;X$2UjkHKX<5i?y*MqlTJN3^Y#|P5%&hEOCp@AUqN6{ycojc7KaGLH)ZnV$%#AR1bBb;Yh{Ivft zy61C)NsH_o_OobDj+jrEi`9B5!I*#z?Z71HJK<6)dOVxM2+mOG^!SkmfzrnWc!-(l18OlqJ92Xrz_gLym zLtUPcf%@6Fmk+Wpw^S#~A0hv^->Q@qhwP8JOjR>iAO##hsQBKgq`+bRvvprek$pNm zN1j3ZcwiQKHPL=7UEZc@f&BLz-&KLHH<4fER`Y!R2;tgI)C;#>li}lC@ve~X$S%kz z6)~)$djqH+4ZOUZahVF*^bZ~R-$u&u@+M)T8BILmv=oojqLxo+~lNKWUqvo zvZOL7Ah$I8S`XpeIsO-WZyt`--}Qe}W|1LDWk?!?5+N1KkdhQK7L}Ap=FIbySt3N4 zXDVe@hHW?z%2Z}jri`a4BC2Oy*L7dV@f^>8_w&bn{O;p-egF9G&hy;%{)}sX_FAv? z{?xmd+o3%ZocCj(5T1wX+QVzFBFIp7ap*v{E!xEZk-DWiGHj~&27N|5;O(EIc^=2h zzHuGdxgN9+(-y_TRr8>1k;9pofaBvh#mRXrztT~;Z2@S9ML$&arRt--x_W`a5bs-| z^+YDO6w4FO-+dd$gPHqN!6uajXi3rRdAfvoea?#C4&(flLthM?+dS-?3*tHc3fC2U z$7X(h$MwTD%7#enNB7cS*T)C&bCsH6E9R$2I+&|c;Qfz;e+XE|@AsMsTse0PudAV; z!0fmH7yjgG{KotD1?s223B&O~%OsMG2dH$%Nx%uWpjR(~?Q|;9_xvTy z6WOC*skR-T*X!?I*&E=z+2?YgYbW+Ym{RbYTWB}@&XR0T@XW(Q#2NPB^Wc4&$Z0li zY`4SZ&Hk#G50gi)_QekE0vkW?xHod>hx>)s9p|C-@|3f=H9kL>%?q5-PAf1ePVUBX zv(Vk|zKPFUnX=a3FR`DiLGv-kADl<5!*t7u^Uz(AWfO1&KNpuSZXY3o@j~XCROIV- z=`x=u>GeB%kB%|`C`3K z_yo_)gHT!X-%3VY*Pq}yo4aiucIb%45#qSsy}Cb88~dq~Le=7sCvxgE%U>w6$dKnw zr=|IT3<;A}g4RN~zBKQB)3l0Q{Qb~NjN@|<(i~uT1bpdeP!|a88S9$Gkx5 z9miuV%E{n7Qt_$-`SSKR=R&jbz2nT7*^5FfkNT&VvlpVt(DHLBQVa7N>_;E$Y11JC z>)Tya&$r?_;+XY%O5hxHD8Fg@L^%iQ!#~X)C(go@=EsPA!LuO6_Tf@O=Paxm{o2Kd z`7bwQnq71LVjl7SZeLj!GJGp5l1{{RkU@_LIU*ACG_@snv#H^G=?S_z8YVJaZdn&0 zu)f^$f1d)r$HnQ!kY;A)Ao*flZxH4YY*6r}u)LfDy2i~k`#W<``z!pwRv~=IppUZD$un zPQ7RFXfOIj7PueZDY%3jI!*tG%;zaM9@gjf%L4r^Czq8XyQe`|GOjRxU>XvrLoWB* zWBz{a`9tQdQ_#yN>NttKzf>OUDf8M%sL)lpKKybLuKu!GBzh*HcXEf512^*KnI`(m z22(Ix+Z^ya06F|O6o&CHrl5$Io64sT{VKfWdFnf-p}^CIn?ijW*cPvGZ#S6+zP}W~ zZxyDY@X8d|!!gu<(73YJU^xZuzdt?6;J|Xu$*a&~{R85*XuLUrdIKZR#d4AB=d2D_ zUB>HPm!>c-$9#cm{b>W%!YMG!{JAoti~gdQcPhm{ss}oPu`3=#11X^6|N2 zKdN6%0xRR^iTf3kAj`OSpk#d#1fvrTd0+~xZu!qC+D(D(mRZh-IP}xJX%28i|57uD zcEJnuXKAon^S=Cvytw}7(Js`-Xey7E7~ekzDV13}KmS6%C!BTs`C=3%h~3Rh$a4pO zV3Qq@9)<8nW!=;0&ty_lN}7|~BkI@gA z$8x)&{2%_-k4E{z=zrxl;HG3`{r zD9r2c4!eqeOGY2}Im#~N&839Y1m2_n_hSux10HiFI76LAk^lEwRXB)xB92st*_#iH z0i&10k~{i6Po4SJEYm#-MS5xK5$JE_xiS(upg0N{>9%}ba-+b>9+~pT75VvA`HyXQ zeVM`PtPE_wbd3VzQwK56;Mjxu^XOM~y;5ghhkn*^AEm7`{Fs;EF1Pj_{je`$%ez_7 zFMEu2=9kkO)LUtc`umL+bz*pwkG#Qpo7&YlQ=(sYR;uv&ZOkh$54}*F?==cu1D?m3 zr7+Jxae*>KXcRgO<092{M&VGzSY9ytrGG2QTw=odY1H1nNrSxoDM^Y?&aI=chmk4U zsvf!dyS$oApGF}`jOp1K3NQ+4nBizb5sc&3Auki1O8ttH>F67rspI=$!H5-6u zM}9iKLJmIh*&&ma&;j6XrTp>$^^rmavZs@F4}fjc^vOqT18`V$=k-h&fW?N#A3B2v zzBRPHm;w9MA-SIvP z(wT9UsQ1$()@ytY^@zl-r+rmG-kxsz6SiS$)O(_DVys#2hj*#Ylh;r;spA!$$_1YR zu-MW1I27+=cj2jF-|;~Rl-aUo$2SOUl6=gCm|sw_BhO5Ta{!b#BK5 zT_UgKLw~OI!`1ejn1h%vpqmiIc@X1I6!+GS>xE&QEIUKH3j5>LjiS@LF&_0O`Il+V zanxVBf37BS`v4eB^nTaI_)3(y!+IvF@9F$z>jO3ODb-Mui20m?$!>6S&C97E!*Lm6<^5K;C3)) zE#7BT(+-PE8~3QTbU=)|`P^3I`&F7+GWG6uz)$(y9NoYU_#nE?-!BXG$>eko4K#Fs zlT!Pz<9G+;wq;fPK(1XrsK#o7t=9GC0Ua!IGb8JEf zNS^Kf9ctbIVT8hs$73! zI-p4Pf#~M}nZDYsMF=so_EB!>3h4|=hE6iw&ezE5(V;oxj<~5x^PcgpzN$9FTX$MTp1f8?#?tsNR*H#pfqYq4RFb>E1KC%pL z6~%J#ANi@c@Vf)zMpstVhq3&}1H~>EBiDa=X;ADI39f3b3g7))2g_N1_idQhfitr_ zOW&nBxG6^~yXlKMga%`j8pyBzuv^Xc%^?9j@58HimFj`R%KOOP?0Rr9e6;UoSv}k_ z8a>77SPw?&)|65!B#8Lr+=pq)v+)aqCZChWB-XHM3_ z<1CA&y|T41v#eLZ6kLlsY$5jz80&!BW9QZ*8Fj#_*va_!Gzrq&91_8yH))mHA9NUVo6x$QkZ_Vw_{?=pQ4 ze?2tH+TGB|CV}0Ex_!qk5}b1kthA0pPQj{1`sWqoB|P{uZEw_pk*Suia0QlIK=Hk$ z776Tg_J#l6AOVGas;+=nJy2HMxj|iy9KDVm{jQXHurq)CpcnNRpEe$$yq!iuznwkb z!}&UJq4L|bfjX$Q%RQizR|{vPgT9Qb)xv={Emph4YGG4a^5K$xE!2AM2`NFI{ztK@ zC#Npz5B9P!=`LaY$+qpRUL+V|3OyFHNCJ&k<)ue*_296?a9qO%d4Am;>M`N7qrJ%*~ zdP?U;DM*hiJOWGk5HR{X(c*R?=*#;a4*F6AbYtXwnw%v- zQ}O%zd*s9SW?6{_%9g^jM@Ll->y|>F>GRt2SdPP`K2}#uOM#={!P_{)Qc$|Fe!`5q z6uxidY+H3LfhQk~4I`Y3;VNsTcGkHfICR!sf?BQ+Y{-+$H%SGMFU_SZv$YW89Umqa zr||pW(+Q`K6@$Nr_?Pp@-_Px+sf>{;1^;(xa0BXM7(JUth7c< zM5DfG#d$T;37sN%By-oM6!|FiTG|dLx&mNl&KvV&&xgRuZAya6q1 z{cet{`QTd}lS-Lh023Uq*fizfVw!G{#tV;xcgGyZKZzxR|v*4jwqH?>lD zpZ_>I?pi76CR?eS+%5&m1NNEx_ex<``}`>m%x`#0EnW7quLQWCIrs`vmw^p(=EqUFA}J34(K-Tj{|*+ zK^mbQ$&hHIb9Ebi8u} ztB^5%Wlx8ad4ARTM=5ZZDOO4)k z(ePZ@U8^8F26j)C?Z`a!92SJe4pt2(qu$kGgrL6g#N*h_ z2yG5tv?Mqnmfnql~Ul9ZmHN%e>|l2KRRl#?=B;cexoEF*c=_&KV_#tg>?D1V!?Fi znHrg?P09d+d-|nT5}Bapz2yDEAQR@kuyzI7WP*~mzRRjzCb*fE=HBAUge;e}GsN$7 z)RU;E5}8T;4?q9k!;k-S_J3O7e{~DwU+=oHeLeyH?ljtW^y_n2`Epl(Q1BUq>-4;* zu8)Hq&IW6*_CJUIk9o86v#6&|m+-eKDj6=-0t8RYm-iaN$%wg3dt1k6O%d}yq*k>>o-K|oRi`D z!M`~^?~~wTj&)>M5 zkp!fLGvuiHL>PEBQ`0{e4+}Rk!g5OWMssi1s%+&b!A3Xo46vHYW*0-@7S zO8TTyV5^hr@QyPn0PKvpWWE#_yRA-RSe*@LM3SNUtfI<~D@kzhnj~l; z3*5IRB>~r%I$cLEG z=Gm!;MCo;z7K6cBglPd!F^KAZ2^$hFhRMc=rjEd3=%X%LUYIO~t^G5lrK`n2bHR*5 zA9e5l9I*(z-BAS0<@}6%0!6TsfAM|MNFm&7WLC>YUR-&|Hp_6K2wobK;$AXKX#5uz6icT@yE}ay2oGJ$I80H0z#bQwP=rf}?FNT?2&!@C< zi$K&Y`DMwTBB+}Qbu1Ri1KzeOb+Q@j1G%r9Ope;-~-f)?Hj&EzK}pq4ndP~k&@prz1B zXJ-;P*ZKK`o08zeF}<7-WfBxAGM&D<5A$_{xr`j}eQ`_K0DT-Kp07UVkjYPi(kp5f zFRqcG+Vyi?SU8^l{!#b$EX-^6_c7~;BY|R@#itY2Bp8pq`fSHp61+Uzy~{#`1Z+%? zjmYAt+x}?l^rH(TXdt;9f1yc&#hC)0hBG7(zM;#p1Mm0d#6v3Q@;WGZ+#opp;q7iydDz>g&_EHxUQ${mpQD=u{L5c1mqMHa$s#v3hB-3Wj?4bF9v(%;d9Sp!f%e?#tIxgH6a$ZzY}@Om%zUg@;P`lngGqP{PS@842C_LpmrfKv8U3H2os z#F{hIM(K7yermI<#zs4654qcKR<=VjuU}{|(GI^4w|glQP!O>L zN}F%|&Hj#g{=9!H?7wzEWS?+CM>+Zz`XBPrJ?nt_2J<<*V({=aqJ*sP=f;$_Ry zRpiw_JG}57K)(H|lIDHwPssN>i@DxH9zXeXmDILmyw2$W%3)LF?ZeLZyt#~iq}&O^ z;9ICaaA3+i4$Dz0H_iG2%l)CWf87ep$+Wj{Hx24BbV}-+x^=h%INZ7uB=Ekf2kE&f zO4>ofYRkoxzII4uKP;z;7Z_g15SCvd#At(m(6nvd*EOmx8Wv+~x~QO97?`^0Hwy#Erzn4t{T zLw;hK{i_e^PVCaqcGT&B$L~#>^U=>EI-#z0#i;`XZd9yuzvzH5(wppGwWxzHS!NxQ zf&RBC3Z-r(^jof8HfKRS_;)p0+kXf31GC1OvZQlAJpB-FAXwZF_p%s<)dUCNeYJ65 zUoLXnG*NbmOsJ2(Av?5JY7lz!ZTyZO8ibemj~Y*)f7T%CvSn*I>T-OIyIc{Dx*N~x zD`d?F;KU17@g}(eu$0Pq$hQ@B?b#PTR+b}weko#Q#-|?|uI)L{^PnFV9|rCDkuu5B#{pls@ zo{aK**pfW}4L9F*nxG%qU&YqtRr3JQmp%FlZ&9bE&*wq7!vM6{3PlOB48Rc)M;f=6 z{csXoOemfD!Qp-X(L;~=p(|_U;M3`T5SSm)Fh)K1?}L43)kg;4{!|&e(2+rS?WulL zOJWd)KP5XIVHpIccIP0j9`w&Tt8lNUp{~69`sR>3`nfCql701%hnFkt5fMPW6K%?~ zhQs~vwdPrN;d5+n-e4Y*T|d~=G3Z5m_e1r4r_V}_Se{MqC%&i`vtOC@)DZe@lRAo% z{Ll}pFV0ZBYZd*|uWgP^4PpMk`0d|sv0e`-M4ijg5B;*sQ`YS@-j7PJ;X4V-!G4LQ z2HVYDEoNjMuM>Og6N`8JC{Xp@D(*Uf+_$jumx`VdkX+n$I;D36c(Z-?#iIXT>XE#h z;j2+NSav6U@z5BUwSB6Jw;F@(HQG;hI*fr`BxyhxquOyBAmg&1*uWrtH^kaR3^+7UVYWKOa3Hp*jA$#jF}F2--1 zU+-fwq8@`5pZ(b{wvRzH&yOya<6}@*OQU)m<30Pwc0@%8jlsyRQwM@E?xMCpIj(;b z`Tjjg4zE^6;Kr-@IsT6$aP;Oo7nOC?hxb^`p2oa@b5$~)y~xX#j+&nD&>Vx9Zz^}= zUB+OzDqD30b>}l{1Pq^JUctKVM*iPbj8Cod7cOADW@5_au-_!xYsahhFb8UyQSL8?PEV=$O3_1l1V3_OG{ z94&@1sAdTF(U2R%`J(wM8TIWW8<`rCF^>8!u}5*&%p@p_uT~t%ME$FfL+4&%obPz$ ziCmAZQ($7V(Ru;-Ys>B*RCiFTE0_hppJT|Sa?tOB#_QK$1$TGd&4y2b9uT+2;nbWx;OqE zSa|4st!XA9f$sJ;2D3?sGFH4|`ezdUROifuAph>duq8ZR6ywE12iN(LpVzgT7*8yp zhWJ(w@3i`9xEHA3a-ez|BILGax?mhrv*FH_w1jCWGu*)q80Ym_9}xY3`egB4&Zn-V zpuYX9gKo$8r{L#vT~uJ01dlDRF9#(}!o=Px=Rwp#V-OfLynS{Geuq2Sx%MI-U#s*jL-hnY|{;y0uIfk zMxPy1kkvkRblbB@I9SQ?OzqetoVnT|G(!FkbcM@<2j{+nphoJ=6wyiC--%=2#Oq1e zy|ry&S$zupl{4G#p^jQkX>(=(>c_KBSZ*QX{idR)zGYy$4`oCq%;51^{p2DWwnzA( zYfEx3reXd1nVU~hAI-rjCDri!H2CX{Xe44jfIp|0mN4oJMqSx?%R_YzB4la=)RC81 z^0LWX)x@L5gpftV99;i#fldNBgx}7^Uj&g)IAt(zR#Jevj(5qWs&nUko6VIA~?!(DBk`t&)PqT|xSa%j09f_+ssI%vE`S0teSdRQ?tG(hfWM~~8Rz3Iy zb$@NP?KzGdiA?5^ZztZ(L;h0s0qri-bzGF`NkMK>q<64kLUSJCSF2`tkTdFo7I?Gz2zJ+_Nd1FG*M&=XBG3LP$I7)4xwsen zetRmLQU5Snty>?tL5GTZ@iyf4zR$LOf*>;3NoeI=LOsch1|=o|t-7W=ti6EhE$k^IiqyHV#@wN(0HAo3+yoo_Wb$S}`EZ_0!9xbih6 z;5_PKs$^HnN-C0JQfJ>TUoX@RJ)Pl|ydvTAM&;F&_ECngu1wkbCk<9D0vD-dD%NwANL~)#erpsot0et*_Qj z-`kNZD()}cri|Qi!qqzSAE*loJVg&@=b*c2v*s8d86u`)CJXQ2{s}n)C!3;BcU74! za1wc-(&OMn*@*4K7)|pDxv3r}F8Yti#|mZ}XMEp}JoGQ&H-lI|OUsoB+bY!4H|Hp7 zX+mCCwV3Z%3UbL}J*rT>N2uH%a^Fz05xkA4xVa&%Y7 z?Wn(hv?$?Q8gkbwSLcj^7GQ5vo7g{efp_0=6*iPbKKUrSHkpLn_GmHdwoWpLzx9&W zA|r2Qe%i>`dJaCnpiDl5`60{82Hg8kAivcm#-)s$n?vul(9UsepNm7iHw2LL?^oVJ z;z1qoH_S2e%k!|Ys=EFlZywmH)+vr5zbPww_t$bg@|99Vu;)+YI3Fa4O#jAl_KkAd z0eQQ%>wSrbbdh7rw-Yd;KwdLEA%%hxx$y>%s#osw!0q`(MG^V${W(T-=U=0~cF)Fb zlkKQ4Z>3zgc484qH@~=^+_wno&Mzn!<`#hHeD-X)bOEBy{`DM+T7b}=^yCN+X3q(iL#TIt-G1*MCgckLvP{V0ys;=3R_{k{@BBNL zwR+?&ciyP`MfnYn*VK+&qr~z2v0_CF`AaV4YNg*{xWB}Uh=p$Cj{~g}Kd7ReW!Jn{ zU3d&RW%;(m-4pXLp1$Dx8~d%-jV>W<9C>j|k!^fO7Qvk1qxz>TiOR+N z5Fhg1OF~^r0hlN8*Nf-r?8tQRG0axPxb3BhRfB^~N<_ zZUHpw7b|DHus`RUy0^400B;AkTJhFJcpsDRd;Sk{_^$|sDdd6=8gIOxMUMH+lP~?} zk#n3_=t^rsed_M8T-j~-ocqN4q1By?`-}+DrEEbv^lG=qTZRY7LwfM8$LQeEeyQj< zJ_qRZ25N>D$S~BWcI41ev{x71T3gXBYFbUwM(Qkp$?IUxbEsz=b?r7IPc8D-4F(VQ zh9IxbuVwI+8|`pFQDDFooaaJPs-McpaI9Z-cPw&pS{=gkDsyCLV$2&JGC>{gFqcHz zR^;~k_xQG9-ia;C(ib5dXEQ6Geu@j>ej>KqRMFQlU!^|9GdLUbJ9f(+BGBGsiuL4t zT0|Z&k9*Gh96qNTc7zD9VII|4o2GTz1>E;!`Pyll1sJsH$ZtwT4mM5gMpPWyg=N=k zPjFn#wwPNnT*i6Td_z6^BHmYrbwA(Pdfd&Cd+afP@lGZQpO55Tj{$YG$7e4lzU4!GXFq+0w-1pA9|@5B zhW+x$En%$@?OB}9YWTV-^1gNpT^yJv^j^vO*KKO#*)x1yTxM`y#=YA+@Nph_$(fIP zy>S1PE+qpO96vXWKDpFg$9+zGwp5z`nuFYnCGm`mIG*c~P{+I<^r!3NK6fAO<5=_5w{2hNAzeOx?=srag^C{kOnH1R^$v43#3DCad)3|-x!KSl?tQVB zk@vj4qLh<@dfTzv(?xN7I{+8$%nr;)*gpQv0`nQpnl+rS*tY<4S(lY+vER7~wI7*u zi||%K@|2y@B5cNgxAu}+L|yix6QXEe+VXmx-{N>N6N!@kf%7lp%1;qWu5Af z=OHd6iDw4wSn$i}f?b&BvOD+d8L|lOn~|dW-e%VV+-1vYi$t#8>NUIS6Y@ORNy@lQ zWW!nI*qj-tVE7;Epr}#2yx)$K@`c7QX%i98KD3VvcGAW>kuTrT>vaAo*5eB;d;bAk zSLxsTDNB!e9yMuWCCH5ri&lGG!+G5L@xd7*3CvH4NiO5YcD2bA3Vj!b-29@_*ed1? zu*vgOkKywpP}w2c7VWchp@-bA<7AL()}8ap!u(3Z&-d%J<{{@9)vNDJxE}r!wwn6^ zpA$d7sz0bEL(Guu!K4*3`pHw?_-o8Vm&q=flUUw01$EvqTtEI|O!V=`@n~H$xRnO^ z{%(=m$G`~3M}Y+pIQ(t0=RWRlBHbk$h2u53<>+JpuDkr+?Sf-y z&lh-?Ym2blZ%0($^dgdi^=nW3bvcmHqUc(ctbPw0}NA@<+;`_$YCu6CLIL<{* z71m#Wg6rz_Bl~UK$#AK=>ir`*)VCKN55A8axsXomO*!{D2uiweF$42u~5kNE?ZKET~(I}a;kj;CdC9Zxx`PCf*f&->V0t0fwF zaIQr2!XK!&-t1JvY&i>VtdkM!9JBCSk^hOv`V7qPe)yAzZx*Npb9Zd_o&}*q1v$XH z7P^wuJUtCXt_M%#%FVzg-Q6$D_hum0b<6pvOEchM$mms!dhJWbZLu|1@cg$1N_+FB zfE30uMawq@>0^pB$^_o8VwFSY@g(pq-_9%YpMIR3H?1KW8dYRr{J3M^7%arlMvbN|7qdrB$$7@OG)8138#L_{N0|4`yQ?_ z``sZ=LJuRku~2&oxo6!CnikaQzqw0PUlDnDTlrzJ67(xMG-z~k&A|5K=e-Xb%)qwj zJyWmm%s~83B4lp=40uykvD>9i!-h?Bf9~HYSn3IAy>B!H*;jR!l~6z4{(OPN-xRD5 zo!X;c0h0iKrrRfiFn^%&VU1DDBsehGwVP(6|7ZOC^mNlClNe7QOXEC<=lJv&E%;p(2>^Lm&s5Sdp>jrVts zZlp+L#(i9wgk#giQD0`9hyD!KH-d9L{~6Xl!(s2a{EO%xziKB%fqr(=o~WJ>LDX$I z>R}>#YZQ8uwcB=K++c`tFwOHj?xUNL#F!~Pi2K8SiK@Oh2&ZC>%NB1R1i_qq`MYtr zKd)5}T?oej?54i3;8fTT4~n#6YEjQ;KqaK*66*PEwg1xB)zJ^HnA6TYrA3_}n(N~$ z;>g?cuCZue#rtTzuAT5jPCfIGqIebVTN|g@Xh$^&=D`!FA~FbDvwqz!)EI7 zG0x+ES0t;l33cc1J-$AGaSb0DJCQ)-?6afTL=@WlLH5szib;1r5SKR3#IE&2o^RvN zgq;JRNNnuWLj9s1o(J7+^ccrEo$HW}@tq}ZzqTPp+-G;S&zAiX>NFMJ3%`Y2`ALrI zdDoqTaJWNl?@iS~X#cSyON;TQOT7`jCVd!J%1sho)yB98RpiUrp?f}vd2@dR>KR2JX`4PU z0OpG(`cW9q@er<C_I1JowQ}!6ymW@w*+`=!-nm1v((LX5aP;=a9!|d-!}A z_q$#-8S)r9h5F~4>N8)FL!azVD3uh&-)+ej0vIPv60}bCc!m3WOZW?IEMidXHE6=%+I;)9dNmL-vwdlfGvB6 zk6*-iWz@kcN7=_1FSh+{eaRj5$r>;7yvB0Od47D`$B%mSomN}!?C*f(Zl_l}^s!!V z$`n{i@N?6?Yd`2Z;eC2c9;09D6~+vjO%d>8fW zIYVdccrYK~IrB!Z#1rOloaGnI(7q3as)ttlj0y%)(bjr2WzB-Ty4-ha&tAlN)3m5Ms*P+hNl|c5I zIv{Pv6|2vpzM$BF++)lnAX!ui?&Kpux>W62kRS=|bcUsrG;;Xq#c!pN7icL77{2O7 z0-;kUo?cHxp5jq%-^C;n+`W=`jLHK!_xV_ziEXZQq&17U!|i)E~WFcV?yUE~NP8rNvb&LeYn&Udn=a7ewu%#6;CxI;oZxlr&)>~}M{v`4m=3~_xKKS`Gy-ouo zmNSLN^RWa~N|iv8u5h<;eF?B!dYNKqFV#vc0YlZXVAxY+Eh_u94M8{#3+$@4z-A^bI*6jl9v&pkjC=-ag0up$J;p&lp``N8L~Po9>16g>c`j3-`{%ziV|p z-cVTtV{^1&r;t~VF1M0Ts40fc_Z-g!@$UtmZ7UF7D}r;fMO&&IiXekdY;Ejr5y%o( z6j`Q=K$T8LL9D45PAxI-p?F*Z4rUL^gOLB{(l;6qx>*9J-clFw*%rgOgSi*u9E;$$ z{@2|b1BFoZc6TPU7Q(T_{F*a@MHtr;){uW(1g3}Ex4g|O0v4Ym$97m1LD4G1m>5+N z>}U^EOC=RzKaAVNEER(1N3(OL;ds4ppFKA;i$Ok9VxRcuVvu9lsmjt=46Wher1;~- zaM#+cp~s{M4Bjj1Mf4W}^=Gpro1Q{&frp2yuOlbc7Wv^Ga&46#DXTDUziwQCW5v_ zI}hFlaS2d;ClS3a(j~>+XqQD5(=!9gqN9 zK2=uu;`R446lV_iB|@vNzRwQTWMCJ0E>yTR1+GbwpV^#EfrsD5=;atwU`|#+|4Ldi zTw^ctk#$Xm-6}s{>=;Rg*t5prY;DLV>U8rN_NT&O>r_jj2WcSt-FhPOcpB(A3r%ig zNrlb<*Zp9X4BnRwGNe8rZ+|7e`uo&#)a&s*#2Wk@ly^TbcqbbV2P5pqpP2^M#xCiTbzu{8)&P3cy_d=+>P9lW8)UNhCm;_y&ag}Mp$#DKO?G?Y=WGJxY zTidKkhEG}30$rNOlWzU8xLYy_SY&AwUQHx`0nN6PFRYM*U7LKt%%2Fw!P#>{#Yw=E z{jT~%VKP2P)mx8Bq=3$yweauaSpR9R!OPvr;Myy>)asQCg3r4>MxG`AhoApnO+)|x zFFYj$4F%;t{`{Z+`^RC|KQBf}`L8d^SpRkHe;>Ev@BjFPl@GTX|L0F~6ek!dPW;{a zuk)z!+kgDNWy_X-UV!euuD6v^FXdlnREPcR_^#4`MAFfSO1zL z6clp*KHzc|I-5hX@URsEfA7_i8VLH zjz}8Hb*Hy+yB^cQ$O#iTq111! zKcq<%sU4yEOJz%phAOsKKY2iqUz68p8H@-unQtJ0e6yuq>*>h6ojI*e1QCPCUpjTR&7|3j0+VE`@J( zfZrUl^Mmhg;mjQga+$~#%#V9x7VyB7_>6ny9M+&ewGXZM}Qf5Q~pF-_$9-X{uD03BYBP%M!|c8uAh6mxuYF1 z@^_%pm(_~keG!nuLL4WQT~ed`4erC`SuUYX_Xn_6vU2D*+(W->>XthjXNio&!b7#M zt%=$yHY3Hib_7Flv-Te*QzAdgw7BY)26Q?7n%0lCg($9z2g*1c;9c7rMKPIs&@Eo2 zs#UE{(#%s}q(;gIB zLuI31-2v+IM}|(c2872cm%De@Tp?}O3wG0oULcljd!zHFEs)CN{d%`wBV@R8p4y4K z5FGw4EOpP^h;(V?h@CG@3A!u(;rvwh;O5-RA3je!K(=4N%`MOas3s6ESCwjrd9H1yh++=(SB+6Udn4ut=Y zcF|Rr@v13t3bu-ij)k9aLsEQl$$4qg!lL9+?mkN|kcPfo4#2;&J%MBQIzzYF2W3?hWDQReF+gHh|k5 zo<9E9&!<%JGBo5tl%d+z$wn+GYoRRY(_9SONQK!C5+_0If+Iq_Y{OAte ziC6SRzslIy1dSU!;G6Sgsk=z1nFpN^3h^OYx-9~lSb_<0!#N{U8DFA#QzlqUNQuz$ zvFE=|_J+sZqE%VrA>iBbqrl@_06hE3nd<%09H<$2`ZEjd3A0aY+@h|21nacXp_oQ5 z;{LVK3H@VwM9`=5wF$}5twlE3&Y;*pUC2)n0EW}wnVk##;LLB`R~@l>3Ck0n8IWXVLo+YJ6xabJ;q_Yj7yt0@8tLojd8fF(NJ z7h;(-734QXLACdc)NxKf!Z$XJ+1ogr*tV}PkNar|QFDCF6p9^)ERy(jDmyE%xbcmj zJIoi$mbL_odzYMz;~luG zuDjqO=nJFjr@Ce00-#1k zK(t?@<}!VGkN7eyc4D~44#M8@4KW_@0i)&T^I7XIFm<;4nb<>F%o|MJsFt!L>bkDP zeCl-|8t*rHZLv@wF84YHbVay>Y;2HKrDia=ehbK4undA-3qjACe%M2fUC@O&d~RPT z5Kn&eAcV-36y9ckKb*K|84$G+u1nj3Qekddrh5Kzh>+io10=d)Nv0vN`!Pe>LlWV+Q#6g-u zOU{D}x)usPWxyc`H_XY<38wnf2>wwObLm61^OW!x?M zq%ZW8%du~9H6%K9>ZJ!h_!49Hg}&`p^&|XR&4b^mSrX&or}Q)mtYN&*rXkGJ6JoeE zL(DjBK*888Fe&^2!Jv4(&Qmay@YeCTo30u`oEVo%cw-+x`|l>El24~gTvOh>XB{fMDRi7IzD zE260LI9X-n9+2PNEiUqNfV*y^gN)8spqPLBqSLfHaYryiu6!hvsM1|aGT#U#=0bF0 zvvr+`YDq?xlL^kif5XO2;Y1!>OuTWx_0V%)=98$j`#j*?279VdCw7ijyq>3(y?-K0mCVZY>Ose zf{QUa9-#AKIeK~*Le=n&H8ZQ=ezeazex@0 z1_xi=_!)+uCRzC`_k`iAw-Ikvzsf+YvctK?YjH$geJsb$5Ruy#V32U2F%=YRdE$S+ zb%LF2<$y=tamd@(bN&xgE~Y%Stv#lBgo=;=ENXrgwGPvgE8oH%v=Uct>=j1M;M-!jj}SKpG0)(%Ia zXrUwJ29*bBUeB#r>P-N;RPdMQjE7g}Y}7BayTLmF&)7aiAAFubnlt@50%@d7MYhE~ z@Ivu_ITf@1Fz{~gozDtsQ1tv?Q*?C(h)O&--=Y`|j7vLwCvDvERv9t&k9A0`7E%%M~@ z{YQ|tKh_0^Jd)iLimjWIvdxAU3*w--l z!A5a8>UIbonsM&tZ_UE3zf|Y`uZ8WkYr@p_RB3oq*17KzqXiBM8)c51_r^<2H*#|B zdSMMmwc)0l2@VR_;!-AAV}nHlCz#O7?_t%LU=OSy#e0QZ%xx-x3Vn zj$HF0Q3diM@unA5>r$yR2LCSDFyhtsJdde`q(I|Cssh_-4REwP=0ts88_w+U;+4OB z4L-_L&*k{tz`Z74QkH($;y;Ve=h${UaKQCx(e7JsG(4wT@$s%ZX1L^6w&WRO)E84_ z(O1TByM6w!SD!bmtcb_{yc7!hQnW0Lg`sfT>22G&Vqehoeo}U?+yYv*lBN#(=;Fh| z^H0jq9n-Iy*o?jK!*Oe2e^bf;yw1*bZrL05R^grQ z@#c-(Z2Z=6z)Xt31p!ox<_6P6kdPps%F12}bJ9P*+}M*0X;w2%f)68b{BVp?#oavQ z%qeZ=4b4K!Ly4zSE484nw4Xb`ns|4s5ba)0s)kMLOA%?E<+^o`(NE7|*QhM&XClcB^alk#Lb>rC5`!1Z3hB6^sdAu7WDf z6_L7Z7<))hS{vhsT$GO~FcBmAsFN;6 zrs7D9#i~}V1~PDemp87=0>1a(RQ{%f;2y!h!paZxu;EGlK#*A;-f*E4N;;i@1Evi+ z>ee>cw&p%{zRLlbt;L?xvZmtItPRP>+@&ZaGj}oPUIpqOYpW?8Ekq*RE|Ypl9W@7Y z{$&{u{w}}ir_!D`!u%0^#n&7yL>}#i_6y1yaIoo=GQX1!w%N(Tx8{AYZDlQplsg?& zZsx7F@TK96`jHpB$D;8b!)nZ`Y7~-6$Xfr{&O(~QJ^5v^m3VNdo}cb>1J*a$b$WEv zqos}FS=Dcac-he8fV@i-Wcu9_wj@k5e|)Jvl|9=AH@V|TzrSt;n}qahLq(Nf$r3bC zcsvloa#TK+tZLXBW7d$Prz$kP=g?MIitp1?tq`)Xh`0b_!Gl+tdd;}xd^N1F)z`@oO!eFQVn zfA;Kf7Ih|}XyRp|8>O`9qta3=?vIRN-m1iV9%-_-Zj|FgzR6*pH<<|XbK>3fW;i6u z`9O2~HOQRfc-#4CK*N_hbCp-p2D++*{ z)9=k#P8R%9_H#1h@`n#L0~@txlkl-wj{2Hr8J=~JT3jZn!4Bn58+2Ezk@Tlz-TC(= z=$yWwZk~zHbhnbcp#yHxqA~ zb{s@r>OX1PO62}-mgQdDECnUMm}h*?N?}J*YGD7fOfZ}MDI)q_9aT)#6;m$fVn=pM zPv(7{X{!As_&rgOa|IUPG zAw`iVG_rs~H9~&%bT&jPJo=}zFAvzB6nB(9DuCKV*D~tcdEnEzm*a$NGPKbMoK0YL zLPK4XLUy@4ETLC(bXly#fCJasyS!@9w?}Y)VtEA$M0`FB@;T_t618;Sj;Q~U>86L2 zT%kwyV@i{kKb%}RJaBT48?36caNn!6g@^pL2Coc5z~-BSXis7~oEb=U@nA0|^4{ne z?FZ9wlQ)+3s!TYZA*qV3BkHvax;#9!nGWXnVku?^i(#@PC(gpE99UkEGCiFv1A~P= z)@8y6%4vu)#KAj_(5H^kAK&o?r{&z@Q|#_Ib+kGpX99z^ejfp_yA* zFqB&Q#c4MSNIdrzRs2f@C7K(3LIGifUw!-7?I=Ec_A|DYujQb@Kq{Nzp3(h|~ePgX~XSGuz zk?6~AQSCEZbk76X(z%jP8f7q2Hr)L!xg3_f$Ih?nl)^5j^#xba90+|l_brPi1}@b) zQ@(8!g+bQM0|b;t4#}sR8qz6vV!b_?CL|ME9z3bcvdY0O=HarV?m768Nv%vHG6Pw4 zbxf)55%-$w9-!h9!4d_N9`n^G2>zgO=CW}Ha14CNb;!zwfA$&M#nhQFcIx9E^GoqS z>G;dfe$W**Gde`NMcmNy_pCFVyFa8G}hLw zOcFj`{^!+i@{~isvogM=$wDywVSG-1Dh)KRmdbY$^D~}g?_aOBU6I#!cW%Wo4qL*z za&pNsP&<+T3F-bE{MOyz+9HsPk>NHyvNKsYsc@0(BC$Wt4S$wrJbbXkr0$lRp*MV@ zZ;wtWOo4a(X2)5kvOt7R-S8xV+x`1`ziRwSg(UqT3R}uBXf2Cl3$75yZ_Siq(r{=ZDdiyG^b|w{7jRQRcu4Q6IaTA#rTMiy;b(XbP&c!*?PY(uGvvAJwA1#YW z5*m@+BxB{WK<+}8hfDk7;2qPe@ox_Ku(mJLpX?>!Yv#4*SV5bq~!)2)qJbJA`_n2rjI(;Dzt!LrFsB3o>3Qt5s?|3|?eMkmm{}o8d@685@ zX}6|Rr!v7LD&C-FJq{$U9FRY9-v!>3AO7*+iW?4$4^VTRiNg7JmJxrblJV4t)CsGi zbX-WGVIv(Ses_HmSz7X3OgLmG{j)v?FBro6l*KY2Eq1Z`^`H>?9DO{($RZ%F_CX`l zg=D;aljjUyeHn7Lv)ox~sKR@Zx_LEm<>+NAb`|O~al0hxSBbO%_W7?*%+18X3)VQQ zDV$NOJmSeQtQA}0%NLj-BaB~G}eAkq-up@9%@$CCh)=1== z*h@RSV+I+Ab?<)u=?5aR>K_E9TtVI3pghmx3Pi-SdTzWjg{^oqhZy!~F#cPdEl%Xa zlaD+pydh8qByS#gGwvyaae)Jpx(1n0CF_)D{n!f47<1Zr2|PC4pO-J5QHhN%M}J>m zt;Enjg(e#9MX0>Vx1GB`8ZYJNl}eBL!Nwu&Y2mmucrJZ8m!~@mt{=XB;DKB=un5M| ze-q3B$yK>r)lYd)G}Pw#c~2o^eoq}6Va$W~1DPo$42jUvz19BtlPeZ}Vl=#9n2TXG zG&()L6^PYTEGN0Eu+QN&mD1-@3=cgJYfs=HM_YamP8nP5wLThC+vEkJGUJ`J3O*na z(UG}x$rQvdZPqGKTn6_PJ&~n(Vs7dFHf%T{4N_BG3NK$OCiDX`amR#%k;1N;Ybq!Y zm)XAy@>b{JWYgn42@Of;d_w*VC51P#FC-6(^#JsQn4uU zKwvsl|Ds)qpZ7&6Dt(uCU1@k<;ld<+R|;x%w>iq*3P&I6jl17Ygrira`fFy$L~(L~ z4|nY=aC?QWf~Kh+S;aGCXH!oQCs%v@!S@I9`jaM!;SUbHO+C6#)BOsV;%!s7(YtjaKM zq)tciXa4agmtryCiqN^w=^@C)6L_6AC?0QxeN1{TUW6(-9*+69Yw&(fW5WIT2CU%c z{3NAXg`Yn(gae+d^g0l^eui8) z_M?5~VMaNWl9?k%Ur4ea3=2p0)QY&-#atxSJ*D%o zAQQ)xnrzjjOo$xP4??L4f$Vs&E%tGqlC`lxC1!ZD>pVN<-x|DI)#>Z1weO;<5kbSIM|`C z%zaJFS4FkE`jvmD;dK!fU7@KcjOV&l!1E&lxVDyUbY)9`wSk3Y_-{Ep-^V-8DUk#B zEGh&9V*@ac@7K_QOXcX-*px+9REre$1~2}dB5;-94MAgK?h|*I$9v{l29VyI;a9m^ z3c_0nTL%X7h@6}Ij$AV~@MP0Yoc>G(>SiaBMEd1n-rMWRS2v>YEql-vQtnh3d%Mlk z&|U$~^7)*3Z>u0$gLkcZ&_V1vsg7wXSeEVWFccv8m?y4{n*GXxXH%qgJJuF(;9T@r&&4D(9TAydzm7?So&!{S_~^df@@H!lVb+?NRN~w=}M?!}u_Dz)GFV z4qldyzdR}H1s~kM*~E7HfYrhW_d9z%p?s?Hg3p*W)P~yjxOyDJ2FVK%v9VmrkLPlSBKXGX> zkV$rzrDs^`vdK@ImsuL1=h@rt<(e+2|F{U_p&I*vUajS-ws*IM`rK+ z^h73)r*hibKB!ta`TD((J2J;?Z@yVI!19-;-8?4sfb60c_LaE7*!^z-wl{o0i7!ON zG0hvcgxVEOh`U1BuSdGhayp>e!l`YNsgLf5Ubx?mbH~nHt=PnIe>CcwX^AV^MOPf6pQFH#xEcR14GA$-!d5Er&i4~T{>=P7Fcy>>39^Of7v5fcsmK!I=70g z9K#{sY~*l9Qjp9=t}H3?K%-pDc5^(nw=eRDLV(ji#&$!ndz` zf?v!69ZOd#Tw8J*-44$J|Hw5tfz)I$yp$ul!Y>4(ZnY~17gEsr@4CX5{drjErf`MI zB^$5xc~r3pN1`Fqp|%}K50EnDXbhlAgd^J*IV^4&%z%lU$}BGn`Jt#PR@mt|pp0ks1e4v*h=#JuJH0s1i`|75IJ)z4Fo9CII_|Rd>Oq!QoI#Q_}6T` zQngfquIqFxHbY^^8SbB&_FoLXi*D0@e_?eTu8^vt96Xr$4tl6T5T#-y@Vp1)4fIJbWfdg z=1%D6_90_ z2d=_J9U}Q%ol4|hy?uB1MgVkslwJ1NZZe3TDQ@Wog6H(vKpc&~z@a+uIu0S)rd}F#wfp zA9Ib`B%s{0>S}}T1Pt9nYh#cSj7Ps)EmwAFfZT0g(Z%?hE=g8!6%U(C^x+JNZS`dWY3O}{qcDSLvtWxX^85^Kb zW{YL`7y3XaIOAb7 zUSX9OU%O+63nw#9KNIzY<3ZNfHoC&#O`Ou3b`gIVQYHkz@5O*F`<%URuqW~g`*sDH z_~2W~=R7BWC?R*slN+;2fgtZxXWR554np?Fa>WpRCo_+~gTR(2$Xt|r7Sra1-|N1UP2HvGe;B&u)euYWZIiC>0BV#6QX7Nz?Ig(9YiDIva7%9JKcSbk-fFs>mZ| zKDgulK)qGPl1Ln5iM~@r%$+pO56Rq|FvHmvCb2>jA8>Y=9`t$`0cM;Ll2SE+kX6dW z#%-&Nb~fXGww{OM=&F*5k8wOQ4oT9c%0=MrY|}%x6lv6;cyBe!8xGRD4Zrj&)l=ISwe=Kxd$^;}RADPF>U}7BIb`j<9IA_!Z2QCQo_WFX z+fuZ86Cs3t>+sH+p&K}N%K99uwnO%48_655B2ZRPsKvH28aX*zYtHcb;%S@dv3W9k zXj+vZk-}*3ew^rIrI!FRN|%N#y2GHiPlRcTgNQu5xprMNJQTm|Fz=lgiNZs+f`#;T zzIfvKnEnr@WM_+s072ou5XJuEZGr;6yIrD5;*QnmGsWGULf2*^WlJB zStv-?Q?POza|fZW4y{ISL)>^8F!tKZAD=eSS8`AXqD9LA8z(YrtUdV5LrlyOT)L(D z+U|vbrwheQhRZPyxPR2gm3A3GtPr$V!0SSTLd;5_1fGZZyfY@U96?1*39 zj`7AySizCciaRq70T6wIy`w|SA7njSnI86-f*<#Sam_PFoHnAaJFF6bSsMmBhGkwT znVMwub>CjBd-Zg=&cqjrRN0$*eU)rsQBg<7Dc5^RVq%spabuZEm7)bqQRIEaw za_Sa0t#26QKA##Ol9M2Gwjz^*#1;qjRtH6*!|>s6uW*EA+i%&?_0GhuR59j5%f%f0wL+zJ0Kjps{`)L~{t#x+637H`g4-R?;8!kZksSw{!mkRh+GiW>OEK^2Wg81%w!xZ|Jkd!esPyf9-g&tG%2#)p2w8H}mggfID38#hCO&w0M%5SND+ z*y;S1p!{bEvr@H`3$BLv|2-l2e;@F_9r)i4{6FtN>*U8z9f1-^gm|Rf{>OnXwWDOo zqaqNnrdII4SrOFa9@zGlUkB~5h}@v-lEAP`rM5nC0o|io^~ye|WALLZRSH`=c%3g- z{N@`qOg%5~;hN4lylvZ2x3^XfzOBb_KjKg)bo8PN(RWoK<3H-F1_c~Y9jZimU{C>t z&7Sr&w`*hKz+|FYh&GlU6!dDJzJ^xZhdv)w6$LZnTIM~Xn$Um0N95;810rYZ*~jqx zy5RjrQ#t#TGKf1^eRQg31eN>gZM{w6_>*LLfJ0XbSH4$=H@_D`5o4aea<@<5$7}v? zzl9w~DK~M?%Qk|25XW!S1%^F|H*gFbY3DI54jZ{YoRGtq=9#@m-Thdvgt-VXJvw>>&vkSsv5! zTc1S}s+)_B;Zn#v$V^-F=psHhZ*<=OhzsR57Frb9X>gNs<5NM{89X_Xx7!dThV;o+ zToD)5@Ue>Eh(MwyO3H_N(vIokta-kDC$Anp_FtmTZB#`Qv8ll;)<bBIakk{gLJ4ra*aaQatgm9K6(COy5b=!C%S6VdbF?*e_0^9qQ|bZEA<4`*>V1 zyBxy3f$l{XK~rFeeB9Z{X$y7%=bMi&Si`s0s}8s4je&&EgS1dW z89eoe*uwvEK%`vlewx&V;p5V8YKi&Oh50qUW>H-rJ7LSr zw4w;ELzcb%MUU>2&FojUzbA_y=#FJF8>yh^($sT$V^tLWqpRDOtB!mlXV1BgXyd-3 z7IxcrddL_%<>e-%jc3y`zVds^pmq777l&KrVJhr=^SfX8*z*KJ%xqd8Ft9 zx$O0ywv}Q?5qE2HNX-%r61W#nUvtGzBDxH-5^hMk`$M$V!VZ~t`)oq<)sQJi`0m9W zHK0GlA55ul1@6jA3hn8(U}CcRT(Q~$GMre4;HwtISDqsc`Em)gzOCwstkc4-^QX#8 zuZytW`GmDDS`C&rbH18X>ca=$t46Ap29QJZyGNf-1I7PW)fuC)Eh6gl4VQf`%7W0cWm zqm7Ogs`_4!SR>Iv(P-{J|7OJ@DMa`4y$i;$)iptVq1FuQ$=wRME!2Qqkd%XWTLoQS zyh(qMZjIAJHwS)Paw7Ca2c$xp>~Jf_Ho54sDUt_~>o2{&iOm%Uij6`Qu(CtJ=^(Km zqo*&5I_X;CUr%YW&#&z8a#`Ku_QJ*B_LqAZ3>4&+BsP7dw?}L=cZJe zKQw%mmVQ3!4}yP>tAV%|c$t6r$)95f>i#ErTNgE<^hcZ0=hxhD=)ao`tf^mi6xQ_UJGC=al@(}v)v;OZWww~oc2(UGlmE@ z@#WQ7p{<@*O|Y;Ec6|^@jtbL&jj>>h;1wH?mM_gs6?cTMw?un_FPi~_FoTX`jvC%% zo$XxP>xIAGiMo_@#aMW(in%?i_hj(m_WRwUHJZODM9#1iI zQ2ADSayQ!xOnP{i+I<2*q3X?hmq!7>Uu$hly6p=ocYMPC-136^0z;G5Lmr^|?BM`k zg$G>Fztp}-%<)yyWsgvbxkEQyqvNKc0~|cJXKh+aA1?dLx?dU+M+@_AXUj!1Y)gEh zRhZ<6-RcfO35VTKA+7B4++8=MeB5?ipw<>UYCVMLa zw@VLIPT3K<3Ks2$`f`NMAj*4y*(oSp}){^J~G zz2OQL%kwwIn=BzfqIOgN{8c#99m!enSPQ9^ZqxEj*1Ui{fK4}y?jt3^BZ^kd>vVU64I0Oe*+vuivwC&QKtKq_qrc8* z(CgsZU)#F$IY)F!Bl#8W=7YB(#gNnagA>aA5VM#y@ne8dd}Q#F1{Jq*A_Cy|!L))1Uv z*|%#p9gI(?I`;^K1fu*w={LRh0Vwz1T`Jncf!NyRXKhFlh^FyDqz{O7c!n+DxGvJQUav_$dX({A<|W9!!Oun`8|03<5QamOL<@-QN#2lYi0t-S3TJiJ!A>O}gQKPoz2|du*{#{Mdj|k}h#> zYi(?eMKMM9!K`qbFv?MtsPKB2;f0_Voc(`&@SYG^Z;NjfZc6QP)*24GMzL|t15^{NsZ=x{C|L)d|i6`#6y6&Mxq5&FhkCr~H_yY@_|Lo>O6wF_a4FIPE z`12#+loN&b@pLbv%X+DB60M1YR|E?Y8Of+caefOz;EJKqxS840tGZ2zA4 z?h6yNp?XK(8NuoF?xj0gW+J*oRZmb2`3A{nXLZ* z9H0B(ddOr=`cY53L?>l?FWwo&XZNLFvS*LXt4=Hv?Z`t2L$_k{z=54vl0#5>io*Rhw(RFc5_`$@|OQ7N$c(6ma1 zJQbp(e0(ScQovcQz<47k5%Lsh&YtXx0k!y2y^~d;aP_|4?z*Te$gjL}@1W<$f8sUA z_KrJa$3?lRWePu>x)^)w#_vFMm3?u~)q)Q@XRI%@c4$HMKO@umL^be?=~(`0p^HZj z?hSf#&JWiQ@-(I?#p3VpCex~y(@@c_cD$=1gTS%>rPx`fW1bR=$X@b9T-af<(l!mn z;q32Q)GusMer@Sp7a21$(P{0a={}D|Q$|uh*RAk)LF2pd3PPt>Y)|>LAOQ8qBIa)u zdtmc_Pd0w1E5MmSS(+WraG-48ZC+_f1NRv5Icwwz6&)S;;xY` z%r?1#(DSiJ+ZKg#M`CbK#oh~5&n31tQc1>a4-Xc1x%cBHZnkL%xof2>W_6x{%Fvz-8h~9-Wvq|ZXa6GGlrr6wgs<%;&}cXNEe><|9LNc~_f?Os9w#%Q$zd|Fpd=mg4^dbf68yNgA3Te$*AjXig@=8Al5Fw4WT;f? zLMVFsBxL?PLnP7ITJ$rF`r_Ht`H-gl5>O*9xOQ_i1U&3&C*MXAdccycvLAjaFrCV& zBS^eo%(=O7e*7;KPQAb0l$`DWDc;q_n~yX>Q$zJdg|h*^{b(a$^cuR-_dE$-M~=!YIx^hJRVnNzWUU<1Orv1TYuBl;05Wax$NzF zR6nbwm(*T|(z7cW1D@sB_4|gq?blfRF#1K=fFc#h)Wg`V%`4%;8q1Rd*$wcyNqJh6 zsSzwoPde9H5_#U<=lEw%Q$6J)}q7iP+6cLQK3;r7xF{Ig)X{RrAH@O~KEj z!cXwG(p~D>=<+e9bKmbLX#`Kwp~E1HGZ|TWkH{=$MBv)Pv-F{1PMF)MCN#q326QzC zt1>7OA%=mHfB9DqWO+R>6@5nV%Ig`lR_n?@FYMh=58-=OxqD*ss!JaH_#B_Hv>Xcx zQ|H5gmZ)!j9+##wi1+&3&8o9qggzj14os~NEuv+(6bH^ zO$3Gq`r#^%{7`aN%`?I^3)klsx9%DhA%ikWn>Kw3N~&@FH|bP}xqQc~lPn2d>Rc_w zWPci#{aP|RyAX?~ESeu>wFMy4N+Fl<0tv{ZP=7j05)P`T9?~9S%77O)=*bI}3c)k* zruqxUGGM!JNdGso4D5q6_xc-zgP3hN=p;vO<;az#_QP7#1(?TL67x2k)q-r^2j5`|n3Y z|C=aw`SBZ}d^8yQY7|27s~*h64bM=tBEPP)=Nv;DzPXZog#UOmjUw#% zjF?NHvCQf$yL36+)hs*SA5{v}dM}l2CbA*<4!7&9f+@-`!Fe&yG%&RaI8IIEFuR+a z`+ZNT76g)}4zwFI0-680Ox4OJSgZbU8L}Edhdk=^*h(F^&bXU>AkJ@a=b*igc{xnz zOAbukD}cy(Yw1n(G+;2*EBoZ@4KJTr$ zq4NIPmrDzgeqfX00~x_Hf7atV!Iq3C^S4L}ZUv(be-?E@xf3xDeEe@{(E|nRkHp{B zi@_~_!`IzcvN4RemZXQY40Foo>Bx>$V`9lOvyyMMcxUDg`@6ncteJXcuq;}IgGYm2 zR}%ch6kl7pbn_ULQ48U2;0gimxJzYHmHBY%)MoSig(@g$@EG%Lu7}4oGE6m^O`sca z(fXNT6I3>vBsagm1-LV{-8hq>1oKFMgxYt$I=+3)L)MZ(VX*pqv{0>Zf z?U8BVOf56MW?KdWtTXHHR%)PQW9%gV^9JxTzBcrnstF{tXpXvZG{WK5<6^%%>)^;+ z!FS(oR1hhq%?VA6eW46y)bDKy!}Y^9+V4+X1HT%c z{H{IGP&h3wHP)C8+({)YDJfYH_r>VE!JQ0{Xx#{WK#~F^`WgB4^|3(hYets#I1Vxs z9KOG9B=~5?ULE`Ea)D)g?jz}wA|lW9oKvC+!P`Ge7V*wF4UXm-HjeYzprJ6gLy~+U z3ehC$44kaT+xsmh+$h_Sc}(Kg#h-1+@o1Y{oudU!kDbZP5UfJ~LskC-5w`m$J2&7FUwezII+9lo$rPYN5NE}=awk3nk%^R+!{h8pg zMS89EbQUCr6j{CLNCRs7TrGW`1h~7C6Enpa3yk|1enwU#0W*H1o?^}d(<7EGCm#^{ zfvY}y`iUHOzw&bW%N)7zUzJ^Rl5;3rk(T)Qt|l3`e{VC5xl|%moc}k2|C&*~Z$_SK zr48wvI7Br$+lcuKr^MaHdi3Yi6_lYXM*js5Im2N$v?JC0$GSfqjwX;(-j6PY=Tmym z?g&i*! zzthSki}BTpvB1}}#QhoMe!x|=4h`uzQfi?F`^hDCJ}i}Be}d|sWUdIb*i#VV`6LAz zRo%Yys8@idJXw~PT|GRS8($GnZh*+kCpS;+tA=)SFJ&F7JUFQ(G2?yT0UAuEmdqTI z(a-Xv*bmtPJbvNsGusy>sPf>1;?>v1xUaxrfjX%G3!TI3lU8!DgOY=fli)4#^$qd= zy_F$t-x4s>oD5UIa4!Qms#{%HSgTp-*iIWw8DAiLIhyI=mWAOI)Q*hU3Hb zPg|?KU_4RyW!IT7%x1}cWU7>d?t|9^GjEn*rr zNs5HyO>>GroKnCuk}8frA{DX@$w&7Q{t0R4qkhGT1pssGG|8^6DVT%|&}r4_z_l^j z&Z-G-2)Or;CuS6}_^U6h9_})!eK6-}AfleP2Pae|~OYp^<+xIufZb##-YBRq2;uK7; zG!q{=osJ3#7alM;reSCKl(5mw1nm8M=k^$VC|15nHDY*ag>PsDPUU5s2d6RV-G9zB zFz-E;$wcq(**^^r_-_(NeXzW3AQW{URe<=Tq>!+6v(BB&1 zcc;@76Kkoq&Jz6RJZ5(-m&4xpDd6H~?mB0*tO)LHVAeuc8zK4@N=2Z!^6TzC8XZu0 zd8J^uYai}YxYJ}>?SzGFu4ng$hGF~P_=k@Z5^z_{P2t0hG&HYlo)8sC$1_@Kj&qoT z6V2mJ)k{&xeeQ7fw^2_#&M^>FM5~PN9fyv2h+F}O_Gi(#Dr(psn>v$P?1m8~83K%_ z0@1eS?+>;fe}XqTAvH>Cf$F-uw*RhpLUdF1g$I%eK>2}Aqh~1-u-M15m4x_iPn&1r z3%MYpUD{UiJ`>_b92>in5~1zE>LI?S01zC1e?Zsj5=@K8t|dNj!G|-t)==P$Y|_mW z+b$m1EAU-qV89Yr#zQU{x&)!N(%GL|OaU0K>1oqU_+1IuH~4+(cfrb$Ne-3*Gn{c| z6q)9@hMc9^ueiMSBF~}m)i(}`SoA7?U0cBpxdn!D|MCUk*7tc1{@EyO=I3)hu|El~ zHZ<{9iKgPt)%6Jn=48BIcIq6>W)$wFA6O5X^}s?J4M{s#W}~q4`&s90p)l&9xQf} zUmyWgPA{MI_!JL!>yyXl5{UJ(kLxrHiDQE2M>Iqz|vcwNU zPmbgrS4VzXjjunNIf=aYtVgX7CpO41rkSSbVw5h+^i4Zwj4)Boq0qAR_DbloBk-NH^(M~= z0=vK|1=HaOsF3%e`cFCr7*(!0FFVG;Y2R6@6_NxZM|7#HQZNDj4u*VGDu{y}-}Up$ z_amXQF8wa~W+0pz4m1+{;sB@k{)nr|+(Ncvo9!{@9MJu`4Y|;Np7`p*oy6K*!beBX z6d`WwgPTuX%_?Tx2oHvi_VgJGq&a8wJb4c&agt|lc$_TZK{+q)O6CGQ$2o;`Uxorh_gt?N zeHvGlBO46xg^BGOk`?X_P~+a8wa;4@!v3tN zDLp$tJomma)*sNv*9`lWdD$JX=vKF8I|IS3I**u;h4|p^orcwca4&pPk!{P_=7_IY zD+i?+RZ&L>4zJW`flHk(!%DpcEF`YlCVVjhe%jNXJA+ry;YuyXo*)~7C)jQ|$K#1c zw<|Uq_5Cn}+fH0)#TUi9HAj0ni1X)k?~&t^p4eUfG^*sBH-2~rLJZ+PSf0r+`k28J zKc`p>On$zLBl00XU-KEjxbs4^(76B@sLdx2O^bqj?~Sg_qY2;=bZuLpDIVlsMNB?d zjDm88{}$vjgP?o(v}k>h8%*0z)*AGg!>uKuTX~;kK#=T3#-$7eyzPC6(znq9l|F~= z|EA}HEJ8tLWk0-drE$A)#Ks9vEZ(sDt?7i5@*cOUn(a{SKci(gIui`uYUt~_A%MeU z8ane|G>IMqcehz$K1X`S$$_cD0*YqRPCqBSC99&+>0-^==)t^O-<9r)DLW!+f7}D{ zi{$q*rHK$64d3K_d@UI7$NB%5%J;<6syZ=)X8OoKwV<0bW&(en2M;VNx`9|~chqE( zH-G|lLu9WPu^$)+t=Dyj!}^L`AzxkKrq5g%&6*2P#*|H_vAKcc_1vGKc5d+2Lx=6} z85g)?NBT0z!xsL!ZHlt6=|UTY;F~zXtElRcC(ui2gbAb;`K3G7_+sJHi}yZ`D9?BP z@^rfsw#KY^oVe+T$HvF~y`%1;lOc!rb7>8%$(S-wToMHl$)b|BRb$ZUJ2c)!dl&Y# zX-B8b7(;A=bou_pwT}HtEzbUjZ1C-?k|*TPeNg1ENMd?RFj~a>#%U6B=r29j`dbOE zc#FAY?}sG^wC(f1b8YhmkkD6m2!10t*ol2le~o)WzZd=RxlV8R;S^r4$m$96thau4 zzjuYe#mv|fRW2Z4L8UGj>I!@4#-36|x@Pg%E;U9Apd9~PM@yi}pL^fsaOq$`~FKNxw42H-S%#eOUf=(33P%8!oljze`oR2|q>FO+;plJYq;R)4mNn>~K9V^G(9Ff$#k)6?M5H{d zas6D3P>X{h#)@5d_PbCLl1paE11RkwIPx)V^tJ~aJd`|hzttP)I$VXUGTmWpB>L*J zIXigGb+FYtR~NKG?5gYyZa^PbyZkO ztVgdAZZKJ`pW87*>La7yIOk2!&c#?;rCJUD8>&dWJa`FAUPi=;-q(cJWiBt+TJ*tY zrpr2j^$tW)Zqg)`38LR{<9%8ZBMf$!-Jl@u2QH#!5ju_*XfFP0p4wC&6H8?Fi(S5r z@zH+I!cPm~oc^ao1%6dL`FS=m?|>P)E%Y4S{$Y*u^K%ij`4(7R8FplU@@?GLM!J=k zXbd@Lw(n`Lx&Y0)k0hGn-jIKczueB$3n(d4F3>%5hH#_rBxkwJf!FW$jni6+5U@|e zj?{X&V`7|1X1w7#J~_{LoJw8<3o3NN6zG*uM0@UY*?V0~2`f(ya5u$4svmxNZYuam z?9oE_jyh^u{gg8+(8PG&TdO}S)sX4!EYIQtDJ+>9eCeLT4Vji_FDjX+Lco22tUO0O z;CwRAs?engX`Wry594{Ey7kMIZd)y!$Rd4Y-)W6Sx*MgsYmV3q=gA&&I^c5i*!aM@ z5q?rQ^k>NaEcm&rMXh-oL68q`i!aSxxSbbKltttcMo$=cA9<|YvNJXW|hiC4RolM z&^}tNjNh^w+VhsKU=Q=sl~T6bApMb-T}(wAycsI*8ywVwdqO?QKa!M)K21N`pZ|%W zYF>}AdW|XGJ2fk28{vSgw`lp<#~jfi?HzOBbt~e$TZKb+N(pI?Sq%FU*U@$o9ED=c zfq&g2X7tcq@ZfM6ee}W@zUg%}K6tN8^z5+ajvcxPvTtVg8>XCrv18l3WQCejj-a<08NEIM31Iyf$gfd)lk;>do)r4K@iFJxeRc@J13!jOX}^rT9RT z<16Vi%`3Q?=SH*SB!_zycu!;`-$7pQOH*K(03j2K}5T*2=ql{;Vw&*Au?QmI4O<&H^$|ktN^zD{|nHp%So~Y8ZB7wUT(|IDd z1YwgsFfZ3t9mw;exgI~%0hI@oUug-iF~=E0D~VMxFndQ@+8)Y?kEaEv4Om6+8@2P# zZVOQya-cU)>9~Qdhg8lVFXccP7Ijyl40e>7URxk{=0%Bw@tJZ@I0Qp*EApsBnO@HDW{IWhlaRvEj1!nTPG&WCdkegDf9HOW;`6BYI054c@`FZQCDS`V;QGVsP3dCl8py;_T0i`RY{tevM zkszhId2Cg2q+Pl7)B`Q_@oW{5^SFb;rUTDOzMjE4jwo-MOhx$d`L(xTtTq&npYHIX z(FU`!@saOFN^tSbz0bNFm*7vW+{uzXH}FNa@~c=zF&qiHnLAwyxG531HGYx@-<6i= z1h%pv?O4u5x*0B18)6f*#Owd#=l`F{Vs2HZOVFxRz zZ;$OB*Cp;B7G9p8)zN)h-S3yL9S-c{q1`RE$Eus|q~m*Zkv&DaZs4pYC}r)QF4<=f z4eVdrN|Nn?CS`aqyHE$Fyw5jTQmJF^K{}zxI%_0((eC{As1+Woq2{1|DTlMu0$=`w z8A0Zwr%}OP4#d9wnpmBTE!ffLDavUmL4mmItuYm2+{)7UX&6bIkKeIhy=~=y*gtgd?ff1h~$FXsVie^*wX^ zE1ood>Xie!jK7o4?Xbk`Glx|)cA1F3e>C2pWdZ9`iJQ$|9l$)?zkI^S684rTv?LFI)-Gv`9A>T$kETh$!=ud7R6^F^ZS7!7_tpWvHZ;wb zsGEXDsoA%oL>4w9LYU2>&IBcR96L;z8lU#XiB4D1LEv+>y}??oCGh zFgx-9S*(}#ycQ&z~~!5kS^rHLt$aRN)B>Tp!n zXHoIyU3@BZ>HH;o2Yh#L%R`*g0AC&)STvN^0heOWrQ&mrpf*N279(g6N{*i72PZ^; zs(>@oQ_2n#)XpYY-tQeepz$qIf}%JMMQjHp#yoPX@yJ^ z724IJtVnFLEmy^?pep&Uj601Jgt;xZF|wM1FiRZe%TjF|X<~@se&mXEG*6#YjC){E zsg?5WrKtPc~onWm-2tdZyDtNoI!<7xAgXYY$`%I8j*{HWbvT~`qo=FpY@sxk`Nu>eN%jlRnUg-;yFI(*Pjs=6tix1uvDn1Z-cYxa_ zo)6bzl)h0;2cp1(WAw_k1c&h8!=+rmIZIVx_|}ikovVbJI#oc7`AV~bX zDTQTWU{JR;9DFDMTt=%WUd9VUa54oA2a7LqYQA(jXBvijAKyklQ4c~o`>ZvU$0jJv znlccf-~x|3r9X7v3xZqC%X?o827pkoRJVJz4P55Y4p;D2XEWhiw}u=CsrDNuRq*zl=(%Gj zT+puj-3Kv(Ta5L-> zB*Qxdt>K0P8)LLmBwVb$nJ?={aAi~T=DQb@!0MOoR>4F#T;ubrKHmN}5A$#xQ@YWsG^O(o7gdCC9d|i+()D5Xa)ZFLy1f#YL%=wN) zp+|_sDF^Qu47GvP^aXQWbM;P|DmGglJ5L4v6zGw-uIhPr765giKX({_FG>+KwjC@u51Xy7F2M*?beU zXFObxLF6;G6L>ujH->=Xy%W!S%o5>qr%-m;a1!W#X?1Ggiv-hLONE{md5A2q>ib9d zA1~e8EhDu^#4;{>&9wvZc$~tV-#6b2H^XG!l{_ZyfBoORpH4(T$*(bQ{fQW$QRuR) z8V>`f%duu3wtaxa;cju|4R=@$bJW%O8UW!9XDGM0qu|01 zf%@7xDe4RzP_w@q?X+r%&h|3=sbac#>G2FHCRspFdh0#SyB_dsFk}6FpabMSTArp; zGQ$GSOVvBe!FV&?rYz)ZIOeq%^Bn%_f>K5Q9_7{tLf@lkCtafqh>CkQpMR_nbUjK> zo$M)qee)L_gSyf{RE?(C@2eNY94`NIb=(hcxZn6UdovzOxEO4m90apt*c0+E|!`g`3y9Z4kbsx+kKBGXrab{*Mm`*4E%Nf3MOo=! zuC+rcnwSE#wMEU}O2dFMXg1$KpZFX&Cj@vC`+BB0z50WcL@&(nDgB3m#!%Li~G3j zQ=-B5y3_K`f3ZM2w)$a&KL&1iL~td0L_pU}BlUtoAZ)p^;&lmIpvo(kI;TX;12i)- z%-Mp_EMbKzpgJCJS$<@W*d{mw>Fuk-c3D^zym)c)7Qx|dRd8Lc&O+&h(nCrusYomF z#As0>1~0exs6~^w;naPf9<5AcSndrA%;E5%^Be<*Ji%|MU72bMi2=vg>PgMtVqkgdd@65u z6r{wmsA#K&!ch;s?F2PXAeGq+=szn3@zZzc_y*ii>`G7KiSH38#I+(tO`ePqGrEVI zJTj0=GF!pEEgM;^c4#{mvhh+ihE$|x;L4P4MG8j}svI`{vA-r5`xNQtjVRR7zb4o4 zVv8#j={@lX?hk^&@Va2hm=I9jJXxsd6$ou%PlW=giwQrb<@!H@PwV*ps_#HhF!Eaz zXIU3z5c?FRiCF6@xFq?*w+_{1)@K-)foix9S&xSMu8qKseJww2-Dnq97#vKkaI-2`C&~4%C}yj{}Ww+ zLwf^Qd8IOOTTyz~<&Gnc@o-SAiY0-Oq1Ekq`CN#KRMa?=vuiv|8BHM8gxR2_{5Je!+HSD3t zuL{R+*R|M>-YmfJ)i)0X9gFcogU=^r>I^(%xNK!u=>t#rpFY24kASw0MLr{I!VlMax6N2V0-r0)K8~VIM=Jfx67G>C1J zS#hFQXoy2laN~W1+u!7+Egdjn8f#5-0(eRK{2#g0H2xn%A+-+5kVA zsCgZE>On(4zGZl<7@nLpQ=Stu#F34cHGApHk;>HTY1{E8JnbsQIbz$44^)$BFH79R zcj}`TzMJG?^FxK#X}PBGmeDcvZhI;myWh&4Ri6oc`!wcKkEFuP=NDOzo+bjHh$oHb zp)~j-e0}}J^#aH&dUH)$y9CIu>pTq!E&}!Aa^;si65t6f%kaS41SEa+fL1l653Hdh^5rrTSMl-J&V7*fqd#<}8q5el+k%{{ttqc;Htdq=P6Po{ym zb*KIwk}Oy_dN8s7a{=*vX1q^+gYcfpF}4kVzXy6$U)Y=F8sO7~xgJ%!Y8W}Sa<~3M z3NT!^T-oy>1D_m|X`J4^hoxR}>7yCVs9xx$u~65DO|@EyDJJDu_&RN;=6(eJ+YWMn zDIWtbtlnL|sY~PuYMAXtUZz903-?2tmL!n+ckZb`KJL1~bcLOJ}lh&iwe^)qSC8s+;aUt`-c}X1g@P`BThVqXt5N6ak{^M)4eIx-hoUjB=2*AJ^+>#0>nfB$ z_{%;03Z~(I4*$vElQ@q-@F}q=~QWG!e)rIH6m|I|!fX zFXcRsqho}3#?-+7V_P!8pPd(>>Mn$#o15xmF(qKtZz~^dR0#KB@$2pBWKd-tzqGJv z38RtsK3g6R!j0|BPq)*f@$F8-(=VY>So+g_c~K-13mYmA-HA)Ujcc!sPd>`T_B&@2 zUu+g)+=GKPmwp!GRCt<5_VEJb4L#ZNTQQvIW7?__B)GBCYwk?D)uphOy`EkEvlglx z>`LXj>wsMaT853qW0V z&s=0p3x1_n9rNT7c5YR2k(Ft%u@mpy-$PjE9QwKCpUAlPz$ZKkZlb3BP* zQdM=RIJIl^ld2l8R`+>43@AY+gRZg17c!AsL-tm=b`VNCO&_NBcZ1*We{0~8{@8FHd-a0wGtGKKz8&XeYNWW;%z+SdroD5 zb4~9*5%pqVVX5_JI8yx|FTpvhtK`p9o}n4&}HTPIhGj2}rzT8>s=1K;`G z(Dnjk`w?KuqLqkmzV2Ewd+B1=u}i$m#Pjk;r-Ps5oHB@f5RGFvRUvFrU(OHuS`5y8 zD-~~7i=os@)NEU=0K|9ZALv#lfpACIa(@*okrhoHthgBq)T;kRluO)JM%$hE`)CG8tk89bT zv=YUOGRxK&>#)s3t7C6j9o}WTD^q-^8Uwlh-4snO!Jo7(OmFNn@mhNmP5-MPB)hyq z4%4m>nN%w@mX`#B!Hy?hSLOn}xn4oYa1k({CA;COQvw{~meZ^0MNn-@#~^H#1#hQT z617qZU$R$7v#~=ET0Hdn{-NgYJi=@8^hh>iX)f4|&;`%_2qnJv;)b zOqRUIH9eq6cvgYNtthZaMCOupsFz=2$#px4Z6sVRnvXYsB_zZHSx%8OsF zr?P-MJUyc7Y%n+%n~1CkIOF*umY8s=NTP40{o^^+Xk@=!cYCxxf|&oGmKjcs#w9if zj_XY+C_Ec>?-jAmvp2o@yAo22`!B@X_Y4-{_%oWA`*i6TcKzV{)&CW5`YjIz zXZN|qXIH@~mKz_Um+Bzyc7(SvG8vvr&m@Xy7#epsC_HMJ0rUTu?O>!c|3wDpdktW>^Yr% zSMLb)z0?=qW03-0Q&np`PYZ#fPvJAqkrMFOCw#9numIRT+VNWbB6=2D|C6vB(gB~( z;=yXSg0b#S;((=FG%B6EgOO{InD|jqXa7nBDwMrU97{~V>5m#^E>AP@n1Qeg)t5qi zwDEXBf2A1XYg6tg{maJ~=3kn1{e<6gu(X~2Wdax;aC+U*RtjkielA}g)q<P zb;f0E2p`BZm#`jZsIHumI3O5~zc=cSQdp-U^ZLOk^)tD6pF2@W zf3yG#^)+ioU;dWfd7oRw$*64`(CePs3UaLK4mPoCiAe}q;Yd}4;r z3|LJxOfWWv*d9dtR?nq17gf_k*P4))%-G^;NIJ(KY!6PECs7Q#kVMk z`+S{no5n#*gfNjy1s9$tAbVlA=$lWK=&2qY%%I(fXId$ZUz;?d_}~MLj?>lnr#AUc zX=WzczvOY07P$hx8sE${Xc8cVno5l=A&u}Ca!K3gXMxm+)8)XXLKxOKL#HtNwTQ zQVVha{@)zt!u81R`%300O%rZU1&9i|)}xK*%jTZ5C76+zv+;1d2&FB<#uVNcp!O+p za_4_R*h~weGocy46;@T!4^^-g9p+K8SO<4WZw^X+s(?$3ccPZ(GvI{&(XT9GN)T{5 zD`})J1Y=*H+X}zrjXN=Je8B1qcYeAv>BmIESq+Ydzmo#tK*(sqfivE?@TN%E5$7KdME68G5`8Wg9S$GN zy8+MQPTfr;@^yj}5;(3`f|t}HwhXk2km{|);1YWx#=7R%Dw4W_Rpd>sdj`b&d8R?( zpKTB*zZAS7Ll=$@+B;IJ&lOTbw!0z)ey4m`?-Hg5+3OM+DGKS^N1dlJx?;5^RfEkT5Awp z0=}Z8Cp*p%0M(wE6~*+~p#AQc_PgX<&^TgbUF;DBmrZ7_eU6R7M^+St`x*<8I-_>K za#9J}G^Tlcxsijrx1;n1EL<^1z$tIv>qxNZR(nCh90&eRx}9Wa*ik37e&)|g8nPQt zjw<~2fZ6!DX3I!h7+5~1} z2fZf;3PJ@Vz$wN;nO7wcHg>nNJVtHdnJu5Pq$Y80bK+LCZD0`6_NzA9lO&*mpK$BJ zH^jM3Ta2*ZhfG{zpSpNSHyfui^*sa{2*3A@J*pXtDcBoWNE=VAvwM@D-Z;kLhKT|^ ztG>4k!Crug?cyI_kW8q*VSFqE+(P=`P)sn4?^-;|_wyrqpV#YN#1Ni)|Ab-IYv$0E zvT`9;%Mhk)YE#RPnS*?RNU2x6BdA^;FMIyp1ERD~U!J?@4~vQYPu?*Hfq~T!D=+aJ zzxW@=%16p@=m|aL(KShM&FqZU{wy(|`9LpJy)g!y=nUDkgQLK<%E`QsGZfU_X8z82 zc*2o~y`$?p($MIg^Jb0H9RoNTsE(LMqHy*pZa9^U+ADAG)t}A4-{Dd$H;8k(<<`h+ zXV$Y&Tx$EIQbjs8ep$GADT?q=oQ&oyQ3=L^Mbf^vH?(l>VL{+`6M{4CW{=O63jz^x zIgL@<5U?|!E5AW_kUwkQi7e9AhbrHG#}0FI+#_#SO(vm%-elx+rMi}|cTrDpqs<+p zhyPe!{m&jm%X@mxFPoyF+zBtsnjn1fM|yTb8$E( zyD0i(DwdZ(*l!zG92xFk*y#*|M{%n!3slpR>JuT-%1!o>A0VRQ7N65W%*EeVBL%IXHQ&Y*MWMCdm?H+bMq<(pKf2m{|TnFcO; zVm-5$=L~Zgw&n*M2(}Ew82v4acS8DLYWebt2X7)QbU#185Sa(NFFM}1Jt_pf(DQx` zwz;5sPH<>RDGqX&Jw#tgDBx7q5&y|Ukx2fP{_IRn0{+!~c=qOlSY*7ZH2ak|0xeDr zJvwnH3Kx3Ct+LjWkp7tjMSEKo@&{ZQxZ0bCRo{eNh10We-$%-h^5kfACZ{@V(qsi7 zG&B5pB@rZ)w~MY2=P>8EV*1{%rvSSe9rN5Ok#CgLN{*#Y2Kj66%dVy+!14T+FYU5~ z=h{_gk=5T7cO^<&S9)XduN&X5CuE7Z`C@)?LNg5S%-U54NlW4j63*ECn?Z1t@}AuW z(T}5$L*f6{A__p?f;9Pd5VSW0Id@Wd0MnW^ukZ^m$hAF3-cuF|_xIc!34Ig`>~A!e zRi4Gb+nK%-i!Z%lg1`9$7qbV-rwNrTb|w@4*(>ZBo;jG+n78qwH3wZDpAzljNX1xw zi^=cSA((gi>t(hfRdhYtZ5qIC1gy=Ejen{N!-c^96T#W0czFHFrL?o=`2Ks3{(*49 zOH@g{F_0GmQRDRF@0}Cj&uYQ$^2;P(-&#zfql$zEOVuTNO<6cYZnqj(AByP*pZ2Sg zCgOSFi1PKdSWK~y-hA8Rfw`sUsT^FKAeHT6{G*x(D4Jl~SqX}P(K{yG6UAY$lzB6K zcHReqsAXTuNxB1XvVpYGzW{J7`WgRAHVWJyz6qtAjDvNx-Az`yaA^6D?XB`Pc_0tH z(e9@mg^tNfV;?9o@yCm}ue$ehQQ)p!0A+I~Hgyk{nTE#WW*~hi&zKAH`7FquczFkY zPL)QS%-03|3v&j^GIubwNBei@&%3y1^6M(|F9l@b8S*9j&jWgUAJiRhh=FTbdz@cA zxDM+1zhC)#8{#W7Q@j7fl`+5R%jH;N?mF^0b!NoF0^;AidXN69@Hn=b?82%A(zDWy zEl~Pm-_&O1QcVCRUu|3E&2_{kJ+GgyKifjjk>d%5Ws#r|tm|;FJPG!S*c6iK5cye# ze^Ts(PyAnz?z&HgImGpJ7c4Kh;K1h>YR$YsD7=J5A{zl{ATM@}Pr?mfjf~`VRN10V zki`|ycfk#wzZMobLHN9XW@w7&vs8U~MSL%9EP6fc+`0OP=+#+f*uSM>g61DTZ2wF5 z1TxnxdJf45c<^&GSrj6{?1{GWN%BDWd2M}roWUGqE1BeVr&KX&L60Q$tv+6NeBR-<`+=@Y1NEAo78)q_ z6pWwn$Ki45@7uvas3@FED`rIGDozYdi4p5(VMBkIa(VI{yg=2Z3Kyj-i;X2TcDVZCrgT1$z|q znbI#>qI!d6NC)Au$fKE_UKTb0!)Xbw5!=AwzuS)S>YAeTNdJWT14*s$ybKh2JikW{+B z%GhIRi%?b2`Mo_odDj;2sYrM}_H)N0Rr^?M-L24WF7TtrmN_sF&`mBKCVH=PJIQs^ z!eGrfX!?4yKTsV!H?#7EA3g?8mxzDz!Hl?mQptr-?3xY;s4osgj+$FtJL^U`5JB}P zoWTW>=O14YCOkb1O+9{cngQ_XYQdkBc5C=)5PHAiizYHptA=>~wZfizzn+DD7soHU zUU~a(6Wq(uS_PJnAfU_n898PZ1QV-jzl`44LYSV8Da8Y4JQJ8{`}05~4yRw0Ptc9S z@X;yJ%D`|UU#1(d{KyfHN0&cS%rXVGd$iBFE!@DjrBvB?+y+9(%u>ba)Ufzg7@Lf+ zD_(ov>Cf8jf^WBHPq;o6LRxFLmdIHT;8sx%IWu92LC?q{gZ4Tg?^@ySWhp&ebmUiR z*{cf^M#_-~h+e`IE<*{+HMVepN9ngq^G)C%yTT_jYKz>p$86{bK zX!6A`BMa?Tc8aAdu#%o}-aZusX0Ht1vm^$Bz}%Jm*!xaUBTf^#M$Cy{QzYV%p3NS9KgbhJf=#WoYU%<<^Hpdo5&9d2<_C>x>#Wa-;wz0HhJ7A z|M0WZ(FlfSz4m@_asZ9)BC82bTcXEIa)*Of5keNl_m|6>px{o#_K6!#cxRDx{4kL( zI=X!!QMySR&GK$@G{4h?sb>|eqM|lHCh^7goh0Gye7%Q`$6Xq%x>>ZjCJfPXIexYB zw+%7{T(Oq-GDnwPj-qY}7A)d@w@dTI4ESr`n~s44j1DVq;TI9v{^rxTR^tC znC!z>CxDH!S>$=L847oQq<-?$7B?g)>W9*e@yBOlkxViP7^=@_UgWX{`O3M!sv`~% z7c3r^b=nkUHVS%n_@t2%f_rVZZE^HMZqv_sCrpnHQ+vmI7q=P2H;zrLb!4_3d^BQY z4(9JAdTZMvK4QOZ`r{XrL>czjY=DS4v5Q9{MDpdyV)Nm3{hfN$o60Gx3~!%%f=pluF>> zw9w?hyDFG}cy*-XUoCu)qr9!}Q46GgH%`5WO8BI8Ii<&k=n;KR8@qoZ3K*4{_k4U8 zjxu)|s7!l{aAr_#!QZ2n$aPVlw)x(GO7#CN=LI%m=|_uI*_YK2W%c>u?fz0wObKQyn8*M@Ki#x@PD-#g&i#XaCj&d=to}XR_TX(Fj2tH_Rc;4d7hwZ^U zujc6l;AVea!LqJ`6j}ZePxd4tRo_7z{*-~xO%+NJpN1bT3Z$H~!qAFM%H43@8GagD zGu~`Y1xwS#<4k#lV6qvaq(#ibKJE`Fzm`=Fu`jtJH)zYDm7yh!Nvs6AHO<%VS>yu0 zyD{mJQ<2~nraoUO6NrMg-1*Eu@-S88^bxwfm6%DrF{;>6hmnTwZ}>#r!`zlIQqK8m zd}{J?u(+@kRcu)3|IO#(s#bfJp?wnm5^>}{mKKCz*=yDJ{N<2y{%cBPXdq~=`fCl) zrU0D?!_=esJXpTQmDrwI1TA?rPBIS)f$8_?@Y7craGa@gCaBgQJigmZm*)qg;QYUy znM-Nd?*zz2l8M=zi*Fpq6Oo#0ptyFQ4VE2wUG;=E8uGU@x8;aAmfpj*w{AyEpuPSn zr{#fixRZP9pL||9ki1ss;r&wzb={(4zupwWb%p)zAw@Jh4v{gRA@ zSA%QzWt8Ai=WB_FTxu|$CP|#T_Z~JBQcX5oti!n5HVgKIpRMlxr+kfDMaZpKYd18W zj+YMw>?%k{*B8@R7}zHlM@7vJwb7Pq++6OgHpPHMoLgBT8j(PPqY;iJ=)yWV`Y`tAZh>hqi+p9 zY<^73h&8y6LF9P{Wi{^k6;rRUSdQY7=Yt#4in0B)U#WL~4jP<(E%MJk0ln^3M98jN z;zN^~X}a7H=&LHEn&C}@_d8sEGmUwW(ww}Rbh;3Jh4$8;qab*iY!&*Pv<%o0UM+hb z6a_7lj*1um2|x|~o8{~6#Chaz&D$Si5^*wnpX%{n320N5^}6Iz0M>p8IJ8mb4{!hL zw|$qD4ONGa*lp32gWQ|5&tHhv!j8;a56h4`XxO%tNms6dCiU;T);Wa`A@Rz{O(qhC z)E}SvX56i-NY3_YbPL`}99R)y9ytlX}D>h?GoZwbl-av9lz3= z{_iYkFiEhMO(}p%=BaHwQ~>`bD;Pcy{8EhL4~s;pTzJ*M-*QMM4NiQ})tjb?0C)B} zAC+|4bwgLKlhx?w{CV>C{wj2dPYCcxtVD~}DZU-QO1yc3>$g}0;g^!gUOFpX ziWS~!ToeSirp@)#*xWo0byCiHh+UG0t5upw3r(>w6+!WS>_Y}zs3A)@QJDv%Z`?De zGzx%fXjk^HQa%JSvAw)0o&}BcZ1b${iJ*3@bzaWZ6O=|+tz<&&kcl>DXsIR;UspRy z^&NJ`rmIg9<^KzTT9cWnA{FBMvm)y#@uwO#Vq=bmk2kX@>PgjFFI zHEF|Ab0wBlANC%OuEd`Ai&B+0iT6K{^3h+yD|VDI`Rqns5xz@ra@oF-j(3mlNXjt! z;MJ|g`*%_UV7bOW=!jJc2tNpTGd5yeB8NxN8oodbbVAfs|V?;$i**C}|mRRQ%!#Y-%5F*y9yVYfG~0hd$g z7}dzy(4Cl`9^-9Axi2zUn*1Ed=j- z;*W%z1VtQdG`cOY{7Zw*tjW)f1P8)5M@qKmM=iYExXs5h(E#7mD+g4L*2A&_%L${1 zVi4xCT3@#hfagJJk6ekld+Rt@8HQid`xjPW)#yTaWO}~;-S7Ro z0vdm$+xizhfbISFja9>+z;n}Z&H~3Cm^|j~mCxS=H*!)KtQ(s_eq5L=#x4i$25n{| zi62;(ckTpl6haNJ&67lh2Ov+kY%Tev2QE~NU$r{@9CY`#nE`aeW7qu@NN}l)d6u%3 z4z*Y)z}WDw?FlM<(b8padWup9OsAPwo}g;m40&Eu3qJ1axUhRQ8xIfgc8TTrV9&LE zM?!ZBFkni#T}`wVzunr2G>_=Qd%n-kx}E948yW5mK z{(AJX1+flv)E2ph*v8`{Gm7LBJ1Y3_s%oS1o*dYX+vJk@*a%7+5w>dE2!}*6ej zK!CA>ddH<3w6nbEF4H}NV&~IjC(5cI($-J%heZU0*rp|vU5ke=G88-9&h>EQux*d( z=`QfE{9t$4<{4C%NpCy1KZU`8AGQYL?V!azX8+t$2G=*=eA0?*#{n@DiZ5NgC_H%o zSzU22PQ1=sHaXvcS2#+9f?BFD^IycNWvMW9TmBN}SQ>{8`vS@m*J`oh|FCzKUsZO| z+E+r6P*hq)5Ct)KKm{dqA_|BIN?3p*C8(q*A>D#B(%s#ilkV7??i4XVKoL|>&VBxa z_wzf>r+dKIW58Z}?X~7w^SXZ5A4w(=xoZ5=Yti+_r~yR`e;4S7H=sn|Zo_;zBzQh% zM<4AkLKXXzSDBv4s9}?5eUT{yUEZ9hk)d)yju`uh>xU!Yc|xYQ)PoA}G0S;I+HQg0 zV?X2{+#&&61I6aS0Ww@3KR02u*#l-O@%83OEg=0w^S(z&4IE}S>i_e&6l%5pXr&HR z!|J(6Yu~@k;9ojaKIqd8{TwfrE+zKBO%8!x%AGB6G{;JnubSxJ=cR7eov*-w9zD*d z{T-N>Q{_DtOhUPxbI0wfJMql=$o2K0dVIpX$d)&skC}cq>HUd5yGP5=;8WFn)X6&k zBkxunPDm{0DQUE0ztS0o*t8xLd6w_j_o@e}icV{`wzgwg?O&F&CmXR)(aMEGp%T~h z-zIcg=HRY$3KI`vSGs%AC(3au7Y=5e{HLE-4?YS_UroC^VJqgVAg3)EjG}gL_A8QM zk#|4;uH&5$oq1-fm8St>cCVn5%$G}NB9F+DF)jwJimy^ooi5j#fn^V7PJ=1alp zo17%9(4{P`c-w`^FJ0et`Ly7#?wDeV-db$Rkv$P4S&Va+Uy7O($77kOl4FZBf{RQa zkL>O)`0K8~^{9^wC$g`FMtJo?8GT=`t!Xcmczyi$?|2`)w`SLVes%zg-|5R~<_^K0 zl>WPo(7OenYVk>N z!%|UKi5(OfD7|m>Sd?lRI$1UDy&KVhTpR>FpSBZOzX!PcdXjM}{=&S>sXh!@%@ODb z=*1zXFA*#LB+Rj%5xZ;Cg*SZWNI56Fa3yP?GQgdLiOr{fr+f6FUCE#JQHueLcXx97 zD%y{w4th7PwsxVcVOkplOF2@oT=^F)l?j59gW+wwEs&nB+I{pr2};kB1Y`_)!RA+W zgOYVG%zaluwzyt+q`~S>rs{_yO!wE2<|(lJUK13-Eu z!eLuSf;iEK1-)ENAk6-%(Did6j7|S{Hq!AOXk3g|S=fxlCCv${=350A5nMGDtb|zZ z{NyQbUkj?W(0^=S=)r}zEw928dQo2DTuL5iAJVR=CRwDC@%e+cUw7zx@a2Vr%4b_U zkxakkGn3ejOre)6qA7c^KgGhDucaSveGTQdy*GfNBKJO(?CZr7qDS8P)i$Ak+LR0X z{SZPIxAH{uZy_vpu=*yf*8?8|t%1^kb|_z1YO6>mLHN#GxA|8Cpt^9`emrap1_I1g zoIg*4gVDH@(ZVbw7A*1Uq|JhPWI3$(P7yry;DvJ^2H+uc9EC<1Y`9d3~~ zcD-J3#&BRSdq4z8epSgE2N9IwJmSq$#&IM+YKTk^XOi+KyBs^>ECLu2?)+VBLhwODfS^R0%;!TMB= z)Mi*`y^^@()&(mo-&6Na^@8cK$-_eW!|-@ba{{>_!mJ_)Uq-*LzJ!sK;Mkh~d1kMlGp$={S5 z-Hf*nX64psm!o*Kx!2Vgfs`^$h)N;Cp zBoB^&%n^a=tI`wj+5DUa>*g55kBE=Zw+#Wy?#%hGC4F%Jg=f0U;aA zOy~l~VrWH2rYBhLi%Su7EP?v2QprE@9q{4e%&k?kL8vn`{ILJ@IP5EGwkqbGfVwNv zV$zJG@bIjl?4b7mY>MA5qkr2Ads*{+M2U5RXLoX?xnw^KPA%N6H6H+H-5bmEkNXIH z)V#OHu6AH3b3eNIBpbLkNSEK=D@C`$1tasqHvDfI79Nt*gd{sE5zJ~ z8k(}N&$|rbnSSvk3yN{HnK#dRTQiL{pArp5R%XzNYBA)L%{1!ve}6a7I*d2}^L3tV z?7$t`AGZC(^KjVg*`qDz2w-iWxqgu;8JglwtJQz<0SeJkt>xt|NGM+3mMQH8$uBY2 zRAPHTEqo@P`d}T*Kh;)$LvZQa2pTlit7N>teMfqkEeI90rz393%qe62Ah4qW(K6o}P^;S?_$Ry_2DcVJjk@(fB9-LtuVP(Lm-o4a zBBmMAN*vs!%@IBYzVnFAD+aYkpRc_qX8~u+Q`KW!{xDqFQLI)=oYy`t35`)U;P{fq zlkZQvaX)Rtn9uk^#)(s=!Yq}j@+Gd*3Y|k~{X{lm-cPT?D>Tv`<_H?-6zLA7$ z>2+s!QXwi9wu#;P+JSre|4i@TCS&c*cGrovPLxaaxixsP6hFrqZV7UifV9Wqe&N0@ zyn5;Leu1=JG)$(rVjJCqmD7`YZ0G9mj}HGO%85W^N~dY-t4{)X11S1TCiIQ#Ge zm8Ag5vL+-tx+K)!sE(u)_k~KrN;Utu9L|NTSERAD!TC)2u`4IZz@~U_BN=glM}p~1 zUZyj|zMVXO&*qzE6GRr-u0AO@fO8-39O|+r>Z*{Htf@FM9$&lfGqFj={sghvGO9K# z&v$!P?OT8;w|Y6Hl!9R;ynA$RBN0|Vy*g<;@*dA$yDoB|rUsw_JnBXB@XXlGpS0e@Kb;ZaWe$AU}HVaw)9nZXIkwP1^d?;(hQ*a@P^2pu6|5> z9QJrUl!RLB)Z6Oxbx8SAcpm41;AZy6KV;7aSeCr}ypE?ElDc_LJBpKm$zJEDk4P_Y zQCEr`H0g)P@q#_ndq-e&(6i{n&MEMS>2zNzoq<8d`5LF}8TfWT>{`0V1e`jcqqzR8 z51#y*j$ovygY#0fbLCPF5X-L7995Hy$M|ECu2aOKI)4|OkJH0b5|SMqRi2pe@bB?- z-x9p|U$MSydIvgqGDyxF4Ptr`ySEGXI1cW9#(nbS1iJj|dcSpQ6t#VM|KwH<;JxsY zsJq3zX!q;WPIdcUX0z)U)}<XLD^xQ9kd&b~hV& zDMGyVDwcwxcqnICbsIRZ{Mz}|oCK4x+EseRWKcYDC+;16ALyj-3jFwL5cqDKWf!6v zhoDys#kxJyz#CRyaF2c#z80_u*hNX|4175)!T3Ac8P2;sF4Hx#g}8-yi8_NIl++;^M(hZ}k4`qNTwBRt zs^Ml5qSXXtWr1hz1owk=&n5ju`3Z2BarCf0Gz0b|uWbRb8E_Ld9=^1H3OGb4C!e1m zg$!XvS)-l-_-}N4nK!2wu0)BdCtV{!PF{acX?{CAJD2Uob*LWv=?<$M-p&L#K0+&< zmw>Kw0wyB9)%fPWIFtPe&6qQNVc(KR7pk5wSK|9Xe2&9@@8t}J(W-9e*rTRN?4Vu~ zp<|duE{*M7|8~qFq?YqkLt3PCH#+3 zE+!oj-tc0LnYGT+8vCp2$$MUh<6ld!EG1;k;B1+VkoS)5Jdc&r9Lrn(PSd z36<~1Q`1nq9j5W% z+*x|mNny4NRO-6E?rv>{<@324GhXEoWhErKPt^&gyd#n0gL?~{8$B+!yR#eoEM zj$_;B|+d4qvA8~K9Fc1l)Diz0HXW;F?8zm!-rquG{$D{GEPT7A1%`Dp z<8F@jz=go_^!~hF$p5CrX-(A0>&Kb80=BFzD5?7EAC-MK-ak#Z^46vYcb__c@z$qiOlC+6 zbInOZ!Hta=Pqhj-NWWz)Oy3E!37!9#hr*hiJZ+E?2S zQhJ99`xqM`$XKHFiclr2mYXOhJkNn_wv~vs7He2bc)xGvY#turrW{kRt4FE+QU0FB zPW1j(^)^<6jF)yR6UUF3Ak5sIT#f6pQL}~1g$ExM%>&NVH zw!I!@Z+(+(zS@CV3wp{5v?PoQeP-DGyc6}i!#L!EYVg1wezT^6UW9&YA7weA z|H|yTe4-hw=sDFXN4wyK+;I=73nZ{02^{zt&;{yyD+z{s3v4{!{`a)H7EC{+Sa_c+ z28tzt2h<7i&~T-ddQsFJwPxX#(!Vq;%Xl5n6k3k-2?O`yoa-^7OX%bY^G2-Tsri!& zbx5Kgx|?2Dg!}$nl{xt^3SWM{TV0iFhiZ3sZ>BB8qgsCSJwLf}{83$Yp5tpHGUu?2 z$sKLQG3?z@2@M#}wXXAgJqt~{g%UQ5v!OE&D?XLf6M2P9GUS40NYP>Iv|g--M)LE~ zA1=j24y5={OHTw;GTSjOo_qs~YV&#*V&dS^!+C)nOeNqTy?jUWbsgL{@Zy_iZG=y; zS0ZU>>fuIlSF3D91ze|2GaI`B{*ju(Oa zj~d6HA<5u3-(}dj84Ll!fyXWe5qdvQWt~WZlcCW$J@)lLEWX#+$(VC59XT>0*7G{@ zP;E2&iI`G8-V~&F(BjF!tASzlC&xnY!%Zdgwn%*t-@)yfNOpi7YV1Y#<^1q`BhxI~ zdNwi^h4@_kRgTlFe`vq{sllY!{yb-o3PL9wR^>5}fm&1yRtAT?!IE^+M7}T|Lg`<8 z=)6z{k$!g0GNL6wn>j-h^e_j~)LH98X5!%D);S|frf{fMV^5B`lL(f9+Qodw^PqpV zs?FW97^+f?j?JGbffhAIt(lN~NO3+gI=+wy>sP%a94Fo2+QjcP<%e(3iTRQE=S!aG z!I!4CONQvz)z%Lzn}y;Vs*Ja+Qc1|^FV7oGmxD8}a@a}}iTt8BJjUM6xtLW-kxF$e z5!rK-cG0Ff;yoYCv#4=`kW=!~;ukKV)Wl~yW8MUOaQ(%U>vRz&yIrx`?NN<#X2Gxa zY*!;!nA|?K{vzy%CPakH?=gn|j8Rf%3g9;X`h$Zd;LE-E3XhiojpA`s+*t_0iNnI{ zvnfytGg<%MM!@P^?wiBUqv38;SmK;VI&3uXnW0D_s9c!1ndn*q(`RH%LS7faUm%Cr zOl5+ln0Mv5$AtfUHfT+VQxcMYe|~mT)eS|0=>+1H-=S!4_CAMWaX28nWw0z3hvzgu z)p1;mM)$D`dMj2Tcp@r_3Ryf+r~3Qw_Fq+;zsDY{?qdrpoaQXs`3`VU_>t~SiAVS{ z$d!zULKkN=%X!Ka5=Jo8xm z(0!Cd_v^h6+|*jhetaeojQe!dH~P|ne^CEIe^Dk_y*!XSHktuvYP9}1rKiK=suG5J z#dLU9yZe1U@#J2M?aNk3gZhBRbrp_ecywb`k}4(!%v=wK8{GDV4dPI~J6Rc1oXwx` zsrh2wel_KKu5i?tm9qUhLU2YNlr=Bw#o@Q0c>!waSUhCuY$Sgp0yWdR=wK)ScTju3 zs`aoz^3Yb8Ry@IlT#KkIwkP~utp(nsa5FGrs(zvP#}y~C=}&d*#vxgnQYh<3Cf>iO z@vEEYD=-~t!u z_s+k1XK=ukpdwaj`%w^|t*;-UfP!1wdFNr~XKOObV+li&*+_NhEh@J`}def;h6 zbYa(GaEa6*cO0Ue-20$01XZ$jU9KhcJd?%OS~<=Wd2@$jS7HR>kfZB<^PS&eMBclm zP2;2&-ibexYqIkcPOvSxUN3TolC;ZLurB~2TpK%C+WkRB>F_mXdV8oRik*+*PWZ5p z*+7Fc3i(+sPb9FVV%bFN;evhHSVI^4=hK}Wd_Iyl@kS*B-PGin^qS&P;pq80REXQH=#bhI)PeovHA#cbTT@bSBug&)*jC%7R=$+4an(Ot?^e?fOld zbRw6FX|M3VWRN@cAe?kJ9vTDfXNIYX+~o5ShW6)NV9n>R^BHS1babe&zI`^@rV& zgNqNF2!4TJ--8bO5crYCTPnlq4t7e7oF6{F#wDw$giekSd@zuDVmmP&H%TPf#D0Rr z^+%LWFCi7*%NvIXoKD63x+c4VUX^%cJ4l4W+^N z>VdHKOo;sTwKKs1>f~n3zYg9%?P(TFobgZ5(bwAU@3G>#fqB$1;`{cv)W|+90kybv z*BIL4(W~dATh>=%9uA8cXVW?2YSEd&9bzuf>D_Dd>rXJWGTu&8Tn+*q z{+>`q6T!39KXym_kT=m65mPn18jZV-&b-_~+(+df;yR#Aor-#Qw^L8#rlNk%=9lXI zDcDXK8L+((k9~aZr)Z9bprN3SQAFZX%oN%ol~ol4B}u}eq;MjCvcqx3^==l#(I|aO zvdV=L)@jj4li4t5AZ0s*X^`ok6`^`G0qXAjid-ut^bixP%(VjVfHB22nPT@V5IcHR z@R+122JU=!dF7oCKJH&e3GN7N-HgYtF7Y^Jz|FATkc_MGfebuR@tDMPHhI`D5O=P~ zN~F$eK$X9$MSd`mE7!%(+8`4R-4T}dZ0nIQV-i^6e#9T%v;18;@IVinQZI4HNrWP` z)XCMeu1R>6wM0d!AOlxyKYTS$CFZ?Nru7ktY>c?--RY~Cj<*73+6G$Uap@i(`;Mu1 zsB?SZ^}{q4y3mQEuHqolh^1<4Jo4VS#U^3 zo_->h?n(GGzAFXu91T7h-AuweC)UR9l|6s(`FPM+hmgW1N-@N9N34D&Pkx%nUtv-QMeKmJWc-3w)U=R&j4 z=Dd?*?o>8*4c*$Yvn&&*=l$yy|0bg-skk-TDje&&5?+WOyo<_@MMT;O24>q;H_dnE z1eYS2o+C{^4=V4rEB-iL08WlfH+9|eLH}k{<`TiXxvawG`F<(|_As**Uhas2_-VES zvuplfyU%V;HqR6cWf!^Bk2vA(mNTiPiNWY3L0=TU9EZNV9f?tAlmCxD|NkmH{=e7# z-w6DFFaj?7ij?0j5_}mdrLccN;aGMfw>2c!8ZBFxZ!$IdK$ctbcMaw^kZXUj)Osul z^rdAToV^nu>9hpTZ3rVc#Ua7$j8;%VA>gX4YX|-%pyWE7(aQb(&XnYu0U1nl= z9hVB%m~ST7M;Q8-seHjrFSjH4cTzT#^X|scMAM7bFZDsjs*4fZ|C%Gy2Eve zo-78vr(^G!Y@Lg8zQ^(GVogI!rKPk&KzZwq|+5T*(A@UZ|-x^*2d@})Z3KpFs2z};;_I0Tg<2t}$%F6pPqH@cGN6R&9F6Yf1W@C0PQSzx1Y8B>CpB}7fv=@j)l`Z_?jt_5}Q^Qvq*PA}}>3Ntp43EhdU6H7^o5 zeT6#(_q?l*gM)msLVI>5!QmURUQT&&1ixIjfA@_5a9RH@e?a*j(rWzp?Iq@qJpWlb zFX@D#eZ(D8ycdb{rf1r1+oCY8m9l-) zt;|^+k)Z5^pK|BL1Gs~|IIthpN|nimeTyjo#>B81Q4@aL58 zhY}$yE}%-8GXkt)Y&u=U9pDWpHT~puK~DiK+C`pV+{tfpFZfmjUTCrD4xx_1fY;4O zo-Rb=CB}rTXoAZrtiwIg{w^L>BBg~7%En@Z&xf{CJb~E%-5m@6GlmPtQ(k`Ji-y(E z^3q2P>2P9`_S~g!*>K!{oIFjF1@9fLmyAV^@Pi_$U_Oajf%uW~!UM(s-zDX%9u zXI^6=L2ri}lzZ|B4mT>w=b77i2Vz|O`+o6`NSsR5sJK{1)N#@!ug@_@pe%F$k@v3e z@Z0gGOQg?QFuC9HN&Lw`cyoZGj;A^r>}BYz4}Fh=lP{I!bmyZXRw2D&uXhj_EKxK! ze6xZo3cm6GjNjmnZj{QL^GA!WwU52`!;t6vYl>6#(Rk(c3372;9JVeS&?df$M?V@X z(rtnh=PjyOOraZs4zoIk!ZY5Wu+*Q>mkeIO!rXUX`fMDeMAgu;OA>kHEQ9oa{t`Sg zHlgc2chzRp&G*` zkw`6RdVNDK4rK$~4venHq2lRJ(VtGm;D7e+{kcBx@d}gpDlMZG+MVWlqP5`)cMcUx z&QuWm9y1&69Y%3*?bDw}{KT{BU@{}8N;sqlr;ZEn^#bxTU8B~F7)ngp5b5A=3EzrX z(!$bvJhu2M_nTxSp3(TWmt!s#@1!hS{ZNfZlSAo~JE-F^Gfy~g*HR>YRQjn(W8sS_ zsy_lKwDcim_rsFi^r3Lcou=#O=_Ihcudn({G#zRwq8(Md37zvB&TmfU$&mGK|L~9T zDCjCU!MmR830pnSIlauEAgd9rlyaIka;Lb8de??xM79`}7LhY-5Z9ghb|DT|`R{=x zvA&hJ>gROkhoYhiDMr}Y2?g#NURXTg33o5q=6Kc;T$0t=>eE{(U_*EQsIy}VT=|iF z&?7q*ibwtKUGnpU6Z%x4e{U21-z9^m&%Qe0nZ4RRD;Ab`L1gR!jVwU2NsuU=kQVsP zJ&RcMu)xPIS2VK^1mkeK+^#I41nky2JG*B`G9Jvn?6O81hxhkfn+$&Ah-KyF)b*d= z!+MZil;E#qh&%oB+uOq#1Yga>eE3H)93SS|)psHcit}|C^6x!@9F`+zviy9Bb9eFQ z8w3x=E~sH2M_mM7Ta3wCGKxg)9qXY-Hly&r(xCHNuLwR!z2^w!NF;7g-hN|sGl1wT zEI(h6zYoXj4z<3D4h219r-qMFNg%?0r9O$+Z?0sMF1WTPg3cdL=JOgMa7aD3my7Vh zF4}orZ0@ner<=Oery?9N%{XgVH(eJ62QEpJq-le6yZR>bvl(^4F41`ED;csL!}8}I0w z{Ye9__Y6uZmhli^VYP9>&J+0Qx2`;Hv&Y?m5vR|TzQ+LJmUHzyKjB@_JrWP2X%{IJ1~MS^9KGwLa}LB+ z8=H(#XTfDJDyfTMNpSoa)59<8A@I@Z{A~(x7hrU+lKxa`2eo!_y{pd%o;$;l>zVrq zo=eN~y$b!Qpec0c`ND}bkSILM;p`I&St5@U`Lu6CF8Q0~2ZuO}4OaehbRrACHc`{D zQ03uGi?W7C;^}Bi?O{;n8j3DI@B2=;zJdmG;U_F#LcuLk<&9EzEsNiLxeUWf89MsoAoFPj`}QV9~8u1m$o?Ey;^#Sz%a zabNm3s~R$TWG=bWgu&B!9vk<$6nHy-ImD3Y8?+sy<{zXa?hRT`e*9Zca39sqrzLI1 zg71R7%L`p!_~R||o+rfy?nh=kN)&R3?ahF=T-$IU+4``_ZYIItYEqyDHNibIcwx6= z4{^T|)_O6@A^<+R76fqX`lIvtq~m8|QgPn%`P(O$i~o4q{z@w|6V>8WuWA!@+^vGoymp)3$msSjr1fbA{Cl4mv%*~p zl0=^Lo|$SuiN0tB10;Cb&v@lKDxlxu^j3s!KA2pVzUa#u1%+YrhK75eLWhJ*ba0y! z>=7&Vpxc)Qr;d4ir?^=GQwbL=Px3W@ikkj6HEaYzwas$&W-a^_3UJS^$^eM4C^yba z#2mFyA5Mm9r0UM;|GB>bTaWPGD`}}ihAE01!SXV+iA%JTU{6E#V+q+b|M}w5jK6c0 zhbIPia}0c!OhoP%HBH@`h3K^&=|wwPiFewDFW>A$WEr40^W>>PRfp|!j-5omaM>vc z*0Qi)^F=1-d4h|gexg-^@XJhD6nI!07r?)|TLMpME8%x7*V=VGgr>kfn(@Qcu$51A zpb6bwVh+u{pL^1Pd%xURi}72ic)MU?Aaf5C_k5rI_dN`blt*yYZx+C~k^ZodYc24u zJ>Hj))(Ee-1SY=7Hh|u(X8*zOrBGP2(a6MW3sJmxcF8|4!tZR%53IfFu*omN)`GnO zMV~QObadBX+UyRAe*y)#^liu?@@f=j3`McrX?DS9CpHc+j)!0zHJ5TweHMnl9S+l{ zDMM*HZ^16N8e|NnlO0JyJi6DjD#W}JDe3Duzv>rasNnu#YYn1~c3dc0pmqeA_+SQs zs5(%f+WtLtuMMnx4rf-eb%OaH?pWpy;%B>4{l4S|u#ce{+^{bPjnLBjUN3WCNLhwA z*EAE(gb%i|6qbP8jdrVx-3<_1IJV1+tqT|0E1^# zPwYA>f$RFBou_gL@&w-=3_4JcX3gCcJAF3G2e2Hy*y!q;17~ zsh=P2_}1e?zuBoGH$r#Z_C&Zorxu%2%{rcXHDWncr@rOIHk3?A$Xw>_MCab`oqV@C zu=6kFR%=reN(a-t-QX|7Z8INfr-Ezn+k?6%Ag~Mymi)g}(KJI}Eu(;pRtLOTx^q*| zy#sEkGqUdCYJrL#UcWW5T6jH|yYq%?A!zfxNl|~80T(pBm`<$a!9ZznXr65i+`mQ> z)fv(X^rwsit)Gzy?);hy#i3p>{*^UynCOo(UeGczeAWc+5~B_pVJR@Ykxjo*S&X-9 z1zt=4Z9%IV4!4Wz-S}AKgf)eF4?5oEu?nZ`K;;RUnm{(<|6j~tTPvx>;RO0<^|y#= zmt_)1)#_1CpWBXQr5X2owQB-H2P&M?wm%`)iN&Xtd|f%)P%E}4;q9Y3eBBb)zKiI8 z7ysCQkwcB(^mZtgTI_FtwTqfWQ?difC-K6wv>q5b%;i3BM4KE(6^JqzvK;xKV?Zs6xaBL1UAD`=kij&GV4Oa%>i>t{P8_xikY1=#4 zto8y+3ukHjzfK5H@-$^ss0GWA8v?wv?l7~nM_i7Wm+RigJwH-(;J=^pwAVa(QBYXC zs3BtjU&o6|e>yybE(szzUXOBzzyFNqwt7n?Y|~#yF4q?D z^gJp816>eqPW@Z)E(w0Ec{qpakih+gv)^|WIiwqu1M{xI>ST1ZtP5Ji@4}pZkTYp3fRQpkcJo88-Xb?35R5e#04iP!i+S*Mc z{aOs;7Op%)^kH_+KDEx3&4UK%tM{zWW<%tTNJFEP3OKGP`BjFe2~wW>QwC)Az`JwO z&5bVx!7Yzm9%nfLdTNPAC0R3IC84F5!9EA;IxPyCk7j{!;Ca!?@+2_JP!!B44}!N* z8d{#POQ5FH@S5wR-C(}^A z^npurdNV%xKKbNA`2coCiYZ=sKZVPR&(rJp=diZ=iBz2U2Rv_P-?>dWhx(>x&3yT% zQMf*$eaD#*{G@I8!Qwhv-ZU`R zkAI_zpM?Nd$?LMzb71#I$#$D>2F&@xbZLi1Vd=1W_I5cLWJDXH=*~94%N^y4h2i<| z-`CKoxRfMNKYGM*{do#}4CUss7D$E%i|2DpUKwEekBl3g+?6OR|NZm!a}ut~B?9+U%^*1Qnl|nDfTo)teNAWPFczk2x`^MmYJHTtP9DeZl=_>~0Rw2C{pFSHuo$1uaRhd6$DpDY+sQ0yU-;Rv_4$dSH_}M@{`06Ugm*IS z`?R&%VJU-Q$xdq!Xc!zGAHF&XDn<~_LdMlInJ`1zOX?Xzk4A$QIGtid>l zn`k{X>>h&4@zH#3Eq$O>C7_ur|kGgX) znr&00`{*=cZB@qCCG&P%{Pn_7wxSDN_!=$ju90wjtc5!`x)(3T4_TV14B`v2=15fh zC^lV;{>%M&0wr|h4<7ZI#JKLT(xB-vELu9wArm}^GR$?u&vi)XHvFh8La-T6u=8&m zeNcmDA~kxq^@`DIEh|yZJ_T22%w{exN5JH{?C||5br7{X`B82c89qkchp5<5Pz|NM z8S6d`m;cz&gbvO?$@Ejb*()>PvD|U{arq?N)LS=c+B*scV#QGxN(Ts?@A~HxD`Yqt z)###rxf8Vs=6ALh#{Y4BX|5plsugl7Cq5|+P)xJ)H3i!Qads$VDd#6EZfCz{|zwC?M*hIZg^Osdhoc?Y(ULj2$qi+ zqs?C#gJSNUf0LD{qkelgQAH$)q)H z637`oDC1>oh2P{>8&B#Q@ZyVqa3HY=3_s3_oNz9M&Bfk|wA4Bf@*TZ!LWS7ZV^v@5 z=I?~4>zDa{D|7&xej(Re`ep#8c}dv=RWSViK-O20 z++ja4@7aEy_IlBV^`3R&BV+AoJ+5ZjFWQ2~cJA5~@w;~SVS$R>{@D}F z#C?e~6E(qma%8<<(aqn8b4OnHuf<6GO(~Koy0xAD#Htnrr{CR0GQA3W?a3 zl;YF9TA#&U6yYl!_i0|gDuScic#xB`2_4uDe|bmv5HGBpYSPo`M9t>ryFbphVU@TY z8`X_^%xm{&UnP9I*Iz$UjaM|pgFe znuD=Iej5nyVpR>VXn@ilDG@r>Vvyd)c)aeaKTJn|ILV&kin;tn<{DQc;m-FR5)t); zPt&mX{-?d&V77KB;;vpl%pAGx_;j%!6qn6+ikYQgqKZ zisy}}Ls~9_O-1Eal%=j<8HFvTn{_&p!Vz5x`WN={>OE@et#7nF>(xP|CWeb z8fo4sd*gulS|z!BPdQw@Z>)HPx)DCK`I~RuZv$0J|JHZpc6dc~@Hoef7En5xVP%_H z1HwPoglLje;agmsqDrwT!D&0Ub7Su=c-%(%MWvk&vXO2PB`wXM;H*))bgCD=H5_QW zq&)!MlsAeF{p^Kz@5DEAd77d0UP{=?4?PIcw{Ttl>I3K6uKYBxDa67{yy6Kr+Hqk~ zd}~;L5W5}o&APNE@z`l`4LUEvpS6$HxOQv?4cc{U%k8G|SN|B(4!d#Ot@uT6g*=2& zzvXYy)b!y~?hAXwC`lM#$FR_sL+H$oomKS-tw0oZR#VV)!L>-flrJ{LaQ>);!p8@V zz+E(Pqs_b>xWCtr6?GE+w1Lk_qADa1C_W|QklhQP1Hb-se>4c9Uh#yvXB0RDD?R9@ zC!qbRjB%^oBy3z|QOYHaL2uk*RDkCoSQ=*Rb9h7oI&-cpet~B2iT2}tt62kr-&K-p zt%~7^Gvl?E&Jx(l4F5dtR)-F?LThwwWX#Te97S0;iay74oXVo6k)3%i z?$P%d+_gLLjre_H93p<4p&y<^KKfq{FE~cg+k|^ZLg4_`99y>AJ443MeSv$l89Om# z@1?^H4fWWr68r6nb2jd{aOq=IX(C*ctK8K1i0~mOO;&BN1***-zS^%w_-1Xzbq^fv zfg^e|A^~^Fp!6-3Zb7pj-pJ`H)4L8smX&cuiTyY%l)TG%6g&xdyys(r&jcj=2JS(F z5%|l@;+JRI2dZ1ly*KuC0CVdnYq?z$Cm- z@T&i~5y5x3nso4la0F`9zjY8SO@ngg1@Y*fX0W-#oZ(qM04myw!?(?*U?!hYeS7yD z{Jze>_lM~N)HvVd8J?d7ZJ#emKRJjx!~a6}l`|s{UMEP;BH9adS5#AiY+B*Z!4k&g zs%kL4U{lyXodq4nmxCjy?Ll5OfkF3v0`B$`%}|#v!olH)r=tC}*j62De0sVKPhZ%X zF0Y2LX}3m@JmM%It~-s7)>JoveD;daxK%wiZF=L3g0fp`PSnf!4VA0qoFTLU(^z$s%j(J7Ga*jhEw|A4!Kvs=yq`ModOf`O; ziSH)*T?4aU_zAuFeh-eu2{Mr%@S)yyq7NTTzRfp~AHe^(`}TaiGJsPG8gIt;^`Wf5 z)1W=ax-kF9O@HqjwaD%$HP*)Bic7V*ZJ#qMK{+|@121(4n1@laI=$(Iv-P)=7{dnO zgDqX?eug1P-PO0rt~vxmW>20C{vL#nck_!FbO%8>k61e3wfN!=7Vkgb|kN7RIt(mmLDHt=dtYsU8@ahu81Q5_OS* zY}(3D4=kN-P`>Tm1kE}_qHVxzbK7u>;1ev)53z;a>HrC~($4c;O>hNc`*Nhp!0o;jCC{P|hV(A8m;00v zx!V4T%KMvfL?ly=-=qV1b9Kg~y*lt(#&V7cZ3||&E0_rHsl~kBGTESAg?Re;JbkA{ zI)2Qwkm!la!vh6-a zESm7_9zj*KNWm|3LV@SHioyNRy@e0!E#P$V(*w!*ZlHT)`KMRD2Yk-u$MpPZhf0ds z+VX=s*liTbO|j&NQ}1niHXL*|>0S zPc_tcw$m@{Zh-yAKSi!p5p}X%Md-z%GPp1CWGvxo8aR|a@fHv8gCSe{f=Ru1peIf} z6j_!CqNe@D8eWC)hW(f4%WsuX_2IM@dn>|Gty<=z>^0zQ`m%Dnr4*{iX>S((%K}~z zGshxtA1Iq`7!P%e!5u3ir~fl8Kz?_vlq;;2=#V%nNn?U2&SB*Cp}8748oKs45ImI| z2?LW0S5lFSb5``$fGf6(4R1Uooq&Y2k)+1OP!vv)3b*-CfZb}_TC?wJanPxlvZA69 zv;X;u|Gd|L^xt)26n>ZDvt6VkJM3)G&Gg>(lt~eMp;JiWaIFKuQ`Zu^7ziEw8#<*X ztbs|g!n&|X0dN}n+}_(11yv824wSxhgZ*`vMP&O!;Y;N?t(r4L4#o|Q0CwIo&NYQz8l6+^u&-EH5hIhK9lXeS<={_ERv`$2bAt+qGo22M$>Mk?DN4yE|^^ z_gWtAh{yZ7>F2-K5c8l^K2=sD2aQ5@d5Se3IKjWO{m4uFANIZ~ zAgkcpSFo@PK}76C6ca2Q1Peh$K@=M$l#mppyIZS?R9}A`Bhmb z-Q3xmE&J9_{}#{oo^oCAV}Ck#uF!^Jjip&kcgSRejb{cM0s)?>+^^FIdc}YPbP^Y;SE6H%NgN5BHnr z|3*X1kgI=arzdE%2X-6fn1XR~il@pkC*Zqz@Y0I6p`bTv^QGIRiSR<|h-YhM3g}p8 zmGM?5!i7HnPS2U#_qN8yX4oY8v2Z9TZh~aC>*2Fjs}GrM|IuE#bKh?;@f)rCoz^9= zp;3vC>$f_v!I&pU9`w$ka z==G=SsXcpIyCrnjcQ<(IXt=bbLWKd6uf;< z-~wOIc=u&{=(CYxS5?0oIkNMHGoIC%n6qWC93L#2ITiMw>YA{q^EkX6DSSH3=?ate zx?tRI8N_xhnZH8nU_7(f)A`Ep3bnrR&vNbvBNo5B@S-_^t zogH_=ne_(K_a$l(?3CEO+e*Aqj7ReHcZINMX58~+^!jPs_YY?s{F`Cx&&2BHZfkDqN7pK`I!oJPn>$V=?&I#M4S?Dg209!l%Hq?F>^s^?lw->gfRx? z<2(=9F}J3Qn9h28mc6G?r(^X6R=stdk%r7^m~+VGP1=`(Y~Q>~AvQK1Y=O}>v(MR4 z%yiYsZUz4|W(&%VbesEGy#^PvJs{0_^O(mS^f8H^$b)pwLH#RhDdeRwaJAe2` zU57uc=L_b0v_A?8nlBn0^N$61={|g-JQ6Y=e(qnn<0{0M&3H}=DP3I=$6n9+u z%>F*g+cnzMiY=P&yd<~F4eT4&p0`Pf;l3O9>X=y5HIR5|;Cy zC^aTK4p!}&VJtf@5WK@5wE{K@3n1N&`UVSGBROWl0!|fYNkJ!0O zb`kf#618HM`zi*0MK2S-F+CJ&-dOMO_UGFDr}i4fNh;iTeN*|X;@#Oew;;j(h#XiMjrbn_Di{(CFI2p^v3v?z9Cd6_3#fv?QTQb2ULale&?CJS&ato6Cpb5k^1^S5ir{- zLq$xFdr{-`X;x0FHPqExseggd7w?94LjXtq@6z`EN%xP3F$lN&Et z#WL^q;*jy1Cr567 zPGrf5j0v$!X8Q%TQtn*Z4UvXDZ#Hu0HhxeTN}L_e_UZ-Bu1>ULpXS_2)Z+64w}<6h z>i7~NMzMSJ!XIf+eLM3|+ld-C^AjQ>VTnqYvwJ}b?6}dp zdXM`JIDf?|c_DX>q3wk&n+A?2LSbjTM2dSbY~Hg^`Lwka^l^XMyngXK({dE_UhiVf z`s#wUo*d%7BfNO4dA$2o_UvNQnf%TOW-`7owUXOsu{cK7Z8EoSZ1p2vA-)gX{-F=j z^FDLmfjJqVy=8ELH>_*aQ?rkZfu6(n%4Cxhz-!~`H=-}Nb1@ZowUbMNA<9HaO|aGo zOjRE8x*qUkGnMW6UiC+^m*Reb2Kl_@Jw?{A=?nqjfD!qq|zZdbHXH+PNT1)aqt>yMfRP8w6?3Mv<+oEo-*_;K2 z2c>N8Ot=9uAu7{~za+uygc&!DtGMU1f-PHl1f8M&QfHg{mW$kuPn9p>N4%MM_Tn{G zd|@mQl!jg|iDOc3-|FTnB{FM+s*zW@IC~@@x?-_kEYowEZ}8)jANxFJT+G}Z24Ji> zf!FMB2s{^7f4D^_9*p%Qe@rz_fSS05dgnFKV5{;X`=NjrtR5aYI{Cg4_dT0tiR}6y zZXej0s@pbkEa2d4Yo2FG>~+a@g@qz1EPS>@vEue zshnej>$65=F zBR+hwVIOq28hLX6w^RG0_{g!#m2Ldde^zIP3)A=`*KHo|z-m1OB4R6S*pzAcYhH5u zlx}zH`HFnDVWaFfFGzahz=EIamkFh~uU31rG?LA2#8*`X6wptsmzcZ3>uU(WBmEiT;^dXo(8PrY-6 zXR{nu)^$5WcEdhpl}`?^?e{`Od3`%rFmLV^ZFO62f6R^9CXx2gXVD$@z1SJT?<}2j zw$&YirK=zOe&+#qTzF-N2VEdrOhnJL+Y)-aMelfa$-@}l_hIJkChXO=2m9A)IWqSN zlh=OsaAmW8jWEkR=*mv@w*;2zIz9h<*$|GkoD2Hf`o z>#_!=WT0Y1>Y+74(y(@3jOy*A^I-NcK(8w3GTiP>p6&6(4xaKHdVICP2~swMpZ4A9 z3?H3Vo11NO;NH`1$POKC33spc-_PG=0`o1(p71`VO9PfLpbr z|DNQ&W9U%*a&+Hb883?O4C#w&B-P zZCS*F`&!!H%-HUv#d!-djo9x|4HrZI8nMdL!4LhHnzLEW;Ww-Wx%<7YkI&M~xbHQF zhg*j^Ik9hxb}tFywP$6^Dx$8)nK7%1gMVGlYO~umYn{?VRaxCZCADuC_ORUxt_k*+ zO<*l<)@Ob99DxVBR;cQhT!PV-ie=8dmLPG)a`p=eM^HceTWE^2GvpX1|N1H71oC=$ znj0HzK_oP-q2RqKgmvsTUlME#QYS^Eo{C?Ah882yT01)!cp+d{uHy)c^Do>Mdt(oa z#h2|+QZa?W-MU*!1h;{N?ZU{YJR`PFr1-t1nJsf&FZTXrh80)gf0%87`_KyJ>f^aw(0DSOc23M!);hj)i1Lv z`r@mMZdtHS>AUS?N13pc-TsK-6BC%B_u5vW zM-zgq|A-mSkcF*^{)@6VZGu~8cg*;>R10diNFN#@WCbTq9y(Xi?gVfA?tUA|>jHj7 zR-^8ow1e*j&)*ep)CQfxIHy(RN0`lw=BdxV>oWZV4|gRU(qJ2xT8^$tm*vg_lXMX@ z<-TLJ_`^=AHO7#Z`Y`8a+*t^IW)z`2?;_LwTpMGnVaJZ>St@qTaAuaHGpo0mapzQI zKHI<>ahbW!@p%}bbd>v_5Z{QqyEnkqbsr*6%PO$JH@%l$t=4BoW9omKYnro5GiOik z?YhGDdb}N*rhSp!ovConL{b@=#rbFTsUBhSr>Dk=Zn?rPig;))baP|2uilrOz2(P3 zHwo@Lr|-*DUcJfXA7#gVM^z~Nq`EG+w;Y+$H_rnc%q^Y<6Hqcu$!J2cLd#X1aC{pNFK%?^`x2sR&P zYZFf_m3nIrp*F`|c%`^=4PFLke}Clz>H^Uor;1!4HDtsHfgbKVT`$;$NmiQhL%eBI zm8A+qItFY|3?2(7r$5h_H%*7lQ^_#DyT^fb^}IU3!@ZZ@D=8CVImMs#c~=xJX6~%8 z?0`t`nDgvX&8K&%@9g1j`Mw>?4Sit5r)@n+C2pX%>+V?3jy;eZR{rK#nm60YpKamz zAe}h1T-ggn_Z`~nJQz>9dk6n^bJqN5`Tj3H1~4z`NwI*b zJFr*9Uu^&Q!rs7~L%bfI;8~%Yrm1ZYPCLY9_?{a;@3Fc5?Mu(VE8zxvv8f8s-uzf; ze*Grae{e|uVUZuJP&ygyU6ROZU%I6lD`&ETbH2T!VzQVIJF;h&bux<(lTTY#<-#8G zjaRz#B?yw{?YsdVu}~l_*TFN=9n?mwYA*T`!{meZM@+G}$u{5RUAdDxhv)a#O?uYd zxlCMFl1FuEEbHHOEOpJCSWue78pAbmA^Y)`aCndp2E+Cz9PVX9Mu8*0$f$6*@NfyA zh_e?98ZddiG&+iTsQbE9F7RPIj<+V-?zsw2!$ZXVwXVa8_FSpmwprjSJ2>Z8;0>6- z+lv}bOOIV5%%g$TiyA<3Nwv1oH=<>`D_u2!$R>;5fpS=V!U^!*gjD&OiR^+OL9rH*sFT@}V$71s7E z-3nvbzl~DuqPM`Nu!d2)zHqR*%Xi}zxuO~nQZNS(W4N@LMQNwg`En8LLs@)hhE0RbIbMp!9VR-#w97a zY4Z!%huC8iq8cih&9b8+vk!9b#drb=b(U7La~Awse1h5R@k7g=u_{>*G2ME{zLgcw z(vo(|)Suh;+hTaF_ii~X6FvNH|gqZF<`9eVM08n@4(oj01h!-u+JHJ8|`Y{(gzn78zGJdEtL z;h$mP4c2PP9u;G`b&PK5WD6wjd6)OpX$Qd(LVro-Z_2dfK!WZ|LWsJ=A z9m{H%Sa|%%yFaU0&kcFAn#Cndy8Mq|neAcbBa>usU_7_aU0-~DQ0RRa_oi9bcg20E zyK<*OzZGDF-#}Bq_C&Zf=wI@^Qm?la-pQ+fZwFt z5>Qt@|M7HFDL5F;-!t`830$k+G!XhC4@$b*=L=Y*!khNwEGzCg)S%+-zi;2_gJ>Tw z@4n0&_H&DtttMX?6IR*ivHbP}w&Jqbs!u!XS)SOX{h#O7F}=M`igEr`%+vdllb+IT zw#r_h%6Lo?vpl%(o05M#ySOva^rt}~>nn`bH4v>~5E-hnm21oiIYH{aIzIoDhCbXFejIQ>5v#E(i3$+RSHmzbS7mFJ= zfGHcdD{AYpf(lr?ZBtNZe*?4}Om4j})Ch~k-c*eCe+YKjH)=P(ErIlwQTdO#|3_3$ zfAVtF;Y8>zwu@JKr?i^X#MIIySuU_J(_&+`f6m`UIt}dS;{icW=UvCN{d-`L|(fEB8NxGrAKV zwy}dQPmUMPXk}OTI*%JYqmd1q*uSP|L=Br}w$A0sloGbDOF6?cHJ!QHKd$DlcZEOU zPt<={<-wu30i(v-Rf1WJ{GaUu_0SbN?@WwN3*^piSU>w%8`%18)_ZWS73PKi)e-b- zf)yQl(*+yr!OFuRd0tB$>(&SdI4PleG_j)tZQlOEyRJ6LV1bS4Q zRu8Jwf_G_)X}UoZZ2IEpai*jdG=v=I7_4oB)3@b+=gew><*Jxe2h!J1~6V8@d)Cb$`1IY(`^J5UcB(|?OMiIsr6r)hUx zYAib*QPZlpHjC}rF!*J!;2oxGH?(MILIrzk2|ud}XSQq~(Zw2H2`+wkqLuBtEirPXd^yX}h~r+UdZNbcv5JBLzk^=)xbMNty}kXx zzUO&Naqg+V`?>wGFBOxX3r(zMKF-}6jlVarKB3z^nx;?K(Nd+{lOMVL^M*S`Rd)6; zvs+hZ98u_Hrp59jM~lB?ch8o78te3&m0vk^`_SZiw%xRA;JsNcn<(b`D(uPN%U3^k{==L)R z=n?MTGj)rjvgEF>Dfy#ws;(D{;m_!a-}o21v~}S^)1iK=QYDmsl~OT z-jBdmyGr8jj4IH*s4KJeKET6Q%~>x(GGSf9yFbtJF0nuNM(lH4p9s?{jLnVqhjZ^q z^k%eh{r>x%RB`dO7N-3zP_~Hs(x2na#r&m>T`W1QIn!xt7x#a-TNQ`3+L^LOhVAY< zZS0JGSWCy+X7=LKN6#&qP0V%rqsJF>nwV*M^Qcv48rkx>Gj5#xUBiSW3mxk^bJ@j! z-oqXrv!G>fxT@&WDtPidVaWt;ALirHXS1}Q)x&%Hna1%|oSz=>hzWOWgpVHGo6NXz z|6=`j%P4^kn0@qUaL0o#(3`zz#Q5FakXN(fOt<|Ds5@p*F{`%)B7LuLU%amb{>Jg& z$5yAq-}|v^jU!fU!sQQ99)_g{^0IhDt5CSE_9GrpdQ zpDpU##_exAW@1_EG>JPGwkvV{_orQ~e)joa?Q=R=chJ?(H3mBbB?%^%0``VUO z(;b`Gj+|Kv9|Aa>ddxOLqlvZhk55^zThHR2b=rSCQO2BF3qDo;v|>}gW%B)gR{{aM z>vxE~sfDdMT{=Ry>%rniP10kihmZ@Kv!mo1KuMNwzn4!l$j9iG?@4+J;p>mu6m$DL zmH0}F&uMgn_!RHT8)v(q`Qefz1^#Cse9mgmPP2MgHom*7GPMYr+zM)=)k5K1f@HtK zQ#bhQJYD=@YdqXk>(ZIiU&kgt)(DjtYGJOwzi*ZiZsxudaqPFOIJZwn!^!0H+$=WI zd6m*{1%J?<`Z!O1t^@q+<^Seup2HdzdBw$rSF(ELI~s+YpMNr+B4RdN&l+-6c712v9v*oJ=S`8%<*}DVvY(u;=jI4NDw1V5GQnqyS$C8{fu&+z> zu}*#na&n1DMTXq}eG<>+f9B%&hl?^VcHJt6MbAfcU%q{v+i!n(m+(M1^C)q@vJC>* zsc(XTuG@3rs~ScqsYh-*7;rO=7UwWEc?cR+HSrEw#{GUX&KiJ(@r-XU7UZPjk<1j z@lheL&4MrP-0{w0r-b`?XH4^DbC(%zsaH#c2SQ_)-K{8w&1NTcgMZwIK9lqbI#=tV zR4VX6DQf_QmA=!a&v*dY?JN4Gjw*tyrl)hGYW3l4qT+=az1;WjKE8`r^$LVyvoirf za%J52Wct3{uWE!|C(*-;QraNm*|-ns9nGL^E%(KYzXC*eP1xpJn$C_ql`cNQjkD&( z<-hqywL*@jRd2)GW>{cg5N^J*n%i$7cIb*Qcm7~w-`lYx{n^%e6D>YebKg0SypXlA zCVGo?U7-sR6>fG?&id+F!nNu-a9k9O|+o8#@y6;cERf zbwNpP-_E1?slzb{jI)!Bd6V9J4TpJ_9!lgqy@Y_b4E1762F%Y&VO4# zEc(X!1-1Wm4!8I9MN%cO_s&m2p4T35G4ORJn-|JF8)Y|+3k!!oqP-)}tSEt154W3$ z9Iu6hU zv)ij{jqm85XSw(PKJk@}hk@H6ua?RMvQJ+eR-TY6W7jXQRrP$($i!p*j=7c9#;$9h z`R@I$nfV_*-#Tkq1q)mq_PC@Z9a6Jq1ZEvbWPUA^R_(G21NZetVwazmgO|46+Ji1F z5V*D{wex!itp3yRxVWeb%w^oqteDdU2C4T=m~cBxkChO1G;M=fn_mi~jBSR&X>9U! z^(H8aopo2Wu?a#>vaRACjqtX>YNpzz2XK1U5#Fi4xciU%gF`V>ve}Pwi;Hq!Rk42I z(xp5<>zGYm=OP(yyeXXsHImP-XZD(#Tary0*^`xvyWVYYWBYeFPA=^%X2NDt9{50^Py&?;3QQz%6H0ns`zZOtw60 zujbPPPEVbER&d|DD7(H>C(f=6%!h^54{x)AulxdW@Bfsr(Z}O;AH1n$+s@p9gd6qT zb4syE;TiQT=+lL$%q`siO%1*;RkLblNgH&mee9mHZk}aFE37-2a?Fb_1~T33vdf3X zPvyAz*LjJD#PnzEChyIHhTwWuGg{zr&Q9(=Wx?l-2E$=&mSmR2nI#@)oXeHn7Zm-xO{=ogrRS zZ)VfLGRFBlSYlAaR?dtUv*7N-H@aN;w3eG^wtjmg;%6MiqHiwDH0RFitCuSHX!kh} zGTkrOmsZ{bw=1&^dPJ+i{K()F3p7#S{Hd+j*0PVPLWuI!LNLOy=~yk4E>KnCR8-uWt@Ev%J&|Ny?3l+}<|%4~ISD3Jzh7|Qqw;99 zZm)X;H8#H!0-x8xh{{zx-7bxABBt@!g2onjTDoG1S7#d-Nw?42bG!}QAMSc{VpTJw zpV(`Cm}{?VM~=<@;9L%pmirD#`sBf}ob25qEy1uQ>YJ~SNea`NSU&Of-4Zt4?dbY2 z_ga?n_DO*dw=PsnsPSuUY307V(Ow%U)y8h^z4&F8L<^fD{x{2VZzFS8zV)`8d%n=K zCSc5JZ|?h5UyYW{32S7sl^0(Yq&Kq(7VR!wTFq>o%#Z1z+;bDvXFjj_XOysxJuYR9 zPO;E`V&Y*D>l_$;TIPCidntr`SR#ET=>hcbd9X721Xpj%n$jEob&zeI<|QP2A0+PG z{b@hHi2L8-!V76hNnji+?^dfA4@cKUM@aG)!o|Le%QgN~gGF*tgcWZSH05t-f0NY= zow6GbtzB3T`+vr(=<^oB`zZny!`t%Mca;%}bLKu`2`8ns{luD>4G;fA(X$O~z_g`{gcMa8_z!&-!Y%!}(nDA?`WD zWQU-4=DX{eKyGcVer~_tHL}} z+T)Ai$jp`R*1K^3=Nc6tKiKjJvUsz`Zsop1U~*mR$TGWH?%aI$4Rc%g!jSHKER83&q_MH*eZd9ozAXo_x_8;+8SH(YxFJj}&MV0H+N}0Xi(~OGArA*jW_`&aK#cX@eLhHV| zY$jafvB7*Go`tOJ7%Sr7%{=8FE6p6VgK?oLmOW)jFi_VJAyAqJ;(eC{zWyqKptjZT zRF9RxE1jKhB`iuH+Dqlvh>EGBo?s=kw$yS9EC2XCHZz-L-2CiEidQB|+HPA_JAJ3f+R2GwPg@}k*djklgp;=I~SM>o&dWHb2w+CbC+bJ2%M=K84L(lF!cbNZi;M&V6Sg*Mm#OKk^&ePN7iy3h2}_S`v^p=+u( zpp$eWy#J;rI9Yft%wF#TN1lC9^*w$HM8qHYcL>=tE1&r%q@Q?m`;()DkEZ)D$r%No z-hXsqN}-pwKT@|~4bzWAhF`wOCIqP8KEL4@yBM~hYW&wJkmc=jNhC@ame`cCpmYZi z+cPiUZlVuJNu6@{AL|dWOgWABl{;LWuHde}T?gLl%gn3{vf<8U4l8U(@M1M<^ps@k zU74o$9(U8xYuKPz>K(^>K5*;((u6G&x%27rC+^r{8wq=SZZ5vP`YIH0$A(X<91EiB z^T+A__Gg!tZ7W$=62bOcZu>KIJdE*HKNDRS?7^m1No6+{N5y29r(;az<* zJt0be++L>-mN4+U`+-D`F7x%h^Z3+TH=cum85dv>H{ z)68ogMyy_QmoE!dV_VLu^T_GTvxFA-B_U$W?u#E-rD|!$T<^|HXz4Iufx{P;>0~N1 z^M+j0ed;n`E9z9#-MyAcSdJ+kC3cB5I#yWCTWQChhBtjHR&!>{8(glGhdQ!N)BOD} zf4R&QlnVX#|6K?bAUnm^+yJC+o-=o-HiYv!6C{-Mk3wj$=_qw>zqe59%wKBCMr>Yt z>DO;g+DstXz?scG0MUjY$C$QV0hfk5eNX>7LV#`SJatKDI2R+4v}QdwN+$a3uKZ{K zAuGmZ-&8*W2W@Rkjlao*rSRJwE&LjAcJRje-kB!AFI{DHN9qdLFMFlBL%|4~23`h# zf2sh_)I>t7nl3UP6A^vpZ^(YlO|W~edz5*6 z9W{Mihb}}vJ*MLJ%nmXt=O=1dIfK}{Kle^3JA!`CcD>nmuRyQiO0$ph>TurlQEA-W zy-@URw_M$mV_+%0LZOcPqnCX9h@~ykX7JslWcRLcQ)s%JKjDO~7ThXR?JEAH#OhO? zj1iUF#2NxFs^88%$1d1;?C#ugnfYz`^H&n=m|a)6N%1KsrhIgG+xR+1W;!MRa{oPB z=HblqdwsDf)81sc@9~f!J8O2;<8_WPYi;Q8dT(mM;;+4rHG6NzWEMn}ZhmFY_}$FB zohDl`m7l|Pu1%UutLZuK@GLcWwyMI-SCExji&HI&EUcbGG$8ha_)ai(o1eO);Yp&!=$XErB<-#_$hg3 zRRcKCp!e3A>&}OmRmVGe&{-d+T8r$IL-9OD)b4^%J{OX})&h~6$>!;Md-7ai% zf@MKWg)55X1YXE*l7)K1@A!*W=DpmvBxtP=gE8 zHfvp0wBf$zx8u2&j|+HgY_EJa*Bypi{4dVi;tJn|7ds@ZcZ5sxPOP3b$_^$Ep0L06 z)dn6e$*UapwudIQ&ZZR;o#DnWn~)VUZt!RB{h=)m?$DO3HoN|wE4)3kOr($7&uMXU zP3zeKGZ^-YtJ&po0$RRr$jdC$WY@FrvM~#+m{Ri%?Wb~1?9+zFN=^o@EK|c>WF+^$ zgR%;XO%ira>|Xlild8dX%<^&9mn3eTaNjiGh44E|_U`ocu%~TyY|Mzmxfuga%usBo z{k@bM`?KkG<<(p_#&_R!--u66Y{spE6+>p`OhZ60aqnn37HYV6ae;sdWOh9EEzol0 z_H~BcQn=y<5i`5`%(y@N=6g#2(vowA9_2-Byej7pCuDEymDs?#tY`D(w%dX9tItnT zHakM1a82i(S{LvN-!a8X(j6*|I!24|xkF8kLA-RKGbk?78=P{*77oVQML*Ki2ce$V zAC=$j_|N|N|JRMj{~PT}jHLf>wf=+u-?W4KpW)$Q?rGDjzyFP8$YT-H5eE^MqFn@X zXXJs%?T||#j{1iWp`9(t`4C-EJ{h?T;un-BBZ{Fs4*7WGWr)X6PU+X6JPhThkjEhR zMsA3F6Y|-JZYY0`I1=Ti$Yqf~MBa`Vib(Yw`2FAQ8-@1E5Hr!P1hE{E@(Vz_MwBZc z*F&C#+!i_2R|Mr$?hmM6MfoAl=8E-S{eKGe707c@pNzN?(F*mVXs?faI_jSz#voFC zebD|G%6k#r5w{?^BDNzMA}&CDib&9KITL1^9AKpj#}h@(XJVJDWWtY z)qgJXBZywuf6|zKH_9o!JaQV(Kam?FlK)>pPU)#%r(*uo5J#ik3%uTOl-D6A|2%_s zT8P4k6^J2-8Hjp_lz$TPb(nu5=5LL1H^c#ypG3QB$f^7SXg3S-G$M^_OXU2Bj)k^II9`3XcSA6=KmuOR9Z5Ook4B8~4cSby@5c+}Il zYeO!FD2qsbQH-4Gmxr9LFNXP7q1*&943YfW5A6z3u8w#Q@g^d*n=#slqx=$LGxj63 zLoCV{puG)pikJS#6A>vcbRqXa3__%Qdy!AUe5rkwBB%K7g?1EoLXrC+E=T(;EI0Lk zI_hb>A40nZwEKX12gEEyio1cx$^R%G%tSfGCF(z3lv8_9|6AhqNPi3M9-v$qIgOv6 zC|5&yHS#^kDK1VyPV>WlHa{5=o(Tf{kN=Zt(AwhP6LF{s~x_zvwHktZRico2@9 z{P_p+XvBAzj^eQy%4;Y;MBRV#S%h)}#2bj?5Gn3aJ&RC25|QGV7nKL)dy!LntwQ-{ zl+*a(N4W+f`THDfuhA$U{zp%7#24*?QU4wDr}>KJC0*3(q5V1JClSjKY5vQ`d_vHE zAL@Ujd?oTfh~0?45TB#nZR8XWMUm6|9D#fW;#|BQ`Sov<%OKJ?Ux7Rak^F+jHI;t= z^`{W;BT`(WIA4JBTEq#6d58*#9}%e>6t_B2PI18=`ADn}wKJ`YssA>k-7jpfQp8V) z9;jb}Nb$iF^~#9b@H!jN&H<79cq?-9XG`R&h-sM5X3S?b@?u1aQ|@TL2IUix-$O2m z=^cczZ5sxP*3YD^6y-fAI0mcBB%Q(iZfIm ziicE=$7nByNb!3D@<`105^{<+BFN2AACDM?@;KzBh__I#i0vnga!o{9kG(=p@m~|u zO-8vCVhhS?9Ypc=9_q;tpCA`Rdz$|b{F5KG?{Snf%-9_yE=lc98e}_o^O!Ez`pNLa@rShs{ zx@U;gjue-IQLc|j^O6Me#fa`${#U=bAKZQFQk2vDNb^G~mOllt3$Y*ZJEl`ZG)MVf z`n2+gJqMqU^wfhUS3qd*gk)UVet-5kVyh_5j{#VJ6pgL*(bj7af98m~j~$^+A7qun9I07UYeg~<63tr6+^ z8<5j;gNMi|p3K1OjQOtzlHBu1YKI?aNB&6l{)}>p4`-3nx{>^j+L_vu>Xm@@)Zc>0 z{jnU?$Z6coz;>begYwBoy8uiliu@QN`HL)anxAQ1OmWBu^)yb%pBJE<#+Lx{Wr%d2 zYlY<_f5||7JmNe=XG9uLm3G0Lf)Cs1CGNb_kK@^Oe`F&$mc0_C(G z3Bq!mKs#y|N;emmji+b{ZZ{#;H9~z&}P)_lU{GQtF6zaDl63@VV z=s9;N>V**3Ar>OWqkSuKTDMdo_eY$DNO6GXiCC0RK=ec0gGkq>d7H)~J@2SRI~q?k zfBqXk^|73^4xshrB}}h_NbzSOa$iIfOwWt`vKILV)bB!!AUou=4yAar1?BX7hx|eT z)cMvF9~r4BE>_RKgl1yVm`KLHx(G6LI?6TB&H*`%WAa;y>#I;ta`GD*PvrO1?<>#`C=O9QXuMb9b*a55j?y^l z!0Uyg-B#ou5$XLEwHx_`GwQVvsXpbHPb@z)i(4eIGWWhZia9yA|0`IG)X z_B3x7qkJslY($DbG_Pp>V@Lf#&og;ZABLEYcn*>Jp#(WSr=9_4g@G>DwuFXbbj zj7a{w8acfuqxL4hXh%KGrxc$iqMZDM#@k_(k3lR)6hx$P7KMB+`Vq}1G>&PVVTpFe z*l)wgw;@t_YLL_TBL5)2CBJ!ub`(c_kdxmO;B{#I9ftfl+TBH@xJ7Z7>U9(K6qjjy z5H_No;x3hg;&umK&j9UcyvZVWL?pkV^yKd}4^p{A(VphbMC8;?)ZTP(}8Xt6D{u1p{5tR@R zAkz2tGfidbcQBLm_iPL@gAnGYTHzKEb!VL47g;*Xrs zeu%b+Etn70ll+}iKc@M> z6ZJue2M{UF{OeaVZ=FWF-H5b~rgxd0#NAnuRLAt*ZMmw56($Rh|%Bg)fVm)XcAV0g0_Uq7o8{%igIMg#t zPxYp8P3=YNL?ui|d=MkdvPYVR@+EY5bX^oZ=imavw}j z>q;sQ&2QA6!Dyd?_B7vVBA<##^`kgKaXSUm|3I9L_M;HLpu7_K|Fkp8$r*$LUKhV0&6!o-zq~|X(DCa|rLOg-j<3;`uIX(B( zLb)6w-S^UbqJ#GT6DN@$M0;BIQ9d+Z??8PBq7|n5f}F}t&*P~bDZi6wABy&85Z|Dj z=9^;VbYGu^oaR9T%2PT)PKduX80dJLnS)`8bB9kmygYck5oj>hY2ly5{VLA;D;fGCbQgy@FXrG3of zQBLa`8vnH@*GHtdF%j3nQ&9dKk;X6e-vpG?eID%tpmpO()Q2O|dW+h@0p&C{sE#SVgn*QS6_{s z+Fchp`7`kySYC=tfOgV|aflxftq=2_`uWN$(N60;qpFyPhFyzi?cO5y+uM{t7Ujw~YqIFR^+8@RA6t`%e z*@yadh&R!W+JWXnP1IjS{ZB+%x7|ejdaNh)b1=#&9*jo)OqAy!(s*b>-i`U+Lr&wI zo{wBWc`RZ)BKh4uybjH)u%&UUz;JP_>qD97@`U0Lve%riq<1yXh-c-j$8}d&lS^qp`6CgS>z-qzi>hM@_&&0 zS{LQwh`;eV6t84aZiF}oQ3&xg+It|U{=A0r(^%f!$d98P&8zf0SOn#B5y?NYF&(uZ z&13Ovd+Iyj%`imB-^;#-V=H?d!7T#>&m#QYW^S|9>q6qbYb)lxkgQBVHYf;<`RTanZK0r{Ug%FiJt zB2vFJAXh-7aZCHJlu+)9I1Z8474)1p6Xnz&H0~+>=b@hbisr{z*iL?^r}1_dIki_b z^7V*Q5UIWt4^pu`XgzQP?WkW*Bd2)q1v%Y!UPgWxk^DFdc?jlThCC4a+ZgM?i+Xyl zPUWpd`?aVKKt2zV)|Doxr*#a)leuU|^ZiBSI}pjA_8}kjk3G%5R1XQ%??t5eNAXk> z7uy@ztDFZG`d${(RU)sy^eI?Au3-D2d9i1b{y2RW6y4!JU-I$|H@ zL)W>C`~d0$5w%fXft=z1`Ckg^H>3V3;u=KSuR`-B`Aa(4Q2W!oLveuO zE4BYPEH^zb3c-9Sey1RhLnJ@EiF^_w`NMwXH1AOU)b0;ZPwVM#Sl{s|r}}y#r}iSh zr}0K{q7vgBrJb9+W$Kr{Qf7Ohsdd3wEu|wfZAOH?Oq|$c&B}6 z)K2u=jpB+M+Alz?Mx^mb<5U#oRK61A)PFX}X??AY+#QkpkH#PQcNpr)Kd66hqMY96 zXkhzMIpt7K?Mw3r^>ZTXHzDd`yOaM$qdW;w1(D(#J=gt-a*CtW9&~@=gnGK~p?$pM zU(I-3`ac52lL$;l{rUmH!m*)L!ltp{@dzvBNjP)3U z=`sTP#_FapvV;3&RWki z7rE+nhpTtJd++ajNA})lpY#0$^v;9x{~_|Ep!Rooe}Ckz%Y5Y4Y5J-8#`!jn`8%vk zw6hvKLOap$=3ov`f4mCUzw}Gv%zfMUEW}>D``Ak4Nx;8>?$ejxLGwA>v;vpXkHjKr+ zZwg25efA9e+w+0zS_M1j!Mdy*?=#MPUn1EPA_&UX*wj??%V%X97z0oYj&jDveW;r`(H&Abqa-Cf$-3wJ$J!^1#- zcQXZU{ZJO(9*hs_U;aKk9J%Xne)N1~Tr|S2HRw9&Pu1w}QuM~naCmZXDro-DKj$Mi zU#+3N?8tY6tH2THJwLd=L?G9%-@tE!=JQmva|FB(CIPLtofrKt0=?_&@2eirep~dJ zh{p!Vy(iS~4`G)AG!L1#tRtL{me@Iu);*$mw>$51e7kr~{~QU=3pzh_;5&K#1Gsg9 z>slT8Nzi?@9Q+G#2klg39?~MWe)t#qJ;)1##mI+8WW+9A8FX81k&3`F|ALaXo?O1OEf+w_)%xpx;0Bw+hJJ#{=M=+jcR2`)Pi0KQX?&zgms` zLgJzm?R^E-LazT`LT)_iAAavOkL$ns-$kCA4t@+K1l`Z{W5357MQAqe?lurIg{+y)K-dxOq{_o5?^cLvSd`oDj7DgpWvpm}B|^Y8ByilLv3 zd?I`w=>3Itp8i^fb#mOzux|tA0DWI$?2TXj!uW9A^h4{HjkIGP=m0mrRf3-bt&@Dt zy3X(Trx|Ax>??w$LF)$d=3?aL?MR-FLT((m&m2T<-nk36-kS!m0y#(9ZxJk8!2EF8b}De)xoTtve3F-(qLoTM*t2^j=^Q{2I@x_d91*^yWd^5nWIB z$$8l8_m0o=mivSD4S8=ho~s2uLLNWX`>!HT%lyuQ&&4h?Xnq-q{t{?jxCNhteGRZ4 zSPb->H5zVw-lGvUUE;Be;?iz>={ zJB4*{^PA6kPLj95ZUyK$a6NoJ_zV4VJwHco9@9^=BlmmkTk_D?%y)70=Ev1=^KLD8 zXY8H#3CLf7uKxjeaqRW~>By~zufg3H%EPT|)514{`gcY6VK9{Um={vPo2myJgW>2~ zz@x!WzyQ$s-v=K6>fi1=FOlmr;nxFP&G=(zX52ZGLjX81?6I|ZH^z5%Y^ zRzcnhd0Q|9x%-&+@X64-&p40v-+LO*nM1LkjsL}`-PFk4pFE!jAvb^ZfqS2B9HvJ; z1AHGm3p&5~;KjfmU0(jJ48hCB+Hkb`}` zy>)Ck_%qPI&(IKV-1MTq`d2daj??-w20Pm=f}M5o9{4)!dc&=^TnF=cd+e+)Mq^IH(|(e$Gg@3VdPkudDif$mG* zkMBkPJ zH*n+2INycbJnML-(ceetr-A0FpW&{Pezu$ET$i5kyVyIgui=x?UxB+n6@mX9G|z3v z{sm}0^IT{iuE}%WJD4{MGJiEe`|}d}=AijAB|I+nC*iF@*G+#fja$_5Vxo*`V?3c`2NE+k#$ywoc!H+lc~e4Z+;t9q>Mwo&GwHu5SVKGeOUB zpTRSL--72r*VDhd;JSB5uV0uy+99{DF9e?i8t>iU^}rXP`-^-c@}!{iQXbwPbRNC8 zD1&?!XdG>WTbEc@+(vGlw+`;T%L=&rmU*Zka_8}DxPH-f z!S(wdaOd>|?dAUOhmJqK=j1~l3|0r7SMQCjM^B({0eb#l1^0g4{yE+e=o5gAW7(gF z>kr1;WaO#AWZ-!4Fyk@q+@I2-4*}1C&Qk}Tlea?u1o>gOb)Dm>fc!0XIpO}U*nP^n z+C1M7`_tI_oxHQ1Tg`}kgPHSKvHZM`!bd0X<4^@igZfW9^Dm{0S`bb0^ukb55VJCAjydB}O1$8&P) z8vW@Wdh;LW}#)-c_tdCt$o^#z(B2URWhQe!L zXPr_B`E}&hh2EPELa+bmr=C~Lf5y3Sxt{iNgP!w@*Uyo61wE&@KUshKo#7|yU+$Ff@uPXro|3*hDl<1Ui+U3dN0dLaSN8D}-&IY9kdKXab!m-nv&vHt-K z0*#NcaQ(@==sKA1KE}@Pt7GZ^F60xze}bDq{Y`&x{N|H%*eSO@^n79+u@O7R?|G#u z>${ZxWP+RbjNiHFt!LuVUS+U2m=DxHtb@%f-iL)?Hw?@Lb_6GZ4|(q)xPGQzc#pLb z{ZlX-cnma88Qt*8t?c z-+a0Q`C!oTJ74#C-u>3~%7Xo2(DfY--@$VS;Sb>U+q~pDc|LO;vh$qz!1aEBTtCPF zHx3r^o?nn#KQ4ujKtBb(7CZquKOeyhfyQx3xcg&u{QFbn<~9A}LacFHS8d0>8Q2~4 zyKpP`EYNx&K3xB>J}};&qc@McU*ASv7W8|Kc3BulM)YUE{a`i5VLhn->UXZwPVC2l zLqPMI<1vq0XLrD^PON@-J~F?$p011cJmEYyly<%X{dZSm(N9J0`g+gg{F-m|AJ@ly z%Dixc`7j>5Z)-yPd%??~@nrrpp6$;X+H)PnygdID^!!l`ULLfbHvgHY3!;BW`<39% z%Pr*Fkaq&Bg0q>oK@AIC{bsWs|Gr{v< z8c@Gm3=aU8^FG&OEpo>f1+M|l2it+hvw2Lv)?de9=lU9d=3V1;J9a+bl5tKzZXVMQ zj1TV#u43nYei81x1j4U@dBCQi>pF?~a2{X5=U}HlwSX4^OMt#F2>t+c-pavmg1y1X z;Bv4Y_#5xf5BL93@ce%rx#w2bcM9@Ia3k$l&wC%*0DVu;JYqi`mwr1NJL6Bka=x^W z!R|FUo%j3i_oI>9-w5>j`*CA5eI=RO1{V&}fEU%&g^@t^zM@n3D8eh1T; Bq__Y8 literal 0 HcmV?d00001 From 453818e85fd2dd4047d148b4e334764983e8a07a Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Wed, 23 Jul 2025 15:04:05 -0700 Subject: [PATCH 08/13] fixed cpp tests, style formatting --- src/M2ulPhyS.cpp | 12 +++++---- src/calorically_perfect.cpp | 10 +++---- src/calorically_perfect.hpp | 12 ++++----- src/em_options.hpp | 4 +-- src/loMach.cpp | 6 +++-- src/lte_thermo_chem.cpp | 2 +- src/quasimagnetostatic.cpp | 8 ++---- src/reactingFlow.cpp | 44 ++++++++++++++----------------- src/reactingFlow.hpp | 16 ++++++------ src/split_flow_base.cpp | 5 ++-- src/split_flow_base.hpp | 1 - src/tomboulides.cpp | 32 +++++++++++------------ src/tomboulides.hpp | 18 ++++++------- src/utils.cpp | 30 +++++++-------------- src/utils.hpp | 49 ++++++++++++++++------------------- src/wallBC.cpp | 4 +-- test/standalone_chemistry.cpp | 2 +- 17 files changed, 115 insertions(+), 140 deletions(-) diff --git a/src/M2ulPhyS.cpp b/src/M2ulPhyS.cpp index 2ad80cfe7..1576be49f 100644 --- a/src/M2ulPhyS.cpp +++ b/src/M2ulPhyS.cpp @@ -237,7 +237,7 @@ void M2ulPhyS::initMixtureAndTransportModels() { TPSCommWorld, trans_data, trans_tables); if (!success) exit(ERROR); - // Instantiate LteTransport class + // Instantiate LteTransport class #if defined(_CUDA_) || defined(_HIP_) // Tables from above have host pointers. Must get device // pointers here before instantiating device class @@ -1784,7 +1784,7 @@ void M2ulPhyS::initSolutionAndVisualizationVectors() { // visualizationNames_.push_back(std::string("rxn_rate: " + config.reactionEquations[r])); } } // if (config.workFluid != DRY_AIR) - } // if tpsP->isVisualizationMode() + } // if tpsP->isVisualizationMode() // If mms, add conserved and exact solution. #ifdef HAVE_MASA @@ -2094,7 +2094,7 @@ void M2ulPhyS::solveStep() { exit(ERROR); #endif } // plane dump - } // step check + } // step check average->addSample(iter, d_mixture); } @@ -2465,7 +2465,9 @@ void M2ulPhyS::Check_NAN() { int dof = vfes->GetNDofs(); #ifdef _GPU_ - { local_print = M2ulPhyS::Check_NaN_GPU(U, dof * num_equation, loc_print); } + { + local_print = M2ulPhyS::Check_NaN_GPU(U, dof * num_equation, loc_print); + } if (local_print > 0) { cout << "Found a NaN!" << endl; } @@ -4234,7 +4236,7 @@ void M2ulPhyS::updateVisualizationVariables() { dataVis[visualIdxs.rxn + r][n] = progressRates[r]; } } // if (!isDryAir) - } // for (int n = 0; n < ndofs; n++) + } // for (int n = 0; n < ndofs; n++) } void M2ulPhyS::evaluatePlasmaConductivityGF() { diff --git a/src/calorically_perfect.cpp b/src/calorically_perfect.cpp index cb81e3452..1c122b7f5 100644 --- a/src/calorically_perfect.cpp +++ b/src/calorically_perfect.cpp @@ -58,7 +58,8 @@ MFEM_HOST_DEVICE double Sutherland(const double T, const double mu_star, const d } CaloricallyPerfectThermoChem::CaloricallyPerfectThermoChem(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, - temporalSchemeCoefficients &time_coeff, ParGridFunction *gridScale, TPS::Tps *tps) + temporalSchemeCoefficients &time_coeff, + ParGridFunction *gridScale, TPS::Tps *tps) : tpsP_(tps), pmesh_(pmesh), time_coeff_(time_coeff) { rank0_ = (pmesh_->GetMyRank() == 0); order_ = loMach_opts->order; @@ -178,7 +179,7 @@ CaloricallyPerfectThermoChem::~CaloricallyPerfectThermoChem() { delete csupg_coeff_; delete uw1_coeff_; delete uw2_coeff_; - delete upwind_coeff_; + delete upwind_coeff_; delete swdiff_coeff_; delete supg_coeff_; @@ -467,7 +468,7 @@ void CaloricallyPerfectThermoChem::initializeOperators() { rhou_coeff_ = new ScalarVectorProductCoefficient(*rhon_next_coeff_, *un_next_coeff_); // artifical diffusion coefficients - if(sw_stab_) { + if (sw_stab_) { umag_coeff_ = new VectorMagnitudeCoefficient(*un_next_coeff_); gscale_coeff_ = new GridFunctionCoefficient(gridScale_gf_); visc_coeff_ = new GridFunctionCoefficient(&visc_gf_); @@ -491,7 +492,6 @@ void CaloricallyPerfectThermoChem::initializeOperators() { supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); } - At_form_ = new ParBilinearForm(sfes_); auto *at_blfi = new ConvectionIntegrator(*rhou_coeff_); @@ -629,7 +629,7 @@ void CaloricallyPerfectThermoChem::initializeOperators() { lqd_blfi->SetIntRule(&ir_di); } LQ_form_->AddDomainIntegrator(lqd_blfi); - + // DiffusionIntegrator *slqd_blfi; if (sw_stab_) { // slqd_blfi = new DiffusionIntegrator(*supg_coeff_); diff --git a/src/calorically_perfect.hpp b/src/calorically_perfect.hpp index 5ecfcd808..dcd6b69bf 100644 --- a/src/calorically_perfect.hpp +++ b/src/calorically_perfect.hpp @@ -181,14 +181,14 @@ class CaloricallyPerfectThermoChem : public ThermoChemModelBase { GridFunctionCoefficient *gscale_coeff_ = nullptr; GridFunctionCoefficient *visc_coeff_ = nullptr; PowerCoefficient *visc_inv_coeff_ = nullptr; - ProductCoefficient *reh1_coeff_ = nullptr; - ProductCoefficient *reh2_coeff_ = nullptr; - ProductCoefficient *Reh_coeff_ = nullptr; + ProductCoefficient *reh1_coeff_ = nullptr; + ProductCoefficient *reh2_coeff_ = nullptr; + ProductCoefficient *Reh_coeff_ = nullptr; TransformedCoefficient *csupg_coeff_ = nullptr; - ProductCoefficient *uw1_coeff_ = nullptr; + ProductCoefficient *uw1_coeff_ = nullptr; ProductCoefficient *uw2_coeff_ = nullptr; - ProductCoefficient *upwind_coeff_ = nullptr; - TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; + ProductCoefficient *upwind_coeff_ = nullptr; + TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; ScalarMatrixProductCoefficient *supg_coeff_ = nullptr; // operators and solvers diff --git a/src/em_options.hpp b/src/em_options.hpp index e78cf70b5..d192c88fc 100644 --- a/src/em_options.hpp +++ b/src/em_options.hpp @@ -52,8 +52,8 @@ class ElectromagneticOptions { double atol; /**< Linear solver absolute tolerance */ double preconditioner_background_sigma; /**< Uniform conductivity to use to preconditioner (ignored if <= 0) */ - bool top_only; /**< Flag to specify current in top rings only */ - bool bot_only; /**< Flag to specify current in bottom rings only */ + bool top_only; /**< Flag to specify current in top rings only */ + bool bot_only; /**< Flag to specify current in bottom rings only */ bool variable_current; /**< Flag to specify variable current in each ring */ bool evaluate_magnetic_field; diff --git a/src/loMach.cpp b/src/loMach.cpp index a3a25a3c8..455c33a33 100644 --- a/src/loMach.cpp +++ b/src/loMach.cpp @@ -166,7 +166,8 @@ void LoMachSolver::initialize() { if (loMach_opts_.thermo_solver == "constant-property") { thermo_ = new ConstantPropertyThermoChem(pmesh_, loMach_opts_.order, tpsP_); } else if (loMach_opts_.thermo_solver == "calorically-perfect") { - thermo_ = new CaloricallyPerfectThermoChem(pmesh_, &loMach_opts_, temporal_coeff_, (meshData_->getGridScale()), tpsP_); + thermo_ = + new CaloricallyPerfectThermoChem(pmesh_, &loMach_opts_, temporal_coeff_, (meshData_->getGridScale()), tpsP_); } else if (loMach_opts_.thermo_solver == "lte-thermo-chem") { thermo_ = new LteThermoChem(pmesh_, &loMach_opts_, temporal_coeff_, tpsP_); } else if (loMach_opts_.thermo_solver == "reacting-flow") { @@ -185,7 +186,8 @@ void LoMachSolver::initialize() { flow_ = new ZeroFlow(pmesh_, 1, tpsP_); } else if (loMach_opts_.flow_solver == "tomboulides") { // Tomboulides flow solver - flow_ = new Tomboulides(pmesh_, loMach_opts_.order, loMach_opts_.order, temporal_coeff_, (meshData_->getGridScale()), tpsP_); + flow_ = new Tomboulides(pmesh_, loMach_opts_.order, loMach_opts_.order, temporal_coeff_, + (meshData_->getGridScale()), tpsP_); } else { // Unknown choice... die if (rank0_) { diff --git a/src/lte_thermo_chem.cpp b/src/lte_thermo_chem.cpp index c41a48ebb..19d0c161a 100644 --- a/src/lte_thermo_chem.cpp +++ b/src/lte_thermo_chem.cpp @@ -453,7 +453,7 @@ void LteThermoChem::initializeSelf() { // outlet bc { - // Assumed homogeneous Nuemann on T, so nothing to do + // Assumed homogeneous Nuemann on T, so nothing to do } // Wall BCs diff --git a/src/quasimagnetostatic.cpp b/src/quasimagnetostatic.cpp index 24d906f07..30288a9b0 100644 --- a/src/quasimagnetostatic.cpp +++ b/src/quasimagnetostatic.cpp @@ -272,9 +272,7 @@ void QuasiMagnetostaticSolver3D::InitializeCurrent() { } if (rank0_) { - std::cout << "J0 = " << J0(0) << ", " << J0(1) - << ", " << J0(2) << ", " << J0(3) - << ", " << J0(4) << endl; + std::cout << "J0 = " << J0(0) << ", " << J0(1) << ", " << J0(2) << ", " << J0(3) << ", " << J0(4) << endl; } PWConstCoefficient J0coef(J0); @@ -924,9 +922,7 @@ void QuasiMagnetostaticSolverAxiSym::InitializeCurrent() { } if (rank0_) { - std::cout << "J0 = " << J0(0) << ", " << J0(1) - << ", " << J0(2) << ", " << J0(3) - << ", " << J0(4) << endl; + std::cout << "J0 = " << J0(0) << ", " << J0(1) << ", " << J0(2) << ", " << J0(3) << ", " << J0(4) << endl; } FunctionCoefficient radius_coeff(radius); diff --git a/src/reactingFlow.cpp b/src/reactingFlow.cpp index 5e626c694..1eb1ab2d3 100644 --- a/src/reactingFlow.cpp +++ b/src/reactingFlow.cpp @@ -542,11 +542,8 @@ ReactingFlow::ReactingFlow(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, tem // artificial diffusion (SUPG) tpsP_->getInput("loMach/reactingFlow/streamwise-stabilization", sw_stab_, false); - // NOTE: clipping Qt for debugging // tpsP_->getInput("loMach/reactingFlow/clip-qt", clip_qt_, false); - - } ReactingFlow::~ReactingFlow() { @@ -637,7 +634,7 @@ ReactingFlow::~ReactingFlow() { delete csupg_coeff_; delete uw1_coeff_; delete uw2_coeff_; - delete upwind_coeff_; + delete upwind_coeff_; delete swdiff_coeff_; delete supg_coeff_; delete supg_cp_coeff_; @@ -996,7 +993,7 @@ void ReactingFlow::initializeSelf() { } AddTempDirichletBC(temperature_value, inlet_attr); - AddSpecDirichletBC(0.0, inlet_attr); + // AddSpecDirichletBC(0.0, inlet_attr); } else if (type == "interpolate") { Array inlet_attr(pmesh_->bdr_attributes.Max()); @@ -1072,7 +1069,7 @@ void ReactingFlow::initializeSelf() { Qt_bc_coeff->constant = 0.0; AddQtDirichletBC(Qt_bc_coeff, attr_wall); - AddSpecDirichletBC(0.0, attr_wall); + // AddSpecDirichletBC(0.0, attr_wall); } } if (rank0_) std::cout << "Temp wall bc completed: " << numWalls << endl; @@ -1153,7 +1150,7 @@ void ReactingFlow::initializeOperators() { } // artifical diffusion coefficients - if(sw_stab_) { + if (sw_stab_) { // run once to avoid division by zero // updateMixture(); // updateDensity(1.0); @@ -1162,28 +1159,27 @@ void ReactingFlow::initializeOperators() { gscale_coeff_ = new GridFunctionCoefficient(gridScale_gf_); visc_coeff_ = new GridFunctionCoefficient(&visc_gf_); visc_inv_coeff_ = new PowerCoefficient(*visc_coeff_, -1.); - + // compute Reh reh1_coeff_ = new ProductCoefficient(*rhon_next_coeff_, *visc_inv_coeff_); reh2_coeff_ = new ProductCoefficient(*reh1_coeff_, *gscale_coeff_); Reh_coeff_ = new ProductCoefficient(*reh2_coeff_, *umag_coeff_); - + // Csupg csupg_coeff_ = new TransformedCoefficient(Reh_coeff_, csupgFactor); - + if (axisym_) { // compute upwind magnitude uw1_coeff_ = new ProductCoefficient(*rad_rho_coeff_, *csupg_coeff_); uw2_coeff_ = new ProductCoefficient(*uw1_coeff_, *gscale_coeff_); upwind_coeff_ = new ProductCoefficient(*uw2_coeff_, *umag_coeff_); - + // streamwise diffusion direction swdiff_coeff_ = new TransformedMatrixVectorCoefficient(un_next_coeff_, &streamwiseTensor); supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); supg_cp_coeff_ = new ScalarMatrixProductCoefficient(*cpMix_coeff_, *supg_coeff_); - } - else { + } else { // compute upwind magnitude uw1_coeff_ = new ProductCoefficient(*rhon_next_coeff_, *csupg_coeff_); uw2_coeff_ = new ProductCoefficient(*uw1_coeff_, *gscale_coeff_); @@ -1304,7 +1300,7 @@ void ReactingFlow::initializeOperators() { hdt_blfi->SetIntRule(&ir_di); } // SUPG - DiffusionIntegrator *sdt_blfi; + DiffusionIntegrator *sdt_blfi; if (sw_stab_) { // auto *sdt_blfi = new DiffusionIntegrator(*supg_coeff_); sdt_blfi = new DiffusionIntegrator(*supg_cp_coeff_); @@ -1510,13 +1506,12 @@ void ReactingFlow::initializeOperators() { LQ_form_->AddDomainIntegrator(lqd_blfi); DiffusionIntegrator *slqd_blfi; - if (sw_stab_) - slqd_blfi = new DiffusionIntegrator(*supg_coeff_); - // SUPG diffusion - // if (numerical_integ_) { - // slqd_blfi->SetIntRule(&ir_di); - // } - LQ_form_->AddDomainIntegrator(slqd_blfi); + if (sw_stab_) slqd_blfi = new DiffusionIntegrator(*supg_coeff_); + // SUPG diffusion + // if (numerical_integ_) { + // slqd_blfi->SetIntRule(&ir_di); + // } + LQ_form_->AddDomainIntegrator(slqd_blfi); if (partial_assembly_) { LQ_form_->SetAssemblyLevel(AssemblyLevel::PARTIAL); @@ -1812,7 +1807,6 @@ void ReactingFlow::step() { if (dynamic_substepping_) evalSubstepNumber(); for (int iSub = 0; iSub < nSub_; iSub++) { - // update wdot quantities at full substep in Yn/Tn state updateMixture(); updateThermoP(); @@ -2395,7 +2389,7 @@ void ReactingFlow::initializeViz(ParaViewDataCollection &pvdc) { pvdc.RegisterField("epsilon_rad", &radiation_sink_gf_); pvdc.RegisterField("weff", &weff_gf_); pvdc.RegisterField("emission", &emission_gf_); - + // diagnose Qt issues, rhs contributions // pvdc.RegisterField("rhsqt_bd", &rhsqt_bd_); //boundary terms // pvdc.RegisterField("rhsqt_fo", &rhsqt_fo_); //bilinear form @@ -2404,7 +2398,7 @@ void ReactingFlow::initializeViz(ParaViewDataCollection &pvdc) { // pvdc.RegisterField("rhsqt_sd", &rhsqt_sd_); ///species-temp diff // pvdc.RegisterField("rhsqt_total", &rhsqt_total_); // pvdc.RegisterField("Xqt", &Xqt_gf_); - + vizSpecFields_.clear(); vizSpecNames_.clear(); for (int sp = 0; sp < nSpecies_; sp++) { @@ -2885,7 +2879,7 @@ void ReactingFlow::computeQtTO() { // rhsqt_jh_.SetFromTrueDofs(jh_); // rhsqt_jh_.Neg(); // printf("%f\n", tmpR0_.Norml2()); - + // heat of formation Ms_->AddMult(hw_, tmpR0_, -1.0); // Ms_->Mult(hw_, rhsqt_hf_); diff --git a/src/reactingFlow.hpp b/src/reactingFlow.hpp index de96897b0..e9e26d7f2 100644 --- a/src/reactingFlow.hpp +++ b/src/reactingFlow.hpp @@ -155,7 +155,7 @@ class ReactingFlow : public ThermoChemModelBase { // streamwise-stabilization bool sw_stab_; - + // clip qt for diagnosis // bool clip_qt_; @@ -266,14 +266,14 @@ class ReactingFlow : public ThermoChemModelBase { GridFunctionCoefficient *gscale_coeff_ = nullptr; GridFunctionCoefficient *visc_coeff_ = nullptr; PowerCoefficient *visc_inv_coeff_ = nullptr; - ProductCoefficient *reh1_coeff_ = nullptr; - ProductCoefficient *reh2_coeff_ = nullptr; - ProductCoefficient *Reh_coeff_ = nullptr; + ProductCoefficient *reh1_coeff_ = nullptr; + ProductCoefficient *reh2_coeff_ = nullptr; + ProductCoefficient *Reh_coeff_ = nullptr; TransformedCoefficient *csupg_coeff_ = nullptr; - ProductCoefficient *uw1_coeff_ = nullptr; + ProductCoefficient *uw1_coeff_ = nullptr; ProductCoefficient *uw2_coeff_ = nullptr; - ProductCoefficient *upwind_coeff_ = nullptr; - TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; + ProductCoefficient *upwind_coeff_ = nullptr; + TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; ScalarMatrixProductCoefficient *supg_coeff_ = nullptr; ScalarMatrixProductCoefficient *supg_cp_coeff_ = nullptr; @@ -388,7 +388,7 @@ class ReactingFlow : public ThermoChemModelBase { std::vector vizSpecNames_; public: - ReactingFlow(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, temporalSchemeCoefficients &timeCoeff, + ReactingFlow(mfem::ParMesh *pmesh, LoMachOptions *loMach_opts, temporalSchemeCoefficients &timeCoeff, ParGridFunction *gridScale, TPS::Tps *tps); virtual ~ReactingFlow(); diff --git a/src/split_flow_base.cpp b/src/split_flow_base.cpp index 776ac7f36..e39c5edf6 100644 --- a/src/split_flow_base.cpp +++ b/src/split_flow_base.cpp @@ -31,16 +31,15 @@ // -----------------------------------------------------------------------------------el- #include "split_flow_base.hpp" + #include "tps.hpp" using namespace mfem; -ZeroFlow::ZeroFlow(mfem::ParMesh *pmesh, int vorder, TPS::Tps *tps) +ZeroFlow::ZeroFlow(mfem::ParMesh *pmesh, int vorder, TPS::Tps *tps) : pmesh_(pmesh), vorder_(vorder), dim_(pmesh->Dimension()), tpsP_(tps) { - // nonzero flow option tpsP_->getInput("loMach/zeroflow/nonzero-flow", nonzero_flow_, false); - } ZeroFlow::~ZeroFlow() { diff --git a/src/split_flow_base.hpp b/src/split_flow_base.hpp index 6467e0421..cd36d2a20 100644 --- a/src/split_flow_base.hpp +++ b/src/split_flow_base.hpp @@ -148,7 +148,6 @@ class FlowBase { class ZeroFlow final : public FlowBase { protected: - // Options-related structures TPS::Tps *tpsP_ = nullptr; diff --git a/src/tomboulides.cpp b/src/tomboulides.cpp index 090ea11a4..6da6ff20d 100644 --- a/src/tomboulides.cpp +++ b/src/tomboulides.cpp @@ -67,8 +67,8 @@ void Orthogonalize(Vector &v, const ParFiniteElementSpace *pfes) { v -= global_sum / static_cast(global_size); } -Tomboulides::Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalSchemeCoefficients &coeff, - mfem::ParGridFunction *gridScale, TPS::Tps *tps) +Tomboulides::Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalSchemeCoefficients &coeff, + mfem::ParGridFunction *gridScale, TPS::Tps *tps) : gll_rules(0, Quadrature1D::GaussLobatto), tpsP_(tps), pmesh_(pmesh), @@ -130,7 +130,7 @@ Tomboulides::Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalS tps->getInput("loMach/tomboulides/psolve-maxIters", pressure_solve_max_iter_, default_max_iter_); tps->getInput("loMach/tomboulides/hsolve-maxIters", hsolve_max_iter_, default_max_iter_); tps->getInput("loMach/tomboulides/msolve-maxIters", mass_inverse_max_iter_, default_max_iter_); - + // artificial diffusion (SUPG) tpsP_->getInput("loMach/tomboulides/streamwise-stabilization", sw_stab_, false); } @@ -210,7 +210,7 @@ Tomboulides::~Tomboulides() { delete rho_over_dt_coeff_; delete iorho_coeff_; delete rho_coeff_; - + delete umag_coeff_; delete gscale_coeff_; delete visc_inv_coeff_; @@ -220,7 +220,7 @@ Tomboulides::~Tomboulides() { delete csupg_coeff_; delete uw1_coeff_; delete uw2_coeff_; - delete upwind_coeff_; + delete upwind_coeff_; delete swdiff_coeff_; delete supg_coeff_; delete visc_coeff_; @@ -650,7 +650,7 @@ void Tomboulides::initializeOperators() { S_mom_coeff_ = new VectorSumCoefficient(*graduT_gradmu_coeff_, *gradmu_Qt_coeff_, 1.0, -1.0); u_next_coeff_ = new VectorGridFunctionCoefficient(u_next_gf_); - + // Coefficients for axisymmetric if (axisym_) { rad_rho_coeff_ = new ProductCoefficient(radius_coeff, *rho_coeff_); @@ -686,34 +686,33 @@ void Tomboulides::initializeOperators() { } // artifical diffusion coefficients - if(sw_stab_) { + if (sw_stab_) { visc_coeff_ = new GridFunctionCoefficient(thermo_interface_->viscosity); umag_coeff_ = new VectorMagnitudeCoefficient(*u_next_coeff_); gscale_coeff_ = new GridFunctionCoefficient(gridScale_gf_); visc_inv_coeff_ = new PowerCoefficient(*visc_coeff_, -1.0); // visc_inv_coeff_ = new PowerCoefficient(*mu_coeff_, -1.0); - + // compute Reh reh1_coeff_ = new ProductCoefficient(*rho_coeff_, *visc_inv_coeff_); reh2_coeff_ = new ProductCoefficient(*reh1_coeff_, *gscale_coeff_); Reh_coeff_ = new ProductCoefficient(*reh2_coeff_, *umag_coeff_); - + // Csupg csupg_coeff_ = new TransformedCoefficient(Reh_coeff_, csupgFactor); - + if (axisym_) { // compute upwind magnitude uw1_coeff_ = new ProductCoefficient(*rad_rho_coeff_, *csupg_coeff_); uw2_coeff_ = new ProductCoefficient(*uw1_coeff_, *gscale_coeff_); upwind_coeff_ = new ProductCoefficient(*uw2_coeff_, *umag_coeff_); - + // streamwise diffusion direction swdiff_coeff_ = new TransformedMatrixVectorCoefficient(u_next_coeff_, &streamwiseTensor); - + supg_coeff_ = new ScalarMatrixProductCoefficient(*upwind_coeff_, *swdiff_coeff_); - } - else { + } else { // compute upwind magnitude // dividing by rho anyway // uw1_coeff_ = new ProductCoefficient(*rho_coeff_, *csupg_coeff_); @@ -977,7 +976,7 @@ void Tomboulides::initializeOperators() { Hv_form_ = new ParBilinearForm(vfes_); VectorMassIntegrator *hmv_blfi; VectorDiffusionIntegrator *hdv_blfi; - + if (axisym_) { hmv_blfi = new VectorMassIntegrator(*rad_rho_over_dt_coeff_); hdv_blfi = new VectorDiffusionIntegrator(*rad_mu_coeff_); @@ -1098,7 +1097,7 @@ void Tomboulides::initializeOperators() { auto *hms_blfi = new MassIntegrator(*rad_rho_over_dt_coeff_); auto *hds_blfi = new DiffusionIntegrator(*rad_mu_coeff_); auto *hfs_blfi = new MassIntegrator(*mu_over_rad_coeff_); - + DiffusionIntegrator *shds_blfi; if (sw_stab_) { // auto *shds_blfi = new DiffusionIntegrator(*supg_coeff_); @@ -1545,7 +1544,6 @@ void Tomboulides::step() { } if (sw_stab_) { - // Update matrix Array empty; Mv_stab_form_->Update(); diff --git a/src/tomboulides.hpp b/src/tomboulides.hpp index 2a4016097..a508115d9 100644 --- a/src/tomboulides.hpp +++ b/src/tomboulides.hpp @@ -294,14 +294,14 @@ class Tomboulides final : public FlowBase { mfem::VectorMagnitudeCoefficient *umag_coeff_ = nullptr; mfem::GridFunctionCoefficient *gscale_coeff_ = nullptr; mfem::PowerCoefficient *visc_inv_coeff_ = nullptr; - mfem::ProductCoefficient *reh1_coeff_ = nullptr; - mfem::ProductCoefficient *reh2_coeff_ = nullptr; - mfem::ProductCoefficient *Reh_coeff_ = nullptr; + mfem::ProductCoefficient *reh1_coeff_ = nullptr; + mfem::ProductCoefficient *reh2_coeff_ = nullptr; + mfem::ProductCoefficient *Reh_coeff_ = nullptr; mfem::TransformedCoefficient *csupg_coeff_ = nullptr; - mfem::ProductCoefficient *uw1_coeff_ = nullptr; + mfem::ProductCoefficient *uw1_coeff_ = nullptr; mfem::ProductCoefficient *uw2_coeff_ = nullptr; - mfem::ProductCoefficient *upwind_coeff_ = nullptr; - mfem::TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; + mfem::ProductCoefficient *upwind_coeff_ = nullptr; + mfem::TransformedMatrixVectorCoefficient *swdiff_coeff_ = nullptr; mfem::ScalarMatrixProductCoefficient *supg_coeff_ = nullptr; mfem::GridFunctionCoefficient *visc_coeff_ = nullptr; @@ -328,7 +328,7 @@ class Tomboulides final : public FlowBase { mfem::ParLinearForm *rho_ur_ut_form_ = nullptr; mfem::ParLinearForm *swirl_var_viscosity_form_ = nullptr; - // streamwise stability + // streamwise stability mfem::ParBilinearForm *Mv_stab_form_ = nullptr; // mfem operator objects @@ -343,7 +343,7 @@ class Tomboulides final : public FlowBase { mfem::OperatorHandle Hs_op_; mfem::OperatorHandle As_op_; - // streamwise stability + // streamwise stability mfem::OperatorHandle Mv_stab_op_; // solver objects @@ -421,7 +421,7 @@ class Tomboulides final : public FlowBase { public: /// Constructor Tomboulides(mfem::ParMesh *pmesh, int vorder, int porder, temporalSchemeCoefficients &coeff, - mfem::ParGridFunction *gridScale, TPS::Tps *tps = nullptr); + mfem::ParGridFunction *gridScale = nullptr, TPS::Tps *tps = nullptr); /// Destructor ~Tomboulides() final; diff --git a/src/utils.cpp b/src/utils.cpp index 69d53643e..6eb17e293 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1133,10 +1133,7 @@ void scalarGrad3DV(FiniteElementSpace *fes, FiniteElementSpace *vfes, Vector u, R1_gf.GetTrueDofs(*gu); } - - void streamwiseTensor(const Vector &vel, DenseMatrix &swMgbl) { - int dim = vel.Size(); // streamwise coordinate system @@ -1154,7 +1151,7 @@ void streamwiseTensor(const Vector &vel, DenseMatrix &swMgbl) { mod = std::max(mod, 1.0e-18); double Umag = std::sqrt(mod); unitNorm /= Umag; - + // std::cout << Umag << " " << endl ; // for zero-flow @@ -1183,7 +1180,7 @@ void streamwiseTensor(const Vector &vel, DenseMatrix &swMgbl) { unitT1[minusInd] = -unitNorm[maxInd]; unitT1[maxInd] = unitNorm[minusInd]; - if (dim == 3) { // DOUBLE CHECK THIS WHEN TESTING 3D + if (dim == 3) { // DOUBLE CHECK THIS WHEN TESTING 3D unitT1[plusInd] = 0.0; } mod = 0.0; @@ -1211,7 +1208,6 @@ void streamwiseTensor(const Vector &vel, DenseMatrix &swMgbl) { swM = 0.0; swM(0, 0) = 1.0; - // std::cout << " " << endl; // for (int i = 0; i < dim; i++) { // for (int j = 0; j < dim; j++) { @@ -1219,7 +1215,6 @@ void streamwiseTensor(const Vector &vel, DenseMatrix &swMgbl) { // } // std::cout << endl; // } - // M_{im} swM_{mn} M_{jn} or M*"mu"*M^T (with n,t1,t2 in columns of M) // DenseMatrix swMgbl(dim, dim); @@ -1241,7 +1236,6 @@ void streamwiseTensor(const Vector &vel, DenseMatrix &swMgbl) { // } // std::cout << endl; // } - } double csupgFactor(double Reh) { @@ -1251,7 +1245,6 @@ double csupgFactor(double Reh) { return 0.5 * (tanh(Reh) + 1.0); } - void EliminateRHS(Operator &A, ConstrainedOperator &constrainedA, const Array &ess_tdof_list, Vector &x, Vector &b, Vector &X, Vector &B, int copy_interior) { const Operator *Po = A.GetOutputProlongation(); @@ -1425,19 +1418,16 @@ void GradientVectorGridFunctionCoefficient::Eval(DenseMatrix &G, ElementTransfor } } +VectorMagnitudeCoefficient::VectorMagnitudeCoefficient(VectorCoefficient &A) : a(&A), va(A.GetVDim()) {} -VectorMagnitudeCoefficient::VectorMagnitudeCoefficient(VectorCoefficient &A) - : a(&A), va(A.GetVDim()) {} - -void VectorMagnitudeCoefficient::SetTime(double t) -{ - if (a) { a->SetTime(t); } +void VectorMagnitudeCoefficient::SetTime(double t) { + if (a) { + a->SetTime(t); + } this->Coefficient::SetTime(t); } -double VectorMagnitudeCoefficient::Eval(ElementTransformation &T, - const IntegrationPoint &ip) -{ +double VectorMagnitudeCoefficient::Eval(ElementTransformation &T, const IntegrationPoint &ip) { a->Eval(va, T, ip); // double res = 0; // for (int i = 0; i < va.size(); i++) { res += va[i] * va[i]} @@ -1447,14 +1437,12 @@ double VectorMagnitudeCoefficient::Eval(ElementTransformation &T, return mod; } -void TransformedMatrixVectorCoefficient::SetTime(double t) -{ +void TransformedMatrixVectorCoefficient::SetTime(double t) { Q1->SetTime(t); this->MatrixCoefficient::SetTime(t); } void TransformedMatrixVectorCoefficient::Eval(DenseMatrix &G, ElementTransformation &T, const IntegrationPoint &ip) { - Vector buf; buf.SetSize(Q1->GetVDim()); Q1->Eval(buf, T, ip); diff --git a/src/utils.hpp b/src/utils.hpp index 7317517e4..a5071a8d1 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -231,40 +231,37 @@ class GradientVectorGridFunctionCoefficient : public MatrixCoefficient { }; /// Scalar coefficient defined as the magnitude of a vector coefficient -class VectorMagnitudeCoefficient : public Coefficient -{ -private: - VectorCoefficient * a; - - mutable Vector va; -public: - /// Construct with the vector coefficient. Result is \sqrt{(\f$ A \cdot a \f$}. - VectorMagnitudeCoefficient(VectorCoefficient &A); - - /// Set the time for internally stored coefficients - void SetTime(double t); - - /// Reset the vector - void SetACoef(VectorCoefficient &A) { a = &A; } - /// Return the vector coefficient - VectorCoefficient * GetACoef() const { return a; } - - /// Evaluate the coefficient at @a ip. - virtual double Eval(ElementTransformation &T, - const IntegrationPoint &ip); +class VectorMagnitudeCoefficient : public Coefficient { + private: + VectorCoefficient *a; + + mutable Vector va; + + public: + /// Construct with the vector coefficient. Result is \sqrt{(\f$ A \cdot a \f$}. + VectorMagnitudeCoefficient(VectorCoefficient &A); + + /// Set the time for internally stored coefficients + void SetTime(double t); + + /// Reset the vector + void SetACoef(VectorCoefficient &A) { a = &A; } + /// Return the vector coefficient + VectorCoefficient *GetACoef() const { return a; } + + /// Evaluate the coefficient at @a ip. + virtual double Eval(ElementTransformation &T, const IntegrationPoint &ip); }; -/// Matrix coefficient computed from a function F(v(x)) of a dim-sized vector coefficient, v(x) +/// Matrix coefficient computed from a function F(v(x)) of a dim-sized vector coefficient, v(x) class TransformedMatrixVectorCoefficient : public MatrixCoefficient { protected: VectorCoefficient *Q1; std::function Function; public: - TransformedMatrixVectorCoefficient(VectorCoefficient *vc, - std::function F) : - MatrixCoefficient(vc->GetVDim() ), - Q1(vc), Function(std::move(F)) { } + TransformedMatrixVectorCoefficient(VectorCoefficient *vc, std::function F) + : MatrixCoefficient(vc->GetVDim()), Q1(vc), Function(std::move(F)) {} /// Set the time for internally stored coefficients void SetTime(double t); diff --git a/src/wallBC.cpp b/src/wallBC.cpp index 73bd193a9..0c6f77ade 100644 --- a/src/wallBC.cpp +++ b/src/wallBC.cpp @@ -814,8 +814,8 @@ void WallBC::interpWalls_gpu(const mfem::Vector &x, const elementIndexingData &e d_flux[eq + q * num_equation + n * maxIntPoints * num_equation] = Rflux[eq]; } } // end quadrature point loop - } // end face loop - }); // end element loop + } // end face loop + }); // end element loop #endif } diff --git a/test/standalone_chemistry.cpp b/test/standalone_chemistry.cpp index 5af6bc822..6538581fd 100644 --- a/test/standalone_chemistry.cpp +++ b/test/standalone_chemistry.cpp @@ -32,7 +32,7 @@ int main(int argc, char *argv[]) { temporalSchemeCoefficients temporal_coeff; temporal_coeff.order = 1; - ReactingFlow *thermo = new ReactingFlow(pmesh, &loMach_opts, temporal_coeff, &tps); + ReactingFlow *thermo = new ReactingFlow(pmesh, &loMach_opts, temporal_coeff, nullptr, &tps); thermo->initializeSelf(); int nSpecies; From 0511ea0dc7144ad61bda4c7ce2d3b8baeb7bd9fc Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Wed, 23 Jul 2025 16:33:17 -0700 Subject: [PATCH 09/13] fixed pressure artificial diffusion laplace operator --- src/reactingFlow.cpp | 14 +++++++------- src/split_flow_base.cpp | 2 +- src/tomboulides.cpp | 17 +++++++---------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/reactingFlow.cpp b/src/reactingFlow.cpp index f5a06d235..bceacda43 100644 --- a/src/reactingFlow.cpp +++ b/src/reactingFlow.cpp @@ -1506,13 +1506,13 @@ void ReactingFlow::initializeOperators() { LQ_form_->AddDomainIntegrator(lqd_blfi); DiffusionIntegrator *slqd_blfi; - if (sw_stab_) slqd_blfi = new DiffusionIntegrator(*supg_coeff_); - // SUPG diffusion - // if (numerical_integ_) { - // slqd_blfi->SetIntRule(&ir_di); - // } - LQ_form_->AddDomainIntegrator(slqd_blfi); - + if (sw_stab_) { + slqd_blfi = new DiffusionIntegrator(*supg_coeff_); + // if (numerical_integ_) { + // slqd_blfi->SetIntRule(&ir_di); + // } + LQ_form_->AddDomainIntegrator(slqd_blfi); + } if (partial_assembly_) { LQ_form_->SetAssemblyLevel(AssemblyLevel::PARTIAL); } diff --git a/src/split_flow_base.cpp b/src/split_flow_base.cpp index e39c5edf6..53789497e 100644 --- a/src/split_flow_base.cpp +++ b/src/split_flow_base.cpp @@ -37,7 +37,7 @@ using namespace mfem; ZeroFlow::ZeroFlow(mfem::ParMesh *pmesh, int vorder, TPS::Tps *tps) - : pmesh_(pmesh), vorder_(vorder), dim_(pmesh->Dimension()), tpsP_(tps) { + : pmesh_(pmesh), vorder_(vorder), tpsP_(tps), dim_(pmesh->Dimension()) { // nonzero flow option tpsP_->getInput("loMach/zeroflow/nonzero-flow", nonzero_flow_, false); } diff --git a/src/tomboulides.cpp b/src/tomboulides.cpp index 6da6ff20d..53f9206a0 100644 --- a/src/tomboulides.cpp +++ b/src/tomboulides.cpp @@ -1549,19 +1549,16 @@ void Tomboulides::step() { Mv_stab_form_->Update(); Mv_stab_form_->Assemble(); Mv_stab_form_->FormSystemMatrix(empty, Mv_stab_op_); - /* - for (int i = 0; i < dim_; i++) { - setScalarFromVector(u_vec_, i, &tmpR0a_); - streamwiseDiffusion(tmpR0a_, tmpR0b_); - setVectorFromScalar(tmpR0b_, i, &swDiff_vec_); - } - */ - Mv_stab_op_->Mult(gradU_, tmpR0b_); + + Mv_stab_op_->Mult(gradU_, tmpR1_); + D_op_->Mult(tmpR1_, tmpR0b_); setVectorFromScalar(tmpR0b_, 0, &swDiff_vec_); - Mv_stab_op_->Mult(gradV_, tmpR0b_); + Mv_stab_op_->Mult(gradV_, tmpR1_); + D_op_->Mult(tmpR1_, tmpR0b_); setVectorFromScalar(tmpR0b_, 1, &swDiff_vec_); if (dim_ == 3) { - Mv_stab_op_->Mult(gradW_, tmpR0b_); + Mv_stab_op_->Mult(gradW_, tmpR1_); + D_op_->Mult(tmpR1_, tmpR0b_); setVectorFromScalar(tmpR0b_, 2, &swDiff_vec_); } Mv_rho_inv_->Mult(swDiff_vec_, tmpR1_); From 2492087b364d79cbf1d5598a20d66b43b405901c Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Wed, 23 Jul 2025 16:42:03 -0700 Subject: [PATCH 10/13] fixed ordering on ZeroFlow constructor assignment --- src/split_flow_base.cpp | 2 +- src/split_flow_base.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/split_flow_base.cpp b/src/split_flow_base.cpp index 53789497e..e39c5edf6 100644 --- a/src/split_flow_base.cpp +++ b/src/split_flow_base.cpp @@ -37,7 +37,7 @@ using namespace mfem; ZeroFlow::ZeroFlow(mfem::ParMesh *pmesh, int vorder, TPS::Tps *tps) - : pmesh_(pmesh), vorder_(vorder), tpsP_(tps), dim_(pmesh->Dimension()) { + : pmesh_(pmesh), vorder_(vorder), dim_(pmesh->Dimension()), tpsP_(tps) { // nonzero flow option tpsP_->getInput("loMach/zeroflow/nonzero-flow", nonzero_flow_, false); } diff --git a/src/split_flow_base.hpp b/src/split_flow_base.hpp index cd36d2a20..e09999eda 100644 --- a/src/split_flow_base.hpp +++ b/src/split_flow_base.hpp @@ -148,9 +148,6 @@ class FlowBase { class ZeroFlow final : public FlowBase { protected: - // Options-related structures - TPS::Tps *tpsP_ = nullptr; - // Options bool nonzero_flow_; @@ -158,6 +155,9 @@ class ZeroFlow final : public FlowBase { const int vorder_; const int dim_; + // Options-related structures + TPS::Tps *tpsP_ = nullptr; + mfem::FiniteElementCollection *fec_ = nullptr; mfem::ParFiniteElementSpace *fes_ = nullptr; mfem::ParGridFunction *velocity_ = nullptr; From 427d7d325cf9f56e6648e1c081702870ba8033af Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Thu, 24 Jul 2025 12:56:23 -0700 Subject: [PATCH 11/13] fixing stabChan test --- test/lomach-chan-stab.test | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/lomach-chan-stab.test b/test/lomach-chan-stab.test index 6ee8992cf..bc500ca44 100755 --- a/test/lomach-chan-stab.test +++ b/test/lomach-chan-stab.test @@ -7,9 +7,9 @@ EXE="../src/tps" RESTART="ref_solns/stabChan/restart_output.sol.h5" setup() { - SOLN_FILE=restart_output.sol.h5 - REF_FILE=ref_solns/stabChan/restart_output.sol.h5 - OUT_FILE=output_solns/restart_output_stabChan.sol.h5 + SOLN_FILE="restart_output.sol.h5" + REF_FILE="ref_solns/stabChan/restart_output.sol.h5" + OUT_FILE="output_solns/restart_output_stabChan.sol.h5" } @test "[$TEST] check for input file $RUNFILE" { @@ -25,6 +25,5 @@ setup() { @test "[$TEST] verify tps output with input -> $RUNFILE" { test -s $SOLN_FILE - test -s $REF_FILE h5diff -r --delta=1e-10 $SOLN_FILE $REF_FILE /velocity } \ No newline at end of file From e022c26fbf08fecc0169e33099b88d4769f668ce Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Thu, 24 Jul 2025 13:50:45 -0700 Subject: [PATCH 12/13] adjusted gitattributes --- test/.gitattributes | 1 + test/lomach-chan-stab.test | 1 + 2 files changed, 2 insertions(+) diff --git a/test/.gitattributes b/test/.gitattributes index 83d6aca25..c68e89fe1 100644 --- a/test/.gitattributes +++ b/test/.gitattributes @@ -54,3 +54,4 @@ meshes/spongeBox.msh filter=lfs diff=lfs merge=lfs -text ref_solns/lequere-varmu/restart_output-lequere-varmu.sol.h5 filter=lfs diff=lfs merge=lfs -text ref_solns/lequere-varmu/reference-lequere-varmu.sol.h5 filter=lfs diff=lfs merge=lfs -text ref_solns/collisions/Oliver.fit.h5 filter=lfs diff=lfs merge=lfs -text +ref_solns/stabChan/restart_output.sol.h5 filter=lfs diff=lfs merge=lfs -text diff --git a/test/lomach-chan-stab.test b/test/lomach-chan-stab.test index bc500ca44..40798288c 100755 --- a/test/lomach-chan-stab.test +++ b/test/lomach-chan-stab.test @@ -25,5 +25,6 @@ setup() { @test "[$TEST] verify tps output with input -> $RUNFILE" { test -s $SOLN_FILE + test -s $REF_FILE h5diff -r --delta=1e-10 $SOLN_FILE $REF_FILE /velocity } \ No newline at end of file From 057f983ffe31ccd54bba81f3a9a3c92a096f2c62 Mon Sep 17 00:00:00 2001 From: Garo Levon Bedonian Date: Thu, 24 Jul 2025 15:05:39 -0700 Subject: [PATCH 13/13] updated Makefile.am with test sol directory --- test/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Makefile.am b/test/Makefile.am index 73ccebf53..e639d7964 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -36,6 +36,7 @@ EXTRA_DIST = tap-driver.sh test_tps_splitcomm.py soln_differ inputs meshes lte- ref_solns/reactSingleRx/*.h5 \ ref_solns/reactTable/*.h5 \ ref_solns/radDecay/*.h5 \ + ref_solns/stabChan/*.h5 \ vpath.sh die.sh count_gpus.sh sniff_mpirun.sh \ cyl3d.gpu.test cyl3d.mflow.gpu.test wedge.gpu.test \ averaging.gpu.test cyl3d.test cyl3d.gpu.python.test cyl3d.mflow.test cyl3d.dtconst.test \