diff --git a/ssc/cmod_csp_heatsink.cpp b/ssc/cmod_csp_heatsink.cpp index 7df3705bf..6ef462d09 100644 --- a/ssc/cmod_csp_heatsink.cpp +++ b/ssc/cmod_csp_heatsink.cpp @@ -60,7 +60,7 @@ class cm_csp_heatsink : public compute_module NS_HX_counterflow_eqs::E_UA_target_type ua_type = NS_HX_counterflow_eqs::E_UA_target_type::E_constant_UA; - mc_phx.initialize(hot_fl, 10, ua_type); + mc_phx.initialize(hot_fl, 10, ua_type, 2024.0); double q_dot = 1000.0; //[kWt] double T_co2_hot = 700.0; //[C] diff --git a/ssc/cmod_sco2_csp_system.cpp b/ssc/cmod_sco2_csp_system.cpp index 778079b5a..792f35fe2 100644 --- a/ssc/cmod_sco2_csp_system.cpp +++ b/ssc/cmod_sco2_csp_system.cpp @@ -38,6 +38,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "sco2_pc_csp_int.h" +#include "sco2_htrbypass_cycle.h" static var_info _cm_vtab_sco2_csp_system[] = { @@ -363,7 +364,7 @@ class cm_sco2_csp_system : public compute_module bool is_od_set_control = is_assigned("od_set_control"); bool is_od_generate_udpc_assigned = is_assigned("od_generate_udpc"); if (is_od_cases_assigned && is_P_mc_in_od_sweep_assigned) - { + { log("Both off design cases and main compressor inlet pressure sweep assigned. Only modeling off design cases"); is_P_mc_in_od_sweep_assigned = false; } @@ -452,11 +453,18 @@ class cm_sco2_csp_system : public compute_module P_LP_comp_in_des = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::MC_IN] / 1000.0; //[MPa] convert from kPa delta_P = 10.0; } - else + else if (cycle_config == 2) { P_LP_comp_in_des = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::PC_IN] / 1000.0; //[MPa] convert from kPa delta_P = 6.0; } + else + { + log("Cycle configuration not supported for off-design"); + std::string err_msg = util::format("Cycle configuration not supported for off-design"); + throw exec_error("sco2_csp_system", err_msg); + return; + } // Get turbine inlet mode int T_t_in_mode = as_integer("od_T_t_in_mode"); @@ -1741,3 +1749,7 @@ class cm_sco2_comp_curves : public compute_module }; DEFINE_MODULE_ENTRY(sco2_comp_curves, "Calls sCO2 auto-design cycle function", 1) + + + + diff --git a/ssc/csp_common.cpp b/ssc/csp_common.cpp index d1f4f1958..7bc6d5623 100644 --- a/ssc/csp_common.cpp +++ b/ssc/csp_common.cpp @@ -753,8 +753,10 @@ var_info vtab_sco2_design[] = { { SSC_INPUT, SSC_NUMBER, "site_elevation", "Site elevation", "m", "", "System Design", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "W_dot_net_des", "Design cycle power output (no cooling parasitics)", "MWe", "", "System Design", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "design_method", "1 = Specify efficiency, 2 = Specify total recup UA, 3 = Specify each recup design","","","System Design","*","", "" }, - { SSC_INPUT, SSC_NUMBER, "eta_thermal_des", "Power cycle thermal efficiency", "", "", "System Design", "design_method=1","", "" }, - + { SSC_INPUT, SSC_NUMBER, "eta_thermal_des", "Power cycle thermal efficiency", "", "", "System Design", "design_method=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "eta_thermal_cutoff", "Minimum eta allowable to solve and return cmod success", "", "", "System Design", "?=0", "", "" }, + + // Heat exchanger design // Combined recuperator design parameter (design_method == 2) { SSC_INPUT, SSC_NUMBER, "UA_recup_tot_des", "Total recuperator conductance", "kW/K", "Combined recuperator design", "Heat Exchanger Design", "design_method=2","", "" }, @@ -779,7 +781,7 @@ var_info vtab_sco2_design[] = { { SSC_INPUT, SSC_NUMBER, "HTR_n_sub_hx", "HTR number of model subsections", "-", "High temperature recuperator", "Heat Exchanger Design", "?=10", "", "" }, { SSC_INPUT, SSC_NUMBER, "HTR_od_model", "0: mass flow scale, 1: conductance ratio model", "-", "High temperature recuperator", "Heat Exchanger Design", "?=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cycle_config", "1 = recompression, 2 = partial cooling", "", "High temperature recuperator", "Heat Exchanger Design", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cycle_config", "1 = recompression, 2 = partial cooling, 3 = recomp with htr bypass, 4 = turbine split flow", "", "High temperature recuperator", "Heat Exchanger Design", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "is_recomp_ok", "1 = Yes, 0 = simple cycle only, < 0 = fix f_recomp to abs(input)","", "High temperature recuperator", "Heat Exchanger Design", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "is_P_high_fixed", "1 = Yes (=P_high_limit), 0 = No, optimized (default)", "", "High temperature recuperator", "Heat Exchanger Design", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "is_PR_fixed", "0 = No, >0 = fixed pressure ratio at input <0 = fixed LP at abs(input)", "High temperature recuperator", "", "Heat Exchanger Design", "?=0", "", "" }, @@ -810,19 +812,49 @@ var_info vtab_sco2_design[] = { { SSC_INPUT, SSC_NUMBER, "eta_air_cooler_fan", "Air cooler fan isentropic efficiency", "", "", "Air Cooler Design", "?=0.5", "", "" }, { SSC_INPUT, SSC_NUMBER, "N_nodes_air_cooler_pass", "Number of nodes in single air cooler pass", "", "", "Air Cooler Design", "?=10", "", "" }, + // HTR Bypass Design + { SSC_INPUT, SSC_NUMBER, "is_bypass_ok", "1 = Yes, 0 = No Bypass, < 0 = fix bp_frac to abs(input)","", "", "System Design", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_bypass_target", "HTR BP Cycle Target Temperature", "C", "", "System Design", "cycle_config=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_target_is_HTF", "Target Temperature is HTF (1) or cold sco2 at BP", "", "", "System Design", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "deltaT_bypass", "sco2 Bypass Outlet Temp - HTR_HP_OUT Temp", "C", "", "System Design", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "set_HTF_mdot", "For HTR Bypass ONLY, 0 = calculate HTF mdot (need to set dT_PHX_cold_approach), > 0 = HTF mdot kg/s", "kg/s", "", "System Design", "?=0", "", "" }, + + // Turbine Split Flow Design + { SSC_INPUT, SSC_NUMBER, "is_turbine_split_ok", "1 = Yes, 0 = No Second Turbine, < 0 = fix split_frac to abs(input)","", "", "", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "eta_isen_t2", "Design secondary turbine isentropic efficiency (TSF only)", "-", "", "", "cycle_config=4", "", "" }, + + // Cost Inputs + { SSC_INPUT, SSC_NUMBER, "yr_inflation", "Inflation target year", "yr", "", "System Design", "?=0", "", "" }, + + + // DEBUG + //{ SSC_OUTPUT, SSC_STRING, "debug_string", "output string used for debug", "C", "", "System Design", "cycle_config=3", "", "" }, + // ** Design OUTPUTS ** + { SSC_OUTPUT, SSC_NUMBER, "cycle_success", "", "", "", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "error_int", "", "", "", "", "*", "", "" }, + { SSC_OUTPUT, SSC_STRING, "error_msg", "", "", "", "", "error_int>0", "", "" }, + + // System Design Solution - { SSC_OUTPUT, SSC_NUMBER, "T_htf_cold_des", "HTF design cold temperature (PHX outlet)", "C", "System Design Solution", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "m_dot_htf_des", "HTF mass flow rate", "kg/s", "System Design Solution", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "T_htf_cold_des", "HTF design cold temperature (HTF outlet)", "C", "System Design Solution", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "T_htf_phx_out_des", "HTF design phx cold temperature (PHX outlet)", "C", "System Design Solution", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "m_dot_htf_des", "HTF mass flow rate", "kg/s", "System Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "eta_thermal_calc", "Calculated cycle thermal efficiency", "-", "System Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "m_dot_co2_full", "CO2 mass flow rate through HTR, PHX, turbine", "kg/s", "System Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "recomp_frac", "Recompression fraction", "-", "System Design Solution", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "cycle_cost", "Cycle cost bare erected", "M$", "System Design Solution", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "bypass_frac", "Bypass fraction", "-", "System Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cycle_cost", "Cycle cost bare erected", "M$", "System Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "cycle_spec_cost", "Cycle specific cost bare erected", "$/kWe", "System Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "cycle_spec_cost_thermal", "Cycle specific (thermal) cost bare erected", "$/kWt", "System Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "W_dot_net_less_cooling", "System power output subtracting cooling parastics", "MWe," "System Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "eta_thermal_net_less_cooling_des","Calculated cycle thermal efficiency using W_dot_net_less_cooling", "-", "System Design Solution","", "*", "", "" }, - // Compressor + { SSC_OUTPUT, SSC_NUMBER, "T_htf_bp_out_des", "HTF design htr bypass cold temperature (BPX outlet)", "C", "System Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "dT_htf_des", "HTF temperature difference", "C", "System Design Solution", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "q_dot_in_total", "Total heat from HTF into cycle", "MW", "System Design Solution", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "turbine_split_frac", "Turbine Split Fraction Solved", "", "System Design Solution", "", "cycle_config=4", "", "" }, + + // Compressor { SSC_OUTPUT, SSC_NUMBER, "T_comp_in", "Compressor inlet temperature", "C", "Compressor", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "P_comp_in", "Compressor inlet pressure", "MPa", "Compressor", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "P_comp_out", "Compressor outlet pressure", "MPa", "Compressor", "", "*", "", "" }, @@ -877,23 +909,38 @@ var_info vtab_sco2_design[] = { { SSC_OUTPUT, SSC_NUMBER, "pc_cost_equipment", "Precompressor cost equipment", "M$", "Precompressor", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "pc_cost_bare_erected", "Precompressor cost equipment plus install", "M$", "Precompressor", "", "*", "", "" }, // Compressor Totals - { SSC_OUTPUT, SSC_NUMBER, "c_tot_cost_equip", "Compressor total cost", "M$", "Compressor Totals", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "c_tot_W_dot", "Compressor total summed power", "MWe", "Compressor Totals", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "c_tot_cost_equip", "Compressor total cost", "M$", "Compressor Totals", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "c_tot_W_dot", "Compressor total summed power", "MWe", "Compressor Totals", "", "*", "", "" }, // Turbine - { SSC_OUTPUT, SSC_NUMBER, "t_W_dot", "Turbine power", "MWe", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_m_dot_des", "Turbine mass flow rate", "kg/s", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "T_turb_in", "Turbine inlet temperature", "C", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_P_in_des", "Turbine design inlet pressure", "MPa", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_T_out_des", "Turbine outlet temperature", "C", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_P_out_des", "Turbine design outlet pressure", "MPa", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_delta_h_isen_des", "Turbine isentropic specific work", "kJ/kg", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_rho_in_des", "Turbine inlet density", "kg/m3", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_nu_des", "Turbine design velocity ratio", "", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_tip_ratio_des", "Turbine design tip speed ratio", "", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_N_des", "Turbine design shaft speed", "rpm", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_D", "Turbine diameter", "m", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_cost_equipment", "Tubine cost - equipment", "M$", "Turbine", "", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "t_cost_bare_erected", "Tubine cost - equipment plus install", "M$", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_W_dot", "Turbine power", "MWe", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_m_dot_des", "Turbine mass flow rate", "kg/s", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "T_turb_in", "Turbine inlet temperature", "C", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_P_in_des", "Turbine design inlet pressure", "MPa", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_T_out_des", "Turbine outlet temperature", "C", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_P_out_des", "Turbine design outlet pressure", "MPa", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_delta_h_isen_des", "Turbine isentropic specific work", "kJ/kg", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_rho_in_des", "Turbine inlet density", "kg/m3", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_nu_des", "Turbine design velocity ratio", "", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_tip_ratio_des", "Turbine design tip speed ratio", "", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_N_des", "Turbine design shaft speed", "rpm", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_D", "Turbine diameter", "m", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_cost_equipment", "Tubine cost - equipment", "M$", "Turbine", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t_cost_bare_erected", "Tubine cost - equipment plus install", "M$", "Turbine", "", "*", "", "" }, + // Secondary Turbine (TSF cycle only) + { SSC_OUTPUT, SSC_NUMBER, "t2_W_dot", "Secondary Turbine power", "MWe", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_m_dot_des", "Secondary Turbine mass flow rate", "kg/s", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "T_turb2_in", "Secondary Turbine inlet temperature", "C", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_P_in_des", "Secondary Turbine design inlet pressure", "MPa", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_T_out_des", "Secondary Turbine outlet temperature", "C", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_P_out_des", "Secondary Turbine design outlet pressure", "MPa", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_delta_h_isen_des", "Secondary Turbine isentropic specific work", "kJ/kg", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_rho_in_des", "Secondary Turbine inlet density", "kg/m3", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_nu_des", "Secondary Turbine design velocity ratio", "", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_tip_ratio_des", "Secondary Turbine design tip speed ratio", "", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_N_des", "Secondary Turbine design shaft speed", "rpm", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_D", "Secondary Turbine diameter", "m", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_cost_equipment", "Secondary Tubine cost - equipment", "M$", "Turbine 2", "", "cycle_config=4", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "t2_cost_bare_erected", "Secondary Tubine cost - equipment plus install", "M$", "Turbine 2", "", "cycle_config=4", "", "" }, // Recuperators { SSC_OUTPUT, SSC_NUMBER, "recup_total_UA_assigned", "Total recuperator UA assigned to design routine", "MW/K", "Recuperators", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "recup_total_UA_calculated", "Total recuperator UA calculated considering max eff and/or min temp diff parameter", "MW/K", "Recuperators", "", "*", "", "" }, @@ -923,7 +970,8 @@ var_info vtab_sco2_design[] = { { SSC_OUTPUT, SSC_NUMBER, "HTR_min_dT", "High temp recuperator min temperature difference", "C", "Recuperators", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "HTR_cost_equipment", "High temp recuperator cost equipment", "M$", "Recuperators", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "HTR_cost_bare_erected","High temp recuperator cost equipment and install", "M$", "Recuperators", "", "*", "", "" }, - // PHX Design Solution + { SSC_OUTPUT, SSC_NUMBER, "HTR_HP_m_dot", "High temp recuperator high pressure mass flow rate", "kg/s", "Recuperators", "", "*", "", "" }, + // PHX Design Solution { SSC_OUTPUT, SSC_NUMBER, "UA_PHX", "PHX Conductance", "MW/K", "PHX Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "eff_PHX", "PHX effectiveness", "", "PHX Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "NTU_PHX", "PHX NTU", "", "PHX Design Solution", "", "*", "", "" }, @@ -935,7 +983,20 @@ var_info vtab_sco2_design[] = { { SSC_OUTPUT, SSC_NUMBER, "PHX_cost_equipment", "PHX cost equipment", "M$", "PHX Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "PHX_cost_bare_erected","PHX cost equipment and install", "M$", "PHX Design Solution", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "PHX_min_dT", "PHX min temperature difference", "C", "PHX Design Solution", "", "*", "", "" }, - // main compressor cooler + // BPX Design Solution + { SSC_OUTPUT, SSC_NUMBER, "UA_BPX", "BPX Conductance", "MW/K", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "eff_BPX", "BPX effectiveness", "", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "NTU_BPX", "BPX NTU", "", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "T_co2_BPX_in", "CO2 temperature at BPX inlet", "C", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "P_co2_BPX_in", "CO2 pressure at BPX inlet", "MPa", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "deltaT_HTF_BPX", "HTF temp difference across BPX", "C", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "q_dot_BPX", "BPX heat transfer", "MWt", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "BPX_co2_deltaP_des", "BPX co2 side design pressure drop", "-", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "BPX_cost_equipment", "BPX cost equipment", "M$", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "BPX_cost_bare_erected","BPX cost equipment and install", "M$", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "BPX_min_dT", "BPX min temperature difference", "C", "BPX Design Solution", "", "cycle_config=3", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "BPX_m_dot", "BPX sco2 mass flow rate", "kg/s", "BPX Design Solution", "", "cycle_config=3", "", "" }, + // main compressor cooler { SSC_OUTPUT, SSC_NUMBER, "mc_cooler_T_in", "Low pressure cross flow cooler inlet temperature", "C", "Low Pressure Cooler", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "mc_cooler_P_in", "Low pressure cross flow cooler inlet pressure", "MPa", "Low Pressure Cooler", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "mc_cooler_rho_in", "Low pressure cross flow cooler inlet density", "kg/m3", "Low Pressure Cooler", "", "*", "", "" }, @@ -992,6 +1053,9 @@ var_info vtab_sco2_design[] = { { SSC_OUTPUT, SSC_ARRAY, "h_rc_data", "Enthalpy points along re compression", "kJ/kg", "P-h plot data", "", "*", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_pc_data", "Pressure points along pre compression", "MPa", "P-h plot data", "", "*", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "h_pc_data", "Enthalpy points along pre compression", "kJ/kg", "P-h plot data", "", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "P_t2_data", "Pressure points along secondary turbine expansion", "MPa", "P-h plot data", "", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "h_t2_data", "Enthalpy points along secondary turbine expansion", "kJ/kg", "P-h plot data", "", "*", "", "" }, + var_info_invalid }; @@ -1060,7 +1124,11 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c cm->log(err_msg, SSC_ERROR, -1.0); } + s_sco2_des_par.m_eta_thermal_cutoff = cm->as_double("eta_thermal_cutoff"); + s_sco2_des_par.m_is_recomp_ok = cm->as_double("is_recomp_ok"); + s_sco2_des_par.m_is_bypass_ok = cm->as_double("is_bypass_ok"); + s_sco2_des_par.m_is_turbine_split_ok = cm->as_double("is_turbine_split_ok"); s_sco2_des_par.m_P_high_limit = cm->as_double("P_high_limit")*1000.0; //[kPa], convert from MPa s_sco2_des_par.m_fixed_P_mc_out = cm->as_integer("is_P_high_fixed"); //[-] @@ -1221,6 +1289,31 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c s_sco2_des_par.m_eta_fan = cm->as_double("eta_air_cooler_fan"); // 0.5; s_sco2_des_par.m_N_nodes_pass = cm->as_integer("N_nodes_air_cooler_pass"); // 10; + // Bypass Configuration Parameters + if (s_sco2_des_par.m_cycle_config == 3) + { + s_sco2_des_par.m_T_bypass_target = cm->as_double("T_bypass_target") + 273.15; // [K] Convert to K + s_sco2_des_par.m_T_target_is_HTF = cm->as_integer("T_target_is_HTF"); + s_sco2_des_par.m_deltaT_bypass = cm->as_double("deltaT_bypass"); // [delta C] + s_sco2_des_par.m_set_HTF_mdot = cm->as_double("set_HTF_mdot"); // [kg/s] For HTR Bypass ONLY, 0 = calculate HTF mdot (need to set dT_PHX_cold_approach), > 0 = HTF mdot + + // Already assigned (above) + //s_sco2_des_par.m_phx_dt_cold_approach = cm->as_double("dT_PHX_cold_approach"); // [delta C] + } + + // Turbine Split Flow Configuration Paramters + if (s_sco2_des_par.m_cycle_config == 4) + { + s_sco2_des_par.m_eta_t2 = cm->as_double("eta_isen_t2"); + } + else + { + s_sco2_des_par.m_eta_t2 = s_sco2_des_par.m_eta_t; + } + + // Inflation target year + s_sco2_des_par.m_yr_inflation = cm->as_double("yr_inflation"); //[yr] + // For try/catch below int out_type = -1; std::string out_msg = ""; @@ -1229,9 +1322,11 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c c_sco2_cycle.mf_callback_update = ssc_cmod_update; c_sco2_cycle.mp_mf_update = (void*)(cm); + int cycle_error_enum; + try { - c_sco2_cycle.design(s_sco2_des_par); + cycle_error_enum = c_sco2_cycle.design(s_sco2_des_par); } catch (C_csp_exception &csp_exception) { @@ -1245,6 +1340,25 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c throw exec_error("sco2_csp_system", csp_exception.m_error_message); } + // Handle cycle errors + cm->assign("error_int", cycle_error_enum); + + // Cycle failed, exit now + // tmb 2025-09-25 Now throwing exception, rather than exiting gracefully + if (cycle_error_enum >= (int)C_sco2_cycle_core::E_cycle_error_msg::E_CANNOT_PRODUCE_POWER) + { + std::string error_text = C_sco2_cycle_core::E_cycle_error_msg::get_error_string(cycle_error_enum); + cm->assign("error_msg", error_text); + cm->assign("cycle_success", 0); + cm->assign("eta_thermal_calc", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_eta_thermal); //[-] + + throw exec_error("sco2_csp_system", error_text); + } + + std::string error_text = ""; + cm->assign("error_msg", error_text); + cm->assign("cycle_success", 1); + // If all calls were successful, log to SSC any messages from sco2_recomp_csp while (c_sco2_cycle.mc_messages.get_message(&out_type, &out_msg)) { @@ -1253,7 +1367,7 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c // Helpful to know right away whether cycle contains recompressor bool is_rc = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_is_rc; - + // Get data for P-h cycle plot std::vector P_t; //[MPa] std::vector h_t; //[kJ/kg] @@ -1263,6 +1377,8 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c std::vector h_rc; //[kJ/kg] std::vector P_pc; //[MPa] std::vector h_pc; //[kJ/kg] + std::vector P_t2; //[MPa] + std::vector h_t2; //[kJ/kg] int ph_err_code = sco2_cycle_plot_data_PH(s_sco2_des_par.m_cycle_config, c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp, c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres, @@ -1273,7 +1389,9 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c P_rc, h_rc, P_pc, - h_pc); + h_pc, + P_t2, + h_t2); if (ph_err_code != 0) throw exec_error("sco2_csp_system", "cycle plot data routine failed"); @@ -1314,6 +1432,15 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c p_h_pc_data[i] = (ssc_number_t)(h_pc[i]); //[kJ/kg] } + n_v = P_t2.size(); + ssc_number_t* p_P_t2_data = cm->allocate("P_t2_data", n_v); + ssc_number_t* p_h_t2_data = cm->allocate("h_t2_data", n_v); + for (size_t i = 0; i < n_v; i++) + { + p_P_t2_data[i] = (ssc_number_t)(P_t2[i]); //[MPa] + p_h_t2_data[i] = (ssc_number_t)(h_t2[i]); //[kJ/kg] + } + // Get data for T-s cycle plot std::vector T_LTR_HP; //[C] std::vector s_LTR_HP; //[kJ/kg-K] @@ -1422,24 +1549,56 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c double comp_power_sum = 0.0; //[MWe] double m_dot_htf_design = c_sco2_cycle.get_phx_des_par()->m_m_dot_hot_des; //[kg/s] double T_htf_cold_calc = c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_T_h_out; //[K] - cm->assign("T_htf_cold_des", (ssc_number_t)(T_htf_cold_calc - 273.15)); //[C] convert from K + double T_htf_bypass_out = c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_T_h_out; //[K] + double cycle_config = cm->as_double("cycle_config"); + cm->assign("T_htf_phx_out_des", (ssc_number_t)(T_htf_cold_calc - 273.15)); //[C] convert from K PHX Outlet Temp cm->assign("m_dot_htf_des", (ssc_number_t)m_dot_htf_design); //[kg/s] cm->assign("eta_thermal_calc", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_eta_thermal); //[-] cm->assign("m_dot_co2_full", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_m_dot_t); //[kg/s] cm->assign("recomp_frac", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_recomp_frac); //[-] - // Compressor - cm->assign("T_comp_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::MC_IN] - 273.15)); //[C] convert from K + if (cycle_config == 3) + { + cm->assign("T_htf_bp_out_des", (ssc_number_t)T_htf_bypass_out - 273.15); // [C] + cm->assign("T_htf_cold_des", (ssc_number_t)(T_htf_bypass_out - 273.15)); //[C] convert from K Final HTF Temp is BPX Outlet for htr bypass cycle + } + else + { + cm->assign("T_htf_cold_des", (ssc_number_t)(T_htf_cold_calc - 273.15)); //[C] convert from K Final HTF Temp (PHX outlet) + } + + // Compressor + cm->assign("T_comp_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::MC_IN] - 273.15)); //[C] convert from K cm->assign("P_comp_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::MC_IN] / 1000.0)); //[MPa] convert from kPa cm->assign("P_comp_out", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::MC_OUT] / 1000.0)); //[MPa] convert from kPa cm->assign("mc_T_out", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::MC_OUT] - 273.15)); //[C] convert from K cm->assign("mc_W_dot", (ssc_number_t)(-c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_W_dot_mc*1.E-3)); //[MWe] convert kWe cm->assign("mc_rho_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_dens[C_sco2_cycle_core::MC_IN])); //[kg/m3] cm->assign("mc_ideal_spec_work", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_ms_des_solved.m_isen_spec_work); //[kJ/kg] - cm->assign("mc_m_dot_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_m_dot_t*(1.0 - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_recomp_frac)); //[kg/s] - cm->assign("mc_phi_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_ms_des_solved.m_phi_des); //[-] + cm->assign("mc_phi_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_ms_des_solved.m_phi_des); //[-] cm->assign("mc_psi_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_ms_des_solved.m_psi_des); //[-] ideal head coefficient cm->assign("mc_tip_ratio_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_ms_des_solved.m_tip_ratio_max); //[-] + ssc_number_t mc_m_dot_des = std::numeric_limits::quiet_NaN(); + if (cycle_config != 4) + mc_m_dot_des = (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_m_dot_t * (1.0 - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_recomp_frac); + else + mc_m_dot_des = (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_m_dot_mc; + + cm->assign("mc_m_dot_des", mc_m_dot_des); //[kg/s] + + + double htf_outlet = cm->as_double("cycle_config") == 3 ? T_htf_bypass_out : T_htf_cold_calc; + double dT_htf = cm->as_double("T_htf_hot_des") - (htf_outlet - 273.15); + cm->assign("dT_htf_des", (ssc_number_t)dT_htf); + + double q_dot_total = c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_Q_dot_design * 1.E-3; // [MWt] + if (s_sco2_des_par.m_cycle_config == 3) + { + double q_dot_BPX = c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_Q_dot_design * 1.E-3; //[MWt] convert from kWt + q_dot_total += q_dot_BPX; + } + cm->assign("q_dot_in_total", (ssc_number_t)q_dot_total); + int n_mc_stages = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_ms_des_solved.m_n_stages; cm->assign("mc_n_stages", (ssc_number_t)n_mc_stages); //[-] cm->assign("mc_N_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_ms_des_solved.m_N_design); //[rpm] @@ -1608,7 +1767,33 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c cost_equip_sum += c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t_des_solved.m_equipment_cost; //[M$] cm->assign("t_cost_bare_erected", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t_des_solved.m_bare_erected_cost); //[M$] cost_bare_erected_sum += c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t_des_solved.m_bare_erected_cost; - // Recuperator + + // Secondary Turbine + if (cycle_config == 4) + { + cm->assign("t2_W_dot", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_W_dot_t2 * 1.E-3)); //[MWe] convert from kWe + cm->assign("t2_m_dot_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_m_dot_t2); //[kg/s] + cm->assign("T_turb2_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::HTR_HP_OUT] - 273.15)); //[C] Turbine 2 inlet temp, convert from K + cm->assign("t2_P_in_des", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::HTR_HP_OUT] * 1.E-3)); //[MPa] Turbine 2 inlet pressure, convert from kPa + cm->assign("t2_T_out_des", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::TURB2_OUT] - 273.15)); //[C] Turbine 2 outlet temp, convert from K + cm->assign("t2_P_out_des", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::TURB2_OUT] * 1.E-3)); //[MPa] Turbine 2 outlet pressure, convert from kPa + cm->assign("t2_delta_h_isen_des", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_delta_h_isen)); //[kJ/kg] + cm->assign("t2_rho_in_des", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_rho_in)); //[kg/m3] + cm->assign("t2_nu_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_nu_design); //[-] + cm->assign("t2_tip_ratio_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_w_tip_ratio); //[-] + cm->assign("t2_N_des", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_N_design); //[rpm] + cm->assign("t2_D", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_D_rotor); //[m] + cm->assign("t2_cost_equipment", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_equipment_cost); //[M$] + cost_equip_sum += c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_equipment_cost; //[M$] + cm->assign("t2_cost_bare_erected", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_bare_erected_cost); //[M$] + cost_bare_erected_sum += c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_t2_des_solved.m_bare_erected_cost; + } + + + + // Recuperator + int LTR_LP_IN_enum = cycle_config == 4 ? C_sco2_cycle_core::TURB2_OUT : C_sco2_cycle_core::HTR_LP_OUT; // Define LTR LP inlet state point + double recup_total_UA_assigned = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_LTR_des_solved.m_UA_allocated*1.E-3; //[MW/K] convert from kW/K double recup_total_UA_calculated = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_LTR_des_solved.m_UA_calc_at_eff_max*1.E-3; //[MW/K] convert from kW/K double recup_total_cost_equip = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_LTR_des_solved.m_cost_equipment; //[M$] @@ -1621,7 +1806,7 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c cm->assign("NTU_LTR", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_LTR_des_solved.m_NTU_design); //[-] cm->assign("q_dot_LTR", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_LTR_des_solved.m_Q_dot_design*1.E-3)); //[MWt] convert from kWt double LTR_LP_deltaP_frac = 1.0 - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::LTR_LP_OUT] / - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::HTR_LP_OUT]; //[-] Fractional pressure drop through LP side of LTR + c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[LTR_LP_IN_enum]; //[-] Fractional pressure drop through LP side of LTR cm->assign("LTR_LP_deltaP_des", (ssc_number_t)LTR_LP_deltaP_frac); //[-] double LTR_HP_deltaP_frac = 1.0 - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::LTR_HP_OUT] / c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::MC_OUT]; //[-] Fractional pressure drop through HP side of LTR @@ -1632,12 +1817,14 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c cost_equip_sum += c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_LTR_des_solved.m_cost_equipment; //[M$] cost_bare_erected_sum += c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_LTR_des_solved.m_cost_bare_erected; //[M$] // High-temp - if (is_rc) + if (is_rc || cycle_config == 3 || cycle_config == 4) // Cycle may still have HTR if it is htr bypass cycle { + int HTR_HP_IN_enum = cycle_config == 4 ? C_sco2_cycle_core::MC_OUT : C_sco2_cycle_core::MIXER_OUT; + recup_total_UA_assigned += c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_HTR_des_solved.m_UA_allocated*1.E-3; //[MW/K] convert from kW/K recup_total_UA_calculated += c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_HTR_des_solved.m_UA_calc_at_eff_max*1.E-3; //[MW/K] convert from kW/K cm->assign("HTR_LP_T_out_des", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::HTR_LP_OUT] - 273.15)); //[C] HTR LP stream outlet temp, convert from K - cm->assign("HTR_HP_T_in_des", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::MIXER_OUT] - 273.15)); //[C] HTR HP stream inlet temp, convert from K + cm->assign("HTR_HP_T_in_des", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[HTR_HP_IN_enum] - 273.15)); //[C] HTR HP stream inlet temp, convert from K cm->assign("HTR_UA_assigned", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_HTR_des_solved.m_UA_allocated*1.E-3)); //[MW/K] convert from kW/K cm->assign("HTR_UA_calculated", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_HTR_des_solved.m_UA_calc_at_eff_max*1.E-3)); //[MW/K] convert from kW/K cm->assign("eff_HTR", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_HTR_des_solved.m_eff_design); //[-] @@ -1656,7 +1843,7 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::TURB_OUT]; //[-] Fractional pressure drop through LP side of HTR cm->assign("HTR_LP_deltaP_des", (ssc_number_t)HTR_LP_deltaP_frac); //[-] double HTR_HP_deltaP_frac = 1.0 - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::HTR_HP_OUT] / - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::LTR_HP_OUT]; //[-] Fractional pressure drop through HP side of HTR + c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[HTR_HP_IN_enum]; //[-] Fractional pressure drop through HP side of HTR cm->assign("HTR_HP_deltaP_des", (ssc_number_t)HTR_HP_deltaP_frac); //[-] } else @@ -1680,31 +1867,78 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c cm->assign("recup_total_UA_calculated", (ssc_number_t)(recup_total_UA_calculated)); //[MW/K] cm->assign("recup_total_cost_equipment", (ssc_number_t)(recup_total_cost_equip)); //[MW/K] cm->assign("recup_total_cost_bare_erected", (ssc_number_t)(recup_total_cost_bare_erected)); - // PHX + + double htr_hp_m_dot = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_m_dot_t; + if (s_sco2_des_par.m_cycle_config == 3) + { + double mdot_t = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_m_dot_t; + htr_hp_m_dot = mdot_t * (1.0 - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_bypass_frac); + } + else if (cycle_config == 4) + { + htr_hp_m_dot = c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_m_dot_t2; //[kg/s] + } + + cm->assign("HTR_HP_m_dot", (ssc_number_t)htr_hp_m_dot); + + // PHX + int PHX_IN_enum = cycle_config == 4 ? C_sco2_cycle_core::LTR_HP_OUT : C_sco2_cycle_core::HTR_HP_OUT; + cm->assign("UA_PHX", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_UA_design*1.E-3)); //[MW/K] convert from kW/K cm->assign("eff_PHX", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_eff_design); //[-] cm->assign("NTU_PHX", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_NTU_design); //[-] - cm->assign("T_co2_PHX_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::HTR_HP_OUT] - 273.15)); //[C] - cm->assign("P_co2_PHX_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::HTR_HP_OUT] * 1.E-3)); //[MPa] convert from kPa + cm->assign("T_co2_PHX_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[PHX_IN_enum] - 273.15)); //[C] + cm->assign("P_co2_PHX_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[PHX_IN_enum] * 1.E-3)); //[MPa] convert from kPa cm->assign("deltaT_HTF_PHX", (ssc_number_t)s_sco2_des_par.m_T_htf_hot_in - T_htf_cold_calc); //[K] cm->assign("q_dot_PHX", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_Q_dot_design*1.E-3)); //[MWt] convert from kWt cm->assign("PHX_min_dT", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_min_DT_design)); //[C/K] double PHX_deltaP_frac = 1.0 - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::TURB_IN] / - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::HTR_HP_OUT]; //[-] Fractional pressure drop through co2 side of PHX + c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[PHX_IN_enum]; //[-] Fractional pressure drop through co2 side of PHX cm->assign("PHX_co2_deltaP_des", (ssc_number_t)PHX_deltaP_frac); cm->assign("PHX_cost_equipment", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_cost_equipment); //[M$] cm->assign("PHX_cost_bare_erected", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_cost_bare_erected); //[M$] cost_equip_sum += c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_cost_equipment; //[M$] cost_bare_erected_sum += c_sco2_cycle.get_design_solved()->ms_phx_des_solved.m_cost_bare_erected; //[M$]; - // Low Pressure Cooler + // BPX + if (s_sco2_des_par.m_cycle_config == 3) + { + cm->assign("bypass_frac", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_bypass_frac); + cm->assign("UA_BPX", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_UA_design * 1.E-3)); //[MW/K] convert from kW/K + cm->assign("eff_BPX", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_eff_design); //[-] + cm->assign("NTU_BPX", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_NTU_design); //[-] + cm->assign("T_co2_BPX_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::MIXER_OUT] - 273.15)); //[C] + cm->assign("P_co2_BPX_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::MIXER_OUT] * 1.E-3)); //[MPa] convert from kPa + cm->assign("deltaT_HTF_BPX", (ssc_number_t)(T_htf_cold_calc - T_htf_bypass_out)); //[K] + cm->assign("q_dot_BPX", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_Q_dot_design * 1.E-3)); //[MWt] convert from kWt + cm->assign("BPX_min_dT", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_min_DT_design)); //[C/K] + double PHX_deltaP_frac = 1.0 - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::BYPASS_OUT] / + c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::MIXER_OUT]; //[-] Fractional pressure drop through co2 side of PHX + cm->assign("BPX_co2_deltaP_des", (ssc_number_t)PHX_deltaP_frac); + cm->assign("BPX_cost_equipment", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_cost_equipment); //[M$] + cm->assign("BPX_cost_bare_erected", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_cost_bare_erected); //[M$] + cm->assign("BPX_m_dot", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_bypass_frac * + c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_m_dot_t)); + cost_equip_sum += c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_cost_equipment; //[M$] + cost_bare_erected_sum += c_sco2_cycle.get_design_solved()->ms_bp_des_solved.m_cost_bare_erected; //[M$]; + } + + // Add turbine split flow specific outputs + if (s_sco2_des_par.m_cycle_config == 4) + { + cm->assign("turbine_split_frac", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_turbine_split_frac); + } + + // Low Pressure Cooler + int MC_COOLER_IN_enum = cycle_config == 4 ? C_sco2_cycle_core::MIXER_OUT : C_sco2_cycle_core::LTR_LP_OUT; + cm->assign("mc_cooler_T_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_air_cooler.m_T_in_co2 - 273.15)); //[C] cm->assign("mc_cooler_P_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_air_cooler.m_P_in_co2 / 1.E3)); //[MPa] - cm->assign("mc_cooler_rho_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_dens[C_sco2_cycle_core::LTR_LP_OUT])); //[kg/m3] + cm->assign("mc_cooler_rho_in", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_dens[MC_COOLER_IN_enum])); //[kg/m3] cm->assign("mc_cooler_m_dot_co2", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_air_cooler.m_m_dot_co2); //[kg/s] cm->assign("mc_cooler_UA", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_air_cooler.m_UA_total*1.E-6)); //[MW/K] convert from W/K cm->assign("mc_cooler_q_dot", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_air_cooler.m_q_dot*1.E-6)); //[MWt] convert from W double LP_cooler_deltaP_frac = 1.0 - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::MC_IN] / - c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::LTR_LP_OUT]; //[-] Fractional pressure drop through co2 side of PHX + c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.m_pres[MC_COOLER_IN_enum]; //[-] Fractional pressure drop through co2 side of PHX cm->assign("mc_cooler_co2_deltaP_des", (ssc_number_t)LP_cooler_deltaP_frac); cm->assign("mc_cooler_W_dot_fan", (ssc_number_t)(c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_air_cooler.m_W_dot_fan)); //[MWe] cm->assign("mc_cooler_cost_equipment", (ssc_number_t)c_sco2_cycle.get_design_solved()->ms_rc_cycle_solved.ms_mc_air_cooler.m_cost_equipment); //[M$] @@ -1801,4 +2035,3 @@ int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_c return 0; } - diff --git a/ssc/csp_common.h b/ssc/csp_common.h index e876b8822..5e36f1ab2 100644 --- a/ssc/csp_common.h +++ b/ssc/csp_common.h @@ -83,8 +83,6 @@ extern var_info vtab_sco2_design[]; int sco2_design_cmod_common(compute_module *cm, C_sco2_phx_air_cooler & c_sco2_cycle); +#endif - - -#endif diff --git a/tcs/CMakeLists.txt b/tcs/CMakeLists.txt index 252d2e7a0..490169ad5 100644 --- a/tcs/CMakeLists.txt +++ b/tcs/CMakeLists.txt @@ -52,10 +52,12 @@ set(TCS_SRC numeric_solvers.cpp ptes_solver_design_point.cpp sco2_cycle_components.cpp + sco2_htrbypass_cycle.cpp sco2_partialcooling_cycle.cpp sco2_pc_csp_int.cpp sco2_power_cycle.cpp sco2_recompression_cycle.cpp + sco2_turbinesplitflow_cycle.cpp tcskernel.cpp trnsys_weatherreader.cpp typelib.cpp @@ -131,10 +133,12 @@ set(TCS_SRC sam_csp_util.h sco2_cycle_components.h sco2_cycle_templates.h + sco2_htrbypass_cycle.h sco2_partialcooling_cycle.h sco2_pc_csp_int.h sco2_power_cycle.h sco2_recompression_cycle.h + sco2_turbinesplitflow_cycle.h storage_hx.h tcskernel.h tcstype.h diff --git a/tcs/heat_exchangers.cpp b/tcs/heat_exchangers.cpp index 8328a7b68..eecb3d4ea 100644 --- a/tcs/heat_exchangers.cpp +++ b/tcs/heat_exchangers.cpp @@ -35,6 +35,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "sam_csp_util.h" #include #include "numeric_solvers.h" +#include "sco2_cycle_components.h" double NS_HX_counterflow_eqs::calc_max_q_dot_enth(int hot_fl_code /*-*/, HTFProperties & hot_htf_class, int cold_fl_code /*-*/, HTFProperties & cold_htf_class, @@ -1523,11 +1524,13 @@ void NS_HX_counterflow_eqs::solve_q_dot_for_fixed_UA_enth(int hot_fl_code /*-*/, throw(C_csp_exception("Off-design heat exchanger method failed")); } - //if (!(od_hx_code == C_monotonic_eq_solver::CONVERGED || od_hx_code == C_monotonic_eq_solver::SLOPE_POS_NO_POS_ERR || od_hx_code == C_monotonic_eq_solver::SLOPE_POS_BOTH_ERRS)) - //{ - // throw(C_csp_exception("Off-design heat exchanger method failed")); - //} + /*if (!(od_hx_code == C_monotonic_eq_solver::CONVERGED || od_hx_code == C_monotonic_eq_solver::SLOPE_POS_NO_POS_ERR || od_hx_code == C_monotonic_eq_solver::SLOPE_POS_BOTH_ERRS)) + { + throw(C_csp_exception("Off-design heat exchanger method failed")); + }*/ + } + else if (test_code == 0 && diff_UA_max_eff <= 0.0) // UA_max_eff <= UA_target) { // At maximum allowable heat transfer, the calculated UA is less than target @@ -1785,16 +1788,37 @@ double C_HX_counterflow_CRM::calc_max_q_dot_enth(double h_h_in /*kJ/kg*/, double double /*M$*/ C_HX_counterflow_CRM::calculate_equipment_cost(double UA /*kWt/K*/, double T_hot_in /*K*/, double P_hot_in /*kPa*/, double m_dot_hot /*kg/s*/, - double T_cold_in /*K*/, double P_cold_in /*kPa*/, double m_dot_cold /*kg/s*/) + double T_cold_in /*K*/, double P_cold_in /*kPa*/, double m_dot_cold /*kg/s*/, + double yr_inflation /*yr*/) { switch (m_cost_model) { case C_HX_counterflow_CRM::E_CARLSON_17_RECUP: - return 1.25*1.E-3*UA; //[M$] needs UA in kWt/K + { + double yr_base_inflation = 2017; + double f_inflation = calculate_inflation_factor(yr_base_inflation, yr_inflation); + return 1.25 * 1.E-3 * UA * f_inflation; //[M$] needs UA in kWt/K + } case C_HX_counterflow_CRM::E_WEILAND_19_RECUP: - return 49.45*std::pow(UA*1.E3, 0.7544)*1.E-6; //[M$] needs UA in Wt/K + { + double C_recup = 49.45 * std::pow(UA * 1.E3, 0.7544) * 1.E-6; //[M$] needs UA in Wt/K + double T_factor = 1; + double T_max_C = std::max(T_hot_in, T_cold_in) - 273.15; + if (T_max_C >= 550) + { + T_factor = 1.0 + 0.02141 * (T_max_C - 550); + } + double yr_base_inflation = 2017; + double f_inflation = calculate_inflation_factor(yr_base_inflation, yr_inflation); + return C_recup * T_factor * f_inflation; //[M$] + break; + } case C_HX_counterflow_CRM::E_CARLSON_17_PHX: - return 3.5*1.E-3*UA; //[M$] needs UA in kWt/K + { + double yr_base_inflation = 2017; + double f_inflation = calculate_inflation_factor(yr_base_inflation, yr_inflation); + return 3.5 * 1.E-3 * UA * f_inflation; //[M$] needs UA in kWt/K + } default: return std::numeric_limits::quiet_NaN(); } @@ -1860,7 +1884,8 @@ void C_HX_counterflow_CRM::design_calc_UA(C_HX_counterflow_CRM::S_des_calc_UA_pa ms_des_solved.m_cost_equipment = calculate_equipment_cost(ms_des_solved.m_UA_design, ms_des_calc_UA_par.m_T_h_in, ms_des_calc_UA_par.m_P_h_in, ms_des_calc_UA_par.m_m_dot_hot_des, - ms_des_calc_UA_par.m_T_c_in, ms_des_calc_UA_par.m_P_c_in, ms_des_calc_UA_par.m_m_dot_cold_des); + ms_des_calc_UA_par.m_T_c_in, ms_des_calc_UA_par.m_P_c_in, ms_des_calc_UA_par.m_m_dot_cold_des, + m_yr_inflation); ms_des_solved.m_cost_bare_erected = calculate_bare_erected_cost(ms_des_solved.m_cost_equipment); @@ -1981,7 +2006,7 @@ void C_HX_counterflow_CRM::design_calc_UA_TP_to_PH(C_HX_counterflow_CRM::S_des_c ms_des_solved.m_cost_equipment = calculate_equipment_cost(ms_des_solved.m_UA_design, ms_des_calc_UA_par.m_T_h_in, ms_des_calc_UA_par.m_P_h_in, ms_des_calc_UA_par.m_m_dot_hot_des, - ms_des_calc_UA_par.m_T_c_in, ms_des_calc_UA_par.m_P_c_in, ms_des_calc_UA_par.m_m_dot_cold_des); + ms_des_calc_UA_par.m_T_c_in, ms_des_calc_UA_par.m_P_c_in, ms_des_calc_UA_par.m_m_dot_cold_des, m_yr_inflation); ms_des_solved.m_cost_bare_erected = calculate_bare_erected_cost(ms_des_solved.m_cost_equipment); @@ -2059,7 +2084,8 @@ void C_HX_counterflow_CRM::design_for_target__calc_outlet(int hx_target_code /*- ms_des_solved.m_cost_equipment = calculate_equipment_cost(ms_des_solved.m_UA_design, T_h_in, P_h_in, m_dot_h, - T_c_in, P_c_in, m_dot_c); + T_c_in, P_c_in, m_dot_c, + m_yr_inflation); ms_des_solved.m_cost_bare_erected = calculate_bare_erected_cost(ms_des_solved.m_cost_equipment); } @@ -3370,7 +3396,8 @@ double NS_HX_counterflow_eqs::UA_scale_vs_m_dot(double m_dot_cold_over_des /*-*/ return pow(m_dot_ratio, 0.8); } -void C_HX_co2_to_co2_CRM::initialize(int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type) +void C_HX_co2_to_co2_CRM::initialize(int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type, + double yr_inflation) { // Set design parameters member structure ms_init_par.m_N_sub_hx = N_sub_hx; @@ -3379,9 +3406,11 @@ void C_HX_co2_to_co2_CRM::initialize(int N_sub_hx, NS_HX_counterflow_eqs::E_UA_t m_is_HX_initialized = true; m_od_UA_target_type = od_UA_target_type; + m_yr_inflation = yr_inflation; } -void C_HX_co2_to_htf::initialize(int hot_fl, util::matrix_t hot_fl_props, int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type) +void C_HX_co2_to_htf::initialize(int hot_fl, util::matrix_t hot_fl_props, int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type, + double yr_inflation) { // Hard-code some of the design parameters ms_init_par.m_N_sub_hx = N_sub_hx; //[-] @@ -3427,13 +3456,16 @@ void C_HX_co2_to_htf::initialize(int hot_fl, util::matrix_t hot_fl_props // Class is initialized m_is_HX_initialized = true; + m_yr_inflation = yr_inflation; + } -void C_HX_co2_to_htf::initialize(int hot_fl, int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type) +void C_HX_co2_to_htf::initialize(int hot_fl, int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type, + double yr_inflation) { util::matrix_t null_fluid_props; - initialize(hot_fl, null_fluid_props, N_sub_hx, od_UA_target_type); + initialize(hot_fl, null_fluid_props, N_sub_hx, od_UA_target_type, yr_inflation); } void C_HX_co2_to_htf::design_and_calc_m_dot_htf(C_HX_counterflow_CRM::S_des_calc_UA_par& des_par, @@ -3691,7 +3723,8 @@ bool C_CO2_to_air_cooler::design_hx(S_des_par_ind des_par_ind, S_des_par_cycle_d solver_code = -1; i_W_par = -1; - while (solver_code != 0 || std::abs(T_hot_in_calc_2 - T_hot_in_calc) / T_hot_in_calc < 0.01) + while (solver_code != 0 || std::abs(T_hot_in_calc_2 - T_hot_in_calc) / T_hot_in_calc < 0.01 + || ms_des_par_cycle_dep.m_T_hot_in_des - T_hot_in_calc_2 > 100.0) { i_W_par++; @@ -3774,7 +3807,7 @@ bool C_CO2_to_air_cooler::design_hx(S_des_par_ind des_par_ind, S_des_par_cycle_d ms_hx_des_sol.m_W_dot_fan = ms_des_par_cycle_dep.m_W_dot_fan_des; //[MWe] ms_hx_des_sol.m_cost_equipment = calculate_equipment_cost(ms_hx_des_sol.m_UA_total*1.E-3, ms_hx_des_sol.m_V_total, - ms_hx_des_sol.m_T_in_co2, ms_hx_des_sol.m_P_in_co2, ms_hx_des_sol.m_m_dot_co2); //[M$] + ms_hx_des_sol.m_T_in_co2, ms_hx_des_sol.m_P_in_co2, ms_hx_des_sol.m_m_dot_co2, des_par_ind.m_yr_inflation); //[M$] ms_hx_des_sol.m_cost_bare_erected = calculate_bare_erected_cost(ms_hx_des_sol.m_cost_equipment); @@ -4361,14 +4394,24 @@ int C_CO2_to_air_cooler::C_MEQ_target_T_hot__width_parallel::operator()(double W } double /*M$*/ C_CO2_to_air_cooler::calculate_equipment_cost(double UA /*kWt/K*/, double V_material /*m^3*/, - double T_hot_in /*K*/, double P_hot_in /*kPa*/, double m_dot_hot /*kg/s*/) + double T_hot_in /*K*/, double P_hot_in /*kPa*/, double m_dot_hot /*kg/s*/, double yr_inflation/**/) { switch (m_cost_model) { case C_CO2_to_air_cooler::E_CARLSON_17: - return 2.3*1.E-3*UA; //[M$] needs UA in kWt/K + { + double yr_base_inflation = 2017; + double f_inflation = calculate_inflation_factor(yr_base_inflation, yr_inflation); + return 2.3 * 1.E-3 * UA * f_inflation; //[M$] needs UA in kWt/K + } + case C_CO2_to_air_cooler::E_WEILAND_19: - return 32.88*std::pow(UA*1.E3, 0.75)*1.E-6; //[M$] needs UA in Wt/K + { + double yr_base_inflation = 2017; + double f_inflation = calculate_inflation_factor(yr_base_inflation, yr_inflation); + return 32.88 * std::pow(UA * 1.E3, 0.75) * 1.E-6 * f_inflation; //[M$] needs UA in Wt/K + } + default: return std::numeric_limits::quiet_NaN(); } diff --git a/tcs/heat_exchangers.h b/tcs/heat_exchangers.h index 51beab3d6..d6b4e3263 100644 --- a/tcs/heat_exchangers.h +++ b/tcs/heat_exchangers.h @@ -478,6 +478,7 @@ class C_HX_counterflow_CRM int m_cost_model; //[-] int m_od_solution_type; //[-] + double m_yr_inflation = 0; //[yr] bool m_is_single_node_des_set; NS_HX_counterflow_eqs::S_hx_node_info ms_node_info_des; @@ -678,7 +679,8 @@ class C_HX_counterflow_CRM double calculate_equipment_cost(double UA /*kWt/K*/, double T_hot_in /*K*/, double P_hot_in /*kPa*/, double m_dot_hot /*kg/s*/, - double T_cold_in /*K*/, double P_cold_in /*kPa*/, double m_dot_cold /*kg/s*/); + double T_cold_in /*K*/, double P_cold_in /*kPa*/, double m_dot_cold /*kg/s*/, + double yr_inflation /*yr*/); double calculate_bare_erected_cost(double cost_equipment /*M$*/); @@ -838,9 +840,10 @@ class C_HX_co2_to_htf : public C_HX_counterflow_CRM void design_and_calc_m_dot_htf(C_HX_counterflow_CRM::S_des_calc_UA_par &des_par, double q_dot_design /*kWt*/, double dt_cold_approach /*C/K*/, C_HX_counterflow_CRM::S_des_solved &des_solved); - virtual void initialize(int hot_fl, util::matrix_t hot_fl_props, int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type); + virtual void initialize(int hot_fl, util::matrix_t hot_fl_props, int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type, + double yr_inflation); - virtual void initialize(int hot_fl, int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type); + virtual void initialize(int hot_fl, int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type, double yr_inflation); }; @@ -861,7 +864,8 @@ class C_HX_co2_to_co2_CRM : public C_HX_counterflow_CRM } - virtual void initialize(int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type); + virtual void initialize(int N_sub_hx, NS_HX_counterflow_eqs::E_UA_target_type od_UA_target_type, + double yr_inflation); }; namespace N_compact_hx @@ -907,10 +911,12 @@ class C_CO2_to_air_cooler double m_eta_fan; //[-] Fan isentropic efficiency int m_N_nodes_pass; //[-] Number of nodes per pass + double m_yr_inflation; //[yr] Inflation year target S_des_par_ind() { - m_T_amb_des = m_elev = std::numeric_limits::quiet_NaN(); + m_T_amb_des = m_elev = m_yr_inflation = + std::numeric_limits::quiet_NaN(); // Set realistic default values so model can solve without inputs for these values m_eta_fan = 0.5; @@ -1414,7 +1420,8 @@ class C_CO2_to_air_cooler double & k_air /*W/m-K*/, double & Pr_air); double /*M$*/ calculate_equipment_cost(double UA /*kWt/K*/, double V_material /*m^3*/, - double T_hot_in /*K*/, double P_hot_in /*kPa*/, double m_dot_hot /*kg/s*/); + double T_hot_in /*K*/, double P_hot_in /*kPa*/, double m_dot_hot /*kg/s*/, + double yr_inflation /*yr*/); double /*M$*/ calculate_bare_erected_cost(double cost_equipment /*M$*/); }; diff --git a/tcs/sco2_cycle_components.cpp b/tcs/sco2_cycle_components.cpp index 79978552d..64233ea30 100644 --- a/tcs/sco2_cycle_components.cpp +++ b/tcs/sco2_cycle_components.cpp @@ -326,6 +326,32 @@ int sco2_cycle_plot_data_TS(int cycle_config, int n_pres = pres.size(); int n_entr = entr.size(); + int HTR_HP_IN_ENUM = -1; + int PHX_IN_ENUM = -1; + int LTR_LP_IN_ENUM = -1; + int COOLER_IN_ENUM = -1; + if (cycle_config == 4) // Turbine Split Flow + { + HTR_HP_IN_ENUM = C_sco2_cycle_core::MC_OUT; + PHX_IN_ENUM = C_sco2_cycle_core::LTR_HP_OUT; + LTR_LP_IN_ENUM = C_sco2_cycle_core::TURB2_OUT; + COOLER_IN_ENUM = C_sco2_cycle_core::MIXER_OUT; + } + else if (cycle_config == 3) // Recompression with HTR Bypass + { + HTR_HP_IN_ENUM = C_sco2_cycle_core::MIXER_OUT; + PHX_IN_ENUM = C_sco2_cycle_core::MIXER2_OUT; + LTR_LP_IN_ENUM = C_sco2_cycle_core::HTR_LP_OUT; + COOLER_IN_ENUM = C_sco2_cycle_core::LTR_LP_OUT; + } + else + { + HTR_HP_IN_ENUM = C_sco2_cycle_core::MIXER_OUT; + PHX_IN_ENUM = C_sco2_cycle_core::HTR_HP_OUT; + LTR_LP_IN_ENUM = C_sco2_cycle_core::HTR_LP_OUT; + COOLER_IN_ENUM = C_sco2_cycle_core::LTR_LP_OUT; + } + // Get LTR HP data int err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::MC_OUT], entr[C_sco2_cycle_core::MC_OUT], pres[C_sco2_cycle_core::LTR_HP_OUT], entr[C_sco2_cycle_core::LTR_HP_OUT], @@ -334,70 +360,72 @@ int sco2_cycle_plot_data_TS(int cycle_config, return err_code; // Get HTR HP data - err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::MIXER_OUT], entr[C_sco2_cycle_core::MIXER_OUT], + err_code = Ts_data_over_linear_dP_ds(pres[HTR_HP_IN_ENUM], entr[HTR_HP_IN_ENUM], pres[C_sco2_cycle_core::HTR_HP_OUT], entr[C_sco2_cycle_core::HTR_HP_OUT], T_HTR_HP, s_HTR_HP, 25); if (err_code != 0) return err_code; // Get PHX data - err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::HTR_HP_OUT], entr[C_sco2_cycle_core::HTR_HP_OUT], + err_code = Ts_data_over_linear_dP_ds(pres[PHX_IN_ENUM], entr[PHX_IN_ENUM], pres[C_sco2_cycle_core::TURB_IN], entr[C_sco2_cycle_core::TURB_IN], T_PHX, s_PHX, 25); if (err_code != 0) return err_code; - // Get HTR HP data + // Get HTR LP data err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::TURB_OUT], entr[C_sco2_cycle_core::TURB_OUT], pres[C_sco2_cycle_core::HTR_LP_OUT], entr[C_sco2_cycle_core::HTR_LP_OUT], T_HTR_LP, s_HTR_LP, 25); if (err_code != 0) return err_code; - // Get LTR HP data - err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::HTR_LP_OUT], entr[C_sco2_cycle_core::HTR_LP_OUT], + // Get LTR LP data + err_code = Ts_data_over_linear_dP_ds(pres[LTR_LP_IN_ENUM], entr[LTR_LP_IN_ENUM], pres[C_sco2_cycle_core::LTR_LP_OUT], entr[C_sco2_cycle_core::LTR_LP_OUT], T_LTR_LP, s_LTR_LP, 25); if (err_code != 0) return err_code; - if (cycle_config != 2) // Recompression Cycle - { - if (n_pres < C_sco2_cycle_core::RC_OUT + 1 || n_entr != n_pres) - return -1; - - // Get main cooler data - err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::LTR_LP_OUT], entr[C_sco2_cycle_core::LTR_LP_OUT], - pres[C_sco2_cycle_core::MC_IN], entr[C_sco2_cycle_core::MC_IN], - T_main_cooler, s_main_cooler, 25); - if (err_code != 0) - return err_code; - - // Set IP data - T_pre_cooler.resize(1); - T_pre_cooler[0] = T_main_cooler[0]; - s_pre_cooler.resize(1); - s_pre_cooler[0] = s_main_cooler[0]; - } - else // Partial Cooling Cycle - { - if (n_pres < C_sco2_cycle_core::PC_OUT + 1 || n_entr != n_pres) - return -1; - - // Get pre cooler data - err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::LTR_LP_OUT], entr[C_sco2_cycle_core::LTR_LP_OUT], - pres[C_sco2_cycle_core::PC_IN], entr[C_sco2_cycle_core::PC_IN], - T_pre_cooler, s_pre_cooler, 25); - if (err_code != 0) - return err_code; - - // Get main cooler data - err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::PC_OUT], entr[C_sco2_cycle_core::PC_OUT], - pres[C_sco2_cycle_core::MC_IN], entr[C_sco2_cycle_core::MC_IN], - T_main_cooler, s_main_cooler, 25); - if (err_code != 0) - return err_code; - } + if (cycle_config == 1 || cycle_config == 3 || cycle_config == 4) // Recompression Cycle or Turbine split flow cycle + { + if (n_pres < C_sco2_cycle_core::RC_OUT + 1 || n_entr != n_pres) + return -1; + + // Get main cooler data + err_code = Ts_data_over_linear_dP_ds(pres[COOLER_IN_ENUM], entr[COOLER_IN_ENUM], + pres[C_sco2_cycle_core::MC_IN], entr[C_sco2_cycle_core::MC_IN], + T_main_cooler, s_main_cooler, 25); + if (err_code != 0) + return err_code; + + // Set IP data + T_pre_cooler.resize(1); + T_pre_cooler[0] = T_main_cooler[0]; + s_pre_cooler.resize(1); + s_pre_cooler[0] = s_main_cooler[0]; + } + else if (cycle_config == 2) // Partial Cooling Cycle + { + if (n_pres < C_sco2_cycle_core::PC_OUT + 1 || n_entr != n_pres) + return -1; + + // Get pre cooler data + err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::LTR_LP_OUT], entr[C_sco2_cycle_core::LTR_LP_OUT], + pres[C_sco2_cycle_core::PC_IN], entr[C_sco2_cycle_core::PC_IN], + T_pre_cooler, s_pre_cooler, 25); + if (err_code != 0) + return err_code; + + // Get main cooler data + err_code = Ts_data_over_linear_dP_ds(pres[C_sco2_cycle_core::PC_OUT], entr[C_sco2_cycle_core::PC_OUT], + pres[C_sco2_cycle_core::MC_IN], entr[C_sco2_cycle_core::MC_IN], + T_main_cooler, s_main_cooler, 25); + if (err_code != 0) + return err_code; + } + else + return -1; // Not modeled return 0; } @@ -412,7 +440,9 @@ int sco2_cycle_plot_data_PH(int cycle_config, std::vector & P_rc /*MPa*/, std::vector & h_rc /*kJ/kg*/, std::vector & P_pc /*MPa*/, - std::vector & h_pc /*kJ/kg*/) + std::vector & h_pc /*kJ/kg*/, + std::vector & P_t2 /*MPa*/, + std::vector & h_t2 /*kJ/kg*/) { int n_pres = pres.size(); int n_temp = temp.size(); @@ -431,46 +461,73 @@ int sco2_cycle_plot_data_PH(int cycle_config, if (err_code != 0) return err_code; - if (cycle_config != 2) // Recompression Cycle - { - if (n_pres < C_sco2_cycle_core::RC_OUT + 1 || n_temp != n_pres) - return -1; - - // Recompressor - err_code = Ph_data_over_turbomachinery(temp[C_sco2_cycle_core::LTR_LP_OUT], pres[C_sco2_cycle_core::LTR_LP_OUT], - temp[C_sco2_cycle_core::RC_OUT], pres[C_sco2_cycle_core::RC_OUT], - P_rc, h_rc, 25); - - if (err_code != 0) - return err_code; - - // Precompressor - P_pc.resize(1); - P_pc[0] = P_mc[0]; - h_pc.resize(1); - h_pc[0] = h_mc[0]; - } - else // Partial Cooling Cycle - { - if (n_pres < C_sco2_cycle_core::PC_OUT + 1 || n_temp != n_pres) - return -1; + if (cycle_config == 4) // Turbine Split Flow Cycle + { + if (n_pres < C_sco2_cycle_core::TURB2_OUT + 1 || n_temp != n_pres) + return -1; + + // Secondary Turbine + int err_code = Ph_data_over_turbomachinery(temp[C_sco2_cycle_core::HTR_HP_OUT], pres[C_sco2_cycle_core::HTR_HP_OUT], + temp[C_sco2_cycle_core::TURB2_OUT], pres[C_sco2_cycle_core::TURB2_OUT], + P_t2, h_t2, 25); + + if (err_code != 0) + return err_code; + + // Recompressor + P_rc.resize(1); + P_rc[0] = P_mc[0]; + h_rc.resize(1); + h_rc[0] = h_mc[0]; + + // Precompressor + P_pc.resize(1); + P_pc[0] = P_mc[0]; + h_pc.resize(1); + h_pc[0] = h_mc[0]; + } + else if (cycle_config == 1 || cycle_config == 3) // Recompression w/o and w/ HTR Bypass + { + if (n_pres < C_sco2_cycle_core::RC_OUT + 1 || n_temp != n_pres) + return -1; + + // Recompressor + err_code = Ph_data_over_turbomachinery(temp[C_sco2_cycle_core::LTR_LP_OUT], pres[C_sco2_cycle_core::LTR_LP_OUT], + temp[C_sco2_cycle_core::RC_OUT], pres[C_sco2_cycle_core::RC_OUT], + P_rc, h_rc, 25); + + if (err_code != 0) + return err_code; + + // Precompressor + P_pc.resize(1); + P_pc[0] = P_mc[0]; + h_pc.resize(1); + h_pc[0] = h_mc[0]; + } + else if (cycle_config == 2) // Partial Cooling Cycle + { + if (n_pres < C_sco2_cycle_core::PC_OUT + 1 || n_temp != n_pres) + return -1; - // Recompressor - err_code = Ph_data_over_turbomachinery(temp[C_sco2_cycle_core::PC_OUT], pres[C_sco2_cycle_core::PC_OUT], - temp[C_sco2_cycle_core::RC_OUT], pres[C_sco2_cycle_core::RC_OUT], - P_rc, h_rc, 25); + // Recompressor + err_code = Ph_data_over_turbomachinery(temp[C_sco2_cycle_core::PC_OUT], pres[C_sco2_cycle_core::PC_OUT], + temp[C_sco2_cycle_core::RC_OUT], pres[C_sco2_cycle_core::RC_OUT], + P_rc, h_rc, 25); - if (err_code != 0) - return err_code; + if (err_code != 0) + return err_code; - // Precompressor - err_code = Ph_data_over_turbomachinery(temp[C_sco2_cycle_core::PC_IN], pres[C_sco2_cycle_core::PC_IN], - temp[C_sco2_cycle_core::PC_OUT], pres[C_sco2_cycle_core::PC_OUT], - P_pc, h_pc, 25); + // Precompressor + err_code = Ph_data_over_turbomachinery(temp[C_sco2_cycle_core::PC_IN], pres[C_sco2_cycle_core::PC_IN], + temp[C_sco2_cycle_core::PC_OUT], pres[C_sco2_cycle_core::PC_OUT], + P_pc, h_pc, 25); - if (err_code != 0) - return err_code; - } + if (err_code != 0) + return err_code; + } + else // Not modeled + return -1; return 0; } @@ -771,6 +828,20 @@ int Ph_dome(double P_low /*MPa*/, std::vector & P_data /*MPa*/, std::vec return Ts_full_dome(T_P_target_solved - 273.15, T_data, s_data, P_data, h_data); } +double calculate_inflation_factor(double yr_base, double yr_target) +{ + if (yr_base == 0 || yr_target == 0) + return 1.0; + + std::vector yr_vec = std::vector{ yr_base, yr_target }; + + if (yr_vec[0] == 2017 && yr_vec[1] == 2024) { + return 1.407577; + } + + return std::numeric_limits::quiet_NaN(); +} + int C_MEQ_CO2_props_at_2phase_P::operator()(double T_co2 /*K*/, double *P_calc /*kPa*/) { int prop_err_code = CO2_TQ(T_co2, 0.0, &mc_co2_props); @@ -806,14 +877,29 @@ void C_HeatExchanger::hxr_conductance(const std::vector & m_dots, double } double C_turbine::calculate_equipment_cost(double T_in /*K*/, double P_in /*kPa*/, double m_dot /*kg/s*/, - double T_out /*K*/, double P_out /*kPa*/, double W_dot /*kWe*/) + double T_out /*K*/, double P_out /*kPa*/, double W_dot /*kWe*/, double yr_inflation/*yr*/) { switch (m_cost_model) { case C_turbine::E_CARLSON_17: - return 7.79*1.E-3*std::pow(W_dot, 0.6842); //[M$] needs power in kWe + { + double yr_base_inflation = 2017; + double f_inflation = calculate_inflation_factor(yr_base_inflation, yr_inflation); + return 7.79 * 1.E-3 * std::pow(W_dot, 0.6842) * f_inflation; //[M$] needs power in kWe + } case C_turbine::E_WEILAND_19__AXIAL: - return 182600*std::pow(W_dot*1.E-3, 0.5561)*1.E-6; //[M$] needs power in MWe + { + double base_cost = 182600 * std::pow(W_dot * 1.E-3, 0.5561); // [$] needs power in MWe + + double cost_mult = 1.0; + double T_in_C = T_in - 273.15; //[C] + if (T_in_C >= 550) { + cost_mult = 1.0 + ((1.106e-4) * std::pow(T_in_C - 550, 2.0)); + } + double yr_base_inflation = 2017; + double f_inflation = calculate_inflation_factor(yr_base_inflation, yr_inflation); + return base_cost * cost_mult * 1e-6 * f_inflation; //[M$] + } default: return std::numeric_limits::quiet_NaN(); } @@ -897,7 +983,7 @@ void C_turbine::turbine_sizing(const S_design_parameters & des_par_in, int & err ms_des_solved.m_W_dot = ms_des_par.m_m_dot*(ms_des_par.m_h_in - ms_des_par.m_h_out); ms_des_solved.m_equipment_cost = calculate_equipment_cost(ms_des_par.m_T_in, ms_des_par.m_P_in, ms_des_par.m_m_dot, - T_out, ms_des_par.m_P_out, ms_des_solved.m_W_dot); + T_out, ms_des_par.m_P_out, ms_des_solved.m_W_dot, ms_des_par.m_yr_inflation); ms_des_solved.m_bare_erected_cost = calculate_bare_erected_cost(ms_des_solved.m_equipment_cost); } @@ -1494,14 +1580,22 @@ int C_comp_multi_stage::C_MEQ_N_rpm__P_out::operator()(double N_rpm /*rpm*/, dou } double C_comp_multi_stage::calculate_equipment_cost(double T_in /*K*/, double P_in /*kPa*/, double m_dot /*kg/s*/, - double T_out /*K*/, double P_out /*kPa*/, double W_dot /*kWe*/) + double T_out /*K*/, double P_out /*kPa*/, double W_dot /*kWe*/, double yr_inflation /*yr*/) { switch (m_cost_model) { case C_comp_multi_stage::E_CARLSON_17: - return 6.898*1.E-3*std::pow(W_dot, 0.7865); //[M$] needs power in kWe + { + double yr_base_inflation = 2017; + double f_inflation = calculate_inflation_factor(yr_base_inflation, yr_inflation); + return 6.898 * 1.E-3 * std::pow(W_dot, 0.7865) * f_inflation; //[M$] needs power in kWe + } case C_comp_multi_stage::E_WEILAND_19__IG: - return 1.23*std::pow(W_dot*1.E-3, 0.3992); //[M$] needs power in MWe + { + double yr_base_inflation = 2017; + double f_inflation = calculate_inflation_factor(yr_base_inflation, yr_inflation); + return 1.23 * std::pow(W_dot * 1.E-3, 0.3992) * f_inflation; //[M$] needs power in MWe + } default: return std::numeric_limits::quiet_NaN(); } @@ -1517,7 +1611,7 @@ double C_comp_multi_stage::calculate_bare_erected_cost(double cost_equipment /*M } int C_comp_multi_stage::design_given_outlet_state(int comp_model_code, double T_in /*K*/, double P_in /*kPa*/, double m_dot_cycle /*kg/s*/, - double T_out /*K*/, double P_out /*K*/, double tol /*-*/) + double T_out /*K*/, double P_out /*K*/, double tol /*-*/, double yr_inflation /*yr*/) { m_compressor_model = comp_model_code; //[-] @@ -1655,7 +1749,7 @@ int C_comp_multi_stage::design_given_outlet_state(int comp_model_code, double T_ } ms_des_solved.m_cost_equipment = calculate_equipment_cost(ms_des_solved.m_T_in, ms_des_solved.m_P_in, ms_des_solved.m_m_dot, - ms_des_solved.m_T_out, ms_des_solved.m_P_out, ms_des_solved.m_W_dot); + ms_des_solved.m_T_out, ms_des_solved.m_P_out, ms_des_solved.m_W_dot, yr_inflation); ms_des_solved.m_cost_bare_erected = calculate_bare_erected_cost(ms_des_solved.m_cost_equipment); //[M$] diff --git a/tcs/sco2_cycle_components.h b/tcs/sco2_cycle_components.h index 42a700a29..e297ea638 100644 --- a/tcs/sco2_cycle_components.h +++ b/tcs/sco2_cycle_components.h @@ -56,7 +56,9 @@ int sco2_cycle_plot_data_PH(int cycle_config, std::vector & P_rc /*MPa*/, std::vector & h_rc /*kJ/kg*/, std::vector & P_pc /*MPa*/, - std::vector & h_pc /*kJ/kg*/); + std::vector & h_pc /*kJ/kg*/, + std::vector & P_t2 /*MPa*/, + std::vector & h_t2 /*kJ/kg*/); int Ts_arrays_over_constP(double T_cold /*C*/, double T_hot /*C*/, std::vector P_consts /*kPa*/, std::vector> & T_data /*C*/, std::vector> & s_data); @@ -71,6 +73,8 @@ int Ts_full_dome(double T_cold /*C*/, std::vector & T_data /*C*/, std::v int Ph_dome(double P_low /*MPa*/, std::vector & P_data /*MPa*/, std::vector & h_data); +double calculate_inflation_factor(double yr_base, double yr_target); + class C_MEQ_CO2_props_at_2phase_P : public C_monotonic_equation { private: @@ -162,11 +166,13 @@ class C_turbine // Mass flow rate double m_m_dot; //[kg/s] (cycle, not basis) + double m_yr_inflation; //[yr] Inflation target year + S_design_parameters() { m_N_design = m_N_comp_design_if_linked = m_P_in = m_T_in = m_D_in = m_h_in = m_s_in = m_P_out = m_h_out = - m_m_dot = std::numeric_limits::quiet_NaN(); + m_m_dot = m_yr_inflation = std::numeric_limits::quiet_NaN(); } }; @@ -246,7 +252,7 @@ class C_turbine } double calculate_equipment_cost(double T_in /*K*/, double P_in /*kPa*/, double m_dot /*kg/s*/, - double T_out /*K*/, double P_out /*kPa*/, double W_dot /*kWe*/); + double T_out /*K*/, double P_out /*kPa*/, double W_dot /*kWe*/, double yr_inflation /*yr*/); double /*M$*/ calculate_bare_erected_cost(double cost_equipment /*M$*/); @@ -659,12 +665,13 @@ class C_comp_multi_stage }; double calculate_equipment_cost(double T_in /*K*/, double P_in /*kPa*/, double m_dot /*kg/s*/, - double T_out /*K*/, double P_out /*kPa*/, double W_dot /*kWe*/); + double T_out /*K*/, double P_out /*kPa*/, double W_dot /*kWe*/, + double yr_inflation /*yr*/); double calculate_bare_erected_cost(double cost_equipment /*M$*/); int design_given_outlet_state(int comp_model_code, double T_in /*K*/, double P_in /*kPa*/, double m_dot /*kg/s*/, - double T_out /*K*/, double P_out /*K*/, double tol /*-*/); + double T_out /*K*/, double P_out /*K*/, double tol /*-*/, double yr_inflation /*yr*/); void off_design_given_N(double T_in /*K*/, double P_in /*kPa*/, double m_dot_cycle /*kg/s*/, double N_rpm /*rpm*/, int & error_code, double & T_out /*K*/, double & P_out /*kPa*/); diff --git a/tcs/sco2_cycle_templates.h b/tcs/sco2_cycle_templates.h index a4e27a75a..846ca4f69 100644 --- a/tcs/sco2_cycle_templates.h +++ b/tcs/sco2_cycle_templates.h @@ -25,6 +25,9 @@ class C_sco2_cycle_core RC_OUT, // Recompresor outlet PC_IN, // Precompressor inlet (partial cooling cycle) PC_OUT, // Precompressor outlet (partial cooling cycle) + BYPASS_OUT, // Bypass outlet (htr bypass cycle) + MIXER2_OUT, // Mixer 2 Outlet (htr bypass cycle) + TURB2_OUT, // Secondary Turbine Outlet (turbine split flow cycle) END_SCO2_STATES }; @@ -35,6 +38,52 @@ class C_sco2_cycle_core E_SET_T_T_IN // Model sets turbine inlet temperature to HTF inlet temperature }; + class E_cycle_error_msg + { + public: + + // Error Types + enum E_cycle_error_types + { + E_CANNOT_PRODUCE_POWER = 200, + E_CO2_PROPS_ERROR, + E_ETA_THRESHOLD, + E_HTR_LTR_CONVERGENCE, + E_AIR_COOLER_CONVERGENCE, + E_NOT_SUPPORTED, + E_NO_ERROR + }; + + // Get error message corresponding to error type + // NO COMMAS in message + static std::string get_error_string(int error_enum) + { + switch (error_enum) + { + case((int)E_NO_ERROR): + return "No error"; + break; + case((int)E_CANNOT_PRODUCE_POWER): + return "Cycle cannot produce power"; + break; + case((int)E_CO2_PROPS_ERROR): + return "Error calculating sCO2 properties"; + case((int)E_ETA_THRESHOLD): + return "Eta below threshold"; + case((int)E_HTR_LTR_CONVERGENCE): + return "HTR LTR convergence issue"; + case((int)E_AIR_COOLER_CONVERGENCE): + return "Air cooler did not converge"; + case((int)E_NOT_SUPPORTED): + return "Feature not supported by configuration"; + default: + return "Error code not recognized"; + break; + } + } + + }; + enum class E_turbo_gen_motor_config { // Options to apply motor and generator losses @@ -66,6 +115,7 @@ class C_sco2_cycle_core double m_m_dot_rc; //[kg/s] double m_m_dot_pc; //[kg/s] double m_m_dot_t; //[kg/s] + double m_m_dot_t2; //[kg/s] double m_recomp_frac; //[-] double m_UA_LTR; //[kW/K] double m_UA_HTR; //[kW/K] @@ -73,6 +123,9 @@ class C_sco2_cycle_core double m_W_dot_rc; //[kWe] double m_W_dot_pc; //[kWe] double m_W_dot_t; //[kWe] + double m_W_dot_t2; //[kWe] + double m_bypass_frac; //[-] Bypass Fraction + double m_turbine_split_frac; // [-] Turbine split fraction (TSF only) double m_W_dot_cooler_tot; //[kWe] @@ -82,6 +135,7 @@ class C_sco2_cycle_core C_comp_multi_stage::S_des_solved ms_rc_ms_des_solved; C_comp_multi_stage::S_des_solved ms_pc_ms_des_solved; C_turbine::S_design_solved ms_t_des_solved; + C_turbine::S_design_solved ms_t2_des_solved; C_HX_counterflow_CRM::S_des_solved ms_LTR_des_solved; C_HX_counterflow_CRM::S_des_solved ms_HTR_des_solved; @@ -122,11 +176,14 @@ class C_sco2_cycle_core double m_eta_pc; //[-] design-point efficiency of the pre-compressor; double m_des_tol; //[-] Convergence tolerance double m_des_opt_tol; //[-] Optimization tolerance - + + double m_eta_thermal_cutoff; //[] Minimum eta to fully design cycle (returns failure below value) + // Air cooler parameters bool m_is_des_air_cooler; //[-] False will skip physical air cooler design. UA will not be available for cost models. double m_is_recomp_ok; //[-] 1 = Yes, 0 = simple cycle only, < 0 = fix f_recomp to abs(input) + double m_is_bypass_ok; //[-] 1 = Yes, 0 = no bp, < 0 = fix f_bypass to abs(input) int m_des_objective_type; //[2] = min phx deltat then max eta, [else] max eta double m_min_phx_deltaT; //[C] @@ -145,12 +202,12 @@ class C_sco2_cycle_core S_auto_opt_design_hit_eta_parameters() { - m_T_pc_in = + m_eta_thermal = m_T_pc_in = m_LTR_UA = m_LTR_min_dT = m_LTR_eff_target = m_LTR_eff_max = m_HTR_UA = m_HTR_min_dT = m_HTR_eff_target = m_HTR_eff_max = m_eta_pc = m_des_tol = m_des_opt_tol = - m_is_recomp_ok = + m_is_recomp_ok = m_is_bypass_ok = m_PR_HP_to_LP_guess = m_f_PR_HP_to_IP_guess = std::numeric_limits::quiet_NaN(); // Recuperator design target codes @@ -202,11 +259,15 @@ class C_sco2_cycle_core double m_des_tol; //[-] Convergence tolerance double m_des_opt_tol; //[-] Optimization tolerance - + + double m_eta_thermal_cutoff; //[] Minimum eta to fully design cycle (returns failure below value) + // Air cooler parameters bool m_is_des_air_cooler; //[-] False will skip physical air cooler design. UA will not be available for cost models. double m_is_recomp_ok; //[-] 1 = Yes, 0 = simple cycle only, < 0 = fix f_recomp to abs(input) + double m_is_bypass_ok; //[-] 1 = Yes, 0 = no bypass, < 0 = fix bp_frac to abs(input) + double m_is_turbinesplit_ok; //[-] 1 = Yes, 0 = no secondary turbine, < 0 = fix split_frac to abs(input) (Turbine split flow ONLY) bool m_fixed_P_mc_out; //[-] if true, P_mc_out is fixed at 'm_P_high_limit' @@ -226,12 +287,12 @@ class C_sco2_cycle_core S_auto_opt_design_parameters() { - m_T_pc_in = + m_T_pc_in = m_UA_rec_total = m_LTR_UA = m_LTR_min_dT = m_LTR_eff_target = m_LTR_eff_max = m_HTR_UA = m_HTR_min_dT = m_HTR_eff_target = m_HTR_eff_max = m_eta_pc = m_des_tol = m_des_opt_tol = - m_is_recomp_ok = + m_is_recomp_ok = m_is_bypass_ok = m_PR_HP_to_LP_guess = m_f_PR_HP_to_IP_guess = std::numeric_limits::quiet_NaN(); // Recuperator design target codes @@ -417,7 +478,7 @@ class C_sco2_cycle_core double m_T_amb_des; //[K] Design point ambient temperature double m_elevation; //[m] Elevation (used to calculate ambient pressure) int m_N_nodes_pass; //[-] Number of nodes per pass - + double m_yr_inflation; //[yr] Inflation target year public: @@ -434,7 +495,8 @@ class C_sco2_cycle_core double eta_t /*-*/, double N_turbine /*rpm*/, double frac_fan_power /*-*/, double eta_fan /*-*/, double deltaP_cooler_frac /*-*/, int N_nodes_pass /*-*/, - double T_amb_des /*K*/, double elevation /*m*/) + double T_amb_des /*K*/, double elevation /*m*/, + double yr_inflation /*yr*/) { m_turbo_gen_motor_config = turbo_gen_motor_config; m_eta_generator = eta_generator; //[-] @@ -467,6 +529,8 @@ class C_sco2_cycle_core m_T_amb_des = T_amb_des; //[K] m_elevation = elevation; //[m] + m_yr_inflation = yr_inflation; //[] + // Set design limits!!!! ms_des_limits.m_UA_net_power_ratio_max = 2.0; //[-/K] ms_des_limits.m_UA_net_power_ratio_min = 1.E-5; //[-/K] diff --git a/tcs/sco2_htrbypass_cycle.cpp b/tcs/sco2_htrbypass_cycle.cpp new file mode 100644 index 000000000..b69e3bdc9 --- /dev/null +++ b/tcs/sco2_htrbypass_cycle.cpp @@ -0,0 +1,2187 @@ +/* +BSD 3-Clause License + +Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/ssc/blob/develop/LICENSE +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "sco2_htrbypass_cycle.h" +#include "sco2_cycle_components.h" + +#include "CO2_properties.h" + + +#include "fmin.h" + + +// ********************************************************************************** C_sco2_htrbp_core CORE MODEL + +void C_sco2_htrbp_core::initialize_solve() +{ + m_outputs.Init(); +} + +int C_sco2_htrbp_core::solve() +{ + initialize_solve(); + m_outputs.m_error_code = -1; + + // Apply scaling to the turbomachinery here + { + m_outputs.m_mc_ms.m_r_W_dot_scale = m_inputs.m_W_dot_net_design / 10.E3; //[-] + m_outputs.m_rc_ms.m_r_W_dot_scale = m_outputs.m_mc_ms.m_r_W_dot_scale; //[-] + m_outputs.m_t.m_r_W_dot_scale = m_outputs.m_mc_ms.m_r_W_dot_scale; //[-] + } + + // Initialize Recuperators + { + // LTR + m_outputs.mc_LT_recup.initialize(m_inputs.m_LTR_N_sub_hxrs, m_inputs.m_LTR_od_UA_target_type, m_inputs.m_yr_inflation); + // HTR + m_outputs.mc_HT_recup.initialize(m_inputs.m_HTR_N_sub_hxrs, m_inputs.m_HTR_od_UA_target_type, m_inputs.m_yr_inflation); + } + + // Initialize a few variables + { + m_outputs.m_temp[C_sco2_cycle_core::MC_IN] = m_inputs.m_T_mc_in; //[K] + m_outputs.m_pres[C_sco2_cycle_core::MC_IN] = m_inputs.m_P_mc_in; + m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] = m_inputs.m_P_mc_out; + m_outputs.m_temp[C_sco2_cycle_core::TURB_IN] = m_inputs.m_T_t_in; //[K] + } + + // Apply pressure drops to heat exchangers, fully defining the pressures at all states + { + if (m_inputs.m_DP_LTR[0] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] - m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] * std::abs(m_inputs.m_DP_LTR[0]); // relative pressure drop specified for LT recuperator (cold stream) + else + m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] - m_inputs.m_DP_LTR[0]; // absolute pressure drop specified for LT recuperator (cold stream) + + if ((m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA && m_inputs.m_LTR_UA < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_UA && m_inputs.m_LTR_UA < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_MIN_DT && m_inputs.m_LTR_min_dT < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_EFFECTIVENESS && m_inputs.m_LTR_eff_target < 1.0E-12)) + m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_OUT]; // If there is no LT recuperator, there is no pressure drop + + m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT] = m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT]; // Assume no pressure drop in mixing valve + m_outputs.m_pres[C_sco2_cycle_core::RC_OUT] = m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT]; // Assume no pressure drop in mixing valve + + if (m_inputs.m_DP_HTR[0] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT] + - m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT] * std::abs(m_inputs.m_DP_HTR[0]); // relative pressure drop specified for HT recuperator (cold stream) + else + m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT] - m_inputs.m_DP_HTR[0]; // absolute pressure drop specified for HT recuperator (cold stream) + + if ((m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA && m_inputs.m_HTR_UA < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_UA && m_inputs.m_HTR_UA < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_MIN_DT && m_inputs.m_HTR_min_dT < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_EFFECTIVENESS && m_inputs.m_HTR_eff_target < 1.0E-12)) + m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT]; // If there is no HT recuperator, there is no pressure drop + + if (m_inputs.m_DP_PHX[0] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::TURB_IN] = m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT] - m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT] * std::abs(m_inputs.m_DP_PHX[0]); // relative pressure drop specified for PHX + else + m_outputs.m_pres[C_sco2_cycle_core::TURB_IN] = m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT] - m_inputs.m_DP_PHX[0]; // absolute pressure drop specified for PHX + + if (m_inputs.m_DP_PC_main[1] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_IN] / (1.0 - std::abs(m_inputs.m_DP_PC_main[1])); // relative pressure drop specified for precooler: P1=P9-P9*rel_DP => P1=P9*(1-rel_DP) + else + m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_IN] + m_inputs.m_DP_PC_main[1]; + + if (m_inputs.m_DP_LTR[1] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT] / (1.0 - std::abs(m_inputs.m_DP_LTR[1])); // relative pressure drop specified for LT recuperator (hot stream) + else + m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT] + m_inputs.m_DP_LTR[1]; // absolute pressure drop specified for LT recuperator (hot stream) + + if ((m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA && m_inputs.m_LTR_UA < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_UA && m_inputs.m_LTR_UA < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_MIN_DT && m_inputs.m_LTR_min_dT < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_EFFECTIVENESS && m_inputs.m_LTR_eff_target < 1.0E-12)) + m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT]; // if there is no LT recuperator, there is no pressure drop + + if (m_inputs.m_DP_HTR[1] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT] = m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT] / (1.0 - std::abs(m_inputs.m_DP_HTR[1])); // relative pressure drop specified for HT recuperator (hot stream) + else + m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT] = m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT] + m_inputs.m_DP_HTR[1]; // absolute pressure drop specified for HT recuperator (hot stream) + + if ((m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA && m_inputs.m_HTR_UA < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_UA && m_inputs.m_HTR_UA < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_MIN_DT && m_inputs.m_HTR_min_dT < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_EFFECTIVENESS && m_inputs.m_HTR_eff_target < 1.0E-12)) + m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT] = m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT]; // if there is no HT recuperator, there is no pressure drop + + + // Added pressures + m_outputs.m_pres[C_sco2_cycle_core::BYPASS_OUT] = m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT]; + m_outputs.m_pres[C_sco2_cycle_core::MIXER2_OUT] = m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT]; + + + } + + // Determine equivalent isentropic efficiencies for main compressor and turbine, if necessary. + double eta_mc_isen = std::numeric_limits::quiet_NaN(); + double eta_t_isen = std::numeric_limits::quiet_NaN(); + { + if (m_inputs.m_eta_mc < 0.0) + { + int poly_error_code = 0; + + isen_eta_from_poly_eta(m_outputs.m_temp[C_sco2_cycle_core::MC_IN], m_outputs.m_pres[C_sco2_cycle_core::MC_IN], m_outputs.m_pres[C_sco2_cycle_core::MC_OUT], std::abs(m_inputs.m_eta_mc), + true, poly_error_code, eta_mc_isen); + + if (poly_error_code != 0) + { + m_outputs.m_error_code = poly_error_code; + return m_outputs.m_error_code; + } + } + else + eta_mc_isen = m_inputs.m_eta_mc; + + if (m_inputs.m_eta_t < 0.0) + { + int poly_error_code = 0; + + isen_eta_from_poly_eta(m_outputs.m_temp[C_sco2_cycle_core::TURB_IN], m_outputs.m_pres[C_sco2_cycle_core::TURB_IN], m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT], std::abs(m_inputs.m_eta_t), + false, poly_error_code, eta_t_isen); + + if (poly_error_code != 0) + { + m_outputs.m_error_code = poly_error_code; + return m_outputs.m_error_code; + } + } + else + eta_t_isen = m_inputs.m_eta_t; + } + + // Determine the outlet state and specific work for the main compressor and turbine. + + // Main compressor + m_outputs.m_w_mc = std::numeric_limits::quiet_NaN(); + { + int comp_error_code = 0; + + calculate_turbomachinery_outlet_1(m_outputs.m_temp[C_sco2_cycle_core::MC_IN], m_outputs.m_pres[C_sco2_cycle_core::MC_IN], m_outputs.m_pres[C_sco2_cycle_core::MC_OUT], eta_mc_isen, true, + comp_error_code, m_outputs.m_enth[C_sco2_cycle_core::MC_IN], m_outputs.m_entr[C_sco2_cycle_core::MC_IN], m_outputs.m_dens[C_sco2_cycle_core::MC_IN], m_outputs.m_temp[C_sco2_cycle_core::MC_OUT], + m_outputs.m_enth[C_sco2_cycle_core::MC_OUT], m_outputs.m_entr[C_sco2_cycle_core::MC_OUT], m_outputs.m_dens[C_sco2_cycle_core::MC_OUT], m_outputs.m_w_mc); + + if (comp_error_code != 0) + { + m_outputs.m_error_code = comp_error_code; + return m_outputs.m_error_code; + } + } + + // Turbine + m_outputs.m_w_t = std::numeric_limits::quiet_NaN(); + { + int turbine_error_code = 0; + + calculate_turbomachinery_outlet_1(m_outputs.m_temp[C_sco2_cycle_core::TURB_IN], m_outputs.m_pres[C_sco2_cycle_core::TURB_IN], m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT], eta_t_isen, false, + turbine_error_code, m_outputs.m_enth[C_sco2_cycle_core::TURB_IN], m_outputs.m_entr[C_sco2_cycle_core::TURB_IN], m_outputs.m_dens[C_sco2_cycle_core::TURB_IN], m_outputs.m_temp[C_sco2_cycle_core::TURB_OUT], + m_outputs.m_enth[C_sco2_cycle_core::TURB_OUT], m_outputs.m_entr[C_sco2_cycle_core::TURB_OUT], m_outputs.m_dens[C_sco2_cycle_core::TURB_OUT], m_outputs.m_w_t); + + if (turbine_error_code != 0) + { + m_outputs.m_error_code = turbine_error_code; + return m_outputs.m_error_code; + } + } + + // Check that this cycle can produce power + m_outputs.m_w_rc = std::numeric_limits::quiet_NaN(); + { + double eta_rc_isen = std::numeric_limits::quiet_NaN(); + + if (m_inputs.m_recomp_frac >= 1.E-12) + { + if (m_inputs.m_eta_rc < 0.0) // need to convert polytropic efficiency to isentropic efficiency + { + int rc_error_code = 0; + + isen_eta_from_poly_eta(m_outputs.m_temp[C_sco2_cycle_core::MC_OUT], m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::RC_OUT], std::abs(m_inputs.m_eta_rc), + true, rc_error_code, eta_rc_isen); + + if (rc_error_code != 0) + { + m_outputs.m_error_code = rc_error_code; + return m_outputs.m_error_code; + } + } + else + eta_rc_isen = m_inputs.m_eta_rc; + + int rc_error_code = 0; + + calculate_turbomachinery_outlet_1(m_outputs.m_temp[C_sco2_cycle_core::MC_OUT], m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::RC_OUT], eta_rc_isen, + true, rc_error_code, m_outputs.m_w_rc); + + if (rc_error_code != 0) + { + m_outputs.m_error_code = rc_error_code; + return m_outputs.m_error_code; + } + } + else + m_outputs.m_w_rc = 0.0; + + if (m_outputs.m_w_mc + m_outputs.m_w_rc + m_outputs.m_w_t <= 0.0) // positive net power is impossible; return an error + { + m_outputs.m_error_code = (int)C_sco2_cycle_core::E_cycle_error_msg::E_CANNOT_PRODUCE_POWER; + return m_outputs.m_error_code; + } + } + + // Solve the recuperators + { + C_mono_htrbp_core_HTR_des HTR_des_eq(this); + C_monotonic_eq_solver HTR_des_solver(HTR_des_eq); + + { + double T_HTR_LP_out_lower = m_outputs.m_temp[C_sco2_cycle_core::MC_OUT]; //[K] Coldest possible temperature + double T_HTR_LP_out_upper = m_outputs.m_temp[C_sco2_cycle_core::TURB_OUT]; //[K] Hottest possible temperature + + double T_HTR_LP_out_guess_lower = std::min(T_HTR_LP_out_upper - 2.0, std::max(T_HTR_LP_out_lower + 15.0, 220.0 + 273.15)); //[K] There is nothing special about these guesses... + double T_HTR_LP_out_guess_upper = std::min(T_HTR_LP_out_guess_lower + 20.0, T_HTR_LP_out_upper - 1.0); //[K] There is nothing special about these guesses, either... + + HTR_des_solver.settings(m_inputs.m_des_tol * m_outputs.m_temp[C_sco2_cycle_core::MC_IN], 1000, T_HTR_LP_out_lower, T_HTR_LP_out_upper, false); + + double T_HTR_LP_out_solved, tol_T_HTR_LP_out_solved; + T_HTR_LP_out_solved = tol_T_HTR_LP_out_solved = std::numeric_limits::quiet_NaN(); + int iter_T_HTR_LP_out = -1; + + int T_HTR_LP_out_code = HTR_des_solver.solve(T_HTR_LP_out_guess_lower, T_HTR_LP_out_guess_upper, 0, + T_HTR_LP_out_solved, tol_T_HTR_LP_out_solved, iter_T_HTR_LP_out); + + if (T_HTR_LP_out_code != C_monotonic_eq_solver::CONVERGED) + { + m_outputs.m_error_code = (int)C_sco2_cycle_core::E_cycle_error_msg::E_HTR_LTR_CONVERGENCE; + return m_outputs.m_error_code; + } + + double test = 0; + solve_HTR(T_HTR_LP_out_solved, &test); + } + + } + + // State 5 can now be fully defined + { + // Check if there is flow through HTR_HP + if (m_outputs.m_m_dot_htr_hp <= 1e-12) + m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT] = m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT]; + else + m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT] = m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT] + m_outputs.m_Q_dot_HT / m_outputs.m_m_dot_htr_hp; // Energy balance on cold stream of high-temp recuperator + + int prop_error_code = CO2_PH(m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT], m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT], &m_co2_props); + if (prop_error_code != 0) + { + m_outputs.m_error_code = prop_error_code; + return m_outputs.m_error_code; + } + m_outputs.m_temp[C_sco2_cycle_core::HTR_HP_OUT] = m_co2_props.temp; + m_outputs.m_entr[C_sco2_cycle_core::HTR_HP_OUT] = m_co2_props.entr; + m_outputs.m_dens[C_sco2_cycle_core::HTR_HP_OUT] = m_co2_props.dens; + } + + // Calculate total work and heat metrics + { + // Work + m_outputs.m_W_dot_mc = m_outputs.m_w_mc * m_outputs.m_m_dot_mc; //[kWe] + m_outputs.m_W_dot_rc = m_outputs.m_w_rc * m_outputs.m_m_dot_rc; //[kWe] + m_outputs.m_W_dot_t = m_outputs.m_w_t * m_outputs.m_m_dot_t; //[kWe] + m_outputs.m_W_dot_net = m_outputs.m_W_dot_mc + m_outputs.m_W_dot_rc + m_outputs.m_W_dot_t; + + // Air Cooler (heat rejection unit) + m_outputs.m_W_dot_air_cooler = m_inputs.m_frac_fan_power * m_outputs.m_W_dot_net; + m_outputs.m_Q_dot_air_cooler = m_outputs.m_m_dot_mc * (m_outputs.m_enth[C_sco2_cycle_core::LTR_LP_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MC_IN]); + + // Total Heat Entering sco2 + m_outputs.m_Q_dot_total = m_outputs.m_W_dot_net + m_outputs.m_Q_dot_air_cooler; + + // LTR + m_outputs.m_Q_dot_LTR_LP = m_outputs.m_m_dot_t * (m_outputs.m_enth[C_sco2_cycle_core::HTR_LP_OUT] - m_outputs.m_enth[C_sco2_cycle_core::LTR_LP_OUT]); + m_outputs.m_Q_dot_LTR_HP = m_outputs.m_m_dot_mc * (m_outputs.m_enth[C_sco2_cycle_core::LTR_HP_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MC_OUT]); + + // LTR + m_outputs.m_Q_dot_HTR_LP = m_outputs.m_m_dot_t * (m_outputs.m_enth[C_sco2_cycle_core::TURB_OUT] - m_outputs.m_enth[C_sco2_cycle_core::HTR_LP_OUT]); + m_outputs.m_Q_dot_HTR_HP = m_outputs.m_m_dot_htr_hp * (m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT]); + } + + // Calculate Bypass Energy + { + // Set Bypass Temp based on HTR_HP_OUT + m_outputs.m_temp[C_sco2_cycle_core::BYPASS_OUT] = m_outputs.m_temp[C_sco2_cycle_core::HTR_HP_OUT] + m_inputs.m_dT_BP; + + // Calculate BYPASS_OUT properties + int prop_error_code = CO2_TP(m_outputs.m_temp[C_sco2_cycle_core::BYPASS_OUT], m_outputs.m_pres[C_sco2_cycle_core::BYPASS_OUT], &m_co2_props); + if (prop_error_code != 0) + { + m_outputs.m_error_code = -1; + return m_outputs.m_error_code; + } + m_outputs.m_enth[C_sco2_cycle_core::BYPASS_OUT] = m_co2_props.enth; + m_outputs.m_entr[C_sco2_cycle_core::BYPASS_OUT] = m_co2_props.entr; + m_outputs.m_dens[C_sco2_cycle_core::BYPASS_OUT] = m_co2_props.dens; + + // Calculate Heat Transfer in Bypass + m_outputs.m_Q_dot_BP = m_outputs.m_m_dot_bp * (m_outputs.m_enth[C_sco2_cycle_core::BYPASS_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT]); + } + + // Simulate Mixer 2 + { + // If Bypass and HTR have flow + if (m_inputs.m_bypass_frac >= 1e-12 && m_inputs.m_bypass_frac <= (1.0 - 1e-12)) + { + m_outputs.m_enth[C_sco2_cycle_core::MIXER2_OUT] = (1.0 - m_inputs.m_bypass_frac) * m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT] + + m_inputs.m_bypass_frac * m_outputs.m_enth[C_sco2_cycle_core::BYPASS_OUT]; //[C_sco2_cycle_core::kJ/kg] + + int prop_error_code = CO2_PH(m_outputs.m_pres[C_sco2_cycle_core::MIXER2_OUT], m_outputs.m_enth[C_sco2_cycle_core::MIXER2_OUT], &m_co2_props); + if (prop_error_code != 0) + { + m_outputs.m_error_code = -1; + return m_outputs.m_error_code; + } + m_outputs.m_temp[C_sco2_cycle_core::MIXER2_OUT] = m_co2_props.temp; //[C_sco2_cycle_core::K] + m_outputs.m_entr[C_sco2_cycle_core::MIXER2_OUT] = m_co2_props.entr; //[C_sco2_cycle_core::kJ/kg-K] + m_outputs.m_dens[C_sco2_cycle_core::MIXER2_OUT] = m_co2_props.dens; //[C_sco2_cycle_core::kg/m^3] + + } + // Flow only through HTR + else if (m_inputs.m_bypass_frac <= (1.0 - 1e-12)) + { + m_outputs.m_temp[C_sco2_cycle_core::MIXER2_OUT] = m_outputs.m_temp[C_sco2_cycle_core::HTR_HP_OUT]; //[C_sco2_cycle_core::K] + m_outputs.m_enth[C_sco2_cycle_core::MIXER2_OUT] = m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT]; //[C_sco2_cycle_core::kJ/kg] + m_outputs.m_entr[C_sco2_cycle_core::MIXER2_OUT] = m_outputs.m_entr[C_sco2_cycle_core::HTR_HP_OUT]; //[C_sco2_cycle_core::kJ/kg-K] + m_outputs.m_dens[C_sco2_cycle_core::MIXER2_OUT] = m_outputs.m_dens[C_sco2_cycle_core::HTR_HP_OUT]; //[C_sco2_cycle_core::kg/m^3] + } + // Flow only through Bypass + else + { + m_outputs.m_temp[C_sco2_cycle_core::MIXER2_OUT] = m_outputs.m_temp[C_sco2_cycle_core::BYPASS_OUT]; //[C_sco2_cycle_core::K] + m_outputs.m_enth[C_sco2_cycle_core::MIXER2_OUT] = m_outputs.m_enth[C_sco2_cycle_core::BYPASS_OUT]; //[C_sco2_cycle_core::kJ/kg] + m_outputs.m_entr[C_sco2_cycle_core::MIXER2_OUT] = m_outputs.m_entr[C_sco2_cycle_core::BYPASS_OUT]; //[C_sco2_cycle_core::kJ/kg-K] + m_outputs.m_dens[C_sco2_cycle_core::MIXER2_OUT] = m_outputs.m_dens[C_sco2_cycle_core::BYPASS_OUT]; //[C_sco2_cycle_core::kg/m^3] + } + } + + // Calculate PHX Heat Transfer + { + m_outputs.m_Q_dot_PHX = m_outputs.m_m_dot_t * (m_outputs.m_enth[C_sco2_cycle_core::TURB_IN] - m_outputs.m_enth[C_sco2_cycle_core::MIXER2_OUT]); + } + + // Back Calculate and Check values + { + // Bypass Temps + double bp_temp_in = m_outputs.m_temp[C_sco2_cycle_core::MIXER_OUT]; + double bp_temp_out = m_outputs.m_temp[C_sco2_cycle_core::BYPASS_OUT]; + + double real_q_dot_total = m_outputs.m_W_dot_t + m_outputs.m_Q_dot_air_cooler; + + double qSum = m_outputs.m_Q_dot_total; + double qSum_calc = m_outputs.m_Q_dot_BP + m_outputs.m_Q_dot_PHX; + + int x = 0; + } + + // HTF + { + // Check if HTF mdot is already assigned + if (m_inputs.m_set_HTF_mdot > 0) + { + // Mdot is Set + m_outputs.m_m_dot_HTF = m_inputs.m_set_HTF_mdot; + + // Calculate PHX HTF Outlet Temperature + m_outputs.m_T_HTF_PHX_out = m_inputs.m_T_HTF_PHX_inlet - m_outputs.m_Q_dot_PHX / (m_outputs.m_m_dot_HTF * m_inputs.m_cp_HTF); + + // Back Calculate PHX cold approach + m_outputs.m_HTF_PHX_cold_approach = m_outputs.m_T_HTF_PHX_out - m_outputs.m_temp[C_sco2_cycle_core::MIXER2_OUT]; + } + else + { + // Use HTF Bypass cold approach to calculate PHX outlet Temperature + m_outputs.m_T_HTF_PHX_out = m_inputs.m_HTF_PHX_cold_approach_input + m_outputs.m_temp[C_sco2_cycle_core::MIXER2_OUT]; + m_outputs.m_HTF_PHX_cold_approach = m_inputs.m_HTF_PHX_cold_approach_input; + + // Calculate HTF mdot + m_outputs.m_m_dot_HTF = m_outputs.m_Q_dot_PHX / ((m_inputs.m_T_HTF_PHX_inlet - m_outputs.m_T_HTF_PHX_out) * m_inputs.m_cp_HTF); + } + + // Calculate Bypass Out Temperature + m_outputs.m_T_HTF_BP_outlet = m_outputs.m_T_HTF_PHX_out - (m_outputs.m_Q_dot_BP / (m_outputs.m_m_dot_HTF * m_inputs.m_cp_HTF)); + + // Calculate HTF Bypass Cold Approach + m_outputs.m_HTF_BP_cold_approach = m_outputs.m_T_HTF_BP_outlet - m_outputs.m_temp[C_sco2_cycle_core::MIXER_OUT]; + + } + + // Define Heat Exchangers and Air Cooler + { + // PHX + C_HeatExchanger::S_design_parameters PHX_des_par; + PHX_des_par.m_DP_design[0] = m_outputs.m_pres[C_sco2_cycle_core::MIXER2_OUT] - m_outputs.m_pres[C_sco2_cycle_core::TURB_IN]; + PHX_des_par.m_DP_design[1] = 0.0; + PHX_des_par.m_m_dot_design[0] = m_outputs.m_m_dot_t; + PHX_des_par.m_m_dot_design[1] = 0.0; + PHX_des_par.m_Q_dot_design = m_outputs.m_m_dot_t * (m_outputs.m_enth[C_sco2_cycle_core::TURB_IN] - m_outputs.m_enth[C_sco2_cycle_core::MIXER2_OUT]); + m_outputs.m_PHX.initialize(PHX_des_par); + + // BPX + C_HeatExchanger::S_design_parameters BPX_des_par; + BPX_des_par.m_DP_design[0] = m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT] - m_outputs.m_pres[C_sco2_cycle_core::BYPASS_OUT]; + BPX_des_par.m_DP_design[1] = 0.0; + BPX_des_par.m_m_dot_design[0] = m_outputs.m_m_dot_bp; + BPX_des_par.m_m_dot_design[1] = 0.0; + BPX_des_par.m_Q_dot_design = m_outputs.m_m_dot_bp * (m_outputs.m_enth[C_sco2_cycle_core::BYPASS_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT]); + m_outputs.m_BPX.initialize(BPX_des_par); + + // Air Cooler + C_HeatExchanger::S_design_parameters PC_des_par; + PC_des_par.m_DP_design[0] = 0.0; + PC_des_par.m_DP_design[1] = m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT] - m_outputs.m_pres[C_sco2_cycle_core::MC_IN]; + PC_des_par.m_m_dot_design[0] = 0.0; + PC_des_par.m_m_dot_design[1] = m_outputs.m_m_dot_mc; + PC_des_par.m_Q_dot_design = m_outputs.m_m_dot_mc * (m_outputs.m_enth[C_sco2_cycle_core::LTR_LP_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MC_IN]); + m_outputs.m_PC.initialize(PC_des_par); + } + + // Calculate Thermal Efficiency + { + m_outputs.m_eta_thermal = m_outputs.m_W_dot_net / m_outputs.m_Q_dot_total; + } + + m_outputs.m_error_code = 0; + return m_outputs.m_error_code; +} + +int C_sco2_htrbp_core::finalize_design(C_sco2_cycle_core::S_design_solved& design_solved) +{ + // Design Main Compressor + { + int mc_design_err = m_outputs.m_mc_ms.design_given_outlet_state(m_inputs.m_mc_comp_model_code, m_outputs.m_temp[C_sco2_cycle_core::MC_IN], + m_outputs.m_pres[C_sco2_cycle_core::MC_IN], + m_outputs.m_m_dot_mc, + m_outputs.m_temp[C_sco2_cycle_core::MC_OUT], + m_outputs.m_pres[C_sco2_cycle_core::MC_OUT], + m_inputs.m_des_tol, + m_inputs.m_yr_inflation); + + if (mc_design_err != 0) + { + m_outputs.m_error_code = mc_design_err; + return m_outputs.m_error_code; + } + } + + // Design Recompressor + if (m_inputs.m_recomp_frac > 0.01) + { + int rc_des_err = m_outputs.m_rc_ms.design_given_outlet_state(m_inputs.m_rc_comp_model_code, m_outputs.m_temp[C_sco2_cycle_core::LTR_LP_OUT], + m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT], + m_outputs.m_m_dot_rc, + m_outputs.m_temp[C_sco2_cycle_core::RC_OUT], + m_outputs.m_pres[C_sco2_cycle_core::RC_OUT], + m_inputs.m_des_tol, + m_inputs.m_yr_inflation); + + if (rc_des_err != 0) + { + m_outputs.m_error_code = rc_des_err; + return m_outputs.m_error_code; + } + + design_solved.m_is_rc = true; + } + else + { + design_solved.m_is_rc = false; + } + + // Size Turbine + { + C_turbine::S_design_parameters t_des_par; + // Set turbine shaft speed + t_des_par.m_N_design = m_inputs.m_N_turbine; + t_des_par.m_N_comp_design_if_linked = m_outputs.m_mc_ms.get_design_solved()->m_N_design; //[rpm] m_mc.get_design_solved()->m_N_design; + // Turbine inlet state + t_des_par.m_P_in = m_outputs.m_pres[C_sco2_cycle_core::TURB_IN]; + t_des_par.m_T_in = m_outputs.m_temp[C_sco2_cycle_core::TURB_IN]; + t_des_par.m_D_in = m_outputs.m_dens[C_sco2_cycle_core::TURB_IN]; + t_des_par.m_h_in = m_outputs.m_enth[C_sco2_cycle_core::TURB_IN]; + t_des_par.m_s_in = m_outputs.m_entr[C_sco2_cycle_core::TURB_IN]; + // Turbine outlet state + t_des_par.m_P_out = m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT]; + t_des_par.m_h_out = m_outputs.m_enth[C_sco2_cycle_core::TURB_OUT]; + // Mass flow + t_des_par.m_m_dot = m_outputs.m_m_dot_t; + // Inflation target yr + t_des_par.m_yr_inflation = m_inputs.m_yr_inflation; + + + int turb_size_error_code = 0; + m_outputs.m_t.turbine_sizing(t_des_par, turb_size_error_code); + + if (turb_size_error_code != 0) + { + m_outputs.m_error_code = turb_size_error_code; + return m_outputs.m_error_code; + } + } + + // Design air cooler + { + // Structure for design parameters that are dependent on cycle design solution + C_CO2_to_air_cooler::S_des_par_cycle_dep s_air_cooler_des_par_dep; + // Set air cooler design parameters that are dependent on the cycle design solution + s_air_cooler_des_par_dep.m_T_hot_in_des = m_outputs.m_temp[C_sco2_cycle_core::LTR_LP_OUT]; // [K] + s_air_cooler_des_par_dep.m_P_hot_in_des = m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT]; // [kPa] + s_air_cooler_des_par_dep.m_m_dot_total = m_outputs.m_m_dot_mc; // [kg/s] + + // This pressure drop is currently uncoupled from the cycle design + double cooler_deltaP = m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT] - m_outputs.m_pres[C_sco2_cycle_core::MC_IN]; // [kPa] + if (cooler_deltaP == 0.0) + s_air_cooler_des_par_dep.m_delta_P_des = m_inputs.m_deltaP_cooler_frac * m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT]; // [kPa] + else + s_air_cooler_des_par_dep.m_delta_P_des = cooler_deltaP; // [kPa] + + s_air_cooler_des_par_dep.m_T_hot_out_des = m_outputs.m_temp[C_sco2_cycle_core::MC_IN]; // [K] + s_air_cooler_des_par_dep.m_W_dot_fan_des = m_inputs.m_frac_fan_power * m_outputs.m_W_dot_net / 1000.0; // [MWe] + // Structure for design parameters that are independent of cycle design solution + C_CO2_to_air_cooler::S_des_par_ind s_air_cooler_des_par_ind; + s_air_cooler_des_par_ind.m_T_amb_des = m_inputs.m_T_amb_des; // [K] + s_air_cooler_des_par_ind.m_elev = m_inputs.m_elevation; // [m] + s_air_cooler_des_par_ind.m_eta_fan = m_inputs.m_eta_fan; // [-] + s_air_cooler_des_par_ind.m_N_nodes_pass = m_inputs.m_N_nodes_pass; // [-] + s_air_cooler_des_par_ind.m_yr_inflation = m_inputs.m_yr_inflation; // [-] + + if (m_inputs.m_is_des_air_cooler && std::isfinite(m_inputs.m_deltaP_cooler_frac) && std::isfinite(m_inputs.m_frac_fan_power) + && std::isfinite(m_inputs.m_T_amb_des) && std::isfinite(m_inputs.m_elevation) && std::isfinite(m_inputs.m_eta_fan) && m_inputs.m_N_nodes_pass > 0) + { + try + { + m_outputs.mc_air_cooler.design_hx(s_air_cooler_des_par_ind, s_air_cooler_des_par_dep, m_inputs.m_des_tol); + } + catch (...) + { + design_solved.m_eta_thermal = m_outputs.m_eta_thermal; + m_outputs.m_error_code = C_sco2_cycle_core::E_cycle_error_msg::E_AIR_COOLER_CONVERGENCE; + return m_outputs.m_error_code; + } + } + } + + // Get 'design_solved' structure from component classes + design_solved.ms_mc_ms_des_solved = *m_outputs.m_mc_ms.get_design_solved(); + design_solved.ms_rc_ms_des_solved = *m_outputs.m_rc_ms.get_design_solved(); + design_solved.ms_t_des_solved = *m_outputs.m_t.get_design_solved(); + design_solved.ms_LTR_des_solved = m_outputs.mc_LT_recup.ms_des_solved; + design_solved.ms_HTR_des_solved = m_outputs.mc_HT_recup.ms_des_solved; + design_solved.ms_mc_air_cooler = *m_outputs.mc_air_cooler.get_design_solved(); + + // Set solved design point metrics + design_solved.m_temp = m_outputs.m_temp; + design_solved.m_pres = m_outputs.m_pres; + design_solved.m_enth = m_outputs.m_enth; + design_solved.m_entr = m_outputs.m_entr; + design_solved.m_dens = m_outputs.m_dens; + + design_solved.m_eta_thermal = m_outputs.m_eta_thermal; + design_solved.m_W_dot_net = m_outputs.m_W_dot_net; + design_solved.m_m_dot_mc = m_outputs.m_m_dot_mc; + design_solved.m_m_dot_rc = m_outputs.m_m_dot_rc; + design_solved.m_m_dot_t = m_outputs.m_m_dot_t; + design_solved.m_recomp_frac = m_outputs.m_m_dot_rc / m_outputs.m_m_dot_t; + design_solved.m_bypass_frac = m_inputs.m_bypass_frac; + + design_solved.m_UA_LTR = m_inputs.m_LTR_UA; + design_solved.m_UA_HTR = m_inputs.m_HTR_UA; + + design_solved.m_W_dot_t = m_outputs.m_W_dot_t; //[kWe] + design_solved.m_W_dot_mc = m_outputs.m_W_dot_mc; //[kWe] + design_solved.m_W_dot_rc = m_outputs.m_W_dot_rc; //[kWe] + + design_solved.m_W_dot_cooler_tot = m_outputs.mc_air_cooler.get_design_solved()->m_W_dot_fan * 1.E3; //[kWe] convert from MWe + + return 0; +} + +int C_sco2_htrbp_core::solve_HTR(double T_HTR_LP_OUT_guess, double* diff_T_HTR_LP_out) +{ + m_outputs.m_w_rc = m_outputs.m_m_dot_t = m_outputs.m_m_dot_rc = m_outputs.m_m_dot_mc = m_outputs.m_Q_dot_LT = m_outputs.m_Q_dot_HT = std::numeric_limits::quiet_NaN(); + + // Set temperature guess + m_outputs.m_temp[C_sco2_cycle_core::HTR_LP_OUT] = T_HTR_LP_OUT_guess; //[K] + + // Solve HTR_LP_OUT properties + { + int prop_error_code = CO2_TP(m_outputs.m_temp[C_sco2_cycle_core::HTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT], &m_co2_props); + if (prop_error_code != 0) + { + *diff_T_HTR_LP_out = std::numeric_limits::quiet_NaN(); + return prop_error_code; + } + m_outputs.m_enth[C_sco2_cycle_core::HTR_LP_OUT] = m_co2_props.enth; + m_outputs.m_entr[C_sco2_cycle_core::HTR_LP_OUT] = m_co2_props.entr; + m_outputs.m_dens[C_sco2_cycle_core::HTR_LP_OUT] = m_co2_props.dens; + } + + // Solve for the LTR solution + { + double T_LTR_LP_out_lower = m_outputs.m_temp[C_sco2_cycle_core::MC_OUT]; //[K] Coldest possible outlet temperature + double T_LTR_LP_out_upper = m_outputs.m_temp[C_sco2_cycle_core::HTR_LP_OUT]; //[K] Hottest possible outlet temperature + + double T_LTR_LP_out_guess_upper = std::min(T_LTR_LP_out_upper, T_LTR_LP_out_lower + 15.0); //[K] There is nothing special about using 15 here... + double T_LTR_LP_out_guess_lower = std::min(T_LTR_LP_out_guess_upper * 0.99, T_LTR_LP_out_lower + 2.0); //[K] There is nothing special about using 2 here... + + C_mono_htrbp_core_LTR_des LTR_des_eq(this); + C_monotonic_eq_solver LTR_des_solver(LTR_des_eq); + + LTR_des_solver.settings(m_inputs.m_des_tol * m_outputs.m_temp[C_sco2_cycle_core::MC_IN], 1000, T_LTR_LP_out_lower, + T_LTR_LP_out_upper, false); + + double T_LTR_LP_out_solved = std::numeric_limits::quiet_NaN(); + double tol_T_LTR_LP_out_solved = std::numeric_limits::quiet_NaN(); + int iter_T_LTR_LP_out = -1; + + int T_LTR_LP_out_code = LTR_des_solver.solve(T_LTR_LP_out_guess_lower, T_LTR_LP_out_guess_upper, 0, T_LTR_LP_out_solved, + tol_T_LTR_LP_out_solved, iter_T_LTR_LP_out); + + if (T_LTR_LP_out_code != C_monotonic_eq_solver::CONVERGED) + { + return 31; + } + } + + // Know LTR performance so we can calculate the HP outlet (Energy balance on LTR HP stream) + { + m_outputs.m_enth[C_sco2_cycle_core::LTR_HP_OUT] = m_outputs.m_enth[C_sco2_cycle_core::MC_OUT] + m_outputs.m_Q_dot_LT / m_outputs.m_m_dot_mc; //[kJ/kg] + int prop_error_code = CO2_PH(m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT], m_outputs.m_enth[C_sco2_cycle_core::LTR_HP_OUT], &m_co2_props); + if (prop_error_code != 0) + { + *diff_T_HTR_LP_out = std::numeric_limits::quiet_NaN(); + return prop_error_code; + } + m_outputs.m_temp[C_sco2_cycle_core::LTR_HP_OUT] = m_co2_props.temp; //[K] + m_outputs.m_entr[C_sco2_cycle_core::LTR_HP_OUT] = m_co2_props.entr; //[kJ/kg-K] + m_outputs.m_dens[C_sco2_cycle_core::LTR_HP_OUT] = m_co2_props.dens; //[kg/m^3] + } + + // Simulate the Mixer + if (m_inputs.m_recomp_frac >= 1.E-12) + { + m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT] = (1.0 - m_inputs.m_recomp_frac) * m_outputs.m_enth[C_sco2_cycle_core::LTR_HP_OUT] + + m_inputs.m_recomp_frac * m_outputs.m_enth[C_sco2_cycle_core::RC_OUT]; //[kJ/kg] + int prop_error_code = CO2_PH(m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT], m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT], &m_co2_props); + if (prop_error_code != 0) + { + *diff_T_HTR_LP_out = std::numeric_limits::quiet_NaN(); + return prop_error_code; + } + m_outputs.m_temp[C_sco2_cycle_core::MIXER_OUT] = m_co2_props.temp; //[K] + m_outputs.m_entr[C_sco2_cycle_core::MIXER_OUT] = m_co2_props.entr; //[kJ/kg-K] + m_outputs.m_dens[C_sco2_cycle_core::MIXER_OUT] = m_co2_props.dens; //[kg/m^3] + } + else + { // No recompressor, so no mixing required, and HTR HP inlet = LTR HP outlet + m_outputs.m_temp[C_sco2_cycle_core::MIXER_OUT] = m_outputs.m_temp[C_sco2_cycle_core::LTR_HP_OUT]; //[K] + m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT] = m_outputs.m_enth[C_sco2_cycle_core::LTR_HP_OUT]; //[kJ/kg] + m_outputs.m_entr[C_sco2_cycle_core::MIXER_OUT] = m_outputs.m_entr[C_sco2_cycle_core::LTR_HP_OUT]; //[kJ/kg-K] + m_outputs.m_dens[C_sco2_cycle_core::MIXER_OUT] = m_outputs.m_dens[C_sco2_cycle_core::LTR_HP_OUT]; //[kg/m^3] + } + + // Solve Mass Flow rates for HTR_HP_OUT and Bypass + { + m_outputs.m_m_dot_bp = m_inputs.m_bypass_frac * m_outputs.m_m_dot_t; + m_outputs.m_m_dot_htr_hp = m_outputs.m_m_dot_t - m_outputs.m_m_dot_bp; + } + + // Find the design solution of the HTR + double T_HTR_LP_out_calc = std::numeric_limits::quiet_NaN(); + { + // If there is no flow through HTR HP side + if (m_outputs.m_m_dot_htr_hp < 1e-12) + { + m_outputs.m_Q_dot_HT = 0; + T_HTR_LP_out_calc = m_outputs.m_temp[C_sco2_cycle_core::TURB_OUT]; + } + + // If there is flow through HTR HP side + else + { + m_outputs.mc_HT_recup.design_for_target__calc_outlet(m_inputs.m_HTR_target_code, + m_inputs.m_HTR_UA, m_inputs.m_HTR_min_dT, m_inputs.m_HTR_eff_target, + m_inputs.m_HTR_eff_max, + m_outputs.m_temp[C_sco2_cycle_core::MIXER_OUT], m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT], m_outputs.m_m_dot_htr_hp, m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT], + m_outputs.m_temp[C_sco2_cycle_core::TURB_OUT], m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT], m_outputs.m_m_dot_t, m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT], + m_inputs.m_des_tol, + m_outputs.m_Q_dot_HT, m_outputs.m_temp[C_sco2_cycle_core::HTR_HP_OUT], T_HTR_LP_out_calc); + } + + } + + *diff_T_HTR_LP_out = T_HTR_LP_out_calc - T_HTR_LP_OUT_guess; + + return 0; +} + +int C_sco2_htrbp_core::solve_LTR(double T_LTR_LP_OUT_guess, double* diff_T_LTR_LP_out) +{ + m_outputs.m_w_rc = m_outputs.m_m_dot_t = m_outputs.m_m_dot_rc = m_outputs.m_m_dot_mc = m_outputs.m_Q_dot_LT = m_outputs.m_Q_dot_HT = std::numeric_limits::quiet_NaN(); + + // Set LTR_LP_OUT guess + m_outputs.m_temp[C_sco2_cycle_core::LTR_LP_OUT] = T_LTR_LP_OUT_guess; + + // First, solve the recompressor model as necessary + if (m_inputs.m_recomp_frac >= 1.E-12) + { + double eta_rc_isen = std::numeric_limits::quiet_NaN(); + + if (m_inputs.m_eta_rc < 0.0) // recalculate isen. efficiency of recompressor because inlet temp changes + { + int rc_error_code = 0; + isen_eta_from_poly_eta(m_outputs.m_temp[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT], + m_outputs.m_pres[C_sco2_cycle_core::RC_OUT], std::abs(m_inputs.m_eta_rc), true, + rc_error_code, eta_rc_isen); + + if (rc_error_code != 0) + { + *diff_T_LTR_LP_out = std::numeric_limits::quiet_NaN(); + return rc_error_code; + } + } + else + { + eta_rc_isen = m_inputs.m_eta_rc; + } + + int rc_error_code = 0; + + calculate_turbomachinery_outlet_1(m_outputs.m_temp[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::RC_OUT], eta_rc_isen, true, rc_error_code, + m_outputs.m_enth[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_entr[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_dens[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_temp[C_sco2_cycle_core::RC_OUT], m_outputs.m_enth[C_sco2_cycle_core::RC_OUT], + m_outputs.m_entr[C_sco2_cycle_core::RC_OUT], m_outputs.m_dens[C_sco2_cycle_core::RC_OUT], m_outputs.m_w_rc); + + if (rc_error_code != 0) + { + *diff_T_LTR_LP_out = std::numeric_limits::quiet_NaN(); + return rc_error_code; + } + } + else + { + m_outputs.m_w_rc = 0.0; // no recompressor + int prop_error_code = CO2_TP(m_outputs.m_temp[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT], &m_co2_props); + if (prop_error_code != 0) + { + *diff_T_LTR_LP_out = std::numeric_limits::quiet_NaN(); + return prop_error_code; + } + m_outputs.m_enth[C_sco2_cycle_core::LTR_LP_OUT] = m_co2_props.enth; + m_outputs.m_entr[C_sco2_cycle_core::LTR_LP_OUT] = m_co2_props.entr; + m_outputs.m_dens[C_sco2_cycle_core::LTR_LP_OUT] = m_co2_props.dens; + m_outputs.m_temp[C_sco2_cycle_core::RC_OUT] = m_outputs.m_temp[C_sco2_cycle_core::LTR_LP_OUT]; + m_outputs.m_enth[C_sco2_cycle_core::RC_OUT] = m_outputs.m_enth[C_sco2_cycle_core::LTR_LP_OUT]; + m_outputs.m_entr[C_sco2_cycle_core::RC_OUT] = m_outputs.m_entr[C_sco2_cycle_core::LTR_LP_OUT]; + m_outputs.m_dens[C_sco2_cycle_core::RC_OUT] = m_outputs.m_dens[C_sco2_cycle_core::LTR_LP_OUT]; + } + + // Solve Mass Flow Rates + { + m_outputs.m_m_dot_t = m_inputs.m_W_dot_net_design / ((m_outputs.m_w_mc * (1.0 - m_inputs.m_recomp_frac) + + m_outputs.m_w_rc * m_inputs.m_recomp_frac + m_outputs.m_w_t) * m_inputs.m_eta_generator); //[C_sco2_cycle_core::kg/s] + + m_outputs.m_m_dot_rc = m_outputs.m_m_dot_t * m_inputs.m_recomp_frac; //[C_sco2_cycle_core::kg/s] + m_outputs.m_m_dot_mc = m_outputs.m_m_dot_t - m_outputs.m_m_dot_rc; + } + + // Solve LTR + *diff_T_LTR_LP_out = std::numeric_limits::quiet_NaN(); + double T_LTR_LP_out_calc = std::numeric_limits::quiet_NaN(); + { + m_outputs.mc_LT_recup.design_for_target__calc_outlet(m_inputs.m_LTR_target_code, + m_inputs.m_LTR_UA, m_inputs.m_LTR_min_dT, m_inputs.m_LTR_eff_target, + m_inputs.m_LTR_eff_max, + m_outputs.m_temp[C_sco2_cycle_core::MC_OUT], m_outputs.m_pres[C_sco2_cycle_core::MC_OUT], m_outputs.m_m_dot_mc, m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT], + m_outputs.m_temp[C_sco2_cycle_core::HTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT], m_outputs.m_m_dot_t, m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT], + m_inputs.m_des_tol, + m_outputs.m_Q_dot_LT, m_outputs.m_temp[C_sco2_cycle_core::LTR_HP_OUT], T_LTR_LP_out_calc); + + } + + *diff_T_LTR_LP_out = T_LTR_LP_out_calc - T_LTR_LP_OUT_guess; + + return 0; +} + +void C_sco2_htrbp_core::reset() +{ + this->m_inputs = S_sco2_htrbp_in(); + this->m_outputs.Init(); +} + +// ********************************************************************************** END C_sco2_htrbp_core + + +// ********************************************************************************** PRIVATE C_HTRBypass_Cycle (: C_sco2_cycle_core) + +/// +/// Core function to optimize cycle (fixed total UA) +/// +void C_HTRBypass_Cycle::auto_opt_design_core(int& error_code) +{ + // Reset optimal htrbp model + m_optimal_htrbp_core.reset(); + + // Check that simple/recomp flag is set + if (ms_auto_opt_des_par.m_is_recomp_ok < -1.0 || (ms_auto_opt_des_par.m_is_recomp_ok > 0 && + ms_auto_opt_des_par.m_is_recomp_ok != 1.0 && ms_auto_opt_des_par.m_is_recomp_ok != 2.0)) + { + throw(C_csp_exception("C_RecompCycle::auto_opt_design_core(...) requires that ms_auto_opt_des_par.m_is_recomp_ok" + " is either between -1 and 0 (fixed recompression fraction) or equal to 1 (recomp allowed)\n")); + } + + // Create 'ms_opt_des_par' for Design Variables + S_opt_design_parameters opt_par; + { + // Max Pressure + double best_P_high = m_P_high_limit; //[kPa] + double PR_mc_guess = 2.5; //[-] + + opt_par.m_fixed_P_mc_out = ms_auto_opt_des_par.m_fixed_P_mc_out; //[-] + if (!opt_par.m_fixed_P_mc_out) + { + double P_low_limit = std::min(m_P_high_limit, std::max(10.E3, m_P_high_limit * 0.2)); //[kPa] + + //best_P_high = fminbr(P_low_limit, m_P_high_limit, &fmin_cb_opt_des_fixed_P_high, this, 1.0); + best_P_high = m_P_high_limit; + } + opt_par.m_P_mc_out_guess = best_P_high; //[kPa] + //ms_opt_des_par.m_fixed_P_mc_out = true; + + // Pressure Ratio (min pressure) + opt_par.m_fixed_PR_HP_to_LP = ms_auto_opt_des_par.m_fixed_PR_HP_to_LP; //[-] + if (opt_par.m_fixed_PR_HP_to_LP) + { + opt_par.m_PR_HP_to_LP_guess = ms_auto_opt_des_par.m_PR_HP_to_LP_guess; //[-] + } + else + { + opt_par.m_PR_HP_to_LP_guess = PR_mc_guess; //[-] + } + + // Is recompression fraction fixed or optimized? + if (ms_auto_opt_des_par.m_is_recomp_ok <= 0.0) + { // fixed + opt_par.m_recomp_frac_guess = std::abs(ms_auto_opt_des_par.m_is_recomp_ok); + opt_par.m_fixed_recomp_frac = true; + } + else + { // optimized + opt_par.m_recomp_frac_guess = 0.3; + opt_par.m_fixed_recomp_frac = false; + } + + // Is bypass fraction fixed or optimized? + if (ms_auto_opt_des_par.m_is_bypass_ok <= 0.0) + { // fixed + opt_par.m_bypass_frac_guess = std::abs(ms_auto_opt_des_par.m_is_bypass_ok); + opt_par.m_fixed_bypass_frac = true; + } + else + { // optimized + opt_par.m_bypass_frac_guess = 0.3; + opt_par.m_fixed_bypass_frac = false; + } + + // LTR HTR UA Ratio + opt_par.m_LT_frac_guess = 0.5; + opt_par.m_fixed_LT_frac = false; + if (ms_auto_opt_des_par.m_LTR_target_code != NS_HX_counterflow_eqs::OPTIMIZE_UA || ms_auto_opt_des_par.m_HTR_target_code != NS_HX_counterflow_eqs::OPTIMIZE_UA) + { + opt_par.m_fixed_LT_frac = true; + } + + // Set Design Method + if (opt_par.m_fixed_LT_frac == true) + opt_par.m_design_method = 3; + else + opt_par.m_design_method = 2; + + } + + // Find optimal inputs + C_sco2_htrbp_core::S_sco2_htrbp_in optimal_inputs_out; + error_code = optimize_bp(ms_auto_opt_des_par, opt_par, optimal_inputs_out); + + if (error_code != 0) + return; + + // Run Optimal Case + m_optimal_htrbp_core.set_inputs(optimal_inputs_out); + error_code = m_optimal_htrbp_core.solve(); + + if (error_code != 0) + return; + + // don't size system if eta is terrible + //if (m_optimal_htrbp_core.m_outputs.m_eta_thermal < 0.15) + //{ + // error_code = (int)C_sco2_cycle_core::E_cycle_error_msg::E_ETA_THRESHOLD; + // return; + //} + + // Check if cycle is below eta cutoff value + if (m_optimal_htrbp_core.m_outputs.m_eta_thermal < ms_auto_opt_des_par.m_eta_thermal_cutoff) + { + ms_des_solved.m_eta_thermal = m_optimal_htrbp_core.m_outputs.m_eta_thermal; + error_code = C_sco2_cycle_core::E_cycle_error_msg::E_ETA_THRESHOLD; + return; + } + + // Finalize Design (pass in reference to solved parameters) + error_code = m_optimal_htrbp_core.finalize_design(ms_des_solved); +} + +/// +/// Core function to optimize cycle for target eta (variable total UA) +/// +void C_HTRBypass_Cycle::auto_opt_design_hit_eta_core(int& error_code, const double eta_thermal_target) +{ + // Create 'ms_opt_des_par' for Design Variables + S_opt_design_parameters opt_par; + { + // Target Thermal Efficiency + opt_par.m_eta_thermal_target = eta_thermal_target; + + // Max Pressure + double best_P_high = m_P_high_limit; //[kPa] + double PR_mc_guess = 2.5; //[-] + opt_par.m_fixed_P_mc_out = ms_auto_opt_des_par.m_fixed_P_mc_out; //[-] + if (!opt_par.m_fixed_P_mc_out) + { + double P_low_limit = std::min(m_P_high_limit, std::max(10.E3, m_P_high_limit * 0.2)); //[kPa] + + //best_P_high = fminbr(P_low_limit, m_P_high_limit, &fmin_cb_opt_des_fixed_P_high, this, 1.0); + best_P_high = m_P_high_limit; + } + opt_par.m_P_mc_out_guess = best_P_high; //[kPa] + //ms_opt_des_par.m_fixed_P_mc_out = true; + + // Pressure Ratio (min pressure) + opt_par.m_fixed_PR_HP_to_LP = ms_auto_opt_des_par.m_fixed_PR_HP_to_LP; //[-] + if (opt_par.m_fixed_PR_HP_to_LP) + { + opt_par.m_PR_HP_to_LP_guess = ms_auto_opt_des_par.m_PR_HP_to_LP_guess; //[-] + } + else + { + opt_par.m_PR_HP_to_LP_guess = PR_mc_guess; //[-] + } + + // Is recompression fraction fixed or optimized? + if (ms_auto_opt_des_par.m_is_recomp_ok <= 0.0) + { // fixed + opt_par.m_recomp_frac_guess = std::abs(ms_auto_opt_des_par.m_is_recomp_ok); + opt_par.m_fixed_recomp_frac = true; + } + else + { // optimized + opt_par.m_recomp_frac_guess = 0.3; + opt_par.m_fixed_recomp_frac = false; + } + + // Is bypass fraction fixed or optimized? + if (ms_auto_opt_des_par.m_is_bypass_ok <= 0.0) + { // fixed + opt_par.m_bypass_frac_guess = std::abs(ms_auto_opt_des_par.m_is_bypass_ok); + opt_par.m_fixed_bypass_frac = true; + } + else + { // optimized + opt_par.m_bypass_frac_guess = 0.3; + opt_par.m_fixed_bypass_frac = false; + } + + // LTR HTR UA Ratio (cannot be fixed because design method is varying total UA) + opt_par.m_LT_frac_guess = 0.5; + opt_par.m_fixed_LT_frac = false; + + } + + // Set design method (it is 1, because total UA is optimized for target eta) + opt_par.m_design_method = 1; + + // Select min and max values + opt_par.m_UA_recup_total_max = ms_des_limits.m_UA_net_power_ratio_max * m_W_dot_net; //[kW/K] + opt_par.m_UA_recup_total_min = ms_des_limits.m_UA_net_power_ratio_min * m_W_dot_net; //[kW/K] + + S_auto_opt_design_parameters auto_par = ms_auto_opt_des_par; + + // Optimize Total UA + C_sco2_htrbp_core::S_sco2_htrbp_in optimal_inputs; + error_code = optimize_totalUA(auto_par, opt_par, optimal_inputs); + if (error_code != 0) + return; + + + // Run Optimal Core model and finalize design + m_optimal_htrbp_core.reset(); + m_optimal_htrbp_core.set_inputs(optimal_inputs); + error_code = m_optimal_htrbp_core.solve(); + if (error_code != 0) + return; + + // Finalize Design (pass in reference to solved parameters) + error_code = m_optimal_htrbp_core.finalize_design(ms_des_solved); + + return; +} + +/// +/// Optimize Total Recuperator UA +/// totalUA -> bp -> UA split, pressure, recomp +/// +/// +/// +/// +/// +int C_HTRBypass_Cycle::optimize_totalUA(const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + C_sco2_htrbp_core::S_sco2_htrbp_in& optimal_inputs) +{ + // Validate Inputs + if (std::isnan(opt_par.m_eta_thermal_target) || + std::isnan(opt_par.m_UA_recup_total_min) || + std::isnan(opt_par.m_UA_recup_total_max)) + { + std::string warning_msg = "The target eta, and max and min UA need to be defined in S_opt_design_parameters"; + return -1; + } + + + // Create Optimizer + nlopt::opt opt_des_cycle(nlopt::GN_DIRECT, 1); + int error_code = -1; + + std::vector lb = { opt_par.m_UA_recup_total_min }; + std::vector ub = { opt_par.m_UA_recup_total_max }; + double UA_recup_guess = (opt_par.m_UA_recup_total_max + opt_par.m_UA_recup_total_min) / 2.0; // [kW/K] + + opt_des_cycle.set_lower_bounds(lb); + opt_des_cycle.set_upper_bounds(ub); + opt_des_cycle.set_initial_step(1000); + //opt_des_cycle.set_xtol_rel(ms_auto_opt_des_par.m_des_opt_tol); + opt_des_cycle.set_xtol_rel(1); + //opt_des_cycle.set_maxeval(50); + + // Make Tuple to pass in parameters + std::tuple par_tuple = { this, &auto_par, &opt_par }; + + // Set max objective function + std::vector x; + x.push_back(UA_recup_guess); + opt_des_cycle.set_max_objective(nlopt_optimize_totalUA_func, &par_tuple); // Calls wrapper/callback that calls 'design_point_eta', which optimizes design point eta through repeated calls to 'design' + double max_f = std::numeric_limits::quiet_NaN(); + + nlopt::result result_des_cycle = opt_des_cycle.optimize(x, max_f); + + /// Check to make sure optimizer worked... + if (opt_des_cycle.get_force_stop()) + { + int w = 0; + } + + // Assign Total UA + double total_UA = x[0]; + ms_auto_opt_des_par.m_UA_rec_total = total_UA; + + // Have Total UA, now rerun optimizer to get other variables (redundant but shouldn't be very costly) + C_sco2_htrbp_core::S_sco2_htrbp_in optimal_inputs_case; + error_code = optimize_bp(ms_auto_opt_des_par, opt_par, optimal_inputs_case); + + if (error_code != 0) + return error_code; + + // Assign Optimal Inputs + optimal_inputs = optimal_inputs_case; + + return error_code; +} + +/// +/// Optimize Bypass Fraction +/// totalUA -> bp -> UA split, pressure, recomp +/// +/// +/// +/// +/// +int C_HTRBypass_Cycle::optimize_bp(const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + C_sco2_htrbp_core::S_sco2_htrbp_in& optimal_inputs) +{ + // Set up baseline core inputs + C_sco2_htrbp_core::S_sco2_htrbp_in core_inputs; + { + + // From Auto Opt Design Parameters + core_inputs.m_LTR_target_code = auto_par.m_LTR_target_code; + core_inputs.m_LTR_UA = auto_par.m_LTR_UA; + core_inputs.m_LTR_min_dT = auto_par.m_LTR_min_dT; + core_inputs.m_LTR_eff_target = auto_par.m_LTR_eff_target; + core_inputs.m_LTR_eff_max = auto_par.m_LTR_eff_max; + + core_inputs.m_LTR_od_UA_target_type = auto_par.m_LTR_od_UA_target_type; + core_inputs.m_HTR_target_code = auto_par.m_HTR_target_code; + core_inputs.m_HTR_UA = auto_par.m_HTR_UA; + core_inputs.m_HTR_min_dT = auto_par.m_HTR_min_dT; + core_inputs.m_HTR_eff_target = auto_par.m_HTR_eff_target; + core_inputs.m_HTR_eff_max = auto_par.m_HTR_eff_max; + core_inputs.m_HTR_od_UA_target_type = auto_par.m_HTR_od_UA_target_type; + core_inputs.m_des_tol = auto_par.m_des_tol; + core_inputs.m_is_des_air_cooler = auto_par.m_is_des_air_cooler; + + // From Constructor + core_inputs.m_LTR_N_sub_hxrs = m_LTR_N_sub_hxrs; // Comes from constructor (constant) + core_inputs.m_HTR_N_sub_hxrs = m_HTR_N_sub_hxrs; // Comes from constructor (constant) + core_inputs.m_W_dot_net_design = m_W_dot_net; // Comes from constructor (constant) + core_inputs.m_T_mc_in = m_T_mc_in; // Comes from constructor (constant) + core_inputs.m_T_t_in = m_T_t_in; // Comes from constructor (constant) + core_inputs.m_DP_LTR = m_DP_LTR; // Comes from constructor (constant) + core_inputs.m_DP_HTR = m_DP_HTR; // Comes from constructor (constant) + core_inputs.m_DP_PC_main = m_DP_PC_main; // Comes from constructor (constant) + core_inputs.m_DP_PHX = m_DP_PHX; // Comes from constructor (constant) + core_inputs.m_eta_mc = m_eta_mc; // Comes from constructor (constant) + core_inputs.m_eta_t = m_eta_t; // Comes from constructor (constant) + core_inputs.m_eta_rc = m_eta_rc; // Comes from constructor (constant) + core_inputs.m_eta_generator = m_eta_generator; // Comes from constructor (constant) + core_inputs.m_frac_fan_power = m_frac_fan_power; // Comes from constructor (constant) + core_inputs.m_eta_fan = m_eta_fan; // Comes from constructor (constant) + core_inputs.m_deltaP_cooler_frac = m_deltaP_cooler_frac; // Comes from constructor (constant) + core_inputs.m_T_amb_des = m_T_amb_des; // Comes from constructor (constant) + core_inputs.m_elevation = m_elevation; // Comes from constructor (constant) + core_inputs.m_N_nodes_pass = m_N_nodes_pass; // Comes from constructor (constant) + core_inputs.m_mc_comp_model_code = m_mc_comp_model_code; // Comes from constructor (constant) + core_inputs.m_N_turbine = m_N_turbine; // Comes from constructor (constant) + + core_inputs.m_rc_comp_model_code = C_comp__psi_eta_vs_phi::E_snl_radial_via_Dyreby; // Constant + core_inputs.m_yr_inflation = m_yr_inflation; // Comes from constructor (constant) + + // From special bypass fraction function (should remove) + core_inputs.m_dT_BP = m_dT_BP; // Comes from bp par function (constant) + core_inputs.m_set_HTF_mdot = m_set_HTF_mdot; // Comes from bp par function (constant) + core_inputs.m_cp_HTF = m_cp_HTF; // Comes from bp par function (constant) + core_inputs.m_T_HTF_PHX_inlet = m_T_HTF_PHX_inlet; // Comes from bp par function (constant) + core_inputs.m_HTF_PHX_cold_approach_input = m_HTF_PHX_cold_approach; // Comes from bp par function (constant) + + + // Handle design variables (check if fixed or free) + { + // Bypass Fraction + if (opt_par.m_fixed_bypass_frac == true) + core_inputs.m_bypass_frac = opt_par.m_bypass_frac_guess; + + // Recompression Fraction + if (opt_par.m_fixed_recomp_frac == true) + core_inputs.m_recomp_frac = opt_par.m_recomp_frac_guess; + + // MC Outlet Pressure + if (opt_par.m_fixed_P_mc_out == true) + core_inputs.m_P_mc_out = opt_par.m_P_mc_out_guess; + + // Recuperator split fraction + double LT_frac_local = opt_par.m_LT_frac_guess; + if (auto_par.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA || auto_par.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA) + { + core_inputs.m_LTR_UA = auto_par.m_UA_rec_total * LT_frac_local; + core_inputs.m_HTR_UA = auto_par.m_UA_rec_total * (1.0 - LT_frac_local); + } + else + { + core_inputs.m_LTR_UA = auto_par.m_LTR_UA; //[kW/K] + core_inputs.m_HTR_UA = auto_par.m_HTR_UA; //[kW/K] + } + + + // Pressure Ratio is calculated in callback + } + + + //core_inputs.m_P_mc_in = ms_des_par.m_P_mc_in; + //core_inputs.m_P_mc_out = ms_des_par.m_P_mc_out; + //core_inputs.m_recomp_frac = ms_des_par.m_recomp_frac; + //core_inputs.m_bypass_frac = ms_opt_des_par.m_bypass_frac_guess; + + + + //opt_nonbp_par(auto_par, opt_par, core_inputs); + } + + // Declare Optimal Inputs Case + C_sco2_htrbp_core::S_sco2_htrbp_in optimal_inputs_final; + + // Bypass is Variable, optimize other variables WITHIN bp optimizer + if (opt_par.m_fixed_bypass_frac == false) + { + // Reset Metrics + m_opt_obj_internal_only = -1000000000000000; + m_optimal_inputs_internal_only = C_sco2_htrbp_core::S_sco2_htrbp_in(); + + // Create Optimizer + nlopt::opt opt_des_cycle(nlopt::GN_DIRECT, 1); + + std::vector lb = { 0 }; + std::vector ub = { 0.99 }; + + opt_des_cycle.set_lower_bounds(lb); + opt_des_cycle.set_upper_bounds(ub); + opt_des_cycle.set_initial_step(0.1); + //opt_des_cycle.set_xtol_rel(auto_par.m_des_opt_tol); + opt_des_cycle.set_xtol_rel(0.1); + //opt_des_cycle.set_maxeval(50); + + // Set up Core Model that will be passed to objective function + C_sco2_htrbp_core htrbp_core; + htrbp_core.set_inputs(core_inputs); + + // Make Tuple to pass in parameters + std::tuple par_tuple = { this, &auto_par, &opt_par, &htrbp_core }; + + // Set max objective function + std::vector x; + x.push_back(0.1); + opt_des_cycle.set_max_objective(nlopt_optimize_bp_func, &par_tuple); // Calls wrapper/callback that calls 'design_point_eta', which optimizes design point eta through repeated calls to 'design' + double max_f = std::numeric_limits::quiet_NaN(); + + nlopt::result result_des_cycle = opt_des_cycle.optimize(x, max_f); + + /// Check to make sure optimizer worked... + if (opt_des_cycle.get_force_stop()) + { + int w = 0; + } + + // Return an optimal input case + optimal_inputs_final = m_optimal_inputs_internal_only; + + // Clear Optimal Input Field + m_optimal_inputs_internal_only = C_sco2_htrbp_core::S_sco2_htrbp_in(); + + } + + // Bypass is Fixed, optimize other variables + else + { + C_sco2_htrbp_core::S_sco2_htrbp_in optimal_inputs_final_temporary; + int error_code = optimize_nonbp(auto_par, opt_par, core_inputs, optimal_inputs_final_temporary); + if (error_code != 0) + return error_code; + + // Return optimal input case + optimal_inputs_final = optimal_inputs_final_temporary; + } + + optimal_inputs = optimal_inputs_final; + + return 0; +} + +/// +/// Optimize internal variables (UA split, pressure, recomp) +/// totalUA -> bp -> UA split, pressure, recomp +/// +/// +/// +/// +/// +/// +int C_HTRBypass_Cycle::optimize_nonbp(const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + C_sco2_htrbp_core::S_sco2_htrbp_in& core_inputs, + C_sco2_htrbp_core::S_sco2_htrbp_in& optimal_inputs) +{ + // Add Applicable Design Variables to Optimizer + int index = 0; + + std::vector x(0); + std::vector lb(0); + std::vector ub(0); + std::vector scale(0); + + if (!auto_par.m_fixed_P_mc_out) + { + x.push_back(opt_par.m_P_mc_out_guess); + lb.push_back(100.0); + ub.push_back(m_P_high_limit); + scale.push_back(500.0); + + index++; + } + + if (!auto_par.m_fixed_PR_HP_to_LP) + { + x.push_back(opt_par.m_PR_HP_to_LP_guess); + lb.push_back(0.0001); + double PR_max = m_P_high_limit / 100.0; + ub.push_back(PR_max); + scale.push_back(0.2); + + index++; + } + + if (!opt_par.m_fixed_recomp_frac) + { + x.push_back(opt_par.m_recomp_frac_guess); + lb.push_back(0.0); + ub.push_back(1.0); + scale.push_back(0.05); + index++; + } + + if (!opt_par.m_fixed_LT_frac) + { + x.push_back(opt_par.m_LT_frac_guess); + lb.push_back(0.0); + ub.push_back(1.0); + scale.push_back(0.05); + + index++; + } + + // Make Optimizer (if there are variables to be optimized) + int error_code = 0; + C_sco2_htrbp_core::S_sco2_htrbp_in optimal_inputs_internal; + if (index > 0) + { + // Set up instance of nlopt class and set optimization parameters + nlopt::opt opt_des_cycle(nlopt::LN_SBPLX, index); + opt_des_cycle.set_lower_bounds(lb); + opt_des_cycle.set_upper_bounds(ub); + opt_des_cycle.set_initial_step(scale); + opt_des_cycle.set_xtol_rel(auto_par.m_des_opt_tol); + //opt_des_cycle.set_maxeval(50); + + // Set up Core Model that will be passed to objective function + C_sco2_htrbp_core htrbp_core; + htrbp_core.set_inputs(core_inputs); + + // Make Tuple to pass in parameters + std::tuple par_tuple = { this, &auto_par, &opt_par, &htrbp_core }; + + // Set max objective function + + opt_des_cycle.set_max_objective(nlopt_optimize_nonbp_func, &par_tuple); + double max_f = std::numeric_limits::quiet_NaN(); + + nlopt::result result_des_cycle = opt_des_cycle.optimize(x, max_f); + + // This returns optimal values ^, need to convert to core_inputs + + // Check if forced stop + int flag = opt_des_cycle.get_force_stop(); + if (flag == true) + { + error_code = -1; + return error_code; + } + + // Get Optimal Input Case + error_code = x_to_inputs(x, auto_par, opt_par, core_inputs); + if (error_code != 0) + return error_code; + } + else + { + // Define P_mc_in (because the ratio and mc_out are constant) + core_inputs.m_P_mc_in = core_inputs.m_P_mc_out / opt_par.m_PR_HP_to_LP_guess; + + // Simulate Case (don't actually need to run...) + C_sco2_htrbp_core core_model; + core_model.set_inputs(core_inputs); + error_code = core_model.solve(); + } + + // Set Optimal Inputs + optimal_inputs = core_inputs; + + return error_code; +} + +/// +/// Take optimizer array 'x', write appropriate values to S_sco2_htrbp_in +/// +int C_HTRBypass_Cycle::x_to_inputs(const std::vector& x, + const S_auto_opt_design_parameters auto_par, + const S_opt_design_parameters opt_par, + C_sco2_htrbp_core::S_sco2_htrbp_in &core_inputs) +{ + // 'x' is array of inputs either being adjusted by optimizer or set constant + // Finish defining core_inputs based on current 'x' values + + int error_message = 0; + int index = 0; + + // Main compressor outlet pressure + + if (!auto_par.m_fixed_P_mc_out) + { + double P_mc_out = x[index]; + if (P_mc_out > m_P_high_limit) + return -1; + index++; + + // assign P_mc_out + core_inputs.m_P_mc_out = P_mc_out; + } + + + // Main compressor pressure ratio + double PR_mc_local = -999.9; + double P_mc_in = -999.9; + if (!opt_par.m_fixed_PR_HP_to_LP) + { + PR_mc_local = x[index]; + if (PR_mc_local > 50.0) + return -1; + index++; + P_mc_in = core_inputs.m_P_mc_out / PR_mc_local; + } + else + { + if (opt_par.m_PR_HP_to_LP_guess >= 0.0) + { + PR_mc_local = opt_par.m_PR_HP_to_LP_guess; + P_mc_in = core_inputs.m_P_mc_out / PR_mc_local; //[kPa] + } + else + { + P_mc_in = std::abs(opt_par.m_PR_HP_to_LP_guess); //[kPa] + } + } + + if (P_mc_in >= core_inputs.m_P_mc_out) + return -1; + if (P_mc_in <= 100.0) + return -1; + + core_inputs.m_P_mc_in = P_mc_in; + + // Recompression fraction + if (!opt_par.m_fixed_recomp_frac) + { + core_inputs.m_recomp_frac = x[index]; + if (core_inputs.m_recomp_frac < 0.0) + return -1; + index++; + } + + // Recuperator split fraction + double LT_frac_local = -999.9; + double LTR_UA, HTR_UA; + if (!opt_par.m_fixed_LT_frac) + { + LT_frac_local = x[index]; + if (LT_frac_local > 1.0 || LT_frac_local < 0.0) + return -1; + index++; + + if (auto_par.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA || auto_par.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA) + { + LTR_UA = auto_par.m_UA_rec_total * LT_frac_local; + HTR_UA = auto_par.m_UA_rec_total * (1.0 - LT_frac_local); + + // ASSIGN LTR_UA and HTR_UA + core_inputs.m_LTR_UA = LTR_UA; + core_inputs.m_HTR_UA = HTR_UA; + } + } + + return 0; +} + +/// +/// Set optimized variables to NaN, to protect them from misuse +/// +int C_HTRBypass_Cycle::clear_x_inputs(const std::vector& x, + const S_auto_opt_design_parameters auto_par, + const S_opt_design_parameters opt_par, + C_sco2_htrbp_core::S_sco2_htrbp_in& core_inputs) +{ + // 'x' is array of inputs either being adjusted by optimizer or set constant + // Finish defining core_inputs based on current 'x' values + + int error_message = 0; + int index = 0; + + // Main compressor outlet pressure + + if (!auto_par.m_fixed_P_mc_out) + { + core_inputs.m_P_mc_out = std::numeric_limits::quiet_NaN(); + } + + + core_inputs.m_P_mc_in = std::numeric_limits::quiet_NaN(); + + // Recompression fraction + if (!opt_par.m_fixed_recomp_frac) + { + core_inputs.m_recomp_frac = std::numeric_limits::quiet_NaN(); + } + + // Recuperator split fraction + if (!opt_par.m_fixed_LT_frac) + { + if (auto_par.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA || auto_par.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA) + { + // ASSIGN LTR_UA and HTR_UA + core_inputs.m_LTR_UA = std::numeric_limits::quiet_NaN();; + core_inputs.m_HTR_UA = std::numeric_limits::quiet_NaN();; + } + } + + return 0; +} + +/// +/// Calculate Temperature penalty, given target and calculated temperature +/// +double C_HTRBypass_Cycle::calc_T_penalty(double target, double calc, double span) +{ + double percent_error = std::abs(target - calc) / span; + //double penalty = 10.0 * (100.0 * sigmoid(percent_error) - 0.5); + + double penalty = percent_error; + + return penalty; +} + +/// +/// Calculate Objective Value (does not consider total UA minimization) +/// +double C_HTRBypass_Cycle::calc_objective(const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + const C_sco2_htrbp_core& htrbp_core) +{ + double obj = 0; + double eta = htrbp_core.m_outputs.m_eta_thermal; + + // Hit a target thermal efficiency + if (opt_par.m_design_method == 1) + { + double eta_error = std::min(eta - opt_par.m_eta_thermal_target, 0.0); + obj = 1.0 - std::abs(eta_error); + } + // Maximize thermal efficiency + else + { + obj = eta; + } + + // Penalize for HTF outlet temp (if necessary) + double penalty = 0; + if (opt_par.m_fixed_bypass_frac == false || auto_par.m_des_objective_type == 2) + { + double temp_calc = 0; + double span = 0; + + if (m_T_target_is_HTF == 0) + { + temp_calc = htrbp_core.m_outputs.m_temp[MIXER_OUT]; + span = htrbp_core.m_outputs.m_temp[TURB_IN] - m_T_target; + } + else + { + temp_calc = htrbp_core.m_outputs.m_T_HTF_BP_outlet; + span = htrbp_core.m_inputs.m_T_HTF_PHX_inlet - m_T_target; + } + + penalty = calc_T_penalty(m_T_target, temp_calc, span); + } + + obj = obj - penalty; + + return obj; +} + +// ********************************************************************************** PUBLIC Methods C_HTRBypass_Cycle (: C_sco2_cycle_core) + +/// +/// Optimize Cycle Design for FIXED total recuperator UA +/// +/// +int C_HTRBypass_Cycle::auto_opt_design(S_auto_opt_design_parameters& auto_opt_des_par_in) +{ + if (m_is_bp_par_set == false) + { + std::string warning_msg = "BP parameters are not defined"; + return -1; + } + + // Reset Counter + m_opt_iteration_count = 0; + + // Collect auto_opt_des parameters + ms_auto_opt_des_par = auto_opt_des_par_in; + + int auto_opt_des_error_code = 0; + + // Design cycle + auto_opt_design_core(auto_opt_des_error_code); + + return auto_opt_des_error_code; +} + +/// +/// Optimize Cycle design to hit target eta (optimize total recuperator UA) +/// +/// +/// +/// +int C_HTRBypass_Cycle::auto_opt_design_hit_eta(S_auto_opt_design_hit_eta_parameters& auto_opt_des_hit_eta_in, std::string& error_msg) +{ + int error_code = -1; + + if (m_is_bp_par_set == false) + { + error_msg = "BP parameters are not defined"; + return -1; + } + + // Reset Counter + m_opt_iteration_count = 0; + + // Fill in 'ms_auto_opt_des_par' from input + { + ms_auto_opt_des_par.m_UA_rec_total = std::numeric_limits::quiet_NaN(); // ***** This method finds the UA required to hit the input efficiency! ***** + // LTR thermal design + ms_auto_opt_des_par.m_LTR_target_code = auto_opt_des_hit_eta_in.m_LTR_target_code; //[-] + ms_auto_opt_des_par.m_LTR_UA = auto_opt_des_hit_eta_in.m_LTR_UA; //[kW/K] + ms_auto_opt_des_par.m_LTR_min_dT = auto_opt_des_hit_eta_in.m_LTR_min_dT; //[K] + ms_auto_opt_des_par.m_LTR_eff_target = auto_opt_des_hit_eta_in.m_LTR_eff_target; //[-] + ms_auto_opt_des_par.m_LTR_eff_max = auto_opt_des_hit_eta_in.m_LTR_eff_max; + ms_auto_opt_des_par.m_LTR_od_UA_target_type = auto_opt_des_hit_eta_in.m_LTR_od_UA_target_type; + // HTR thermal design + ms_auto_opt_des_par.m_HTR_target_code = auto_opt_des_hit_eta_in.m_HTR_target_code; //[-] + ms_auto_opt_des_par.m_HTR_UA = auto_opt_des_hit_eta_in.m_HTR_UA; //[kW/K] + ms_auto_opt_des_par.m_HTR_min_dT = auto_opt_des_hit_eta_in.m_HTR_min_dT; //[K] + ms_auto_opt_des_par.m_HTR_eff_target = auto_opt_des_hit_eta_in.m_HTR_eff_target; //[-] + ms_auto_opt_des_par.m_HTR_eff_max = auto_opt_des_hit_eta_in.m_HTR_eff_max; //[-] + ms_auto_opt_des_par.m_HTR_od_UA_target_type = auto_opt_des_hit_eta_in.m_HTR_od_UA_target_type; + // + ms_auto_opt_des_par.m_des_tol = auto_opt_des_hit_eta_in.m_des_tol; //[-] Convergence tolerance + ms_auto_opt_des_par.m_des_opt_tol = auto_opt_des_hit_eta_in.m_des_opt_tol; //[-] Optimization tolerance + ms_auto_opt_des_par.m_is_recomp_ok = auto_opt_des_hit_eta_in.m_is_recomp_ok; //[-] 1 = yes, 0 = no, other = invalid + ms_auto_opt_des_par.m_is_bypass_ok = auto_opt_des_hit_eta_in.m_is_bypass_ok; //[-] 1 = yes, 0 = no, other = invalid + + ms_auto_opt_des_par.m_is_des_air_cooler = auto_opt_des_hit_eta_in.m_is_des_air_cooler; //[-] + + ms_auto_opt_des_par.m_des_objective_type = auto_opt_des_hit_eta_in.m_des_objective_type; //[-] + ms_auto_opt_des_par.m_min_phx_deltaT = auto_opt_des_hit_eta_in.m_min_phx_deltaT; //[C] + + ms_auto_opt_des_par.mf_callback_log = auto_opt_des_hit_eta_in.mf_callback_log; + ms_auto_opt_des_par.mp_mf_active = auto_opt_des_hit_eta_in.mp_mf_active; + + ms_auto_opt_des_par.m_fixed_P_mc_out = auto_opt_des_hit_eta_in.m_fixed_P_mc_out; //[-] + + ms_auto_opt_des_par.m_PR_HP_to_LP_guess = auto_opt_des_hit_eta_in.m_PR_HP_to_LP_guess; //[-] Initial guess for ratio of P_mc_out to P_mc_in + ms_auto_opt_des_par.m_fixed_PR_HP_to_LP = auto_opt_des_hit_eta_in.m_fixed_PR_HP_to_LP; //[-] if true, ratio of P_mc_out to P_mc_in is fixed at PR_mc_guess + } + + // Check that simple/recomp flag is set + if (ms_auto_opt_des_par.m_is_recomp_ok < -1.0 || (ms_auto_opt_des_par.m_is_recomp_ok > 0 && + ms_auto_opt_des_par.m_is_recomp_ok != 1.0 && ms_auto_opt_des_par.m_is_recomp_ok != 2.0)) + { + throw(C_csp_exception("C_RecompCycle::auto_opt_design_core(...) requires that ms_auto_opt_des_par.m_is_recomp_ok" + " is either between -1 and 0 (fixed recompression fraction) or equal to 1 (recomp allowed)\n")); + } + + // Validate Inputs + error_msg = ""; + { + // Check that simple/recomp flag is set + if (ms_auto_opt_des_par.m_is_recomp_ok < -1.0 || (ms_auto_opt_des_par.m_is_recomp_ok > 0 && + ms_auto_opt_des_par.m_is_recomp_ok != 1.0 && ms_auto_opt_des_par.m_is_recomp_ok != 2.0)) + { + throw(C_csp_exception("C_RecompCycle::auto_opt_design_core(...) requires that ms_auto_opt_des_par.m_is_recomp_ok" + " is either between -1 and 0 (fixed recompression fraction) or equal to 1 (recomp allowed)\n")); + } + // Can't operate compressore in 2-phase region + if (m_T_mc_in <= N_co2_props::T_crit) + { + error_msg.append(util::format("Only single phase cycle operation is allowed in this model." + "The compressor inlet temperature (%lg [C]) must be great than the critical temperature: %lg [C]", + m_T_mc_in - 273.15, ((N_co2_props::T_crit)-273.15))); + + return -1; + } + + // "Reasonable" ceiling on compressor inlet temp + double T_mc_in_max = 70.0 + 273.15; //[K] Arbitrary value for max compressor inlet temperature + if (m_T_mc_in > T_mc_in_max) + { + error_msg.append(util::format("The compressor inlet temperature input was %lg [C]. This value was reset internally to the max allowable inlet temperature: %lg [C]\n", + m_T_mc_in - 273.15, T_mc_in_max - 273.15)); + + m_T_mc_in = T_mc_in_max; + } + + // "Reasonable" floor on turbine inlet temp + double T_t_in_min = 300.0 + 273.15; //[K] Arbitrary value for min turbine inlet temperature + if (m_T_t_in < T_t_in_min) + { + error_msg.append(util::format("The turbine inlet temperature input was %lg [C]. This value was reset internally to the min allowable inlet temperature: %lg [C]\n", + m_T_t_in - 273.15, T_t_in_min - 273.15)); + + m_T_t_in = T_t_in_min; + } + + // Turbine inlet temperature must be hotter than compressor outlet temperature + if (m_T_t_in <= m_T_mc_in) + { + error_msg.append(util::format("The turbine inlet temperature, %lg [C], is colder than the specified compressor inlet temperature %lg [C]", + m_T_t_in - 273.15, m_T_mc_in - 273.15)); + + return -1; + } + + // Turbine inlet temperature must be colder than property limits + if (m_T_t_in >= N_co2_props::T_upper_limit) + { + error_msg.append(util::format("The turbine inlet temperature, %lg [C], is hotter than the maximum allow temperature in the CO2 property code %lg [C]", + m_T_t_in - 273.15, N_co2_props::T_upper_limit - 273.15)); + + return -1; + } + + // REMOVED CHECKS (not compatible with negative values) + // Check for realistic isentropic efficiencies + /*if (m_eta_mc > 1.0) + { + error_msg.append(util::format("The main compressor isentropic efficiency, %lg, was reset to theoretical maximum 1.0\n", + m_eta_mc)); + + m_eta_mc = 1.0; + } + if (m_eta_rc > 1.0) + { + error_msg.append(util::format("The re-compressor isentropic efficiency, %lg, was reset to theoretical maximum 1.0\n", + m_eta_rc)); + + m_eta_rc = 1.0; + } + if (m_eta_t > 1.0) + { + error_msg.append(util::format("The turbine isentropic efficiency, %lg, was reset to theoretical maximum 1.0\n", + m_eta_t)); + + m_eta_t = 1.0; + } + if (m_eta_mc < 0.1) + { + error_msg.append(util::format("The main compressor isentropic efficiency, %lg, was increased to the internal limit of 0.1 to improve solution stability\n", + m_eta_mc)); + + m_eta_mc = 0.1; + } + if (m_eta_rc < 0.1) + { + error_msg.append(util::format("The re-compressor isentropic efficiency, %lg, was increased to the internal limit of 0.1 to improve solution stability\n", + m_eta_rc)); + + m_eta_rc = 0.1; + } + if (m_eta_t < 0.1) + { + error_msg.append(util::format("The turbine isentropic efficiency, %lg, was increased to the internal limit of 0.1 to improve solution stability\n", + m_eta_t)); + + m_eta_t = 0.1; + }*/ + + if (ms_auto_opt_des_par.m_LTR_eff_max > 1.0) + { + error_msg.append(util::format("The LT recuperator max effectiveness, %lg, was decreased to the limit of 1.0\n", ms_auto_opt_des_par.m_LTR_eff_max)); + + ms_auto_opt_des_par.m_LTR_eff_max = 1.0; + } + + if (ms_auto_opt_des_par.m_LTR_eff_max < 0.70) + { + error_msg.append(util::format("The LT recuperator max effectiveness, %lg, was increased to the internal limit of 0.70 improve convergence\n", ms_auto_opt_des_par.m_LTR_eff_max)); + + ms_auto_opt_des_par.m_LTR_eff_max = 0.7; + } + + if (ms_auto_opt_des_par.m_HTR_eff_max > 1.0) + { + error_msg.append(util::format("The HT recuperator max effectiveness, %lg, was decreased to the limit of 1.0\n", ms_auto_opt_des_par.m_HTR_eff_max)); + + ms_auto_opt_des_par.m_HTR_eff_max = 1.0; + } + + if (ms_auto_opt_des_par.m_HTR_eff_max < 0.70) + { + error_msg.append(util::format("The LT recuperator max effectiveness, %lg, was increased to the internal limit of 0.70 improve convergence\n", ms_auto_opt_des_par.m_HTR_eff_max)); + + ms_auto_opt_des_par.m_HTR_eff_max = 0.7; + } + + // Limits on high pressure limit + if (m_P_high_limit >= N_co2_props::P_upper_limit) + { + error_msg.append(util::format("The upper pressure limit, %lg [MPa], was set to the internal limit in the CO2 properties code %lg [MPa]\n", + m_P_high_limit, N_co2_props::P_upper_limit)); + + m_P_high_limit = N_co2_props::P_upper_limit; + } + double P_high_limit_min = 10.0 * 1.E3; //[kPa] + if (m_P_high_limit <= P_high_limit_min) + { + error_msg.append(util::format("The upper pressure limit, %lg [MPa], must be greater than %lg [MPa] to ensure solution stability", + m_P_high_limit, P_high_limit_min)); + + return -1; + } + + // Finally, check thermal efficiency + if (auto_opt_des_hit_eta_in.m_eta_thermal <= 0.0) + { + error_msg.append(util::format("The design cycle thermal efficiency, %lg, must be at least greater than 0 ", + auto_opt_des_hit_eta_in.m_eta_thermal)); + + return -1; + } + double eta_carnot = 1.0 - m_T_mc_in / m_T_t_in; + if (auto_opt_des_hit_eta_in.m_eta_thermal >= eta_carnot) + { + error_msg.append(util::format("To solve the cycle within the allowable recuperator conductance, the design cycle thermal efficiency, %lg, must be at least less than the Carnot efficiency: %lg ", + auto_opt_des_hit_eta_in.m_eta_thermal, eta_carnot)); + + return -1; + } + + + } + + // Send log update upstream + if (ms_auto_opt_des_par.mf_callback_log && ms_auto_opt_des_par.mp_mf_active) + { + std::string msg_log = util::format("Iterate on total recuperator conductance to hit target cycle efficiency: %lg [-]", auto_opt_des_hit_eta_in.m_eta_thermal); + std::string msg_progress = "Designing cycle..."; + if (!ms_auto_opt_des_par.mf_callback_log(msg_log, msg_progress, ms_auto_opt_des_par.mp_mf_active, 0.0, 2)) + { + std::string error_msg = "User terminated simulation..."; + std::string loc_msg = "C_MEQ_sco2_design_hit_eta__UA_total"; + throw(C_csp_exception(error_msg, loc_msg, 1)); + } + } + + // Optimize + auto_opt_design_hit_eta_core(error_code, auto_opt_des_hit_eta_in.m_eta_thermal); + + // Send log update upstream + if (ms_auto_opt_des_par.mf_callback_log && ms_auto_opt_des_par.mp_mf_active) + { + std::string msg_log = "Optimization finished in " + std::to_string(m_opt_iteration_count) + " iterations."; + std::string msg_progress = ""; + if (!ms_auto_opt_des_par.mf_callback_log(msg_log, msg_progress, ms_auto_opt_des_par.mp_mf_active, 100.0, 2)) + { + std::string error_msg = "User terminated simulation..."; + std::string loc_msg = "C_MEQ_sco2_design_hit_eta__UA_total"; + throw(C_csp_exception(error_msg, loc_msg, 1)); + } + } + + return error_code; +} + +// ********************************************************************************** PUBLIC Objective Functions (internal use only) + +/// +/// Objective Function for total UA (outermost layer) +/// Total UA -> Bypass Fraction -> pressure, split UA, recomp frac +/// +/// ONLY Objective Value +double C_HTRBypass_Cycle::optimize_totalUA_return_objective_metric(const std::vector& x, + const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par +) +{ + // Update counter + m_opt_iteration_count++; // global + + // Copy optimal parameters + S_auto_opt_design_parameters auto_par_internal = auto_par; + S_opt_design_parameters opt_par_internal = opt_par; + + // Assign Total Recuperator UA + double total_UA = x[0]; + auto_par_internal.m_UA_rec_total = total_UA; + + // Optimize all internal variables (bypass_frac -> recomp frac, UA ratio, pressure ratio) + C_sco2_htrbp_core::S_sco2_htrbp_in optimal_inputs_case; + int error_code = optimize_bp(auto_par_internal, opt_par_internal, optimal_inputs_case); + + if (error_code != 0) + return -100000000000000000; + + // ^ Get optimal core_inputs from here + + // Run Optimal Case, return objective function + C_sco2_htrbp_core htrbp_core; + htrbp_core.set_inputs(optimal_inputs_case); + error_code = htrbp_core.solve(); + if (error_code != 0) + return -100000000000000000; + + // Calculate Objective Value + double obj = calc_objective(auto_par_internal, opt_par_internal, htrbp_core); + + // This objectve ^ targets a thermal efficiency (and tries to hit a temperature, if applicable) + // Need to incentivize lower UA value + + // Hitting eta and temperature is more important than low UA + double UA_percent = (total_UA - opt_par.m_UA_recup_total_min) / (opt_par.m_UA_recup_total_max - opt_par.m_UA_recup_total_min); // Lower is closer to 0 + double UA_penalty = UA_percent; + + // Increase obj scale (making eta and temp more weighted) + double obj_weighted = obj * 1e2; + + // Subtract UA_penalty + obj_weighted = obj_weighted - UA_penalty; + + return obj_weighted; +} + +/// +/// Objective Function for BYPASS optimization (calls internal optimization for other variables) +/// Total UA -> Bypass Fraction -> pressure, split UA, recomp frac +/// +/// +double C_HTRBypass_Cycle::optimize_bp_return_objective_metric(const std::vector& x, + const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + C_sco2_htrbp_core& htrbp_core) +{ + // Update counter + m_opt_iteration_count++; // global + + if (opt_par.m_fixed_bypass_frac == true) + { + //throw std::exception("Optimizing bypass fraction even though it is fixed"); + throw(C_csp_exception("Optimizing bypass fraction even though it is fixed")); + } + + // Set Bypass Fraction + htrbp_core.m_inputs.m_bypass_frac = x[0]; + + // Optimize all other variables + C_sco2_htrbp_core::S_sco2_htrbp_in optimal_inputs_case; + optimize_nonbp(auto_par, opt_par, htrbp_core.m_inputs, optimal_inputs_case); + + // ^ Get optimal core_inputs from here + + // Run Optimal Case, return objective function + + htrbp_core.set_inputs(optimal_inputs_case); + int error_code = htrbp_core.solve(); + if (error_code != 0) + return -100000000000000000; + + // Calculate Objective Value + double obj = calc_objective(auto_par, opt_par, htrbp_core); + + if (obj > m_opt_obj_internal_only) + { + m_opt_obj_internal_only = obj; + m_optimal_inputs_internal_only = optimal_inputs_case; + } + + // Reset htrbp_core + htrbp_core.m_inputs.m_bypass_frac = std::numeric_limits::quiet_NaN(); + htrbp_core.m_outputs.Init(); + + return obj; +} + +/// +/// Objective Function for NON Bypass optimization +/// Total UA -> Bypass Fraction -> pressure, split UA, recomp frac +/// +/// ONLY Objective Value +double C_HTRBypass_Cycle::optimize_nonbp_return_objective_metric(const std::vector& x, + const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + C_sco2_htrbp_core& htrbp_core) +{ + // Update counter + m_opt_iteration_count++; // global + + // Modify Core Inputs with Variable Parameters + int error_code = x_to_inputs(x, auto_par, opt_par, htrbp_core.m_inputs); + + if (error_code != 0) + return -1000000000; + + // AT this point, will have fully defined core input struct + // Run the core model + error_code = htrbp_core.solve(); + + // Set Objective + double objective_metric = -10000000000.0; + if (error_code == 0) + { + objective_metric = calc_objective(auto_par, opt_par, htrbp_core); + } + + // Clear Optimized Inputs + clear_x_inputs(x, auto_par, opt_par, htrbp_core.m_inputs); + + return objective_metric; +} + +// ********************************************************************************** Off Design Functions + + +void C_HTRBypass_Cycle::reset_ms_od_turbo_bal_csp_solved() +{ +} + +int C_HTRBypass_Cycle::off_design_fix_shaft_speeds(S_od_par& od_phi_par_in, double od_tol) +{ + return 0; +} + +int C_HTRBypass_Cycle::solve_OD_all_coolers_fan_power(double T_amb, double od_tol, double& W_dot_fan) +{ + return 0; +} + +int C_HTRBypass_Cycle::solve_OD_mc_cooler_fan_power(double T_amb, double od_tol, double& W_dot_mc_cooler_fan, double& P_co2_out) +{ + return 0; +} + +int C_HTRBypass_Cycle::solve_OD_pc_cooler_fan_power(double T_amb, double od_tol, double& W_dot_pc_cooler_fan, double& P_co2_out) +{ + return 0; +} + +double C_HTRBypass_Cycle::get_od_temp(int n_state_point) +{ + return 0.0; +} + +double C_HTRBypass_Cycle::get_od_pres(int n_state_point) +{ + return 0.0; +} + +void C_HTRBypass_Cycle::check_od_solution(double& diff_m_dot, double& diff_E_cycle, double& diff_Q_LTR, double& diff_Q_HTR) +{ +} + +void C_HTRBypass_Cycle::set_od_temp(int n_state_point, double temp_K) +{ +} + +void C_HTRBypass_Cycle::set_od_pres(int n_state_point, double pres_kPa) +{ +} + +void C_HTRBypass_Cycle::off_design_recompressor(double T_in, double P_in, double m_dot, double P_out, double tol, int& error_code, double& T_out) +{ +} + +void C_HTRBypass_Cycle::estimate_od_turbo_operation(double T_mc_in, double P_mc_in, double f_recomp, double T_t_in, double phi_mc, int& mc_error_code, double& mc_w_tip_ratio, double& P_mc_out, int& rc_error_code, double& rc_w_tip_ratio, double& rc_phi, bool is_update_ms_od_solved) +{ +} + + +// ********************************************************************************** PUBLIC Methods defined outside of any class + +double nlopt_optimize_totalUA_func(const std::vector& x, std::vector& grad, void* data) +{ + // Unpack Data Tuple + std::tuple* data_tuple + = static_cast*>(data); + + C_HTRBypass_Cycle* frame = std::get<0>(*data_tuple); + const C_HTRBypass_Cycle::S_auto_opt_design_parameters* auto_opt_par = std::get<1>(*data_tuple); + const C_HTRBypass_Cycle::S_opt_design_parameters* opt_par = std::get<2>(*data_tuple); + + if (frame != NULL) + return frame->optimize_totalUA_return_objective_metric(x, *auto_opt_par, *opt_par); + else + return 0.0; +} + +double nlopt_optimize_bp_func(const std::vector& x, std::vector& grad, void* data) +{ + // Unpack Data Tuple + std::tuple* data_tuple + = static_cast*>(data); + + C_HTRBypass_Cycle* frame = std::get<0>(*data_tuple); + const C_HTRBypass_Cycle::S_auto_opt_design_parameters* auto_opt_par = std::get<1>(*data_tuple); + const C_HTRBypass_Cycle::S_opt_design_parameters* opt_par = std::get<2>(*data_tuple); + C_sco2_htrbp_core* htrbp_core = std::get<3>(*data_tuple); + + if (frame != NULL) + return frame->optimize_bp_return_objective_metric(x, *auto_opt_par, *opt_par, *htrbp_core); + else + return 0.0; +} + +double nlopt_optimize_nonbp_func(const std::vector& x, std::vector& grad, void* data) +{ + // Unpack Data Tuple + std::tuple* data_tuple + = static_cast*>(data); + + C_HTRBypass_Cycle* frame = std::get<0>(*data_tuple); + const C_HTRBypass_Cycle::S_auto_opt_design_parameters* auto_opt_par = std::get<1>(*data_tuple); + const C_HTRBypass_Cycle::S_opt_design_parameters* opt_par = std::get<2>(*data_tuple); + C_sco2_htrbp_core* htrbp_core = std::get<3>(*data_tuple); + + if (frame != NULL) + return frame->optimize_nonbp_return_objective_metric(x, *auto_opt_par, *opt_par, *htrbp_core); + else + return 0.0; +} + +double sigmoid(const double val) +{ + return 1.0 / (1.0 + std::exp(-1.0 * val)); +} + +double logit(const double val) +{ + return std::log(val / (1.0 - val)); +} diff --git a/tcs/sco2_htrbypass_cycle.h b/tcs/sco2_htrbypass_cycle.h new file mode 100644 index 000000000..cc0ea5eb5 --- /dev/null +++ b/tcs/sco2_htrbypass_cycle.h @@ -0,0 +1,521 @@ +/* +BSD 3-Clause License + +Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/ssc/blob/develop/LICENSE +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __SCO2_HTRBYPASS_ +#define __SCO2_HTRBYPASS_ + +#include "sco2_cycle_components.h" +#include "sco2_cycle_templates.h" + +#include "heat_exchangers.h" +#include "CO2_properties.h" + +#include "numeric_solvers.h" + + +#include "nlopt.hpp" +#include "nlopt_callbacks.h" + + + +// This class is purely for solving the cycle +// No optimization +class C_sco2_htrbp_core +{ +public: + + // Defines sco2 htr bypass input variables (no optimized variables) + struct S_sco2_htrbp_in + { + + double m_P_mc_in; //[kPa] Compressor inlet pressure + double m_P_mc_out; //[kPa] Compressor outlet pressure + + // LTR thermal design + int m_LTR_target_code; //[-] 1 = UA, 2 = min dT, 3 = effectiveness + double m_LTR_UA; //[kW/K] target LTR conductance + double m_LTR_min_dT; //[K] target LTR minimum temperature difference + double m_LTR_eff_target; //[-] target LTR effectiveness + double m_LTR_eff_max; //[-] Maximum allowable effectiveness in LT recuperator + NS_HX_counterflow_eqs::E_UA_target_type m_LTR_od_UA_target_type; + int m_LTR_N_sub_hxrs; //[-] Number of sub-hxs to use in hx model + + // HTR thermal design + int m_HTR_target_code; //[-] 1 = UA, 2 = min dT, 3 = effectiveness + double m_HTR_UA; //[kW/K] target HTR conductance + double m_HTR_min_dT; //[K] target HTR min temperature difference + double m_HTR_eff_target; //[-] target HTR effectiveness + double m_HTR_eff_max; //[-] Maximum allowable effectiveness in HT recuperator + NS_HX_counterflow_eqs::E_UA_target_type m_HTR_od_UA_target_type; + int m_HTR_N_sub_hxrs; //[-] Number of sub-hxs to use in hx model + + double m_recomp_frac; //[-] Fraction of flow that bypasses the precooler and the main compressor at the design point + double m_bypass_frac; //[-] Fraction of flow that bypasses the HTR and passes through the Bypass HX + double m_des_tol; //[-] Convergence tolerance + + // Air cooler parameters + bool m_is_des_air_cooler; //[-] False will skip physical air cooler design. UA will not be available for cost models. + + + // Added + double m_W_dot_net_design; //[kWe] Target net cycle power + double m_T_mc_in; //[K] Compressor inlet temperature + double m_T_t_in; //[K] Turbine inlet temperature + double m_dT_BP; //[delta K/C] BYPASS_OUT - HTR_HP_OUT + std::vector m_DP_LTR; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) + std::vector m_DP_HTR; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) + std::vector m_DP_PC_main; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) + std::vector m_DP_PHX; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) + double m_eta_mc; //[-] design-point efficiency of the main compressor; isentropic if positive, polytropic if negative + double m_eta_t; //[-] design-point efficiency of the turbine; isentropic if positive, polytropic if negative + double m_eta_rc; //[-] design-point efficiency of the recompressor; isentropic if positive, polytropic if negative + double m_eta_generator; //[-] Mechanical-to-electrical efficiency of generator + double m_frac_fan_power; //[-] Fraction of total cycle power 'S_des_par_cycle_dep.m_W_dot_fan_des' consumed by air fan + double m_set_HTF_mdot; //[kg/s] > 0 set HTF mass flow, <= 0 use bypass approach temp to calculate HTF mdot + double m_cp_HTF; //[kJ/kg K] HTF specific heat + double m_T_HTF_PHX_inlet; //[K] HTF Inlet Temperature + double m_eta_fan; //[-] Fan isentropic efficiency + double m_deltaP_cooler_frac; //[-] Fraction of high side (of cycle, i.e. comp outlet) pressure that is allowed as pressure drop to design the ACC + double m_T_amb_des; //[K] Design point ambient temperature + double m_elevation; //[m] Elevation (used to calculate ambient pressure) + int m_N_nodes_pass; //[-] Number of nodes per pass + double m_HTF_PHX_cold_approach_input; //[delta K] PHX cold approach temperature. Only needed if m_set_HTF_mdot < 0 + int m_mc_comp_model_code; // Main compressor model code + int m_rc_comp_model_code; // Recompressor model code + int m_N_turbine; //[rpm] Turbine rpm + double m_yr_inflation; //[yr] Inflation target year + + S_sco2_htrbp_in() + { + m_P_mc_in = m_P_mc_out = + m_LTR_UA = m_LTR_min_dT = m_LTR_eff_target = m_LTR_eff_max = + m_HTR_UA = m_HTR_min_dT = m_HTR_eff_target = m_HTR_eff_max = + m_recomp_frac = + m_bypass_frac = + m_des_tol = + m_W_dot_net_design = + m_T_mc_in = + m_T_t_in = + m_eta_mc = + m_eta_t = + m_eta_rc = + m_eta_generator = + m_frac_fan_power = + m_set_HTF_mdot = + m_cp_HTF = + m_T_HTF_PHX_inlet = + m_eta_fan = + m_deltaP_cooler_frac = + m_T_amb_des = + m_elevation = + m_dT_BP = + m_HTF_PHX_cold_approach_input = + m_yr_inflation = + std::numeric_limits::quiet_NaN(); + + m_N_nodes_pass = 0; + m_LTR_N_sub_hxrs = 0; + m_HTR_N_sub_hxrs = 0; + m_mc_comp_model_code = -1; + m_rc_comp_model_code = -1; + m_N_turbine = -1; + + // Recuperator design target codes + m_LTR_target_code = 1; // default to target conductance + m_LTR_od_UA_target_type = NS_HX_counterflow_eqs::E_UA_target_type::E_calc_UA; + m_HTR_target_code = 1; // default to target conductance + m_HTR_od_UA_target_type = NS_HX_counterflow_eqs::E_UA_target_type::E_calc_UA; + + + // Air cooler default + m_is_des_air_cooler = true; + + } + + }; + + // Defines sco2 htr bypass output variables (no optimized variables) + struct S_sco2_htrbp_out + { + int m_error_code; + C_turbine m_t; // Turbine model + C_comp_multi_stage m_mc_ms; // Main Compressor Model + C_comp_multi_stage m_rc_ms; // Recompressor Model + C_HeatExchanger m_PHX, m_PC, m_BPX; // Primary, Cooler, Bypass Heat Exchanger Models + C_HX_co2_to_co2_CRM mc_LT_recup; // LTR + C_HX_co2_to_co2_CRM mc_HT_recup; // HTR + C_CO2_to_air_cooler mc_air_cooler; // Air Cooler + std::vector m_temp, m_pres, m_enth, m_entr, m_dens; // thermodynamic states (K, kPa, kJ/kg, kJ/kg-K, kg/m3) + double m_w_t, m_w_mc, m_w_rc; // [kJ/kg] specific work of turbine, main compressor, recompressor + double m_m_dot_t, m_m_dot_mc, m_m_dot_rc; // [kg/s] sco2 Mass flow in main compressor, recompressor, turbine + double m_m_dot_bp, m_m_dot_htr_hp; // [kg/s] sco2 Mass flow through bypass, hot side HTR + double m_Q_dot_LT, m_Q_dot_HT; // [kWt] Heat Transfer in LTR, HTR + double m_W_dot_mc, m_W_dot_rc, m_W_dot_t; // [kWt] Energy consumed by main compressor, recompressor, produced by turbine + double m_W_dot_net; // [kWt] ACTUAL produced net work in system + double m_W_dot_air_cooler; // [kWe] Energy consumed by air cooler + double m_Q_dot_air_cooler; // [kWt] Heat rejected by air cooler + double m_Q_dot_LTR_LP, m_Q_dot_LTR_HP, m_Q_dot_HTR_LP, m_Q_dot_HTR_HP; // kWt Heat change on LTR low pressure, etc... + double m_Q_dot_total; // [kWt] Total heat entering sco2 + double m_Q_dot_PHX, m_Q_dot_BP; // [kWt] Energy exchange in PHX, BPX + double m_m_dot_HTF; // [kg/s] HTF mass flow rate + double m_T_HTF_PHX_out; // [K] HTF PHX outlet temperature + double m_HTF_PHX_cold_approach; // [delta K/C] PHX cold approach temperature + double m_T_HTF_BP_outlet; // [K] HTF BPX outlet temperature + double m_HTF_BP_cold_approach; // [K] BPX cold approach temperature + double m_eta_thermal; // Thermal Efficiency + + S_sco2_htrbp_out() + { + Init(); + } + + void Init() + { + m_w_t = m_w_mc = m_w_rc + = m_m_dot_t = m_m_dot_mc = m_m_dot_rc + = m_m_dot_bp = m_m_dot_htr_hp + = m_Q_dot_LT = m_Q_dot_HT + = m_W_dot_mc = m_W_dot_rc = m_W_dot_t + = m_W_dot_net = m_W_dot_air_cooler = m_Q_dot_air_cooler + = m_Q_dot_LTR_LP = m_Q_dot_LTR_HP = m_Q_dot_HTR_LP = m_Q_dot_HTR_HP + = m_Q_dot_total = m_Q_dot_PHX = m_Q_dot_BP + = m_m_dot_HTF = m_T_HTF_PHX_out = m_HTF_PHX_cold_approach + = m_T_HTF_BP_outlet = m_HTF_BP_cold_approach = m_eta_thermal + = std::numeric_limits::quiet_NaN(); + + m_error_code = -1; + + // Clear and Size Output Vectors + m_temp.resize(C_sco2_cycle_core::END_SCO2_STATES); + std::fill(m_temp.begin(), m_temp.end(), std::numeric_limits::quiet_NaN()); + m_pres = m_enth = m_entr = m_dens = m_temp; + } + }; + + +private: + CO2_state m_co2_props; + + class C_mono_htrbp_core_HTR_des : public C_monotonic_equation + { + private: + C_sco2_htrbp_core* m_htr_bypass_cycle; + + public: + C_mono_htrbp_core_HTR_des(C_sco2_htrbp_core* htr_bypass_cycle) + { + m_htr_bypass_cycle = htr_bypass_cycle; + } + + virtual int operator()(double T_HTR_LP_OUT_guess /*K*/, double* diff_T_HTR_LP_out /*K*/) + { + return m_htr_bypass_cycle->solve_HTR(T_HTR_LP_OUT_guess, diff_T_HTR_LP_out); + }; + }; + + class C_mono_htrbp_core_LTR_des : public C_monotonic_equation + { + private: + C_sco2_htrbp_core* m_htr_bypass_cycle; + + public: + C_mono_htrbp_core_LTR_des(C_sco2_htrbp_core* htr_bypass_cycle) + { + m_htr_bypass_cycle = htr_bypass_cycle; + } + + virtual int operator()(double T_LTR_LP_OUT_guess /*K*/, double* diff_T_LTR_LP_out /*K*/) + { + return m_htr_bypass_cycle->solve_LTR(T_LTR_LP_OUT_guess, diff_T_LTR_LP_out); + }; + }; + + int solve_HTR(double T_HTR_LP_OUT_guess, double* diff_T_HTR_LP_out); + int solve_LTR(double T_LTR_LP_OUT_guess, double* diff_T_LTR_LP_out); + + void initialize_solve(); + +public: + // Inputs Struct + S_sco2_htrbp_in m_inputs; + + // Outputs Struct + S_sco2_htrbp_out m_outputs; + + // Public Methods + C_sco2_htrbp_core() + { + m_outputs.Init(); + m_co2_props = CO2_state(); + } + + void set_inputs(S_sco2_htrbp_in inputs) { m_inputs = inputs; }; + + int solve(); + + int finalize_design(C_sco2_cycle_core::S_design_solved& design_solved); + + void reset(); +}; + + + +class C_HTRBypass_Cycle : public C_sco2_cycle_core +{ +public: + + // Struct to store optimization variables + struct S_opt_design_parameters + { + + double m_P_mc_out_guess; //[kPa] Initial guess for main compressor outlet pressure + bool m_fixed_P_mc_out; //[-] if true, P_mc_out is fixed at P_mc_out_guess + + double m_PR_HP_to_LP_guess; //[-] Initial guess for ratio of P_mc_out to P_LP_in + bool m_fixed_PR_HP_to_LP; //[-] if true, ratio of P_mc_out to P_mc_in is fixed at PR_mc_guess + + double m_recomp_frac_guess; //[-] Initial guess for design-point recompression fraction + bool m_fixed_recomp_frac; //[-] if true, recomp_frac is fixed at recomp_frac_guess + + double m_bypass_frac_guess; //[-] Initial guess for design-point bypass fraction + bool m_fixed_bypass_frac; //[-] if true, bypass_frac is fixed at bypass_frac_guess + + double m_LT_frac_guess; //[-] Initial guess for fraction of UA_rec_total that is in the low-temperature recuperator + bool m_fixed_LT_frac; //[-] if true, LT_frac is fixed at LT_frac_guess + + // ADDED + int m_design_method; //[] Design Method [1] Optimize total UA for target eta, [2] Optimize UA split ratio, [3] set LTR HTR directly + double m_eta_thermal_target; //[] Cycle thermal efficiency target (used by total UA optimization) + double m_UA_recup_total_max; //[kW/K] Maximum recuperator conductance (for total UA optimizer) + double m_UA_recup_total_min; //[kW/K] Minimum recuperator conductance (for total UA optimizer) + + + S_opt_design_parameters() + { + m_P_mc_out_guess = m_PR_HP_to_LP_guess = m_recomp_frac_guess = m_LT_frac_guess = + m_bypass_frac_guess = m_eta_thermal_target = + m_UA_recup_total_max = m_UA_recup_total_min = + std::numeric_limits::quiet_NaN(); + + + m_design_method = -1; + m_fixed_recomp_frac = false; + m_fixed_bypass_frac = false; + m_fixed_LT_frac = false; + m_fixed_PR_HP_to_LP = false; + m_fixed_P_mc_out = false; + + } + + + }; + +private: + + // Optimal inputs, for bypass optimizer DO NOT USE + C_sco2_htrbp_core::S_sco2_htrbp_in m_optimal_inputs_internal_only; + double m_opt_obj_internal_only; + + int m_opt_iteration_count; // Counter of bypass iterations + + // Bypass Specific HTF variables + int m_T_target_is_HTF; // Target Temperature is HTF (1) or cold sco2 at BP + double m_T_target; // [K] Target temperature (either HTF or sco2) + double m_T_HTF_PHX_inlet; // [K] HTF Primary Heat Exchanger Inlet Temperature + double m_set_HTF_mdot; // [kg/s] [0] calculate HTF mdot (need to set dT_PHX_cold_approach) [>0] mdot + double m_HTF_PHX_cold_approach; // [K] PHX cold approach temperature (need if m_set_HTF_mdot == 0) + double m_dT_BP; // [K] Temperature difference at second mixer inlet + double m_cp_HTF; // [kJ/kg K] HTF specific heat + bool m_is_bp_par_set; // Are bp parameters set + + // Optimal htrbp core class (contains all results and component data) + C_sco2_htrbp_core m_optimal_htrbp_core; + + void auto_opt_design_core(int& error_code); + + void auto_opt_design_hit_eta_core(int& error_code, const double eta_thermal_target); + + int optimize_totalUA(const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, C_sco2_htrbp_core::S_sco2_htrbp_in& optimal_inputs); + + int optimize_bp(const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, C_sco2_htrbp_core::S_sco2_htrbp_in& optimal_inputs); + + int optimize_nonbp(const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, C_sco2_htrbp_core::S_sco2_htrbp_in& core_inputs, C_sco2_htrbp_core::S_sco2_htrbp_in& optimal_inputs); + + int x_to_inputs(const std::vector& x, const S_auto_opt_design_parameters auto_par, const S_opt_design_parameters opt_par, C_sco2_htrbp_core::S_sco2_htrbp_in& core_inputs); + + int clear_x_inputs(const std::vector& x, const S_auto_opt_design_parameters auto_par, const S_opt_design_parameters opt_par, C_sco2_htrbp_core::S_sco2_htrbp_in& core_inputs); + + double calc_T_penalty(double target, double calc, double span); + + double calc_objective(const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, const C_sco2_htrbp_core& htrbp_core); + +protected: + + +public: + + C_HTRBypass_Cycle(C_sco2_cycle_core::E_turbo_gen_motor_config turbo_gen_motor_config, + double eta_generator, + double T_mc_in, + double W_dot_net, + double T_t_in, double P_high_limit, + std::vector DP_LTR, std::vector DP_HTR, + std::vector DP_PC_main, std::vector DP_PHX, + int LTR_N_sub_hxrs, int HTR_N_sub_hxrs, + double eta_mc, int mc_comp_model_code, + double eta_rc, + double eta_t, double N_turbine, + double frac_fan_power, double eta_fan, double deltaP_cooler_frac, + int N_nodes_pass, + double T_amb_des, double elevation, + double yr_inflation) : + C_sco2_cycle_core(turbo_gen_motor_config, + eta_generator, + T_mc_in, + W_dot_net, + T_t_in, P_high_limit, + DP_LTR, DP_HTR, + DP_PC_main, DP_PHX, + LTR_N_sub_hxrs, HTR_N_sub_hxrs, + eta_mc, mc_comp_model_code, + eta_rc, + eta_t, N_turbine, + frac_fan_power, eta_fan, deltaP_cooler_frac, + N_nodes_pass, + T_amb_des, elevation, + yr_inflation) + { + m_T_target = m_T_HTF_PHX_inlet = m_set_HTF_mdot + = m_HTF_PHX_cold_approach = m_dT_BP + = m_cp_HTF = m_opt_obj_internal_only + = std::numeric_limits::quiet_NaN(); + + m_T_target_is_HTF = -1; + m_opt_iteration_count = 0; + + m_is_bp_par_set = false; + } + + ~C_HTRBypass_Cycle() {}; + + // Set Bypass Specific Parameters + void set_bp_par(double T_htf_phx_in, double T_target, double cp_htf, double dT_bp, + double htf_phx_cold_approach, double set_HTF_mdot, int T_target_is_HTF) + { + m_T_HTF_PHX_inlet = T_htf_phx_in; // K + m_T_target = T_target; // K + m_T_target_is_HTF = T_target_is_HTF; + m_cp_HTF = cp_htf; // kJ/kg K + m_dT_BP = dT_bp; + m_HTF_PHX_cold_approach = htf_phx_cold_approach; + m_set_HTF_mdot = set_HTF_mdot; + + m_is_bp_par_set = true; + } + + // Overridden - Optimize Cycle (fixed total UA) + int auto_opt_design(S_auto_opt_design_parameters& auto_opt_des_par_in); + + // Overridden - Optimize Cycle for target eta (variable total UA) + int auto_opt_design_hit_eta(S_auto_opt_design_hit_eta_parameters& auto_opt_des_hit_eta_in, std::string& error_msg); + + // Objective Functions (internal use only) + double optimize_totalUA_return_objective_metric(const std::vector& x, const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par); + double optimize_bp_return_objective_metric(const std::vector& x, const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, C_sco2_htrbp_core& htrbp_core); + double optimize_nonbp_return_objective_metric(const std::vector& x, const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, C_sco2_htrbp_core& htrbp_core); + + // Off Design + + void reset_ms_od_turbo_bal_csp_solved(); + + int off_design_fix_shaft_speeds(S_od_par& od_phi_par_in, double od_tol /*-*/); + + virtual int solve_OD_all_coolers_fan_power(double T_amb /*K*/, double od_tol /*-*/, double& W_dot_fan /*MWe*/); + + virtual int solve_OD_mc_cooler_fan_power(double T_amb /*K*/, double od_tol /*-*/, double& W_dot_mc_cooler_fan /*MWe*/, double& P_co2_out /*kPa*/); + + virtual int solve_OD_pc_cooler_fan_power(double T_amb /*K*/, double od_tol /*-*/, double& W_dot_pc_cooler_fan /*MWe*/, double& P_co2_out /*kPa*/); + + double get_od_temp(int n_state_point); + + double get_od_pres(int n_state_point); + + virtual void check_od_solution(double& diff_m_dot, double& diff_E_cycle, + double& diff_Q_LTR, double& diff_Q_HTR); + + void set_od_temp(int n_state_point, double temp_K); + + void set_od_pres(int n_state_point, double pres_kPa); + + void off_design_recompressor(double T_in, double P_in, double m_dot, double P_out, double tol /*-*/, int& error_code, double& T_out); + + void estimate_od_turbo_operation(double T_mc_in /*K*/, double P_mc_in /*kPa*/, double f_recomp /*-*/, double T_t_in /*K*/, double phi_mc /*-*/, + int& mc_error_code, double& mc_w_tip_ratio /*-*/, double& P_mc_out /*kPa*/, + int& rc_error_code, double& rc_w_tip_ratio /*-*/, double& rc_phi /*-*/, + bool is_update_ms_od_solved = false); + + const C_comp_multi_stage::S_od_solved* get_rc_od_solved() + { + return m_optimal_htrbp_core.m_outputs.m_rc_ms.get_od_solved(); + //return m_rc_ms.get_od_solved(); + } + + /*const S_od_turbo_bal_csp_solved* get_od_turbo_bal_csp_solved() + { + return &ms_od_turbo_bal_csp_solved; + } + + double get_max_target() + { + return m_biggest_target; + }*/ + + + +}; + +// Nlopt objective functions +double nlopt_optimize_totalUA_func(const std::vector& x, std::vector& grad, void* data); + +double nlopt_optimize_bp_func(const std::vector& x, std::vector& grad, void* data); + +double nlopt_optimize_nonbp_func(const std::vector& x, std::vector& grad, void* data); + + + + +// Penalty value methods +double sigmoid(const double val); + +double logit(const double val); + +#endif diff --git a/tcs/sco2_partialcooling_cycle.cpp b/tcs/sco2_partialcooling_cycle.cpp index 35bc22b86..843811505 100644 --- a/tcs/sco2_partialcooling_cycle.cpp +++ b/tcs/sco2_partialcooling_cycle.cpp @@ -63,8 +63,8 @@ int C_PartialCooling_Cycle::design_core() } // Initialize Recuperators - mc_LTR.initialize(m_LTR_N_sub_hxrs, ms_des_par.m_LTR_od_UA_target_type); - mc_HTR.initialize(m_HTR_N_sub_hxrs, ms_des_par.m_HTR_od_UA_target_type); + mc_LTR.initialize(m_LTR_N_sub_hxrs, ms_des_par.m_LTR_od_UA_target_type, m_yr_inflation); + mc_HTR.initialize(m_HTR_N_sub_hxrs, ms_des_par.m_HTR_od_UA_target_type, m_yr_inflation); // Initialize known temps and pressures from design parameters m_temp_last[MC_IN] = m_T_mc_in; //[K] @@ -214,6 +214,14 @@ int C_PartialCooling_Cycle::design_core() if (comp_error_code != 0) return comp_error_code; } + else + { + // No recompressor, so set all RC_OUT equal to PC_OUT + m_temp_last[RC_OUT] = m_temp_last[PC_OUT]; + m_dens_last[RC_OUT] = m_dens_last[PC_OUT]; + m_enth_last[RC_OUT] = m_enth_last[PC_OUT]; + m_entr_last[RC_OUT] = m_entr_last[PC_OUT]; + } double w_t = std::numeric_limits::quiet_NaN(); calculate_turbomachinery_outlet_1(m_temp_last[TURB_IN], m_pres_last[TURB_IN], m_pres_last[TURB_OUT], eta_t_isen, false, @@ -308,8 +316,11 @@ int C_PartialCooling_Cycle::design_core() { double phx_deltaT = m_temp_last[TURB_IN] - m_temp_last[HTR_HP_OUT]; double under_min_deltaT = std::max(0.0, ms_des_par.m_min_phx_deltaT - phx_deltaT); - double eta_deltaT_scale = std::exp(-under_min_deltaT); - m_objective_metric_last = m_eta_thermal_calc_last * eta_deltaT_scale; + //double eta_deltaT_scale = std::exp(-under_min_deltaT); + //m_objective_metric_last = m_eta_thermal_calc_last * eta_deltaT_scale; + + double percent_err = under_min_deltaT / ms_des_par.m_min_phx_deltaT; + m_objective_metric_last = m_eta_thermal_calc_last - percent_err; } else { @@ -522,7 +533,8 @@ int C_PartialCooling_Cycle::finalize_design() m_pres_last[MC_IN], m_m_dot_mc, m_temp_last[MC_OUT], - m_pres_last[MC_OUT], ms_des_par.m_des_tol); + m_pres_last[MC_OUT], ms_des_par.m_des_tol, + m_yr_inflation); if (mc_des_err != 0) { @@ -533,7 +545,8 @@ int C_PartialCooling_Cycle::finalize_design() m_pres_last[PC_IN], m_m_dot_pc, m_temp_last[PC_OUT], - m_pres_last[PC_OUT], ms_des_par.m_des_tol); + m_pres_last[PC_OUT], ms_des_par.m_des_tol, + m_yr_inflation); if (pc_des_err != 0) { @@ -546,7 +559,8 @@ int C_PartialCooling_Cycle::finalize_design() m_pres_last[PC_OUT], m_m_dot_rc, m_temp_last[RC_OUT], - m_pres_last[RC_OUT], ms_des_par.m_des_tol); + m_pres_last[RC_OUT], ms_des_par.m_des_tol, + m_yr_inflation); if (rc_des_err != 0) { @@ -573,6 +587,7 @@ int C_PartialCooling_Cycle::finalize_design() t_des_par.m_h_out = m_enth_last[TURB_OUT]; //[kJ/kg] // Mass flow t_des_par.m_m_dot = m_m_dot_t; //[kg/s] + t_des_par.m_yr_inflation = m_yr_inflation; int turb_size_err = 0; mc_t.turbine_sizing(t_des_par, turb_size_err); @@ -610,11 +625,20 @@ int C_PartialCooling_Cycle::finalize_design() s_LP_air_cooler_des_par_ind.m_elev = m_elevation; //[m] s_LP_air_cooler_des_par_ind.m_eta_fan = m_eta_fan; //[-] s_LP_air_cooler_des_par_ind.m_N_nodes_pass = m_N_nodes_pass; //[-] + s_LP_air_cooler_des_par_ind.m_yr_inflation = m_yr_inflation; //[yr] if (ms_des_par.m_is_des_air_cooler && std::isfinite(m_deltaP_cooler_frac) && std::isfinite(m_frac_fan_power) && std::isfinite(m_T_amb_des) && std::isfinite(m_elevation) && std::isfinite(m_eta_fan) && m_N_nodes_pass > 0) { - mc_pc_air_cooler.design_hx(s_LP_air_cooler_des_par_ind, s_LP_air_cooler_des_par_dep, ms_des_par.m_des_tol); + try + { + mc_pc_air_cooler.design_hx(s_LP_air_cooler_des_par_ind, s_LP_air_cooler_des_par_dep, ms_des_par.m_des_tol); + } + catch (...) + { + ms_des_solved.m_eta_thermal = m_eta_thermal_calc_last; + return C_sco2_cycle_core::E_cycle_error_msg::E_AIR_COOLER_CONVERGENCE; + } } // High Pressure @@ -640,11 +664,20 @@ int C_PartialCooling_Cycle::finalize_design() s_IP_air_cooler_des_par_ind.m_elev = m_elevation; //[m] s_IP_air_cooler_des_par_ind.m_eta_fan = m_eta_fan; //[-] s_IP_air_cooler_des_par_ind.m_N_nodes_pass = m_N_nodes_pass; //[-] + s_IP_air_cooler_des_par_ind.m_yr_inflation = m_yr_inflation; //[yr] if (ms_des_par.m_is_des_air_cooler && std::isfinite(m_deltaP_cooler_frac) && std::isfinite(m_frac_fan_power) && std::isfinite(m_T_amb_des) && std::isfinite(m_elevation) && std::isfinite(m_eta_fan) && m_N_nodes_pass > 0) { - mc_mc_air_cooler.design_hx(s_IP_air_cooler_des_par_ind, s_IP_air_cooler_des_par_dep, ms_des_par.m_des_tol); + try + { + mc_mc_air_cooler.design_hx(s_IP_air_cooler_des_par_ind, s_IP_air_cooler_des_par_dep, ms_des_par.m_des_tol); + } + catch (...) + { + ms_des_solved.m_eta_thermal = m_eta_thermal_calc_last; + return C_sco2_cycle_core::E_cycle_error_msg::E_AIR_COOLER_CONVERGENCE; + } } @@ -891,6 +924,7 @@ int C_PartialCooling_Cycle::opt_design_core() opt_des_cycle.set_upper_bounds(ub); opt_des_cycle.set_initial_step(scale); opt_des_cycle.set_xtol_rel(ms_opt_des_par.m_des_opt_tol); + //opt_des_cycle.set_maxeval(10); // set max objective function opt_des_cycle.set_max_objective(nlopt_cb_opt_partialcooling_des, this); @@ -944,6 +978,13 @@ int C_PartialCooling_Cycle::opt_design(S_opt_des_params & opt_des_par_in) if (opt_des_err_code != 0) return opt_des_err_code; + // Check if cycle is below eta cutoff value + if (m_eta_thermal_calc_last < ms_auto_opt_des_par.m_eta_thermal_cutoff) + { + ms_des_solved.m_eta_thermal = m_eta_thermal_calc_last; + return C_sco2_cycle_core::E_cycle_error_msg::E_ETA_THRESHOLD; + } + return finalize_design(); } @@ -1041,7 +1082,7 @@ int C_PartialCooling_Cycle::auto_opt_design_core() // Is recompression fraction fixed or optimized? - if (ms_auto_opt_des_par.m_is_recomp_ok < 0.0) + if (ms_auto_opt_des_par.m_is_recomp_ok <= 0.0) { ms_opt_des_par.m_recomp_frac_guess = std::abs(ms_auto_opt_des_par.m_is_recomp_ok); //[-] ms_opt_des_par.m_fixed_recomp_frac = true; @@ -1077,7 +1118,15 @@ int C_PartialCooling_Cycle::auto_opt_design_core() return pc_opt_des_error_code; } - pc_opt_des_error_code = finalize_design(); + + // Check if cycle is below eta cutoff value + if (m_eta_thermal_calc_last < ms_auto_opt_des_par.m_eta_thermal_cutoff) + { + ms_des_solved.m_eta_thermal = m_eta_thermal_calc_last; + return C_sco2_cycle_core::E_cycle_error_msg::E_ETA_THRESHOLD; + } + + pc_opt_des_error_code = finalize_design(); return pc_opt_des_error_code; } diff --git a/tcs/sco2_partialcooling_cycle.h b/tcs/sco2_partialcooling_cycle.h index 2636cb799..97d46c89e 100644 --- a/tcs/sco2_partialcooling_cycle.h +++ b/tcs/sco2_partialcooling_cycle.h @@ -270,7 +270,8 @@ class C_PartialCooling_Cycle : public C_sco2_cycle_core double eta_t /*-*/, double N_turbine /*rpm*/, double frac_fan_power /*-*/, double eta_fan /*-*/, double deltaP_cooler_frac /*-*/, int N_nodes_pass /*-*/, - double T_amb_des /*K*/, double elevation /*m*/) : + double T_amb_des /*K*/, double elevation /*m*/, + double yr_inflation /*yr*/) : C_sco2_cycle_core(turbo_gen_motor_config, eta_generator, T_mc_in, @@ -284,7 +285,8 @@ class C_PartialCooling_Cycle : public C_sco2_cycle_core eta_t, N_turbine, frac_fan_power, eta_fan, deltaP_cooler_frac, N_nodes_pass, - T_amb_des, elevation) + T_amb_des, elevation, + yr_inflation) { m_temp_last.resize(END_SCO2_STATES); std::fill(m_temp_last.begin(), m_temp_last.end(), std::numeric_limits::quiet_NaN()); diff --git a/tcs/sco2_pc_csp_int.cpp b/tcs/sco2_pc_csp_int.cpp index 5e840da7b..beb6a6d67 100644 --- a/tcs/sco2_pc_csp_int.cpp +++ b/tcs/sco2_pc_csp_int.cpp @@ -35,6 +35,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //#include "sco2_pc_core.h" #include "sco2_recompression_cycle.h" #include "sco2_partialcooling_cycle.h" +#include "sco2_htrbypass_cycle.h" +#include "sco2_turbinesplitflow_cycle.h" #include "csp_solver_util.h" #include "CO2_properties.h" @@ -60,11 +62,11 @@ C_sco2_phx_air_cooler::C_sco2_phx_air_cooler() mp_mf_update = 0; // NULL } -void C_sco2_phx_air_cooler::design(S_des_par des_par) +int C_sco2_phx_air_cooler::design(S_des_par des_par) { ms_des_par = des_par; - design_core(); + return design_core(); } void C_sco2_phx_air_cooler::C_P_LP_in_iter_tracker::reset_vectors() @@ -86,7 +88,7 @@ void C_sco2_phx_air_cooler::C_P_LP_in_iter_tracker::push_back_vectors(double P_L mv_is_converged.push_back(is_converged); //[-] } -void C_sco2_phx_air_cooler::design_core() +int C_sco2_phx_air_cooler::design_core() { // using -> C_RecompCycle::S_auto_opt_design_hit_eta_parameters std::string error_msg; @@ -120,15 +122,16 @@ void C_sco2_phx_air_cooler::design_core() ms_des_par.m_eta_t, ms_des_par.m_N_turbine, ms_des_par.m_frac_fan_power, ms_des_par.m_eta_fan, ms_des_par.m_deltaP_cooler_frac, ms_des_par.m_N_nodes_pass, - ms_des_par.m_T_amb_des, ms_des_par.m_elevation)); + ms_des_par.m_T_amb_des, ms_des_par.m_elevation, + ms_des_par.m_yr_inflation)); s_cycle_config = "partial cooling"; mpc_sco2_cycle = std::move(c_pc_cycle); } - else + else if(ms_des_par.m_cycle_config == 3) { - std::shared_ptr c_rc_cycle = std::unique_ptr(new C_RecompCycle( + std::unique_ptr c_bp_cycle = std::unique_ptr(new C_HTRBypass_Cycle( turbo_gen_motor_config, eta_generator, T_mc_in, @@ -142,12 +145,112 @@ void C_sco2_phx_air_cooler::design_core() ms_des_par.m_eta_t, ms_des_par.m_N_turbine, ms_des_par.m_frac_fan_power, ms_des_par.m_eta_fan, ms_des_par.m_deltaP_cooler_frac, ms_des_par.m_N_nodes_pass, - ms_des_par.m_T_amb_des, ms_des_par.m_elevation)); + ms_des_par.m_T_amb_des, ms_des_par.m_elevation, + ms_des_par.m_yr_inflation)); - s_cycle_config = "recompression"; + s_cycle_config = "htr bypass"; - mpc_sco2_cycle = std::move(c_rc_cycle); + // Get and Set HTF Parameters + { + int htf_code = ms_des_par.m_hot_fl_code; + util::matrix_t htf_user_props = ms_des_par.mc_hot_fl_props; + HTFProperties htf_props; + + if (htf_code != HTFProperties::User_defined && htf_code < HTFProperties::End_Library_Fluids) + { + if (!htf_props.SetFluid(htf_code, true)) + { + throw(C_csp_exception("Hot fluid code is not recognized", "C_HX_co2_to_htf::initialization")); + } + } + else if (htf_code == HTFProperties::User_defined) + { + int n_rows = (int)htf_user_props.nrows(); + int n_cols = (int)htf_user_props.ncols(); + if (n_rows > 2 && n_cols == 7) + { + if (!htf_props.SetUserDefinedFluid(htf_user_props, true)) + { + std::string error_msg = util::format(htf_props.UserFluidErrMessage(), n_rows, n_cols); + throw(C_csp_exception(error_msg, "C_HX_co2_to_htf::initialization")); + } + } + else + { + std::string error_msg = util::format("The user defined hot fluid table must contain at least 3 rows and exactly 7 columns. The current table contains %d row(s) and %d column(s)", n_rows, n_cols); + throw(C_csp_exception(error_msg, "C_HX_co2_to_htf::initialization")); + } + } + else + { + throw(C_csp_exception("Hot fluid code is not recognized", "C_HX_co2_to_htf::initialization")); + } + + double HTF_PHX_inlet = ms_des_par.m_T_htf_hot_in; + double cp_htf = htf_props.Cp(HTF_PHX_inlet); + double T_target = ms_des_par.m_T_bypass_target; + double T_target_is_htf = ms_des_par.m_T_target_is_HTF; + double deltaT_bp = ms_des_par.m_deltaT_bypass; + double HTF_PHX_cold_approach = ms_des_par.m_phx_dt_cold_approach; + double set_HTF_mdot = ms_des_par.m_set_HTF_mdot; + + c_bp_cycle->set_bp_par(HTF_PHX_inlet, T_target, cp_htf, deltaT_bp, HTF_PHX_cold_approach, set_HTF_mdot, T_target_is_htf); + + } + + + mpc_sco2_cycle = std::move(c_bp_cycle); } + else if (ms_des_par.m_cycle_config == 4) + { + std::unique_ptr c_tsf_cycle = std::unique_ptr(new C_TurbineSplitFlow_Cycle( + turbo_gen_motor_config, + eta_generator, + T_mc_in, + ms_des_par.m_W_dot_net, + T_t_in, ms_des_par.m_P_high_limit, + ms_des_par.m_DP_LT, ms_des_par.m_DP_HT, + ms_des_par.m_DP_PC, ms_des_par.m_DP_PHX, + ms_des_par.m_LTR_N_sub_hxrs, ms_des_par.m_HTR_N_sub_hxrs, + ms_des_par.m_eta_mc, ms_des_par.m_mc_comp_type, + ms_des_par.m_eta_rc, + ms_des_par.m_eta_t, ms_des_par.m_eta_t2, + ms_des_par.m_N_turbine, + ms_des_par.m_frac_fan_power, ms_des_par.m_eta_fan, ms_des_par.m_deltaP_cooler_frac, + ms_des_par.m_N_nodes_pass, + ms_des_par.m_T_amb_des, ms_des_par.m_elevation, + ms_des_par.m_yr_inflation)); + + s_cycle_config = "turbine split flow"; + + + mpc_sco2_cycle = std::move(c_tsf_cycle); + } + else + { + std::unique_ptr c_rc_cycle = std::unique_ptr(new C_RecompCycle( + turbo_gen_motor_config, + eta_generator, + T_mc_in, + ms_des_par.m_W_dot_net, + T_t_in, ms_des_par.m_P_high_limit, + ms_des_par.m_DP_LT, ms_des_par.m_DP_HT, + ms_des_par.m_DP_PC, ms_des_par.m_DP_PHX, + ms_des_par.m_LTR_N_sub_hxrs, ms_des_par.m_HTR_N_sub_hxrs, + ms_des_par.m_eta_mc, ms_des_par.m_mc_comp_type, + ms_des_par.m_eta_rc, + ms_des_par.m_eta_t, ms_des_par.m_N_turbine, + ms_des_par.m_frac_fan_power, ms_des_par.m_eta_fan, ms_des_par.m_deltaP_cooler_frac, + ms_des_par.m_N_nodes_pass, + ms_des_par.m_T_amb_des, ms_des_par.m_elevation, + ms_des_par.m_yr_inflation)); + + s_cycle_config = "recompression"; + + mpc_sco2_cycle = std::move(c_rc_cycle); + } + + // Set min temp m_T_mc_in_min = mpc_sco2_cycle->get_design_limits().m_T_mc_in_min; //[K] @@ -186,12 +289,15 @@ void C_sco2_phx_air_cooler::design_core() ms_cycle_des_par.m_des_tol = ms_des_par.m_des_tol; ms_cycle_des_par.m_des_opt_tol = ms_des_par.m_des_opt_tol; ms_cycle_des_par.m_is_recomp_ok = ms_des_par.m_is_recomp_ok; + ms_cycle_des_par.m_is_bypass_ok = ms_des_par.m_is_bypass_ok; ms_cycle_des_par.m_is_des_air_cooler = ms_des_par.m_is_des_air_cooler; //[-] ms_cycle_des_par.m_des_objective_type = ms_des_par.m_des_objective_type; //[-] ms_cycle_des_par.m_min_phx_deltaT = ms_des_par.m_min_phx_deltaT; //[C] + ms_cycle_des_par.m_eta_thermal_cutoff = ms_des_par.m_eta_thermal_cutoff; //[] + ms_cycle_des_par.m_fixed_P_mc_out = ms_des_par.m_fixed_P_mc_out; //[-] ms_cycle_des_par.m_PR_HP_to_LP_guess = ms_des_par.m_PR_HP_to_LP_guess; //[-] @@ -261,6 +367,10 @@ void C_sco2_phx_air_cooler::design_core() des_params.m_fixed_f_PR_HP_to_IP = ms_des_par.m_fixed_f_PR_HP_to_IP; //[-] des_params.m_is_recomp_ok = ms_des_par.m_is_recomp_ok; + des_params.m_is_bypass_ok = ms_des_par.m_is_bypass_ok; + des_params.m_is_turbinesplit_ok = ms_des_par.m_is_turbine_split_ok; + + des_params.m_eta_thermal_cutoff = ms_des_par.m_eta_thermal_cutoff; auto_err_code = mpc_sco2_cycle->auto_opt_design(des_params); } @@ -273,7 +383,17 @@ void C_sco2_phx_air_cooler::design_core() if (auto_err_code != 0) { - throw(C_csp_exception(error_msg.c_str())); + // Check if error code is handled (need to report out) + if (auto_err_code >= (int)C_sco2_cycle_core::E_cycle_error_msg::E_CANNOT_PRODUCE_POWER + && auto_err_code < (int)C_sco2_cycle_core::E_cycle_error_msg::E_NO_ERROR) + { + ms_des_solved.ms_rc_cycle_solved = *mpc_sco2_cycle->get_design_solved(); + return auto_err_code; + } + + // Unhandled exception + else + throw(C_csp_exception(error_msg.c_str())); } if (error_msg.empty()) @@ -289,31 +409,103 @@ void C_sco2_phx_air_cooler::design_core() ms_des_solved.ms_rc_cycle_solved = *mpc_sco2_cycle->get_design_solved(); // Initialize the PHX - mc_phx.initialize(ms_des_par.m_hot_fl_code, ms_des_par.mc_hot_fl_props, ms_des_par.m_phx_N_sub_hx, ms_des_par.m_phx_od_UA_target_type); + mc_phx.initialize(ms_des_par.m_hot_fl_code, ms_des_par.mc_hot_fl_props, ms_des_par.m_phx_N_sub_hx, + ms_des_par.m_phx_od_UA_target_type, ms_des_par.m_yr_inflation); + + // Define state enumerable for sco2 into PHX + int phx_cold_inlet_state = C_sco2_cycle_core::HTR_HP_OUT; + if (ms_des_par.m_cycle_config == 3) + phx_cold_inlet_state = C_sco2_cycle_core::MIXER2_OUT; + else if (ms_des_par.m_cycle_config == 4) + phx_cold_inlet_state = C_sco2_cycle_core::LTR_HP_OUT; + // Design the PHX - double q_dot_des_phx = ms_des_solved.ms_rc_cycle_solved.m_W_dot_net / ms_des_solved.ms_rc_cycle_solved.m_eta_thermal; + + // Calculate q_dot_phx using sco2 enthalpies and mass flow (using thermal efficiency does not work for htr bypass) + double q_dot_des_phx_old = ms_des_solved.ms_rc_cycle_solved.m_W_dot_net / ms_des_solved.ms_rc_cycle_solved.m_eta_thermal; + double q_dot_des_phx = ms_des_solved.ms_rc_cycle_solved.m_m_dot_t + * (ms_des_solved.ms_rc_cycle_solved.m_enth[C_sco2_cycle_core::TURB_IN] + - ms_des_solved.ms_rc_cycle_solved.m_enth[phx_cold_inlet_state]); + //ms_phx_des_par.m_Q_dot_design = ms_des_solved.ms_rc_cycle_solved.m_W_dot_net / ms_des_solved.ms_rc_cycle_solved.m_eta_thermal; //[kWt] ms_phx_des_par.m_T_h_in = ms_des_par.m_T_htf_hot_in; //[K] HTF hot inlet temperature // Okay, but CO2-HTF HX is assumed here. How does "structure inheritance" work? ms_phx_des_par.m_P_h_in = 1.0; // Assuming HTF is incompressible... ms_phx_des_par.m_P_h_out = 1.0; // Assuming HTF is incompressible... // ................................................................................. - ms_phx_des_par.m_T_c_in = ms_des_solved.ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::HTR_HP_OUT]; //[K] - ms_phx_des_par.m_P_c_in = ms_des_solved.ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::HTR_HP_OUT]; //[K] - ms_phx_des_par.m_P_c_out = ms_des_solved.ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::TURB_IN]; //[K] - ms_phx_des_par.m_m_dot_cold_des = ms_des_solved.ms_rc_cycle_solved.m_m_dot_t; //[kg/s] + + + ms_phx_des_par.m_T_c_in = ms_des_solved.ms_rc_cycle_solved.m_temp[phx_cold_inlet_state]; //[K] + ms_phx_des_par.m_P_c_in = ms_des_solved.ms_rc_cycle_solved.m_pres[phx_cold_inlet_state]; //[K] + ms_phx_des_par.m_P_c_out = ms_des_solved.ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::TURB_IN]; //[K] + ms_phx_des_par.m_m_dot_cold_des = ms_des_solved.ms_rc_cycle_solved.m_m_dot_t; //[kg/s] // Calculating the HTF mass flow rate in 'design_and_calc_m_dot_htf' ms_phx_des_par.m_m_dot_hot_des = std::numeric_limits::quiet_NaN(); // Set maximum effectiveness ms_phx_des_par.m_eff_max = 1.0; - + mc_phx.design_and_calc_m_dot_htf(ms_phx_des_par, q_dot_des_phx, ms_des_par.m_phx_dt_cold_approach, ms_des_solved.ms_phx_des_solved); //************************************************************************************* //************************************************************************************* - return; + // Solve the Bypass HX (if necessary) + if (ms_des_par.m_cycle_config == 3) + { + // hard coded + int bp_N_subs_hx = ms_des_par.m_phx_N_sub_hx; + auto bp_od_UA_target_type = ms_des_par.m_phx_od_UA_target_type; + + mc_bp.initialize(ms_des_par.m_hot_fl_code, ms_des_par.mc_hot_fl_props, bp_N_subs_hx, bp_od_UA_target_type, + ms_des_par.m_yr_inflation); + + // Calculate BP heat transfer + double m_dot_bp_sco2 = ms_des_solved.ms_rc_cycle_solved.m_bypass_frac * (ms_des_solved.ms_rc_cycle_solved.m_m_dot_t); + double q_dot_des_bp = m_dot_bp_sco2 * (ms_des_solved.ms_rc_cycle_solved.m_enth[C_sco2_cycle_core::BYPASS_OUT] + - ms_des_solved.ms_rc_cycle_solved.m_enth[C_sco2_cycle_core::MIXER_OUT]); + + // Hot Parameters (HTF) + ms_bp_des_par.m_T_h_in = mc_phx.ms_des_solved.m_T_h_out; // [K] Inlet to Bypass is outlet of PHX + ms_bp_des_par.m_P_h_in = 1.0; // Assuming HTF is incompressible... + ms_bp_des_par.m_P_h_out = 1.0; // Assuming HTF is incompressible... + + // Cold Parameters (sco2) + ms_bp_des_par.m_T_c_in = ms_des_solved.ms_rc_cycle_solved.m_temp[C_sco2_cycle_core::MIXER_OUT]; //[K] + ms_bp_des_par.m_P_c_in = ms_des_solved.ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::MIXER_OUT]; //[K] + ms_bp_des_par.m_P_c_out = ms_des_solved.ms_rc_cycle_solved.m_pres[C_sco2_cycle_core::BYPASS_OUT]; //[K] + ms_bp_des_par.m_m_dot_cold_des = m_dot_bp_sco2; //[kg/s] + + // Calculating the HTF mass flow rate in 'design_and_calc_m_dot_htf' + ms_bp_des_par.m_m_dot_hot_des = std::numeric_limits::quiet_NaN(); + + // Set maximum effectiveness + ms_bp_des_par.m_eff_max = 1.0; + + // Set Mass Flow (it is known because it is equal to PHX) + ms_bp_des_par.m_m_dot_hot_des = ms_phx_des_par.m_m_dot_hot_des; + + + + // Design + if (ms_bp_des_par.m_m_dot_cold_des > 0 && q_dot_des_bp > 1e-2) + { + mc_bp.design_calc_UA(ms_bp_des_par, q_dot_des_bp, ms_des_solved.ms_bp_des_solved); + } + // No Mass Flow + else + { + ms_des_solved.ms_bp_des_solved.m_T_h_out = mc_phx.ms_des_solved.m_T_h_out; + ms_des_solved.ms_bp_des_solved.m_cost_bare_erected = 0; + ms_des_solved.ms_bp_des_solved.m_cost_equipment = 0; + ms_des_solved.ms_bp_des_solved.m_UA_design = 0; + ms_des_solved.ms_bp_des_solved.m_eff_design = 0; + ms_des_solved.ms_bp_des_solved.m_min_DT_design = 0; + ms_des_solved.ms_bp_des_solved.m_Q_dot_design = 0; + } + } + + return auto_err_code; } diff --git a/tcs/sco2_pc_csp_int.h b/tcs/sco2_pc_csp_int.h index c08d27684..a172b5e76 100644 --- a/tcs/sco2_pc_csp_int.h +++ b/tcs/sco2_pc_csp_int.h @@ -66,7 +66,8 @@ class C_sco2_phx_air_cooler double m_eta_thermal; //[-] Cycle thermal efficiency double m_UA_recup_tot_des; //[kW/K] Total recuperator conductance int m_cycle_config; //[-] 2 = partial cooling, [else] = recompression - + double m_eta_thermal_cutoff; //[] Minimum eta to fully design cycle (returns failure below value) + // Cycle design parameters std::vector m_DP_LT; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) std::vector m_DP_HT; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) @@ -94,12 +95,15 @@ class C_sco2_phx_air_cooler double m_eta_rc; //[-] design-point efficiency of the recompressor; isentropic if positive, polytropic if negative double m_eta_pc; //[-] design-point efficiency of the precompressor; isentropic if positive, polytropic if negative double m_eta_t; //[-] design-point efficiency of the turbine; isentropic if positive, polytropic if negative - double m_P_high_limit; //[kPa] maximum allowable pressure in cycle + double m_eta_t2; //[-] design-point efficiency of the secondary turbine (TSF ONLY); isentropic if positive, polytropic if negative + double m_P_high_limit; //[kPa] maximum allowable pressure in cycle double m_des_tol; //[-] Design point convergence tolerance double m_des_opt_tol; //[-] Optimization tolerance double m_N_turbine; //[rpm] Turbine shaft speed (negative values link turbine to compressor) double m_is_recomp_ok; //[-] 1 = Yes, 0 = simple cycle only, < 0 = fix f_recomp to abs(input) - + double m_is_bypass_ok; //[-] 1 = Yes, 0 = no bypass, < 0 = fix bp_frac to abs(input) + double m_is_turbine_split_ok; //[-] 1 = Yes, 0 = No Second Turbine, < 0 = fix split_frac to abs(input) + int m_des_objective_type; //[2] = min phx deltat then max eta, [else] max eta double m_min_phx_deltaT; //[C] @@ -124,6 +128,16 @@ class C_sco2_phx_air_cooler double m_eta_fan; //[-] Fan isentropic efficiency int m_N_nodes_pass; //[-] Number of nodes per pass + + // Bypass Configuration Parameters + double m_T_bypass_target; // [K] Bypass Target Temperature + int m_T_target_is_HTF; // (1) BP Target is HTF temp, (0) Target is sco2 at cold bypass + double m_deltaT_bypass; // [delta K] sco2 Bypass Outlet Temp - HTR_HP_OUT Temp + double m_set_HTF_mdot; // [kg/s] For HTR Bypass ONLY, 0 = calculate HTF mdot (need to set dT_PHX_cold_approach), > 0 = HTF mdot kg/s + + // Inflation target year + double m_yr_inflation; // [yr] Inflation target year + S_des_par() { m_hot_fl_code = m_design_method = m_LTR_N_sub_hxrs = m_LTR_N_sub_hxrs = m_phx_N_sub_hx = -1; @@ -154,13 +168,14 @@ class C_sco2_phx_air_cooler m_LTR_UA = m_LTR_min_dT = m_LTR_eff_target = m_LTR_eff_max = m_HTR_UA = m_HTR_min_dT = m_HTR_eff_target = m_HTR_eff_max = - m_eta_mc = m_eta_rc = m_eta_pc = m_eta_t = + m_eta_mc = m_eta_rc = m_eta_pc = m_eta_t = m_eta_t2 = m_P_high_limit = m_des_tol = m_des_opt_tol = m_N_turbine = - m_is_recomp_ok = + m_is_recomp_ok = m_is_turbine_split_ok = m_PR_HP_to_LP_guess = m_f_PR_HP_to_IP_guess = m_phx_dt_cold_approach = m_frac_fan_power = m_deltaP_cooler_frac = m_eta_fan = + m_yr_inflation = std::numeric_limits::quiet_NaN(); m_fixed_P_mc_out = false; //[-] If false, then should default to optimizing this parameter @@ -174,6 +189,7 @@ class C_sco2_phx_air_cooler { C_HX_counterflow_CRM::S_des_solved ms_phx_des_solved; C_sco2_cycle_core::S_design_solved ms_rc_cycle_solved; + C_HX_counterflow_CRM::S_des_solved ms_bp_des_solved; }; struct S_od_par @@ -362,11 +378,14 @@ class C_sco2_phx_air_cooler std::shared_ptr mpc_sco2_cycle; C_HX_co2_to_htf mc_phx; + C_HX_co2_to_htf mc_bp; // Bypass Heat Exchanger S_des_par ms_des_par; C_sco2_cycle_core::S_auto_opt_design_hit_eta_parameters ms_cycle_des_par; C_HX_counterflow_CRM::S_des_calc_UA_par ms_phx_des_par; - + + C_HX_counterflow_CRM::S_des_calc_UA_par ms_bp_des_par; // Bypass HX Design Parameters + S_des_solved ms_des_solved; S_od_par ms_od_par; @@ -388,7 +407,7 @@ class C_sco2_phx_air_cooler double m_T_co2_crit; //[K] double m_P_co2_crit; //[kPa] - void design_core(); + int design_core(); double adjust_P_mc_in_away_2phase(double T_co2 /*K*/, double P_mc_in /*kPa*/); @@ -613,7 +632,7 @@ class C_sco2_phx_air_cooler util::matrix_t & T_htf_ind, util::matrix_t & T_amb_ind, util::matrix_t & m_dot_htf_ND_ind, double od_opt_tol /*-*/, double od_tol /*-*/); - void design(S_des_par des_par); + int design(S_des_par des_par); int off_design__constant_N__calc_max_htf_massflow__T_mc_in_P_LP_in__objective(C_sco2_phx_air_cooler::S_od_par od_par, bool is_rc_N_od_at_design, double rc_N_od_f_des /*-*/, diff --git a/tcs/sco2_recompression_cycle.cpp b/tcs/sco2_recompression_cycle.cpp index fe18b5aad..2a747ed1d 100644 --- a/tcs/sco2_recompression_cycle.cpp +++ b/tcs/sco2_recompression_cycle.cpp @@ -1851,9 +1851,9 @@ void C_RecompCycle::design_core_standard(int & error_code) // Initialize Recuperators // LTR - mc_LT_recup.initialize(m_LTR_N_sub_hxrs, ms_des_par.m_LTR_od_UA_target_type); + mc_LT_recup.initialize(m_LTR_N_sub_hxrs, ms_des_par.m_LTR_od_UA_target_type, m_yr_inflation); // HTR - mc_HT_recup.initialize(m_HTR_N_sub_hxrs, ms_des_par.m_HTR_od_UA_target_type); + mc_HT_recup.initialize(m_HTR_N_sub_hxrs, ms_des_par.m_HTR_od_UA_target_type, m_yr_inflation); // Initialize a few variables double m_dot_t, m_dot_mc, m_dot_rc, Q_dot_LT, Q_dot_HT, UA_LT_calc, UA_HT_calc; @@ -2114,8 +2114,12 @@ void C_RecompCycle::design_core_standard(int & error_code) { double phx_deltaT = m_temp_last[TURB_IN] - m_temp_last[HTR_HP_OUT]; double under_min_deltaT = std::max(0.0, ms_des_par.m_min_phx_deltaT - phx_deltaT); - double eta_deltaT_scale = std::exp(-under_min_deltaT); - m_objective_metric_last = m_eta_thermal_calc_last * eta_deltaT_scale; + //double eta_deltaT_scale = std::exp(-under_min_deltaT); + //m_objective_metric_last = m_eta_thermal_calc_last * eta_deltaT_scale; + + double percent_err = under_min_deltaT / ms_des_par.m_min_phx_deltaT; + m_objective_metric_last = m_eta_thermal_calc_last - percent_err; + } else { @@ -2388,7 +2392,7 @@ void C_RecompCycle::design(S_design_parameters & des_par_in, int & error_code) error_code = design_error_code; } -void C_RecompCycle::opt_design(S_opt_design_parameters & opt_des_par_in, int & error_code) +int C_RecompCycle::opt_design(S_opt_design_parameters & opt_des_par_in, int & error_code) { ms_opt_des_par = opt_des_par_in; @@ -2398,10 +2402,19 @@ void C_RecompCycle::opt_design(S_opt_design_parameters & opt_des_par_in, int & e if(error_code != 0) { - return; + return error_code; } - finalize_design(error_code); + // Check if cycle is below eta cutoff value + if (m_eta_thermal_calc_last < ms_auto_opt_des_par.m_eta_thermal_cutoff) + { + ms_des_solved.m_eta_thermal = m_eta_thermal_calc_last; + return C_sco2_cycle_core::E_cycle_error_msg::E_ETA_THRESHOLD; + } + + finalize_design(error_code); + + return error_code; } void C_RecompCycle::opt_design_core(int & error_code) @@ -2494,7 +2507,7 @@ void C_RecompCycle::opt_design_core(int & error_code) opt_des_cycle.set_upper_bounds(ub); opt_des_cycle.set_initial_step(scale); opt_des_cycle.set_xtol_rel(ms_opt_des_par.m_des_opt_tol); - + //opt_des_cycle.set_maxeval(10); // Set max objective function opt_des_cycle.set_max_objective(nlopt_cb_opt_des, this); // Calls wrapper/callback that calls 'design_point_eta', which optimizes design point eta through repeated calls to 'design' double max_f = std::numeric_limits::quiet_NaN(); @@ -2816,7 +2829,15 @@ void C_RecompCycle::auto_opt_design_core(int & error_code) return; } - finalize_design(optimal_design_error_code); + // Check if cycle is below eta cutoff value + if (m_eta_thermal_calc_last < ms_auto_opt_des_par.m_eta_thermal_cutoff) + { + ms_des_solved.m_eta_thermal = m_eta_thermal_calc_last; + error_code = C_sco2_cycle_core::E_cycle_error_msg::E_ETA_THRESHOLD; + return; + } + + finalize_design(optimal_design_error_code); error_code = optimal_design_error_code; } @@ -3329,7 +3350,8 @@ void C_RecompCycle::finalize_design(int & error_code) m_m_dot_mc, m_temp_last[MC_OUT], m_pres_last[MC_OUT], - ms_des_par.m_des_tol); + ms_des_par.m_des_tol, + m_yr_inflation); if (mc_design_err != 0) { @@ -3343,7 +3365,8 @@ void C_RecompCycle::finalize_design(int & error_code) m_pres_last[LTR_LP_OUT], m_m_dot_rc, m_temp_last[RC_OUT], - m_pres_last[RC_OUT], ms_des_par.m_des_tol); + m_pres_last[RC_OUT], ms_des_par.m_des_tol, + m_yr_inflation); if (rc_des_err != 0) { @@ -3372,6 +3395,8 @@ void C_RecompCycle::finalize_design(int & error_code) t_des_par.m_h_out = m_enth_last[7-cpp_offset]; // Mass flow t_des_par.m_m_dot = m_m_dot_t; + // Inflation target year + t_des_par.m_yr_inflation = m_yr_inflation; //[yr] int turb_size_error_code = 0; m_t.turbine_sizing(t_des_par, turb_size_error_code); @@ -3405,11 +3430,21 @@ void C_RecompCycle::finalize_design(int & error_code) s_air_cooler_des_par_ind.m_elev = m_elevation; //[m] s_air_cooler_des_par_ind.m_eta_fan = m_eta_fan; //[-] s_air_cooler_des_par_ind.m_N_nodes_pass = m_N_nodes_pass; //[-] + s_air_cooler_des_par_ind.m_yr_inflation = m_yr_inflation; //[yr] if (ms_des_par.m_is_des_air_cooler && std::isfinite(m_deltaP_cooler_frac) && std::isfinite(m_frac_fan_power) && std::isfinite(m_T_amb_des) && std::isfinite(m_elevation) && std::isfinite(m_eta_fan) && m_N_nodes_pass > 0) { - mc_air_cooler.design_hx(s_air_cooler_des_par_ind, s_air_cooler_des_par_dep, ms_des_par.m_des_tol); + try + { + mc_air_cooler.design_hx(s_air_cooler_des_par_ind, s_air_cooler_des_par_dep, ms_des_par.m_des_tol); + } + catch (...) + { + ms_des_solved.m_eta_thermal = m_eta_thermal_calc_last; + error_code = C_sco2_cycle_core::E_cycle_error_msg::E_AIR_COOLER_CONVERGENCE; + return; + } } diff --git a/tcs/sco2_recompression_cycle.h b/tcs/sco2_recompression_cycle.h index 408e029d7..43d8ca503 100644 --- a/tcs/sco2_recompression_cycle.h +++ b/tcs/sco2_recompression_cycle.h @@ -496,7 +496,8 @@ class C_RecompCycle : public C_sco2_cycle_core double eta_t /*-*/, double N_turbine /*rpm*/, double frac_fan_power /*-*/, double eta_fan /*-*/, double deltaP_cooler_frac /*-*/, int N_nodes_pass /*-*/, - double T_amb_des /*K*/, double elevation /*m*/) : + double T_amb_des /*K*/, double elevation /*m*/, + double m_yr_inflation /*yr*/) : C_sco2_cycle_core(turbo_gen_motor_config, eta_generator, T_mc_in, @@ -510,7 +511,8 @@ class C_RecompCycle : public C_sco2_cycle_core eta_t, N_turbine, frac_fan_power, eta_fan, deltaP_cooler_frac, N_nodes_pass, - T_amb_des, elevation) + T_amb_des, elevation, + m_yr_inflation) { m_temp_last.resize(END_SCO2_STATES); std::fill(m_temp_last.begin(), m_temp_last.end(), std::numeric_limits::quiet_NaN()); @@ -554,7 +556,7 @@ class C_RecompCycle : public C_sco2_cycle_core void design(S_design_parameters & des_par_in, int & error_code); - void opt_design(S_opt_design_parameters & opt_des_par_in, int & error_code); + int opt_design(S_opt_design_parameters & opt_des_par_in, int & error_code); //void od_turbo_bal_csp(const S_od_turbo_bal_csp_par & par_in); diff --git a/tcs/sco2_turbinesplitflow_cycle.cpp b/tcs/sco2_turbinesplitflow_cycle.cpp new file mode 100644 index 000000000..7ee71df60 --- /dev/null +++ b/tcs/sco2_turbinesplitflow_cycle.cpp @@ -0,0 +1,1262 @@ +/* +BSD 3-Clause License + +Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/ssc/blob/develop/LICENSE +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "sco2_turbinesplitflow_cycle.h" +#include "sco2_cycle_components.h" + +#include "CO2_properties.h" + + +#include "fmin.h" + + +// ********************************************************************************** C_sco2_tsf_core CORE MODEL + +void C_sco2_tsf_core::initialize_solve() +{ + m_outputs.Init(); +} + +int C_sco2_tsf_core::solve() +{ + initialize_solve(); + m_outputs.m_error_code = -1; + + // Apply scaling to the turbomachinery here + { + m_outputs.m_mc_ms.m_r_W_dot_scale = m_inputs.m_W_dot_net_design / 10.E3; //[-] + m_outputs.m_t.m_r_W_dot_scale = m_outputs.m_mc_ms.m_r_W_dot_scale; //[-] + m_outputs.m_t2.m_r_W_dot_scale = m_outputs.m_mc_ms.m_r_W_dot_scale; //[-] + } + + // Initialize Recuperators + { + // LTR + m_outputs.mc_LT_recup.initialize(m_inputs.m_LTR_N_sub_hxrs, m_inputs.m_LTR_od_UA_target_type, m_inputs.m_yr_inflation); + // HTR + m_outputs.mc_HT_recup.initialize(m_inputs.m_HTR_N_sub_hxrs, m_inputs.m_HTR_od_UA_target_type, m_inputs.m_yr_inflation); + } + + // Initialize a few variables + { + m_outputs.m_temp[C_sco2_cycle_core::MC_IN] = m_inputs.m_T_mc_in; //[K] + m_outputs.m_pres[C_sco2_cycle_core::MC_IN] = m_inputs.m_P_mc_in; + m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] = m_inputs.m_P_mc_out; + m_outputs.m_temp[C_sco2_cycle_core::TURB_IN] = m_inputs.m_T_t_in; //[K] + } + + // Apply pressure drops to heat exchangers, fully defining the pressures at all states + { + // LTR_HP_OUT + if (m_inputs.m_DP_LTR[0] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] - m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] * std::abs(m_inputs.m_DP_LTR[0]); // relative pressure drop specified for LT recuperator (cold stream) + else + m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] - m_inputs.m_DP_LTR[0]; // absolute pressure drop specified for LT recuperator (cold stream) + if ((m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA && m_inputs.m_LTR_UA < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_UA && m_inputs.m_LTR_UA < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_MIN_DT && m_inputs.m_LTR_min_dT < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_EFFECTIVENESS && m_inputs.m_LTR_eff_target < 1.0E-12)) + m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_OUT]; // If there is no LT recuperator, there is no pressure drop + + // TURB_IN + if (m_inputs.m_DP_PHX[0] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::TURB_IN] = m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] - m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] * std::abs(m_inputs.m_DP_PHX[0]); // relative pressure drop specified for PHX + else + m_outputs.m_pres[C_sco2_cycle_core::TURB_IN] = m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] - m_inputs.m_DP_PHX[0]; // absolute pressure drop specified for PHX + + // HTR_HP_OUT + if (m_inputs.m_DP_HTR[0] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] - m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] * std::abs(m_inputs.m_DP_HTR[0]); // relative pressure drop specified for HT recuperator (cold stream) + else + m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_OUT] - m_inputs.m_DP_HTR[0]; // absolute pressure drop specified for HT recuperator (cold stream) + if ((m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA && m_inputs.m_HTR_UA < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_UA && m_inputs.m_HTR_UA < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_MIN_DT && m_inputs.m_HTR_min_dT < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_EFFECTIVENESS && m_inputs.m_HTR_eff_target < 1.0E-12)) + m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_OUT]; // If there is no HT recuperator, there is no pressure drop + + // MIXER_OUT + if (m_inputs.m_DP_PC_main[1] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_IN] / (1.0 - std::abs(m_inputs.m_DP_PC_main[1])); // relative pressure drop specified for precooler: P1=P9-P9*rel_DP => P1=P9*(1-rel_DP) + else + m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MC_IN] + m_inputs.m_DP_PC_main[1]; + + // LTR_LP_OUT + m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT]; // Assume no pressure drop in mixer + + // HTR_LP_OUT + m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT] = m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT]; // Assume no pressure drop in mixer + + // TURB_OUT + if (m_inputs.m_DP_HTR[1] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT] = m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT] / (1.0 - std::abs(m_inputs.m_DP_HTR[1])); // relative pressure drop specified for HT recuperator (hot stream) + else + m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT] = m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT] + m_inputs.m_DP_HTR[1]; // absolute pressure drop specified for HT recuperator (hot stream) + if ((m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA && m_inputs.m_HTR_UA < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_UA && m_inputs.m_HTR_UA < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_MIN_DT && m_inputs.m_HTR_min_dT < 1.0E-12) + || (m_inputs.m_HTR_target_code == NS_HX_counterflow_eqs::TARGET_EFFECTIVENESS && m_inputs.m_HTR_eff_target < 1.0E-12)) + m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT] = m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT]; // if there is no HT recuperator, there is no pressure drop + + // TURB2_OUT + if (m_inputs.m_DP_LTR[1] < 0.0) + m_outputs.m_pres[C_sco2_cycle_core::TURB2_OUT] = m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT] / (1.0 - std::abs(m_inputs.m_DP_LTR[1])); // relative pressure drop specified for LT recuperator (hot stream) + else + m_outputs.m_pres[C_sco2_cycle_core::TURB2_OUT] = m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT] + m_inputs.m_DP_LTR[1]; // absolute pressure drop specified for HT recuperator (hot stream) + if ((m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA && m_inputs.m_LTR_UA < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_UA && m_inputs.m_LTR_UA < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_MIN_DT && m_inputs.m_LTR_min_dT < 1.0E-12) + || (m_inputs.m_LTR_target_code == NS_HX_counterflow_eqs::TARGET_EFFECTIVENESS && m_inputs.m_LTR_eff_target < 1.0E-12)) + m_outputs.m_pres[C_sco2_cycle_core::TURB2_OUT] = m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT]; // if there is no LT recuperator, there is no pressure drop + + } + + // Determine equivalent isentropic efficiencies for main compressor and turbine, if necessary. + double eta_mc_isen = std::numeric_limits::quiet_NaN(); + double eta_t_isen = std::numeric_limits::quiet_NaN(); + { + if (m_inputs.m_eta_mc < 0.0) + { + int poly_error_code = 0; + + isen_eta_from_poly_eta(m_outputs.m_temp[C_sco2_cycle_core::MC_IN], m_outputs.m_pres[C_sco2_cycle_core::MC_IN], m_outputs.m_pres[C_sco2_cycle_core::MC_OUT], std::abs(m_inputs.m_eta_mc), + true, poly_error_code, eta_mc_isen); + + if (poly_error_code != 0) + { + m_outputs.m_error_code = poly_error_code; + return m_outputs.m_error_code; + } + } + else + eta_mc_isen = m_inputs.m_eta_mc; + + if (m_inputs.m_eta_t < 0.0) + { + int poly_error_code = 0; + + isen_eta_from_poly_eta(m_outputs.m_temp[C_sco2_cycle_core::TURB_IN], m_outputs.m_pres[C_sco2_cycle_core::TURB_IN], m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT], std::abs(m_inputs.m_eta_t), + false, poly_error_code, eta_t_isen); + + if (poly_error_code != 0) + { + m_outputs.m_error_code = poly_error_code; + return m_outputs.m_error_code; + } + } + else + eta_t_isen = m_inputs.m_eta_t; + } + + // Determine the outlet state and specific work for the main compressor and turbine. + + // Main compressor + m_outputs.m_w_mc = std::numeric_limits::quiet_NaN(); + { + int comp_error_code = 0; + + calculate_turbomachinery_outlet_1(m_outputs.m_temp[C_sco2_cycle_core::MC_IN], m_outputs.m_pres[C_sco2_cycle_core::MC_IN], m_outputs.m_pres[C_sco2_cycle_core::MC_OUT], eta_mc_isen, true, + comp_error_code, m_outputs.m_enth[C_sco2_cycle_core::MC_IN], m_outputs.m_entr[C_sco2_cycle_core::MC_IN], m_outputs.m_dens[C_sco2_cycle_core::MC_IN], m_outputs.m_temp[C_sco2_cycle_core::MC_OUT], + m_outputs.m_enth[C_sco2_cycle_core::MC_OUT], m_outputs.m_entr[C_sco2_cycle_core::MC_OUT], m_outputs.m_dens[C_sco2_cycle_core::MC_OUT], m_outputs.m_w_mc); + + if (comp_error_code != 0) + { + m_outputs.m_error_code = comp_error_code; + return m_outputs.m_error_code; + } + } + + // Turbine + m_outputs.m_w_t = std::numeric_limits::quiet_NaN(); + { + int turbine_error_code = 0; + + calculate_turbomachinery_outlet_1(m_outputs.m_temp[C_sco2_cycle_core::TURB_IN], m_outputs.m_pres[C_sco2_cycle_core::TURB_IN], m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT], eta_t_isen, false, + turbine_error_code, m_outputs.m_enth[C_sco2_cycle_core::TURB_IN], m_outputs.m_entr[C_sco2_cycle_core::TURB_IN], m_outputs.m_dens[C_sco2_cycle_core::TURB_IN], m_outputs.m_temp[C_sco2_cycle_core::TURB_OUT], + m_outputs.m_enth[C_sco2_cycle_core::TURB_OUT], m_outputs.m_entr[C_sco2_cycle_core::TURB_OUT], m_outputs.m_dens[C_sco2_cycle_core::TURB_OUT], m_outputs.m_w_t); + + if (turbine_error_code != 0) + { + m_outputs.m_error_code = turbine_error_code; + return m_outputs.m_error_code; + } + } + + // Solve HTR_HP_OUT Temp (iteratively) + { + // Make monotonic solver + C_mono_tsf_core_HTR_des HTR_des_eq(this); + C_monotonic_eq_solver HTR_des_solver(HTR_des_eq); + + // Bounds + double T_HTR_HP_out_lower = m_outputs.m_temp[C_sco2_cycle_core::MC_OUT]; //[K] Coldest possible temp + double T_HTR_HP_out_upper = m_outputs.m_temp[C_sco2_cycle_core::TURB_IN]; //[K] Coldest possible temp (probably is TURB_OUT) + + // Solution Guess + double T_HTR_HP_out_guess_lower = 0.25 * (T_HTR_HP_out_upper - T_HTR_HP_out_lower) + T_HTR_HP_out_lower; // [K] + double T_HTR_HP_out_guess_upper = 0.75 * (T_HTR_HP_out_upper - T_HTR_HP_out_lower) + T_HTR_HP_out_lower; // [K] + + // Optimization Settings + HTR_des_solver.settings(m_inputs.m_des_tol* m_outputs.m_temp[C_sco2_cycle_core::MC_IN], 1000, T_HTR_HP_out_lower, T_HTR_HP_out_upper, false); + + // Optimization Output variables + double tol_T_HTR_HP_out_solved; + double T_HTR_HP_out_solved = tol_T_HTR_HP_out_solved = std::numeric_limits::quiet_NaN(); + int iter_T_HTR_LP_out = -1; + + // Optimize + int T_HTR_HP_out_code = HTR_des_solver.solve(T_HTR_HP_out_guess_lower, T_HTR_HP_out_guess_upper, 0, + T_HTR_HP_out_solved, tol_T_HTR_HP_out_solved, iter_T_HTR_LP_out); + + // Check if converged + if (T_HTR_HP_out_code != C_monotonic_eq_solver::CONVERGED) + { + m_outputs.m_error_code = 25; + return m_outputs.m_error_code; + } + + // Run Calculated HTR HP Out (to set correct temps) + double dummy; + solve_HTR(T_HTR_HP_out_solved, &dummy); + } + + // Complete HTR_HP_OUT and HTR_LP_OUT co2 properties + { + int prop_error_code = CO2_TP(m_outputs.m_temp[C_sco2_cycle_core::HTR_HP_OUT], m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT], &m_co2_props); + if (prop_error_code != 0) + { + m_outputs.m_error_code = prop_error_code; + return m_outputs.m_error_code; + } + m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT] = m_co2_props.enth; + m_outputs.m_entr[C_sco2_cycle_core::HTR_HP_OUT] = m_co2_props.entr; + m_outputs.m_dens[C_sco2_cycle_core::HTR_HP_OUT] = m_co2_props.dens; + + prop_error_code = CO2_TP(m_outputs.m_temp[C_sco2_cycle_core::HTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT], &m_co2_props); + if (prop_error_code != 0) + { + m_outputs.m_error_code = prop_error_code; + return m_outputs.m_error_code; + } + m_outputs.m_enth[C_sco2_cycle_core::HTR_LP_OUT] = m_co2_props.enth; + m_outputs.m_entr[C_sco2_cycle_core::HTR_LP_OUT] = m_co2_props.entr; + m_outputs.m_dens[C_sco2_cycle_core::HTR_LP_OUT] = m_co2_props.dens; + } + + // Simulate LTR + { + m_outputs.mc_LT_recup.design_for_target__calc_outlet(m_inputs.m_LTR_target_code, + m_inputs.m_LTR_UA, m_inputs.m_LTR_min_dT, m_inputs.m_LTR_eff_target, + m_inputs.m_LTR_eff_max, + m_outputs.m_temp[C_sco2_cycle_core::MC_OUT], m_outputs.m_pres[C_sco2_cycle_core::MC_OUT], m_outputs.m_m_dot_t, m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT], + m_outputs.m_temp[C_sco2_cycle_core::TURB2_OUT], m_outputs.m_pres[C_sco2_cycle_core::TURB2_OUT], m_outputs.m_m_dot_t2, m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT], + m_inputs.m_des_tol, + m_outputs.m_Q_dot_LT, m_outputs.m_temp[C_sco2_cycle_core::LTR_HP_OUT], m_outputs.m_temp[C_sco2_cycle_core::LTR_LP_OUT]); + } + + // Complete LTR_HP_OUT and LTR_LP_OUT co2 properties + { + int prop_error_code = CO2_TP(m_outputs.m_temp[C_sco2_cycle_core::LTR_HP_OUT], m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT], &m_co2_props); + if (prop_error_code != 0) + { + m_outputs.m_error_code = prop_error_code; + return m_outputs.m_error_code; + } + m_outputs.m_enth[C_sco2_cycle_core::LTR_HP_OUT] = m_co2_props.enth; + m_outputs.m_entr[C_sco2_cycle_core::LTR_HP_OUT] = m_co2_props.entr; + m_outputs.m_dens[C_sco2_cycle_core::LTR_HP_OUT] = m_co2_props.dens; + + prop_error_code = CO2_TP(m_outputs.m_temp[C_sco2_cycle_core::LTR_LP_OUT], m_outputs.m_pres[C_sco2_cycle_core::LTR_LP_OUT], &m_co2_props); + if (prop_error_code != 0) + { + m_outputs.m_error_code = prop_error_code; + return m_outputs.m_error_code; + } + m_outputs.m_enth[C_sco2_cycle_core::LTR_LP_OUT] = m_co2_props.enth; + m_outputs.m_entr[C_sco2_cycle_core::LTR_LP_OUT] = m_co2_props.entr; + m_outputs.m_dens[C_sco2_cycle_core::LTR_LP_OUT] = m_co2_props.dens; + } + + // Simulate Mixer + { + m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT] = ((1.0 - m_inputs.m_split_frac) * m_outputs.m_enth[C_sco2_cycle_core::HTR_LP_OUT]) + + (m_inputs.m_split_frac * m_outputs.m_enth[C_sco2_cycle_core::LTR_LP_OUT]); + + int prop_error_code = CO2_PH(m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT], m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT], &m_co2_props); + if (prop_error_code != 0) + { + m_outputs.m_error_code = prop_error_code; + return m_outputs.m_error_code; + } + m_outputs.m_temp[C_sco2_cycle_core::MIXER_OUT] = m_co2_props.temp; //[K] + m_outputs.m_entr[C_sco2_cycle_core::MIXER_OUT] = m_co2_props.entr; //[kJ/kg-K] + m_outputs.m_dens[C_sco2_cycle_core::MIXER_OUT] = m_co2_props.dens; //[kg/m^3] + } + + // Calculate total work and heat metrics + { + // Work + m_outputs.m_W_dot_mc = m_outputs.m_w_mc * m_outputs.m_m_dot_mc; + m_outputs.m_W_dot_t = m_outputs.m_w_t * m_outputs.m_m_dot_t; + m_outputs.m_W_dot_t2 = m_outputs.m_w_t2 * m_outputs.m_m_dot_t2; + m_outputs.m_W_dot_net = m_outputs.m_W_dot_mc + m_outputs.m_W_dot_t + m_outputs.m_W_dot_t2; + + // Air Cooler (heat rejection unit) + m_outputs.m_W_dot_air_cooler = m_inputs.m_frac_fan_power * m_outputs.m_W_dot_net; + m_outputs.m_Q_dot_air_cooler = m_outputs.m_m_dot_mc * (m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MC_IN]); + + // Total heat entering sco2 + m_outputs.m_Q_dot_PHX = m_outputs.m_m_dot_t * (m_outputs.m_enth[C_sco2_cycle_core::TURB_IN] - m_outputs.m_enth[C_sco2_cycle_core::LTR_HP_OUT]); + m_outputs.m_Q_dot_total = m_outputs.m_Q_dot_PHX; + + // LTR + m_outputs.m_Q_dot_LTR_LP = m_outputs.m_m_dot_t2 * (m_outputs.m_enth[C_sco2_cycle_core::TURB2_OUT] - m_outputs.m_enth[C_sco2_cycle_core::LTR_LP_OUT]); + m_outputs.m_Q_dot_LTR_HP = m_outputs.m_m_dot_t * (m_outputs.m_enth[C_sco2_cycle_core::LTR_HP_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MC_OUT]); + + // LTR + m_outputs.m_Q_dot_HTR_LP = m_outputs.m_m_dot_t * (m_outputs.m_enth[C_sco2_cycle_core::TURB_OUT] - m_outputs.m_enth[C_sco2_cycle_core::HTR_LP_OUT]); + m_outputs.m_Q_dot_HTR_HP = m_outputs.m_m_dot_t2 * (m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MC_OUT]); + + // Thermal Efficiency + m_outputs.m_eta_thermal = m_outputs.m_W_dot_net / m_outputs.m_Q_dot_total; + + // Back Calculate Heat In + double Q_in_calc = m_outputs.m_W_dot_net + m_outputs.m_Q_dot_air_cooler; + } + + // Define Heat Exchangers and Air Cooler + { + // PHX + C_HeatExchanger::S_design_parameters PHX_des_par; + PHX_des_par.m_DP_design[0] = m_outputs.m_pres[C_sco2_cycle_core::LTR_HP_OUT] - m_outputs.m_pres[C_sco2_cycle_core::TURB_IN]; + PHX_des_par.m_DP_design[1] = 0.0; + PHX_des_par.m_m_dot_design[0] = m_outputs.m_m_dot_t; + PHX_des_par.m_m_dot_design[1] = 0.0; + PHX_des_par.m_Q_dot_design = m_outputs.m_m_dot_t * (m_outputs.m_enth[C_sco2_cycle_core::TURB_IN] - m_outputs.m_enth[C_sco2_cycle_core::LTR_HP_OUT]); + m_outputs.m_PHX.initialize(PHX_des_par); + + // Air Cooler + C_HeatExchanger::S_design_parameters PC_des_par; + PC_des_par.m_DP_design[0] = 0.0; + PC_des_par.m_DP_design[1] = m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT] - m_outputs.m_pres[C_sco2_cycle_core::MC_IN]; + PC_des_par.m_m_dot_design[0] = 0.0; + PC_des_par.m_m_dot_design[1] = m_outputs.m_m_dot_mc; + PC_des_par.m_Q_dot_design = m_outputs.m_m_dot_mc * (m_outputs.m_enth[C_sco2_cycle_core::MIXER_OUT] - m_outputs.m_enth[C_sco2_cycle_core::MC_IN]); + m_outputs.m_PC.initialize(PC_des_par); + } + + m_outputs.m_error_code = 0; + + return m_outputs.m_error_code; +} + +int C_sco2_tsf_core::finalize_design(C_sco2_cycle_core::S_design_solved& design_solved) +{ + // Design Main Compressor + { + int mc_design_err = m_outputs.m_mc_ms.design_given_outlet_state(m_inputs.m_mc_comp_model_code, m_outputs.m_temp[C_sco2_cycle_core::MC_IN], + m_outputs.m_pres[C_sco2_cycle_core::MC_IN], + m_outputs.m_m_dot_mc, + m_outputs.m_temp[C_sco2_cycle_core::MC_OUT], + m_outputs.m_pres[C_sco2_cycle_core::MC_OUT], + m_inputs.m_des_tol, + m_inputs.m_yr_inflation); + + if (mc_design_err != 0) + { + m_outputs.m_error_code = mc_design_err; + return m_outputs.m_error_code; + } + } + + // Size Turbine + { + C_turbine::S_design_parameters t_des_par; + // Set turbine shaft speed + t_des_par.m_N_design = m_inputs.m_N_turbine; + t_des_par.m_N_comp_design_if_linked = m_outputs.m_mc_ms.get_design_solved()->m_N_design; //[rpm] m_mc.get_design_solved()->m_N_design; + // Turbine inlet state + t_des_par.m_P_in = m_outputs.m_pres[C_sco2_cycle_core::TURB_IN]; + t_des_par.m_T_in = m_outputs.m_temp[C_sco2_cycle_core::TURB_IN]; + t_des_par.m_D_in = m_outputs.m_dens[C_sco2_cycle_core::TURB_IN]; + t_des_par.m_h_in = m_outputs.m_enth[C_sco2_cycle_core::TURB_IN]; + t_des_par.m_s_in = m_outputs.m_entr[C_sco2_cycle_core::TURB_IN]; + // Turbine outlet state + t_des_par.m_P_out = m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT]; + t_des_par.m_h_out = m_outputs.m_enth[C_sco2_cycle_core::TURB_OUT]; + // Mass flow + t_des_par.m_m_dot = m_outputs.m_m_dot_t; + // Inflation target year + t_des_par.m_yr_inflation = m_inputs.m_yr_inflation; + + int turb_size_error_code = 0; + m_outputs.m_t.turbine_sizing(t_des_par, turb_size_error_code); + + if (turb_size_error_code != 0) + { + m_outputs.m_error_code = turb_size_error_code; + return m_outputs.m_error_code; + } + } + + // Size Secondary Turbine + { + C_turbine::S_design_parameters t2_des_par; + // Set turbine shaft speed + t2_des_par.m_N_design = m_inputs.m_N_turbine; + t2_des_par.m_N_comp_design_if_linked = m_outputs.m_mc_ms.get_design_solved()->m_N_design; //[rpm] m_mc.get_design_solved()->m_N_design; + // Turbine inlet state + t2_des_par.m_P_in = m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT]; + t2_des_par.m_T_in = m_outputs.m_temp[C_sco2_cycle_core::HTR_HP_OUT]; + t2_des_par.m_D_in = m_outputs.m_dens[C_sco2_cycle_core::HTR_HP_OUT]; + t2_des_par.m_h_in = m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT]; + t2_des_par.m_s_in = m_outputs.m_entr[C_sco2_cycle_core::HTR_HP_OUT]; + // Turbine outlet state + t2_des_par.m_P_out = m_outputs.m_pres[C_sco2_cycle_core::TURB2_OUT]; + t2_des_par.m_h_out = m_outputs.m_enth[C_sco2_cycle_core::TURB2_OUT]; + // Mass flow + t2_des_par.m_m_dot = m_outputs.m_m_dot_t2; + // Inflation target year + t2_des_par.m_yr_inflation = m_inputs.m_yr_inflation; + + int turb_size_error_code = 0; + m_outputs.m_t2.turbine_sizing(t2_des_par, turb_size_error_code); + + if (turb_size_error_code != 0) + { + m_outputs.m_error_code = turb_size_error_code; + return m_outputs.m_error_code; + } + } + + // Design air cooler + { + // Structure for design parameters that are dependent on cycle design solution + C_CO2_to_air_cooler::S_des_par_cycle_dep s_air_cooler_des_par_dep; + // Set air cooler design parameters that are dependent on the cycle design solution + s_air_cooler_des_par_dep.m_T_hot_in_des = m_outputs.m_temp[C_sco2_cycle_core::MIXER_OUT]; // [K] + s_air_cooler_des_par_dep.m_P_hot_in_des = m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT]; // [kPa] + s_air_cooler_des_par_dep.m_m_dot_total = m_outputs.m_m_dot_mc; // [kg/s] + + // This pressure drop is currently uncoupled from the cycle design + double cooler_deltaP = m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT] - m_outputs.m_pres[C_sco2_cycle_core::MC_IN]; // [kPa] + if (cooler_deltaP == 0.0) + s_air_cooler_des_par_dep.m_delta_P_des = m_inputs.m_deltaP_cooler_frac * m_outputs.m_pres[C_sco2_cycle_core::MIXER_OUT]; // [kPa] + else + s_air_cooler_des_par_dep.m_delta_P_des = cooler_deltaP; // [kPa] + + s_air_cooler_des_par_dep.m_T_hot_out_des = m_outputs.m_temp[C_sco2_cycle_core::MC_IN]; // [K] + s_air_cooler_des_par_dep.m_W_dot_fan_des = m_inputs.m_frac_fan_power * m_outputs.m_W_dot_net / 1000.0; // [MWe] + // Structure for design parameters that are independent of cycle design solution + C_CO2_to_air_cooler::S_des_par_ind s_air_cooler_des_par_ind; + s_air_cooler_des_par_ind.m_T_amb_des = m_inputs.m_T_amb_des; // [K] + s_air_cooler_des_par_ind.m_elev = m_inputs.m_elevation; // [m] + s_air_cooler_des_par_ind.m_eta_fan = m_inputs.m_eta_fan; // [-] + s_air_cooler_des_par_ind.m_N_nodes_pass = m_inputs.m_N_nodes_pass; // [-] + s_air_cooler_des_par_ind.m_yr_inflation = m_inputs.m_yr_inflation; // [yr] + + if (m_inputs.m_is_des_air_cooler && std::isfinite(m_inputs.m_deltaP_cooler_frac) && std::isfinite(m_inputs.m_frac_fan_power) + && std::isfinite(m_inputs.m_T_amb_des) && std::isfinite(m_inputs.m_elevation) && std::isfinite(m_inputs.m_eta_fan) && m_inputs.m_N_nodes_pass > 0) + { + try + { + m_outputs.mc_air_cooler.design_hx(s_air_cooler_des_par_ind, s_air_cooler_des_par_dep, m_inputs.m_des_tol); + } + catch (...) + { + design_solved.m_eta_thermal = m_outputs.m_eta_thermal; + m_outputs.m_error_code = C_sco2_cycle_core::E_cycle_error_msg::E_AIR_COOLER_CONVERGENCE; + return m_outputs.m_error_code; + } + + } + } + + // Get 'design_solved' structure from component classes + design_solved.ms_mc_ms_des_solved = *m_outputs.m_mc_ms.get_design_solved(); + design_solved.ms_t_des_solved = *m_outputs.m_t.get_design_solved(); + design_solved.ms_t2_des_solved = *m_outputs.m_t2.get_design_solved(); + design_solved.ms_LTR_des_solved = m_outputs.mc_LT_recup.ms_des_solved; + design_solved.ms_HTR_des_solved = m_outputs.mc_HT_recup.ms_des_solved; + design_solved.ms_mc_air_cooler = *m_outputs.mc_air_cooler.get_design_solved(); + + // Set solved design point metrics + design_solved.m_temp = m_outputs.m_temp; + design_solved.m_pres = m_outputs.m_pres; + design_solved.m_enth = m_outputs.m_enth; + design_solved.m_entr = m_outputs.m_entr; + design_solved.m_dens = m_outputs.m_dens; + + design_solved.m_eta_thermal = m_outputs.m_eta_thermal; + design_solved.m_W_dot_net = m_outputs.m_W_dot_net; + design_solved.m_m_dot_mc = m_outputs.m_m_dot_mc; + design_solved.m_m_dot_t = m_outputs.m_m_dot_t; + design_solved.m_m_dot_t2 = m_outputs.m_m_dot_t2; + design_solved.m_turbine_split_frac = m_inputs.m_split_frac; + + design_solved.m_UA_LTR = m_inputs.m_LTR_UA; + design_solved.m_UA_HTR = m_inputs.m_HTR_UA; + + design_solved.m_W_dot_t = m_outputs.m_W_dot_t; //[kWe] + design_solved.m_W_dot_t2 = m_outputs.m_W_dot_t2; //[kWe] + design_solved.m_W_dot_mc = m_outputs.m_W_dot_mc; //[kWe] + + design_solved.m_is_rc = false; + + design_solved.m_W_dot_cooler_tot = m_outputs.mc_air_cooler.get_design_solved()->m_W_dot_fan * 1.E3; //[kWe] convert from MWe + + return 0; +} + +int C_sco2_tsf_core::solve_HTR(double T_HTR_HP_OUT_guess, double* diff_T_HTR_HP_out) +{ + // Set HTR_HP_OUT temp + m_outputs.m_temp[C_sco2_cycle_core::HTR_HP_OUT] = T_HTR_HP_OUT_guess; + + // Simulate Secondary Turbine + { + // Determine equivalent isentropic efficiencies for secondary turbine, if necessary + double eta_t2_isen = std::numeric_limits::quiet_NaN(); + { + if (m_inputs.m_eta_t2 < 0.0) + { + int poly_error_code = 0; + + isen_eta_from_poly_eta(m_outputs.m_temp[C_sco2_cycle_core::HTR_HP_OUT], m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT], m_outputs.m_pres[C_sco2_cycle_core::TURB2_OUT], std::abs(m_inputs.m_eta_t2), + false, poly_error_code, eta_t2_isen); + + if (poly_error_code != 0) + { + m_outputs.m_error_code = poly_error_code; + return m_outputs.m_error_code; + } + } + else + eta_t2_isen = m_inputs.m_eta_t2; + } + + // Simulate Secondary Turbine + { + int turbine2_error_code = 0; + + calculate_turbomachinery_outlet_1(m_outputs.m_temp[C_sco2_cycle_core::HTR_HP_OUT], m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT], m_outputs.m_pres[C_sco2_cycle_core::TURB2_OUT], eta_t2_isen, false, + turbine2_error_code, m_outputs.m_enth[C_sco2_cycle_core::HTR_HP_OUT], m_outputs.m_entr[C_sco2_cycle_core::HTR_HP_OUT], m_outputs.m_dens[C_sco2_cycle_core::HTR_HP_OUT], m_outputs.m_temp[C_sco2_cycle_core::TURB2_OUT], + m_outputs.m_enth[C_sco2_cycle_core::TURB2_OUT], m_outputs.m_entr[C_sco2_cycle_core::TURB2_OUT], m_outputs.m_dens[C_sco2_cycle_core::TURB2_OUT], m_outputs.m_w_t2); + + if (turbine2_error_code != 0) + { + m_outputs.m_error_code = turbine2_error_code; + return m_outputs.m_error_code; + } + } + } + + // Calculate Mass Flow Rates + m_outputs.m_m_dot_mc = m_inputs.m_W_dot_net_design + / (((m_outputs.m_w_t * (1.0 - m_inputs.m_split_frac)) + + (m_outputs.m_w_t2 * m_inputs.m_split_frac) + + (m_outputs.m_w_mc)) + * m_inputs.m_eta_generator); //[kg/s] + + m_outputs.m_m_dot_t = m_outputs.m_m_dot_mc * (1.0 - m_inputs.m_split_frac); //[kg/s] + m_outputs.m_m_dot_t2 = m_outputs.m_m_dot_mc * m_inputs.m_split_frac; //[kg/s] + + // Solve HTR + double T_HTR_HP_out_calc = std::numeric_limits::quiet_NaN(); + m_outputs.mc_HT_recup.design_for_target__calc_outlet(m_inputs.m_HTR_target_code, + m_inputs.m_HTR_UA, m_inputs.m_HTR_min_dT, m_inputs.m_HTR_eff_target, + m_inputs.m_HTR_eff_max, + m_outputs.m_temp[C_sco2_cycle_core::MC_OUT], m_outputs.m_pres[C_sco2_cycle_core::MC_OUT], m_outputs.m_m_dot_t2, m_outputs.m_pres[C_sco2_cycle_core::HTR_HP_OUT], + m_outputs.m_temp[C_sco2_cycle_core::TURB_OUT], m_outputs.m_pres[C_sco2_cycle_core::TURB_OUT], m_outputs.m_m_dot_t, m_outputs.m_pres[C_sco2_cycle_core::HTR_LP_OUT], + m_inputs.m_des_tol, + m_outputs.m_Q_dot_HT, T_HTR_HP_out_calc, m_outputs.m_temp[C_sco2_cycle_core::HTR_LP_OUT]); + + *diff_T_HTR_HP_out = T_HTR_HP_OUT_guess - T_HTR_HP_out_calc; + + return 0; +} + +void C_sco2_tsf_core::reset() +{ + this->m_inputs = S_sco2_tsf_in(); + this->m_outputs.Init(); +} + +// ********************************************************************************** END C_sco2_tsf_core + + +// ********************************************************************************** PRIVATE C_TurbineSplitFlow_Cycle (: C_sco2_cycle_core) + +/// +/// Core function to optimize cycle (fixed total UA) +/// +void C_TurbineSplitFlow_Cycle::auto_opt_design_core(int& error_code) +{ + // Create 'ms_opt_des_par' for Design Variables + S_opt_design_parameters opt_par; + { + // Max Pressure + double best_P_high = m_P_high_limit; //[kPa] + double PR_mc_guess = 2.5; //[-] + + opt_par.m_fixed_P_mc_out = ms_auto_opt_des_par.m_fixed_P_mc_out; //[-] + if (!opt_par.m_fixed_P_mc_out) + { + double P_low_limit = std::min(m_P_high_limit, std::max(10.E3, m_P_high_limit * 0.2)); //[kPa] + + //best_P_high = fminbr(P_low_limit, m_P_high_limit, &fmin_cb_opt_des_fixed_P_high, this, 1.0); + best_P_high = m_P_high_limit; + } + opt_par.m_P_mc_out_guess = best_P_high; //[kPa] + //ms_opt_des_par.m_fixed_P_mc_out = true; + + // Pressure Ratio (min pressure) + opt_par.m_fixed_PR_HP_to_LP = ms_auto_opt_des_par.m_fixed_PR_HP_to_LP; //[-] + if (opt_par.m_fixed_PR_HP_to_LP) + { + opt_par.m_PR_HP_to_LP_guess = ms_auto_opt_des_par.m_PR_HP_to_LP_guess; //[-] + } + else + { + opt_par.m_PR_HP_to_LP_guess = PR_mc_guess; //[-] + } + + // Is recompression fraction fixed or optimized? + if (ms_auto_opt_des_par.m_is_turbinesplit_ok <= 0.0) + { // fixed + opt_par.m_split_frac_guess = std::abs(ms_auto_opt_des_par.m_is_turbinesplit_ok); + opt_par.m_fixed_split_frac = true; + } + else + { // optimized + opt_par.m_split_frac_guess = 0.5; + opt_par.m_fixed_split_frac = false; + } + + // LTR HTR UA Ratio + opt_par.m_LT_frac_guess = 0.5; + opt_par.m_fixed_LT_frac = false; + if (ms_auto_opt_des_par.m_LTR_target_code != NS_HX_counterflow_eqs::OPTIMIZE_UA || ms_auto_opt_des_par.m_HTR_target_code != NS_HX_counterflow_eqs::OPTIMIZE_UA) + { + opt_par.m_fixed_LT_frac = true; + } + + // Set Design Method + if (opt_par.m_fixed_LT_frac == true) + opt_par.m_design_method = 3; + else + opt_par.m_design_method = 2; + + } + + C_sco2_tsf_core::S_sco2_tsf_in optimal_inputs_out; + error_code = optimize_par(ms_auto_opt_des_par, opt_par, optimal_inputs_out); + + if (error_code != 0) + return; + + // Run Optimal Case + m_optimal_tsf_core.set_inputs(optimal_inputs_out); + error_code = m_optimal_tsf_core.solve(); + + if (error_code != 0) + return; + + // Check if cycle is below eta cutoff value + if (m_optimal_tsf_core.m_outputs.m_eta_thermal < ms_auto_opt_des_par.m_eta_thermal_cutoff) + { + ms_des_solved.m_eta_thermal = m_optimal_tsf_core.m_outputs.m_eta_thermal; + error_code = C_sco2_cycle_core::E_cycle_error_msg::E_ETA_THRESHOLD; + return; + } + + // Finalize Design (pass in reference to solved parameters) + error_code = m_optimal_tsf_core.finalize_design(ms_des_solved); + +} + +/// +/// Core function to optimize cycle for target eta (variable total UA) +/// +void C_TurbineSplitFlow_Cycle::auto_opt_design_hit_eta_core(int& error_code, const double eta_thermal_target) +{ + return; +} + +/// +/// Optimize Total Recuperator UA +/// totalUA -> bp -> UA split, pressure, recomp +/// +/// +/// +/// +/// +int C_TurbineSplitFlow_Cycle::optimize_totalUA(const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + C_sco2_tsf_core::S_sco2_tsf_in& optimal_inputs) +{ + return -1;; +} + +/// +/// Optimize internal variables (UA split, pressure, recomp) +/// totalUA -> bp -> UA split, pressure, recomp +/// +/// +/// +/// +/// +/// +int C_TurbineSplitFlow_Cycle::optimize_par(const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + C_sco2_tsf_core::S_sco2_tsf_in& optimal_inputs) +{ + + // Set up baseline core inputs + C_sco2_tsf_core::S_sco2_tsf_in core_inputs; + { + // From Auto Opt Design Parameters + core_inputs.m_LTR_target_code = ms_auto_opt_des_par.m_LTR_target_code; + core_inputs.m_LTR_UA = ms_auto_opt_des_par.m_LTR_UA; + core_inputs.m_LTR_min_dT = ms_auto_opt_des_par.m_LTR_min_dT; + core_inputs.m_LTR_eff_target = ms_auto_opt_des_par.m_LTR_eff_target; + core_inputs.m_LTR_eff_max = ms_auto_opt_des_par.m_LTR_eff_max; + + core_inputs.m_LTR_od_UA_target_type = ms_auto_opt_des_par.m_LTR_od_UA_target_type; + core_inputs.m_HTR_target_code = ms_auto_opt_des_par.m_HTR_target_code; + core_inputs.m_HTR_UA = ms_auto_opt_des_par.m_HTR_UA; + core_inputs.m_HTR_min_dT = ms_auto_opt_des_par.m_HTR_min_dT; + core_inputs.m_HTR_eff_target = ms_auto_opt_des_par.m_HTR_eff_target; + core_inputs.m_HTR_eff_max = ms_auto_opt_des_par.m_HTR_eff_max; + core_inputs.m_HTR_od_UA_target_type = ms_auto_opt_des_par.m_HTR_od_UA_target_type; + core_inputs.m_des_tol = ms_auto_opt_des_par.m_des_tol; + core_inputs.m_is_des_air_cooler = ms_auto_opt_des_par.m_is_des_air_cooler; + + // From Constructor + core_inputs.m_LTR_N_sub_hxrs = m_LTR_N_sub_hxrs; // Comes from constructor (constant) + core_inputs.m_HTR_N_sub_hxrs = m_HTR_N_sub_hxrs; // Comes from constructor (constant) + core_inputs.m_W_dot_net_design = m_W_dot_net; // Comes from constructor (constant) + core_inputs.m_T_mc_in = m_T_mc_in; // Comes from constructor (constant) + core_inputs.m_T_t_in = m_T_t_in; // Comes from constructor (constant) + core_inputs.m_DP_LTR = m_DP_LTR; // Comes from constructor (constant) + core_inputs.m_DP_HTR = m_DP_HTR; // Comes from constructor (constant) + core_inputs.m_DP_PC_main = m_DP_PC_main; // Comes from constructor (constant) + core_inputs.m_DP_PHX = m_DP_PHX; // Comes from constructor (constant) + core_inputs.m_eta_mc = m_eta_mc; // Comes from constructor (constant) + core_inputs.m_eta_t = m_eta_t; // Comes from constructor (constant) + core_inputs.m_eta_t2 = m_eta_t2; // Comes from constructor (constant) + core_inputs.m_eta_generator = m_eta_generator; // Comes from constructor (constant) + core_inputs.m_frac_fan_power = m_frac_fan_power; // Comes from constructor (constant) + core_inputs.m_eta_fan = m_eta_fan; // Comes from constructor (constant) + core_inputs.m_deltaP_cooler_frac = m_deltaP_cooler_frac; // Comes from constructor (constant) + core_inputs.m_T_amb_des = m_T_amb_des; // Comes from constructor (constant) + core_inputs.m_elevation = m_elevation; // Comes from constructor (constant) + core_inputs.m_N_nodes_pass = m_N_nodes_pass; // Comes from constructor (constant) + core_inputs.m_mc_comp_model_code = m_mc_comp_model_code; // Comes from constructor (constant) + core_inputs.m_N_turbine = m_N_turbine; // Comes from constructor (constant) + core_inputs.m_yr_inflation = m_yr_inflation; // Comes from constructor (constant) + + // Handle design variables (check if fixed or free) + { + // Turbine Split Fraction + if (opt_par.m_fixed_split_frac == true) + core_inputs.m_split_frac = opt_par.m_split_frac_guess; + + // MC Outlet Pressure + if (opt_par.m_fixed_P_mc_out == true) + core_inputs.m_P_mc_out = opt_par.m_P_mc_out_guess; + + // Recuperator split fraction + double LT_frac_local = opt_par.m_LT_frac_guess; + if (ms_auto_opt_des_par.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA || ms_auto_opt_des_par.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA) + { + core_inputs.m_LTR_UA = ms_auto_opt_des_par.m_UA_rec_total * LT_frac_local; + core_inputs.m_HTR_UA = ms_auto_opt_des_par.m_UA_rec_total * (1.0 - LT_frac_local); + } + else + { + core_inputs.m_LTR_UA = ms_auto_opt_des_par.m_LTR_UA; //[kW/K] + core_inputs.m_HTR_UA = ms_auto_opt_des_par.m_HTR_UA; //[kW/K] + } + + // Pressure Ratio is calculated in callback + } + + } + + // Add applicable design variables to Optimizer + int index = 0; + + std::vector x(0); + std::vector lb(0); + std::vector ub(0); + std::vector scale(0); + + if (!auto_par.m_fixed_P_mc_out) + { + x.push_back(opt_par.m_P_mc_out_guess); + lb.push_back(100.0); + ub.push_back(m_P_high_limit); + scale.push_back(500.0); + + index++; + } + + if (!auto_par.m_fixed_PR_HP_to_LP) + { + x.push_back(opt_par.m_PR_HP_to_LP_guess); + lb.push_back(0.0001); + double PR_max = m_P_high_limit / 100.0; + ub.push_back(PR_max); + scale.push_back(0.2); + + index++; + } + + if (!opt_par.m_fixed_split_frac) + { + x.push_back(opt_par.m_split_frac_guess); + lb.push_back(0.0); + ub.push_back(0.99); + scale.push_back(0.05); + index++; + } + + if (!opt_par.m_fixed_LT_frac) + { + x.push_back(opt_par.m_LT_frac_guess); + lb.push_back(0.0); + ub.push_back(1.0); + scale.push_back(0.05); + + index++; + } + + // Make Optimizer (if there are variables to be optimized) + int error_code = 0; + C_sco2_tsf_core::S_sco2_tsf_in optimal_inputs_internal; + if (index > 0) + { + // Set up instance of nlopt class and set optimization parameters + nlopt::opt opt_des_cycle(nlopt::LN_SBPLX, index); + opt_des_cycle.set_lower_bounds(lb); + opt_des_cycle.set_upper_bounds(ub); + opt_des_cycle.set_initial_step(scale); + opt_des_cycle.set_xtol_rel(auto_par.m_des_opt_tol); + //opt_des_cycle.set_maxeval(50); + + // Set up core model that will be passed to objective function + C_sco2_tsf_core tsf_core; + tsf_core.set_inputs(core_inputs); + + // Make Tuple to pass in parameters + std::tuple par_tuple = { this, &auto_par, &opt_par, &tsf_core }; + + // Set max objective function + opt_des_cycle.set_max_objective(nlopt_tsf_optimize_par_func, &par_tuple); + double max_f = std::numeric_limits::quiet_NaN(); + + // Optimize + nlopt::result result_des_cycle = opt_des_cycle.optimize(x, max_f); + + // Check if forced stop + int flag = opt_des_cycle.get_force_stop(); + if (flag == true) + { + error_code = -1; + return error_code; + } + + // Get Optimal Input Case + error_code = x_to_inputs(x, auto_par, opt_par, core_inputs); + if (error_code != 0) + return error_code; + } + // Nothing to optimize + else + { + // Define P_mc_in (because pressure ratio and mc_out are constant) + core_inputs.m_P_mc_in = core_inputs.m_P_mc_out / opt_par.m_PR_HP_to_LP_guess; + + // Simulate Case (don't actually need to run...) + C_sco2_tsf_core core_model; + core_model.set_inputs(core_inputs); + error_code = core_model.solve(); + } + + // Set Optimal Inputs + optimal_inputs = core_inputs; + + return error_code; +} + +/// +/// Take optimizer array 'x', write appropriate values to S_sco2_tsf_in +/// +int C_TurbineSplitFlow_Cycle::x_to_inputs(const std::vector& x, + const S_auto_opt_design_parameters auto_par, + const S_opt_design_parameters opt_par, + C_sco2_tsf_core::S_sco2_tsf_in& core_inputs) +{ + // 'x' is array of inputs either being adjusted by optimizer or set constant + // Finish defining core_inputs based on current 'x' values + + int error_message = 0; + int index = 0; + + // Main compressor outlet pressure + + if (!auto_par.m_fixed_P_mc_out) + { + double P_mc_out = x[index]; + if (P_mc_out > m_P_high_limit) + return -1; + index++; + + // assign P_mc_out + core_inputs.m_P_mc_out = P_mc_out; + } + + + // Main compressor pressure ratio + double PR_mc_local = -999.9; + double P_mc_in = -999.9; + if (!opt_par.m_fixed_PR_HP_to_LP) + { + PR_mc_local = x[index]; + if (PR_mc_local > 50.0) + return -1; + index++; + P_mc_in = core_inputs.m_P_mc_out / PR_mc_local; + } + else + { + if (opt_par.m_PR_HP_to_LP_guess >= 0.0) + { + PR_mc_local = opt_par.m_PR_HP_to_LP_guess; + P_mc_in = core_inputs.m_P_mc_out / PR_mc_local; //[kPa] + } + else + { + P_mc_in = std::abs(opt_par.m_PR_HP_to_LP_guess); //[kPa] + } + } + + if (P_mc_in >= core_inputs.m_P_mc_out) + return -1; + if (P_mc_in <= 100.0) + return -1; + + core_inputs.m_P_mc_in = P_mc_in; + + // Turbine split fraction + if (!opt_par.m_fixed_split_frac) + { + core_inputs.m_split_frac = x[index]; + if (core_inputs.m_split_frac < 0.0) + return -1; + index++; + } + + // Recuperator split fraction + double LT_frac_local = -999.9; + double LTR_UA, HTR_UA; + if (!opt_par.m_fixed_LT_frac) + { + LT_frac_local = x[index]; + if (LT_frac_local > 1.0 || LT_frac_local < 0.0) + return -1; + index++; + + if (auto_par.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA || auto_par.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA) + { + LTR_UA = auto_par.m_UA_rec_total * LT_frac_local; + HTR_UA = auto_par.m_UA_rec_total * (1.0 - LT_frac_local); + + // ASSIGN LTR_UA and HTR_UA + core_inputs.m_LTR_UA = LTR_UA; + core_inputs.m_HTR_UA = HTR_UA; + } + } + + return 0; +} + +/// +/// Set optimized variables to NaN, to protect them from misuse +/// +int C_TurbineSplitFlow_Cycle::clear_x_inputs(const std::vector& x, + const S_auto_opt_design_parameters auto_par, + const S_opt_design_parameters opt_par, + C_sco2_tsf_core::S_sco2_tsf_in& core_inputs) +{ + // 'x' is array of inputs either being adjusted by optimizer or set constant + // Finish defining core_inputs based on current 'x' values + + int error_message = 0; + int index = 0; + + // Main compressor outlet pressure + + if (!auto_par.m_fixed_P_mc_out) + { + core_inputs.m_P_mc_out = std::numeric_limits::quiet_NaN(); + } + + + core_inputs.m_P_mc_in = std::numeric_limits::quiet_NaN(); + + // Turbine split fraction + if (!opt_par.m_fixed_split_frac) + { + core_inputs.m_split_frac = std::numeric_limits::quiet_NaN(); + } + + // Recuperator split fraction + if (!opt_par.m_fixed_LT_frac) + { + if (auto_par.m_LTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA || auto_par.m_HTR_target_code == NS_HX_counterflow_eqs::OPTIMIZE_UA) + { + // ASSIGN LTR_UA and HTR_UA + core_inputs.m_LTR_UA = std::numeric_limits::quiet_NaN();; + core_inputs.m_HTR_UA = std::numeric_limits::quiet_NaN();; + } + } + + return 0; +} + +/// +/// Calculate Objective Value (does not consider total UA minimization) +/// +double C_TurbineSplitFlow_Cycle::calc_objective(const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + const C_sco2_tsf_core& tsf_core) +{ + double obj = 0; + double eta = tsf_core.m_outputs.m_eta_thermal; + + // Hit a target thermal efficiency + if (opt_par.m_design_method == 1) + { + double eta_error = std::min(eta - opt_par.m_eta_thermal_target, 0.0); + obj = 1.0 - std::abs(eta_error); + } + // Maximize thermal efficiency + else + { + obj = eta; + } + + // Penalize for PHX sco2 temp diff (if necessary) + double penalty = 0; + if (auto_par.m_des_objective_type == 2) + { + double phx_deltaT = tsf_core.m_outputs.m_temp[C_sco2_cycle_core::TURB_IN] - tsf_core.m_outputs.m_temp[C_sco2_cycle_core::LTR_HP_OUT]; + double under_min_deltaT = std::max(0.0, auto_par.m_min_phx_deltaT - phx_deltaT); + + double percent_err = under_min_deltaT / auto_par.m_min_phx_deltaT; + + penalty = percent_err; + } + + obj = obj - penalty; + + return obj; +} + +// ********************************************************************************** PUBLIC Methods C_TurbineSplitFlow_Cycle (: C_sco2_cycle_core) + +/// +/// Optimize Cycle Design for FIXED total recuperator UA +/// +/// +int C_TurbineSplitFlow_Cycle::auto_opt_design(S_auto_opt_design_parameters& auto_opt_des_par_in) +{ + // Reset Counter + m_opt_iteration_count = 0; + + // Collect auto_opt_des parameters + ms_auto_opt_des_par = auto_opt_des_par_in; + + int auto_opt_des_error_code = 0; + + // Design cycle + auto_opt_design_core(auto_opt_des_error_code); + + return auto_opt_des_error_code; +} + +/// +/// Optimize Cycle design to hit target eta (optimize total recuperator UA) +/// +/// +/// +/// +int C_TurbineSplitFlow_Cycle::auto_opt_design_hit_eta(S_auto_opt_design_hit_eta_parameters& auto_opt_des_hit_eta_in, std::string& error_msg) +{ + return C_sco2_cycle_core::E_cycle_error_msg::E_NOT_SUPPORTED; +} + +// ********************************************************************************** PUBLIC Objective Functions (internal use only) + +/// +/// Objective Function for total UA (outermost layer) +/// Total UA -> Bypass Fraction -> pressure, split UA, recomp frac +/// +/// ONLY Objective Value +double C_TurbineSplitFlow_Cycle::optimize_totalUA_return_objective_metric(const std::vector& x, + const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par +) +{ + return 0; +} + +/// +/// Objective Function for general parameters +/// Total UA -> pressure, split UA, recomp frac +/// +/// ONLY Objective Value +double C_TurbineSplitFlow_Cycle::optimize_par_return_objective_metric(const std::vector& x, + const S_auto_opt_design_parameters& auto_par, + const S_opt_design_parameters& opt_par, + C_sco2_tsf_core& tsf_core) +{ + // Update counter + m_opt_iteration_count++; // global + + // Modify core inputs with Variable Parameters + int error_code = x_to_inputs(x, auto_par, opt_par, tsf_core.m_inputs); + + if (error_code != 0) + return -100000; + + // At this point, have fully defined core input struct + // Run the core model + error_code = tsf_core.solve(); + + // Set Objective + double objective_metric = -10000000000.0; + if (error_code == 0) + { + objective_metric = calc_objective(auto_par, opt_par, tsf_core); + } + + // Clear optimized inputs + clear_x_inputs(x, auto_par, opt_par, tsf_core.m_inputs); + + return objective_metric; +} + +// ********************************************************************************** Off Design Functions + + +void C_TurbineSplitFlow_Cycle::reset_ms_od_turbo_bal_csp_solved() +{ +} + +int C_TurbineSplitFlow_Cycle::off_design_fix_shaft_speeds(S_od_par& od_phi_par_in, double od_tol) +{ + return 0; +} + +int C_TurbineSplitFlow_Cycle::solve_OD_all_coolers_fan_power(double T_amb, double od_tol, double& W_dot_fan) +{ + return 0; +} + +int C_TurbineSplitFlow_Cycle::solve_OD_mc_cooler_fan_power(double T_amb, double od_tol, double& W_dot_mc_cooler_fan, double& P_co2_out) +{ + return 0; +} + +int C_TurbineSplitFlow_Cycle::solve_OD_pc_cooler_fan_power(double T_amb, double od_tol, double& W_dot_pc_cooler_fan, double& P_co2_out) +{ + return 0; +} + +double C_TurbineSplitFlow_Cycle::get_od_temp(int n_state_point) +{ + return 0.0; +} + +double C_TurbineSplitFlow_Cycle::get_od_pres(int n_state_point) +{ + return 0.0; +} + +void C_TurbineSplitFlow_Cycle::check_od_solution(double& diff_m_dot, double& diff_E_cycle, double& diff_Q_LTR, double& diff_Q_HTR) +{ +} + +void C_TurbineSplitFlow_Cycle::set_od_temp(int n_state_point, double temp_K) +{ +} + +void C_TurbineSplitFlow_Cycle::set_od_pres(int n_state_point, double pres_kPa) +{ +} + +void C_TurbineSplitFlow_Cycle::off_design_recompressor(double T_in, double P_in, double m_dot, double P_out, double tol, int& error_code, double& T_out) +{ +} + +void C_TurbineSplitFlow_Cycle::estimate_od_turbo_operation(double T_mc_in, double P_mc_in, double f_recomp, double T_t_in, double phi_mc, int& mc_error_code, double& mc_w_tip_ratio, double& P_mc_out, int& rc_error_code, double& rc_w_tip_ratio, double& rc_phi, bool is_update_ms_od_solved) +{ +} + + +// ********************************************************************************** PUBLIC Methods defined outside of any class + +double nlopt_tsf_optimize_totalUA_func(const std::vector& x, std::vector& grad, void* data) +{ + return 0; +} + +double nlopt_tsf_optimize_par_func(const std::vector& x, std::vector& grad, void* data) +{ + // Unpack Data Tuple + std::tuple* data_tuple + = static_cast* > (data); + + C_TurbineSplitFlow_Cycle* frame = std::get<0>(*data_tuple); + const C_TurbineSplitFlow_Cycle::S_auto_opt_design_parameters* auto_opt_par = std::get<1>(*data_tuple); + const C_TurbineSplitFlow_Cycle::S_opt_design_parameters* opt_par = std::get<2>(*data_tuple); + C_sco2_tsf_core* tsf_core = std::get<3>(*data_tuple); + + if (frame != NULL) + return frame->optimize_par_return_objective_metric(x, *auto_opt_par, *opt_par, *tsf_core); + else + return 0.0; +} + diff --git a/tcs/sco2_turbinesplitflow_cycle.h b/tcs/sco2_turbinesplitflow_cycle.h new file mode 100644 index 000000000..21360294f --- /dev/null +++ b/tcs/sco2_turbinesplitflow_cycle.h @@ -0,0 +1,431 @@ +/* +BSD 3-Clause License + +Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/ssc/blob/develop/LICENSE +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __SCO2_TURBINESPLITFLOW_ +#define __SCO2_TURBINESPLITFLOW_ + +#include "sco2_cycle_components.h" +#include "sco2_cycle_templates.h" + +#include "heat_exchangers.h" +#include "CO2_properties.h" + +#include "numeric_solvers.h" + + +#include "nlopt.hpp" +#include "nlopt_callbacks.h" + + +// Core Turbine Split Flow Model +// This class is purely for solving the cycle +// No optimization +class C_sco2_tsf_core +{ +public: + + // Defines turbine split flow input variables (no optimized variables) + struct S_sco2_tsf_in + { + + double m_P_mc_in; //[kPa] Compressor inlet pressure + double m_P_mc_out; //[kPa] Compressor outlet pressure + + // LTR thermal design + int m_LTR_target_code; //[-] 1 = UA, 2 = min dT, 3 = effectiveness + double m_LTR_UA; //[kW/K] target LTR conductance + double m_LTR_min_dT; //[K] target LTR minimum temperature difference + double m_LTR_eff_target; //[-] target LTR effectiveness + double m_LTR_eff_max; //[-] Maximum allowable effectiveness in LT recuperator + NS_HX_counterflow_eqs::E_UA_target_type m_LTR_od_UA_target_type; + int m_LTR_N_sub_hxrs; //[-] Number of sub-hxs to use in hx model + + // HTR thermal design + int m_HTR_target_code; //[-] 1 = UA, 2 = min dT, 3 = effectiveness + double m_HTR_UA; //[kW/K] target HTR conductance + double m_HTR_min_dT; //[K] target HTR min temperature difference + double m_HTR_eff_target; //[-] target HTR effectiveness + double m_HTR_eff_max; //[-] Maximum allowable effectiveness in HT recuperator + NS_HX_counterflow_eqs::E_UA_target_type m_HTR_od_UA_target_type; + int m_HTR_N_sub_hxrs; //[-] Number of sub-hxs to use in hx model + + double m_split_frac; // [-] Fraction of flow that goes to secondary turbine (0 = no secondary turbine) + + double m_des_tol; //[-] Convergence tolerance + + // Air cooler parameters + bool m_is_des_air_cooler; //[-] False will skip physical air cooler design. UA will not be available for cost models. + + double m_W_dot_net_design; //[kWe] Target net cycle power + double m_T_mc_in; //[K] Compressor inlet temperature + double m_T_t_in; //[K] Turbine inlet temperature + std::vector m_DP_LTR; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) + std::vector m_DP_HTR; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) + std::vector m_DP_PC_main; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) + std::vector m_DP_PHX; //(cold, hot) positive values are absolute [kPa], negative values are relative (-) + double m_eta_mc; //[-] design-point efficiency of the main compressor; isentropic if positive, polytropic if negative + double m_eta_t; //[-] design-point efficiency of the turbine; isentropic if positive, polytropic if negative + double m_eta_t2; //[-] design-point efficiency of the secondary turbine; isentropic if positive, polytropic if negative + double m_eta_generator; //[-] Mechanical-to-electrical efficiency of generator + double m_frac_fan_power; //[-] Fraction of total cycle power 'S_des_par_cycle_dep.m_W_dot_fan_des' consumed by air fan + double m_eta_fan; //[-] Fan isentropic efficiency + double m_deltaP_cooler_frac; //[-] Fraction of high side (of cycle, i.e. comp outlet) pressure that is allowed as pressure drop to design the ACC + double m_T_amb_des; //[K] Design point ambient temperature + double m_elevation; //[m] Elevation (used to calculate ambient pressure) + int m_N_nodes_pass; //[-] Number of nodes per pass + int m_mc_comp_model_code; // Main compressor model code + int m_N_turbine; //[rpm] Turbine rpm + + double m_yr_inflation; //[yr] Inflation target year + + S_sco2_tsf_in() + { + m_P_mc_in = m_P_mc_out = + m_LTR_UA = m_LTR_min_dT = m_LTR_eff_target = m_LTR_eff_max = + m_HTR_UA = m_HTR_min_dT = m_HTR_eff_target = m_HTR_eff_max = + m_split_frac = + m_des_tol = + m_W_dot_net_design = + m_T_mc_in = + m_T_t_in = + m_eta_mc = + m_eta_t = + m_eta_t2 = + m_eta_generator = + m_frac_fan_power = + m_eta_fan = + m_deltaP_cooler_frac = + m_T_amb_des = + m_elevation = + m_yr_inflation = + std::numeric_limits::quiet_NaN(); + + m_N_nodes_pass = 0; + m_LTR_N_sub_hxrs = 0; + m_HTR_N_sub_hxrs = 0; + m_mc_comp_model_code = -1; + m_N_turbine = -1; + + // Recuperator design target codes + m_LTR_target_code = 1; // default to target conductance + m_LTR_od_UA_target_type = NS_HX_counterflow_eqs::E_UA_target_type::E_calc_UA; + m_HTR_target_code = 1; // default to target conductance + m_HTR_od_UA_target_type = NS_HX_counterflow_eqs::E_UA_target_type::E_calc_UA; + + + // Air cooler default + m_is_des_air_cooler = true; + + } + + }; + + // Defines sco2 htr bypass output variables (no optimized variables) + struct S_sco2_tsf_out + { + int m_error_code; + C_turbine m_t; // Turbine model + C_turbine m_t2; // Secondary Turbine model + C_comp_multi_stage m_mc_ms; // Main Compressor Model + C_HeatExchanger m_PHX, m_PC; // Primary and Cooler Heat Exchanger Models + C_HX_co2_to_co2_CRM mc_LT_recup; // LTR + C_HX_co2_to_co2_CRM mc_HT_recup; // HTR + C_CO2_to_air_cooler mc_air_cooler; // Air Cooler + std::vector m_temp, m_pres, m_enth, m_entr, m_dens; // thermodynamic states (K, kPa, kJ/kg, kJ/kg-K, kg/m3) + double m_w_t, m_w_t2, m_w_mc; // [kJ/kg] specific work of turbine, main compressor, recompressor + double m_m_dot_t, m_m_dot_t2, m_m_dot_mc; // [kg/s] sco2 Mass flow in turbine, secondary turbine, and main compressor (total) + double m_Q_dot_LT, m_Q_dot_HT; // [kWt] Heat Transfer in LTR, HTR + double m_W_dot_mc, m_W_dot_t, m_W_dot_t2; // [kWt] Energy consumed by main compressor, produced by turbine and secondary turbine + double m_W_dot_net; // [kWt] ACTUAL produced net work in system + double m_W_dot_air_cooler; // [kWe] Energy consumed by air cooler + double m_Q_dot_air_cooler; // [kWt] Heat rejected by air cooler + double m_Q_dot_LTR_LP, m_Q_dot_LTR_HP, m_Q_dot_HTR_LP, m_Q_dot_HTR_HP; // kWt Heat change on LTR low pressure, etc... + double m_Q_dot_total; // [kWt] Total heat entering sco2 + double m_Q_dot_PHX; // [kWt] Energy exchange in PHX, BPX + double m_eta_thermal; // Thermal Efficiency + + S_sco2_tsf_out() + { + Init(); + } + + void Init() + { + m_w_t = m_w_mc + = m_m_dot_t = m_m_dot_t2 = m_m_dot_mc + = m_Q_dot_LT = m_Q_dot_HT + = m_W_dot_mc = m_W_dot_t = m_W_dot_t2 + = m_W_dot_net = m_W_dot_air_cooler = m_Q_dot_air_cooler + = m_Q_dot_LTR_LP = m_Q_dot_LTR_HP = m_Q_dot_HTR_LP = m_Q_dot_HTR_HP + = m_Q_dot_total = m_Q_dot_PHX + = m_eta_thermal + = std::numeric_limits::quiet_NaN(); + + m_error_code = -1; + + // Clear and Size Output Vectors + m_temp.resize(C_sco2_cycle_core::END_SCO2_STATES); + std::fill(m_temp.begin(), m_temp.end(), std::numeric_limits::quiet_NaN()); + m_pres = m_enth = m_entr = m_dens = m_temp; + } + }; + + +private: + CO2_state m_co2_props; + + class C_mono_tsf_core_HTR_des : public C_monotonic_equation + { + private: + C_sco2_tsf_core* m_tsf_cycle; + + public: + C_mono_tsf_core_HTR_des(C_sco2_tsf_core* tsf_cycle) + { + m_tsf_cycle = tsf_cycle; + } + + virtual int operator()(double T_HTR_HP_OUT_guess /*K*/, double* diff_T_HTR_HP_out /*K*/) + { + return m_tsf_cycle->solve_HTR(T_HTR_HP_OUT_guess, diff_T_HTR_HP_out); + }; + }; + + int solve_HTR(double T_HTR_HP_OUT_guess, double* diff_T_HTR_HP_out); + + void initialize_solve(); + +public: + // Inputs Struct + S_sco2_tsf_in m_inputs; + + // Outputs Struct + S_sco2_tsf_out m_outputs; + + // Public Methods + C_sco2_tsf_core() + { + m_outputs.Init(); + m_co2_props = CO2_state(); + } + + void set_inputs(S_sco2_tsf_in inputs) { m_inputs = inputs; }; + + int solve(); + + int finalize_design(C_sco2_cycle_core::S_design_solved& design_solved); + + void reset(); +}; + + + +class C_TurbineSplitFlow_Cycle : public C_sco2_cycle_core +{ +public: + + // Struct to store optimization variables + struct S_opt_design_parameters + { + + double m_P_mc_out_guess; //[kPa] Initial guess for main compressor outlet pressure + bool m_fixed_P_mc_out; //[-] if true, P_mc_out is fixed at P_mc_out_guess + + double m_PR_HP_to_LP_guess; //[-] Initial guess for ratio of P_mc_out to P_LP_in + bool m_fixed_PR_HP_to_LP; //[-] if true, ratio of P_mc_out to P_mc_in is fixed at PR_mc_guess + + double m_split_frac_guess; //[-] Initial guess for design-point split flow fraction (0.0 is no secondary turbine) + bool m_fixed_split_frac; //[-] if true, split_frac is fixed at split_frac_guess + + double m_LT_frac_guess; //[-] Initial guess for fraction of UA_rec_total that is in the low-temperature recuperator + bool m_fixed_LT_frac; //[-] if true, LT_frac is fixed at LT_frac_guess + + // ADDED + int m_design_method; //[] Design Method [1] Optimize total UA for target eta, [2] Optimize UA split ratio, [3] set LTR HTR directly + double m_eta_thermal_target; //[] Cycle thermal efficiency target (used by total UA optimization) + double m_UA_recup_total_max; //[kW/K] Maximum recuperator conductance (for total UA optimizer) + double m_UA_recup_total_min; //[kW/K] Minimum recuperator conductance (for total UA optimizer) + + + S_opt_design_parameters() + { + m_P_mc_out_guess = m_PR_HP_to_LP_guess = m_split_frac_guess = m_LT_frac_guess = + m_eta_thermal_target = + m_UA_recup_total_max = m_UA_recup_total_min = + std::numeric_limits::quiet_NaN(); + + + m_design_method = -1; + m_fixed_split_frac = false; + m_fixed_LT_frac = false; + m_fixed_PR_HP_to_LP = false; + m_fixed_P_mc_out = false; + + } + + + }; + +private: + + int m_opt_iteration_count; // Counter of bypass iterations + + // TSF specific parameters + double m_eta_t2; + + // Optimal tsf core class (contains all results and component data) + C_sco2_tsf_core m_optimal_tsf_core; + + void auto_opt_design_core(int& error_code); + + void auto_opt_design_hit_eta_core(int& error_code, const double eta_thermal_target); + + int optimize_totalUA(const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, C_sco2_tsf_core::S_sco2_tsf_in& optimal_inputs); + + int optimize_par(const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, C_sco2_tsf_core::S_sco2_tsf_in& optimal_inputs); + + int x_to_inputs(const std::vector& x, const S_auto_opt_design_parameters auto_par, const S_opt_design_parameters opt_par, C_sco2_tsf_core::S_sco2_tsf_in& core_inputs); + + int clear_x_inputs(const std::vector& x, const S_auto_opt_design_parameters auto_par, const S_opt_design_parameters opt_par, C_sco2_tsf_core::S_sco2_tsf_in& core_inputs); + + double calc_objective(const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, const C_sco2_tsf_core& tsf_core); + + +protected: + + +public: + + C_TurbineSplitFlow_Cycle(C_sco2_cycle_core::E_turbo_gen_motor_config turbo_gen_motor_config, + double eta_generator, + double T_mc_in, + double W_dot_net, + double T_t_in, double P_high_limit, + std::vector DP_LTR, std::vector DP_HTR, + std::vector DP_PC_main, std::vector DP_PHX, + int LTR_N_sub_hxrs, int HTR_N_sub_hxrs, + double eta_mc, int mc_comp_model_code, + double eta_rc, + double eta_t, double eta_t2, + double N_turbine, + double frac_fan_power, double eta_fan, double deltaP_cooler_frac, + int N_nodes_pass, + double T_amb_des, double elevation, + double m_yr_inflation) : + C_sco2_cycle_core(turbo_gen_motor_config, + eta_generator, + T_mc_in, + W_dot_net, + T_t_in, P_high_limit, + DP_LTR, DP_HTR, + DP_PC_main, DP_PHX, + LTR_N_sub_hxrs, HTR_N_sub_hxrs, + eta_mc, mc_comp_model_code, + eta_rc, + eta_t, N_turbine, + frac_fan_power, eta_fan, deltaP_cooler_frac, + N_nodes_pass, + T_amb_des, elevation, m_yr_inflation), + m_eta_t2(eta_t2) + { + m_opt_iteration_count = 0; + } + + ~C_TurbineSplitFlow_Cycle() {}; + + // Overridden - Optimize Cycle (fixed total UA) + int auto_opt_design(S_auto_opt_design_parameters& auto_opt_des_par_in); + + // Overridden - Optimize Cycle for target eta (variable total UA) + int auto_opt_design_hit_eta(S_auto_opt_design_hit_eta_parameters& auto_opt_des_hit_eta_in, std::string& error_msg); + + // Objective Functions (internal use only) + double optimize_totalUA_return_objective_metric(const std::vector& x, const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par); + double optimize_par_return_objective_metric(const std::vector& x, const S_auto_opt_design_parameters& auto_par, const S_opt_design_parameters& opt_par, C_sco2_tsf_core& tsf_core); + + // Off Design + + void reset_ms_od_turbo_bal_csp_solved(); + + int off_design_fix_shaft_speeds(S_od_par& od_phi_par_in, double od_tol /*-*/); + + virtual int solve_OD_all_coolers_fan_power(double T_amb /*K*/, double od_tol /*-*/, double& W_dot_fan /*MWe*/); + + virtual int solve_OD_mc_cooler_fan_power(double T_amb /*K*/, double od_tol /*-*/, double& W_dot_mc_cooler_fan /*MWe*/, double& P_co2_out /*kPa*/); + + virtual int solve_OD_pc_cooler_fan_power(double T_amb /*K*/, double od_tol /*-*/, double& W_dot_pc_cooler_fan /*MWe*/, double& P_co2_out /*kPa*/); + + double get_od_temp(int n_state_point); + + double get_od_pres(int n_state_point); + + virtual void check_od_solution(double& diff_m_dot, double& diff_E_cycle, + double& diff_Q_LTR, double& diff_Q_HTR); + + void set_od_temp(int n_state_point, double temp_K); + + void set_od_pres(int n_state_point, double pres_kPa); + + void off_design_recompressor(double T_in, double P_in, double m_dot, double P_out, double tol /*-*/, int& error_code, double& T_out); + + void estimate_od_turbo_operation(double T_mc_in /*K*/, double P_mc_in /*kPa*/, double f_recomp /*-*/, double T_t_in /*K*/, double phi_mc /*-*/, + int& mc_error_code, double& mc_w_tip_ratio /*-*/, double& P_mc_out /*kPa*/, + int& rc_error_code, double& rc_w_tip_ratio /*-*/, double& rc_phi /*-*/, + bool is_update_ms_od_solved = false); + + const C_comp_multi_stage::S_od_solved* get_rc_od_solved() + { + throw(C_csp_exception("Turbine split flow config does not have a recompressor")); + //return m_rc_ms.get_od_solved(); + } + + /*const S_od_turbo_bal_csp_solved* get_od_turbo_bal_csp_solved() + { + return &ms_od_turbo_bal_csp_solved; + } + + double get_max_target() + { + return m_biggest_target; + }*/ + + + +}; + +// Nlopt objective functions +double nlopt_tsf_optimize_totalUA_func(const std::vector& x, std::vector& grad, void* data); + +double nlopt_tsf_optimize_par_func(const std::vector& x, std::vector& grad, void* data); + + +#endif diff --git a/test/ssc_test/cmod_sco2_csp_system_test.cpp b/test/ssc_test/cmod_sco2_csp_system_test.cpp index 68b079701..5586540ce 100644 --- a/test/ssc_test/cmod_sco2_csp_system_test.cpp +++ b/test/ssc_test/cmod_sco2_csp_system_test.cpp @@ -35,12 +35,102 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //#include "tcsmolten_salt_defaults.h" #include "csp_common_test.h" #include "vs_google_test_explorer_namespace.h" +#include //#include "../input_cases/code_generator_utilities.h" namespace sco2_tests {} using namespace sco2_tests; +double kTol = 0.01; + +// Helper functions + +ssc_data_t get_default_sco2_pars() +{ + ssc_data_t data = ssc_data_create(); + + // Basic design parameters + ssc_data_set_number(data, "htf", 17); // Solar salt + ssc_data_set_number(data, "T_htf_hot_des", 670.0); // [C] HTF design hot temperature + ssc_data_set_number(data, "dT_PHX_hot_approach", 20.0); // [C/K] Temperature difference between hot HTF and turbine inlet + ssc_data_set_number(data, "T_amb_des", 35.0); // [C] Ambient temperature at design + ssc_data_set_number(data, "dT_mc_approach", 6.0); // [C] Temperature difference between main compressor CO2 inlet and ambient air + ssc_data_set_number(data, "site_elevation", 588); // [m] Elevation + ssc_data_set_number(data, "W_dot_net_des", 50.0); // [MWe] Design cycle power output + + // Cycle configuration + ssc_data_set_number(data, "cycle_config", 1); // [1] = RC, [2] = PC + ssc_data_set_number(data, "design_method", 2); // [-] 1 = specify efficiency, 2 = specify total recup UA, 3 = Specify each recup design + ssc_data_set_number(data, "eta_thermal_des", 0.44); // [-] Target power cycle thermal efficiency + ssc_data_set_number(data, "UA_recup_tot_des", 15000.0); // [kW/K] Total recuperator UA (15 * 1000 * 50.0 / 50.0) + + // Pressure and recompression settings + ssc_data_set_number(data, "is_recomp_ok", 1); // 1 = Yes, 0 = simple cycle only + ssc_data_set_number(data, "is_P_high_fixed", 1); // 0 = No, optimize. 1 = Yes + ssc_data_set_number(data, "is_PR_fixed", 0); // 0 = No, >0 = fixed pressure ratio + ssc_data_set_number(data, "is_IP_fixed", 0); // 0 = No, >0 = fixed HP-IP pressure ratio + + // Convergence criteria + ssc_data_set_number(data, "rel_tol", 3); // [-] Solver relative tolerance exponent + + // Component efficiencies + ssc_data_set_number(data, "eta_isen_mc", 0.85); // [-] Main compressor isentropic efficiency + ssc_data_set_number(data, "eta_isen_rc", 0.85); // [-] Recompressor isentropic efficiency + ssc_data_set_number(data, "eta_isen_pc", 0.85); // [-] Precompressor isentropic efficiency + ssc_data_set_number(data, "eta_isen_t", 0.90); // [-] Turbine isentropic efficiency + + // Pressure limits + ssc_data_set_number(data, "P_high_limit", 25); // [MPa] Cycle high pressure limit + + // Recuperators + double eff_max = 1.0; + double deltaP_recup_HP = 0.0056; // [-] = 0.14[MPa]/25[MPa] + double deltaP_recup_LP = 0.0311; // [-] = 0.28[MPa]/9[MPa] + + // LTR (Low Temperature Recuperator) + ssc_data_set_number(data, "LTR_design_code", 3); // 1 = UA, 2 = min dT, 3 = effectiveness + ssc_data_set_number(data, "LTR_UA_des_in", 2200.0); // [kW/K] + ssc_data_set_number(data, "LTR_min_dT_des_in", 12.0); // [C] + ssc_data_set_number(data, "LTR_eff_des_in", 0.895); // [-] + ssc_data_set_number(data, "LT_recup_eff_max", eff_max); // [-] + ssc_data_set_number(data, "LTR_LP_deltaP_des_in", deltaP_recup_LP); // [-] + ssc_data_set_number(data, "LTR_HP_deltaP_des_in", deltaP_recup_HP); // [-] + + // HTR (High Temperature Recuperator) + ssc_data_set_number(data, "HTR_design_code", 3); // 1 = UA, 2 = min dT, 3 = effectiveness + ssc_data_set_number(data, "HTR_UA_des_in", 2800.0); // [kW/K] + ssc_data_set_number(data, "HTR_min_dT_des_in", 19.2); // [C] + ssc_data_set_number(data, "HTR_eff_des_in", 0.945); // [-] + ssc_data_set_number(data, "HT_recup_eff_max", eff_max); // [-] + ssc_data_set_number(data, "HTR_LP_deltaP_des_in", deltaP_recup_LP); // [-] + ssc_data_set_number(data, "HTR_HP_deltaP_des_in", deltaP_recup_HP); // [-] + + // PHX (Primary Heat Exchanger) + ssc_data_set_number(data, "PHX_co2_deltaP_des_in", deltaP_recup_HP); // [-] + ssc_data_set_number(data, "dT_PHX_cold_approach", 20); // [C/K] + + // Air Cooler + ssc_data_set_number(data, "deltaP_cooler_frac", 0.005); // [-] + ssc_data_set_number(data, "fan_power_frac", 0.02); // [-] + + return data; +} + +void check_result_vals(CmodUnderTest& sco2, std::unordered_map result_map) +{ + for (const auto& result : result_map) + { + std::string key = result.first; + double result_expected = result.second; + double result_actual = sco2.GetOutput(key); + + ASSERT_NEAR(result_expected, result_actual, kTol); + } + + return; +} + //========Tests=================================================================================== NAMESPACE_TEST(sco2_tests, SCO2Cycle, Parametrics) { @@ -119,7 +209,7 @@ NAMESPACE_TEST(sco2_tests, SCO2Cycle, Parametrics) EXPECT_NEAR_FRAC(sco2.GetOutput("m_dot_htf_des"), 513.344, kErrorToleranceLo); EXPECT_NEAR_FRAC(sco2.GetOutput("m_dot_co2_full"), 410.528, kErrorToleranceLo); EXPECT_NEAR_FRAC(sco2.GetOutput("P_comp_in"), 7.67490, kErrorToleranceLo); - EXPECT_NEAR_FRAC(sco2.GetOutput("cycle_cost"), 53.2909, kErrorToleranceLo); + EXPECT_NEAR_FRAC(sco2.GetOutput("cycle_cost"), 61.122, kErrorToleranceLo); std::vector eta_thermal_od_exp{ 0.50689, 0.507197 }; EXPECT_FLOATS_NEARLY_EQ(sco2.GetOutputVector("eta_thermal_od"), eta_thermal_od_exp, kErrorToleranceLo*eta_thermal_od_exp[0]); @@ -133,3 +223,208 @@ NAMESPACE_TEST(sco2_tests, SCO2Cycle, Parametrics) } } + + +// Design method 2 (optimize with fixed total UA) +NAMESPACE_TEST(sco2_design_tests, SCO2Design, recompression_default) +{ + // This test is design method 2, maximizing efficiency using a fixed total UA, + // optimizing min pressure, UA split, and flow fractions. + + ssc_data_t data = get_default_sco2_pars(); + CmodUnderTest sco2 = CmodUnderTest("sco2_csp_system", data); + int errors = sco2.RunModule(); + + // Check for errors + ASSERT_TRUE(errors == 0); + + // Define known results + std::unordered_map result_dict; + result_dict["eta_thermal_calc"] = 0.46326288645840497; + result_dict["T_htf_cold_des"] = 509.7489245863944; + result_dict["cycle_cost"] = 62.85323351704139; + + // Check expected vs actual results + check_result_vals(sco2, result_dict); +} + +NAMESPACE_TEST(sco2_design_tests, SCO2Design, simple_default) +{ + // This test is design method 2, maximizing efficiency using a fixed total UA, + // optimizing min pressure, UA split, and flow fractions. + + // Get default parameters + ssc_data_t data = get_default_sco2_pars(); + + // Set test specific values + ssc_data_set_number(data, "is_recomp_ok", 0); + + // Call cmod + CmodUnderTest sco2 = CmodUnderTest("sco2_csp_system", data); + int errors = sco2.RunModule(); + + // Check for errors + ASSERT_TRUE(errors == 0); + + // Define known results + std::unordered_map result_dict; + result_dict["eta_thermal_calc"] = 0.43185849042562524; + result_dict["T_htf_cold_des"] = 466.8855038441511; + result_dict["cycle_cost"] = 57.323164238332325; + + // Check expected vs actual results + check_result_vals(sco2, result_dict); +} + +NAMESPACE_TEST(sco2_design_tests, SCO2Design, partial_default) +{ + // This test is design method 2, maximizing efficiency using a fixed total UA, + // optimizing min pressure, UA split, and flow fractions. + + // Get default parameters + ssc_data_t data = get_default_sco2_pars(); + + // Set test specific values + ssc_data_set_number(data, "cycle_config", 2); + + // Call cmod + CmodUnderTest sco2 = CmodUnderTest("sco2_csp_system", data); + int errors = sco2.RunModule(); + + // Check for errors + ASSERT_TRUE(errors == 0); + + // Define known results + std::unordered_map result_dict; + result_dict["eta_thermal_calc"] = 0.4680242988429887; + result_dict["T_htf_cold_des"] = 454.4556200450701; + result_dict["cycle_cost"] = 66.2574259286688; + + // Check expected vs actual results + check_result_vals(sco2, result_dict); +} + +NAMESPACE_TEST(sco2_design_tests, SCO2Design, htrbp_default) +{ + // This test is design method 2, maximizing efficiency using a fixed total UA, + // optimizing min pressure, UA split, and flow fractions. + // ALSO, htr bp is varying the bp frac to hit the target outlet temperature + + // Get default parameters + ssc_data_t data = get_default_sco2_pars(); + + // Set test specific values + ssc_data_set_number(data, "cycle_config", 3); + ssc_data_set_number(data, "is_bypass_ok", 1); + ssc_data_set_number(data, "T_bypass_target", 400); + ssc_data_set_number(data, "T_target_is_HTF", 1); + ssc_data_set_number(data, "deltaT_bypass", 0); + ssc_data_set_number(data, "set_HTF_mdot", 0); + + // Call cmod + CmodUnderTest sco2 = CmodUnderTest("sco2_csp_system", data); + int errors = sco2.RunModule(); + + // Check for errors + ASSERT_TRUE(errors == 0); + + // Define known results + std::unordered_map result_dict; + result_dict["eta_thermal_calc"] = 0.33776551440239994; + result_dict["T_htf_cold_des"] = 400.0; + result_dict["cycle_cost"] = 68.82623980549134; + + // Check expected vs actual results + check_result_vals(sco2, result_dict); +} + +NAMESPACE_TEST(sco2_design_tests, SCO2Design, tsf_default) +{ + // This test is design method 2, maximizing efficiency using a fixed total UA, + // optimizing min pressure, UA split, and flow fractions. + + // Get default parameters + ssc_data_t data = get_default_sco2_pars(); + + // Set test specific values + ssc_data_set_number(data, "cycle_config", 4); + ssc_data_set_number(data, "is_turbine_split_ok", 1); + ssc_data_set_number(data, "eta_isen_t2", 0.90); + + // Call cmod + CmodUnderTest sco2 = CmodUnderTest("sco2_csp_system", data); + int errors = sco2.RunModule(); + + // Check for errors + ASSERT_TRUE(errors == 0); + + // Define known results + std::unordered_map result_dict; + result_dict["eta_thermal_calc"] = 0.40642770633106834; + result_dict["T_htf_cold_des"] = 306.45863972890197; + result_dict["cycle_cost"] = 60.89798090322589; + + // Check expected vs actual results + check_result_vals(sco2, result_dict); +} + +// Design method 1 (hit target eta by varying total UA) +NAMESPACE_TEST(sco2_design_tests, SCO2Design, htrbp_des1) +{ + // Design method 2, vary total UA to hit target eta, + // AND vary bypass frac to hit target outlet temp + + // Get default parameters + ssc_data_t data = get_default_sco2_pars(); + + // Set test specific values + ssc_data_set_number(data, "cycle_config", 3); + ssc_data_set_number(data, "design_method", 1); // Hit target eta, vary total UA + ssc_data_set_number(data, "eta_thermal_des", 0.33); // Target eta + ssc_data_set_number(data, "is_bypass_ok", 1); + ssc_data_set_number(data, "T_bypass_target", 400); + ssc_data_set_number(data, "T_target_is_HTF", 1); + ssc_data_set_number(data, "deltaT_bypass", 0); + ssc_data_set_number(data, "set_HTF_mdot", 0); + + // Call cmod + CmodUnderTest sco2 = CmodUnderTest("sco2_csp_system", data); + int errors = sco2.RunModule(); + + // Check for errors + ASSERT_TRUE(errors == 0); + + // Define known results + std::unordered_map result_dict; + result_dict["eta_thermal_calc"] = 0.3377427765707046; + result_dict["T_htf_cold_des"] = 400.00000000000114; + result_dict["cycle_cost"] = 92.73527550752384; + + // Check expected vs actual results + check_result_vals(sco2, result_dict); +} + + +// Fail tests +NAMESPACE_TEST(sco2_design_tests, SCO2Design, tsf_des1_fail) +{ + // This test purposefully fails, by trying to run TSF with design method 1 + + // Get default parameters + ssc_data_t data = get_default_sco2_pars(); + + // Assign TSF specific pars + ssc_data_set_number(data, "cycle_config", 4); + ssc_data_set_number(data, "is_turbine_split_ok", 1); + ssc_data_set_number(data, "eta_isen_t2", 0.90); + + // Set design method 1 (which TSF does not support) + ssc_data_set_number(data, "design_method", 1); + + // Run cmod + CmodUnderTest sco2 = CmodUnderTest("sco2_csp_system", data); + int errors = sco2.RunModule(); + + // Expect to have errors + ASSERT_FALSE(errors == 0); +}