diff --git a/.gitignore b/.gitignore index efd9f5e619..3d6d6f9877 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ *.o *.py_o *.so +*.coverage +.cache/* +bin/* diff --git a/examples/example8/aa_test_advection_diffusion_coupling.py b/examples/example8/aa_test_advection_diffusion_coupling.py index ba826bb323..122e51da57 100644 --- a/examples/example8/aa_test_advection_diffusion_coupling.py +++ b/examples/example8/aa_test_advection_diffusion_coupling.py @@ -125,6 +125,7 @@ def add_data_advection_diffusion(gb, domain, tol): #------------------------------------------------------------------------------# +do_save = False folder = os.path.dirname(os.path.realpath(__file__)) + "/" export_folder = folder + 'advection_diffusion_coupling' tol = 1e-3 @@ -164,7 +165,8 @@ def add_data_advection_diffusion(gb, domain, tol): d["p"] = darcy.discr.extract_p(g, d["up"]) d["P0u"] = darcy.discr.project_u(g, discharge, d) -exporter.export_vtk(gb, 'darcy', ["p", "P0u"], folder=export_folder) +if do_save: + exporter.export_vtk(gb, 'darcy', ["p", "P0u"], folder=export_folder) ################################################################# @@ -184,6 +186,6 @@ def add_data_advection_diffusion(gb, domain, tol): theta = sps.linalg.spsolve(D + U, rhs_u + rhs_d) diffusion.split(gb, "temperature", theta) -exporter.export_vtk(gb, 'advection_diffusion', [ - "temperature"], folder=export_folder) - +if do_save: + exporter.export_vtk(gb, 'advection_diffusion', [ + "temperature"], folder=export_folder) diff --git a/src/porepy/fracs/meshing.py b/src/porepy/fracs/meshing.py index c261c12447..e3f4879f46 100644 --- a/src/porepy/fracs/meshing.py +++ b/src/porepy/fracs/meshing.py @@ -8,6 +8,7 @@ """ import numpy as np import scipy.sparse as sps +import warnings from porepy.fracs import structured, simplex, split_grid from porepy.fracs.fractures import Intersection @@ -23,6 +24,14 @@ def simplex_grid(fracs, domain, **kwargs): Main function for grid generation. Creates a fractured simiplex grid in 2 or 3 dimensions. + NOTE: For some fracture networks, what appears to be a bug in Gmsh leads to + surface grids with cells that does not have a corresponding face in the 3d + grid. The problem may have been resolved (at least partly) by newer + versions of Gmsh, but can still be an issue for our purposes. If this + behavior is detected, an assertion error is raised. To avoid the issue, + and go on with a surface mesh that likely is problematic, kwargs should + contain a keyword ensure_matching_face_cell=False. + Parameters ---------- fracs (list of np.ndarray): One list item for each fracture. Each item @@ -81,10 +90,11 @@ def simplex_grid(fracs, domain, **kwargs): tag_faces(grids) # Assemble grids in a bucket - gb = assemble_in_bucket(grids) + gb = assemble_in_bucket(grids, **kwargs) gb.compute_geometry() # Split the grids. - split_grid.split_fractures(gb) + split_grid.split_fractures(gb, **kwargs) + gb.assign_node_ordering() return gb #------------------------------------------------------------------------------# @@ -211,6 +221,7 @@ def from_gmsh(file_name, dim, **kwargs): #------------------------------------------------------------------------------# + def cart_grid(fracs, nx, **kwargs): """ Creates a cartesian fractured GridBucket in 2- or 3-dimensions. @@ -275,6 +286,7 @@ def cart_grid(fracs, nx, **kwargs): # Split grid. split_grid.split_fractures(gb, **kwargs) + gb.assign_node_ordering() return gb @@ -356,7 +368,7 @@ def nodes_per_face(g): return n_per_face -def assemble_in_bucket(grids): +def assemble_in_bucket(grids, **kwargs): """ Create a GridBucket from a list of grids. Parameters @@ -392,7 +404,7 @@ def assemble_in_bucket(grids): for lg in grids[dim + 1]: cell_2_face, cell = obtain_interdim_mappings( - lg, fn, n_per_face) + lg, fn, n_per_face, **kwargs) face_cells = sps.csc_matrix( (np.array([True] * cell.size), (cell, cell_2_face)), (lg.num_cells, hg.num_faces)) @@ -404,10 +416,20 @@ def assemble_in_bucket(grids): return bucket -def obtain_interdim_mappings(lg, fn, n_per_face): +def obtain_interdim_mappings(lg, fn, n_per_face, + ensure_matching_face_cell=True, **kwargs): """ Find mappings between faces in higher dimension and cells in the lower dimension + + Parameters: + lg: Lower dimensional grid. + fn: Higher dimensional face-node relation. + n_per_face: Number of nodes per face in the higher-dimensional grid. + ensure_matching_face_cell: Boolean, defaults to True. If True, an + assertion is made that all lower-dimensional cells corresponds to a + higher dimensional cell. + """ if lg.dim > 0: cn_loc = lg.cell_nodes().indices.reshape((n_per_face, @@ -426,6 +448,14 @@ def obtain_interdim_mappings(lg, fn, n_per_face): # An element in cell_2_face gives, for all cells in the # lower-dimensional grid, the index of the corresponding face # in the higher-dimensional structure. - + if not (np.all(is_mem) or np.all(~is_mem)): + if ensure_matching_face_cell: + raise ValueError( + '''Either all cells should have a corresponding face in a higher + dim grid or no cells should have a corresponding face in a higher + dim grid. This likely is related to gmsh behavior. ''') + else: + warnings.warn('''Found inconsistency between cells and higher + dimensional faces. Continuing, faces crossed''') low_dim_cell = np.where(is_mem)[0] return cell_2_face, low_dim_cell diff --git a/src/porepy/numerics/compressible/problems.py b/src/porepy/numerics/compressible/problems.py index ceabea1c15..57176611f7 100644 --- a/src/porepy/numerics/compressible/problems.py +++ b/src/porepy/numerics/compressible/problems.py @@ -2,88 +2,110 @@ import scipy.sparse as sps from porepy.grids import structured -from porepy.numerics.fv import tpfa -from porepy.numerics.compressible import solvers +from porepy.numerics.parabolic import * +from porepy.numerics.fv import tpfa, mass_matrix, fvutils +from porepy.numerics.mixed_dim.coupler import Coupler from porepy.params.data import Parameters from porepy.params import tensor from porepy.params import bc from porepy.viz.exporter import export_vtk, export_pvd -class SlightlyCompressible(): - """ - Base-class for slightly compressible flow. Initialize all needed - attributes for a slightly compressible solver. - """ +class SlightlyCompressible(ParabolicProblem): + ''' + Inherits from ParabolicProblem + This class solves equations of the type: + phi *c_p dp/dt - \nabla K \nabla p = q - def __init__(self): - self.solver = solvers.Implicit(self) - self.solver.parameters['store_results'] = True - self.parameters = {'file_name': 'pressure'} - self.parameters['folder_name'] = 'results' - self.data = dict() - #---------Discretization--------------- + Init: + - gb (Grid/GridBucket) Grid or grid bucket for the problem + - physics (string) Physics key word. See Parameters class for valid physics - def flux_disc(self): - """ - Returns the flux discretization. - """ - return tpfa.Tpfa() + functions: + discharge(): computes the discharges and saves it in the grid bucket as 'p' + Also see functions from ParabolicProblem + + Example: + # We create a problem with standard data + + gb = meshing.cart_grid([], [10,10], physdims=[1,1]) + for g, d in gb: + d['problem'] = SlightlyCompressibleData(g, d) + problem = SlightlyCompressible(gb) + problem.solve() + ''' + + def __init__(self, gb, physics='flow'): + ParabolicProblem.__init__(self, gb, physics) - def solve(self): + def space_disc(self): + return self.diffusive_disc(), self.source_disc() + + def time_disc(self): """ - Call the solver + Returns the time discretization. """ - self.data = self.solver.solve() - return self.data - - #-----Parameters------------ - def porosity(self): - return np.ones(self.grid().num_cells) + class TimeDisc(mass_matrix.MassMatrix): + def __init__(self, deltaT): + self.deltaT = deltaT + + def matrix_rhs(self, g, data): + lhs, rhs = mass_matrix.MassMatrix.matrix_rhs(self, g, data) + return lhs * data['compressibility'], rhs * data['compressibility'] + single_dim_discr = TimeDisc(self.time_step()) + multi_dim_discr = Coupler(single_dim_discr) + return multi_dim_discr + + def discharge(self): + self.diffusive_disc().split(self.grid(), 'p', self._solver.p) + fvutils.compute_discharges(self.grid()) + + +class SlightlyCompressibleData(ParabolicData): + ''' + Inherits from ParabolicData + Base class for assigning valid data for a slighly compressible problem. + Init: + - g (Grid) Grid that data should correspond to + - d (dictionary) data dictionary that data will be assigned to + - physics (string) Physics key word. See Parameters class for valid physics + + Functions: + compressibility: (float) the compressibility of the fluid + permeability: (tensor.SecondOrder) The permeability tensor for the rock. + Setting the permeability is equivalent to setting + the ParabolicData.diffusivity() function. + Example: + # We set an inflow and outflow boundary condition by overloading the + # bc_val term + class ExampleData(SlightlyCompressibleData): + def __init__(g, d): + SlightlyCompressibleData.__init__(self, g, d) + def bc_val(self): + left = self.grid().nodes[0] < 1e-6 + right = self.grid().nodes[0] > 1 - 1e-6 + val = np.zeros(g.num_faces) + val[left] = 1 + val[right] = -1 + return val + gb = meshing.cart_grid([], [10,10], physdims=[1,1]) + for g, d in gb: + d['problem'] = ExampleData(g, d) + ''' + + def __init__(self, g, data, physics='flow'): + ParabolicData.__init__(self, g, data, physics) + + def _set_data(self): + ParabolicData._set_data(self) + self.data()['compressibility'] = self.compressibility() def compressibility(self): - return 1 + return 1.0 def permeability(self): kxx = np.ones(self.grid().num_cells) return tensor.SecondOrder(self.grid().dim, kxx) - #--------Inn/outflow terms--------------- - - def initial_pressure(self): - return np.zeros(self.grid().num_cells) - - def source(self, t): - return np.zeros(self.g.num_cells) - - def bc(self): - dir_bound = np.array([]) - return bc.BoundaryCondition(self.grid(), dir_bound, - ['dir'] * dir_bound.size) - - def bc_val(self, t): - return np.zeros(self.grid().num_faces) - - #---------Overloaded Functions----------------- - def grid(self): - raise NotImplementedError('subclass must overload function grid()') - - #--------Time stepping------------ - def time_step(self): - return 1.0 - - def end_time(self): - return 1.0 - - def save_results(self): - pressures = self.data['pressure'] - times = np.array(self.data['times']) - folder = self.parameters['folder_name'] - f_name = self.parameters['file_name'] - for i, p in enumerate(pressures): - data_to_plot = {'pressure': p} - export_vtk( - self.grid(), f_name, data_to_plot, time_step=i, folder=folder) - - export_pvd( - self.grid(), self.parameters['file_name'], times, folder=folder) + def diffusivity(self): + return self.permeability() diff --git a/src/porepy/numerics/compressible/solvers.py b/src/porepy/numerics/compressible/solvers.py deleted file mode 100644 index 541a8325ac..0000000000 --- a/src/porepy/numerics/compressible/solvers.py +++ /dev/null @@ -1,125 +0,0 @@ -import numpy as np -import scipy.sparse as sps - -from porepy.grids import structured -from porepy.params.data import Parameters -from porepy.params import tensor -from porepy.params import bc - - -class Implicit(): - """ - Class for solving slightly compressible flow using backward Euler. - We solve the equation: - c_p * phi * (p^k+1 - p^k)/dt - nabla * K * grad(p^k+1) = f^k+1. - """ - - def __init__(self, problem): - """ - Parameters: - ---------- - problem: a problem class. Must have the attributes - problem.grid() - problem.porosity() - problem.compressibility() - problem.flux_disc() - problem.permeability() - problem.time_step() - problem.end_time() - problem.bc() - problem.bc_val(t) - problem.initial_pressure() - problem.source(t) - """ - g = problem.grid() - por = problem.porosity() - cell_vol = g.cell_volumes - c_p = problem.compressibility() - dt = problem.time_step() - T = problem.end_time() - flux_disc = problem.flux_disc() - param = Parameters(g) - data = dict() - param.set_tensor(flux_disc, problem.permeability()) - param.set_source(flux_disc, problem.source(dt)) - param.set_bc(flux_disc, problem.bc()) - param.set_bc_val(flux_disc, problem.bc_val(dt)) - data['param'] = param - data['pressure'] = [] - data['times'] = [] - lhs_flux, rhs_flux = flux_disc.matrix_rhs(g, data) - p0 = problem.initial_pressure() - p = p0 - data['pressure'].append(p) - data['times'].append(0.0) - - self.problem = problem - self.g = g - self.data = data - self.por = por - self.dt = dt - self.T = T - self.flux_disc = flux_disc - self.p0 = p0 - self.p = p - self.lhs_flux = lhs_flux - self.rhs_flux = rhs_flux - - # First initial empty lhs and rhs, then initialize them throug - # reassemble - self.lhs = [] - self.rhs = [] - self.reassemble() - - self.parameters = {'store_results': False, 'verbose': False} - - def solve(self): - """ - Solve problem. - """ - t = self.dt - while t < self.T + 1e-14: - if self.parameters['verbose']: - print('solving time step: ', t) - self.step() - self.update(t) - self.reassemble() - t += self.dt - return self.data - - def step(self): - """ - Take one time step - """ - self.p = sps.linalg.spsolve(self.lhs, self.rhs) - - def update(self, t): - """ - update parameters for next time step - """ - param = self.data['param'] - param.set_source(self.flux_disc, self.problem.source(t + self.dt)) - param.set_bc_val(self.flux_disc, self.problem.bc_val(t + self.dt)) - self.p0 = self.p - self.reassemble() - # Store result - if self.parameters['store_results'] == True: - self.data['pressure'].append(self.p) - self.data['times'].append(t) - - def reassemble(self): - """ - reassemble matrices. This must be called between every time step to - update the rhs of the system. - """ - bound_flux = self.data['bound_flux'] - param = self.data['param'] - por = self.problem.porosity() - cell_vol = self.g.cell_volumes - c_p = self.problem.compressibility() - I = np.eye(self.flux_disc.ndof(self.g)) - - self.lhs = cell_vol * por * c_p * I + self.dt * self.lhs_flux - self.rhs_flux = self.dt * self.flux_disc.rhs( - self.g, bound_flux, param.bc_val_flow, param.source_flow) - self.rhs = cell_vol * por * c_p * self.p0 + self.dt * self.rhs_flux diff --git a/src/porepy/numerics/darcy_and_transport.py b/src/porepy/numerics/darcy_and_transport.py new file mode 100644 index 0000000000..dc05740954 --- /dev/null +++ b/src/porepy/numerics/darcy_and_transport.py @@ -0,0 +1,29 @@ +class DarcyAndTransport(): + """ + Wrapper for a stationary Darcy problem and a transport problem + on the resulting fluxes. + The flow and transport inputs should be members of the + Darcy and Parabolic classes, respectively. + """ + + def __init__(self, flow, transport): + self.flow = flow + self.transport = transport + + def solve(self): + """ + Solve both problems. + """ + self.flow.reassemble() + p = self.flow.step() + self.flow.discharge() + + s = self.transport.solve() + return p, s['transport'] + + def save(self, export_every=1): + """ + Save for visualization. + """ + self.flow.save() + self.transport.save(save_every=export_every) diff --git a/src/porepy/numerics/elliptic.py b/src/porepy/numerics/elliptic.py new file mode 100644 index 0000000000..407e3fad45 --- /dev/null +++ b/src/porepy/numerics/elliptic.py @@ -0,0 +1,211 @@ +''' +Module for initializing, assigning data, solve, and save an elliptic pde. +Ths can for example be incompressible flow +problem assuming darcy's law. Please see the tutorial darcy's equation on the +porepy github: https://github.com/pmgbergen/porepy +''' +import numpy as np +import scipy.sparse as sps + +from porepy.numerics.fv import tpfa, source, fvutils +from porepy.grids.grid_bucket import GridBucket +from porepy.params import bc, tensor +from porepy.params.data import Parameters +from porepy.viz.exporter import export_vtk + + +class Elliptic(): + ''' + Class for solving an incompressible flow problem: + \nabla K \nabla p = q, + where K is the second order permeability tenser, p the fluid pressure + and q sinks and sources. + + Parameters in Init: + gb: (Grid /GridBucket) a grid or grid bucket object. If gb = GridBucket + a Parameter class should be added to each grid bucket data node with + keyword 'param'. + data: (dictionary) Defaults to None. Only used if gb is a Grid. Should + contain a Parameter class with the keyword 'Param' + physics: (string): defaults to 'flow' + + Functions: + solve(): Calls reassemble and solves the linear system. + Returns: the pressure p. + Sets attributes: self.p + step(): Same as solve, but without reassemble of the matrices + reassemble(): Assembles the lhs matrix and rhs array. + Returns: lhs, rhs. + Sets attributes: self.lhs, self.rhs + source_disc(): Defines the discretization of the source term. + Returns Source discretization object + flux_disc(): Defines the discretization of the flux term. + Returns Flux discretization object (E.g., Tpfa) + grid(): Returns: the Grid or GridBucket + data(): Returns: Data dictionary + split(name): Assignes the pressure self.p to the data dictionary at each + node in the GridBucket. + Parameters: + name: (string) The keyword assigned to the pressure + discharge(): Calls split('p'). Then calculate the discharges over each + face in the grids and between edges in the GridBucket + save(): calls split('p'). Then export the pressure to a vtk file to the + folder self.parameters['folder_name'] with file name + self.parameters['file_name'] + ''' + + def __init__(self, gb, data=None, physics='flow'): + self.physics = physics + self._gb = gb + self._data = data + self.lhs = [] + self.rhs = [] + self.p = np.zeros(self.flux_disc().ndof(self.grid())) + self.parameters = {'file_name': physics} + self.parameters['folder_name'] = 'results' + + def solve(self): + self.lhs, self.rhs = self.reassemble() + self.p = sps.linalg.spsolve(self.lhs, self.rhs) + return self.p + + def step(self): + return self.solve() + + def reassemble(self): + """ + reassemble matrices. This must be called between every time step to + update the rhs of the system. + """ + lhs_flux, rhs_flux = self._discretize(self.flux_disc()) + lhs_source, rhs_source = self._discretize(self.source_disc()) + assert lhs_source.nnz == 0, 'Source lhs different from zero!' + self.lhs = lhs_flux + self.rhs = rhs_flux + rhs_source + return self.lhs, self.rhs + + def source_disc(self): + if isinstance(self.grid(), GridBucket): + source_discr = source.IntegralMultiDim(physics=self.physics) + else: + source_discr = source.Integral(physics=self.physics) + return source_discr + + def flux_disc(self): + if isinstance(self.grid(), GridBucket): + diffusive_discr = tpfa.TpfaMultiDim(physics=self.physics) + else: + diffusive_discr = tpfa.Tpfa(physics=self.physics) + return diffusive_discr + + def _discretize(self, discr): + if isinstance(self.grid(), GridBucket): + lhs, rhs = discr.matrix_rhs(self.grid()) + else: + lhs, rhs = discr.matrix_rhs(self.grid(), self.data()) + return lhs, rhs + + def grid(self): + return self._gb + + def data(self): + return self._data + + def split(self, name): + self.flux_disc().split(self.grid(), name, self.p) + + def discharge(self): + self.split('p') + fvutils.compute_discharges(self.grid()) + + def save(self, save_every=None): + self.split('p') + folder = self.parameters['folder_name'] + f_name = self.parameters['file_name'] + export_vtk(self.grid(), f_name, ['p'], folder=folder) + + +class EllipticData(): + ''' + Class for setting data to an incompressible flow problem: + \nabla K \nabla p = q, + where K is the second order permeability tenser, p the fluid pressure + and q sinks and sources. This class creates a Parameter object and + assigns the data to this object by calling EllipticData's functions. + + To change the default values create a class that inherits from EllipticData. + Then overload the values you whish to change. + + Parameters in Init: + gb: (Grid /GridBucket) a grid or grid bucket object + data: (dictionary) Dictionary which Parameter will be added to with keyword + 'param' + physics: (string): defaults to 'flow' + + Functions that assign data to Parameter class: + bc(): defaults to neumann boundary condition + Returns: (Object) boundary condition + bc_val(): defaults to 0 + returns: (ndarray) boundary condition values + porosity(): defaults to 1 + returns: (ndarray) porosity of each cell + apperture(): defaults to 1 + returns: (ndarray) aperture of each cell + permeability(): defaults to 1 + returns: (tensor.SecondOrder) Permeabillity tensor + source(): defaults to 0 + returns: (ndarray) The source and sinks + + Utility functions: + grid(): returns: the grid + + ''' + + def __init__(self, g, data, physics='flow'): + self._g = g + self._data = data + + self.physics = physics + self._set_data() + + def bc(self): + return bc.BoundaryCondition(self.grid()) + + def bc_val(self): + return np.zeros(self.grid().num_faces) + + def porosity(self): + '''Returns apperture of each cell. If None is returned, default + Parameter class value is used''' + return None + + def aperture(self): + '''Returns apperture of each cell. If None is returned, default + Parameter class value is used''' + return None + + def permeability(self): + kxx = np.ones(self.grid().num_cells) + return tensor.SecondOrder(self.grid().dim, kxx) + + def source(self): + return np.zeros(self.grid().num_cells) + + def data(self): + return self._data + + def grid(self): + return self._g + + def _set_data(self): + if 'param' not in self._data: + self._data['param'] = Parameters(self.grid()) + self._data['param'].set_tensor(self.physics, self.permeability()) + self._data['param'].set_bc(self.physics, self.bc()) + self._data['param'].set_bc_val(self.physics, self.bc_val()) + self._data['param'].set_source(self.physics, self.source()) + + if self.porosity() is not None: + self._data['param'].set_porosity(self.porosity()) + if self.aperture() is not None: + self._data['param'].set_aperture(self.aperture()) diff --git a/src/porepy/numerics/fv/mpfa.py b/src/porepy/numerics/fv/mpfa.py index 44a6b9f8ee..144fb513b4 100644 --- a/src/porepy/numerics/fv/mpfa.py +++ b/src/porepy/numerics/fv/mpfa.py @@ -88,11 +88,6 @@ def matrix_rhs(self, g, data, discretize=True): The name of data in the input dictionary (data) are: k : second_order_tensor Permeability defined cell-wise. - f : array (self.g.num_cells) - Scalar source term defined cell-wise. Given as net inn/out-flow, i.e. - should already have been multiplied with the cell sizes. Positive - values are considered innflow. If not given a zero source - term is assumed and a warning arised. bc : boundary conditions (optional) bc_val : dictionary (optional) Values of the boundary conditions. The dictionary has at most the @@ -129,24 +124,20 @@ def matrix_rhs(self, g, data, discretize=True): param = data['param'] bc_val = param.get_bc_val(self) - f = param.get_source(self) - return M, self.rhs(g, bound_flux, bc_val, f) + return M, self.rhs(g, bound_flux, bc_val) #------------------------------------------------------------------------------# - def rhs(self, g, bound_flux, bc_val, f): + def rhs(self, g, bound_flux, bc_val): """ Return the righ-hand side for a discretization of a second order elliptic equation using the MPFA method. See self.matrix_rhs for a detaild description. """ - if f is None: - f = np.zeros(g.num_cells) - warnings.warn('Scalar source not assigned, assumed null') div = g.cell_faces.T - return -div * bound_flux * bc_val + f + return -div * bound_flux * bc_val #------------------------------------------------------------------------------# @@ -156,11 +147,6 @@ def discretize(self, g, data): k : second_order_tensor Permeability defined cell-wise. If not given a identity permeability is assumed and a warning arised. - f : array (self.g.num_cells) - Scalar source term defined cell-wise. Given as net inn/out-flow, i.e. - should already have been multiplied with the cell sizes. Positive - values are considered innflow. If not given a zero source - term is assumed and a warning arised. bc : boundary conditions (optional) bc_val : dictionary (optional) Values of the boundary conditions. The dictionary has at most the @@ -321,6 +307,35 @@ def mpfa(g, k, bnd, eta=None, inverter=None, apertures=None, max_memory=None, #------------------------------------------------------------------------------ +class MpfaMultiDim(Solver): + """ + Solver class for a multi-dimensional Mpfa discretization with a Tpfa + coupling between dimensions. + """ + + def __init__(self, physics='flow'): + self.physics = physics + discr = Mpfa(self.physics) + coupling_conditions = TpfaCoupling(discr) + self.solver = Coupler(discr, coupling_conditions) + + def ndof(self, gb): + ndof = 0 + for g, _ in gb: + ndof += g.num_cells + return ndof + + def matrix_rhs(self, gb): + """ + Returns the solution matrix and right hand side for the global system, + see Coupler.matrix_rhs. + """ + return self.solver.matrix_rhs(gb) + + def split(self, gb, names, var): + return self.solver.split(gb, names, var) +#------------------------------------------------------------------------------ + def mpfa_partial(g, k, bnd, eta=0, inverter='numba', cells=None, faces=None, nodes=None, apertures=None): """ diff --git a/src/porepy/numerics/fv/source.py b/src/porepy/numerics/fv/source.py new file mode 100644 index 0000000000..ad605da78e --- /dev/null +++ b/src/porepy/numerics/fv/source.py @@ -0,0 +1,53 @@ +''' +Discretization of the flux term of an equation. +''' + +import numpy as np +import scipy.sparse as sps + +from porepy.numerics.mixed_dim.solver import Solver +from porepy.numerics.mixed_dim.coupler import Coupler + + +class Integral(Solver): + ''' + Discretization of the integrated source term + int q * dx + over each grid cell. + + All this function does is returning a zero lhs and + rhs = param.get_source.physics. + ''' + + def __init__(self, physics='flow'): + self.physics = physics + Solver.__init__(self) + + def ndof(self, g): + return g.num_cells + + def matrix_rhs(self, g, data): + param = data['param'] + sources = param.get_source(self) + lhs = sps.csc_matrix((g.num_cells, g.num_cells)) + assert sources.size == g.num_cells, 'There should be one soure value for each cell' + return lhs, sources + + +class IntegralMultiDim(Solver): + def __init__(self, physics='flow'): + self.physics = physics + discr = Integral(self.physics) + self.solver = Coupler(discr) + Solver.__init__(self) + + def ndof(self, gb): + ndof = 0 + for g, _ in gb: + ndof += g.num_cells + + def matrix_rhs(self, gb): + return self.solver.matrix_rhs(gb) + + def split(self, gb, names, var): + return self.solver.split(gb, names, var) diff --git a/src/porepy/numerics/fv/tpfa.py b/src/porepy/numerics/fv/tpfa.py index e57915750a..c1d86f29f0 100644 --- a/src/porepy/numerics/fv/tpfa.py +++ b/src/porepy/numerics/fv/tpfa.py @@ -134,23 +134,19 @@ def matrix_rhs(self, g, data, faces=None, discretize=True): bound_flux = data['bound_flux'] param = data['param'] bc_val = param.get_bc_val(self) - sources = param.get_source(self) - return M, self.rhs(g, bound_flux, bc_val, sources) + return M, self.rhs(g, bound_flux, bc_val) #------------------------------------------------------------------------------# - def rhs(self, g, bound_flux, bc_val, f): + def rhs(self, g, bound_flux, bc_val): """ Return the righ-hand side for a discretization of a second order elliptic equation using the TPFA method. See self.matrix_rhs for a detaild description. """ - if f is None: - f = np.zeros(g.num_cells) - warnings.warn('Scalar source not assigned, assumed null') div = g.cell_faces.T - return -div * bound_flux * bc_val + f + return -div * bound_flux * bc_val #------------------------------------------------------------------------------# @@ -179,6 +175,12 @@ def discretize(self, g, data, faces=None): apertures : (np.ndarray) (optional) apertures of the cells for scaling of the face normals. + Hidden option (intended as "advanced" option that one should normally not + care about): + Half transmissibility calculation according to Ivar Aavatsmark, see + folk.uib.no/fciia/elliptisk.pdf. Activated by adding the entry + Aavatsmark_transmissibilities: True to the data dictionary. + Parameters ---------- g : grid, or a subclass, with geometry fields computed. @@ -216,12 +218,20 @@ def discretize(self, g, data, faces=None): # Transpose normal vectors to match the shape of K and multiply the two nk = perm * n - nk = nk.sum(axis=0) + nk = nk.sum(axis=1) + + if data.get('Aavatsmark_transmissibilities', False): + # These work better in some cases (possibly if the problem is grid + # quality rather than anisotropy?). To be explored (with care) or + # ignored. + dist_face_cell = np.linalg.norm(fc_cc, 2, axis=0) + t_face = np.linalg.norm(nk, 2, axis=0) + else: + nk *= fc_cc + t_face = nk.sum(axis=0) + dist_face_cell = np.power(fc_cc, 2).sum(axis=0) + - # Divide the norm of the normal permeability through the distance between - # the face centre and the cell centre - t_face = np.linalg.norm(nk, 2, axis=0) - dist_face_cell = np.linalg.norm(fc_cc, 2, axis=0) t_face = np.divide(t_face, dist_face_cell) # Return harmonic average @@ -230,7 +240,7 @@ def discretize(self, g, data, faces=None): # Move Neumann faces to Neumann transmissibility bndr_ind = g.get_boundary_faces() t_b = np.zeros(g.num_faces) - t_b[bnd.is_dir] = - t[bnd.is_dir] + t_b[bnd.is_dir] = -t[bnd.is_dir] t_b[bnd.is_neu] = 1 t_b = t_b[bndr_ind] t[np.logical_or(bnd.is_neu, is_not_active)] = 0 @@ -249,6 +259,32 @@ def discretize(self, g, data, faces=None): #------------------------------------------------------------------------------ +class TpfaMultiDim(Solver): + """ + Solver class for a multi-dimensional Tpfa discretization including coupling + between dimensions. + """ + + def __init__(self, physics='flow'): + self.physics = physics + discr = Tpfa(self.physics) + coupling_conditions = TpfaCoupling(discr) + self.solver = Coupler(discr, coupling_conditions) + + def ndof(self, gb): + ndof = 0 + for g, _ in gb: + ndof += g.num_cells + return ndof + + def matrix_rhs(self, gb): + return self.solver.matrix_rhs(gb) + + def split(self, gb, names, var): + return self.solver.split(gb, names, var) + +#------------------------------------------------------------------------------ + class TpfaCoupling(AbstractCoupling): def __init__(self, solver): @@ -266,6 +302,17 @@ def matrix_rhs(self, g_h, g_l, data_h, data_l, data_edge): to contain both permeability values ('perm') and apertures ('apertures') for each of the cells in the grids. + Two hidden options (intended as "advanced" options that one should + normally not care about): + Half transmissibility calculation according to Ivar Aavatsmark, see + folk.uib.no/fciia/elliptisk.pdf. Activated by adding the entry + 'Aavatsmark_transmissibilities': True to the edge data. + + Aperture correction. The face centre is moved half an aperture + away from the fracture for the matrix side transmissibility + calculation. Activated by adding the entry + 'aperture_correction': True to the edge data. + Returns: cc: Discretization matrices for the coupling terms assembled in a csc.sparse matrix. @@ -288,22 +335,38 @@ def matrix_rhs(self, g_h, g_l, data_h, data_l, data_edge): cells_h, sgn_h = cells_h[faces_h], sgn_h[faces_h] # The procedure for obtaining the face transmissibilities of the higher - # grid is analougous to the one used in the discretize function of the - # Tpfa class. + # grid is in the main analougous to the one used in the discretize function + # of the Tpfa class. n = g_h.face_normals[:, faces_h] n *= sgn_h perm_h = k_h.perm[:, :, cells_h] - fc_cc_h = g_h.face_centers[::, faces_h] - g_h.cell_centers[::, cells_h] + + # Compute the distance between face center and cell center. If specified + # (edgewise), the face centroid is shifted half an aperture in the normal + # direction of the fracture. Unless matrix cell size approaches the + # aperture, this has minimal impact. + if data_edge.get('aperture_correction', False): + apt_dim = np.divide(a_l[cells_l], a_h[cells_h]) + fc_corrected = g_h.face_centers[::,faces_h].copy()-apt_dim/2*n + fc_cc_h = fc_corrected - g_h.cell_centers[::,cells_h] + else: + fc_cc_h = g_h.face_centers[::, faces_h] - g_h.cell_centers[::, cells_h] nk_h = perm_h * n - nk_h = nk_h.sum(axis=0) + nk_h = nk_h.sum(axis=1) + if data_edge.get('Aavatsmark_transmissibilities', False): + dist_face_cell_h = np.linalg.norm(fc_cc_h, 2, axis=0) + t_face_h = np.linalg.norm(nk_h, 2, axis=0) + else: + nk_h *= fc_cc_h + t_face_h = nk_h.sum(axis=0) + dist_face_cell_h = np.power(fc_cc_h, 2).sum(axis=0) # Account for the apertures - t_face_h = np.linalg.norm(nk_h, 2, axis=0) * a_h[cells_h] - dist_face_cell_h = np.linalg.norm(fc_cc_h, 2, axis=0) + t_face_h = t_face_h * a_h[cells_h] + # and compute the matrix side half transmissibilities t_face_h = np.divide(t_face_h, dist_face_cell_h) - # For the lower dimension some simplifications can be made, due to the # alignment of the face normals and (normal) permeabilities of the # cells. First, the normal component of the permeability of the lower diff --git a/src/porepy/numerics/fv/transport/upwind.py b/src/porepy/numerics/fv/transport/upwind.py index eae3c6163a..5a32a349ac 100644 --- a/src/porepy/numerics/fv/transport/upwind.py +++ b/src/porepy/numerics/fv/transport/upwind.py @@ -116,7 +116,6 @@ def matrix_rhs(self, g, data): discharge = param.get_discharge() bc = param.get_bc(self) bc_val = param.get_bc_val(self) - f = param.get_source(self) has_bc = not(bc is None or bc_val is None) @@ -158,7 +157,7 @@ def matrix_rhs(self, g, data): flow_cells.tocsr() if not has_bc: - return flow_cells, f + return flow_cells, np.zeros(g.num_cells) # Impose the boundary conditions bc_val_dir = np.zeros(g.num_faces) @@ -174,8 +173,8 @@ def matrix_rhs(self, g, data): is_neu = np.where(bc.is_neu)[0] bc_val_neu[is_neu] = bc_val[is_neu] - return flow_cells, f - inflow.transpose() * bc_val_dir \ - - np.abs(g.cell_faces.transpose()) * bc_val_neu + return flow_cells, - inflow.transpose() * bc_val_dir \ + - np.abs(g.cell_faces.transpose()) * bc_val_neu #------------------------------------------------------------------------------# diff --git a/src/porepy/numerics/mixed_dim/abstract_coupling.py b/src/porepy/numerics/mixed_dim/abstract_coupling.py index ebcc0fdfd3..e9404e074f 100644 --- a/src/porepy/numerics/mixed_dim/abstract_coupling.py +++ b/src/porepy/numerics/mixed_dim/abstract_coupling.py @@ -1,9 +1,10 @@ import numpy as np import scipy.sparse as sps + class AbstractCoupling(object): -#------------------------------------------------------------------------------# + #------------------------------------------------------------------------------# def __init__(self, discr=None, discr_ndof=None): @@ -75,7 +76,7 @@ def create_block_matrix(self, g_h, g_l): dof = np.array([self.discr_ndof(g_h), self.discr_ndof(g_l)]) # Create the block matrix for the contributions - cc = np.array([sps.coo_matrix((i,j)) for i in dof for j in dof]) + cc = np.array([sps.coo_matrix((i, j)) for i in dof for j in dof]) return dof, cc.reshape((2, 2)) diff --git a/src/porepy/numerics/mixed_dim/coupler.py b/src/porepy/numerics/mixed_dim/coupler.py index d5be3afab9..f70af4d26b 100644 --- a/src/porepy/numerics/mixed_dim/coupler.py +++ b/src/porepy/numerics/mixed_dim/coupler.py @@ -1,9 +1,10 @@ import numpy as np import scipy.sparse as sps + class Coupler(object): -#------------------------------------------------------------------------------# + #------------------------------------------------------------------------------# def __init__(self, discr=None, coupling=None, **kwargs): @@ -128,6 +129,34 @@ def split(self, gb, key, values): gb.add_node_prop(key) for g, d in gb: i = d['node_number'] - d[key] = values[slice(dofs[i], dofs[i+1])] + d[key] = values[slice(dofs[i], dofs[i + 1])] + +#------------------------------------------------------------------------------# + def merge(self, gb, key): + """ + Merge the stored split function stored in the grid bucket to a vector. + The values are put into the global vector according to the numeration + given by "node_number". + + Parameters + ---------- + gb : grid bucket with geometry fields computed. + key: new name of the solution to be stored in the grid bucket. + + Returns + ------- + values: (ndarray) the values stored in the bucket as an array + """ + self.ndof(gb) + dofs = np.empty(gb.size(), dtype=int) + for _, d in gb: + dofs[d['node_number']] = d['dof'] + dofs = np.r_[0, np.cumsum(dofs)] + values = np.zeros(dofs[-1]) + + for g, d in gb: + i = d['node_number'] + values[slice(dofs[i], dofs[i + 1])] = d[key] + return values #------------------------------------------------------------------------------# diff --git a/src/porepy/numerics/parabolic.py b/src/porepy/numerics/parabolic.py new file mode 100644 index 0000000000..bc7fad65bb --- /dev/null +++ b/src/porepy/numerics/parabolic.py @@ -0,0 +1,274 @@ +import numpy as np +import scipy.sparse as sps + +from porepy.params.data import Parameters +from porepy.params import tensor, bc +from porepy.fracs import meshing +from porepy.numerics.fv import fvutils, tpfa, mass_matrix, source +from porepy.numerics.fv.transport import upwind, upwind_coupling +from porepy.numerics import pde_solver +from porepy.numerics.mixed_dim import coupler +from porepy.viz.exporter import export_vtk, export_pvd + + +class ParabolicProblem(): + ''' + Base class for solving general pde problems. This class solves equations of + the type: + dT/dt + v*\nabla T - \nabla K \nabla T = q + + Init: + - gb (Grid/GridBucket) Grid or grid bucket for the problem + - physics (string) Physics key word. See Parameters class for valid physics + + Functions: + data(): returns data dictionary. Is only used for single grids (I.e. not + GridBucket) + solve(): solve problem + step(): take one time step + update(t): update parameters to time t + reassemble(): reassemble matrices and right hand side + solver(): initiate solver (see numerics.pde_solver) + advective_disc(): discretization of the advective term + diffusive_disc(): discretization of the diffusive term + soruce_disc(): discretization of the source term, q + space_disc(): returns one or more of the above discretizations. If + advective_disc(), source_disc() are returned we solve + the problem without diffusion + time_disc(): returns the time discretization + initial_condition(): returns the initial condition for global variable + grid(): returns the grid bucket for the problem + time_step(): returns time step length + end_time(): returns end time + save(save_every=1): save solution. Parameter: save_every, save only every + save_every time steps + + Example: + # We create a problem with default data, neglecting the advective term + + class ExampleProblem(ParabolicProblem): + def __init__(self, gb): + self._g = gb + ParabolicProblem.__init__(self) + + def space_disc(self): + return self.source_disc(), self.diffusive_discr() + gb = meshing.cart_grid([], [10,10], physdims=[1,1]) + for g, d in gb: + d['problem'] = ParabolicData(g, d) + problem = ExampleProblem(gb) + problem.solve() + ''' + + def __init__(self, gb, physics='transport'): + self._gb = gb + self.physics = physics + self._data = dict() + self._set_data() + self._solver = self.solver() + self._solver.parameters['store_results'] = True + self.parameters = {'file_name': physics} + self.parameters['folder_name'] = 'results' + + def data(self): + 'Get data dictionary' + return self._data + + def _set_data(self): + for _, d in self.grid(): + d['deltaT'] = self.time_step() + + def solve(self): + 'Solve problem' + return self._solver.solve() + + def step(self): + 'Take one time step' + return self._solver.step() + + def update(self, t): + 'Update parameters to time t' + for g, d in self.grid(): + d['problem'].update(t) + + def reassemble(self): + 'Reassemble matrices and rhs' + return self._solver.reassemble() + + def solver(self): + 'Initiate solver' + return pde_solver.Implicit(self) + + def advective_disc(self): + 'Discretization of term v * \nabla T' + advection_discr = upwind.Upwind(physics=self.physics) + advection_coupling = upwind_coupling.UpwindCoupling(advection_discr) + advection_solver = coupler.Coupler(advection_discr, advection_coupling) + return advection_solver + + def diffusive_disc(self): + 'Discretization of term \nabla K \nabla T' + diffusive_discr = tpfa.TpfaMultiDim(physics=self.physics) + return diffusive_discr + + def source_disc(self): + 'Discretization of source term, q' + return source.IntegralMultiDim(physics=self.physics) + + def space_disc(self): + '''Space discretization. Returns the discretization terms that should be + used in the model''' + return self.advective_disc(), self.diffusive_disc(), self.source_disc() + + def time_disc(self): + """ + Returns the flux discretization. + """ + mass_matrix_discr = mass_matrix.MassMatrix(physics=self.physics) + multi_dim_discr = coupler.Coupler(mass_matrix_discr) + return multi_dim_discr + + def initial_condition(self): + 'Returns initial condition for global variable' + for _, d in self.grid(): + d[self.physics] = d['problem'].initial_condition() + + global_variable = self.time_disc().merge(self.grid(), self.physics) + return global_variable + + def grid(self): + 'Returns grid/grid_bucket' + return self._gb + + def time_step(self): + 'Returns the time step' + return 1.0 + + def end_time(self): + 'Returns the end time' + return 1.0 + + def save(self, save_every=1): + 'Saves the solution' + variables = self.data()[self.physics][::save_every] + times = np.array(self.data()['times'])[::save_every] + folder = self.parameters['folder_name'] + f_name = self.parameters['file_name'] + + for i, p in enumerate(variables): + self.time_disc().split(self.grid(), self.physics, p) + data_to_plot = [self.physics] + export_vtk( + self.grid(), f_name, data_to_plot, time_step=i, folder=folder) + + export_pvd( + self.grid(), self.parameters['file_name'], times, folder=folder) + + +class ParabolicData(): + ''' + Base class for assigning valid data to a grid. + Init: + - g (Grid) Grid that data should correspond to + - d (dictionary) data dictionary that data will be assigned to + - physics (string) Physics key word. See Parameters class for valid physics + + Functions: + update(t): Update source and bc term to time t + bc: Set boundary condition + bc_val(t): boundary condition value at time t + initial_condition(): initial condition for problem + source(): source term for problem + porosity(): porosity of each cell + diffusivity(): second order diffusivity tensor + aperture(): the aperture of each cell + data(): returns data dictionary + grid(): returns the grid g + + Example: + # We set an inflow and outflow boundary condition by overloading the + # bc_val term + class ExampleData(ParabolicData): + def __init__(g, d): + ParabolicData.__init__(self, g, d) + def bc_val(self): + left = self.grid().nodes[0] < 1e-6 + right = self.grid().nodes[0] > 1 - 1e-6 + val = np.zeros(g.num_faces) + val[left] = 1 + val[right] = -1 + return val + gb = meshing.cart_grid([], [10,10], physdims=[1,1]) + for g, d in gb: + d['problem'] = ExampleData(g, d) + ''' + + def __init__(self, g, data, physics='transport'): + self._g = g + self._data = data + self.physics = physics + self._set_data() + + def update(self, t): + 'Update source and bc_val term to time step t' + source = self.source(t) + bc_val = self.bc_val(t) + self.data()['param'].set_source(self.physics, source) + self.data()['param'].set_bc_val(self.physics, bc_val) + + def bc(self): + 'Define boundary condition' + dir_bound = np.array([]) + return bc.BoundaryCondition(self.grid(), dir_bound, + ['dir'] * dir_bound.size) + + def bc_val(self, t): + 'Returns boundary condition values at time t' + return np.zeros(self.grid().num_faces) + + def initial_condition(self): + 'Returns initial condition' + return np.zeros(self.grid().num_cells) + + def source(self, t): + 'Returns source term' + return np.zeros(self.grid().num_cells) + + def porosity(self): + '''Returns apperture of each cell. If None is returned, default + Parameter class value is used''' + return None + + def diffusivity(self): + 'Returns diffusivity tensor' + kxx = np.ones(self.grid().num_cells) + return tensor.SecondOrder(self.grid().dim, kxx) + + def aperture(self): + '''Returns apperture of each cell. If None is returned, default + Parameter class value is used''' + return None + + def data(self): + 'Returns data dictionary' + return self._data + + def grid(self): + 'Returns grid' + return self._g + + def _set_data(self): + '''Create a Parameter object and assign data based on the returned + values from the functions (e.g., self.source(t)) + ''' + if 'param' not in self._data: + self._data['param'] = Parameters(self.grid()) + self._data['param'].set_tensor(self.physics, self.diffusivity()) + self._data['param'].set_bc(self.physics, self.bc()) + self._data['param'].set_bc_val(self.physics, self.bc_val(0.0)) + self._data['param'].set_source(self.physics, self.source(0.0)) + + if self.porosity() is not None: + self._data['param'].set_porosity(self.porosity()) + if self.aperture() is not None: + self._data['param'].set_aperture(self.aperture()) diff --git a/src/porepy/numerics/pde_solver.py b/src/porepy/numerics/pde_solver.py new file mode 100644 index 0000000000..c0b4af9a78 --- /dev/null +++ b/src/porepy/numerics/pde_solver.py @@ -0,0 +1,239 @@ +import numpy as np +import scipy.sparse as sps + +from porepy.grids import structured +from porepy.grids.grid_bucket import GridBucket +from porepy.params.data import Parameters +from porepy.params import tensor +from porepy.params import bc +from porepy.numerics.mixed_dim.solver import Solver + + +class AbstractSolver(object): + """ + Abstract base class for solving a general first order time pde problem. + dT/dt + G(T) = 0, + where G(T) is a space discretization + """ + + def __init__(self, problem): + """ + Parameters: + ---------- + problem: a problem class. Must have the attributes + problem.grid() + problem.data() + problem.space_disc() + problem.time_disc() + problem.time_step() + problem.end_time() + problem.initial_pressure() + """ + # Get data + g = problem.grid() + + data = problem.data() + data[problem.physics] = [] + data['times'] = [] + + p0 = problem.initial_condition() + p = p0 + + self.problem = problem + self.g = g + self.data = data + self.dt = problem.time_step() + self.T = problem.end_time() + self.space_disc = problem.space_disc() + self.time_disc = problem.time_disc() + self.p0 = p0 + self.p = p + # First initial empty lhs and rhs, then initialize them through + # reassemble + self.lhs = [] + self.rhs = [] + + self.parameters = {'store_results': False, 'verbose': False} + + def solve(self): + """ + Solve problem. + """ + t = self.dt + while t < self.T + 1e-14: + if self.parameters['verbose']: + print('solving time step: ', t) + self.update(t) + self.reassemble() + self.step() + t += self.dt + self.update(t) + return self.data + + def step(self): + """ + Take one time step + """ + self.p = sps.linalg.spsolve(self.lhs, self.rhs) + return self.p + + def update(self, t): + """ + update parameters for next time step + """ + self.problem.update(t) + self.p0 = self.p + # Store result + if self.parameters['store_results'] == True: + self.data[self.problem.physics].append(self.p) + self.data['times'].append(t - self.dt) + + def reassemble(self): + """ + reassemble matrices. This must be called between every time step to + update the rhs of the system. + """ + raise NotImplementedError( + 'subclass must overload function reasemble()') + + def _discretize(self, discs): + if isinstance(self.g, GridBucket): + if not isinstance(discs, tuple): + discs = [discs] + lhs, rhs = np.array(discs[0].matrix_rhs(self.g)) + for disc in discs[1:]: + lhs_n, rhs_n = disc.matrix_rhs(self.g) + lhs += lhs_n + rhs += rhs_n + else: + if not isinstance(discs, tuple): + discs = [discs] + lhs, rhs = discs[0].matrix_rhs(self.g, self.data) + for disc in discs[1:]: + lhs_n, rhs_n = disc.matrix_rhs(self.g, self.data) + lhs += lhs_n + rhs += rhs_n + return lhs, rhs + + +class Implicit(AbstractSolver): + """ + Implicit time discretization: + (y_k+1 - y_k) / dt = F^k+1 + """ + + def __init__(self, problem): + AbstractSolver.__init__(self, problem) + + def reassemble(self): + lhs_flux, rhs_flux = self._discretize(self.space_disc) + lhs_time, rhs_time = self._discretize(self.time_disc) + + self.lhs = lhs_time + lhs_flux + self.rhs = lhs_time * self.p0 + rhs_flux + rhs_time + + +class BDF2(AbstractSolver): + """ + Second order implicit time discretization: + (y_k+2 - 4/3 * y_k+1 + 1/3 * y_k) / dt = 2/3 * F^k+2 + """ + + def __init__(self, problem): + self.flag_first = True + AbstractSolver.__init__(self, problem) + self.p_1 = self.p0 + + def update(self, t): + """ + update parameters for next time step + """ + if t > self.dt + 1e-6: + self.flag_first = False + else: + self.flag_first = True + self.p_1 = self.p0 + AbstractSolver.update(self, t) + + def reassemble(self): + + lhs_flux, rhs_flux = self._discretize(self.space_disc) + lhs_time, rhs_time = self._discretize(self.time_disc) + + if self.flag_first: + self.lhs = lhs_time + lhs_flux + self.rhs = lhs_time * self.p0 + rhs_flux + rhs_time + else: + self.lhs = lhs_time + 2. / 3 * lhs_flux + bdf2_rhs = 4. / 3 * lhs_time * self.p0 - 1. / 3 * lhs_time * self.p_1 + self.rhs = bdf2_rhs + 2. / 3 * rhs_flux + rhs_time + + +class Explicit(AbstractSolver): + """ + Explicit time discretization: + (y_k - y_k-1)/dt = F^k + """ + + def __init__(self, problem): + AbstractSolver.__init__(self, problem) + + def solve(self): + """ + Solve problem. + """ + t = 0.0 + while t < self.T - self.dt + 1e-14: + if self.parameters['verbose']: + print('solving time step: ', t) + self.update(t) + self.reassemble() + self.step() + t += self.dt + + return self.data + + def reassemble(self): + + lhs_flux, rhs_flux = self._discretize(self.space_disc) + lhs_time, rhs_time = self._discretize(self.time_disc) + + self.lhs = lhs_time + self.rhs = (lhs_time - lhs_flux) * self.p0 + rhs_flux + rhs_time + + +class CrankNicolson(AbstractSolver): + """ + Crank-Nicolson time discretization: + (y_k+1 - y_k) / dt = 0.5 * (F^k+1 + F^k) + """ + + def __init__(self, problem): + self.g = problem.grid() + self.lhs_flux, self.rhs_flux = self._discretize(problem.space_disc()) + self.lhs_time, self.rhs_time = self._discretize(problem.time_disc()) + self.lhs_flux_0 = self.lhs_flux + self.rhs_flux_0 = self.rhs_flux + self.lhs_time_0 = self.lhs_time + self.rhs_time_0 = self.rhs_time + AbstractSolver.__init__(self, problem) + + def update(self, t): + """ + update parameters for next time step + """ + AbstractSolver.update(self, t) + self.lhs_flux_0 = self.lhs_flux + self.rhs_flux_0 = self.rhs_flux + self.lhs_time_0 = self.lhs_time + self.rhs_time_0 = self.rhs_time + + def reassemble(self): + self.lhs_flux, self.rhs_flux = self._discretize(self.space_disc) + self.lhs_time, self.rhs_time = self._discretize(self.time_disc) + + rhs1 = 0.5 * (self.rhs_flux + self.rhs_time) + rhs0 = 0.5 * (self.rhs_flux_0 + self.rhs_time_0) + self.lhs = self.lhs_time + 0.5 * self.lhs_flux + self.rhs = (self.lhs_time - 0.5 * self.lhs_flux_0) * \ + self.p0 + rhs1 + rhs0 diff --git a/src/porepy/params/tensor.py b/src/porepy/params/tensor.py index 042275704d..b2cc13aec9 100644 --- a/src/porepy/params/tensor.py +++ b/src/porepy/params/tensor.py @@ -84,7 +84,7 @@ def __init__(self, dim, kxx, kyy=None, kzz=None, kyz = 0 * kxx # Onsager's principle if not np.all((kxx * (kyy * kzz - kyz * kyz) - - kxy * (kyz * kzz - kxz * kyz) + + kxy * (kxy * kzz - kxz * kyz) + kxz * (kxy * kyz - kxz * kyy)) > 0): raise ValueError('Tensor is not positive definite because of ' 'components in z-direction') diff --git a/src/porepy/params/units.py b/src/porepy/params/units.py index 9964d84735..039fe65991 100644 --- a/src/porepy/params/units.py +++ b/src/porepy/params/units.py @@ -1,9 +1,11 @@ +NANO = 1e-9 MICRO = 1e-6 MILLI = 1e-3 CENTI = 1e-2 DECI = 1e-1 KILO = 1e3 MEGA = 1e6 +GIGA = 1e9 # Time units SECOND = 1 @@ -21,4 +23,3 @@ DARCY = 9.869233e-13 PASCAL = 1 BAR = 101325 * PASCAL - diff --git a/src/porepy/utils/sparse_mat.py b/src/porepy/utils/sparse_mat.py index b7368b802b..64835aef7e 100644 --- a/src/porepy/utils/sparse_mat.py +++ b/src/porepy/utils/sparse_mat.py @@ -4,6 +4,31 @@ from porepy.utils.mcolon import mcolon +def zero_columns(A, cols): + ''' + Function to zero out columns in matrix A. Note that this function does not + change the sparcity structure of the matrix, it only changes the column + values to 0 + + Parameter + --------- + A (scipy.sparse.spmatrix): A sparce matrix + cols (ndarray): A numpy array of columns that should be zeroed + Return + ------ + None + + + ''' + + if A.getformat() != 'csc': + raise ValueError('Need a csc matrix') + + indptr = A.indptr + col_indptr = mcolon(indptr[cols], indptr[cols + 1]) + A.data[col_indptr] = 0 + + def slice_indices(A, slice_ind): """ Function for slicing sparse matrix along rows or columns. diff --git a/test/integration/test_advective_diffusive.py b/test/integration/test_advective_diffusive.py new file mode 100644 index 0000000000..bf67140485 --- /dev/null +++ b/test/integration/test_advective_diffusive.py @@ -0,0 +1,228 @@ +import numpy as np +import unittest + +from porepy.numerics.parabolic import * +from porepy.numerics import elliptic +from porepy.fracs import meshing +from porepy.params.data import Parameters +from porepy.params import tensor, bc +from porepy.params.units import * +from porepy.grids.grid import FaceTag + + +class BasicsTest(unittest.TestCase): + + def __init__(self, *args, **kwargs): + f = np.array([[1, 1, 4, 4], [1, 4, 4, 1], [2, 2, 2, 2]]) + box = {'xmin': 0, 'ymin': 0, 'zmin': 0, + 'xmax': 5, 'ymax': 5, 'zmax': 5} + mesh_size = 1.0 + mesh_kwargs = {'mode': 'constant', + 'value': mesh_size, 'bound_value': mesh_size} + self.gb3d = meshing.simplex_grid([f], box, mesh_size=mesh_kwargs) + unittest.TestCase.__init__(self, *args, **kwargs) + + #------------------------------------------------------------------------------# + + def test_src_2d(self): + """ + test that the mono_dimensional elliptic solver gives the same answer as + the grid bucket elliptic + """ + gb = meshing.cart_grid([], [10, 10]) + + for sub_g, d in gb: + if sub_g.dim == 2: + d['problem'] = InjectionDomain(sub_g, d) + else: + d['problem'] = MatrixDomain(sub_g, d) + + problem = SourceProblem(gb) + problem.solve() + dE = change_in_energy(problem) + assert np.abs(dE - 10) < 1e-6 + + def test_src_3d(self): + """ + test that the mono_dimensional elliptic solver gives the same answer as + the grid bucket elliptic + """ + delete_node_data(self.gb3d) + + for sub_g, d in self.gb3d: + if sub_g.dim == 2: + d['problem'] = InjectionDomain(sub_g, d) + else: + d['problem'] = MatrixDomain(sub_g, d) + + problem = SourceProblem(self.gb3d) + problem.solve() + dE = change_in_energy(problem) + assert np.abs(dE - 10) < 1e-6 + + def test_src_advective(self): + delete_node_data(self.gb3d) + + for g, d in self.gb3d: + if g.dim == 2: + d['problem'] = InjectionDomain(g, d) + else: + d['problem'] = MatrixDomain(g, d) + solve_elliptic_problem(self.gb3d) + problem = SourceAdvectiveProblem(self.gb3d) + problem.solve() + dE = change_in_energy(problem) + assert np.abs(dE - 10) < 1e-6 + + def test_src_advective_diffusive(self): + delete_node_data(self.gb3d) + for g, d in self.gb3d: + if g.dim == 2: + d['problem'] = InjectionDomain(g, d) + else: + d['problem'] = MatrixDomain(g, d) + solve_elliptic_problem(self.gb3d) + problem = SourceAdvectiveDiffusiveProblem(self.gb3d) + problem.solve() + dE = change_in_energy(problem) + assert np.abs(dE - 10) < 1e-6 + + def test_constant_temp(self): + delete_node_data(self.gb3d) + for g, d in self.gb3d: + if g.dim == 2: + d['problem'] = InjectionDomain(g, d) + else: + d['problem'] = MatrixDomain(g, d) + solve_elliptic_problem(self.gb3d) + problem = SourceAdvectiveDiffusiveDirBound(self.gb3d) + problem.solve() + for _, d in self.gb3d: + T_list = d['transport'] + const_temp = [np.allclose(T, 10) for T in T_list] + assert np.all(const_temp) + + +class SourceProblem(ParabolicProblem): + def __init__(self, g): + ParabolicProblem.__init__(self, g) + + def space_disc(self): + return self.source_disc() + + def time_step(self): + return 0.5 + + +class SourceAdvectiveProblem(ParabolicProblem): + def __init__(self, g): + ParabolicProblem.__init__(self, g) + + def space_disc(self): + return self.source_disc(), self.advective_disc() + + def time_step(self): + return 0.5 + + +class SourceAdvectiveDiffusiveProblem(ParabolicProblem): + def __init__(self, g): + ParabolicProblem.__init__(self, g) + + def space_disc(self): + return self.source_disc(), self.advective_disc(), self.diffusive_disc() + + def time_step(self): + return 0.5 + + +class SourceAdvectiveDiffusiveDirBound(SourceAdvectiveDiffusiveProblem): + def __init__(self, g): + SourceAdvectiveDiffusiveProblem.__init__(self, g) + + def bc(self): + dir_faces = self.grid().has_face_tag(FaceTag.DOMAIN_BOUNDARY) + dir_faces = np.ravel(np.argwhere(dir_faces)) + bc_cond = bc.BoundaryCondition( + self.grid(), dir_faces, ['dir'] * dir_faces.size) + return bc_cond + + def bc_val(self, t): + dir_faces = self.grid().has_face_tag(FaceTag.DOMAIN_BOUNDARY) + dir_faces = np.ravel(np.argwhere(dir_faces)) + val = np.zeros(self.grid().num_faces) + val[dir_faces] = 10 * PASCAL + return val + + +def source(g, t): + tol = 1e-4 + value = np.zeros(g.num_cells) + cell_coord = np.atleast_2d(np.mean(g.cell_centers, axis=1)).T + cell = np.argmin( + np.sum(np.abs(g.cell_centers - cell_coord), axis=0)) + + value[cell] = 1.0 * KILOGRAM / SECOND + return value + + +class MatrixDomain(ParabolicData): + def __init__(self, g, d, physics='transport'): + ParabolicData.__init__(self, g, d, physics) + + def initial_condition(self): + return 10 * np.ones(self.grid().num_cells) + + +class InjectionDomain(MatrixDomain): + def __init__(self, g, d, physics='transport'): + MatrixDomain.__init__(self, g, d, physics) + + def source(self, t): + flux = source(self.grid(), t) + T_in = 10 + return flux * T_in + + def aperture(self): + return 0.1 * np.ones(self.grid().num_cells) + + +def change_in_energy(problem): + dE = 0 + problem.advective_disc().split( + problem.grid(), 'T', problem._solver.p) + for g, d in problem.grid(): + p = d['T'] + p0 = d['problem'].initial_condition() + V = g.cell_volumes * d['param'].get_aperture() + dE += np.sum((p - p0) * V) + + return dE + + +def solve_elliptic_problem(gb): + for g, d in gb: + if g.dim == 2: + d['param'].set_source( + 'flow', source(g, 0.0)) + + dir_bound = np.argwhere(g.has_face_tag(FaceTag.DOMAIN_BOUNDARY)) + bc_cond = bc.BoundaryCondition( + g, dir_bound, ['dir'] * dir_bound.size) + d['param'].set_bc('flow', bc_cond) + + gb.add_edge_prop('param') + for e, d in gb.edges_props(): + g_h = gb.sorted_nodes_of_edge(e)[1] + d['param'] = Parameters(g_h) + flux = elliptic.Elliptic(gb) + p = flux.solve() + flux.split('p') + fvutils.compute_discharges(gb) + + +def delete_node_data(gb): + for _, d in gb: + d.clear() + + gb.assign_node_ordering() diff --git a/test/integration/test_compressible_flow.py b/test/integration/test_compressible_flow.py new file mode 100644 index 0000000000..a226a6a611 --- /dev/null +++ b/test/integration/test_compressible_flow.py @@ -0,0 +1,114 @@ +import unittest +import numpy as np + +from porepy.numerics.compressible import problems +from porepy.grids import structured +from porepy.fracs import meshing +from porepy.params import tensor + + +class TestCompressibleFlow(unittest.TestCase): + + def test_3d_Fracture_problem(self): + problem = UnitSquareInjectionMultiDim() + solution = problem.solve() + p = solution['flow'][-1] + problem.time_disc().split(problem.grid(), 'pressure', p) + for g, d in problem.grid(): + if g.dim == 3: + pT = d['pressure'] + p_refT = _reference_solution_multi_grid() + + assert np.allclose(pT, p_refT) + + +############################################################################### + + +class MatrixDomain(problems.SlightlyCompressibleData): + def __init__(self, g, d): + problems.SlightlyCompressibleData.__init__(self, g, d) + + +class FractureDomain(problems.SlightlyCompressibleData): + def __init__(self, g, d): + problems.SlightlyCompressibleData.__init__(self, g, d) + aperture = np.power(0.001, 3 - g.dim) + self.data()['param'].set_aperture(aperture) + + def permeability(self): + kxx = 1000 * np.ones(self.grid().num_cells) + return tensor.SecondOrder(3, kxx) + + +class IntersectionDomain(FractureDomain): + def __init__(self, g, d): + FractureDomain.__init__(self, g, d) + + def source(self, t): + assert self.grid().num_cells == 1, '0D grid should only have 1 cell' + f = .4 * self.grid().cell_volumes # m**3/s + return f * (t < .05) + + +def set_sub_problems(gb): + gb.add_node_props(['problem']) + for g, d in gb: + if g.dim == 3: + d['problem'] = MatrixDomain(g, d) + elif g.dim == 2 or g.dim == 1: + d['problem'] = FractureDomain(g, d) + elif g.dim == 0: + d['problem'] = IntersectionDomain(g, d) + else: + raise ValueError('Unkown grid-dimension %d' % g.dim) + + +class UnitSquareInjectionMultiDim(problems.SlightlyCompressible): + + def __init__(self): + + f_1 = np.array([[0, 1, 1, 0], [.5, .5, .5, .5], [0, 0, 1, 1]]) + f_2 = np.array([[.5, .5, .5, .5], [0, 1, 1, 0], [0, 0, 1, 1]]) + f_3 = np.array([[0, 1, 1, 0], [0, 0, 1, 1], [.5, .5, .5, .5]]) + f_set = [f_1, f_2, f_3] + + g = meshing.cart_grid(f_set, [4, 4, 4], physdims=[1, 1, 1]) + g.compute_geometry() + g.assign_node_ordering() + set_sub_problems(g) + self.g = g + # Initialize base class + problems.SlightlyCompressible.__init__(self, 'flow') + + #--------grid function-------- + + def grid(self): + return self.g + + #--------Time stepping------------ + + def time_step(self): + return .005 + + def end_time(self): + return 0.05 + +############################################################################### + + +def _reference_solution_multi_grid(): + pT = np.array([0.00983112, 0.01637886, 0.01637886, 0.00983112, 0.01637886, + 0.02264049, 0.02264049, 0.01637886, 0.01637886, 0.02264049, + 0.02264049, 0.01637886, 0.00983112, 0.01637886, 0.01637886, + 0.00983112, 0.01637886, 0.02264049, 0.02264049, 0.01637886, + 0.02264049, 0.03234641, 0.03234641, 0.02264049, 0.02264049, + 0.03234641, 0.03234641, 0.02264049, 0.01637886, 0.02264049, + 0.02264049, 0.01637886, 0.01637886, 0.02264049, 0.02264049, + 0.01637886, 0.02264049, 0.03234641, 0.03234641, 0.02264049, + 0.02264049, 0.03234641, 0.03234641, 0.02264049, 0.01637886, + 0.02264049, 0.02264049, 0.01637886, 0.00983112, 0.01637886, + 0.01637886, 0.00983112, 0.01637886, 0.02264049, 0.02264049, + 0.01637886, 0.01637886, 0.02264049, 0.02264049, 0.01637886, + 0.00983112, 0.01637886, 0.01637886, 0.00983112]) + return pT diff --git a/test/integration/test_darcy.py b/test/integration/test_darcy.py new file mode 100644 index 0000000000..2a1cbf176b --- /dev/null +++ b/test/integration/test_darcy.py @@ -0,0 +1,195 @@ +import numpy as np +import unittest + +from porepy.numerics import elliptic +from porepy.grids.structured import CartGrid +from porepy.fracs import meshing +from porepy.params.data import Parameters +from porepy.params import tensor, bc + + +class BasicsTest(unittest.TestCase): + + #------------------------------------------------------------------------------# + + def test_mono_equals_multi(self): + """ + test that the mono_dimensional elliptic solver gives the same answer as + the grid bucket elliptic + """ + g = CartGrid([10, 10]) + g.compute_geometry() + gb = meshing.cart_grid([], [10, 10]) + param_g = Parameters(g) + + def bc_val(g): + left = g.face_centers[0] < 1e-6 + right = g.face_centers[0] > 10 - 1e-6 + + bc_val = np.zeros(g.num_faces) + bc_val[left] = -1 + bc_val[right] = 1 + return bc_val + + param_g.set_bc_val('flow', bc_val(g)) + + gb.add_node_props(['param']) + for sub_g, d in gb: + d['param'] = Parameters(sub_g) + d['param'].set_bc_val('flow', bc_val(g)) + + problem_mono = elliptic.Elliptic(g, {'param': param_g}) + problem_mult = elliptic.Elliptic(gb) + + p_mono = problem_mono.solve() + p_mult = problem_mult.solve() + + assert np.allclose(p_mono, p_mult) +#------------------------------------------------------------------------------# + + def test_elliptic_uniform_flow_cart(self): + gb = setup_2d_1d([10, 10]) + problem = elliptic.Elliptic(gb) + p = problem.solve() + problem.split('pressure') + + for g, d in gb: + pressure = d['pressure'] + p_analytic = g.cell_centers[1] + p_diff = pressure - p_analytic + assert np.max(np.abs(p_diff)) < 0.03 +#------------------------------------------------------------------------------# + + def test_elliptic_uniform_flow_simplex(self): + """ + Unstructured simplex grid. Note that the solution depends + on the grid quality. Also sensitive to the way in which + the tpfa half transmissibilities are computed. + """ + gb = setup_2d_1d(np.array([10, 10]), simplex_grid=True) + problem = elliptic.Elliptic(gb) + p = problem.solve() + problem.split('pressure') + + for g, d in gb: + pressure = d['pressure'] + p_analytic = g.cell_centers[1] + p_diff = pressure - p_analytic + assert np.max(np.abs(p_diff)) < 0.033 + + def test_elliptic_dirich_neumann_source_sink_cart(self): + gb = setup_3d(np.array([4, 4, 4]), simplex_grid=False) + problem = elliptic.Elliptic(gb) + p = problem.solve() + problem.split('pressure') + + for g, d in gb: + if g.dim == 3: + p_ref = elliptic_dirich_neumann_source_sink_cart_ref_3d() + assert np.allclose(d['pressure'], p_ref) + if g.dim == 0: + p_ref = [-10788.06883149] + assert np.allclose(d['pressure'], p_ref) + return gb + + +def setup_3d(nx, simplex_grid=False): + f1 = np.array( + [[0.2, 0.2, 0.8, 0.8], [0.2, 0.8, 0.8, 0.2], [0.5, 0.5, 0.5, 0.5]]) + f2 = np.array( + [[0.2, 0.8, 0.8, 0.2], [0.5, 0.5, 0.5, 0.5], [0.2, 0.2, 0.8, 0.8]]) + f3 = np.array( + [[0.5, 0.5, 0.5, 0.5], [0.2, 0.8, 0.8, 0.2], [0.2, 0.2, 0.8, 0.8]]) + fracs = [f1, f2, f3] + if not simplex_grid: + gb = meshing.cart_grid(fracs, nx, physdims=[1, 1, 1]) + else: + mesh_kwargs = {} + mesh_size = .3 + mesh_kwargs['mesh_size'] = {'mode': 'constant', + 'value': mesh_size, 'bound_value': 2 * mesh_size} + domain = {'xmin': 0, 'ymin': 0, 'xmax': 1, 'ymax': 1} + gb = meshing.simplex_grid(fracs, domain, **mesh_kwargs) + + gb.add_node_props(['param']) + for g, d in gb: + a = 0.01 / np.max(nx) + a = np.power(a, gb.dim_max() - g.dim) + param = Parameters(g) + param.set_aperture(a) + + # BoundaryCondition + left = g.face_centers[0] < 1e-6 + top = g.face_centers[2] > 1 - 1e-6 + dir_faces = np.argwhere(left) + bc_cond = bc.BoundaryCondition(g, dir_faces, ['dir'] * dir_faces.size) + bc_val = np.zeros(g.num_faces) + bc_val[dir_faces] = 3 + bc_val[top] = 2.4 + param.set_bc('flow', bc_cond) + param.set_bc_val('flow', bc_val) + + # Source and sink + src = np.zeros(g.num_cells) + src[0] = np.pi + src[-1] = -np.pi + param.set_source('flow', src) + d['param'] = param + return gb + + +def setup_2d_1d(nx, simplex_grid=False): + frac1 = np.array([[0.2, 0.8], [0.5, 0.5]]) + frac2 = np.array([[0.5, 0.5], [0.8, 0.2]]) + fracs = [frac1, frac2] + if not simplex_grid: + gb = meshing.cart_grid(fracs, nx, physdims=[1, 1]) + else: + mesh_kwargs = {} + mesh_size = .3 + mesh_kwargs['mesh_size'] = {'mode': 'constant', + 'value': mesh_size, 'bound_value': 1 * mesh_size} + domain = {'xmin': 0, 'ymin': 0, 'xmax': 1, 'ymax': 1} + gb = meshing.simplex_grid(fracs, domain, **mesh_kwargs) + + gb.compute_geometry() + gb.assign_node_ordering() + gb.add_node_props(['param']) + for g, d in gb: + kxx = np.ones(g.num_cells) + perm = tensor.SecondOrder(3, kxx) + a = 0.01 / np.max(nx) + a = np.power(a, gb.dim_max() - g.dim) + param = Parameters(g) + param.set_tensor('flow', perm) + param.set_aperture(a) + if g.dim == 2: + bound_faces = g.get_boundary_faces() + bound = bc.BoundaryCondition(g, bound_faces.ravel('F'), + ['dir'] * bound_faces.size) + bc_val = g.face_centers[1] + param.set_bc('flow', bound) + param.set_bc_val('flow', bc_val) + d['param'] = param + + return gb + + +def elliptic_dirich_neumann_source_sink_cart_ref_3d(): + p_ref = np.array([0.54570555, -11.33848749, -19.44484907, -23.13293673, + -2.03828237, -12.73249228, -20.96189563, -24.14626244, + -2.81412045, -14.03104316, -22.2699576, -25.06029852, + -2.82350853, -13.6157183, -21.47553858, -24.92564315, + -2.46107297, -13.72231418, -22.34607642, -25.80769869, + -3.22878706, -15.29275276, -26.21591677, -27.42991885, + -3.99188865, -18.72292714, -29.82101211, -28.89933092, + -3.68770392, -16.13278292, -25.09083527, -28.24109233, + -4.36104216, -17.17318124, -26.53960339, -30.32186276, + -4.81751268, -18.63731792, -30.45264082, -32.08038545, + -5.489682, -22.06836116, -34.23287575, -33.94433277, + -5.17804343, -19.54672997, -29.78375042, -34.04856, + -7.71448607, -22.60562853, -32.40425572, -36.8597635, + -8.00575965, -23.53059111, -34.01202746, -38.25317203, + -8.37196805, -24.79222197, -35.8194776, -40.46051172, + -8.34414468, -24.57071193, -35.99975111, -44.22506448]) + return p_ref diff --git a/test/integration/test_pdesolver.py b/test/integration/test_pdesolver.py new file mode 100644 index 0000000000..15ee6ab2e1 --- /dev/null +++ b/test/integration/test_pdesolver.py @@ -0,0 +1,116 @@ +import unittest +import numpy as np + +from porepy.numerics.parabolic import ParabolicProblem, ParabolicData +from porepy.numerics.pde_solver import * +from porepy.grids import structured +from porepy.fracs import meshing +from porepy.params import tensor + + +class TestBase(unittest.TestCase): + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + + f_1 = np.array([[0, 1, 1, 0], [.5, .5, .5, .5], [0, 0, 1, 1]]) + f_2 = np.array([[.5, .5, .5, .5], [0, 1, 1, 0], [0, 0, 1, 1]]) + f_3 = np.array([[0, 1, 1, 0], [0, 0, 1, 1], [.5, .5, .5, .5]]) + f_set = [f_1, f_2, f_3] + + self.gb = meshing.cart_grid(f_set, [4, 4, 4], physdims=[1, 1, 1]) + self.gb.add_node_props(['problem']) + for g, d in self.gb: + d['problem'] = ParabolicData(g, d) + + def test_implicit_solver(self): + '''Inject 1 in cell 0. Test that rhs and pressure solution + is correct''' + problem = UnitSquareInjectionMultiDim(self.gb) + problem.update(0.0) + solver = Implicit(problem) + solver.solve() + assert np.sum(np.abs(solver.rhs) > 1e-6) == 1 + assert np.sum(np.abs(solver.rhs - 1) < 1e-6) == 1 + assert np.sum(np.abs(solver.p) > 1e-6) == 1 + assert np.sum(np.abs(solver.p - 1) < 1e-6) == 1 + + def test_BDF2_solver(self): + '''Inject 1 in cell 0. Test that rhs and pressure solution + is correct''' + problem = UnitSquareInjectionTwoSteps(self.gb) + problem.update(0.0) + solver = BDF2(problem) + solver.update(solver.dt) + solver.reassemble() + solver.step() + # The first step should be an implicit step + assert np.sum(np.abs(solver.rhs) > 1e-6) == 1 + assert np.sum(np.abs(solver.rhs - 1.0) < 1e-6) == 1 + assert np.sum(np.abs(solver.p) > 1e-6) == 1 + assert np.sum(np.abs(solver.p - 1.0 / 2.0) < 1e-6) == 1 + assert np.allclose(solver.p0, 0) + assert np.allclose(solver.p_1, 0) + + solver.update(2 * solver.dt) + solver.reassemble() + solver.step() + # The second step should be a full bdf2 step + assert np.sum(np.abs(solver.rhs) > 1e-6) == 1 + assert np.sum(np.abs(solver.rhs - 2.0) < 1e-6) == 1 + assert np.sum(np.abs(solver.p) > 1e-6) == 1 + assert np.sum(np.abs(solver.p - 1) < 1e-6) == 1 + assert np.sum(np.abs(solver.p0 - 0.5) < 1e-6) == 1 + assert np.allclose(solver.p_1, 0.0) + + def test_explicit_solver(self): + '''Inject 1 in cell 0. Test that rhs and pressure solution + is correct''' + problem = UnitSquareInjectionMultiDim(self.gb) + problem.update(0.0) + solver = Explicit(problem) + solver.solve() + assert np.sum(np.abs(solver.rhs) > 1e-6) == 0 + assert np.sum(np.abs(solver.p) > 1e-6) == 0 + + def test_CrankNicolson_solver(self): + '''Inject 1 in cell 0. Test that rhs and pressure solution + is correct''' + problem = UnitSquareInjectionMultiDim(self.gb) + problem.update(0.0) + solver = CrankNicolson(problem) + solver.solve() + + assert np.sum(np.abs(solver.rhs) > 1e-6) == 1 + assert np.sum(np.abs(solver.rhs - 0.5) < 1e-6) == 1 + assert np.sum(np.abs(solver.p) > 1e-6) == 1 + assert np.sum(np.abs(solver.p - 0.5) < 1e-6) == 1 + + +############################################################################### + + +class UnitSquareInjectionMultiDim(ParabolicProblem): + def __init__(self, gb): + # Initialize base class + ParabolicProblem.__init__(self, gb) + + def space_disc(self): + return self.source_disc() + + #--------Time stepping------------ + + def update(self, t): + for g, d in self.grid(): + source = np.zeros(g.num_cells) + if g.dim == 0 and t > self.time_step() - 1e-5: + source[0] = 1.0 + d['param'].set_source('transport', source) + + +############################################################################### +class UnitSquareInjectionTwoSteps(UnitSquareInjectionMultiDim): + def __init__(self, gb): + UnitSquareInjectionMultiDim.__init__(self, gb) + + def time_step(self): + return 0.5 diff --git a/test/integration/test_tpfaMultiDim.py b/test/integration/test_tpfaMultiDim.py index ab8507c689..34edea2436 100644 --- a/test/integration/test_tpfaMultiDim.py +++ b/test/integration/test_tpfaMultiDim.py @@ -45,13 +45,15 @@ def setup_2d_1d(nx, simplex_grid=False): def check_pressures(gb): """ Check that the pressures are not too far from an approximate - analytical solution. + analytical solution. Note that the solution depends + on the grid quality. Also sensitive to the way in which + the tpfa half transmissibilities are computed. """ for g, d in gb: pressure = d['pressure'] pressure_analytic = g.cell_centers[1] p_diff = pressure - pressure_analytic - assert np.max(np.abs(p_diff)) < 0.03 + assert np.max(np.abs(p_diff)) < 0.033 def test_uniform_flow_cart_2d_1d_cartesian(): # Structured Cartesian grid diff --git a/test/unit/test_darcyEq.py b/test/unit/test_darcyEq.py new file mode 100644 index 0000000000..e617bac03b --- /dev/null +++ b/test/unit/test_darcyEq.py @@ -0,0 +1,99 @@ +import numpy as np +import unittest + +from porepy.numerics.elliptic import EllipticData +from porepy.grids import simplex +from porepy.params import bc, tensor +from porepy.params.data import Parameters + +#------------------------------------------------------------------------------# + + +class BasicsTest(unittest.TestCase): + + #------------------------------------------------------------------------------# + + def test_elliptic_data_default_values(self): + """ + test that the elliptic data initialize the correct data. + """ + p = np.random.rand(3, 10) + g = simplex.TetrahedralGrid(p) + param = Parameters(g) + elliptic_data = dict() + EllipticData(g, elliptic_data) + elliptic_param = elliptic_data['param'] + + check_parameters(elliptic_param, param) + + def test_elliptic_data_given_values(self): + """ + test that the elliptic data initialize the correct data. + """ + p = np.random.rand(3, 10) + g = simplex.TetrahedralGrid(p) + # Set values + bc_val = np.pi * np.ones(g.num_faces) + dir_faces = g.get_boundary_faces() + bc_cond = bc.BoundaryCondition(g, dir_faces, ['dir'] * dir_faces.size) + porosity = 1 / np.pi * np.ones(g.num_cells) + apperture = 0.5 * np.ones(g.num_cells) + kxx = 2 * np.ones(g.num_cells) + kyy = 3 * np.ones(g.num_cells) + K = tensor.SecondOrder(g.dim, kxx, kyy) + source = 42 * np.ones(g.num_cells) + # Assign to parameter + param = Parameters(g) + param.set_bc_val('flow', bc_val) + param.set_bc('flow', bc_cond) + param.set_porosity(porosity) + param.set_aperture(apperture) + param.set_tensor('flow', K) + param.set_source('flow', source) + # Define EllipticData class + + class Data(EllipticData): + def __init__(self, g, data): + EllipticData.__init__(self, g, data) + + def bc(self): + return bc_cond + + def bc_val(self): + return bc_val + + def porosity(self): + return porosity + + def aperture(self): + return apperture + + def permeability(self): + return K + + def source(self): + return source + + elliptic_data = dict() + Data(g, elliptic_data) + elliptic_param = elliptic_data['param'] + + check_parameters(elliptic_param, param) + + +#------------------------------------------------------------------------------# + + +def check_parameters(param_c, param_t): + + bc_c = param_c.get_bc('flow') + bc_t = param_t.get_bc('flow') + k_c = param_c.get_tensor('flow').perm + k_t = param_t.get_tensor('flow').perm + + assert np.alltrue(bc_c.is_dir == bc_t.is_dir) + assert np.alltrue(param_c.get_bc_val('flow') == param_t.get_bc_val('flow')) + assert np.alltrue(param_c.get_porosity() == param_t.get_porosity()) + assert np.alltrue(param_c.get_aperture() == param_t.get_aperture()) + assert np.alltrue(k_c == k_t) + assert np.alltrue(param_c.get_source('flow') == param_t.get_source('flow')) diff --git a/test/unit/test_source.py b/test/unit/test_source.py new file mode 100644 index 0000000000..e9196c3e49 --- /dev/null +++ b/test/unit/test_source.py @@ -0,0 +1,33 @@ +import unittest +import scipy.sparse as sps +import numpy as np + +from porepy.grids.structured import CartGrid +from porepy.numerics.fv import source +from porepy.params.data import Parameters + + +class TestSource(unittest.TestCase): + def test_integral(self): + g, d = setup_3d_grid() + src_disc = source.Integral() + lhs, rhs = src_disc.matrix_rhs(g, d) + + rhs_t = np.array([0, 0, 0, 0, 1, 0, 0, 0]) + assert src_disc.ndof(g) == g.num_cells + assert np.all(rhs == rhs_t) + assert lhs.shape == (8, 8) + assert lhs.nnz == 0 + + if __name__ == '__main__': + unittest.main() + + +def setup_3d_grid(): + g = CartGrid([2, 2, 2], physdims=[1, 1, 1]) + g.compute_geometry() + d = {'param': Parameters(g)} + src = np.zeros(g.num_cells) + src[4] = 1 + d['param'].set_source('flow', src) + return g, d diff --git a/test/unit/test_sparse_mat.py b/test/unit/test_sparse_mat.py index 647b8a5357..df43adebb0 100644 --- a/test/unit/test_sparse_mat.py +++ b/test/unit/test_sparse_mat.py @@ -1,11 +1,27 @@ +import unittest import scipy.sparse as sps import numpy as np -import unittest + from porepy.utils import sparse_mat -class SparseMatTest(unittest.TestCase): +class TestSparseMath(unittest.TestCase): + def zero_1_column(self): + A = sps.csc_matrix((np.array([1, 2, 3]), (np.array( + [0, 2, 1]), np.array([0, 1, 2]))), shape=(3, 3)) + sparse_mat.zero_columns(A, 0) + assert np.all(A.A == np.array([[0, 0, 0], [0, 0, 3], [0, 2, 0]])) + assert A.nnz == 3 + assert A.getformat() == 'csc' + + def zero_2_columns(self): + A = sps.csc_matrix((np.array([1, 2, 3]), (np.array( + [0, 2, 1]), np.array([0, 1, 2]))), shape=(3, 3)) + sparse_mat.zero_columns(A, np.array([0, 2])) + assert np.all(A.A == np.array([[0, 0, 0], [0, 0, 0], [0, 2, 0]])) + assert A.nnz == 3 + assert A.getformat() == 'csc' def test_csr_slice(self): # Test slicing of csr_matrix @@ -33,5 +49,31 @@ def test_csc_slice(self): assert rows_2 == np.array([2]) assert np.all(rows0_2 == np.array([1, 2])) + def test_zero_columns(self): + # Test slicing of csr_matrix + A = sps.csc_matrix(np.array([[0, 0, 0], + [1, 0, 0], + [0, 0, 3]])) + + A0_t = sps.csc_matrix(np.array([[0, 0, 0], + [0, 0, 0], + [0, 0, 3]])) + A2_t = sps.csc_matrix(np.array([[0, 0, 0], + [1, 0, 0], + [0, 0, 0]])) + A0_2_t = sps.csc_matrix(np.array([[0, 0, 0], + [0, 0, 0], + [0, 0, 0]])) + A0 = A.copy() + A2 = A.copy() + A0_2 = A.copy() + sparse_mat.zero_columns(A0, np.array([0])) + sparse_mat.zero_columns(A2, np.array([2])) + sparse_mat.zero_columns(A0_2, np.array([0, 1, 2])) + + assert np.sum(A0 != A0_t) == 0 + assert np.sum(A2 != A2_t) == 0 + assert np.sum(A0_2 != A0_2_t) == 0 + if __name__ == '__main__': unittest.main() diff --git a/test/unit/test_upwind_elimination.py b/test/unit/test_upwind_elimination.py index 7b9477381a..21f3a8a780 100644 --- a/test/unit/test_upwind_elimination.py +++ b/test/unit/test_upwind_elimination.py @@ -10,10 +10,12 @@ from porepy.numerics.fv import tpfa, fvutils from porepy.numerics.fv.transport import upwind +from porepy.numerics.fv.source import IntegralMultiDim from porepy.numerics.mixed_dim import coupler, condensation #------------------------------------------------------------------------------# -class BasicsTest( unittest.TestCase ): + +class BasicsTest(unittest.TestCase): """ Tests for the elimination fluxes. """ @@ -30,24 +32,24 @@ def test_upwind_2d_1d_cross_with_elimination(self): [.5, .5]]) f2 = np.array([[.5, .5], [0, 1]]) - domain = {'xmin': 0, 'ymin': 0, 'xmax':1, 'ymax':1} + domain = {'xmin': 0, 'ymin': 0, 'xmax': 1, 'ymax': 1} mesh_size = 0.4 mesh_kwargs = {} mesh_kwargs['mesh_size'] = {'mode': 'constant', - 'value': mesh_size, 'bound_value': mesh_size} - gb = meshing.cart_grid( [f1,f2], [2, 2], **{'physdims': [1, 1]}) + 'value': mesh_size, 'bound_value': mesh_size} + gb = meshing.cart_grid([f1, f2], [2, 2], **{'physdims': [1, 1]}) #gb = meshing.simplex_grid( [f1, f2],domain,**mesh_kwargs) gb.compute_geometry() gb.assign_node_ordering() # Enforce node orderning because of Python 3.5 and 2.7. # Don't do it in general. - cell_centers_1 = np.array([[ 7.50000000e-01, 2.500000000e-01], - [ 5.00000000e-01, 5.00000000e-01], - [ -5.55111512e-17, 5.55111512e-17]]) - cell_centers_2 = np.array([[ 5.00000000e-01, 5.00000000e-01], - [ 7.50000000e-01, 2.500000000e-01], - [ -5.55111512e-17, 5.55111512e-17]]) + cell_centers_1 = np.array([[7.50000000e-01, 2.500000000e-01], + [5.00000000e-01, 5.00000000e-01], + [-5.55111512e-17, 5.55111512e-17]]) + cell_centers_2 = np.array([[5.00000000e-01, 5.00000000e-01], + [7.50000000e-01, 2.500000000e-01], + [-5.55111512e-17, 5.55111512e-17]]) for g, d in gb: if g.dim == 1: @@ -66,11 +68,11 @@ def test_upwind_2d_1d_cross_with_elimination(self): param = Parameters(g) a_dim = np.power(a, gb.dim_max() - g.dim) - aperture = np.ones(g.num_cells)*a_dim + aperture = np.ones(g.num_cells) * a_dim param.set_aperture(aperture) - kxx = np.ones(g.num_cells) * np.power(1e3, g.dim" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "problem = elliptic.Elliptic(g, d)\n", + "p = problem.solve()\n", + "plot_grid(g, p)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Add fractures\n", + "We now try to solve the same problem, but with a fracture in the domain. We create a multidimensional grid:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "f = np.array([[0,10],[5,5]])\n", + "gb = meshing.cart_grid([f], [10,10])\n", + "gb.assign_node_ordering()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now make use of the DarcyData class to assign data to the fracture and matrix. We wish to set zero dirichlet boundary conditions. However, the data class assigns Neumann conditions by default, so we overload the bc function. We set a small aperture for the fracture, but a high permeability. Note that we let the FractureDomain innherit from the MatrixDomain. This way we assign the same parameters to the fractures as the matrix, unless we overload the parameter function(e.g., the permeabillity) " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class MatrixDomain(elliptic.EllipticData):\n", + " def __init__(self, g, d):\n", + " elliptic.EllipticData.__init__(self, g, d)\n", + "\n", + " def bc(self):\n", + " dir_bound = np.ravel(np.argwhere((self.grid().has_face_tag(FaceTag.DOMAIN_BOUNDARY))))\n", + " return bc.BoundaryCondition(self.grid(), dir_bound, ['dir']*dir_bound.size)\n", + "\n", + "class FractureDomain(MatrixDomain):\n", + " def __init(self, g, d):\n", + " MatrixDomain.__init__(self, g, d)\n", + " \n", + " def permeability(self):\n", + " kxx = 100 * np.ones(self.grid().num_cells)\n", + " return tensor.SecondOrder(2, kxx)\n", + " \n", + " def source(self):\n", + " val = np.ones(self.grid().num_cells)\n", + " val[round(self.grid().num_cells/2)] = 1\n", + " return val\n", + " \n", + " def aperture(self):\n", + " val = 0.01 * np.ones(self.grid().num_cells)\n", + " return val\n", + " \n", + "def assign_darcy_data(gb):\n", + " gb.add_node_props(['problem'])\n", + " for g, d in gb:\n", + " if g.dim == 2:\n", + " d['problem'] = MatrixDomain(g, d)\n", + " else:\n", + " d['problem'] = FractureDomain(g, d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are now ready to declare the problem and solve it" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "assign_darcy_data(gb)\n", + "problem = elliptic.Elliptic(gb)\n", + "problem.solve()\n", + "problem.split('pressure')\n", + "plot_grid(gb, 'pressure')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Change discretization\n", + "By default, the darcy solver uses the TPFA discretization for the fluxes. If we set a anisotropic permeability tensor tpfa will not give a consistent discretization. In these cases we would like to use, e.g., the MPSA discretization. We can do this by overloading the flux_disc() function of the darcy solver. Note that we have already assigned data to the grid, so we do not have to do this again" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from porepy.numerics.fv import mpfa\n", + "class DarcyMPFA(elliptic.Elliptic):\n", + " def __init__(self, gb):\n", + " elliptic.Elliptic.__init__(self, gb)\n", + " \n", + " def flux_disc(self):\n", + " return mpfa.MpfaMultiDim()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "problem = DarcyMPFA(gb)\n", + "problem.solve()\n", + "problem.split('pressure')\n", + "plot_grid(gb, 'pressure')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/slightly compressible flow .ipynb b/tutorials/slightly compressible flow .ipynb index cdc46e6550..2ba3860bb7 100644 --- a/tutorials/slightly compressible flow .ipynb +++ b/tutorials/slightly compressible flow .ipynb @@ -22,7 +22,7 @@ "with boundary conditions on $\\partial \\Omega_d$ and $\\partial \\Omega_n$:\n", "$$ p = p_b \\qquad - K \\nabla p \\cdot \\mathbf{n} = u_b$$\n", "\n", - "Where $\\phi$ is the porosity, $\\rho$ is the fluid density, $f$ is a scalar source/sink term, $K$ is the permeability matrix, $p_b$ is the pressure at the boundary (Dirichlet condition), and $u_p$ is the flux at the boundary (Neumann condition).
\n", + "Where $\\phi$ is the porosity, $\\rho$ is the fluid density, $f$ is a scalar source/sink term, $K$ is the permeability matrix, $p_b$ is the pressure at the boundary (Dirichlet condition), and $u_b$ is the flux at the boundary (Neumann condition).
\n", "\n", "As a relationship between pressure and density we use $c_p\\rho = \\text{d}\\rho/\\text{d}p$. Assuming slightly compressible flow (e.g., $\\nabla\\rho\\cdot K\\nabla p \\ll 1$) we can write conservation of mass as\n", "\n", @@ -44,15 +44,17 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", - "from porepy.numerics.compressible.problems import SlightlyCompressibleProblem\n", - "\n", - "from porepy.grids import structured\n", + "from porepy.numerics.compressible.problems import SlightlyCompressible, SlightlyCompressibleData\n", + "from porepy.fracs import meshing\n", + "from porepy.params import tensor\n", "from porepy.viz import plot_grid" ] }, @@ -61,41 +63,84 @@ "metadata": {}, "source": [ "# Define problem:\n", - "We define our problem in the unit square. We let our problem inherit from the compressible problem base class to set default values." + "We define our problem in the unit square. We let our problem inherit from the compressible problem base class to set default values. We create two different types of problems, one for the fractures, and one for the matrix. For the fractures we use all the standard parameters, but we set the apperture to 0.01. We also define a source term in the intersection of the two fractures" ] }, { "cell_type": "code", "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class MatrixDomain(SlightlyCompressibleData):\n", + " def __init__(self, g, data):\n", + " SlightlyCompressibleData.__init__(self, g, data)\n", + "\n", + "class FractureDomain(MatrixDomain):\n", + " def __init__(self, g, data):\n", + " MatrixDomain.__init__(self, g, data)\n", + " \n", + " def permeability(self):\n", + " kxx = 1000 * np.ones(self.grid().num_cells)\n", + " return tensor.SecondOrder(2, kxx)\n", + " \n", + " def aperture(self):\n", + " return np.power(0.001, 2 - self.grid().dim)\n", + "\n", + "class IntersectionDomain(FractureDomain):\n", + " def __init__(self, g, data):\n", + " FractureDomain.__init__(self, g, data)\n", + "\n", + " def source(self, t):\n", + " assert self.grid().num_cells == 1, 'Assumes Intersection domain only has 1 cell'\n", + " f = .4 * self.grid().cell_volumes # m**3/s\n", + " return f * (t < .05)\n", + " \n", + "def set_sub_problems(gb):\n", + " gb.add_node_props(['problem'])\n", + " for g, d in gb:\n", + " if g.dim == 2:\n", + " d['problem'] = MatrixDomain(g, d)\n", + " elif g.dim == 1:\n", + " d['problem'] = FractureDomain(g, d)\n", + " elif g.dim == 0:\n", + " d['problem'] = IntersectionDomain(g, d)\n", + " else:\n", + " raise ValueError('Unkown grid-dimension %d' %g.dim)" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "We can now create the global problem, which inherits from the SlightlyCompressibleMultiDim class. The important thing to notice with this classs is the set_sub_problem() function. Here we define a problem for each of the grids in the GridBucket, which is stored in the data with the keyword 'problem'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ - "class UnitSquareInjection(SlightlyCompressibleProblem):\n", + "class UnitSquareInjection(SlightlyCompressible):\n", "\n", " def __init__(self):\n", - " nx = 11\n", - " ny = 11\n", - "\n", + " nx = 12\n", + " ny = 12\n", + " frac1 = np.array([[0, 1], [.5, .5]])\n", + " frac2 = np.array([[.5, .5], [0, 1]])\n", + " fracs = [frac1, frac2]\n", " physdims = [1, 1]\n", - "\n", - " g = structured.CartGrid([nx, ny], physdims=physdims)\n", + " g = meshing.cart_grid(fracs, [nx, ny], physdims=physdims)\n", + " g.assign_node_ordering()\n", " g.compute_geometry()\n", - " self.g = g\n", + " set_sub_problems(g)\n", " # Initialize base class\n", - " SlightlyCompressibleProblem.__init__(self)\n", - "\n", - " #--------grid function--------\n", - " \n", - " def grid(self):\n", - " return self.g\n", - " \n", - " #--------Inn/outflow terms---------------\n", - "\n", - " def source(self, t):\n", - " f = np.zeros(self.g.num_cells)\n", - " source_cell = round(self.g.num_cells / 2)\n", - " f[source_cell] = 10*self.g.cell_volumes[source_cell] # m**3/s\n", - " return f * (t < .05)\n", + " SlightlyCompressible.__init__(self, g)\n", "\n", "\n", " #--------Time stepping------------\n", @@ -103,53 +148,43 @@ " return .001\n", "\n", " def end_time(self):\n", - " return 0.1\n" + " return 0.1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We can now initialize the problem and solve" + "We now initialize the problem and solve it:" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/runar/anaconda3/envs/porepy/lib/python3.5/site-packages/scipy/sparse/linalg/dsolve/linsolve.py:102: SparseEfficiencyWarning: spsolve requires A be CSC or CSR matrix format\n", - " SparseEfficiencyWarning)\n" - ] - } - ], + "outputs": [], "source": [ "problem = UnitSquareInjection()\n", - "problem.solve()\n", - "pressures = problem.data['pressure']" + "solution = problem.solve()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "And we plot the solution" + "And we can plot the result" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n1113yV133RWwL3/605/k3HPPbbBv9uzZcs8995xW9tixYwLI4cOH6/a9/vrrcv755zc4rl69eknv\n3r1l+vTpcuTIkYBx7T7XdqxZyelCNyI9QtvaY/LWMe8Q3XPPPfTp04euXbvy4IMPsmrVqrr3XC4X\nixYtIj4+noSEBJYvX86sWbMYM2YMMTEx3HrrrcTHx7Np0yZiYmKorKxkx44dVFdX079/fwYNGgRA\nbGwsBQUFFBcX06lTJzIzM0Pu30UXXcTkyZNxuVwkJCTw29/+lscee4zU1FTi4+NZuHAha9asaTAu\nXatbt25ce+21JCYm0rlzZx588EE+/PBDx+fooYce4p133iE3N5eJEyee9v68efM466yzGDp0KOef\nfz7jxo1j4MCBJCUlcdVVV5GXlwdge/4Ali1bxrJlywL24eTJkyQlJTXYl5SUxIkTJwKWrX0/UNmU\nlBS2bt3K/v372bZtGydOnOCmm24KGNfuc1VNVHujdyhbO6TJO0R9+vSpe92vXz8OHTpU93P37t3p\n0KFD3c/79+/nqaeeIjk5uW47ePAghw4dIi0tjaeffpqFCxfSo0cPpk6dWtfWihUr2L17N+eeey6j\nRo3ib3/7W5P6V9uHa665pi7+eeedR0xMDN98881pdcvLy5k1axb9+vXjrLPO4oc//CElJSV4vd6Q\n4wMcO3aMkydPcuLECaqrq097v2fPnnWvExISTvu5Npnanb/GdOrUidLS0gb7SktL6dy5c8Cyte8H\nKtupUycyMjJwu9307NmTZ599lrfffvu09gHbz1U1kSZvW5q8Q3Tw4MG61wcOHKBXr151PxtjGpTt\n06cPDz74ICUlJXVbeXk5N954IwDTpk3jo48+Yv/+/RhjmDdvHgCDBw9m1apVfPvtt8ybN48pU6ZQ\nVlZGx44dKS8vr2vf6/Vy5MiRBjED9eHNN99s0IeKigp69+592rE99dRT7Nq1i82bN1NaWsrf//53\nwDekFqhtf7Xv33nnnfziF7/gpptuYvXq1bZ17DR2/uwMHTqUPXv2NLjS/vTTTxk6dOhpZbt06cL3\nvvc9Pv3000bLwv8eZ+158Rfsc1XNoMk7KE3eIXruuecoLCzk2LFjPP7449xwww1By86cOZPnn3+e\nzZs3IyKUlZXxxhtvcOLECXbt2sV7771HZWUlHTp0ICEhgZgY31fqr776KkeOHMHlcpGcnAz4/hw/\n55xzqKio4I033qC6uppHH32UyspK2/7eeeedPPjgg+zfvx+AI0eOsHbt2oBlT5w4QUJCAsnJyRw7\ndoxFixY1eL9nz57s2bMnaKzu3btjjKGyspJp06Yxf/589u7dy/Hjx237GIzd+WvMOeecw/Dhw1m0\naBEVFRX8+c9/5rPPPuPaa68NWP6WW27h0Ucf5bvvvmPnzp0sX76c6dOnA7B582Z27dpFTU0NR48e\n5d577+Xyyy8/bVgGsP1cVRMZICbErR3S5B2iadOm1Y3RDhw4kIceeiho2YyMDJYvX84999xDly5d\nSEtLY+XKlYDvvuL58+eTkpLC2Wefzbfffsvjjz8OwPr16xk6dCidOnXivvvuIzs7mw4dOpCUlMSy\nZcu4/fbb6d27Nx07diQ1NdW2v/fddx9ZWVmMGzeOzp07k5mZyebNmwOW/elPf8qpU6dISUkhMzOz\nwW1ytW2tWbOGLl26cO+9955WPzExkYceeohPPvmE5ORktm7dyoIFCwImuVDYnT/w/WK68847g9bP\nzs4mNzeXLl26MH/+fNasWVN3m+Af/vCHBlfWixYtYtCgQfTr14/LLruMOXPm1B3/nj17mDBhAp07\nd+b8888nPj6+wXcd9dl9rqqJdNjElgn2J2AQjgqfKfr378+LL77Ij370o0h3Ram2xH68rREZiUZy\nh4QY6BO2iUhGc+K1Ne30d5ZSKurpYwVt6alRSkUnTd629NSEYN++fZHuglLtUzv9MjIUmryVUtFJ\nr7xt6alRSkUnTd629NQopaKTAeIj3YnopclbKRWd9Mrblk7SCdH69esZMmQIaWlpLF68+LT3Kysr\nueGGG0hLS2PMmDHN+pKzsVhLliwhPT2dYcOGceWVV9bNomyNWLXWrFmDMYbc3Nwmxwo13urVq0lP\nT2fo0KFMmzat1WIdOHCAsWPHMmLECIYNG8a6deuaHGvGjBn06NGD888/P+D7IsK9995LWloaw4YN\n41//+leTY7UbOknHnsPHELZLHo9HBg4cKF999ZVUVlbKsGHDZPv27Q3KPPfcczJr1iwREVm1apVc\nf/31rRbrvffek7KyMhERWbZsWavGEhEpLS2VSy+9VMaMGSNbt25tUqxQ4+3evVuGDx8ux44dExGR\nb775ptVizZw5U5YtWyYiItu3b5d+/fo1KZaIyIcffijbtm2ToUOHBnz/jTfekAkTJkhNTY18/PHH\n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\nkCZvpZRqg/4fuLdZW2lAqUkAAAAASUVORK5CYII=\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAVwAAADxCAYAAACH4w+oAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xt4FPXZ+P/3nd0cCMQEw0EhyMFQNFAVjYClrSAKSGuw\nFRHlsfKIVlQufWy/Kha1YMXSbyv2V0GtSItWJSrfKnkqoKJor1qMhIItYCGRYzgoIQIhkNPu/ftj\nN2myJJuZEDab5X5d11zu7twz85mZ9Wby2c9BVBVjjDGnXlxbF8AYY04XlnCNMSZCLOEaY0yEWMI1\nxpgIsYRrjDERYgnXGGMixBKuMea0ICJjRWSLiBSJyIxG1ieKyGvB9fki0idk/TkiclRE/o/TfYay\nhGuMiXki4gEWAFcDWcCNIpIVEjYV+FpVM4GngF+FrH8KWOFynw1YwjXGnA6GAEWquk1Vq4BcYHxI\nzHjgxeDrpcAoEREAEbkW2AZscrnPBrwuC23d0owxTsnJbJwposccxu4LJMKKeh89r6rP13vfE9hd\n730xMDRkN3UxqlojIoeBdBE5DjwIXAX8n8biw+yzAbcJ1xhjIuIYcIfD2FlQoarZYUIaS/6hD5BN\nxcwGnlLVo8EHXjf7bMASrjEmKgmtmqCKgV713mcAe5uIKRYRL5AKlBJ4ap0gIv8XSAP8IlIBrHOw\nzwYs4RpjolIc0KH1drcW6C8ifYE9wCTgppCYPOAWYA0wAfhAA6N7fac2QERmAUdVdX4wKTe3zwYs\n4RpjopIA8a20r2Cd7HTgHcAD/EFVN4nIY0CBquYBi4A/iUgRgSfbSS3ZZ7htxOXwjPajmTHGqZP6\n0ay3iDbbsDXoLljXTB1uVLAnXGNMVGrNJ9xoYQnXGBOVWvlHs6gQa+djjIkR9oRrjDER0sqtFKKC\nJVxjTFSyJ1xjjImgWEtQsXY+xpgYYU+4xhgTIdZKwRhjIsR+NDPGmAixKgVjjIkQq1IwxpgIsSdc\nY4yJEHvCNcaYCLEnXGOMiRAh9lop2Ky95pR45ZVXGD16dFsXw7RjAsR7nS3thSVc06wRI0bwwgsv\nNLl+x44diAg1NTV1n02ePJl33303EsVrtDwjR44kOTmZ8847j1WrVjUZW1lZya233soZZ5zBWWed\nxbx58xrsR0To1KlT3fKLX/wiEqdgABHwep0t7UU7Kmr7UFNTg7eNvwHRUIa2dOONN3LZZZexfPly\nli9fzoQJEygsLKRr164nxM6aNYvCwkJ27tzJ/v37GTlyJFlZWYwdO7Yu5tChQ6f19WwrIhDvaetS\ntDJVdbOclnr37q1PPPGEnn/++ZqWlqZTpkzR48ePq6rq6tWrtWfPnjp37lzt3r27/td//Zeqqv7v\n//6vXnjhhZqamqqXXXaZfvbZZ3X7mzt3rvbo0UM7deqk3/jGN3TVqlWqqpqfn6+XXHKJpqSkaLdu\n3fS+++5rcIzQMr333nuqqvrzn/9cr7vuOp08ebKmpKTowoUL1efz6S9/+Uvt16+fnnnmmXr99dfr\nwYMHGz2/0tJS/d73vqddunTRtLQ0/d73vqe7d+9WVdWf/exnGhcXp4mJidqxY0e9++67T9i+V69e\nCmhcXJx27NhR33//fe3Ro4cOHjy4LgbQBQsWaGZmpnbq1EkffvhhLSoq0mHDhmlKSopef/31WllZ\nWRcf7vqFs2XLFk1ISNAjR47Uffbtb39bn3322Ubje/Tooe+8807d+4cfflhvuOEGVVXdvn27Alpd\nXe3o2E3d19OY2/zSYLnYg1alOlsIzEsWdn/AWGALUATMaGR9IvBacH0+0Cf4+RBgQ3D5DPhBvW12\nAP8Krmu+DC4vwmmpd+/eOnDgQN21a5cePHhQv/Wtb+nMmTNVNZAMPR6PPvDAA1pRUaHHjh3TdevW\nadeuXfWTTz7RmpoaXbx4sfbu3VsrKir03//+t2ZkZOiePXtUNfA/dVFRkaqqDhs2TF966SVVVS0r\nK9M1a9bUHaO5hOv1evXNN99Un8+nx44d06eeekqHDh2qu3fv1oqKCv3xj3+skyZNavT8SkpKdOnS\npVpeXq5HjhzRCRMm6Pjx4+vWX3755bpw4cImr09tYnrooYf0pz/9qd5111163XXX6fDhw+tiAL3m\nmmv08OHDunHjRk1ISNArrrhCv/jiCz106JCef/75unjxYlXVsNdPVfXOO+/UO++8s9Gy/PnPf9bz\nzjuvwWd33323Tp8+/YTY0tJSBXT//v11n73xxhs6aNCgBufVo0cP7dmzp06ZMkUPHDjQ6HHD3dfT\n2Ekl3Eu8qHZztjSX7AhM8vgF0A9ICCbOrJCYu4Dngq8nAa8FXycD3uDrs4Gv6r3fAXRxek5Wh+vQ\n9OnT6dWrF2eeeSYzZ85kyZIldevi4uKYPXs2iYmJdOjQgYULF3LHHXcwdOhQPB4Pt9xyC4mJiXzy\nySd4PB4qKyvZvHkz1dXV9OnTh3PPPReA+Ph4ioqKKCkpoVOnTgwbNsxx+S677DKuvfZa4uLi6NCh\nA7///e+ZM2cOGRkZJCYmMmvWLJYuXdqgnrVWeno61113HcnJyaSkpDBz5kw++ugj19fo4Ycf5r33\n3qOgoIBx48adsP7BBx/kjDPOYODAgQwaNIjRo0fTr18/UlNTufrqq1m/fj1A2OsH8Mwzz/DMM880\nWoajR4+Smpra4LPU1FTKysoaja1d31hsly5dWLt2LTt37mTdunWUlZUxefLkRo8b7r6aFqptiOtk\nad4QoEhVt6lqFZALjA+JGQ+8GHy9FBglIqKqx1S19n+cJE5iMl1LuA716tWr7nXv3r3Zu3dv3fuu\nXbuSlJRU937nzp08+eSTpKWl1S27d+9m7969ZGZm8tvf/pZZs2bRrVs3Jk2aVLevRYsWsXXrVs47\n7zwuvfRS/vKXv7SofLVl+MEPflB3/PPPPx+Px8OXX355wrbHjh3jjjvuoHfv3pxxxhl897vf5dCh\nQ/h8PsfHBygtLeXo0aOUlZVRXV19wvru3bvXve7QocMJ72sTYLjr15xOnTpx5MiRBp8dOXKElJSU\nRmNr1zcW26lTJ7Kzs/F6vXTv3p358+fz7rvvnrB/IOx9NS3Uugm3J7C73vvi4GeNxgQT7GEgHUBE\nhorIJgLVB9PqJWAF3hWRdSLy4+YKYQnXod27/3Ovdu3aRY8ePereizScDbpXr17MnDmTQ4cO1S3H\njh3jxhtvBOCmm27ib3/7Gzt37kREePDBBwHo378/S5Ys4auvvuLBBx9kwoQJlJeX07FjR44dO1a3\nf5/Px4EDBxocs7EyrFixokEZKioq6Nkz9DsGTz75JFu2bCE/P58jR47w17/+FaD2z6wT9h2qdv20\nadP4xS9+weTJk3n99dfDbhNOc9cvnIEDB7Jt27YGT7SfffYZAwcOPCG2c+fOnH322Xz22WfNxsJ/\nzrP2uoRq6r6ak+A84XYRkYJ6S2jya+xLHHojm4xR1XxVHQhcCjwkIrVPWMNV9WLgauBuEfluuNOx\nhOvQggULKC4uprS0lCeeeIIbbrihydjbb7+d5557jvz8fFSV8vJy3n77bcrKytiyZQsffPABlZWV\nJCUl0aFDBzyewE+xL7/8MgcOHCAuLo60tDQg8KfqN77xDSoqKnj77beprq7m8ccfp7KyMmx5p02b\nxsyZM9m5cycABw4cYNmyZY3GlpWV0aFDB9LS0igtLWX27NkN1nfv3p1t27Y1eayuXbsiIlRWVnLT\nTTcxY8YMtm/fzuHDh8OWsSnhrl9zvvGNb3DRRRcxe/ZsKioqePPNN/nnP//Jdddd12j8j370Ix5/\n/HG+/vpr/v3vf7Nw4UKmTJkCQH5+Plu2bMHv93Pw4EHuueceRowYcUKVBRD2vpoWEgI1r04WKFHV\n7HrL8yF7Kwbq/xmYAYT+CVIXIyJeIBUorR+gqp8D5cCg4Pu9wf9+BbxJoOqiSZZwHbrpppvq6hz7\n9evHww8/3GRsdnY2CxcuZPr06XTu3JnMzEwWL14MBNp9zpgxgy5dunDWWWfx1Vdf8cQTTwCwcuVK\nBg4cSKdOnbj33nvJzc0lKSmJ1NRUnnnmGW677TZ69uxJx44dycjICFvee++9l5ycHEaPHk1KSgrD\nhg0jPz+/0dj/+Z//4fjx43Tp0oVhw4Y1aBJVu6+lS5fSuXNn7rnnnhO2T05O5uGHH2bDhg2kpaWx\ndu1aHn300UYTkxPhrh8E/jGZNm1ak9vn5uZSUFBA586dmTFjBkuXLq1rEvbKK680eIKdPXs25557\nLr179+byyy/n/vvvrzv/bdu2MXbsWFJSUhg0aBCJiYkN6u7rC3dfTQu1bpXCWqC/iPQVkQQCP4rl\nhcTkAbcEX08APlBVDW7jBRCR3sAAYIeIdBSRlODnHYHRwMawp9TUn0dNaHFlcXvWp08fXnjhBa68\n8sq2Loox7Un4uqhmZCeLFgxweKANrFPV7LAxIuOA3xJ4Jv6Dqs4RkccItHDIC1YT/AkYTODJdpKq\nbhORm4EZQDXgBx5T1bdEpB+Bp1oIpP1XVXVOuDJYa25jTHRq5eHCVHU5sDzks0frva4Arm9kuz8R\nSMShn28DLnRTBku4xpjoFIPjM8bY6ZwaO3bsaOsiGHN6irHfHS3hGmOikz3hGmNMhFjCNcaYCBEC\nw8nEEEu4xpjoFINPuNbxwaGVK1cyYMAAMjMzmTt37gnrKysrueGGG8jMzGTo0KEn9UNbc8eaN28e\nWVlZXHDBBYwaNaquN9mpOFatpUuXIiIUFBS0+FhOj/f666+TlZXFwIEDuemmm07ZsXbt2sXIkSMZ\nPHgwF1xwAcuXL29kL87ceuutdOvWjUGDBjW6XlW55557yMzM5IILLuAf//hHi4912mjdjg/RweWQ\naaelmpoa7devn37xxRdaWVmpF1xwgW7atKlBzIIFC/SOO+5QVdUlS5boxIkTT9mxPvjgAy0vL1dV\n1WeeeeaUHktV9ciRI/qd73xHhw4dqmvXrm3RsZweb+vWrXrRRRdpaWmpqqp++eWXp+xYt99+uz7z\nzDOqqrpp0ybt3bt3i46lqvrRRx/punXrdODAgY2uf/vtt3Xs2LHq9/t1zZo1OmTIkBYfqx05ueEZ\nO6N6vbMFB2PRRsNiT7ghqqurqampaTBAyaeffkpmZib9+vUjISGBSZMmnTAuwbJly7jllkCvwAkT\nJvD+++83OchJOE6OVTt9DMCwYcMoLi52fRynxwJ45JFHeOCBBxqMiHaqjrdw4ULuvvtuOnfuDEC3\nbt1O2bFEpG7kr8OHDzcYkMit7373u5x55plNrl+2bBk/+tGPEBGGDRvGoUOH2LdvX4uPd1qIwSdc\nS7ghfD7fCUl3z549DYY/zMjIYM+ePQ22qx/j9XpJTU3l4MGDro/v5Fj1LVq0iKuvvtr1cZwea/36\n9ezevZvvf//7LTqG2+Nt3bqVrVu3Mnz4cIYNG8bKlStP2bFmzZrFyy+/TEZGBuPGjePpp59u0bFa\nqzwmRO2PZk6WdsISbgi/3w8EOjtUVVXVHw2+gdAhC53EOOFmPy+//DIFBQXcf//9ro/j5Fh+v5/7\n7ruPJ598skX7d3s8CMzHVlhYyIcffsiSJUu47bbbOHTo0Ck51pIlS5gyZQrFxcUsX76cm2++ue7+\nt7bW+n6cVuwJN/YdPHiQdevWsXv3bnbt2kVlZSU9evRoMB5ucXHxCX9+ZmRk1MXU1NRw+PDhsH9i\nNqX+fpo6FsCqVauYM2cOeXl5JCa27J/45o5VVlbGxo0bGTFiBH369OGTTz4hJyenxT+cOTm3jIwM\nxo8fT3x8PH379mXAgAEUFhaekmMtWrSIiRMnAoEZMyoqKigpKXF9rNYqjwlhCTf2de3ald69e1Ne\nXl736396ejqFhYVs376dqqoqcnNzycnJabBdTk4OL74YmJ1j6dKlXHHFFS16grn00kubPdb69eu5\n4447yMvLa3Edp5NjpaamUlJSwo4dO9ixYwfDhg0jLy+P7OywgzKd1Llde+21rF69GoCSkhK2bt1K\nv379TsmxzjnnHN5//30APv/8cyoqKhqd2bc15OTk8NJLL6GqfPLJJ6SmpnL22WefkmPFlBhLuO2o\nqJHTtWtXkpOTOX78OMXFxezbt48nn3yS0aNH4/f7ufXWWxk4cCDnnnsuTz31FDk5OUydOpWbb76Z\nzMxMzjzzTHJzc1t0bK/Xy/z58xkzZgw+n6/uWI8++ih/+ctf+Mc//sH999/P0aNHuf76wMBG55xz\nDnl5oUN7ts6xWpOT440ZM4Z3332XrKwsPB4Pv/71r0lPT2/VY2VnZ/PMM88wb948br/9dp566ilE\nhMWLF7f4z/wbb7yRDz/8kJKSEjIyMpg9e3bdNENvvfUWK1asYPny5WRmZpKcnMwf//jHFh3ntFI7\nAHkMsfFwQ1RUVACwZs0aVJWUlBQOHDhAUlISvXr14uJLhnK07GvnO5R40BPn92pSXDz4XcR746HG\nebzEe9Bq53OVeeOFmmrnt/3nM2F22BFBG4qL9+B3UR635+v6erq9Xy3YJiWlM0eOlDYf2P6d3Hi4\nZ4kW/JfDAz3Z/Hi40cCecMMQEb75zW/y0UcfcezYMXbt2hVItn1d/LuzXeAyF/FrBO5zEf+UwMfO\n43W4MFobn2qnMe/KeP6mlziOH3pkHVc97jz+27LOdXncnC/DW3A93dwvCNwzF9+Jsu32Y5kj1rX3\n9CMiJCQk4PF4GkzkaIw5xaxr7+nL4/HQsWPHti6GiRGh88aZRsRgK4V2VNS2Z+0mTWtpaYeO04o9\n4RpjWpsNfBOG82nSmyUiY0Vki4gUiciMRtYnishrwfX5ItIn+PkQEdkQXD4TkR843WcoS7jGtLEp\nU6aEfeJdsWIFhYWFFBYW8vzzz3PnnXdGsHRtqBWrFETEAywArgaygBtFJCskbCrwtapmAk8Bvwp+\nvhHIVtWLgLHA70XE63CfDVjCNaaN2cA3TWjdsRSGAEWquk1Vq4BcYHxIzHjgxeDrpcAoERFVPaaq\nNcHPk/hP81gn+2zAEq4xUe60HfjG3RNuFxEpqLf8OGRvPYHd9d4XBz9rNCaYYA8D6QAiMlRENgH/\nAqYF1zvZZwMxViVtTOw5bQe+cfejWUkzHR8au2ChF7bJGFXNBwaKyPnAiyKywuE+G7CE65bEBzoz\nOOYNNIx3Ks4baHzvlMcbaNzvkHg9gc4DjncvfFvWOY5f/TqMnOg83m153J6v6+spLu8XAF533wmJ\nd7X303bgm9bt2lsM9Kr3PgPY20RMsYh4gVSgQZdAVf1cRMqBQQ732YAlXLe02n3PsQku4pcKzHUR\nP0Ngp/Nupdo7nin6rOP4xXIn29T5ICt9i/a5iu8n+1yXx8350jve/fV0c78gcM/cfidcyMnJYf78\n+UyaNIn8/PzTZ+Cb1m0WthboLyJ9gT3AJCB0/qY84BZgDTAB+EBVNbjNblWtEZHewABgB3DIwT4b\nsIRrTBsLN/DNtGnTGDdu3Ok58I0Q+ImqFQST5XTgHQLPzX9Q1U0i8hiB6XnygEXAn0SkiMCT7aTg\n5t8GZohINeAH7lLVEoDG9hmuHJZwjWljS5YsCbteRFiwYEGEShNFWnm0MFVdDiwP+ezReq8rgOsb\n2e5PwJ+c7jMcS7jGmOgUgz3NYux0jDExJcYyVIydjjEmZsTgAOSWcI0x0cmqFIwxJkJsAHJjjIkQ\ne8I1SLy7huviDTSMdyrOG2h875THG2jc77g4cYHOAy5230+cD5Ty4W9hxP84j3dbHrzuztf19XR7\nv2q3cfWdcNfT7LRlCTf2VVZW4vOFmdRQq93PkeW2p5PLObsG6aeOwzfKEObrVMfx02URWui8OHyO\nq3jp73ddHrfn63oONDf3CwL3zO13wjTPEm7sq6qq4vPPP+fYsWMkJsZYBZIx7U2MtVKw4RlDpKSk\nMGTIEBITE6msrKSgoICamprmNzTGtK4YnNPMEm4TPB4PycnJDBgwgOrqasrLy+v6txtjIqB1ByCP\nCu3o34a2kZKSQocOHfD7/VRVVbV1cYw5fcRgHa494ToUFxdHUlIrDV1kTns2TboDVqVgjGkNoZNG\nrly5kgEDBpCZmcncuXNPiN+1axcjR45k8ODBXHDBBSxf7niAqvbLEq4xprX5fD7uvvtuVqxYwebN\nm1myZAmbN29uEPP4448zceJE1q9fT25uLnfddVcblTay1ONsaS/a0b8NxsSmTz/9lMzMTPr16wfA\npEmTWLZsGVlZ/5lxW0Q4cuQIAIcPHz4tptjROKiKsVo8S7huxcW7a7jekp5jbubs8noCjfsdF0eY\nLovc7B7p77w4H06HETnO492Wx+35tmgONDf3q3YbV9+Jhj3NGpuVNz8/v0HMrFmzGD16NE8//TTl\n5eWsWrXKXRnbIRWo8Tj9I9x/SsvSWizhuuWvdt9zyeUcXG57Uv1MH3Ec/4T8gp3azXlx5Cv0Ccfh\nUIGrePmZui6P2/N13TPNzf2CQFdjt9+JepzMyrtkyRKmTJnCT3/6U9asWcPNN9/Mxo0biYuL3VpB\nFcHndZqi2kcLoti9W8a0E05m5V20aBETJ04E4LLLLqOiooKSkpKIlrMt+DweR4sTIjJWRLaISJGI\nzGhkfaKIvBZcny8ifYKfXyUi60TkX8H/XlFvmw+D+9wQXMI+PVjCNaaNXXrppRQWFrJ9+3aqqqrI\nzc0lJ6dhvcw555zD+++/D8Dnn39ORUUFXbt2bYviRowi+PA4WpojIh5gAXA1kAXcKCJZIWFTga9V\nNRN4CvhV8PMS4BpV/SaBWX1D5zebrKoXBZevwpXDEq4xbczr9TJ//nzGjBnD+eefz8SJExk4cCCP\nPvooeXl5ADz55JMsXLiQCy+8kBtvvJHFixefUO0QaxShBo+jxYEhQJGqblPVKiAXGB8SMx54Mfh6\nKTBKRERV16vq3uDnm4AkEWlR/zarwzUmCowbN45x48Y1+Oyxxx6re52VlcXHH38c6WK1KUWoct5v\nt4uIFNR7/7yqPl/vfU9gd733xcDQkH3UxQSnVT8MpBN4wq11HbBeVSvrffZHEfEB/w94XBurlA+y\nhGuMiUq1VQoOlahqdpj1jf05EJoYw8aIyEAC1Qyj662frKp7RCSFQMK9GXipqUJYlYIxJmq1Vh0u\ngSfaXvXeZwB7m4oRES+QCpQG32cAbwI/UtUvajdQ1T3B/5YBrxKoumiSJVxjTFRq5TrctUB/Eekr\nIgnAJCAvJCaPwI9iABOAD1RVRSQNeBt4SFXr6nVExCsiXYKv44HvAxvDFcKqFIwxUSlQpdA6KSpY\nJzsdeIfAsOZ/UNVNIvIYUKCqecAi4E8iUkTgyXZScPPpQCbwiIjUNgIfDZQD7wSTrQdYBSwMVw4J\nU7/baLndBLdHFRUVAKxZswaAb33rW/z973+vW3/F6DFQ46JhvNcLbgYw93qgJswUPyHivHH4a5z3\nsvF4weemOHHgYvd8OApGvO9i/y4vj9vzdXs9XRcIWnBR49Hq9tFQ/ySdVDOKrOwO+kpBX0exF8vn\n65qpw40K9oTrVk01o3WZ4/B3ZTxT9FnH8YvlTtdzfLnuOfac43BkGrjYPXjdxctXuCyP3/X5ur2e\nbu4XBO6Z2++EaZ6C0+qCdsMSrjEmSrVelUK0iK2zMcbEDJfNwtoFS7jGmKhlCdcYYyLAnnBPA0eO\nHGHfvn1UVVXh8Xhs4khj2ogiVLanKXkdsIQbokOHDqSnp7N//36qq6vZsGEDR48eRURieuxRY6JN\nLD7hWgYJER8fT5cuXUhISCApKYkhQ4bQqVMnkpOTSUhIaOvimRhhs/Y2rzWHZ4wW1vEhRHMdH64c\ncxX+aucN6cUbh7poqB/nFfw1zi/zqe7I4AXcdAP4sA+M2HEKy+O6o4S76+n2fgW28aBuOqvEe/BV\nNTyJlStXcu+99+Lz+bjtttuYMeOE8bF5/fXXmTVrFiLChRdeyKuvvuqqnG3gpDo+ZGan6v8tGO4o\n9jpZYR0fYpG/2sff9BLH8d+WdWzTsx3H95N9aKHz8kh/t1PauO+Y8LjzcC73u4t/2N+CjhIup/Bx\ndz39ru4XBO6Z2+9EfbWz9r733ntkZGRw6aWXkpOT02ASycLCQn75y1/y8ccf07lzZ776Kuw41zGh\nNbv2RgurUjCmjdWftTchIaFu1t76Fi5cyN13303nzp0B6NbNTfe/9ikWqxQs4RrTxhqbtXfPnj0N\nYrZu3crWrVsZPnw4w4YNY+XKlZEuZsQFWikkOFrai9h6XjemHXIya29NTQ2FhYV8+OGHFBcX853v\nfIeNGzeSlpYWqWJGnFUpGGNanZNZezMyMhg/fjzx8fH07duXAQMGUFjoonK6nbIqBWNMq3Iya++1\n117L6tWrASgpKWHr1q3069evLYobMbFYhxtbz+vGtEP1Z+31+XzceuutdbP2Zmdnk5OTw5gxY3j3\n3XfJysrC4/Hw61//mvT09LYu+ikVix0fLOEaEwWam7VXRJg3bx7z5s0Lu59HHnmELl26cO+99wIw\nc+ZMunfvzj333NP6hT7FYrFrr1UpGBNDpk6dyosvvgiA3+8nNzeXyZMnt3GpWsaqFAzeeDmh4Xo4\nHm+gYbzj/XsCnRkcx8cFOjM4jifQecCpOOBh5+EMr3AX77Y8rs/X7fV0eb8gcI/dfCe88SfVASus\nPn36kJ6ezvr16/nyyy8ZPHhwu656aM1kKiJjgf+PwPxjL6jq3JD1iQSmOL8EOAjcoKo7ROQqYC6Q\nAFQB96vqB8FtLgEWAx2A5cC9Gqb7riVcl2qq1XqahTEiyWVPM051TzNcX89I9zRrbbfddhuLFy9m\n//793Hrrraf0WKdS7ay9rUFEPMAC4CoC06GvFZE8Vd1cL2wq8LWqZorIJOBXwA1ACXCNqu4VkUEE\nJqLsGdzmWeDHwCcEEu5YYEVT5bAqBWNizA9+8ANWrlzJ2rVrGTNmTFsXp8Vq2+E6WRwYAhSp6jZV\nrQJygdDJ5cYDLwZfLwVGiYio6npV3Rv8fBOQJCKJInI2cIaqrgk+1b4EXBuuEPaEa0yMSUhIYOTI\nkaSlpeHrCrCyAAAXbklEQVTxtJ/6zca4qFLoIiIF9d4/r6rP13vfE9hd730xMDRkH3UxwWnVDwPp\nBJ5wa10HrFfVShHpGdxP/X32JAxLuMbEGL/fzyeffMIbb7zR1kU5KYpQ5bzbbkkzo4U1VnEeWtca\nNkZEBhKoZhjtYp8NWJWCMTFk8+bNZGZmMmrUKPr3d/FrYRSqrcN1sjhQDPSq9z4D2NtUjIh4gVSg\nNPg+A3gT+JGqflEvPqOZfTZgT7jGxJCsrCy2bdvW1sVoFa08lsJaoL+I9AX2AJOAm0Ji8oBbgDXA\nBOADVVURSQPeBh5S1Y/ryqe6T0TKRGQYkA/8CHg6XCHsCdcYE7Vaqx2uqtYA0wm0MPgceF1VN4nI\nYyJS2496EZAuIkXAT4DaUeCnA5nAIyKyIbjUtq25E3gBKAK+IEwLBbAnXGNMlGrtrr2qupxA0636\nnz1a73UFcH0j2z1OE60dVbUAGOS0DJZwjTFRqTXb4UYLS7guWU+z8IanWE+zZo9xCnuaxZJAK4XY\nGkvBEm4j/H5/3bJt2zaOHz+O3+9HVampVkbrsuZ3EvSujGeKPus4frHcyXyd6jh+uixip4uuWr3l\nK/Q5x+HINHc9wcgAPeBi/1/hujxuz9ft9XRzvyBwz9x+J8aOHXtazNpwMmJxtDD70SzEwYMHyc/P\np6KiAp/PR1JSEgkJCSQnJ9OpU6e2Lp6JEaHJduXKlQwYMIDMzEzmzp3bxFawdOlSRISCgoImY2JJ\nrA1eYwk3RHp6OpdddhnJyckkJibSo0cPPB7PCVOeGNNaamftXbFiBZs3b2bJkiVs3rz5hLiysjJ+\n97vfMXRoaAep2NTK7XCjgiVcY9qYk1l7ITDW7QMPPEBSUlIblDLyWnkshahgCdeYNuZk1t7169ez\ne/duvv/970e6eG2mtmuvk6W9aD//NBgTo5qbtdfv93PfffexePHiCJaq7VmzMGNMq2tu1t6ysjI2\nbtzIiBEjANi/fz85OTnk5eWRnR1uvJb2rz1VFzgRW2djTDtUf9benj17kpuby6uvvlq3PjU1lZKS\n/4wQOGLECH7zm9/EfLK1ZmHGmFZXf9be888/n4kTJ9bN2puXl9fWxWszsTinmYSZfqcxroLbo4qK\nCgDWrFkDwLe+9S3+/ve/160fNeYqtNrneH/ijUNr/I7j47yCv8b5ZfZ4wVfjOBxvHLgoDl7Axe75\n8FoY8dYpLI8XalwUyO31dHu/Att40BoX34l4D/4qN1e13TqptpQp2d/QiwoWOIr9m4xe18x4uFHB\nqhRc0moffOz8f2AdLrCz2nG8v3c8g/RTx/EbZQg/00ccxz8hv3DfM83FHGJUgA5xHi4/c99zzO35\nur2ebu4XgPaOd/+dMM3yE2dde40xJlLaU3WBE5ZwjTFRKRZ/NLOEa4yJSgrWDtcYYyKjVafYiQqx\ndTbGmJgRi1UK1g7XGBOVFKGSBEeLEyIyVkS2iEiRiMxoZH2iiLwWXJ8vIn2Cn6eLyGoROSoi80O2\n+TC4z9C5zhplT7jGmKjUmrP2iogHWABcRWB687Uikqeq9cfBnAp8raqZIjIJ+BVwA1ABPEJg7rLG\n5i+bHJzbrFn2hGuMiVqt2NNsCFCkqttUtQrIBcaHxIwHXgy+XgqMEhFR1XJV/RuBxHtS7AnXLW88\nuGm47vVC73gX8Z5A43uH4rxxPCG/cBzv8QY6Dzgujss5xD58CEY85jze67I8bs/X7fV0fb8gcFFd\nfSdc7v805bIOt4uI1H/KfF5Vn6/3viewu977YiB0JPe6GFWtEZHDQDpQQnh/FBEf8P+AxzVM911L\nuG7VVMN9Lno4PyUw10X8DHHVa8k/XFz3pHI7x5cWOg6Hz3EVL/1xXR7XPcdcXE+Gu7xfELhnbr8T\nplmK4PM7TrglzXTtbeyih940JzGhJqvqHhFJIZBwbwZeairYEq4xJiqpX6isaLWuvcVAr3rvM4C9\nTcQUi4gXSAVKw5ZRdU/wv2Ui8iqBqosmE67V4bpQ42bUFGPMSVEVfDUeR4sDa4H+ItJXRBKASUDo\nUGx5wC3B1xOAD8JVD4iIV0S6BF/HA98HNoYrhCXcZvj9fqqrqykvL6e62t2gJsY0ZezYsQ3eNzdr\n77x588jKyuKCCy5g1KhR7Ny5M1JFbTtKqyVcVa0BpgPvAJ8Dr6vqJhF5TERygmGLgHQRKQJ+AtQ1\nHRORHcA8YIqIFItIFpAIvCMi/wQ2AHuAheHKYVUKTVBVqqurWbNmDT6fj+TkZJu517Sa+tOk187a\n+95775GRkcGll15KTk4OWVlZdTGDBw+moKCA5ORknn32WR544AFee+21tih6xKgKNdWt1/FBVZcD\ny0M+e7Te6wrg+ia27dPEbi9xUwZ7wg1RXV3NF198QXl5OarKkCFDSEpKsmRrThkns/aOHDmS5ORk\nAIYNG0ZxcXFbFDXCBL/P62hpLyzhhigvLychIYGOHTuSmJhIfLw14TGnlpNZe+tbtGgRV199dSSK\n1rYUqPE4W9qJ9vNPQ4SkpaWRlpZ2mjxBmGjQ3Ky99b388ssUFBTw0UcfnepitT2/QEVspajYOptI\niIt3144yzhtop+mU60b0bjtKCNNlkZvdI/2dF+fD52FETvNxLS2P644Mbq+n2/tVu42r70TDv5qa\nm7W31qpVq5gzZw4fffQRiYmxNRNCk2KsYZAlXLf81XCZi0buawQmuIhf2oKOEi6n8JmizzqOXyx3\nsk3Pdhzft2ifq/h+ss91eVxNgdM73v31dHO/IHDP3H4n6mlu1l6A9evXc8cdd7By5Uq6dXM+JVG7\nFhgQN6ZYwjWmjdWftdfn83HrrbfWzdqbnZ1NTk4O999/P0ePHuX66wM/op9zzjmxP6OvJVxjzKkw\nbtw4xo0b1+Czxx77z6AUq1atinSR2p4CMdb03RKuMSY6KVDZ1oVoXZZwjTHRyaoUjDEmQizhGmNM\nhFjCNcaYCLGEa4wxEWQJ9zQn8Sc0XA8f7w00jHeqJT3TXEwJI964QOcBF7vvJ/scx3/wFlzR33m8\n2/K4ngLH7fV0e79qt3H1nbDxORzx0wqziEUXS7huaTX0ddGraHsLeiG5na7FxRQyOlwYrcuaDwx6\nV8bzN3U+At3QI+tcxX9b1rkuj+spc9xeTzf3CwL3zO13wjTPqhSMMSZCLOEaY0yEWMI1xpgIirGE\nawOQG2OiU+0TrpPFAREZKyJbRKRIRGY0sj5RRF4Lrs8XkT7Bz9NFZLWIHBWR+SHbXCIi/wpu8ztp\nZmoYS7jGmOjkB447XJohIh5gAXA1kAXcGJwIsr6pwNeqmgk8Bfwq+HkF8AjwfxrZ9bPAj4H+wWVs\nIzF1LOE6pKocO3asrYthYoTbWXsrKyu54YYbyMzMZOjQoezYsSNCJW1DCvgcLs0bAhSp6jZVrQJy\ngfEhMeOBF4OvlwKjRERUtVxV/0ZIIzURORs4Q1XXBKdTfwm4NlwhLOE64PP5KC8vt/nNTKtpbNbe\nFStWsHnzZpYsWcLmzZsbxC9atIjOnTtTVFTEfffdx4MPPhjpIrcN51UKXUSkoN7y45A99QR213tf\nHPys0ZjgtOqHgfQwpesZ3E+4fTZgCbcZ+/bt4/jx4yQnJ1vCNaeEk1l7ly1bxi233ALAhAkTeP/9\n9xudCy2muKvDLVHV7HrL8yF7a6xuNfQCOok5mXhrpRBORUUF+/fvp2PHjogIcXFxdEg+g+NuGq67\n7Znmds40T7yrObsk3hPoPOB498K3ZZ3j+J/PhNlznMe7LQ9ed+fr+nq6vV+127j8TgwaNIiMjAxW\nrlzZ6Ky9+fn5DTapH+P1eklNTeXgwYN06dLFXVnbk9ZtFlYM9Kr3PgPY20RMsYh4gVSgtJl9ZjSz\nzwYs4TaisrKS8vJyvF4vF110EWvWrCEuLo6amhpef+0VPB5PxCbx8/v9dU/YzfwA2mqqqqoQkRY/\n0b9/hfttysvL6dixY4uO1xJVVVX4/X6SkpIicrza+5iYmMhDDz3Ezp07qaioIDs7m5qaGrKzsxvE\nh95rNzP7xozW7dq7FugvIn2BPcAk4KaQmDzgFmANMAH4QMP8GaGq+0SkTESGAfnAj4CnwxXCqhRC\nHD58mHXr1pGYmEhiYiKqSlxcHNXV1Rw7doz4+PiIJVtVpaKigqSkpIj+z+Xz+fB4PBE7HkBcXBx+\nvz9ix0tISMDv91NTE5mGnnFxcSQnJ1NVVcWcOXPo06cP1dXVHDhwgIqKCt5666262MZm7a0/s29N\nTQ2HDx/mzDPPjEjZ21QrNQsL1slOB94BPgdeV9VNIvKYiNTOM70ISBeRIuAnQF3TMRHZAcwDpohI\ncb0WDncCLwBFwBfAinDlEJf1QDFeaQSHDh0CArOkxsXF4fP58Pl8dYkvkomosjIwv0ikp8SO9NMm\nBJ44IZAII6Ut/nqo/UdUREhISOAnP/kJBw4coLS0lGHDhvHee+9x6aWX8uqrrzJw4MC67RYsWMC/\n/vUvnnvuOXJzc/nzn//M66+/HpEyn4STuqjSLVuZUOAs+FlZp6rZzQe2LatSCJGUlISqoqpcfPHF\nfPnll+zevZuhQ4fSoUOHiJXj8OHDfPHFFwwePDiiT7fl5eVs376dQYMGReyYAGVlZezevZusrNCm\nkadWSUkJe/fu5Zvf/GZEk+7OnTs5dOgQq1ev5tVXX+Xpp59m165dnH/++Y3O2jt16lRuvvlmMjMz\nOfPMM8nNzY1IWdtUDE4iaU+4IY4dO0ZVVRUbNmygurqaysrKiP9JD4Gn2/j4eOLiIlvr4/P5UFW8\n3sj/W1xZWRnxp/na47bFta6pqcHn8/Hzn/+cQ4cOsWfPHs4991x69uzZoNlYO3ZyT7hdspUch0+4\nf2wfT7iWcEOoaoO6RFWN/R8nTJup//06cOAA3bt3j6Xv28kl3PRs5XsOE+6f2kfCtSqFECIS8R+M\njAE466yz2roI0UVx1G23PbGEa4yJTrVde2OIJVxjTHSy8XCNMSZCLOEaY0yExGCzMOtpZkyUKC0t\n5aqrrqJ///5cddVVfP31143Gvfjii/Tv35/+/fvz4osvnrA+Jycn4u2oT5nWG54xKljCNSZKzJ07\nl1GjRpGfn8+OHTvo16/fCYm3tLSU2bNnk5+fz3333cdtt93GueeeW5d4lyxZwrp16ygsLGTgwIHM\nmHHCxAbtR+1YCk6WdsISrjFRYtmyZZxzzjlkZmZSUlKC1+tl1KhRDQYk/8tf/oKIkJ2dzU9+8hNy\ncnKYMWMGs2fP5sEHH+S///u/8fl8nH322axfv56PP/6YFSvCdu+PXrVVCk6WdsISrjFRYv/+/Tzy\nyCOkpqayYcMGSktLueyyyxoMbPPWW2+Rnp7OnDlzGD58OFu3buXw4cNkZ2fz7LPP8tJLL/HGG2+w\nd+9ePB4PF198McXFxWGOGsVad8aHqGA/mhkTQVdeeSX79+8/4fM5c+bg8/nIzMwkPz+fzz//HFXl\nhz/8YYOpnTZt2sTll1/Ozp07+fLLL9m8eTO/+93vSE9PJykpic6dO3PXXXdRU1PDeeedx9GjR7n3\n3nsjeYqtK8ZaKdgTrjERtGrVKjZu3HhCr7KZM2dSWVnJ8ePHUVWmTZtGSkpK3Uh19913HxAY66O8\nvJz8/Py60esOHTrEP//5Tw4fPswtt9xCSUkJALt372b//v38+c9/jvh5topWnrU3GtgTrjFtYNWq\nVSd8lpOTw759+0hNTUVEqKmpYdy4caxevZrc3Fxuv/12OnbsyF//+lfOOOMMIDDY0KeffkpWVha9\nevWiqKiIHTt2MGDAANLS0igtLY3YmL+trnUHII8K9oRrTJS466672LZtGwcPHuSrr77irLPOoqys\njPPOOw+fz8eyZcvo3bs3N998M4WFhWzevJnExES6desGBKbhUVV+85vfUFNTQ2ZmJhkZGW0y8lur\niMEnXEu4xkSJK6+8ktTUVB599FESEhLYsWMHX3/9NZMnT+bCCy9k/fr1lJWVUVpaSv/+/euehIcM\nGUJaWho+n4+CggIWLFiA3+/n008/5ciRIyfMj9auWMI1xpwKXq+X+fPnM3/+fI4dO8bgwYPJz8/n\njTfe4Pjx43Tv3p0PPviAgwcPsn37do4fP857773Hv//9b+Li4ujUqRPXX389Z5xxBp07d2b16tVM\nnz6doUOHtvWptYw1CzPGnErjxo1j+/btpKen182BdvDgQbKzs+nRowdJSUm88cYb/PrXv6Zbt27E\nxcWxdOlSRowYQVxcHOvXr6empgav18vkyZP57W9/yxNPPMH8+fPb+tTca+VmYSIyVkS2iEiRiJzQ\nI0REEkXkteD6fBHpU2/dQ8HPt4jImHqf7xCRf4nIBhFpdvBeS7jGRBmv18vvf/971qxZQ2ZmJj/8\n4Q9ZvXo1O3fuJC8vD4CpU6fSpUsXxo4dy7x58/jOd77DFVdcwYIFCwDo0aMHaWlpJCUlcc899zB9\n+vS2PKWWacU6XBHxAAuAq4Es4MZ6E0HWmgp8raqZwFPAr4LbZhGY5XcgMBZ4Jri/WiNV9SInA6Bb\nwjUmCl1zzTUsW7aMpKQkXnrpJSZOnMhzzz1HQUEBeXl5JCUlsWbNGkaPHk1paSmvvPIKc+fO5eGH\nH6a8vJwNGzawYcMGpk2bRqdOnQB47rnnuOiii7jooovo27cvI0eObOOzbIafwADkTpbmDQGKVHWb\nqlYBucD4kJjxQO3gFEuBURKYfmM8kKuqlaq6ncAMvUNacko2xY4xp5nq6mquuOIKHnjgAa655ppT\neaiTm2JHspXm/0oP0PBT7IjIBGCsqt4WfH8zMFRVp9eL2RiMKQ6+/wIYCswCPlHVl4OfLwJWqOpS\nEdkOfE0gN/5eVZ8PV8x22l7EGNNS9957L1dcccWpTratw/kjXpeQOtTnQ5JfY8k/dO9NxYTbdriq\n7hWRbsB7IvJvVf1rU4W0hGvMaWTx4sXs3Lmzff6IFl5JM3WoxUCveu8zgL1NxBSLiBdIBUrDbauq\ntf/9SkTeJFDV0GTCtTpcY04T69at4ze/+Q0vv/xyxKeEjwJrgf4i0ldEEgj8CJYXEpMH3BJ8PQH4\nQAN1rnnApGArhr5Af+BTEekoIikAItIRGA1sDFcIe8I15jQxf/58SktL634sy87O5oUXXmjjUkWG\nqtaIyHTgHcAD/EFVN4nIY0CBquYBi4A/iUgRgSfbScFtN4nI68BmAm0i7lZVn4h0B94MTmvvBV5V\n1ZXhymE/mhljTpWT/NHsYoWPHUYnh/3RLFrYE64xJkrF3qRmlnCNMVEq9qbttYRrjIlS9oRrjDER\nYgnXGGMiRHHab7e9sIRrjIlSVodrjDERYlUKxhgTIfaEa4wxEWJPuMYYEyH2hGuMMRFSOwJ57LCE\na4yJUlalYIwxEWRVCsYYEwH2hGuMMRFiCdcYYyLEWikYY0yEWCsFY4yJEKtSMMaYCIm9KoXTbupO\nY0x7UfuE62RpnoiMFZEtIlIkIjMaWZ8oIq8F1+eLSJ966x4Kfr5FRMY43WcoS7jGmChV+4TrZAlP\nRDzAAuBqIAu4UUSyQsKmAl+raibwFPCr4LZZBGbwHQiMBZ4REY/DfTZgCdcYE6VqfzRzsjRrCFCk\nqttUtQrIBcaHxIwHXgy+XgqMksAc6OOBXFWtVNXtQFFwf0722YDbOtyTmvbYGGOc2/cOzOriMDhJ\nRArqvX9eVZ+v974nsLve+2JgaMg+6mJUtUZEDgPpwc8/Cdm2Z/B1c/tswH40M8ZEJVUd24q7a+xh\nUR3GNPV5YzUEoftswKoUjDGng2KgV733GcDepmJExAukAqVhtnWyzwYs4RpjTgdrgf4i0ldEEgj8\nCJYXEpMH3BJ8PQH4QFU1+PmkYCuGvkB/4FOH+2zAqhSMMTEvWCc7HXgH8AB/UNVNIvIYUKCqecAi\n4E8iUkTgyXZScNtNIvI6sJlAk4i7VdUH0Ng+w5VDAgncGGPMqWZVCsYYEyGWcI0xJkIs4RpjTIRY\nwjXGmAixhGuMMRFiCdcYYyLEEq4xxkTI/w+X43WHfiUyVgAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -157,9 +192,9 @@ }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAW8AAADxCAYAAAAEJzaTAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xt4VNXZ+P3vnZkkJIDhTAuRk0EkWAQMEDxU0f4AaRvw\nERWoVi8UT/h6eFoOfaiKVq3+KuBblfqItHhoiZS2EitiPbdaTqHYKigkckwQJcFwSJgcJvf7x0zy\nhjCzM3sSkgm5P9c11zXZs9a6194b7uys2WsvUVWMMca0LnEt3QFjjDHuWfI2xphWyJK3Mca0Qpa8\njTGmFbLkbYwxrZAlb2OMaYUseRtj2gQRmSAi20UkX0Tmhfg8UUReCX6+QUT61fnsZ8Ht20VkfENt\nikj/YBt5wTYTgtu/KyL/EpEqEZlSL/4NwfJ5InJDQ/tjydsYc9oTEQ/wDHAFkA5ME5H0esVuAr5R\n1TRgMfB4sG46MBUYAkwAloiIp4E2HwcWq+pA4Jtg2wB7gRuBP9TrXxfgAWA0MAp4QEQ6O+2TJW9j\nTFswCshX1Z2qWgFkA5PqlZkEvBB8vwq4XEQkuD1bVctVdReQH2wvZJvBOpcF2yDY5mQAVd2tqv8B\nquvFHg+8paqHVPUb4C0CvyjC8rrbf2w6pjEmUtKYymkiWhZh2S9hK+Crs+k5VX2uzs+9gX11fi4g\ncJVLqDKqWiUih4Guwe3r69XtHXwfqs2uQImqVoUoH06o/jnWcZu8jTGmWZQBt0ZYdgH4VDXDoUio\nXyT1L0bDlQm3PdTIhVN5J67r2LCJMSYmCYGry0heESgAzqzzcyqwP1wZEfECKcAhh7rhthcBnYJt\nhIsVTf9OYMnbGBOT4oCkCF8R2AQMDN4FkkDgC8icemVygJq7PKYA72rgyX05wNTg3Sj9gYHAxnBt\nBuu8F2yDYJurG+jfm8A4Eekc/KJyXHBbWDZsYoyJSQLEN1FbwTHsOwkkRA/wW1XdKiIPAbmqmgMs\nA14SkXwCV9xTg3W3ishKYBtQBcxSVT9AqDaDIecC2SLyMLAl2DYiMhL4C9AZ+KGIPKiqQ1T1kIj8\ngsAvBICHVPWQ0z6Jy0fC2heWxphINeoLy74ietLN2GHcAZsbGPM+7diVtzEmJjXllffpyJK3MSYm\n1XxhaUKzY2OMiUl25e3MkrcxJibV3G1iQrPkbYyJSXbl7cyStzEmZlmCCs+OjTEmJtmVtzNL3saY\nmGR3mzizY2OMiUn2haUzS97GmJhkwybOLHkbY2KSDZs4s2NjjIlJduXtzJK3MSYm2ZW3Mzs2xpiY\nZFfezix5G2NikmB3mzixlXRMs/j973/PuHHjWrobphURIN4b2astsuRtXLv00kt5/vnnw36+e/du\nRISqqqrabT/60Y/429/+1hzdC9mfsWPHkpyczDnnnMPbb78dtuzKlSu54IILSE5O5tJLL22+TpqT\niIDXG9mrLbLkfYrVTWBtuQ8tadq0aQwfPpzi4mIeeeQRpkyZwsGDB0OW7dKlC/fccw/z5kW6hos5\nVUQg3hPZq01SVTcvo6p9+/bVRx99VAcPHqydOnXSG2+8UY8fP66qqu+995727t1bH3vsMe3Zs6de\nd911qqr62muv6XnnnacpKSk6ZswY/fe//13b3mOPPaa9evXSDh066Nlnn61vv/22qqpu2LBBzz//\nfO3YsaP26NFD77333hNi1O/TW2+9paqqDzzwgF511VX6ox/9SDt27KhLly5Vv9+vv/zlL3XAgAHa\npUsXvfrqq7W4uDjk/h06dEi///3va7du3bRTp076/e9/X/ft26eqqv/zP/+jcXFxmpiYqO3bt9dZ\ns2apqurGjRv1O9/5jh4/flxTU1MV0KSkJG3fvr3+85//1N/97nd64YUX1sYA9JlnntG0tDTt0KGD\n/vznP9f8/HzNzMzUjh076tVXX63l5eW15Z2On5Pt27drQkKCHjlypHbbRRddpL/5zW8c6y1dulQv\nueQSxzIHDx7U73//+5qSkqKdO3fWiy66SP1+f0T9aiPc5pcTXiM8aEVKZC8C61A2Kl5re1nyjkLf\nvn11yJAhunfvXi0uLtYLLrhA58+fr6qBxOrxeHTOnDnq8/m0rKxMN2/erN27d9f169drVVWVLl++\nXPv27as+n08///xzTU1N1cLCQlVV3bVrl+bn56uqamZmpr744ouqqnr06FFdt25dbYyGkrfX69W/\n/OUv6vf7taysTBcvXqyjR4/Wffv2qc/n01tuuUWnTp0acv+Kiop01apVWlpaqkeOHNEpU6bopEmT\naj+/5JJLdOnSpSfVmz9/vv7kJz/R6667TgGtrKys/SxU8v7hD3+ohw8f1k8//VQTEhL0sssu0y++\n+EJLSkp08ODBunz5clVVx+Onqnr77bfr7bffHnJf/vznP+s555xzwrZZs2bpnXfeGbJ8jUiS97x5\n8/TWW2/ViooKraio0L///e9aXV3tWKeNaVRyOt+Lao/IXm0xeduwSZTuvPNOzjzzTLp06cL8+fNZ\nsWJF7WdxcXE8+OCDJCYmkpSUxNKlS7n11lsZPXo0Ho+HG264gcTERNavX4/H46G8vJxt27ZRWVlJ\nv379OOusswCIj48nPz+foqIiOnToQGZmZsT9GzNmDJMnTyYuLo6kpCT+93//l0ceeYTU1FQSExNZ\nsGABq1atCjmk0rVrV6666iqSk5Pp2LEj8+fP54MPPmgw5v33389bb73FJ598ElEf586dyxlnnMGQ\nIUM499xzGTduHAMGDCAlJYUrrriCLVu2ADgeP4AlS5awZMmSkDGOHTtGSkrKCdtSUlI4evRoRH10\nEh8fz5dffsmePXuIj4/n4osvRqRRa+6aumpu9I7k1QZZ8o7SmWeeWfu+b9++7N+/v/bn7t27065d\nu9qf9+zZw8KFC+nUqVPta9++fezfv5+0tDSefPJJFixYQI8ePZg6dWptW8uWLWPHjh2cc845jBw5\nkr/+9a9R9a+mD1deeWVt/MGDB+PxePjqq69OqltWVsatt95K3759OeOMM/jud79LSUkJfr/fMeah\nQ4c4duwYx44di6iPPXv2rH2flJR00s817Tgdv4Z06NCBI0eOnLDtyJEjdOzYMaI+Opk9ezZpaWm1\nv3Qee+yxRrdp6rDk7ciSd5T27dtX+37v3r306tWr9uf6V19nnnkm8+fPp6SkpPZVVlbGtGnTAJg+\nfToffvghe/bsQUSYO3cuAAMHDmTFihV8/fXXzJ07lylTplBaWkr79u0pKyurbd/v95/0BVyoPrzx\nxhsn9MHn89G7d++T9m3hwoVs376dDRs2cOTIEf7+978DgSG2UG3XuOWWW/jFL37B5MmTnQ+eSw0d\nPydDhgxh586dJ1xp//vf/2bIkCGN7lfHjh1ZuHAhO3fu5LXXXmPRokW88847jW7X1GHJOyxL3lF6\n5plnKCgo4NChQzz66KNce+21YcvOnDmTZ599lg0bNqCqlJaW8vrrr3P06FG2b9/Ou+++S3l5Oe3a\ntSMpKQmPJ/D1+csvv8zBgweJi4ujU6dOAHg8Hs4++2x8Ph+vv/46lZWVPPzww5SXlzv297bbbmP+\n/Pns2bMHgIMHD7J69eqQZY8ePUpSUhKdOnXi0KFDPPjggyd83rNnT3bu3HnCthdffBGv18v06dN5\n4IEHgMC93U3B6fg15Oyzz2bYsGE8+OCD+Hw+/vKXv/Cf//yHq666KmR5v9+Pz+ejqqqK6upqfD4f\nlZWVIcv+9a9/JT8/H1XljDPOwOPx1J470wQE8ET4aoMseUdp+vTptX8uDxgwgJ///Odhy2ZkZLB0\n6VLuvPNOOnfuTFpaGsuXLwegvLycefPm0a1bN771rW/x9ddf8+ijjwKwdu1ahgwZQocOHbj77rvJ\nzs6mXbt2pKSksGTJEm6++WZ69+5N+/btSU1Ndezv3XffTVZWFuPGjaNjx45kZmayYcOGkGXvuece\njh8/Trdu3cjMzGTChAkntbVq1So6d+7MXXfdBcCPf/xj/vznPwOBK9L77ruPn/70p3Tq1Kl2bDpa\nTscPAr+YbrvttrD1s7Ozyc3NpXPnzsybN49Vq1bRvXt3IPALpu5V+EsvvURSUhK33347//jHP0hK\nSmLmzJkh283Ly+N73/seHTp0YMyYMdxxxx12b3hTsmETR1Lzp3CEXBU+XfXr14/nn3+e733vey3d\nFWNiWaO+vc1IFs0dFGGgj9msqhmNidfatNHfWcaYmGePFXRkh8YYE5sseTuyQxOF3bt3t3QXjGkb\n2uiXkZGw5G2MiU125e3IDo0xJjZZ8nZkh8YYE5sESGzpTsQuS97GmNhkV96ObJJOhNauXcugQYNI\nS0sL+QyL8vJyrr32WtLS0hg9enSjvtRsKNaiRYtIT09n6NChXH755bWzJk9FrBqrVq1CRMjNzY06\nVqTxVq5cSXp6OkOGDGH69OmnLNbevXsZO3Ysw4cPZ+jQoaxZsybqWDNmzKBHjx6ce+65IT9XVe66\n6y7S0tIYOnQo//rXv6KO1WbYJB1nLh9D2CZVVVXpgAED9IsvvtDy8nIdOnSobt269YQyzzzzjN56\n662qqrpixQq95pprTlmsd999V0tLS1VVdcmSJac0lqrqkSNH9OKLL9bRo0frpk2boooVabwdO3bo\nsGHD9NChQ6qq+tVXX52yWDNnztQlS5aoqurWrVu1b9++UcVSVf3ggw908+bNOmTIkJCfv/766zph\nwgStrq7WdevW6ahRo6KO1Yo07pGwnVG9OrIX9khYU1lZSVVVVe1DmAA2btxIWloaAwYMICEhgalT\np570XJDVq1dzww03ADBlyhTeeeedE9qIVCSxapb0AsjMzKSgoMB1nEhjAdx3333MmTPnhCclnqp4\nS5cuZdasWXTu3BmAHj16nLJYIlL7xMHDhw+f8HAxt7773e/SpUuXsJ+vXr2aH//4x4gImZmZlJSU\n8OWXX0Ydr01o4itvEZkgIttFJF9ETloqSUQSReSV4OcbRKRfnc9+Fty+XUTGN9SmiPQPtpEXbDPB\nKYaIxIvICyLyiYh8JiI/a2h/LHnX4/f72bVr1wkJvLCw8IRHrKamplJYWHhCvbplvF4vKSkpFBcX\nu44fSay6li1bxhVXXOE6TqSxtmzZwr59+/jBD34QVQy38Xbs2MGOHTu48MILyczMZO3atacs1oIF\nC3j55ZdJTU1l4sSJPPXUU1HFaqr+mHpqvrCM5NVQUyIe4BngCiAdmCYi6fWK3QR8o6ppwGLg8WDd\ndGAqMASYACwREU8DbT4OLFbVgcA3wbbDxgCuBhJV9TvA+cCtdX95hGLJu57q6moKCgqoqqriiy++\nqP0Tpb76j0WNpEwk3LTz8ssvk5uby+zZs13HiSRWdXU19957LwsXLoyqfbfxILDeZl5eHu+//z4r\nVqzg5ptvpqSk5JTEWrFiBTfeeCMFBQWsWbOG66+/nurqatexmqo/pp6mvfIeBeSr6k5VrQCygUn1\nykwCXgi+XwVcLoGTNAnIVtVyVd0F5AfbC9lmsM5lwTYItjm5gRgKtBcRL5AEVAAnPoi+Hkve9RQX\nF1NaWkplZSX79u0jPz+fXr16nfD87oKCgpP+xE5NTa0tU1VVxeHDhx3/jA6nbjvhYgG8/fbbPPLI\nI+Tk5JCYGN39VA3FOnr0KJ9++imXXnop/fr1Y/369WRlZUX9pWUk+5aamsqkSZOIj4+nf//+DBo0\niLy8vFMSa9myZVxzzTVAYOUhn89HUVGR61hN1R9Tj7vk3U1Ecuu8bqnXWm9gX52fC4LbQpZR1Srg\nMNDVoW647V2BkmAb9WOFi7EKKAW+BPYCT6jqIafDY8m7nu7du5OYmEhubi5+v5+CggKGDh1KXl4e\nu3btoqKiguzsbLKysk6ol5WVxQsvBH6hrlq1issuuyyqK6uRI0c2GGvLli3ceuut5OTkRD0mHEms\nlJQUioqK2L17N7t37yYzM5OcnBwyMqJ7eFsk+zZ58mTee+89AIqKitixYwcDBgw4JbH69OlTu3jC\nZ599hs/nq31UbFPLysrixRdfRFVZv349KSkpfPvb3z4lsU4rkSfvIlXNqPN6rl5Lof4z1v9zKFyZ\nptruFGMU4Ad6Af2Bn4iI4z/8tnqTjSOv18uwYcP46KOPiI+Px+v1snDhQsaNG0d1dTUzZsxgyJAh\nnHXWWSxevJisrCxuuukmrr/+etLS0ujSpQvZ2dlRx3766acZP348fr+/Ntb999/PX//6V/71r38x\ne/Zsjh07xtVXXw0EklBOTs4pidWUIok3fvx4/va3v5Geno7H4+FXv/oVXbt2bdJYGRkZLFmyhEWL\nFjFz5kwWL16MiLB8+fKohzKmTZvG+++/T1FREampqTz44IO1izi8+uqrvPHGG6xZs4a0tDSSk5P5\n3e9+F1WcNqVmMYamUQDUXRswFai/jl5NmYLg8EUKcKiBuqG2FwGdRMQbvLquWz5cjOnAWlWtBL4W\nkY+ADODEVU/qsOd51+Pz+Vi3bh0XXHABH330ET6fjx49enDkyBHOPPNM+vTpQ0JCAiJCRkZGo+97\njlR1dTWjRo1qtnin8775/X5Gjx7dLPGae99iTOOe5/0t0dzrIgy00Pl53sFEuQO4HCgENgHTVXVr\nnTKzgO+o6m0iMhX4L1W9RkSGAH8gcHXcC3gHGEhg/0K2KSJ/BP6kqtki8izwH1Vd4hBjLnAOMANI\nDrY1VVX/E26f7MrbgYiQlJRESkoKBw4cYO/evQDMmjWLoqIiiouLGTFiRLP0JTMzk927dzdbvObc\ntxEjRrBnz55mizdz5ky2bdvWLPG+853vsHfv3tpYe/fuPWXj6qedJpwer6pVInIn8CaB6/nfBpPs\nQwTuEc8BlgEviUg+gavhqcG6W0VkJbANqAJmqaofIFSbwZBzgWwReRjYEmybcDEI3LXyO+DT4J7/\nzilxgyXviPTp04c9e/ZQVlbGrl27+OMf/0hVVRXXXnstr7zySrP0obCwkD179vDiiy82S7zm3LcD\nBw6wf/9+XnrppWaJt2XLFsaPH8/zzz9/ymN9/fXXHDx4kJdeegkRsdWX3Gji6fGqugZYU2/b/XXe\n+wjcsheq7iPAI5G0Gdy+k8CVev3tIWOo6rFwscOx5B0hj8dD+/bt8fl8fPbZZ1RXV/PPDRvp1q2b\nu4a88VAVekHbSMpHFC/eC5VVDZcLkngvGqK8U6xwdaKJEzaey/0AXB3fV1991f35APDEgz/yOkkd\nz2D79u385Cc/4fPPPycjI4Nu3bpFfQ97m2HPNnFkh8aFmmGUESNGUFVVxfFjR+Edl18DXC6w2UWd\n8wUKfe5i9G5HV3/ksy6LPamcpZ+6CvGFnMvZ+m9XdXbIea7ifCHnutoPCOyLq+PVu5278wGBc+Li\nvB+/XBg2bBjvvPMOF110UVsd/3bPkrcju1XQmBZmD7Vy4Inw1QZZ8jamhd14442OQyhvvPEGeXl5\n5OXl8dxzz3H77bc3Y+9akD1V0JElb2NamD3UKowmfLbJ6ciStzExrs0+1MquvB210d02pvVosw+1\nsi8sHdmhMSbGtdmHWjXt9PjTjg2bGBPj2uxDrWzYxFEb3W1jYofTQ61uu+02Jk6c2DYfaiVA4xZv\nOq1Z8m4MT3xg0o2rOt7AJI9Ieb2BiSRueL2BySoRl/fwhYS+x9ipzg45z3UdV3Hc7kewjqvj5fZ8\n1NRxc9498Y4fr1ixwvFzEeGZZ56JPN7pwoZNHFnybgx/ZXSz87a7qDNI6FB60FWIY+2701c/i7j8\nHhnMMF3nKsbHMoZR+oGrOhvlEldxPpYxrvYDAvvi5ngda9/d3fkAGBTFLFnjnn1h6cgOjTEmdlmG\nCssOjTEmNtmwiSNL3saY2GTDJo7s0BhjYlMTLsZwOrLkbYyJTXbl7cgOjTEmNlnydmSHpp7y8nKq\nq6tbuhvGGEvejuzQ1FNRUcHx48f5+OOP8fv9eDz2dbcxLcb++4Vlzzapp2PHjrRv356+fftSXl5O\nWVkZxcXFLd0tY9oee7aJoza62w3r3LkzycnJ+P1+CgsLKS0tJSEhAa+3ziHzxkc3tXqQu+nxx9p3\ndxfD62GPDHZV/mMZ4zrGRrnEdR1XcdzuB7g/Xm7PR00dV484cJ4eb8Kwu00cWfJugMfjYejQoXz4\n4YdUVFRQXl7OunXrAs9YrqqManFgt9O3o5kiPkbfjbj8OrmMK/RPrmK8IVcxWZ2fyVHfqzLNVZw3\n5CpX+wGBfXH7aIBoHj/gdpHjjRs3Eh9vSdwVG/N2ZIcmQnFxcbRr1w5VZdSoUfj9/pbukmlFMjIy\nuPLKK/n888/JyMigW7dujutWGix5N8DGvF0SETweD3FxduhM5OLi4li9ejXnnHMOubm5JyXutWvX\nMmjQINLS0njsscdOqr93717Gjh3L8OHDGTp0KGvWrGmurrccG/N2ZBnImBbm9/uZNWsWb7zxBtu2\nbWPFihVs27bthDIPP/ww11xzDVu2bCE7O5s77rijhXrbvNQT2astaqO/s4yJHRs3biQtLY0BAwYA\nMHXqVFavXk16enptGRHhyJEjABw+fLhNLIOmcVBhizGEZcnbmBYWanX4DRs2nFBmwYIFjBs3jqee\neorS0lLefvvt5u5ms1OBKk+kgwNtb2KdDZsY08IiWR1+xYoV3HjjjRQUFLBmzRquv/76034msIrg\n93ojerVFbXOvjYkhkawOv2zZstovOceMGYPP56OoqIgePXo0a1+bm99mOIdlV97GtLCRI0eSl5fH\nrl27qKioIDs7m6ysrBPK9OnTh3feeQeAzz77DJ/PR/fuLidvtTKK4McT0astsivvxohycWBXMwCj\nmmXoYZ1cFnFx8Xp4Q65yFUK8Hl6Vaa7ruIkjLvcDiGJ2aTQzWF2e9wZmWHq9Xp5++mnGjx+P3+9n\nxowZDBkyhPvvv5+MjAyysrJYuHAhM2fOZPHixYgIy5cvP2lo5XSjCFVtNDFHwpJ3Y1RV0dVf4KpK\nsSfV9QzAaGYZXqUvR1z+T3Idt+iTrmI8J/fw/+j/dVXnKZnjKs5zco+r/YDAvridXRrNDFY3573Y\nk9pgmYkTJzJx4sQTtj300EO179PT0/noo48i7+RpQBEqbH58WJa8jTExqWbYxIRmY97GmJjVlGPe\nIjJBRLaLSL6IzAvxeaKIvBL8fIOI9Kvz2c+C27eLyPiG2hSR/sE28oJtJkQQY6iIrBORrSLyiYg4\njs1Z8jbGxKSaMe9IXg0REQ/wDHAFkA5ME5H0esVuAr5R1TRgMfB4sG46MBUYAkwAloiIp4E2HwcW\nq+pA4Jtg204xvMDLwG2qOgS4FKh02idL3saYmBQYNvFG9IrAKCBfVXeqagWQDUyqV2YS8ELw/Srg\ncgl8KzwJyFbVclXdBeQH2wvZZrDOZcE2CLY5uYEY44D/qOq/AVS1WFUdn35nydsYE5MCX1gmRPQC\nuolIbp3XLfWa6w3sq/NzQXBbyDKqWgUcBro61A23vStQEmyjfqxwMc4GVETeFJF/icicho6PfWFp\njIlJCm5uFSxS1QyHz0PdV1l/amu4MuG2h7r4dSrvFMMLXASMBMqAd0Rks6q+E6I8hAlujDExoEmH\nTQqAM+v8nArsD1cmOAadAhxyqBtuexHQKdhG/VhOMT5Q1SJVLQPWACOcdsiStzEmJjXxDMtNwMDg\nXSAJBL6AzKlXJge4Ifh+CvCuBh48kwNMDd4p0h8YCGwM12awznvBNgi2ubqBGG8CQ0UkOZjULwFO\nfC5wPTZs0hjx3ogmYJwgivUl3c4yFK+HP8l1LsrH8Zzc4zJGHE81PCzXqDjijXO1H4E6Lo9XlOtk\nujrv8fbfLFpNdZ+3qlaJyJ0EkqQH+K2qbhWRh4BcVc0BlgEviUg+gavhqcG6W0VkJYFkWgXMqvky\nMVSbwZBzgWwReRjYEmwbhxjfiMgiAr8QFFijqq877ZP9q2qMyirO0k9dVflCzmWYrou4/McyJqr1\nJd3OZLxP/8dVjF/Io/xCf+Kqzn2y0FWcX8ijUc38dLtOppvzAYFz4ua8fyHnumrfBDT1JB1VXUNg\nOKLutvvrvPcBV4ep+wjwSCRtBrfvJHA3Sv3tTjFeJnC7YEQseddz5MgRfD4fBQUF+P1+W+7MmBai\nCOU2PT4sS971JCUl4fV68fv9VFZW4vf7+eijjygrKyMuLo64uDh279592j9L2TStvXv32oWASzY9\n3pn9a6onPj4er9dL3759adeuHe3bt+fCCy8kOTmZhIQE4uLi8Hq9eNvoA+BNdLxeLzfffHPt6vET\nJkxo6S7FPHskrDNL3hGqWTU+Pj6e1NTUNrGGoGk6vXr1Yu3atVGvHg+wcuVK0tPTGTJkCNOnT2+O\nbre4ppoefzqyy0djWljN6vFvvfUWqampjBw5kqysrBMWIM7Ly+OXv/wlH330EZ07d+brr79uwR43\nj5rp8SY0u/I2poXVXT0+ISGhdvX4upYuXcqsWbPo3LkzwGm//BnYsElDLHkb08JCrR5fWFh4Qpkd\nO3awY8cOLrzwQjIzM08adjkdBe42SYjo1RbZ3yTGtLBIVo+vqqoiLy+P999/n4KCAi6++GI+/fRT\nOnXq1FzdbHY2bOLMjkwjSLzX/QQMr4ePZUzkMaJaX9LdTMY4bxy/kEddxYjzxnGfLHRdx02c6GZ+\nujxeLs9HTR03510amGEZyerxqampZGZmEh8fT//+/Rk0aBB5eXmMHDnSXd9bmbY6JBIJS96NoJVV\nnB14/G7Edsh5jNIPIi6/US5hsq5wFeNVmeZqfcmnZE5UsyUX6h2u6vxElriKc58sjGqdTDfH61WZ\n5up8QOCcuDnvO+Q8x8/rrh7fu3dvsrOz+cMf/nBCmcmTJ7NixQpuvPFGioqK2LFjBwMGDHDV79bG\n7vN2ZsnbmBYWyerx48eP529/+xvp6el4PB5+9atf0bVr15bu+illyduZJW9jYkBDq8eLCIsWLWLR\nokWO7dx3331069aNu+++G4D58+fTs2dP7rrrrqbv9Clm0+Od2d0mxpxGbrrpJl54IbDKVnV1NdnZ\n2fzoRz+ZPpHFAAAYIElEQVRq4V5Fx24VdGZX3sacRvr160fXrl3ZsmULX331FcOHD2/VwyttNTFH\nwpK3MaeZm2++meXLl3PgwAFmzJjR0t2JWs3q8SY0GzYx5jRz5ZVXsnbtWjZt2sT48eNbujtRa+LV\n4087bXOvjTmNJSQkMHbsWDp16oTH07qvXG3YJDxL3sacZqqrq1m/fj1//OMfW7orjaIIFW106nsk\nLHk3gsR7G5yAcRKvh41ySeQxvB5elWnu+uVyfcnoZksKP5ElLuu4ixPdOpkuj5fL81FTx815b2iG\nZVPatm0bP/jBD7jyyisZOHBgs8U9FWzM25kl70ZQW8PSVR1bw/LUS09PZ+fOnc0W71SyZ5s4syNj\njIlZNuYdniVvY0xMsunxzix5G2Niko15O7Pk7ZKqUl5ejt/vb+mumFakoqKipbvQ6gTuNrFnm4Rj\nyTsEVaW0tJSqqir8fj+ffPIJpaWlqCoiwn/+85+QD9A3JpxPP/2Un/70p7Wrx3fr1q1NrIbTGDZs\n4sxmWNZTXFxMaWkpeXl5+P1+4uLi6NOnD8nJyXTo0IH27dszcuRIRowY0dJdNa3IiBEjePfddxu1\nejzAqlWrEBFyc3NPdZdjgj2YKjxL3vV07dqVDh06MGzYMBITE4mPjyclJeWkZamMaSo1q8e/8cYb\nbNu2jRUrVrBt27aTyh09epRf//rXjB49ugV62fxqxrwjebVFlryNaWGRrB4PgWd1z5kzh3bt2rVA\nL5ufPdvEWdvc6yZia1jaGpYR9amBGZahVo/fsGHDCWW2bNnCvn37+MEPfsATTzzhrr+tlE2Pd2bJ\nuxG0soqu/gJXdYo9qfTVzyIuv0cGM0bfdRVjnVzGVfpyxOX/JNdFNZMxmvUl3c78dLMfENgXN8dr\nnVzm6nxA4Jy4Oe/FnlTHzxtaPb66upp7772X5cuXRxzzdGC3Cjqz5G1MC2to9fijR4/y6aefcuml\nlwJw4MABsrKyyMnJISMjo7m726za6pBIJOzIGNPCGlo9PiUlhaKiotqfL730Up544onTPnHbrYLO\n7AtLY1pY3dXjBw8ezDXXXFO7enxOTk5Ld6/F2BqWzuzK25gY0NDq8XW9//77zdCj2GBj3uFZ8jbG\nxKRq4mx6vAMbNjHGxKymHDYRkQkisl1E8kVkXojPE0XkleDnG0SkX53Pfhbcvl1ExjfUpoj0D7aR\nF2wzoaEYwc/7iMgxEflpQ/tjydsYE5OacsxbRDzAM8AVQDowTUTS6xW7CfhGVdOAxcDjwbrpwFRg\nCDABWCIingbafBxYrKoDgW+CbYeNUcdi4I1Ijo8lb2NMTFJoyunxo4B8Vd2pqhVANjCpXplJwAvB\n96uAyyVww/0kIFtVy1V1F5AfbC9km8E6lwXbINjm5AZiICKTgZ3A1kh2yJK3MSZGuZoe301Ecuu8\nbqnXWG9gX52fC4LbQpZR1SrgMNDVoW647V2BkmAb9WOFjCEi7YG5wIORHh37wrIx4r0Nzp47idfD\nHhnsqvw6ucxVCPF6+JNc56J8NNPQo1kc2F0c8ca52o9AHZfHy+35APC6PO/NuADx6cTlfd5Fqup0\n43uoJ8vVn9oarky47aEufp3KO8V4kMAwy7FIH4Jn/6oao7IKCn3u6vRuR4fSgxEXP9a+e1TTt91O\nEY9mkePJusJVnVdlmuvFgaN5NIDbxw+4OR8QOCeuznvvtvEgqaamCOVN92yTAuDMOj+nAvvDlCkQ\nES+QAhxqoG6o7UVAJxHxBq+u65YPF2M0MEVE/i/QCagWEZ+qPh1uh2zYxBgTk5r4qYKbgIHBu0AS\nCHwBWX8GVA5wQ/D9FOBdDTx4JgeYGrxTpD8wENgYrs1gnfeCbRBsc7VTDFW9WFX7qWo/4EngUafE\nDXblbYyJYU01e1JVq0TkTuBNwAP8VlW3ishDQK6q5gDLgJdEJJ/A1fDUYN2tIrIS2AZUAbNU1Q8Q\nqs1gyLlAtog8DGwJtk24GNGw5G2MiUlN/WwTVV0DrKm37f46733A1WHqPgI8Ekmbwe07CdyNUn97\n2Bh1yixw+ryGJW9jTExSBH+1TY8Px5K3MSYmabVQ7rPp8eFY8nahqqqKiooKNmzYYKvHG1dyc3OJ\nj49v6W60KqqCv8quvMOxu00isH//fkpLS6msrCQxMZERI0YwbNiwlu6WaUWGDRvGfffdx+eff05G\nRgYTJkw44fOGVo9ftGgR6enpDB06lMsvv5w9e/Y0V9dbjoK/yhPRqy2yK+8w/H4/FRUVVFRUcPTo\nUZKTk2uXpoqPj7fV5I0rXq+XnJwcLrroInJzc0/4rGb1+LfeeovU1FRGjhxJVlYW6en//6M3hg8f\nTm5uLsnJyfzmN79hzpw5vPLKK829G81KVaiqbJuJORKWvOuprKykvLycdevWoaq0b9+eQYMGUVxc\nfHJhb7z7CRheb2CSR8Tlo5kB6G6WYXSLHHt4Vaa5ruMmjuvZkhDFDFaX5yNYx9V59zoPl9RdPR6o\nXT2+bvIeO3Zs7fvMzExeftnd2p6tk1DttxQVjh2ZekpLSxERxowZc9IK3iepqoTNLse+zxfY7qLO\nIIlqBqDbWYbDdJ2rGB/LGEbpB67qbJRLXMX5WMZENbvU7QxWV+cDYJC4O+/nO/+VFsnq8XUtW7aM\nK664IvL4rZUCbXRIJBKWvOvp1KkTCQkJeDz2j8Y0j4ZWj6/r5ZdfJjc3lw8+cPeLs1WqFvBZigrH\njowxLayh1eNrvP322zzyyCN88MEHJCa2kVvoqhou0lbZ3SbGtLC6q8dXVFSQnZ1NVlbWCWW2bNnC\nrbfeSk5ODj169GihnjazwAO9I3u1QXblbUwLq7t6vN/vZ8aMGbWrx2dkZJCVlcXs2bM5duwYV18d\nmFndp0+f039l+ZrkbUKy5G1MDGho9fi33367ubvU8hSobOlOxC5L3saY2KRAeUt3InZZ8jbGxCYb\nNnFkydsYE5sseTuy5N0Y3vgGJ2CcxOMNTPKIOEY0MwDdr5P5sYxxHWOjXOK6jqs4Ua4v6ep4uT0f\nNXXcnPcGZliaMCx5O7Lk3RhVlfCOy9l5l0cxOy+KdTK7+gsiLl7sSeUs/dRViC/kXM7Wf7uqs0PO\ncxXnCznX1X5AYF9cry8ZzSxZN+f9cnsOTlQseTuy5G2MiV2WvMOy5G2MiU3VgMs/OtsSS97GmNhk\nwyaOLHkbY2KTJW9HlryNMbHJkrcjS97GmNhlyTssS97GmNhkV96OLHm7VFlZyd69e6murm7prphW\npKCggLg4ewKzK9XA8ZbuROyy5B0hVeX48eMnrHCS1OEMjrudgOF2VmY062TGewOTVSIk8V6+kHNd\nhZB4LzvkPNd1XMVxuR+A++MV1SzZeFcTb5I7ngHAjBkzaleP79atG2vXrq0ts3btWu6++278fj83\n33wz8+bNO6GN8vJyfvzjH7N582a6du3KK6+8Qr9+/dz1u7VRwN/SnYhdlrwjcPToUUpLS0lMTMTr\n9ZKamkpVVRV/Xf0XRo0a1Sx92Lt3LwkJCXzrW99qlngbN25stn0rLCxEVUlNdZmoo9Sc+3bgwAEq\nKiro1asXr7zyChMnToxq9fhly5bRuXNn8vPzyc7OZu7cuaf96vGADZs4sOTdgMrKSj755BOSk5OJ\ni4tDRPjoo4+orKykqqqq4UWKm4Cq4vP5SEhIYM+ePc0S7/jx482ybxA4xqpKYWFhs8QrKyvjn//8\nZ7OsU+r3+ykvL6ewsJDZs2ezZ88ehg8fTs+ePWuvvCNZPX716tUsWLAAgClTpnDnnXeiqmHXujwt\n2Ji3I0veYVRXV+Pz+aiurubCCy9k48aNqCojRowgPz+fyspKBg8e3CzjmF9++SUlJSUMHuzyIU1R\nOnLkCIWFhc0Wr6ysjPz8fIYOHdos8Y4dO8bnn3/O8OHDmyWBHzp0iPz8fNasWcNbb73Fk08+yaJF\ni2o/j2T1+LplvF4vKSkpFBcX061bt1Pe/xZjyduRJe8Qqqur2bRpEyJSe8WtqsTFxfGPf/wDj8dD\nYmIimzdvbpa+HD9+nOTkZDZt2nTK4wFUVFQgIs0WD6C0tLRZ41VUVPDPf/6Tdu1cfp8QJVVl8+bN\nPPXUU5SWlpKRkUGvXr1IS0vjpptuOql8/StqNyvMnzZserwj+/q7nsOHD1NWVkZaWlrtCt1VVYFf\n/0ePHiU+Pr7ZVu6uGS5p165ds/5H9fv9zXJFWldcXFyz3sGTkJBAdXV17bk91eLi4khOTmbBggX8\n+te/Jj09nSNHjvDJJ5/wxBNPNLh6fN0V5quqqjh8+DBdunRplr63KFuAOCxL3vUkJiaSnJxMly5d\naq+2169fT2lpKe3atSM+vvmezVxRUYHH42n2RFpdXd3st7V5PJ5mS6Q12rVrR3l5ecir2lNBREhK\nSsLv9/PQQw/Rt29fAHbs2MH777/PJZdcEnb1+KysLF544QUAVq1axWWXXXb6X3nb6vGOLHnX065d\nO+Li4mqvPn0+Hz6fj6SkpGZNon6/H7/f32xX+TVaInFDIHn7/c17X1hcXByJiYn4fM33t3lNAhcR\nHnjgAV588UX69u1Lhw4dWL9+PZ06deKaa66pXT2+ZoX4m266ieLiYtLS0li0aBGPPfZYs/W5xdQs\nQBzJqw0Sl1cdzXOJ0oLKyspYt24d559/PmVlZezcuZNzzjkHr7d5vx7Iy8sjNTWVpKSkZo1bUlJC\neXk5PXv2bNa4qkp+fj4DBw5s1rgA+fn5fPvb36Z9+/bNGvfgwYOUlJQwcOBANmzYwOzZs1m9ejX9\n+/dv1n6cQo3600C6ZShZuQ0XBPidbFbVjMbEa20sedejqnz44YfNfhVo2rY5c+ZQUlJCYmIiiYmJ\nJ03iaaUal7y7ZijfjzB5v9Rw8haRCcD/C3iA51X1sXqfJwIvAucDxcC1qro7+NnPgJsITBu6S1Xf\ndGpTRPoD2UAX4F/A9apaES6GiPwf4DEgAagAZqvqu477Y8nbGHOKNC55d8lQLo8wea9yTt4i4gF2\nAP8HKAA2AdNUdVudMncAQ1X1NhGZClypqteKSDqwAhgF9ALeBs4OVgvZpoisBP6sqtki8izwb1X9\njUOM4cBXqrpfRM4F3lTV3k67bGPexpjYVDM9PpJXw0YB+aq6U1UrCFwVT6pXZhLwQvD9KuByCXwr\nPAnIVtVyVd0F5AfbC9lmsM5lwTYItjnZKYaqblHV/cHtW4F2wav0sCx5G2Nik7u7TbqJSG6d1y31\nWusN7Kvzc0FwW8gyqloFHAa6OtQNt70rUBJso36scDHqugrYoqrlIY5KLZukY4yJTe5mWBY1MOYd\nagin/jBwuDLhtoe6+HUq32A/RGQI8DgwLkS5E9iVtzExYu3atQwaNIi0tLSQtwKWl5dz7bXXkpaW\nxujRo9m9e3fzd7I5Ne2tggXAmXV+TgX2hysjIl4gBTjkUDfc9iKgU7CN+rHCxUBEUoG/AD9W1S8a\n2iFL3sbEgJonC86bN4+4uDgeeOAB7r333hPKLFu2jH379pGQkMCXX37J6NGjm+VBZS2q6ca8NwED\nRaS/iCQAU4GcemVygBuC76cA72rgjo4cYKqIJAbvIhkIbAzXZrDOe8E2CLa52imGiHQCXgd+pqof\nRbJDlryNiQEbN27krLPO4uGHH2bt2rX8/Oc/Jzs7m23bam+GYPXq1UyfPp3c3Fx27txJaWkpc+bM\nacFen2I1zzaJ5NWA4PjyncCbwGfASlXdKiIPiUjNdNZlQFcRyQf+G5gXrLsVWAlsA9YCs1TVH67N\nYFtzgf8OttU12HbYGMF20oD7ROTj4KuH0z7ZmLcxMaCwsJDExMTaR8MeOXKEkpISLr74YmbPns28\nefMoLCxk8uTJJCcnA9CpUyc2b95c+xCxjIzTbI5KzbBJUzWnugZYU2/b/XXe+4Crw9R9BHgkkjaD\n23cSuBul/vaQMVT1YeDhBneiDrvyNiYGqCplZWWceeaZ+P1+XnzxRUaMGEFmZiYPPPAAffr04eDB\ngyfUOXLkCBUVFaSlpdVOqZ8+fXoL7cEp0LS3Cp52LHkbEwNSU1MpKioCAkMoKSkp9OrViw8//JC7\n7rqLmTNnUlZWxt///ncAXnjhBY4dO8acOXM4cOAAzz//PFu3buXJJ59syd1oevZgqrBs2MSYGDBy\n5EgOHjzIjh072LNnD0VFRcTFxdGzZ0+GDh3KypUrUVVmzJjBu+++S05ODj179mTv3r306tWLsrIy\nxo4dS0lJCX6/n8cee4yJEye29G41ji3G4MimxxsTI1577TX+67/+i5SUFPr06UNxcTHJycmMGzeO\n5cuXM3DgQLZs2UJ1dTVJSUmsXbuWRYsWsXnzZsrKyujYsSPnnXceW7Zs4cCBA6xfv54RI0a05C41\nbnp8YobSO8Lp8bva3oOpbNjEmBjxwx/+kNWrV5OUlMTnn3/OBRdcwIUXXsiHH35I+/btGT9+PN26\ndUNE8Pl8fO973+O1115j//79HD9+nLPOOos333yTpUuXMmjQIG6//faW3qXGsed5O7LkbUwMmThx\nIrt27eJb3/oWU6ZMqR1CGTNmDB06dOCGG26gV69e3HHHHVRUVDBz5kwGDhzIn//8Z7Zs2YLf7+fq\nq69m+fLllJSU8OWXX7b0LjWOJe+wLHkbE2O8Xi9PP/008+bN4/3332fo0KGkpKTw5JNP1j5zvGYV\nncmTJ1NSUsKbb75J9+7d6dy5MytXruT6668nNTWVwsLCltyVxrHFGBxZ8jYmBk2cOJG8vDxWr17N\nxx9/THZ2NnfddRc33HADJSUlHD58GIDx48czffp0lixZQmFhIXfccQcTJkzA5/PVLiTdatmtgo7s\nbhNjYljNMMrZZ5/N9OnTqaiooKqqirlz5wKBK/BFixbx2Wefcfz4cQYNGsRnn32Gz+fj66+/Pmkh\n41bF7jZxZHebGNMKrFmzhnvuuQefz0d5eTlfffUV999/PxkZGWRlZbFt2zauuuoqCgsL6d+/PzNm\nzOD3v/89GzdurG3j2Wef5dlnnwXg8OHD9OvXj/fee+9Udrtxd5tIhuKN8G6TKrvbxBgTgyZOnMj5\n559PZWUlhw4dIjU1lb59+7J//36effZZ0tPT2bZtG9dffz3Hjh3j+eefZ8mSJSe0cdttt/Hxxx+z\nadMmUlNT+e///u8W2hsXbNgkLLvyNqaNueOOO+jevTsPPvjgqQ7V+CtvIrzypu1deduYtzFtyPLl\ny9mzZw9PP/10S3fFNJIlb2PaiM2bN/PEE0/wj3/8g7g4GzFt7ewMGtNGPP300xw6dIixY8cybNgw\nbr755pbukmkEG/M2xpwqjRzzHqEQ0aIyQLKNeRtjTGxo4tUYTjOWvI0xMcpm6Tix5G2MiVF25e3E\nkrcxJkZZ8nZiydsYE6MUON7SnYhZlryNMTHKxrydWPI2xsQoGzZxYsnbGBOj7MrbiSVvY0yMsitv\nJ5a8jTExyq68nVjyNsbEqGrsbpPwLHkbY2KUDZs4seRtjIlhNmwSjiVvY0yMsitvJ5a8jTExypK3\nE0vexpgYZXebOLHkbYyJUXa3iRNL3saYGGXDJk4seRtjYpQNmzixBYiNMTGq5so7klfDRGSCiGwX\nkXwRmRfi80QReSX4+QYR6Vfns58Ft28XkfENtSki/YNt5AXbTIg2RjiWvI0xMarmyjuSlzMR8QDP\nAFcA6cA0EUmvV+wm4BtVTQMWA48H66YDU4EhwARgiYh4GmjzcWCxqg4Evgm27TqG0z5Z8jbGxKia\nLywjeTVoFJCvqjtVtQLIBibVKzMJeCH4fhVwuYhIcHu2qpar6i4gP9heyDaDdS4LtkGwzclRxgjL\n7Zi3uCxvjDFR+vJNWNAtwsLtRCS3zs/PqepzdX7uDeyr83MBMLpeG7VlVLVKRA4DXYPb19er2zv4\nPlSbXYESVa0KUT6aGCHZF5bGmJikqhOasLlQF54aYZlw20ONXDiVjyZGWDZsYoxpCwqAM+v8nArs\nD1dGRLxACnDIoW647UVAp2Ab9WO5jRGWJW9jTFuwCRgYvAskgcCXgzn1yuQANwTfTwHeVVUNbp8a\nvFOkPzAQ2BiuzWCd94JtEGxzdZQxwrJhE2PMaS84vnwn8CbgAX6rqltF5CEgV1VzgGXASyKST+Bq\neGqw7lYRWQlsI3BryyxV9QOEajMYci6QLSIPA1uCbRNNjHAkkPSNMca0JjZsYowxrZAlb2OMaYUs\neRtjTCtkydsYY1ohS97GGNMKWfI2xphWyJK3Mca0Qv8fTrau2cJSKFMAAAAASUVORK5CYII=\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAVwAAADxCAYAAACH4w+oAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xl8VNX5+PHPk8lGABMIgoUgi0E0cTcC1lpBFBFrsBUR\npahfcZe6tFWwiMUFxbagvwpoQVpQC1HzVUlVUFHUby1GQ6EVsEJkDYsSwhrINvP8/phJOhmy3AmT\nyWR43q/XfTH33ufee2ZueHJy5p5zRFUxxhjT/GJaugDGGHOssIRrjDFhYgnXGGPCxBKuMcaEiSVc\nY4wJE0u4xhgTJpZwjTHHBBEZKiLfiEihiEyoY3+CiLzq258vIj0D9p8oIgdF5NdOzxnIEq4xJuqJ\niAuYCVwOZADXiUhGQNhYYI+qpgPPAE8H7H8GWBzkOWuxhGuMORb0AwpVdYOqVgA5wPCAmOHAfN/r\nXGCwiAiAiFwFbADWBHnOWmKDLLR1SzPGOCVHc3C6iB5yGLvDmwjL/DbNVtXZfuvdgK1+60VA/4DT\n1MSoapWI7ANSReQwMB64FPh1XfENnLOWYBOuMcaExSHgdoexk6FMVbMaCKkr+QdWIOuLeRR4RlUP\n+iq8wZyzFku4xpiIJIQ0QRUB3f3W04Dt9cQUiUgskAyU4K21jhCR3wEpgEdEyoAVDs5ZiyVcY0xE\nigHahO50XwJ9RKQXsA0YBVwfEJMH3AgsB0YAH6l3dK8LqwNEZDJwUFVn+JJyY+esxRKuMSYiCRAX\nonP52mTHAe8BLuDPqrpGRB4DClQ1D5gLvCwihXhrtqOacs6GjpEgh2e0L82MMU4d1ZdmPUS00Qdb\nfe6CFY204UYEq+EaYyJSKGu4kcISrjEmIoX4S7OIEG3vxxgTJayGa4wxYRLipxQigiVcY0xEshqu\nMcaEUbQlqGh7P8aYKGE1XGOMCRN7SsEYY8LEvjQzxpgwsSYFY4wJE2tSMMaYMLEarjHGhInVcI0x\nJkyshmuMMWEiRN9TCjZrrwmLv/71rwwZMqSli2FaEQHiYp0trYUlXBO0gQMH8uKLL9a7f9OmTYgI\nVVVVNdtGjx7N+++/H47i1VmeQYMGkZSUxCmnnMLSpUvrjX3ttdf44Q9/SFJSEgMHDgxfIc0RRCA2\n1tnSWljCbWb+SedYLkNLuu666zj77LPZvXs3U6ZMYcSIEezatavO2I4dO3LfffcxYYLTuQZMcxGB\nOJezpdVQ1WAWo6o9evTQJ598Uk899VRNSUnRm266SQ8fPqyqqsuWLdNu3brp1KlTtUuXLvrzn/9c\nVVX/9re/6ZlnnqnJycl6/vnn67/+9a+a802dOlW7du2q7dq105NPPlmXLl2qqqr5+fl67rnnavv2\n7bVz5856//3317pGYJk++OADVVX97W9/q1dffbWOHj1a27dvr3PmzFG3261PPfWU9u7dWzt27KjX\nXHON7t69u873V1JSoldccYV26tRJU1JS9IorrtCtW7eqqupvfvMbjYmJ0YSEBG3btq3efffdqqr6\nxRdf6Omnn66HDx/WtLQ0BbRNmzbatm1b/cc//qF/+ctf9IILLqi5BqAzZ87U9PR0bdeunT788MNa\nWFioAwYM0Pbt2+s111yj5eXlNfENfX4N+eabbzQ+Pl73799fs+1HP/qRPv/88w0eN2fOHL3ooosa\njNm1a5deccUVmpycrB06dNAf/ehH6na7HZXrGBFsfqm1nONCK5KdLXjnJWvwfMBQ4BugEJhQx/4E\n4FXf/nygp297P2CVb/kX8FO/YzYBX/n2NV6GID8Eo97klpmZqVu2bNHdu3frD3/4Q504caKqepOh\ny+XSBx98UMvKyvTQoUO6YsUKPf744/Xzzz/XqqoqnTdvnvbo0UPLysr0P//5j6alpem2bdtUVXXj\nxo1aWFioqqoDBgzQl156SVVVDxw4oMuXL6+5RmMJNzY2Vt988011u9166NAhfeaZZ7R///66detW\nLSsr09tuu01HjRpV5/srLi7W3NxcLS0t1f379+uIESN0+PDhNfsvuuginTNnzhHHTZw4UX/1q1/p\nz3/+cwW0srKyZl9dCffKK6/Uffv26erVqzU+Pl4vvvhi/fbbb3Xv3r166qmn6rx581RVG/z8VFXv\nvPNOvfPOO+t8L2+88YaecsoptbbdfffdOm7cuDrjqzlJuBMmTNDbb79dKyoqtKKiQj/99FP1eDwN\nHnOMOaqEe24sqp2dLY0lO7yTPH4L9AbifYkzIyDmLuAF3+tRwKu+10lArO/1D4Dv/dY3AZ2cvidr\nUmiicePG0b17dzp27MjEiRNZuHBhzb6YmBgeffRREhISaNOmDXPmzOH222+nf//+uFwubrzxRhIS\nEvj8889xuVyUl5ezdu1aKisr6dmzJyeddBIAcXFxFBYWUlxcTLt27RgwYIDj8p1//vlcddVVxMTE\n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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -167,14 +202,16 @@ } ], "source": [ + "pressures = solution['flow']\n", "time_steps = [50,100]\n", "dt = problem.time_step()\n", "\n", "for t in time_steps:\n", " p = pressures[t]\n", - " plot_grid.plot_grid(problem.g, p, color_map = [0, 1*10**-5], if_plot=False)\n", + " problem.time_disc().split(problem.grid(), 'pressure', p)\n", + " plot_grid.plot_grid(problem.grid(), 'pressure', color_map = [0, 0.04], if_plot=False)\n", " plt.title('pressure at time: %g s' %(t*dt))\n", - " plt.show()\n" + " plt.show()" ] } ],