Source code for prommis.superstructure.superstructure_function

#####################################################################################################
# “PrOMMiS” was produced under the DOE Process Optimization and Modeling for Minerals Sustainability
# (“PrOMMiS”) initiative, and is copyright (c) 2023-2026 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory, et al. All rights reserved.
# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license information.
#####################################################################################################
"""
Superstructure Code
===================

Author: Chris Laliwala
"""

import pyomo.environ as pyo
from pyomo.environ import units as pyunits

from idaes.core.scaling import CustomScalerBase

from prommis.superstructure.add_superstructure_blocks import (
    add_byproduct_valorization_cons,
    add_byproduct_valorization_params,
    add_byproduct_valorization_vars,
    add_capital_cost_cons,
    add_cash_flow_cons,
    add_costing_objective_functions,
    add_costing_params,
    add_costing_vars,
    add_discretized_costing_params,
    add_environmental_impact_cons,
    add_environmental_impact_params,
    add_environmental_impact_vars,
    add_feed_params,
    add_mass_balance_cons,
    add_mass_balance_params,
    add_mass_balance_vars,
    add_objective_function_choice_param,
    add_operating_cost_cons,
    add_operating_params,
    add_plant_lifetime_params,
    add_profit_cons,
    add_supe_formulation_params,
)
from prommis.superstructure.check_superstructure_inputs import (
    check_byproduct_valorization_params,
    check_discretized_costing_params,
    check_environmental_impact_params,
    check_feed_params,
    check_objective_function_choice,
    check_operating_params,
    check_plant_lifetime_params,
    check_supe_formulation_params,
)


