def main(): m = build_column(min_trays=8, max_trays=17, xD=0.95, xB=0.95) # Fix feed conditions m.feed['benzene'].fix(50) m.feed['toluene'].fix(50) m.T_feed.fix(368) m.feed_vap_frac.fix(0.40395) # Initial values of reflux and reboil ratios m.reflux_ratio.set_value(1.4) m.reboil_ratio.set_value(1.3) # Fix to be total condenser m.partial_cond.deactivate() m.total_cond.indicator_var.fix(1) # Give initial values of the tray existence/absence for t in m.conditional_trays: m.tray[t].indicator_var.set_value(1) m.no_tray[t].indicator_var.set_value(0) # Run custom initialization routine initialize(m) m.BigM = Suffix(direction=Suffix.LOCAL) m.BigM[None] = 100 SolverFactory('gdpopt').solve(m, tee=True, init_strategy='fix_disjuncts', mip_solver='glpk') log_infeasible_constraints(m, tol=1E-3) display_column(m) return m
def test_log_infeasible_constraints(self): """Test for logging of infeasible constraints.""" m = self.build_model() output = StringIO() with LoggingIntercept(output, 'pyomo.util.infeasible', logging.INFO): log_infeasible_constraints(m) expected_output = [ "CONSTR c: 1 < 2.0", "CONSTR c2: 1 != 4.0", "CONSTR c3: 1 > 0.0" ] self.assertEqual(output.getvalue().splitlines(), expected_output)
def solve_model(self, m): """Solve model""" # Solve model solve_status = self.opt.solve(m, tee=True, options=self.solver_options, keepfiles=self.keepfiles) # Log infeasible constraints if they exist log_infeasible_constraints(m) return m, solve_status
def test_log_infeasible_constraints(self): """Test for logging of infeasible constraints.""" m = self.build_model() output = StringIO() with LoggingIntercept(output, 'pyomo.util.infeasible', logging.INFO): log_infeasible_constraints(m) expected_output = [ "CONSTR c: 2.0 </= 1", "CONSTR c2: 1 =/= 4.0", "CONSTR c3: 1 </= 0.0", "CONSTR c5: 5.0 <?= missing variable value" ] self.assertEqual(expected_output, output.getvalue().splitlines())
def test_FP(self): """Test the feasibility pump algorithm.""" with SolverFactory('mindtpy') as opt: for model in model_list: results = opt.solve(model, strategy='FP', mip_solver=required_solvers[1], nlp_solver=required_solvers[0], bound_tolerance=1E-5) log_infeasible_constraints(model) self.assertTrue(is_feasible(model, self.get_config(opt)))
def test_FP_Feasibility_Pump1(self): """Test the feasibility pump algorithm.""" with SolverFactory('mindtpy') as opt: model = Feasibility_Pump1() print('\n Solving Feasibility_Pump1 with feasibility pump') results = opt.solve(model, strategy='FP', mip_solver=required_solvers[1], nlp_solver=required_solvers[0], bound_tolerance=1E-5) log_infeasible_constraints(model) self.assertTrue(is_feasible(model, self.get_config(opt)))
def test_FP_8PP(self): """Test the feasibility pump algorithm.""" with SolverFactory('mindtpy') as opt: model = EightProcessFlowsheet(convex=True) print('\n Solving 8PP problem using feasibility pump') results = opt.solve(model, strategy='FP', mip_solver=required_solvers[1], nlp_solver=required_solvers[0], bound_tolerance=1E-5) log_infeasible_constraints(model) self.assertTrue(is_feasible(model, self.get_config(opt)))
def print_infeasible_constraints(m, tol=1e-6, print_expression=False, print_variables=False, output_file=None): """ print the infeasble constraints in the model """ with _logging_handler(output_file) as logger: log_infeasible_constraints(m, tol=tol, logger=logger, log_expression=print_expression, log_variables=print_variables)
def test_FP_8PP_Norm_infinity_with_norm_constraint(self): """Test the feasibility pump algorithm.""" with SolverFactory('mindtpy') as opt: model = EightProcessFlowsheet(convex=True) print( '\n Solving 8PP problem using feasibility pump with Norm infinity in mip regularization problem' ) results = opt.solve(model, strategy='FP', mip_solver=required_solvers[1], nlp_solver=required_solvers[0], bound_tolerance=1E-5, fp_main_norm='L_infinity', fp_norm_constraint=False) log_infeasible_constraints(model) self.assertTrue(is_feasible(model, self.get_config(opt)))
def test_log_infeasible_constraints_verbose_variables(self): """Test for logging of infeasible constraints.""" m = self.build_model() output = StringIO() with LoggingIntercept(output, 'pyomo.util.infeasible', logging.INFO): log_infeasible_constraints(m, log_variables=True) expected_output = [ "CONSTR c1: 2.0 </= 1", " - VAR x: 1", "CONSTR c2: 1 =/= 4.0", " - VAR x: 1", "CONSTR c3: 1 </= 0.0", " - VAR x: 1", "CONSTR c5: 5.0 <?= missing variable value <?= 10.0", " - VAR z: None", "CONSTR c7: missing variable value =?= 6.0", " - VAR z: None", "CONSTR c8: 3.0 </= 1 <= 6.0", " - VAR x: 1", "CONSTR c9: 0.0 <= 1 </= 0.5", " - VAR x: 1", ] self.assertEqual(expected_output, output.getvalue().splitlines())
def print_results(m, results, start_time=None, finish_time=None): ''' Prints out the result from pyomo opimizer, the solver status, the final value of the cost function and the time the solver took in minutes. If the result is infesable then the constraints violated are printed as well. Inputs: results - Pyomo Optional: start_time - start time in seconds (I used time.time() from the time library) finish_time - finish time in seconds (I used time.time() from the time library) ''' print(results.solver.status) print(results.solver.termination_condition) print(m.Cost.expr()) if start_time != None and finish_time != None: print('Time:', (finish_time - start_time) / 60, 'min') if results.solver.termination_condition == "infeasible": print( '___________________________________________________________________' ) from pyomo.util.infeasible import log_infeasible_constraints log_infeasible_constraints(m)
def run_price_smoothing_heuristic_case(self, params, output_dir, overwrite=False): """Smooth prices over entire model horizon using approximated price functions""" # Get filename based on run mode if params['mode'] == 'bau_deviation_minimisation': filename = f"heuristic_baudev_ty-{params['transition_year']}_cp-{params['carbon_price']}.pickle" elif params['mode'] == 'price_change_minimisation': filename = f"heuristic_pdev_ty-{params['transition_year']}_cp-{params['carbon_price']}.pickle" elif params['mode'] == 'price_target': filename = f"heuristic_ptar_ty-{params['transition_year']}_cp-{params['carbon_price']}.pickle" else: raise Exception(f"Unexpected run mode: {params['mode']}") # Check if case already solved if (not overwrite) and (filename in os.listdir(output_dir)): print(f'Already solved: {filename}') return # Get hash for case case_id = self.get_hash(params) # Save case ID and associated model parameters self.save_hash(case_id, params, output_dir) # Start timer for model run t_start = time.time() self.algorithm_logger('run_price_smoothing_heuristic_case', 'Starting case with params: ' + str(params)) # Load REP results with open(os.path.join(output_dir, params['rep_filename']), 'rb') as f: rep_results = pickle.load(f) # Get results corresponding to last iteration of REP solution rep_iteration = rep_results['stage_2_rep'][max( rep_results['stage_2_rep'].keys())] # Model parameters used to initialise classes that construct and run models start, end, scenarios = params['start'], params['end'], params[ 'scenarios'] bau_initial_price = self.get_bau_initial_price(output_dir, start) # Classes used to construct and run primal and MPPDC programs primal = Primal(start, end, scenarios) baseline = BaselineUpdater(start, end, scenarios, params['transition_year']) # Construct primal program m_p = primal.construct_model() # Results to extract from primal program primal_keys = [ 'x_c', 'p', 'p_V', 'p_in', 'p_out', 'p_L', 'baseline', 'permit_price', 'YEAR_EMISSIONS', 'YEAR_EMISSIONS_INTENSITY', 'YEAR_SCHEME_REVENUE', 'TOTAL_SCHEME_REVENUE', 'C_MC', 'ETA', 'DELTA', 'RHO', 'EMISSIONS_RATE', 'YEAR_CUMULATIVE_SCHEME_REVENUE', 'YEAR_SCHEME_EMISSIONS_INTENSITY', 'OBJECTIVE' ] # Results to extract from baseline targeting model baseline_keys = [ 'YEAR_AVERAGE_PRICE', 'YEAR_AVERAGE_PRICE_0', 'YEAR_ABSOLUTE_PRICE_DIFFERENCE', 'TOTAL_ABSOLUTE_PRICE_DIFFERENCE', 'PRICE_WEIGHTS', 'YEAR_SCHEME_REVENUE', 'YEAR_CUMULATIVE_SCHEME_REVENUE', 'baseline' ] # Container for iteration results i_results = dict() # Initialise price setting generator input as results obtained from final REP iteration psg_input = rep_iteration # Initialise iteration counter counter = 1 while True: self.algorithm_logger('run_price_smoothing_heuristic_case', f'Starting iteration={counter}') # Identify price setting generators psg = baseline.prices.get_price_setting_generators_from_model_results( psg_input) # Construct model used to calibrate baseline m_b = baseline.construct_model(psg) # Update parameters m_b = baseline.update_parameters(m_b, psg_input) m_b.YEAR_AVERAGE_PRICE_0 = float(bau_initial_price) m_b.PRICE_WEIGHTS.store_values(params['price_weights']) # Activate constraints and objectives depending on case being run m_b.NON_NEGATIVE_TRANSITION_REVENUE_CONS.activate() if params['mode'] == 'bau_deviation_minimisation': # Set the price target to be BAU price bau_price_target = {y: bau_initial_price for y in m_b.Y} m_b.YEAR_AVERAGE_PRICE_TARGET.store_values(bau_price_target) # Activate price targeting constraints and objective m_b.PRICE_TARGET_DEVIATION_1.activate() m_b.PRICE_TARGET_DEVIATION_2.activate() m_b.OBJECTIVE_PRICE_TARGET_DIFFERENCE.activate() # Append name of objective so objective value can be extracted, and create filename for case baseline_keys.append('OBJECTIVE_PRICE_TARGET_DIFFERENCE') elif params['mode'] == 'price_change_minimisation': # Activate constraints penalised price deviations over successive years m_b.PRICE_CHANGE_DEVIATION_1.activate() m_b.PRICE_CHANGE_DEVIATION_2.activate() m_b.OBJECTIVE_PRICE_DEVIATION.activate() # Append name of objective so objective value can be extracted, and create filename for case baseline_keys.append('OBJECTIVE_PRICE_DEVIATION') elif params['mode'] == 'price_target': # Set target price trajectory to prices obtained from BAU model over same period m_b.YEAR_AVERAGE_PRICE_TARGET.store_values( params['price_target']) # Activate price targeting constraints and objective function m_b.PRICE_TARGET_DEVIATION_1.activate() m_b.PRICE_TARGET_DEVIATION_2.activate() m_b.OBJECTIVE_PRICE_TARGET_DIFFERENCE.activate() # Append name of objective so objective value can be extracted, and create filename for case baseline_keys.append('OBJECTIVE_PRICE_TARGET_DIFFERENCE') else: raise Exception(f"Unexpected run mode: {params['mode']}") for y in m_b.Y: if y >= params['transition_year']: m_b.YEAR_NET_SCHEME_REVENUE_NEUTRAL_CONS[y].activate() # Solve model m_b, m_b_status = baseline.solve_model(m_b) r_b = copy.deepcopy( {k: self.extract_result(m_b, k) for k in baseline_keys}) # Update baselines and permit prices in primal model for y in m_p.Y: m_p.baseline[y].fix(m_b.baseline[y].value) m_p.permit_price[y].fix(m_b.PERMIT_PRICE[y].value) # Solve primal program m_p, m_p_status = primal.solve_model(m_p) # Log all infeasible constraints log_infeasible_constraints(m_p) # Get results r_p = copy.deepcopy( {v: self.extract_result(m_p, v) for v in primal_keys}) r_p['PRICES'] = copy.deepcopy({ k: m_p.dual[m_p.POWER_BALANCE[k]] for k in m_p.POWER_BALANCE.keys() }) i_results[counter] = {'primal': r_p, 'auxiliary': r_b} # Max difference in capacity sizing decision between iterations max_capacity_difference = self.get_successive_iteration_difference( psg_input, r_p, 'x_c') print(f'Max capacity difference: {max_capacity_difference} MW') # Max absolute baseline difference between successive iterations max_baseline_difference = self.get_successive_iteration_difference( psg_input, r_p, 'baseline') print( f'{counter}: Maximum baseline difference = {max_baseline_difference} tCO2/MWh' ) self.algorithm_logger('run_price_smoothing_heuristic_case', f'Finished iteration={counter}') # If baseline difference between successive iterations is sufficiently small then stop if max_baseline_difference < 0.05: break # Stop iterating if max iteration limit exceeded elif counter > 9: message = f'Max iterations exceeded. Exiting loop.' print(message) self.algorithm_logger('run_price_smoothing_heuristic_case', message) break else: # Update dictionary of price setting generator program inputs psg_input = r_p # Update iteration counter counter += 1 self.algorithm_logger('run_price_smoothing_heuristic_case', f'Finished solving model') # Combine results into a single dictionary results = { **rep_results, 'stage_3_price_targeting': i_results, 'parameters': params } # Save results self.save_results(results, output_dir, filename) # Combine output for method (can be used for debugging) output = { 'auxiliary_model': m_b, 'auxiliary_status': m_b_status, 'primal_model': m_p, 'primal_status': m_p_status, 'results': results } # Total iterations total_iterations = max(i_results.keys()) # Save summary of the solution time solution_summary = { 'case_id': case_id, 'mode': params['mode'], 'carbon_price': params['carbon_price'], 'transition_year': params['transition_year'], 'total_solution_time': time.time() - t_start, 'total_iterations': total_iterations, 'max_capacity_difference': max_capacity_difference, 'max_baseline_difference': max_baseline_difference } self.save_solution_summary(solution_summary, output_dir) message = f"Finished heuristic case: " + str(solution_summary) self.algorithm_logger('run_price_smoothing_heuristic_case', message) return output
amodel.EnforceUpTimeConstraintsSubsequent = Constraint( amodel.generator, amodel.periods, rule=enforce_up_time_constraints_subsequent) amodel.EnforceDownTimeConstraintsInitial = Constraint( amodel.generator, rule=enforce_down_time_constraints_initial) amodel.EnforceDownTimeConstraintsSubsequent = Constraint( amodel.generator, amodel.periods, rule=enforce_down_time_constraints_subsequent) amodel.TotalCostObjective = Objective(rule=total_cost_objective_rule, sense=minimize) #%% compile instance = amodel.create_instance() from pyomo.util.infeasible import log_infeasible_constraints #instance.pprint() opt = SolverFactory("gurobi", solver_io="python") results = opt.solve(instance, tee=True, keepfiles=False) log_infeasible_constraints(instance) #, options_string="mip_tolerances_integrality=1e-9 mip_tolerances_mipgap=0" for v in instance.component_objects(Var, active=True): print("Variable component object", v) print("Type of component object: ", str(type(v))[1:-1]) # Stripping <> for nbconvert varobject = getattr(instance, str(v)) print("Type of object accessed via getattr: ", str(type(varobject))[1:-1]) for index in varobject: print(" ", index, varobject[index].value)
def run_price_smoothing_mppdc_case(self, params, output_dir, overwrite=False): """Run case to smooth prices over model horizon, subject to total revenue constraint""" # Get case filename based on run mode if params['mode'] == 'bau_deviation_minimisation': filename = f"mppdc_baudev_ty-{params['transition_year']}_cp-{params['carbon_price']}.pickle" elif params['mode'] == 'price_change_minimisation': filename = f"mppdc_pdev_ty-{params['transition_year']}_cp-{params['carbon_price']}.pickle" elif params['mode'] == 'price_target': filename = f"mppdc_ptar_ty-{params['transition_year']}_cp-{params['carbon_price']}.pickle" else: raise Exception(f"Unexpected run mode: {params['mode']}") # Check if case already solved if (not overwrite) and (filename in os.listdir(output_dir)): print(f'Already solved: {filename}') return # Construct hash for case ID case_id = self.get_hash(params) # Save hash and associated parameters self.save_hash(case_id, params, output_dir) # Start timer for model run t_start = time.time() self.algorithm_logger( 'run_price_smoothing_mppdc_case', 'Starting MPPDC case with params: ' + str(params)) # Load REP results with open(os.path.join(output_dir, params['rep_filename']), 'rb') as f: rep_results = pickle.load(f) # Get results corresponding to last iteration of REP solution rep_iteration = rep_results['stage_2_rep'][max( rep_results['stage_2_rep'].keys())] # Extract parameters from last iteration of REP program results start, end, scenarios = params['start'], params['end'], params[ 'scenarios'] bau_initial_price = self.get_bau_initial_price(output_dir, start) # Classes used to construct and run primal and MPPDC programs mppdc = MPPDCModel(start, end, scenarios, params['transition_year']) primal = Primal(start, end, scenarios) # Construct MPPDC m_m = mppdc.construct_model(include_primal_constraints=True) # Construct primal model m_p = primal.construct_model() # Update MPPDC model parameters m_m.YEAR_AVERAGE_PRICE_0 = float(bau_initial_price) m_m.PRICE_WEIGHTS.store_values(params['price_weights']) # Activate necessary constraints depending on run mode m_m.NON_NEGATIVE_TRANSITION_REVENUE_CONS.activate() if params['mode'] == 'bau_deviation_minimisation': m_m.PRICE_BAU_DEVIATION_1.activate() m_m.PRICE_BAU_DEVIATION_2.activate() elif params['mode'] == 'price_change_minimisation': m_m.PRICE_CHANGE_DEVIATION_1.activate() m_m.PRICE_CHANGE_DEVIATION_2.activate() elif params['mode'] == 'price_target': m_m.YEAR_AVERAGE_PRICE_TARGET.store_values(params['price_target']) m_m.PRICE_TARGET_DEVIATION_1.activate() m_m.PRICE_TARGET_DEVIATION_2.activate() else: raise Exception(f"Unexpected run mode: {params['mode']}") for y in m_m.Y: if y >= params['transition_year']: m_m.YEAR_NET_SCHEME_REVENUE_NEUTRAL_CONS[y].activate() # Primal variables primal_vars = [ 'x_c', 'p', 'p_in', 'p_out', 'q', 'p_V', 'p_L', 'permit_price' ] fixed_vars = {v: rep_iteration[v] for v in primal_vars} # Results to extract from MPPDC model mppdc_keys = [ 'x_c', 'p', 'p_V', 'p_in', 'p_out', 'p_L', 'q', 'baseline', 'permit_price', 'lamb', 'YEAR_EMISSIONS', 'YEAR_EMISSIONS_INTENSITY', 'YEAR_SCHEME_EMISSIONS_INTENSITY', 'YEAR_SCHEME_REVENUE', 'YEAR_CUMULATIVE_SCHEME_REVENUE', 'TOTAL_SCHEME_REVENUE', 'YEAR_ABSOLUTE_PRICE_DIFFERENCE', 'YEAR_AVERAGE_PRICE_0', 'YEAR_AVERAGE_PRICE', 'YEAR_SUM_CUMULATIVE_PRICE_DIFFERENCE_WEIGHTED', 'OBJECTIVE', 'YEAR_ABSOLUTE_PRICE_DIFFERENCE_WEIGHTED', 'TOTAL_ABSOLUTE_PRICE_DIFFERENCE_WEIGHTED', 'YEAR_CUMULATIVE_PRICE_DIFFERENCE_WEIGHTED', 'sd_1', 'sd_2', 'STRONG_DUALITY_VIOLATION_COST', 'TRANSITION_YEAR', 'PRICE_WEIGHTS' ] # Container for iteration results i_results = {} # Initialise iteration counter counter = 1 # Placeholder for max difference variables max_baseline_difference = None max_capacity_difference = None while True: self.algorithm_logger('run_price_smoothing_mppdc_case', f'Starting iteration={counter}') # Fix MPPDC variables m_m = mppdc.fix_variables(m_m, fixed_vars) # Solve MPPDC m_m, m_m_status = mppdc.solve_model(m_m) # Model timeout will cause sub-optimal termination condition if m_m_status.solver.termination_condition != TerminationCondition.optimal: i_results[counter] = None self.algorithm_logger('run_price_smoothing_mppdc_case', f'Sub-optimal solution') self.algorithm_logger( 'run_price_smoothing_mppdc_case', f'User time: {m_m_status.solver.user_time}s') # No primal model solved m_p, m_p_status = None, None break # Log infeasible constraints log_infeasible_constraints(m_m) # Results from MPPDC program r_m = copy.deepcopy( {v: self.extract_result(m_m, v) for v in mppdc_keys}) i_results[counter] = r_m # Update primal program with baselines and permit prices obtained from MPPDC model for y in m_p.Y: m_p.baseline[y].fix(m_m.baseline[y].value) m_p.permit_price[y].fix(m_m.permit_price[y].value) # Solve primal model m_p, m_p_status = primal.solve_model(m_p) log_infeasible_constraints(m_p) # Results from primal program p_r = copy.deepcopy( {v: self.extract_result(m_p, v) for v in primal_vars}) p_r['PRICES'] = copy.deepcopy({ k: m_p.dual[m_p.POWER_BALANCE[k]] for k in m_p.POWER_BALANCE.keys() }) i_results[counter]['primal'] = p_r # Max absolute capacity difference between MPPDC and primal program max_capacity_difference = max( abs(m_m.x_c[k].value - m_p.x_c[k].value) for k in m_m.x_c.keys()) print(f'Max capacity difference: {max_capacity_difference} MW') # Max absolute baseline difference between MPPDC and primal program max_baseline_difference = max( abs(m_m.baseline[k].value - m_p.baseline[k].value) for k in m_m.baseline.keys()) print( f'Max baseline difference: {max_baseline_difference} tCO2/MWh') # Check if capacity variables have changed if max_baseline_difference < 0.05: break # Check if max iterations exceeded elif counter > 9: message = f'Max iterations exceeded. Exiting loop.' print(message) self.algorithm_logger('run_price_smoothing_mppdc_case', message) break else: # Update dictionary of fixed variables to be used in next iteration fixed_vars = {v: p_r[v] for v in primal_vars} self.algorithm_logger('run_price_smoothing_mppdc_case', f'Finished iteration={counter}') counter += 1 self.algorithm_logger('run_price_smoothing_mppdc_case', f'Finished solving model') # Combine results into a single dictionary results = { **rep_results, 'stage_3_price_targeting': i_results, 'parameters': params } # Save results self.save_results(results, output_dir, filename) # Method output output = { 'mppdc_model': m_m, 'mppdc_status': m_m_status, 'primal_model': m_p, 'primal_status': m_p_status, 'results': results } self.algorithm_logger('run_price_smoothing_mppdc_case', 'Finished MPPDC case') # Total iterations total_iterations = max(i_results.keys()) # Save summary of the solution time solution_summary = { 'case_id': case_id, 'mode': params['mode'], 'carbon_price': params['carbon_price'], 'transition_year': params['transition_year'], 'total_solution_time': time.time() - t_start, 'total_iterations': total_iterations, 'max_capacity_difference': max_capacity_difference, 'max_baseline_difference': max_baseline_difference } self.save_solution_summary(solution_summary, output_dir) message = f"Finished MPPDC case: " + str(solution_summary) self.algorithm_logger('run_price_smoothing_mppdc_case', message) return output
def calculate_Fenske(xD, xB): m = ConcreteModel() min_T, max_T = 300, 400 m.comps = Set(initialize=['benzene', 'toluene']) m.trays = Set(initialize=['condenser', 'reboiler']) m.Kc = Var(m.comps, m.trays, doc='Phase equilibrium constant', domain=NonNegativeReals, initialize=1, bounds=(0, 1000)) m.T = Var(m.trays, doc='Temperature [K]', domain=NonNegativeReals, bounds=(min_T, max_T)) m.T['condenser'].fix(82 + 273.15) m.T['reboiler'].fix(108 + 273.15) m.P = Var(doc='Pressure [bar]', bounds=(0, 5)) m.P.fix(1.01) m.T_ref = 298.15 m.gamma = Var(m.comps, m.trays, doc='liquid activity coefficent of component on tray', domain=NonNegativeReals, bounds=(0, 10), initialize=1) m.Pvap = Var( m.comps, m.trays, doc='pure component vapor pressure of component on tray in bar', domain=NonNegativeReals, bounds=(1E-3, 5), initialize=0.4) m.Pvap_X = Var( m.comps, m.trays, doc='Related to fraction of critical temperature (1 - T/Tc)', bounds=(0.25, 0.5), initialize=0.4) m.pvap_const = { 'benzene': { 'A': -6.98273, 'B': 1.33213, 'C': -2.62863, 'D': -3.33399, 'Tc': 562.2, 'Pc': 48.9 }, 'toluene': { 'A': -7.28607, 'B': 1.38091, 'C': -2.83433, 'D': -2.79168, 'Tc': 591.8, 'Pc': 41.0 } } @m.Constraint(m.comps, m.trays) def phase_equil_const(_, c, t): return m.Kc[c, t] * m.P == (m.gamma[c, t] * m.Pvap[c, t]) @m.Constraint(m.comps, m.trays) def Pvap_relation(_, c, t): k = m.pvap_const[c] x = m.Pvap_X[c, t] return (log(m.Pvap[c, t]) - log(k['Pc'])) * (1 - x) == ( k['A'] * x + k['B'] * x**1.5 + k['C'] * x**3 + k['D'] * x**6) @m.Constraint(m.comps, m.trays) def Pvap_X_defn(_, c, t): k = m.pvap_const[c] return m.Pvap_X[c, t] == 1 - m.T[t] / k['Tc'] @m.Constraint(m.comps, m.trays) def gamma_calc(_, c, t): return m.gamma[c, t] == 1 m.relative_volatility = Var(m.trays, domain=NonNegativeReals) @m.Constraint(m.trays) def relative_volatility_calc(_, t): return m.Kc['benzene', t] == (m.Kc['toluene', t] * m.relative_volatility[t]) @m.Expression() def fenske(_): return log((xD / (1 - xD)) * (xB / (1 - xB))) / (log( sqrt(m.relative_volatility['condenser'] * m.relative_volatility['reboiler']))) SolverFactory('ipopt').solve(m, tee=True) from pyomo.util.infeasible import log_infeasible_constraints log_infeasible_constraints(m, tol=1E-3) m.fenske.display()
def generate_support(config, index, parcels, logger): cache = config["meta"]["cache"].joinpath(__CACHE_SUPPORT_DIR__) cache.mkdir(parents=True, exist_ok=True) cached_data = cache.joinpath("{}_{}.npy".format(index[0], index[1])) if cached_data.exists(): return parcel = parcels.parcel_at_index(index) surface = parcel[1] # we want to support the bottom crop_index, cropped = crop_nans(surface) logger.debug("Cropping NaNs took surface shape from {} to {}".format(surface.shape, cropped.shape)) # Given the minimum feature size the user has requested, we can down-sample the original dataset to reduce # the complexity of the optimization problem to solve. There's no real loss in fidelity if we down-sample # here, we don't need a very smooth shape coming out of this algorithm to ensure a reasonable 3D mesh later. # Down-sampling here also is critical to have any reasonable guarantee that the optimization problem can be # solved in human timescales (minutes) instead of geological ones using the native resolution. Allow 30 pixels # per minimum feature diameter: inter-pixel distance is the 1/10th the minimum feature radius. pixel_size = (config["model"]["support"]["minimum_feature_radius_millimeters"] / 1e3) / 5 scaling_factor = (config["printer"]["xy_resolution_microns"] / 1e6) / pixel_size # Any interpolations on data-sets with NaN values are prone to extrapolate the NaNs across the whole data-set. # Setting these values to some non-NaN value will result in the surface being "pulled" there at the edges when # we interpolate, so a Gaussian kernel is used to fill nearby values with values similar to those in the dataset. # As a boundary between NaN and non-NaN values without much curvature will result in NaN fills of about half # magnitude, we optimistically multiply by 1.75 here to reduce as much of the "pull" as possible. blurred = numpy.copy(cropped) if numpy.isnan(cropped).any(): with_zeroes = numpy.where(numpy.isnan(cropped), 0, cropped) blur = ndimage.gaussian_filter(with_zeroes, sigma=10, mode="nearest") blurred = numpy.where(numpy.isnan(cropped), 1.75 * blur, cropped) scaled = ndimage.zoom(blurred, zoom=scaling_factor) if numpy.isnan(cropped).any(): # Let's replace pixels in the scaled version with NaN when the raw data was all NaNs only_nans = numpy.where(numpy.isnan(cropped), 1, 0) scaled_nans = ndimage.zoom(only_nans, zoom=scaling_factor) scaled = numpy.where(scaled_nans == 1, numpy.nan, scaled) logger.debug( "Scaling surface to a pixel size of {}mm (using a scaling factor of 1:{:.2f}) results in a surface shape of {}.".format( config["model"]["support"]["minimum_feature_radius_millimeters"], 1 / scaling_factor, scaled.shape ) ) # Slice the total Z height in this parcel into layers as high as our pixels are wide, or our angle calculations # get all messy. Add one layer for the fixed variables modeling the build plate at the bottom. layers = math.floor((numpy.nanmax(scaled) - numpy.nanmin(scaled)) / pixel_size) + 2 i, j, k = scaled.shape[0], scaled.shape[1], layers logger.debug( "Full abstract model with shape ({},{},{}) will contain up to {} variables.".format( scaled.shape[0], scaled.shape[1], layers, scaled.shape[0] * scaled.shape[1] * layers ) ) regularized_surface = numpy.floor((scaled - numpy.nanmin(scaled)) / pixel_size) + 1 indices = numpy.where(numpy.isnan(regularized_surface), 0, regularized_surface) logger.debug( "After fixing surface pixels, those above them and NaN regions, {} variables remain.".format( int(numpy.ndarray.sum(indices)) ) ) logger.debug("Instantiating model...") instance_start = datetime.now() feature_radius_pixels = math.sqrt(3) / 2 model = concrete_model( (i, j, k), regularized_surface, feature_radius_pixels, config["model"]["support"]["self_supporting_angle_degrees"], 10.0, 1.0, logger ) logger.debug("Instantiating model took {}.".format(datetime.now() - instance_start)) fixing_start = datetime.now() logger.debug("Fixing variables...") for I in range(i + 1): for J in range(j + 1): model.nodal_support[I, J, 0].fix(1) # build plate for I in range(i): for J in range(j): surface_index = indices[I, J] if surface_index == 0: continue # no surface here model.elemental_density[I, J, surface_index].fix(1) # surface for K in range(int(surface_index) + 1, k): model.elemental_density[I, J, K].fix(0) # above the surface logger.debug("Fixing variables took {}.".format(datetime.now() - fixing_start)) opt = pyo.SolverFactory('ipopt') solving_start = datetime.now() logger.debug("Solving for optimal support...") opt.solve(model, tee=True) logger.debug("Solving for optimal support took {}.".format(datetime.now() - solving_start)) log_infeasible_constraints(model, logger=logger, log_variables=True, log_expression=True) output = numpy.empty((i, j, k - 1)) output[:] = numpy.nan for I in range(i): for J in range(j): for K in range(1, k): # ignore the build plate output[I, J, K - 1] = model.elemental_density[I, J, K].value # TODO: uncrop to size of original data numpy.save(cached_data, output)
def run_rep_case(self, params, output_dir, overwrite=False): """Run carbon tax scenario""" # Extract case parameters start, end, scenarios = params['start'], params['end'], params[ 'scenarios'] permit_prices = params['permit_prices'] # First run carbon tax case baselines = {y: float(0) for y in range(start, end + 1)} # Check that carbon tax is same for all years in model horizon assert len(set(permit_prices.values( ))) == 1, f'Permit price trajectory is not flat: {permit_prices}' # Extract carbon price in first year (same as all other used). To be used in filename. carbon_price = permit_prices[start] # Filename for REP case filename = f'rep_cp-{carbon_price:.0f}.pickle' # Check if model has already been solved if (not overwrite) and (filename in os.listdir(output_dir)): print(f'Already solved: {filename}') return # Construct hash for case ID case_id = self.get_hash(params) # Save hash and associated parameters self.save_hash(case_id, params, output_dir) # Start timer for model run t_start = time.time() self.algorithm_logger('run_rep_case', 'Starting case with params: ' + str(params)) # Results to extract result_keys = [ 'x_c', 'p', 'p_V', 'p_in', 'p_out', 'q', 'p_L', 'baseline', 'permit_price', 'YEAR_EMISSIONS', 'YEAR_EMISSIONS_INTENSITY', 'YEAR_SCHEME_REVENUE', 'TOTAL_SCHEME_REVENUE', 'C_MC', 'ETA', 'DELTA', 'RHO', 'EMISSIONS_RATE', 'YEAR_SCHEME_EMISSIONS_INTENSITY', 'YEAR_CUMULATIVE_SCHEME_REVENUE', 'OBJECTIVE' ] # Run carbon tax case self.algorithm_logger('run_rep_case', 'Starting carbon tax case solve') m, status = self.run_primal_fixed_policy(start, end, scenarios, permit_prices, baselines) log_infeasible_constraints(m) self.algorithm_logger('run_rep_case', 'Finished carbon tax case solve') # Model results carbon_tax_results = { k: self.extract_result(m, k) for k in result_keys } # Add dual variable from power balance constraint carbon_tax_results['PRICES'] = { k: m.dual[m.POWER_BALANCE[k]] for k in m.POWER_BALANCE.keys() } # Update baselines so they = emissions intensity of output from participating generators baselines = carbon_tax_results['YEAR_SCHEME_EMISSIONS_INTENSITY'] # Container for iteration results i_results = dict() # Iteration counter counter = 1 # Initialise iteration input to carbon tax scenario results (used to check stopping criterion) i_input = carbon_tax_results while True: # Re-run model with new baselines self.algorithm_logger( 'run_rep_case', f'Starting solve for REP iteration={counter}') m, status = self.run_primal_fixed_policy(start, end, scenarios, permit_prices, baselines) log_infeasible_constraints(m) self.algorithm_logger( 'run_rep_case', f'Finished solved for REP iteration={counter}') # Model results i_output = {k: self.extract_result(m, k) for k in result_keys} # Get dual variable values from power balance constraint i_output['PRICES'] = { k: m.dual[m.POWER_BALANCE[k]] for k in m.POWER_BALANCE.keys() } # Add results to iteration results container i_results[counter] = copy.deepcopy(i_output) # Check max absolute capacity difference between successive iterations max_capacity_difference = self.get_successive_iteration_difference( i_input, i_output, 'x_c') print( f'{counter}: Maximum capacity difference = {max_capacity_difference} MW' ) # Max absolute baseline difference between successive iterations max_baseline_difference = self.get_successive_iteration_difference( i_input, i_output, 'baseline') print( f'{counter}: Maximum baseline difference = {max_baseline_difference} tCO2/MWh' ) # If max absolute difference between successive iterations is sufficiently small stop iterating if max_baseline_difference < 0.05: break # If iteration limit exceeded elif counter > 9: message = f'Max iterations exceeded. Exiting loop.' print(message) self.algorithm_logger('run_rep_case', message) break # Update iteration inputs (used to check stopping criterion in next iteration) i_input = copy.deepcopy(i_output) # Update baselines to be used in next iteration baselines = i_output['YEAR_SCHEME_EMISSIONS_INTENSITY'] # Update iteration counter counter += 1 # Combine results into single dictionary results = { 'stage_1_carbon_tax': carbon_tax_results, 'stage_2_rep': i_results } # Save results self.save_results(results, output_dir, filename) # Dictionary to be returned by method output = {'results': results, 'model': m, 'status': status} self.algorithm_logger('run_rep_case', f'Finished REP case') # Total number of iterations processed total_iterations = max(i_results.keys()) # Save summary of the solution time solution_summary = { 'case_id': case_id, 'mode': params['mode'], 'carbon_price': carbon_price, 'total_solution_time': time.time() - t_start, 'total_iterations': total_iterations, 'max_capacity_difference': max_capacity_difference, 'max_baseline_difference': max_baseline_difference } self.save_solution_summary(solution_summary, output_dir) message = 'Finished REP case: ' + str(solution_summary) self.algorithm_logger('run_rep_case', message) return output
m.fs.costing.cost_process() # m.fs.crystallizer.k_param = 0.06 # solving m.fs.crystallizer.initialize(outlvl=idaeslog.DEBUG) assert_units_consistent(m) # check that units are consistent assert ( degrees_of_freedom(m) == 0 ) # check that the degrees of freedom are what we expect solver = get_solver() # solver = SolverFactory('ipopt') # solver.options = {'tol': 1e-8, 'nlp_scaling_method': 'user-scaling', 'halt_on_ampl_error': 'yes'} results = solver.solve(m, tee=True, symbolic_solver_labels=True) output = StringIO() with LoggingIntercept(output, "pyomo.util.infeasible", logging.INFO): log_infeasible_constraints(m) print(output.getvalue().splitlines()) assert results.solver.termination_condition == TerminationCondition.optimal # m.fs.crystallizer.display() m.fs.crystallizer.report() # assert False ########################################## # # Case 2: Fix crystallizer yield ########################################## print("\n--- Case 2 ---") # m.fs.crystallizer.T_crystallization.unfix() m.fs.crystallizer.solids.flow_mass_phase_comp[0, "Sol", "NaCl"].unfix() m.fs.crystallizer.crystallization_yield["NaCl"].fix(0.7)
def _process_solver_results(self, model): """ Method to post process results from 'solve_model' method :param model: solved model :return: dictionary of solver results """ from pyomo.environ import Set, RealSet, RangeSet, Param, Var # Write solution to stdout/file if self.write_solution and ( str(self.solver_results.solver.Status) in ['ok'] or str(self.solver_results.solver.Termination_condition) in ['maxTimeLimit']): if self.write_solution_to_stdout: # Write solution to screen self.solver_results.write() else: pass # Write solution to file named <model_name>/DD_MM_YY_HH_MM_xx.json # Create (if it does not exist) the '_results_store' folder results_store_folder = path.join('_results_store', self.__model_name_str, '') if not path.exists(results_store_folder): makedirs(results_store_folder) if self.__pyomo_version <= '5.7.1': model.solutions.store_to( self.solver_results) # define solutions storage folder else: pass self.__current_datetime_str = datetime.now().strftime( "%d_%m_%y_%H_%M_") file_suffix = 0 # Results filename if self.__model_name_str == 'Unknown' or len( self.__model_name_str) <= 10: result_filename = self.__model_name_str + self.__current_datetime_str + str( file_suffix) + ".json" else: result_filename = self.__model_name_str[:4] + '..' + self.__model_name_str[-4:] + \ self.__current_datetime_str + str(file_suffix) + ".json" while path.exists(results_store_folder + result_filename): file_suffix += 1 result_filename = self.__current_datetime_str + str( file_suffix) + ".json" result_filename = self.__current_datetime_str + str( file_suffix) + ".json" self.solver_results.write(filename=results_store_folder + result_filename, format="json") else: pass # Create dictionary to for solver statistics and solution final_result = dict() # Include the default solver results from opt_solver.solve & current state of model final_result['solver_results_def'] = self.solver_results final_result[ 'model'] = model # copy.deepcopy(model) # include all model attributes # _include solver statistics acceptable_termination_conditions = [ 'maxTimeLimit', 'maxIterations', 'locallyOptimal', 'globallyOptimal', 'optimal', 'other' ] if str(self.solver_results.solver.Status) == 'ok' or ( str(self.solver_results.solver.Status) == 'aborted' and str(self.solver_results.solver.Termination_condition) in acceptable_termination_conditions): final_result['solver'] = dict( ) # Create dictionary for solver statistics final_result['solver'] = { 'status': str(self.solver_results.solver.Status), 'solver_message': str(self.solver_results.solver.Message), 'termination_condition': str(self.solver_results.solver.Termination_condition) } try: final_result['solver'][ 'wall_time'] = self.solver_results.solver.wall_time except AttributeError: final_result['solver']['wall_time'] = None try: final_result['solver'][ 'wall_time'] = self.solver_results.solver.wall_time except AttributeError: final_result['solver']['wall_time'] = None try: final_result['solver'][ 'cpu_time'] = self.solver_results.solver.time except AttributeError: final_result['solver']['cpu_time'] = None try: if self.solver_results.problem.Upper_bound == 0 and self.solver_results.problem.Lower_bound == 0: final_result['solver']['gap'] = 0 elif self.solver_results.problem.Upper_bound == 0: final_result['solver']['gap'] = abs( round(self.solver_results.problem.Lower_bound, 2)) else: final_result['solver']['gap'] = abs(round( (self.solver_results.problem.Upper_bound - self.solver_results.problem.Lower_bound) \ * 100 / self.solver_results.problem.Upper_bound, 2)) except AttributeError: final_result['solver']['gap'] = None # Check state of available solution if self.__pyomo_version < '5.7.1': try: for key, value in final_result['solver_results_def'][ 'Solution'][0]['Objective'].items(): objective_value = value['Value'] final_result['solution_status'] = True except: final_result['solution_status'] = False objective_value = 'Unk' else: try: objective_value = model.solutions.solutions[0]._entry[ 'objective'][list( model.solutions.solutions[0]._entry['objective']. keys())[0]][1]['Value'] final_result['solution_status'] = True except: final_result['solution_status'] = False objective_value = 'Unk' if self.return_solution and final_result['solution_status']: # True: include values of all model objects in 'final_result' # write list of sets, parameters and variables final_result['sets_list'] = [ str(i) for i in chain(model.component_objects(Set), model.component_objects(RealSet), model.component_objects(RangeSet)) if (re.split("_", str(i))[-1] != 'index') if (re.split("_", str(i))[-1] != 'domain') ] final_result['parameters_list'] = [ str(i) for i in model.component_objects(Param) ] final_result['variables_list'] = [ str(i) for i in model.component_objects(Var) ] # Populate final results for sets, parameters and variables # Create method to return array def indexed_value_extract(index, object): return array([value for value in object[index].value]) # Sets final_result['sets'] = dict() for set in final_result['sets_list']: set_object = getattr(model, str(set)) final_result['sets'][set] = array( list(set_object)) # save array of set elements # Parameters final_result['parameters'] = dict() if self.verbosity: print('\nProcessing parameters . . . ') else: pass for par in final_result['parameters_list']: if self.verbosity: print(par, ' ', end="") else: pass par_object = getattr(model, str(par)) par_object_dim = par_object.dim( ) # get dimension of parameter if par_object_dim == 0: final_result['parameters'][par] = par_object.value elif par_object_dim == 1: final_result['parameters'][par] = array( [value for value in par_object.values()]) else: try: par_set_list = self.__get_set_list(par_object) par_set_lens = [ len(getattr(model, str(set))) for set in par_set_list ] except AttributeError: par_set_list = [str(par_object._index.name)] temp_par_set = getattr(model, par_set_list[0]) if temp_par_set.dimen == 1: par_set_lens = [len(temp_par_set)] else: par_set_lens = [ len(set) for set in self.__get_set_list_alt(temp_par_set) ] # print(par_set_lens) final_result['parameters'][par] = zeros( shape=par_set_lens, dtype=float) if par_object_dim == 2: if len(par_set_list) == par_object_dim: for ind_i, i in enumerate( getattr(model, str(par_set_list[0]))): for ind_j, j in enumerate( getattr(model, str(par_set_list[1]))): final_result['parameters'][par][ind_i][ ind_j] = par_object[i, j] elif len(par_set_list) == 1: for set in par_set_list: for ind, (i, j) in enumerate( getattr(model, str(set))): # print(type(final_result['parameters'][par]),final_result['parameters'][par]) # print(i,j,final_result['parameters'][par][i-1][j-1]) # print(par_set_lens) # print(par_set_list) # print(par_object_dim) if self.__pyomo_version < '5.7': # FIXME: Better way to do this? final_result['parameters'][par][ i - 1][j - 1] = par_object[i, j] else: final_result['parameters'][par][ ind] = par_object[i, j] else: pass else: pass # FIXME 3-dimensional variables are not considered yet # Variables final_result['variables'] = dict() # Include objective functionv value final_result['variables']['Objective'] = objective_value if self.verbosity: print('\nProcessing results of variables . . . ') else: pass for variable in final_result['variables_list']: try: if self.verbosity: print(variable, ' ', end="") else: pass variable_object = getattr(model, str(variable)) variable_object_dim = variable_object.dim( ) # get dimension of variable if variable_object_dim == 0: final_result['variables'][ variable] = variable_object.value elif variable_object_dim == 1: final_result['variables'][variable] = array([ value.value for value in variable_object.values() ]) else: try: variable_set_list = self.__get_set_list( variable_object) variable_set_lens = [ len(getattr(model, str(set))) for set in variable_set_list ] except AttributeError: variable_set_list = [ str(variable_object._index.name) ] temp_variable_set = getattr( model, variable_set_list[0]) if temp_variable_set.dimen == 1: variable_set_lens = [ len(temp_variable_set) ] else: variable_set_lens = [ len(set) for set in self.__get_set_list_alt( temp_variable_set) ] # print(variable_set_lens) final_result['variables'][variable] = zeros( shape=variable_set_lens, dtype=float) if variable_object_dim == 2: if len(variable_set_list ) == variable_object_dim: for ind_i, i in enumerate( getattr( model, str(variable_set_list[0]))): for ind_j, j in enumerate( getattr( model, str(variable_set_list[1])) ): final_result['variables'][ variable][ind_i][ ind_j] = variable_object[ i, j].value elif len(variable_set_list) == 1: for set in variable_set_list: for ind, (i, j) in enumerate( getattr(model, str(set))): # print(type(final_result['variables'][variable]),final_result['variables'][variable]) # print(i,j,final_result['variables'][variable][i-1][j-1]) if self.__pyomo_version < '5.7': # FIXME: Better way to do this? final_result['variables'][ variable][i - 1][ j - 1] = variable_object[ i, j].value else: final_result['variables'][ variable][ ind] = variable_object[ i, j].value else: pass else: pass # FIXME 3-dimensional variables are not considered yet except AttributeError: pass print('\n') else: # if solver_status != 'ok' or amongst acceptable termination conditions if self.debug_mode: if self.verbose_debug_mode: # Print troublesome constraints from pyomo.util.infeasible import log_infeasible_constraints log_infeasible_constraints(model) else: pass self.__psmsg('An optimal solution could not be processed' ) # leave program running to debug else: self.__pemsg('An optimal solution could not be processed') else: # if solver_status != ok self.__pemsg('An optimal solution was NOT found') return final_result
def run_bau_case(self, params, output_dir, overwrite=False): """Run business-as-usual case""" # Case filename filename = 'bau_case.pickle' # Check if case exists if (not overwrite) and (filename in os.listdir(output_dir)): print(f'Already solved: {filename}') return # Construct hash for case case_id = self.get_hash(params) # Save case params and associated hash self.save_hash(case_id, params, output_dir) # Extract case parameters for model start, end, scenarios = params['start'], params['end'], params[ 'scenarios'] # Start timer for case run t_start = time.time() message = f"""Starting case: first_year={start}, final_year={end}, scenarios_per_year={scenarios}""" self.algorithm_logger('run_bau_case', message) # Permit prices and emissions intensity baselines for BAU case (all 0) permit_prices = {y: float(0) for y in range(start, end + 1)} baselines = {y: float(0) for y in range(start, end + 1)} # Run model self.algorithm_logger('run_bau_case', 'Starting solve') m, status = self.run_primal_fixed_policy(start, end, scenarios, permit_prices, baselines) log_infeasible_constraints(m) self.algorithm_logger('run_bau_case', 'Finished solve') # Results to extract result_keys = [ 'x_c', 'p', 'p_V', 'p_in', 'p_out', 'p_L', 'baseline', 'permit_price', 'YEAR_EMISSIONS', 'YEAR_EMISSIONS_INTENSITY', 'YEAR_SCHEME_REVENUE', 'TOTAL_SCHEME_REVENUE', 'C_MC', 'ETA', 'DELTA', 'RHO', 'EMISSIONS_RATE', 'OBJECTIVE' ] # Model results results = {k: self.extract_result(m, k) for k in result_keys} # Add dual variable from power balance constraint results['PRICES'] = { k: m.dual[m.POWER_BALANCE[k]] for k in m.POWER_BALANCE.keys() } # Save results self.save_results(results, output_dir, filename) # Combine output in dictionary. To be returned by method. output = {'results': results, 'model': m, 'status': status} # Solution summary solution_summary = { 'case_id': case_id, 'mode': params['mode'], 'total_solution_time': time.time() - t_start } self.save_solution_summary(solution_summary, output_dir) self.algorithm_logger( 'run_bau_case', f'Finished BAU case: case_id={case_id}, total_solution_time={time.time() - t_start}s' ) return output