def reconstruct_solution(gq, lq, bases, silent=True): # Berechne reduzierte Loesung anhand gegebener Basis if not silent: print("reconstructing solution") op = gq["op"] rhs = gq["rhs"] localizer = gq["localizer"] spaces = gq["spaces"] operator_reductor = LRBOperatorProjection(op, rhs, localizer, spaces, bases, spaces, bases) rop = operator_reductor.get_reduced_operator() rrhs = operator_reductor.get_reduced_rhs() rd = StationaryModel(rop, rrhs, cache_region=None) ru = operator_reductor.reconstruct_source(rd.solve()) return ru
def discretize(dim, n, order): # ### problem definition import dolfin as df if dim == 2: mesh = df.UnitSquareMesh(n, n) elif dim == 3: mesh = df.UnitCubeMesh(n, n, n) else: raise NotImplementedError V = df.FunctionSpace(mesh, "CG", order) g = df.Constant(1.0) c = df.Constant(1.) class DirichletBoundary(df.SubDomain): def inside(self, x, on_boundary): return abs(x[0] - 1.0) < df.DOLFIN_EPS and on_boundary db = DirichletBoundary() bc = df.DirichletBC(V, g, db) u = df.Function(V) v = df.TestFunction(V) f = df.Expression("x[0]*sin(x[1])", degree=2) F = df.inner( (1 + c * u**2) * df.grad(u), df.grad(v)) * df.dx - f * v * df.dx df.solve(F == 0, u, bc, solver_parameters={"newton_solver": { "relative_tolerance": 1e-6 }}) # ### pyMOR wrapping from pymor.bindings.fenics import FenicsVectorSpace, FenicsOperator, FenicsVisualizer from pymor.models.basic import StationaryModel from pymor.operators.constructions import VectorOperator space = FenicsVectorSpace(V) op = FenicsOperator( F, space, space, u, (bc, ), parameter_setter=lambda mu: c.assign(mu['c'].item()), parameters={'c': 1}, solver_options={'inverse': { 'type': 'newton', 'rtol': 1e-6 }}) rhs = VectorOperator(op.range.zeros()) fom = StationaryModel(op, rhs, visualizer=FenicsVisualizer(space)) return fom
def test_memory_region_safety(): op = NumpyMatrixOperator(np.eye(1)) rhs = op.range.make_array(np.array([1])) m = StationaryModel(op, rhs) m.enable_caching('memory') U = m.solve() del U[:] U = m.solve() assert len(U) == 1 del U[:] U = m.solve() assert len(U) == 1
def build_rom(self, projected_operators, error_estimator): return StationaryModel(error_estimator=error_estimator, **projected_operators)
def discretize_stationary_cg(analytical_problem, diameter=None, domain_discretizer=None, grid_type=None, grid=None, boundary_info=None, preassemble=True): """Discretizes a |StationaryProblem| using finite elements. Parameters ---------- analytical_problem The |StationaryProblem| to discretize. diameter If not `None`, `diameter` is passed as an argument to the `domain_discretizer`. domain_discretizer Discretizer to be used for discretizing the analytical domain. This has to be a function `domain_discretizer(domain_description, diameter, ...)`. If `None`, |discretize_domain_default| is used. grid_type If not `None`, this parameter is forwarded to `domain_discretizer` to specify the type of the generated |Grid|. grid Instead of using a domain discretizer, the |Grid| can also be passed directly using this parameter. boundary_info A |BoundaryInfo| specifying the boundary types of the grid boundary entities. Must be provided if `grid` is specified. preassemble If `True`, preassemble all operators in the resulting |Model|. Returns ------- m The |Model| that has been generated. data Dictionary with the following entries: :grid: The generated |Grid|. :boundary_info: The generated |BoundaryInfo|. :unassembled_m: In case `preassemble` is `True`, the generated |Model| before preassembling operators. """ assert isinstance(analytical_problem, StationaryProblem) assert grid is None or boundary_info is not None assert boundary_info is None or grid is not None assert grid is None or domain_discretizer is None assert grid_type is None or grid is None p = analytical_problem if not (p.nonlinear_advection == p.nonlinear_advection_derivative == p.nonlinear_reaction == p.nonlinear_reaction_derivative is None): raise NotImplementedError if grid is None: domain_discretizer = domain_discretizer or discretize_domain_default if grid_type: domain_discretizer = partial(domain_discretizer, grid_type=grid_type) if diameter is None: grid, boundary_info = domain_discretizer(p.domain) else: grid, boundary_info = domain_discretizer(p.domain, diameter=diameter) assert grid.reference_element in (line, triangle, square) if grid.reference_element is square: DiffusionOperator = DiffusionOperatorQ1 AdvectionOperator = AdvectionOperatorQ1 ReactionOperator = L2ProductQ1 L2Functional = L2ProductFunctionalQ1 BoundaryL2Functional = BoundaryL2ProductFunctional else: DiffusionOperator = DiffusionOperatorP1 AdvectionOperator = AdvectionOperatorP1 ReactionOperator = L2ProductP1 L2Functional = L2ProductFunctionalP1 BoundaryL2Functional = BoundaryL2ProductFunctional Li = [ DiffusionOperator(grid, boundary_info, diffusion_constant=0, name='boundary_part') ] coefficients = [1.] # diffusion part if isinstance(p.diffusion, LincombFunction): Li += [ DiffusionOperator(grid, boundary_info, diffusion_function=df, dirichlet_clear_diag=True, name=f'diffusion_{i}') for i, df in enumerate(p.diffusion.functions) ] coefficients += list(p.diffusion.coefficients) elif p.diffusion is not None: Li += [ DiffusionOperator(grid, boundary_info, diffusion_function=p.diffusion, dirichlet_clear_diag=True, name='diffusion') ] coefficients.append(1.) # advection part if isinstance(p.advection, LincombFunction): Li += [ AdvectionOperator(grid, boundary_info, advection_function=af, dirichlet_clear_diag=True, name=f'advection_{i}') for i, af in enumerate(p.advection.functions) ] coefficients += list(p.advection.coefficients) elif p.advection is not None: Li += [ AdvectionOperator(grid, boundary_info, advection_function=p.advection, dirichlet_clear_diag=True, name='advection') ] coefficients.append(1.) # reaction part if isinstance(p.reaction, LincombFunction): Li += [ ReactionOperator(grid, boundary_info, coefficient_function=rf, dirichlet_clear_diag=True, name=f'reaction_{i}') for i, rf in enumerate(p.reaction.functions) ] coefficients += list(p.reaction.coefficients) elif p.reaction is not None: Li += [ ReactionOperator(grid, boundary_info, coefficient_function=p.reaction, dirichlet_clear_diag=True, name='reaction') ] coefficients.append(1.) # robin boundaries if p.robin_data is not None: assert isinstance(p.robin_data, tuple) and len(p.robin_data) == 2 if isinstance(p.robin_data[0], LincombFunction): for i, rd in enumerate(p.robin_data[0].functions): robin_tuple = (rd, p.robin_data[1]) Li += [ RobinBoundaryOperator(grid, boundary_info, robin_data=robin_tuple, name=f'robin_{i}') ] coefficients += list(p.robin_data[0].coefficients) else: Li += [ RobinBoundaryOperator(grid, boundary_info, robin_data=p.robin_data, name=f'robin') ] coefficients.append(1.) L = LincombOperator(operators=Li, coefficients=coefficients, name='ellipticOperator') # right-hand side rhs = p.rhs or ConstantFunction(0., dim_domain=p.domain.dim) Fi = [] coefficients_F = [] if isinstance(p.rhs, LincombFunction): Fi += [ L2Functional(grid, rh, dirichlet_clear_dofs=True, boundary_info=boundary_info, name=f'rhs_{i}') for i, rh in enumerate(p.rhs.functions) ] coefficients_F += list(p.rhs.coefficients) else: Fi += [ L2Functional(grid, rhs, dirichlet_clear_dofs=True, boundary_info=boundary_info, name='rhs') ] coefficients_F.append(1.) if p.neumann_data is not None and boundary_info.has_neumann: if isinstance(p.neumann_data, LincombFunction): Fi += [ BoundaryL2Functional(grid, -ne, boundary_info=boundary_info, boundary_type='neumann', dirichlet_clear_dofs=True, name=f'neumann_{i}') for i, ne in enumerate(p.neumann_data.functions) ] coefficients_F += list(p.neumann_data.coefficients) else: Fi += [ BoundaryL2Functional(grid, -p.neumann_data, boundary_info=boundary_info, boundary_type='neumann', dirichlet_clear_dofs=True) ] coefficients_F.append(1.) if p.robin_data is not None and boundary_info.has_robin: if isinstance(p.robin_data[0], LincombFunction): Fi += [ BoundaryL2Functional(grid, rob * p.robin_data[1], boundary_info=boundary_info, boundary_type='robin', dirichlet_clear_dofs=True, name=f'robin_{i}') for i, rob in enumerate(p.robin_data[0].functions) ] coefficients_F += list(p.robin_data[0].coefficients) else: Fi += [ BoundaryL2Functional(grid, p.robin_data[0] * p.robin_data[1], boundary_info=boundary_info, boundary_type='robin', dirichlet_clear_dofs=True) ] coefficients_F.append(1.) if p.dirichlet_data is not None and boundary_info.has_dirichlet: if isinstance(p.dirichlet_data, LincombFunction): Fi += [ BoundaryDirichletFunctional(grid, di, boundary_info, name=f'dirichlet{i}') for i, di in enumerate(p.dirichlet_data.functions) ] coefficients_F += list(p.dirichlet_data.coefficients) else: Fi += [ BoundaryDirichletFunctional(grid, p.dirichlet_data, boundary_info) ] coefficients_F.append(1.) F = LincombOperator(operators=Fi, coefficients=coefficients_F, name='rhsOperator') if grid.reference_element in (triangle, square): visualizer = PatchVisualizer(grid=grid, bounding_box=grid.bounding_box(), codim=2) elif grid.reference_element is line: visualizer = OnedVisualizer(grid=grid, codim=1) else: visualizer = None Prod = L2ProductQ1 if grid.reference_element is square else L2ProductP1 empty_bi = EmptyBoundaryInfo(grid) l2_product = Prod(grid, empty_bi, name='l2') l2_0_product = Prod(grid, boundary_info, dirichlet_clear_columns=True, name='l2_0') h1_semi_product = DiffusionOperator(grid, empty_bi, name='h1_semi') h1_0_semi_product = DiffusionOperator(grid, boundary_info, dirichlet_clear_columns=True, name='h1_0_semi') products = { 'h1': l2_product + h1_semi_product, 'h1_semi': h1_semi_product, 'l2': l2_product, 'h1_0': l2_0_product + h1_0_semi_product, 'h1_0_semi': h1_0_semi_product, 'l2_0': l2_0_product } # assemble additionals output functionals if p.outputs: if any(v[0] not in ('l2', 'l2_boundary') for v in p.outputs): raise NotImplementedError outputs = [ L2Functional(grid, v[1], dirichlet_clear_dofs=False).H if v[0] == 'l2' else BoundaryL2Functional( grid, v[1], dirichlet_clear_dofs=False).H for v in p.outputs ] if len(outputs) > 1: from pymor.operators.block import BlockColumnOperator output_functional = BlockColumnOperator(outputs) else: output_functional = outputs[0] else: output_functional = None parameter_space = p.parameter_space if hasattr(p, 'parameter_space') else None m = StationaryModel(L, F, output_functional=output_functional, products=products, visualizer=visualizer, parameter_space=parameter_space, name=f'{p.name}_CG') data = {'grid': grid, 'boundary_info': boundary_info} if preassemble: data['unassembled_m'] = m m = preassemble_(m) return m, data
def discretize_stationary_fv(analytical_problem, diameter=None, domain_discretizer=None, grid_type=None, num_flux='lax_friedrichs', lxf_lambda=1., eo_gausspoints=5, eo_intervals=1, grid=None, boundary_info=None, preassemble=True): """Discretizes a |StationaryProblem| using the finite volume method. Parameters ---------- analytical_problem The |StationaryProblem| to discretize. diameter If not `None`, `diameter` is passed as an argument to the `domain_discretizer`. domain_discretizer Discretizer to be used for discretizing the analytical domain. This has to be a function `domain_discretizer(domain_description, diameter, ...)`. If `None`, |discretize_domain_default| is used. grid_type If not `None`, this parameter is forwarded to `domain_discretizer` to specify the type of the generated |Grid|. num_flux The numerical flux to use in the finite volume formulation. Allowed values are `'lax_friedrichs'`, `'engquist_osher'`, `'simplified_engquist_osher'` (see :mod:`pymor.discretizers.builtin.fv`). lxf_lambda The stabilization parameter for the Lax-Friedrichs numerical flux (ignored, if different flux is chosen). eo_gausspoints Number of Gauss points for the Engquist-Osher numerical flux (ignored, if different flux is chosen). eo_intervals Number of sub-intervals to use for integration when using Engquist-Osher numerical flux (ignored, if different flux is chosen). grid Instead of using a domain discretizer, the |Grid| can also be passed directly using this parameter. boundary_info A |BoundaryInfo| specifying the boundary types of the grid boundary entities. Must be provided if `grid` is specified. preassemble If `True`, preassemble all operators in the resulting |Model|. Returns ------- m The |Model| that has been generated. data Dictionary with the following entries: :grid: The generated |Grid|. :boundary_info: The generated |BoundaryInfo|. :unassembled_m: In case `preassemble` is `True`, the generated |Model| before preassembling operators. """ assert isinstance(analytical_problem, StationaryProblem) assert grid is None or boundary_info is not None assert boundary_info is None or grid is not None assert grid is None or domain_discretizer is None assert grid_type is None or grid is None p = analytical_problem if analytical_problem.robin_data is not None: raise NotImplementedError if p.outputs: raise NotImplementedError if grid is None: domain_discretizer = domain_discretizer or discretize_domain_default if grid_type: domain_discretizer = partial(domain_discretizer, grid_type=grid_type) if diameter is None: grid, boundary_info = domain_discretizer(analytical_problem.domain) else: grid, boundary_info = domain_discretizer(analytical_problem.domain, diameter=diameter) L, L_coefficients = [], [] F, F_coefficients = [], [] if p.rhs is not None or p.neumann_data is not None: F += [L2ProductFunctional(grid, p.rhs, boundary_info=boundary_info, neumann_data=p.neumann_data)] F_coefficients += [1.] # diffusion part if isinstance(p.diffusion, LincombFunction): L += [DiffusionOperator(grid, boundary_info, diffusion_function=df, name=f'diffusion_{i}') for i, df in enumerate(p.diffusion.functions)] L_coefficients += p.diffusion.coefficients if p.dirichlet_data is not None: F += [L2ProductFunctional(grid, None, boundary_info=boundary_info, dirichlet_data=p.dirichlet_data, diffusion_function=df, name=f'dirichlet_{i}') for i, df in enumerate(p.diffusion.functions)] F_coefficients += p.diffusion.coefficients elif p.diffusion is not None: L += [DiffusionOperator(grid, boundary_info, diffusion_function=p.diffusion, name='diffusion')] L_coefficients += [1.] if p.dirichlet_data is not None: F += [L2ProductFunctional(grid, None, boundary_info=boundary_info, dirichlet_data=p.dirichlet_data, diffusion_function=p.diffusion, name='dirichlet')] F_coefficients += [1.] # advection part if isinstance(p.advection, LincombFunction): L += [LinearAdvectionLaxFriedrichs(grid, boundary_info, af, name=f'advection_{i}') for i, af in enumerate(p.advection.functions)] L_coefficients += list(p.advection.coefficients) elif p.advection is not None: L += [LinearAdvectionLaxFriedrichs(grid, boundary_info, p.advection, name='advection')] L_coefficients.append(1.) # nonlinear advection part if p.nonlinear_advection is not None: if num_flux == 'lax_friedrichs': L += [nonlinear_advection_lax_friedrichs_operator(grid, boundary_info, p.nonlinear_advection, dirichlet_data=p.dirichlet_data, lxf_lambda=lxf_lambda)] elif num_flux == 'engquist_osher': L += [nonlinear_advection_engquist_osher_operator(grid, boundary_info, p.nonlinear_advection, p.nonlinear_advection_derivative, gausspoints=eo_gausspoints, intervals=eo_intervals, dirichlet_data=p.dirichlet_data)] elif num_flux == 'simplified_engquist_osher': L += [nonlinear_advection_simplified_engquist_osher_operator(grid, boundary_info, p.nonlinear_advection, p.nonlinear_advection_derivative, dirichlet_data=p.dirichlet_data)] else: raise NotImplementedError L_coefficients.append(1.) # reaction part if isinstance(p.reaction, LincombFunction): raise NotImplementedError elif p.reaction is not None: L += [ReactionOperator(grid, p.reaction, name='reaction')] L_coefficients += [1.] # nonlinear reaction part if p.nonlinear_reaction is not None: L += [NonlinearReactionOperator(grid, p.nonlinear_reaction, p.nonlinear_reaction_derivative)] L_coefficients += [1.] # system operator if len(L_coefficients) == 1 and L_coefficients[0] == 1.: L = L[0] else: L = LincombOperator(operators=L, coefficients=L_coefficients, name='elliptic_operator') # rhs if len(F_coefficients) == 0: F = ZeroOperator(L.range, NumpyVectorSpace(1)) elif len(F_coefficients) == 1 and F_coefficients[0] == 1.: F = F[0] else: F = LincombOperator(operators=F, coefficients=F_coefficients, name='rhs') if grid.reference_element in (triangle, square): visualizer = PatchVisualizer(grid=grid, bounding_box=grid.bounding_box(), codim=0) elif grid.reference_element is line: visualizer = OnedVisualizer(grid=grid, codim=0) else: visualizer = None l2_product = L2Product(grid, name='l2') products = {'l2': l2_product} parameter_space = p.parameter_space if hasattr(p, 'parameter_space') else None m = StationaryModel(L, F, products=products, visualizer=visualizer, parameter_space=parameter_space, name=f'{p.name}_FV') data = {'grid': grid, 'boundary_info': boundary_info} if preassemble: data['unassembled_m'] = m m = preassemble_(m) return m, data
def _discretize_fenics(xblocks, yblocks, grid_num_intervals, element_order): # assemble system matrices - FEniCS code ######################################## import dolfin as df mesh = df.UnitSquareMesh(grid_num_intervals, grid_num_intervals, 'crossed') V = df.FunctionSpace(mesh, 'Lagrange', element_order) u = df.TrialFunction(V) v = df.TestFunction(V) diffusion = df.Expression('(lower0 <= x[0]) * (open0 ? (x[0] < upper0) : (x[0] <= upper0)) *' '(lower1 <= x[1]) * (open1 ? (x[1] < upper1) : (x[1] <= upper1))', lower0=0., upper0=0., open0=0, lower1=0., upper1=0., open1=0, element=df.FunctionSpace(mesh, 'DG', 0).ufl_element()) def assemble_matrix(x, y, nx, ny): diffusion.user_parameters['lower0'] = x/nx diffusion.user_parameters['lower1'] = y/ny diffusion.user_parameters['upper0'] = (x + 1)/nx diffusion.user_parameters['upper1'] = (y + 1)/ny diffusion.user_parameters['open0'] = (x + 1 == nx) diffusion.user_parameters['open1'] = (y + 1 == ny) return df.assemble(df.inner(diffusion * df.nabla_grad(u), df.nabla_grad(v)) * df.dx) mats = [assemble_matrix(x, y, xblocks, yblocks) for x in range(xblocks) for y in range(yblocks)] mat0 = mats[0].copy() mat0.zero() h1_mat = df.assemble(df.inner(df.nabla_grad(u), df.nabla_grad(v)) * df.dx) l2_mat = df.assemble(u * v * df.dx) f = df.Constant(1.) * v * df.dx F = df.assemble(f) bc = df.DirichletBC(V, 0., df.DomainBoundary()) for m in mats: bc.zero(m) bc.apply(mat0) bc.apply(h1_mat) bc.apply(F) # wrap everything as a pyMOR model ################################## # FEniCS wrappers from pymor.bindings.fenics import FenicsVectorSpace, FenicsMatrixOperator, FenicsVisualizer # generic pyMOR classes from pymor.models.basic import StationaryModel from pymor.operators.constructions import LincombOperator, VectorOperator from pymor.parameters.functionals import ProjectionParameterFunctional from pymor.parameters.spaces import CubicParameterSpace # define parameter functionals (same as in pymor.analyticalproblems.thermalblock) def parameter_functional_factory(x, y): return ProjectionParameterFunctional(component_name='diffusion', component_shape=(yblocks, xblocks), index=(yblocks - y - 1, x), name=f'diffusion_{x}_{y}') parameter_functionals = tuple(parameter_functional_factory(x, y) for x in range(xblocks) for y in range(yblocks)) # wrap operators ops = [FenicsMatrixOperator(mat0, V, V)] + [FenicsMatrixOperator(m, V, V) for m in mats] op = LincombOperator(ops, (1.,) + parameter_functionals) rhs = VectorOperator(FenicsVectorSpace(V).make_array([F])) h1_product = FenicsMatrixOperator(h1_mat, V, V, name='h1_0_semi') l2_product = FenicsMatrixOperator(l2_mat, V, V, name='l2') # build model visualizer = FenicsVisualizer(FenicsVectorSpace(V)) parameter_space = CubicParameterSpace(op.parameter_type, 0.1, 1.) fom = StationaryModel(op, rhs, products={'h1_0_semi': h1_product, 'l2': l2_product}, parameter_space=parameter_space, visualizer=visualizer) return fom
def build_rom(self, projected_operators, estimator): return StationaryModel(parameter_space=self.fom.parameter_space, estimator=estimator, **projected_operators)
def discretize_stationary_from_disk(parameter_file): """Load a linear affinely decomposed |StationaryModel| from file. The model is defined via an `.ini`-style file as follows :: [system-matrices] L_1.mat: l_1(μ_1,...,μ_n) L_2.mat: l_2(μ_1,...,μ_n) ... [rhs-vectors] F_1.mat: f_1(μ_1,...,μ_n) F_2.mat: f_2(μ_1,...,μ_n) ... [parameter] μ_1: a_1,b_1 ... μ_n: a_n,b_n [products] Prod1: P_1.mat Prod2: P_2.mat ... Here, `L_1.mat`, `L_2.mat`, ..., `F_1.mat`, `F_2.mat`, ... are files containing matrices `L_1`, `L_2`, ... and vectors `F_1.mat`, `F_2.mat`, ... which correspond to the affine components of the operator and right-hand side. The respective coefficient functionals, are given via the string expressions `l_1(...)`, `l_2(...)`, ..., `f_1(...)` in the (scalar-valued) |Parameter| components `w_1`, ..., `w_n`. The allowed lower and upper bounds `a_i, b_i` for the component `μ_i` are specified in the `[parameters]` section. The resulting operator and right-hand side are then of the form :: L(μ) = l_1(μ)*L_1 + l_2(μ)*L_2+ ... F(μ) = f_1(μ)*F_1 + f_2(μ)*L_2+ ... In the `[products]` section, an optional list of inner products `Prod1`, `Prod2`, .. with corresponding matrices `P_1.mat`, `P_2.mat` can be specified. Example:: [system-matrices] matrix1.mat: 1. matrix2.mat: 1. - theta**2 [rhs-vectors] rhs.mat: 1. [parameter] theta: 0, 0.5 [products] h1: h1.mat l2: mass.mat Parameters ---------- parameter_file Path to the parameter file. Returns ------- m The |StationaryModel| that has been generated. """ assert ".ini" == parameter_file[ -4:], f'Given file is not an .ini file: {parameter_file}' assert os.path.isfile(parameter_file) base_path = os.path.dirname(parameter_file) # Get input from parameter file config = configparser.ConfigParser() config.optionxform = str config.read(parameter_file) # Assert that all needed entries given assert 'system-matrices' in config.sections() assert 'rhs-vectors' in config.sections() assert 'parameter' in config.sections() system_mat = config.items('system-matrices') rhs_vec = config.items('rhs-vectors') parameter = config.items('parameter') # Dict of parameters types and ranges parameter_type = {} parameter_range = {} # get parameters for i in range(len(parameter)): parameter_name = parameter[i][0] parameter_list = tuple( float(j) for j in parameter[i][1].replace(" ", "").split(',')) parameter_range[parameter_name] = parameter_list # Assume scalar parameter dependence parameter_type[parameter_name] = 0 # Create parameter space parameter_space = CubicParameterSpace(parameter_type=parameter_type, ranges=parameter_range) # Assemble operators system_operators, system_functionals = [], [] # get parameter functionals and system matrices for i in range(len(system_mat)): path = os.path.join(base_path, system_mat[i][0]) expr = system_mat[i][1] parameter_functional = ExpressionParameterFunctional( expr, parameter_type=parameter_type) system_operators.append( NumpyMatrixOperator.from_file(path, source_id='STATE', range_id='STATE')) system_functionals.append(parameter_functional) system_lincombOperator = LincombOperator(system_operators, coefficients=system_functionals) # get rhs vectors rhs_operators, rhs_functionals = [], [] for i in range(len(rhs_vec)): path = os.path.join(base_path, rhs_vec[i][0]) expr = rhs_vec[i][1] parameter_functional = ExpressionParameterFunctional( expr, parameter_type=parameter_type) op = NumpyMatrixOperator.from_file(path, range_id='STATE') assert isinstance(op.matrix, np.ndarray) op = op.with_(matrix=op.matrix.reshape((-1, 1))) rhs_operators.append(op) rhs_functionals.append(parameter_functional) rhs_lincombOperator = LincombOperator(rhs_operators, coefficients=rhs_functionals) # get products if given if 'products' in config.sections(): product = config.items('products') products = {} for i in range(len(product)): product_name = product[i][0] product_path = os.path.join(base_path, product[i][1]) products[product_name] = NumpyMatrixOperator.from_file( product_path, source_id='STATE', range_id='STATE') else: products = None # Create and return stationary model return StationaryModel(operator=system_lincombOperator, rhs=rhs_lincombOperator, parameter_space=parameter_space, products=products)