[docs] class SuperstructureScaler(CustomScalerBase):
[docs] def variable_scaling_routine( self, model, overwrite: bool = False, submodel_scalers: dict = None ): # Scale flowrate variables for c, v in model.fs.f.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) for c, v in model.fs.f_in.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) for c, v in model.fs.f_out.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) for c, v in model.fs.piecewise_flow_entering.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) # scale costing variables for c, v in model.fs.costing.net_present_value.items(): self.set_variable_scaling_factor(v, 1e-6, overwrite=overwrite) for c, v in model.fs.costing.main_product_profit.items(): self.set_variable_scaling_factor(v, 1e-6, overwrite=overwrite) for c, v in model.fs.costing.total_profit.items(): self.set_variable_scaling_factor(v, 1e-6, overwrite=overwrite) for c, v in model.fs.costing.piecewise_equipment_cost.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.equipment_cost.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) for c, v in model.fs.costing.total_plant_cost.items(): self.set_variable_scaling_factor(v, 1e-6, overwrite=overwrite) for c, v in model.fs.costing.financing.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) for c, v in model.fs.costing.other_costs.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.total_overnight_cost.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.opt_variable_operating_cost.items(): self.set_variable_scaling_factor(v, 1e-3, overwrite=overwrite) for c, v in model.fs.costing.aggregate_variable_operating_cost.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.operators_per_option.items(): self.set_variable_scaling_factor(v, 1e1, overwrite=overwrite) for c, v in model.fs.costing.cost_of_labor.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.m_and_sm.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) for c, v in model.fs.costing.sa_and_qa_qc.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) for c, v in model.fs.costing.s_ip_r_and_d.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) for c, v in model.fs.costing.a_and_sl.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.fb.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.pt_and_i.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.aggregate_fixed_operating_cost.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.total_overnight_cost_expended.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.plant_overhead.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) for c, v in model.fs.costing.total_operating_expense.items(): self.set_variable_scaling_factor(v, 1e-6, overwrite=overwrite) for c, v in model.fs.costing.cash_flow.items(): self.set_variable_scaling_factor(v, 1e-5, overwrite=overwrite) # scale vars only if environmental impacts are considered if hasattr(model.fs, "environmental_impacts"): for c, v in model.fs.environmental_impacts.option_yearly_impacts.items(): self.set_variable_scaling_factor(v, 1e-7, overwrite=overwrite) for c, v in model.fs.environmental_impacts.total_yearly_impacts.items(): self.set_variable_scaling_factor(v, 1e-8, overwrite=overwrite) for c, v in model.fs.environmental_impacts.total_impacts.items(): self.set_variable_scaling_factor(v, 1e-9, overwrite=overwrite) # scale vars only if byproduct valorization is being considered if hasattr(model.fs, "byproduct_valorization"): for c, v in model.fs.byproduct_valorization.byproduct_produced.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) for c, v in model.fs.byproduct_valorization.byproduct_produced.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) if hasattr(model.fs.costing, "byproduct_profit"): for c, v in model.fs.costing.byproduct_profit.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) if hasattr(model.fs.costing, "total_byproduct_profit"): for c, v in model.fs.costing.total_byproduct_profit.items(): self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) # scale var only if NPV is chosen as the objective function if hasattr(model.fs.costing, "opt_profit"): for c, v in model.fs.costing.opt_profit.items(): self.set_variable_scaling_factor(v, 1e-6, overwrite=overwrite) # scale var only if COR is chosen as the objective function if hasattr(model.fs.costing, "cost_of_recovery"): for c, v in model.fs.costing.cost_of_recovery.items(): self.set_variable_scaling_factor(v, 1, overwrite=overwrite)
[docs] def constraint_scaling_routine( self, model, overwrite: bool = False, submodel_scalers: dict = None ): # Scale flowrate constraints for j, c in model.fs.inlet_flow_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.init_flow_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.intermediate_flow_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.outlet_flow_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.init_flow_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.f_in_big_m_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.f_out_big_m_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.max_flow_entering_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) # Scale costing constraints for j, c in model.fs.costing.calculate_total_profit_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.discrete_opts_equipment_cost_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.add_units_to_piecewise_costs.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_total_plant_cost_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_financing_cost_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_other_costs_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_total_overnight_cost_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_opt_yearly_variable_expense_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for ( j, c, ) in ( model.fs.costing.calculate_total_yearly_variable_operating_costs_cons.items() ): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_cost_of_labor_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_m_and_sm_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_sa_and_qa_qc_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_s_ip_r_and_d_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_a_and_sl_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_fb_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_pt_and_i_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for ( j, c, ) in model.fs.costing.calculate_total_yearly_fixed_operating_costs_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for ( j, c, ) in model.fs.costing.calculate_total_overnight_cost_expended_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_plant_overhead_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_total_operating_expense_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_cash_flows.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_net_present_value_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.costing.calculate_main_product_profit_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) # Scale constraint only if npv is objective function if hasattr(model.fs.costing, "calculate_final_opts_profit_cons"): for j, c in model.fs.costing.calculate_final_opts_profit_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) # Scale constraint only if cor is objective function if hasattr(model.fs.costing, "set_net_present_value_to_zero_con"): for j, c in model.fs.costing.set_net_present_value_to_zero_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) # Scale constraints only if byproduct valorization is considered if hasattr(model.fs, "byproduct_valorization"): for ( j, c, ) in ( model.fs.byproduct_valorization.calculate_byproduct_produced_cons.items() ): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) if hasattr(model.fs.costing, "calculate_byproduct_profit_cons"): for j, c in model.fs.costing.calculate_byproduct_profit_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) if hasattr(model.fs.costing, "calculate_opt_byprod_val_cons"): for j, c in model.fs.costing.calculate_opt_byprod_val_cons.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) # Scale constraints only if environmental impacts are considered if hasattr(model.fs, "environmental_impacts"): for ( j, c, ) in ( model.fs.environmental_impacts.calculate_opt_yearly_impacts_con.items() ): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for ( j, c, ) in model.fs.environmental_impacts.calculate_yearly_impacts_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for ( j, c, ) in model.fs.environmental_impacts.calculate_total_impacts_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, ) for j, c in model.fs.environmental_impacts.epsilon_con.items(): self.scale_constraint_by_nominal_value( c, scheme="inverse_maximum", overwrite=overwrite, )
[docs] def define_custom_units(): """ This function defines custom units that are needed throughout the model. """ # Define custom units pyunits.load_definitions_from_strings(["EOL_Product = [item]"]) pyunits.load_definitions_from_strings(["Operator = [item]"]) pyunits.load_definitions_from_strings(["Disassembly_Unit = [item]"]) pyunits.load_definitions_from_strings(["USD = [currency]"])
[docs] def build_model( ### Choice of objective function obj_func, ### Plant lifetime parameters plant_start, plant_lifetime, ### Feed parameters available_feed, collection_rate, tracked_comps, prod_comp_mass, ### Superstructure formulation parameters num_stages, options_in_stage, option_outlets, option_efficiencies, ### Operating parameters profit, opt_var_oc_params, operators_per_discrete_unit, yearly_cost_per_unit, capital_cost_per_unit, processing_rate, num_operators, labor_rate, ### Discretized costing parameters discretized_purchased_equipment_cost, ### Environmental impacts parameters consider_environmental_impacts, options_environmental_impacts, epsilon, ### Byproduct valorization parameters consider_byproduct_valorization, byproduct_values, byproduct_opt_conversions, ): """ This function builds a superstructure model based on specifications from the user. Args: obj_func (ObjectiveFunctionChoice): Choice of objective function. Options are ``ObjectiveFunctionChoice.NET_PRESENT_VALUE`` or ``ObjectiveFunctionChoice.COST_OF_RECOVERY``. plant_start (int): The year that plant construction begins. plant_lifetime (int): The total lifetime of the plant, including plant construction. Must be at least three years. available_feed (dict): Total feedstock available (number of EOL products) for recycling each year. collection_rate (float): Collection rate for how much available feed is processed by the plant each year. tracked_comps (list): List of tracked components. prod_comp_mass (dict): Mass of tracked components per EOL product (kg / EOL product). num_stages (int): Number of total stages. options_in_stage (dict): Number of options in each stage. option_outlets (dict): Set of options k' in stage j+1 connected to option k in stage j. option_efficiencies (dict): Tracked component retention efficiency for each option. profit (dict): Profit per unit of product in terms of tracked components ($/kg tracked component). opt_var_oc_params (dict): Variable operating cost parameters for options that are continuous. Variable operating costs are assumed to be proportional to the feed entering the option. operators_per_discrete_unit (dict): Number of operators needed per discrete unit for options that utilize discrete units. yearly_cost_per_unit (dict): Yearly operating costs per unit ($/year) for options which utilize discrete units. capital_cost_per_unit (dict): Cost per unit ($) for options which utilize discrete units. processing_rate (dict): Processing rate per unit for options that utilize discrete units. In terms of end-of-life products disassembled per year per unit (number of EOL products / year). num_operators (dict): Number of operators needed for each continuous option. labor_rate (float): Yearly wage per operator ($ / year). discretized_purchased_equipment_cost (dict): Discretized cost by flows entering for each continuous option ($/kg). consider_environmental_impacts (bool): Choice of whether or not to consider environmental impacts. options_environmental_impacts (dict): Environmental impacts matrix. Unit chosen indicator per unit of incoming flowrate. epsilon (float): Epsilon factor for generating the Pareto front. consider_byproduct_valorization (bool): Decide whether or not to consider the valorization of byproducts. byproduct_values (dict): Byproducts considered, and their value ($/kg). byproduct_opt_conversions (dict): Conversion factors for each byproduct for each option. Returns: ConcreteModel: Pyomo superstructure model. """ ################################################################################################# ### Define custom units define_custom_units() ### Build model m = pyo.ConcreteModel() ### Objective function # Check that choice of objective function is feasible check_objective_function_choice(obj_func) # Add choice of objective function as parameter to model add_objective_function_choice_param(m, obj_func) ### Plant lifetime parameters # Check that plant lifetime parameters are feasible. check_plant_lifetime_params(plant_start, plant_lifetime) # Add plant lifetime parameters. add_plant_lifetime_params(m, plant_start, plant_lifetime) ### Feed parameters # Check that feed parameters are feasible. check_feed_params(m, available_feed, collection_rate, tracked_comps, prod_comp_mass) # Add feed parameters. add_feed_params(m, available_feed, collection_rate, tracked_comps, prod_comp_mass) ### Superstructure formulation parameters # Check that superstructure formulation parameters are feasible. check_supe_formulation_params( m, num_stages, options_in_stage, option_outlets, option_efficiencies ) # Add superstructure formulation parameters. add_supe_formulation_params( m, num_stages, options_in_stage, option_outlets, option_efficiencies ) ### Operating parameters # Check that operating parameters are feasible. check_operating_params( m, profit, opt_var_oc_params, operators_per_discrete_unit, yearly_cost_per_unit, capital_cost_per_unit, processing_rate, num_operators, labor_rate, ) # Add operating parameters. add_operating_params( m, profit, opt_var_oc_params, operators_per_discrete_unit, yearly_cost_per_unit, capital_cost_per_unit, processing_rate, num_operators, labor_rate, ) ### Costing parameters # Check that costing parameters are feasible. check_discretized_costing_params(m, discretized_purchased_equipment_cost) # Add discretized costing parameters. add_discretized_costing_params(m, discretized_purchased_equipment_cost) ### Mass balances # Generate mass balance parameters. add_mass_balance_params(m) # Generate mass balance variables. add_mass_balance_vars(m) # Generate mass balance constraints. add_mass_balance_cons(m) ### Costing # Generate costing parameters. add_costing_params(m) # Generate costing variables. add_costing_vars(m) ### Environmental impacts check_environmental_impact_params( m, consider_environmental_impacts, options_environmental_impacts, epsilon ) # only add params, vars, and cons if environmental impacts are considered. if consider_environmental_impacts: add_environmental_impact_params(m, options_environmental_impacts, epsilon) add_environmental_impact_vars(m) add_environmental_impact_cons(m) ### Byproduct valorization check_byproduct_valorization_params( m, consider_byproduct_valorization, byproduct_values, byproduct_opt_conversions ) # only add vars and cons if byproduct valorization is considered if consider_byproduct_valorization: add_byproduct_valorization_params( m, byproduct_values, byproduct_opt_conversions ) add_byproduct_valorization_vars(m) add_byproduct_valorization_cons(m) # Generate costing constraints. add_profit_cons(m, consider_byproduct_valorization) add_capital_cost_cons(m) add_operating_cost_cons(m) add_cash_flow_cons(m) add_costing_objective_functions(m, obj_func) ### Return model return m