def test_complex(): np.random.seed(0) I = np.eye(5) A = np.random.randn(5, 5) B = np.random.randn(5, 5) C = np.random.randn(3, 5) Iop = NumpyMatrixOperator(I) Aop = NumpyMatrixOperator(A) Bop = NumpyMatrixOperator(B) Cva = NumpyVectorSpace.from_data(C) # assemble_lincomb assert not np.iscomplexobj( Aop.assemble_lincomb((Iop, Bop), (1, 1))._matrix) assert not np.iscomplexobj( Aop.assemble_lincomb((Aop, Bop), (1, 1))._matrix) assert np.iscomplexobj( Aop.assemble_lincomb((Aop, Bop), (1 + 0j, 1 + 0j))._matrix) assert np.iscomplexobj(Aop.assemble_lincomb((Aop, Bop), (1j, 1))._matrix) assert np.iscomplexobj(Aop.assemble_lincomb((Bop, Aop), (1, 1j))._matrix) # apply_inverse assert not np.iscomplexobj(Aop.apply_inverse(Cva).data) assert np.iscomplexobj((Aop * 1j).apply_inverse(Cva).data) assert np.iscomplexobj( Aop.assemble_lincomb((Aop, Bop), (1, 1j)).apply_inverse(Cva).data) assert np.iscomplexobj(Aop.apply_inverse(Cva * 1j).data) # append for rsrv in (0, 10): for o_ind in (slice(None), [0]): va = NumpyVectorSpace(5).empty(reserve=rsrv) va.append(Cva) D = np.random.randn(1, 5) + 1j * np.random.randn(1, 5) Dva = NumpyVectorSpace.from_data(D) assert not np.iscomplexobj(va.data) assert np.iscomplexobj(Dva.data) va.append(Dva[o_ind]) assert np.iscomplexobj(va.data) # scal assert not np.iscomplexobj(Cva.data) assert np.iscomplexobj((Cva * 1j).data) assert np.iscomplexobj((Cva * (1 + 0j)).data) # axpy assert not np.iscomplexobj(Cva.data) Cva[0].axpy(1, Dva) assert np.iscomplexobj(Cva.data) Cva = NumpyVectorSpace.from_data(C) assert not np.iscomplexobj(Cva.data) Cva[0].axpy(1j, Dva) assert np.iscomplexobj(Cva.data)
def test_complex(): np.random.seed(0) I = np.eye(5) A = np.random.randn(5, 5) B = np.random.randn(5, 5) C = np.random.randn(3, 5) Iop = NumpyMatrixOperator(I) Aop = NumpyMatrixOperator(A) Bop = NumpyMatrixOperator(B) Cva = NumpyVectorArray(C) # assemble_lincomb assert not np.iscomplexobj(Aop.assemble_lincomb((Iop, Bop), (1, 1))._matrix) assert not np.iscomplexobj(Aop.assemble_lincomb((Aop, Bop), (1, 1))._matrix) assert not np.iscomplexobj(Aop.assemble_lincomb((Aop, Bop), (1 + 0j, 1 + 0j))._matrix) assert np.iscomplexobj(Aop.assemble_lincomb((Aop, Bop), (1j, 1))._matrix) assert np.iscomplexobj(Aop.assemble_lincomb((Bop, Aop), (1, 1j))._matrix) # apply_inverse assert not np.iscomplexobj(Aop.apply_inverse(Cva).data) assert np.iscomplexobj((Aop * 1j).apply_inverse(Cva).data) assert np.iscomplexobj(Aop.assemble_lincomb((Aop, Bop), (1, 1j)).apply_inverse(Cva).data) assert np.iscomplexobj(Aop.apply_inverse(Cva * 1j).data) # append for rsrv in (0, 10): for o_ind in (None, [0]): va = NumpyVectorArray.make_array(subtype=5, reserve=rsrv) va.append(Cva) D = np.random.randn(1, 5) + 1j * np.random.randn(1, 5) Dva = NumpyVectorArray(D) assert not np.iscomplexobj(va.data) assert np.iscomplexobj(Dva.data) va.append(Dva, o_ind) assert np.iscomplexobj(va.data) # scal assert not np.iscomplexobj(Cva.data) assert np.iscomplexobj((Cva * 1j).data) assert np.iscomplexobj((Cva * (1 + 0j)).data) # axpy assert not np.iscomplexobj(Cva.data) Cva.axpy(1, Dva, 0) assert np.iscomplexobj(Cva.data) Cva = NumpyVectorArray(C) assert not np.iscomplexobj(Cva.data) Cva.axpy(1j, Dva, 0) assert np.iscomplexobj(Cva.data)
def test_complex(): np.random.seed(0) I = np.eye(5) A = np.random.randn(5, 5) B = np.random.randn(5, 5) C = np.random.randn(3, 5) Iop = NumpyMatrixOperator(I) Aop = NumpyMatrixOperator(A) Bop = NumpyMatrixOperator(B) Cva = NumpyVectorSpace.from_numpy(C) # lincombs assert not np.iscomplexobj((Iop * 1 + Bop * 1).assemble().matrix) assert not np.iscomplexobj((Aop * 1 + Bop * 1).assemble().matrix) assert np.iscomplexobj((Aop * (1+0j) + Bop * (1+0j)).assemble().matrix) assert np.iscomplexobj((Aop * 1j + Bop * 1).assemble().matrix) assert np.iscomplexobj((Bop * 1 + Aop * 1j).assemble().matrix) # apply_inverse assert not np.iscomplexobj(Aop.apply_inverse(Cva).to_numpy()) assert np.iscomplexobj((Aop * 1j).apply_inverse(Cva).to_numpy()) assert np.iscomplexobj((Aop * 1 + Bop * 1j).assemble().apply_inverse(Cva).to_numpy()) assert np.iscomplexobj(Aop.apply_inverse(Cva * 1j).to_numpy()) # append for rsrv in (0, 10): for o_ind in (slice(None), [0]): va = NumpyVectorSpace(5).empty(reserve=rsrv) va.append(Cva) D = np.random.randn(1, 5) + 1j * np.random.randn(1, 5) Dva = NumpyVectorSpace.from_numpy(D) assert not np.iscomplexobj(va.to_numpy()) assert np.iscomplexobj(Dva.to_numpy()) va.append(Dva[o_ind]) assert np.iscomplexobj(va.to_numpy()) # scal assert not np.iscomplexobj(Cva.to_numpy()) assert np.iscomplexobj((Cva * 1j).to_numpy()) assert np.iscomplexobj((Cva * (1 + 0j)).to_numpy()) # axpy assert not np.iscomplexobj(Cva.to_numpy()) Cva[0].axpy(1, Dva) assert np.iscomplexobj(Cva.to_numpy()) Cva = NumpyVectorSpace.from_numpy(C) assert not np.iscomplexobj(Cva.to_numpy()) Cva[0].axpy(1j, Dva) assert np.iscomplexobj(Cva.to_numpy())
def apply_inverse_adjoint(self, U, ind=None, mu=None, source_product=None, range_product=None, least_squares=False): if source_product or range_product: return super().apply_inverse_adjoint(U, ind=ind, mu=mu, source_product=source_product, range_product=range_product, least_squares=least_squares) else: options = { 'inverse': self.solver_options.get('inverse_adjoint') if self.solver_options else None } adjoint_op = NumpyMatrixOperator(self._matrix.T, solver_options=options) return adjoint_op.apply_inverse(U, ind=ind, mu=mu, least_squares=least_squares)
def apply_inverse_adjoint(self, U, ind=None, mu=None, source_product=None, range_product=None, least_squares=False): if source_product or range_product: return super(NumpyMatrixOperator, self).apply_inverse_adjoint(U, ind=ind, mu=mu, source_product=source_product, range_product=range_product, least_squares=least_squares) else: options = {'inverse': self.solver_options.get('inverse_adjoint') if self.solver_options else None} adjoint_op = NumpyMatrixOperator(self._matrix.T, solver_options=options) return adjoint_op.apply_inverse(U, ind=ind, mu=mu, least_squares=least_squares)
def apply_inverse(self, V, mu=None, least_squares=False): assert V in self.range assert not self.functional and not self.vector if V.dim == 0: if self.source.dim == 0 and least_squares: return self.source.make_array([np.zeros(0) for _ in range(len(V))]) else: raise InversionError op = NumpyMatrixOperator(self.matrix, solver_options=self.solver_options) return self.source.make_array([op.apply_inverse(NumpyVectorSpace.make_array(v._array), least_squares=least_squares).to_numpy().ravel() for v in V._list])
def test_numpy_sparse_solvers(numpy_sparse_solver): op = NumpyMatrixOperator(diags([np.arange(1., 11.)], [0]), solver_options=numpy_sparse_solver) rhs = op.range.make_array(np.ones(10)) solution = op.apply_inverse(rhs) assert ((op.apply(solution) - rhs).l2_norm() / rhs.l2_norm())[0] < 1e-8
def test_numpy_dense_solvers(): op = NumpyMatrixOperator(np.eye(10) * np.arange(1, 11)) rhs = op.range.make_array(np.ones(10)) solution = op.apply_inverse(rhs) assert ((op.apply(solution) - rhs).l2_norm() / rhs.l2_norm())[0] < 1e-8
def test_numpy_sparse_solvers(numpy_sparse_solver): op = NumpyMatrixOperator(diags([np.arange(1., 11.)], [0]), solver_options=numpy_sparse_solver) rhs = NumpyVectorArray(np.ones(10)) solution = op.apply_inverse(rhs) assert ((op.apply(solution) - rhs).l2_norm() / rhs.l2_norm())[0] < 1e-8
def test_numpy_dense_solvers(numpy_dense_solver): op = NumpyMatrixOperator(np.eye(10) * np.arange(1, 11), solver_options=numpy_dense_solver) rhs = NumpyVectorArray(np.ones(10)) solution = op.apply_inverse(rhs) assert ((op.apply(solution) - rhs).l2_norm() / rhs.l2_norm())[0] < 1e-8
def test_complex(): np.random.seed(0) I = np.eye(5) A = np.random.randn(5, 5) B = np.random.randn(5, 5) C = np.random.randn(3, 5) Iop = NumpyMatrixOperator(I) Aop = NumpyMatrixOperator(A) Bop = NumpyMatrixOperator(B) Cva = NumpyVectorArray(C) # assemble_lincomb assert not np.iscomplexobj( Aop.assemble_lincomb((Iop, Bop), (1, 1))._matrix) assert not np.iscomplexobj( Aop.assemble_lincomb((Aop, Bop), (1, 1))._matrix) assert not np.iscomplexobj( Aop.assemble_lincomb((Aop, Bop), (1 + 0j, 1 + 0j))._matrix) assert np.iscomplexobj(Aop.assemble_lincomb((Aop, Bop), (1j, 1))._matrix) assert np.iscomplexobj(Aop.assemble_lincomb((Bop, Aop), (1, 1j))._matrix) # apply_inverse assert not np.iscomplexobj(Aop.apply_inverse(Cva).data) assert np.iscomplexobj((Aop * 1j).apply_inverse(Cva).data) assert np.iscomplexobj( Aop.assemble_lincomb((Aop, Bop), (1, 1j)).apply_inverse(Cva).data) assert np.iscomplexobj(Aop.apply_inverse(Cva * 1j).data) # append for rsrv in (0, 10): for o_ind in (None, [0]): va = NumpyVectorArray.make_array(subtype=5, reserve=rsrv) va.append(Cva) D = np.random.randn(1, 5) + 1j * np.random.randn(1, 5) Dva = NumpyVectorArray(D) assert not np.iscomplexobj(va.data) assert np.iscomplexobj(Dva.data) va.append(Dva, o_ind) assert np.iscomplexobj(va.data) # replace va = NumpyVectorArray.make_array(subtype=5, reserve=0) va.append(Cva) D = np.random.randn(1, 5) + 1j * np.random.randn(1, 5) Dva = NumpyVectorArray(D) assert not np.iscomplexobj(va.data) assert np.iscomplexobj(Dva.data) va.replace(Dva, 1, 0) assert np.iscomplexobj(va.data) # scal assert not np.iscomplexobj(Cva.data) assert np.iscomplexobj((Cva * 1j).data) assert np.iscomplexobj((Cva * (1 + 0j)).data) # axpy assert not np.iscomplexobj(Cva.data) Cva.axpy(1, Dva, 0) assert np.iscomplexobj(Cva.data) Cva = NumpyVectorArray(C) assert not np.iscomplexobj(Cva.data) Cva.axpy(1j, Dva, 0) assert np.iscomplexobj(Cva.data)
def solve_for_local_correction(self, subdomain, Us, mu=None, inverse_options=None): grid, local_boundary_info, affine_lambda, kappa, f, block_space = self.enrichment_data neighborhood = self.neighborhoods[subdomain] neighborhood_space = block_space.restricted_to_neighborhood( neighborhood) # Compute current solution restricted to the neighborhood to be usable as Dirichlet values for the correction # problem. current_solution = [U._list for U in Us] assert np.all(len(v) == 1 for v in current_solution) current_solution = [v[0].impl for v in current_solution] current_solution = neighborhood_space.project_onto_neighborhood( current_solution, neighborhood) current_solution = make_discrete_function(neighborhood_space, current_solution) # Solve the local corrector problem. # LHS ops = [] for lambda_ in affine_lambda['functions']: ops.append( make_elliptic_swipdg_matrix_operator_on_neighborhood( grid, subdomain, local_boundary_info, neighborhood_space, lambda_, kappa, over_integrate=0)) ops_coeffs = affine_lambda['coefficients'].copy() # RHS funcs = [] # We don't have any boundary treatment right now. Things will probably # break in multiple ways in case of non-trivial boundary conditions, # so we can comment this out for now .. # for lambda_ in affine_lambda['functions']: # funcs.append(make_elliptic_swipdg_vector_functional_on_neighborhood( # grid, subdomain, local_boundary_info, # neighborhood_space, # current_solution, lambda_, kappa, # over_integrate=0)) # funcs_coeffs = affine_lambda['coefficients'].copy() funcs.append( make_l2_vector_functional_on_neighborhood(grid, subdomain, neighborhood_space, f, over_integrate=2)) # funcs_coeffs.append(1.) funcs_coeffs = [1] # assemble in one grid walk neighborhood_assembler = make_neighborhood_system_assembler( grid, subdomain, neighborhood_space) for op in ops: neighborhood_assembler.append(op) for func in funcs: neighborhood_assembler.append(func) neighborhood_assembler.assemble() # solve local_space_id = self.solution_space.subspaces[subdomain].id # lhs = LincombOperator([DuneXTMatrixOperator(o.matrix(), source_id=local_space_id, range_id=local_space_id) # for o in ops], # ops_coeffs) cols, rows = ops[0].matrix().cols, ops[0].matrix().rows assert cols == rows simple = False if simple: from scipy.sparse import coo_matrix eye = coo_matrix(np.eye(rows, cols)) lhs = NumpyMatrixOperator(eye, source_id=local_space_id, range_id=local_space_id) rhs = VectorFunctional(lhs.range.make_array(np.ones(rows))) correction = lhs.apply_inverse(rhs.as_source_array(mu), mu=mu, inverse_options=None) localized_corrections_as_np = correction else: lhs = LincombOperator([ DuneXTMatrixOperator(o.matrix(), source_id=local_space_id, range_id=local_space_id) for o in ops ], ops_coeffs) rhs = LincombOperator([ VectorFunctional(lhs.range.make_array([v.vector()])) for v in funcs ], funcs_coeffs) correction = lhs.apply_inverse(rhs.as_source_array(mu), mu=mu, inverse_options=inverse_options) # correction = rhs.as_source_array(mu) assert len(correction) == 1 # restrict to subdomain local_sizes = [ block_space.local_space(nn).size() for nn in neighborhood ] local_starts = [ int(np.sum(local_sizes[:nn])) for nn in range(len(local_sizes)) ] local_starts.append(neighborhood_space.mapper.size) localized_corrections_as_np = np.array(correction._list[0].impl, copy=False) localized_corrections_as_np = [ localized_corrections_as_np[local_starts[nn]:local_starts[nn + 1]] for nn in range(len(local_sizes)) ] subdomain_index_in_neighborhood = np.where( np.array(list(neighborhood)) == subdomain)[0] assert len(subdomain_index_in_neighborhood) == 1 subdomain_index_in_neighborhood = subdomain_index_in_neighborhood[0] subdomain_correction = Vector( local_sizes[subdomain_index_in_neighborhood], 0.) subdomain_correction_as_np = np.array(subdomain_correction, copy=False) subdomain_correction_as_np[:] = localized_corrections_as_np[ subdomain_index_in_neighborhood][:] return self.solution_space.subspaces[subdomain].make_array( [subdomain_correction])
def localize_problem(p, coarse_grid_resolution, fine_grid_resolution, mus=None, calT=False, calTd=False, calQ=False, dof_codim=2, localization_codim=2, discretizer=None, m=1): assert coarse_grid_resolution > (m + 1) * 2 assert fine_grid_resolution % coarse_grid_resolution == 0 print("localizing problem") global_quantities = {} global_quantities["coarse_grid_resolution"] = coarse_grid_resolution local_quantities = {} # Diskretisierung auf dem feinen Gitter: diameter = 1. / fine_grid_resolution global_quantities["diameter"] = diameter if discretizer is None: discretizer = discretize_stationary_cg d, data = discretizer(p, diameter=diameter) grid = data["grid"] global_quantities["d"] = d global_quantities["data"] = data global_operator = d.operator.assemble(mus) global_quantities["op"] = global_operator global_quantities["op_not_assembled"] = d.operator global_rhs = d.rhs.assemble(mus) global_quantities["rhs"] = global_rhs op_fixed = copy.deepcopy(d.operator) # Skalierung der Dirichlet-Freiheitsgrade: try: dirichlet_dofs = data['boundary_info'].dirichlet_boundaries(dof_codim) for op in op_fixed.operators: op.assemble(mus).matrix[dirichlet_dofs, dirichlet_dofs] *= 1e5 # d.rhs.assemble(mus)._matrix[:, dirichlet_dofs] *= 1e5 except KeyError: pass global_operator_fixed = op_fixed.assemble(mus) global_quantities["op_fixed"] = global_operator_fixed global_quantities["op_fixed_not_assembled"] = op_fixed global_quantities["p"] = p try: dmask = data['boundary_info'].dirichlet_mask(dof_codim) except KeyError: dmask = None # Konstruktion der Teilraeume: subspaces, subspaces_per_codim = build_subspaces( *partition_any_grid(grid, num_intervals=(coarse_grid_resolution, coarse_grid_resolution), dmask=dmask, codim=dof_codim)) global_quantities["subspaces"] = subspaces global_quantities["subspaces_per_codim"] = subspaces_per_codim localizer = NumpyLocalizer(d.solution_space, subspaces['dofs']) global_quantities["localizer"] = localizer def create_m_patch(xspace, m): for i in range(m): space = xspace cspace = tuple( set(i for k in space if subspaces[k]['codim'] == 0 for i in subspaces[k]['cpatch'] if i not in space)) xspace = tuple( set(i for k in cspace if subspaces[k]['codim'] == 1 for i in subspaces[k]['env']) | set(space)) cxspace = tuple( set(i for k in xspace if subspaces[k]['codim'] == 0 for i in subspaces[k]['cpatch'] if i not in xspace)) return xspace, cxspace pou = localized_pou(subspaces, subspaces_per_codim, localizer, coarse_grid_resolution, grid, localization_codim, dof_codim) global_quantities["pou"] = pou spaces = [subspaces[s_id]["env"] for s_id in subspaces_per_codim[2]] global_quantities["spaces"] = spaces full_l2_product = d.products["l2"].assemble() full_h1_semi_product = d.products["h1_semi"].assemble() k_product = LincombOperator((full_h1_semi_product, full_l2_product), (1, mus["k"]**2)).assemble() global_quantities["full_norm"] = induced_norm(k_product) global_quantities["k_product"] = k_product for xpos in range(coarse_grid_resolution - 1): for ypos in range(coarse_grid_resolution - 1): # print "localizing..." s_id = subspaces_per_codim[localization_codim][ ypos + xpos * (coarse_grid_resolution - 1)] space = subspaces[s_id]["env"] ldict = {} local_quantities[space] = ldict ldict["pos"] = (xpos, ypos) # Konstruktion der lokalen Raeume: range_space = subspaces[s_id]["env"] ldict["range_space"] = range_space omega_space = tuple( sorted( set(subspaces[s_id]['env']) | set(subspaces[s_id]['cenv']))) ldict["omega_space"] = omega_space training_space, source_space = create_m_patch(range_space, m) # source_space = subspaces[s_id]["cxenv"] ldict["source_space"] = source_space # training_space = subspaces[s_id]["xenv"] ldict["training_space"] = training_space omega_star_space = tuple( sorted(set(training_space) | set(source_space))) ldict["omega_star_space"] = omega_star_space # lokale Shift-Loesung mit f(Dirichlet) local_op = localizer.localize_operator(global_operator, training_space, training_space) local_rhs = localizer.localize_operator(global_rhs, None, training_space) local_solution = local_op.apply_inverse(local_rhs.as_range_array()) local_solution = localizer.to_space(local_solution, training_space, range_space) local_solution = pou[range_space](local_solution) ldict["local_solution_dirichlet"] = local_solution # Dirichlet Transferoperator: rhsop = localizer.localize_operator(global_operator, training_space, source_space) transop_dirichlet = create_dirichlet_transfer( localizer, local_op, rhsop, source_space, training_space, range_space, pou) ldict["dirichlet_transfer"] = transop_dirichlet # subgrid xmin = max(0, xpos - m) xsize = min(xpos + 2 + m, coarse_grid_resolution) - xmin ymin = max(0, ypos - m) ysize = min(ypos + 2 + m, coarse_grid_resolution) - ymin ldict["posext"] = [(xmin / coarse_grid_resolution, ymin / coarse_grid_resolution), ((xmin + xsize) / coarse_grid_resolution, (ymin + ysize) / coarse_grid_resolution)] mysubgrid = getsubgrid(grid, xmin, ymin, coarse_grid_resolution, xsize=xsize, ysize=ysize) mysubbi = SubGridBoundaryInfo(mysubgrid, grid, data['boundary_info'], 'robin') ld, ldata = discretizer(p, grid=mysubgrid, boundary_info=mysubbi) lop = ld.operator.assemble(mus) # index conversion ndofsext = len(ldata['grid'].parent_indices(dof_codim)) global_dofnrsext = -100000000 * np.ones( shape=(d.solution_space.dim, )) global_dofnrsext[ldata['grid'].parent_indices( dof_codim)] = np.array(range(ndofsext)) lvecext = localizer.localize_vector_array( NumpyVectorSpace.make_array(global_dofnrsext), omega_star_space).data[0] # Robin Transferoperator: bilifo = NumpyMatrixOperator(lop.matrix[:, lvecext][lvecext, :]) transop_robin = create_robin_transfer(localizer, bilifo, source_space, omega_star_space, range_space, pou) ldict["robin_transfer"] = transop_robin # lokale Shift-Loesung mit f(Robin) lrhs = ld.rhs.assemble(mus) llrhs = NumpyMatrixOperator(lrhs.matrix[lvecext.astype(int)]) local_solution = bilifo.apply_inverse(llrhs.as_range_array()) ldict["local_sol2"] = local_solution local_solution = localizer.to_space(local_solution, omega_star_space, range_space) local_solution_pou = pou[range_space](local_solution) ldict["local_solution_robin"] = local_solution_pou if calT: # Transfer-Matrix: ldict["transfer_matrix_robin"] = transop_robin.as_range_array( ).data.T if calTd: # Transfer-Matrix: ldict[ "transfer_matrix_dirichlet"] = transop_dirichlet.as_range_array( ).data.T # Konstruktion der Produkte: range_k = localizer.localize_operator(k_product, range_space, range_space) omstar_k = LincombOperator((NumpyMatrixOperator( ld.products["h1_semi"].assemble().matrix[:, lvecext][lvecext, :]), NumpyMatrixOperator( ld.products["l2"].assemble( ).matrix[:, lvecext][lvecext, :])), (1, mus["k"]**2)).assemble() ldict["omega_star_product"] = omstar_k ldict["range_product"] = range_k if calQ: # Loesungs-Matrix: solution_op_robin = create_robin_solop(localizer, bilifo, source_space, omega_star_space) Q_r = solution_op_robin.as_range_array() ldict["solution_matrix_robin"] = Q_r.data.T source_Q_r_product = NumpyMatrixOperator( omstar_k.apply(Q_r).data.T) ldict["source_product"] = source_Q_r_product lproduct = localizer.localize_operator(full_l2_product, source_space, source_space) lmat = lproduct.matrix.tocoo() lmat.data = np.array([ 4. / 6. * diameter if (row == col) else diameter / 6. for row, col in zip(lmat.row, lmat.col) ]) ldict["source_product"] = NumpyMatrixOperator(lmat.tocsc().astype( np.cfloat)) return global_quantities, local_quantities