def test_base_max_theta_parameter_functional(): thetas = (ExpressionParameterFunctional('2*mu[0]', {'mu': 1}, derivative_expressions={ 'mu': ['2'] }), ConstantParameterFunctional(1)) # theta prime can for example be the derivatives of all theta thetas_prime = tuple([theta.d_mu('mu', 0) for theta in thetas]) mu_bar = -3 gamma_mu_bar = 10 theta = BaseMaxThetaParameterFunctional(thetas_prime, thetas, mu_bar, gamma_mu_bar) thetas = [ ConstantParameterFunctional(t) if not isinstance(t, ParameterFunctional) else t for t in thetas ] thetas_prime = [ ConstantParameterFunctional(t) if not isinstance(t, ParameterFunctional) else t for t in thetas_prime ] mu = theta.parameters.parse(1) mu_bar = theta.parameters.parse(mu_bar) expected_value = gamma_mu_bar * np.abs( np.max( np.array([t(mu) for t in thetas_prime]) / np.array([np.abs(t(mu_bar)) for t in thetas]))) actual_value = theta.evaluate(mu) assert expected_value == actual_value
def test_min_theta_parameter_functional_fails_for_wrong_input(): thetas = (ExpressionParameterFunctional('2*mu[0]', {'mu': 1}), ConstantParameterFunctional(1), -1) mu_bar = -3 alpha_mu_bar = 10 with pytest.raises(AssertionError): theta = MinThetaParameterFunctional(thetas, mu_bar, alpha_mu_bar)
def test_min_theta_parameter_functional(): thetas = (ExpressionParameterFunctional('2*mu', {'mu': ()}), ConstantParameterFunctional(1), 1) mu_bar = 3 alpha_mu_bar = 10 theta = MinThetaParameterFunctional(thetas, mu_bar, alpha_mu_bar) thetas = [ ConstantParameterFunctional(t) if not isinstance(t, ParameterFunctional) else t for t in thetas ] mu = 1 expected_value = alpha_mu_bar * np.min( np.array([t(mu) for t in thetas]) / np.array([t(mu_bar) for t in thetas])) actual_value = theta.evaluate(mu) assert expected_value == actual_value
def test_max_theta_parameter_functional(): thetas = (ExpressionParameterFunctional('2*mu[0]', {'mu': 1}), ConstantParameterFunctional(1), -1) mu_bar = -3 gamma_mu_bar = 10 theta = MaxThetaParameterFunctional(thetas, mu_bar, gamma_mu_bar) thetas = [ ConstantParameterFunctional(t) if not isinstance(t, ParameterFunctional) else t for t in thetas ] mu = theta.parameters.parse(1) mu_bar = theta.parameters.parse(mu_bar) expected_value = gamma_mu_bar * np.abs( np.max( np.array([t(mu) for t in thetas]) / np.array([t(mu_bar) for t in thetas]))) actual_value = theta.evaluate(mu) assert expected_value == actual_value
def discretize_quadratic_pdeopt_stationary_cg(problem, diameter=np.sqrt(2) / 100., weights=None, parameter_scales=None, domain_of_interest=None, desired_temperature=None, mu_for_u_d=None, mu_for_tikhonov=False, parameters_in_q=True, product='h1_l2_boundary', solver_options=None, use_corrected_functional=True, use_corrected_gradient=False, adjoint_approach=False): if use_corrected_functional: print('I am using the corrected functional!!') else: print('I am using the OLD functional!!') if use_corrected_gradient: print('I am using the corrected gradient!!') else: if adjoint_approach: print( 'I am using the adjoint approach for computing the gradient!!') print('I am using the OLD gradient!!') mu_bar = _construct_mu_bar(problem) print(mu_bar) primal_fom, data = discretize_stationary_cg(problem, diameter=diameter, grid_type=RectGrid, energy_product=mu_bar) #Preassemble non parametric parts: simplify, put the constant part in only one function simplified_operators = [ ZeroOperator(primal_fom.solution_space, primal_fom.solution_space) ] simplified_coefficients = [1] to_pre_assemble = ZeroOperator(primal_fom.solution_space, primal_fom.solution_space) if isinstance(primal_fom.operator, LincombOperator): for (i, coef) in enumerate(primal_fom.operator.coefficients): if isinstance(coef, Parametric): simplified_coefficients.append(coef) simplified_operators.append(primal_fom.operator.operators[i]) else: to_pre_assemble += coef * primal_fom.operator.operators[i] else: to_pre_assemble += primal_fom.operator simplified_operators[0] += to_pre_assemble simplified_operators[0] = simplified_operators[0].assemble() lincomb_operator = LincombOperator( simplified_operators, simplified_coefficients, solver_options=primal_fom.operator.solver_options) simplified_rhs = [ ZeroOperator(primal_fom.solution_space, NumpyVectorSpace(1)) ] simplified_rhs_coefficients = [1] to_pre_assemble = ZeroOperator(primal_fom.solution_space, NumpyVectorSpace(1)) if isinstance(primal_fom.rhs, LincombOperator): for (i, coef) in enumerate(primal_fom.rhs.coefficients): if isinstance(coef, Parametric): simplified_rhs_coefficients.append(coef) simplified_rhs.append(primal_fom.rhs.operators[i]) else: to_pre_assemble += coef * primal_fom.rhs.operators[i] else: to_pre_assemble += primal_fom.rhs simplified_rhs[0] += to_pre_assemble simplified_rhs[0] = simplified_rhs[0].assemble() lincomb_rhs = LincombOperator(simplified_rhs, simplified_rhs_coefficients) primal_fom = primal_fom.with_(operator=lincomb_operator, rhs=lincomb_rhs) grid = data['grid'] d = grid.dim # prepare data functions if desired_temperature is not None: u_desired = ConstantFunction(desired_temperature, d) if domain_of_interest is None: domain_of_interest = ConstantFunction(1., d) if mu_for_u_d is not None: domain_of_interest = ConstantFunction(1., d) modifified_mu = mu_for_u_d.copy() for key in mu_for_u_d.keys(): if len(mu_for_u_d[key]) == 0: modifified_mu.pop(key) u_d = primal_fom.solve(modifified_mu) else: assert desired_temperature is not None u_d = InterpolationOperator(grid, u_desired).as_vector() if grid.reference_element is square: L2_OP = L2ProductQ1 else: L2_OP = L2ProductP1 Restricted_L2_OP = L2_OP(grid, data['boundary_info'], dirichlet_clear_rows=False, coefficient_function=domain_of_interest) l2_u_d_squared = Restricted_L2_OP.apply2(u_d, u_d)[0][0] constant_part = 0.5 * l2_u_d_squared # assemble output functional from pdeopt.theta import build_output_coefficient if weights is not None: weight_for_J = weights.pop('state') else: weight_for_J = 1. if isinstance(weight_for_J, dict): assert len( weight_for_J ) == 4, 'you need to give all derivatives including second order' state_functional = ExpressionParameterFunctional( weight_for_J['function'], weight_for_J['parameter_type'], derivative_expressions=weight_for_J['derivative'], second_derivative_expressions=weight_for_J['second_derivatives']) elif isinstance(weight_for_J, float) or isinstance(weight_for_J, int): state_functional = ConstantParameterFunctional(weight_for_J) else: assert 0, 'state weight needs to be an integer or a dict with derivatives' if mu_for_tikhonov: if mu_for_u_d is not None: mu_for_tikhonov = mu_for_u_d else: assert isinstance(mu_for_tikhonov, dict) output_coefficient = build_output_coefficient( primal_fom.parameter_type, weights, mu_for_tikhonov, parameter_scales, state_functional, constant_part) else: output_coefficient = build_output_coefficient( primal_fom.parameter_type, weights, None, parameter_scales, state_functional, constant_part) output_functional = {} output_functional['output_coefficient'] = output_coefficient output_functional['linear_part'] = LincombOperator( [VectorOperator(Restricted_L2_OP.apply(u_d))], [-state_functional]) # j(.) output_functional['bilinear_part'] = LincombOperator( [Restricted_L2_OP], [0.5 * state_functional]) # k(.,.) output_functional['d_u_linear_part'] = LincombOperator( [VectorOperator(Restricted_L2_OP.apply(u_d))], [-state_functional]) # j(.) output_functional['d_u_bilinear_part'] = LincombOperator( [Restricted_L2_OP], [state_functional]) # 2k(.,.) l2_boundary_product = RobinBoundaryOperator( grid, data['boundary_info'], robin_data=(ConstantFunction(1, 2), ConstantFunction(1, 2)), name='l2_boundary_product') # choose product if product == 'h1_l2_boundary': opt_product = primal_fom.h1_semi_product + l2_boundary_product # h1_semi + l2_boundary elif product == 'fixed_energy': opt_product = primal_fom.energy_product # energy w.r.t. mu_bar (see above) else: assert 0, 'product: {} is not nown'.format(product) print('my product is {}'.format(product)) primal_fom = primal_fom.with_( products=dict(opt=opt_product, l2_boundary=l2_boundary_product, **primal_fom.products)) pde_opt_fom = QuadraticPdeoptStationaryModel( primal_fom, output_functional, opt_product=opt_product, use_corrected_functional=use_corrected_functional, use_corrected_gradient=use_corrected_gradient) return pde_opt_fom, data, mu_bar
def discretize_fin_pdeopt_stationary_cg(problem, grid, boundary_info, mu_for_u_d, product='h1_l2_boundary', use_corrected_functional=True, use_corrected_gradient=False, add_constant_term=False): if use_corrected_functional: print('I am using the corrected functional!!') else: print('I am using the OLD functional!!') if use_corrected_gradient: print('I am using the corrected gradient!!') else: print('I am using the OLD gradient!!') mu_bar = _construct_mu_bar(problem) print(mu_bar) primal_fom, data = discretize_stationary_cg(problem, grid=grid, boundary_info=boundary_info, energy_product=mu_bar) u_d = primal_fom.solve(mu_for_u_d) Boundary_Functional = primal_fom.rhs.operators[1] T_root_d = Boundary_Functional.apply_adjoint(u_d) T_root_d_squared = T_root_d.to_numpy()[0][0]**2 # assemble output functional from pdeopt.theta import build_output_coefficient weights = {} mu_for_tikhonov = {} parameter_scales = {} for key, item in mu_for_u_d.items(): if isinstance(item, list): weights[key] = 1. / item[0]**2 else: weights[key] = 1. / item**2 mu_for_tikhonov[key] = mu_for_u_d[key] parameter_scales[key] = 1. if add_constant_term: state_functional = ConstantParameterFunctional(1.) output_coefficient = build_output_coefficient( primal_fom.parameter_type, weights, mu_for_tikhonov, parameter_scales, state_functional=state_functional, constant_part=0.5 * T_root_d_squared) else: output_coefficient = build_output_coefficient( primal_fom.parameter_type, weights, mu_for_tikhonov, parameter_scales) output_functional = {} class Boundary_squared_half(ImmutableObject): def __init__(self, source, range): self.__auto_init(locals()) self.linear = False def apply2(self, u, v, mu=None): return Boundary_Functional.apply_adjoint(u).to_numpy( ) * Boundary_Functional.apply_adjoint(v).to_numpy() * 0.5 class Boundary_squared(ImmutableObject): def __init__(self, source, range): self.__auto_init(locals()) self.linear = False def apply2(self, u, v, mu=None): return Boundary_Functional.apply_adjoint(u).to_numpy( ) * Boundary_Functional.apply_adjoint(v).to_numpy() def apply(self, u, mu=None): b = (Boundary_Functional.apply_adjoint(u).to_numpy()[0][0] * Boundary_Functional).as_vector() return b source_ = Boundary_Functional.source range_ = Boundary_Functional.range output_functional['output_coefficient'] = output_coefficient output_functional[ 'linear_part'] = -1 * Boundary_Functional * T_root_d.to_numpy()[0][ 0] # j(.) output_functional['bilinear_part'] = Boundary_squared_half( range_, range_) # k(.,.) output_functional[ 'd_u_linear_part'] = -1 * Boundary_Functional * T_root_d.to_numpy()[0][ 0] # j(.) output_functional['d_u_bilinear_part'] = Boundary_squared( range_, range_) # 2k(.,.) # choose product l2_boundary_product = RobinBoundaryOperator( grid, data['boundary_info'], robin_data=(ConstantFunction(1, 2), ConstantFunction(1, 2)), name='l2_boundary_product') # choose product if product == 'h1_l2_boundary': opt_product = primal_fom.h1_semi_product + l2_boundary_product # h1_semi + l2_boundary elif product == 'fixed_energy': opt_product = primal_fom.energy_product # energy w.r.t. mu_bar (see above) else: assert 0, 'product: {} is not known'.format(product) print('my product is {}'.format(product)) primal_fom = primal_fom.with_( products=dict(opt=opt_product, l2_boundary=l2_boundary_product, **primal_fom.products)) pde_opt_fom = QuadraticPdeoptStationaryModel( primal_fom, output_functional, opt_product=opt_product, fin_model=True, use_corrected_functional=use_corrected_functional, use_corrected_gradient=use_corrected_gradient) return pde_opt_fom, data, mu_bar
def build_output_coefficient(parameter_type, parameter_weights=None, mu_d_=None, parameter_scales=None, state_functional=None, constant_part=None): def _collect_indices(item): item_dict = {} if sum(item) < 2: # case () and (1,) item_dict[1] = () new_item = () else: assert len(item) == 1 # case (l,) with l > 1 for l in range(item[0]): item_dict[l] = (l, ) new_item = item return item_dict, new_item if parameter_weights is None: parameter_weights = { 'walls': 1, 'heaters': 1, 'doors': 1, 'windows': 1 } if parameter_scales is None: parameter_scales = {'walls': 1, 'heaters': 1, 'doors': 1, 'windows': 1} mu_d = {} if mu_d_ is None: mu_d_ = {} for key, item in parameter_type.items(): if not isinstance(parameter_weights[key], list): if len(item) == 0: # for the case when item = () parameter_weights[key] = [parameter_weights[key]] else: parameter_weights[key] = [ parameter_weights[key] for i in range(item[0]) ] if key not in mu_d_: mu_d_[key] = 0 if not isinstance(mu_d_[key], list): if len(item) == 0: # for the case when item = () mu_d[key] = [mu_d_[key]] else: if isinstance(mu_d_[key], type(np.array([]))): mu_d[key] = [mu_d_[key][i] for i in range(item[0])] else: mu_d[key] = [mu_d_[key] for i in range(item[0])] else: mu_d[key] = mu_d_[key] parameter_functionals = [] if constant_part is not None: # sigma_d * constant_part parameter_functionals.append(state_functional * constant_part) # + 1 parameter_functionals.append(ConstantParameterFunctional(1)) def make_zero_expressions(parameter_type): zero_derivative_expression = {} for key, item in parameter_type.items(): zero_expressions = np.array([], dtype='<U60') item_dict, new_item = _collect_indices(item) for (l, index) in item_dict.items(): zero_expressions = np.append(zero_expressions, ['0']) if len(zero_expressions) == 1: zero_expressions = np.array(zero_expressions[0], dtype='<U60') else: zero_expressions = np.array(zero_expressions, dtype='<U60') zero_derivative_expression[key] = zero_expressions return zero_derivative_expression #prepare dict def make_dict_zero_expressions(parameter_type): zero_dict = {} for key, item in parameter_type.items(): index_dict, new_item = _collect_indices(item) zero_ = np.empty(new_item, dtype=dict) zero_dict[key] = zero_ for (l, index) in index_dict.items(): zero_dict[key][index] = make_zero_expressions(parameter_type) return zero_dict for key, item in parameter_type.items(): if len(item) == 0: # for the case when item = () weight = parameter_weights[key][0] derivative_expression = make_zero_expressions(parameter_type) derivative_expression[key] = '{}*{}**2*'.format(weight,parameter_scales[key]) \ +'({}[{}]'.format(key,()) \ +'-{}'.format(mu_d[key][0]) +')' second_derivative_expressions = make_dict_zero_expressions( parameter_type) second_derivative_expressions[key][()][key] = '{}*{}**2'.format( weight, parameter_scales[key]) parameter_functionals.append(ExpressionParameterFunctional('{}*{}**2*0.5*({}[{}]'.format( weight,parameter_scales[key],key,()) \ +'-{}'.format(mu_d[key][0])+')**2', parameter_type, derivative_expressions=derivative_expression, second_derivative_expressions=second_derivative_expressions)) else: for i in range(item[0]): weight = parameter_weights[key][i] derivative_expression = make_zero_expressions(parameter_type) second_derivative_expressions = make_dict_zero_expressions( parameter_type) if item[0] == 1: derivative_expression[key] = \ '{}*{}**2*({}[{}]-'.format(weight,parameter_scales[key],key,i) + '{}'.format(mu_d[key][i])+')' second_derivative_expressions[key][( )][key] = '{}*{}**2'.format(weight, parameter_scales[key]) else: derivative_expression[key][i] = \ '{}*{}**2*({}[{}]-'.format(weight,parameter_scales[key],key,i) + '{}'.format(mu_d[key][i])+')' second_derivative_expressions[key][i][key][ i] = '{}*{}**2'.format(weight, parameter_scales[key]) parameter_functionals.append(ExpressionParameterFunctional('{}*{}**2*0.5*({}[{}]'.format( \ weight,parameter_scales[key],key,i) \ +'-{}'.format(mu_d[key][i])+')**2', parameter_type, derivative_expressions=derivative_expression, second_derivative_expressions=second_derivative_expressions)) def mapping(mu): ret = 0 for f in parameter_functionals: ret += f.evaluate(mu) return ret def make_mapping(key, i): def sum_derivatives(mu): ret = 0 if i == -1: index = () else: index = (i, ) for f in parameter_functionals: ret += f.d_mu(key, index).evaluate(mu) return ret return sum_derivatives def make_second_mapping(key, i): def sum_second_derivatives(mu): ret = 0 if i == -1: index = () else: index = (i, ) for f in parameter_functionals: ret += f.d_mu(key, index).d_mu(key, index).evaluate(mu) return ret return sum_second_derivatives def make_zero_mappings(parameter_type): zero_derivative_mappings = {} for key, item in parameter_type.items(): zero_mappings = np.array([], dtype=object) zero_mapping = lambda mu: 0. if len(item) == 0: # for the case when item = () zero_mappings = np.append(zero_mappings, [zero_mapping]) zero_mappings = np.array(zero_mappings[0]) else: for i in range(item[0]): zero_mappings = np.append(zero_mappings, [zero_mapping]) if item[0] == 1: zero_mappings = np.array(zero_mappings[0]) zero_derivative_mappings[key] = zero_mappings return zero_derivative_mappings #prepare dict def make_dict_zero_mapping(parameter_type): zero_dict = {} for key, item in parameter_type.items(): index_dict, new_item = _collect_indices(item) zero_ = np.empty(new_item, dtype=dict) zero_dict[key] = zero_ for (l, index) in index_dict.items(): zero_dict[key][index] = make_zero_mappings(parameter_type) return zero_dict derivative_mappings = make_zero_mappings(parameter_type) for key, item in parameter_type.items(): if len(item) == 0: # for the case when item = () derivative_mappings[key] = np.array(make_mapping(key, -1)) else: for i in range(item[0]): if item[0] == 1: derivative_mappings[key] = np.array(make_mapping(key, -1)) else: derivative_mappings[key][i] = make_mapping(key, i) second_derivative_mappings = make_dict_zero_mapping(parameter_type) for key, item in parameter_type.items(): if len(item) == 0: # for the case when item = () second_derivative_mappings[key][()][key] = np.array( make_second_mapping(key, -1)) else: for i in range(item[0]): if item[0] == 1: second_derivative_mappings[key][()][key] = np.array( make_second_mapping(key, -1)) else: second_derivative_mappings[key][i][key][ i] = make_second_mapping(key, i) output_coefficient = GenericParameterFunctional( mapping, parameter_type, derivative_mappings=derivative_mappings, second_derivative_mappings=second_derivative_mappings) return output_coefficient
def build_output_coefficient(parameters, parameter_weights=None, mu_d_=None, parameter_scales=None, state_functional=None, constant_part=None): if parameter_weights is None: parameter_weights= {'walls': 1, 'heaters': 1, 'doors': 1, 'windows': 1} if parameter_scales is None: parameter_scales= {'walls': 1, 'heaters': 1, 'doors': 1, 'windows': 1} mu_d={} if mu_d_ is None: mu_d_ = {} for key, size in parameters.items(): if not isinstance(parameter_weights[key], list): parameter_weights[key] = [parameter_weights[key] for i in range(size)] if key not in mu_d_: mu_d_[key] = [0 for i in range(size)] if not isinstance(mu_d_[key], list): mu_d[key] = [mu_d_[key][i] for i in range(size)] else: mu_d[key] = mu_d_[key] parameter_functionals = [] if constant_part is not None: # sigma_d * constant_part parameter_functionals.append(state_functional * constant_part) # + 1 parameter_functionals.append(ConstantParameterFunctional(1)) def make_zero_expressions(parameters): zero_derivative_expression = {} for key, size in parameters.items(): zero_expressions = np.array([], dtype='<U60') for l in range(size): zero_expressions = np.append(zero_expressions, ['0']) zero_expressions = np.array(zero_expressions, dtype='<U60') zero_derivative_expression[key] = zero_expressions return zero_derivative_expression #prepare dict def make_dict_zero_expressions(parameters): zero_dict = {} for key, size in parameters.items(): zero_ = np.empty(size, dtype=dict) zero_dict[key] = zero_ for l in range(size): zero_dict[key][l] = make_zero_expressions(parameters) return zero_dict for key, size in parameters.items(): for i in range(size): weight = parameter_weights[key][i] derivative_expression = make_zero_expressions(parameters) second_derivative_expressions = make_dict_zero_expressions(parameters) derivative_expression[key][i] = \ '{}*{}**2*({}[{}]-'.format(weight,parameter_scales[key],key,i) + '{}'.format(mu_d[key][i])+')' second_derivative_expressions[key][i][key][i]= '{}*{}**2'.format(weight,parameter_scales[key]) parameter_functionals.append(ExpressionParameterFunctional('{}*{}**2*0.5*({}[{}]'.format( \ weight,parameter_scales[key],key,i) \ +'-{}'.format(mu_d[key][i])+')**2', parameters, derivative_expressions=derivative_expression, second_derivative_expressions=second_derivative_expressions)) def mapping(mu): ret = 0 for f in parameter_functionals: ret += f.evaluate(mu) return ret def make_mapping(key, i): def sum_derivatives(mu): ret = 0 for f in parameter_functionals: ret += f.d_mu(key, i).evaluate(mu) return ret return sum_derivatives def make_second_mapping(key, i): def sum_second_derivatives(mu): ret = 0 for f in parameter_functionals: ret += f.d_mu(key, i).d_mu(key, i).evaluate(mu) return ret return sum_second_derivatives def make_zero_mappings(parameters): zero_derivative_mappings = {} for key, size in parameters.items(): zero_mappings = np.array([], dtype=object) zero_mapping = lambda mu: 0. for i in range(size): zero_mappings = np.append(zero_mappings, [zero_mapping]) zero_derivative_mappings[key] = zero_mappings return zero_derivative_mappings #prepare dict def make_dict_zero_mapping(parameters): zero_dict = {} for key, size in parameters.items(): zero_ = np.empty(size, dtype=dict) zero_dict[key] = zero_ for l in range(size): zero_dict[key][l] = make_zero_mappings(parameters) return zero_dict derivative_mappings = make_zero_mappings(parameters) for key, size in parameters.items(): for i in range(size): derivative_mappings[key][i] = make_mapping(key,i) second_derivative_mappings = make_dict_zero_mapping(parameters) for key, size in parameters.items(): for i in range(size): second_derivative_mappings[key][i][key][i] = make_second_mapping(key,i) output_coefficient = GenericParameterFunctional(mapping, parameters, derivative_mappings=derivative_mappings, second_derivative_mappings=second_derivative_mappings) return output_coefficient